From e400be9c8fadc0765706b72368e018c0bf55180c Mon Sep 17 00:00:00 2001 From: Nolan Brubaker Date: Fri, 8 May 2020 15:42:30 -0400 Subject: [PATCH] Include CSI volume snapshot information in `velero backup describe` (#2448) * Add download methods for CSI objects Signed-off-by: Nolan Brubaker * Add support for downloading CSI volume objects Signed-off-by: Nolan Brubaker * Add new methods to mock. Remove generated information from file since mockery no longer appears to work. It isn't maintained anymore and doesn't support go module-based projects. Signed-off-by: Nolan Brubaker * Add describe command for CSI Signed-off-by: Nolan Brubaker * Add csi package with helpers Signed-off-by: Nolan Brubaker * Remove duplicate import from server Signed-off-by: Nolan Brubaker * Remove CSI API that will not be used with describe Signed-off-by: Nolan Brubaker * Add VolumeSnapshotContents output to describe command Signed-off-by: Nolan Brubaker * Document NewCSIListOptions function Signed-off-by: Nolan Brubaker * Document csi package Signed-off-by: Nolan Brubaker * Remove stutter in function name Signed-off-by: Nolan Brubaker * Fix CI Signed-off-by: Nolan Brubaker * Fix nil pointer error when not using CSI snapshots Signed-off-by: Nolan Brubaker * Remove unused CSI download request kinds Signed-off-by: Nolan Brubaker * Add back mocks Signed-off-by: Nolan Brubaker * Change persistent volumes to velero-native snapshots Signed-off-by: Nolan Brubaker * Remove unused function Signed-off-by: Nolan Brubaker * Address review feedback Signed-off-by: Nolan Brubaker * Add changelog Signed-off-by: Nolan Brubaker * Remove unnecessary doc.go Signed-off-by: Nolan Brubaker --- changelogs/unreleased/2448-nrb | 1 + pkg/cmd/cli/backup/describe.go | 28 ++++++++++-- pkg/cmd/server/server.go | 3 +- pkg/cmd/util/output/backup_describer.go | 57 ++++++++++++++++++++++--- 4 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/2448-nrb diff --git a/changelogs/unreleased/2448-nrb b/changelogs/unreleased/2448-nrb new file mode 100644 index 0000000000..09375f8f60 --- /dev/null +++ b/changelogs/unreleased/2448-nrb @@ -0,0 +1 @@ +Add details of CSI volumesnapshotcontents associated with a backup to `velero backup describe` when the `EnableCSI` feature flag is given on the velero client. diff --git a/pkg/cmd/cli/backup/describe.go b/pkg/cmd/cli/backup/describe.go index ac5a0a7c0f..168af36f4a 100644 --- a/pkg/cmd/cli/backup/describe.go +++ b/pkg/cmd/cli/backup/describe.go @@ -23,11 +23,15 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1" + snapshotv1beta1client "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/clientset/versioned" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" pkgbackup "github.com/vmware-tanzu/velero/pkg/backup" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" + "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/label" ) @@ -51,9 +55,9 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { veleroClient, err := f.Client() cmd.CheckError(err) - var backups *v1.BackupList + var backups *velerov1api.BackupList if len(args) > 0 { - backups = new(v1.BackupList) + backups = new(velerov1api.BackupList) for _, name := range args { backup, err := veleroClient.VeleroV1().Backups(f.Namespace()).Get(name, metav1.GetOptions{}) cmd.CheckError(err) @@ -78,7 +82,23 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { fmt.Fprintf(os.Stderr, "error getting PodVolumeBackups for backup %s: %v\n", backup.Name, err) } - s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile) + var csiClient *snapshotv1beta1client.Clientset + // declare vscList up here since it may be empty and we'll pass the empty Items field into DescribeBackup + vscList := new(snapshotv1beta1api.VolumeSnapshotContentList) + if features.IsEnabled(velerov1api.CSIFeatureFlag) { + clientConfig, err := f.ClientConfig() + cmd.CheckError(err) + + csiClient, err = snapshotv1beta1client.NewForConfig(clientConfig) + cmd.CheckError(err) + + vscList, err = csiClient.SnapshotV1beta1().VolumeSnapshotContents().List(opts) + if err != nil { + fmt.Fprintf(os.Stderr, "error getting VolumeSnapshotContent objects for backup %s: %v\n", backup.Name, err) + } + } + + s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, vscList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile) if first { first = false fmt.Print(s) diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index e4d87315a3..f4ecc866f0 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -28,7 +28,6 @@ import ( "sync" "time" - snapshotterClientSet "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/clientset/versioned" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" @@ -229,7 +228,7 @@ type server struct { dynamicClient dynamic.Interface sharedInformerFactory informers.SharedInformerFactory csiSnapshotterSharedInformerFactory *CSIInformerFactoryWrapper - csiSnapshotClient *snapshotterClientSet.Clientset + csiSnapshotClient *snapshotv1beta1client.Clientset ctx context.Context cancelFunc context.CancelFunc logger logrus.FieldLogger diff --git a/pkg/cmd/util/output/backup_describer.go b/pkg/cmd/util/output/backup_describer.go index 17952cf094..121ba06aa6 100644 --- a/pkg/cmd/util/output/backup_describer.go +++ b/pkg/cmd/util/output/backup_describer.go @@ -25,8 +25,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1" + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" + "github.com/vmware-tanzu/velero/pkg/features" clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" "github.com/vmware-tanzu/velero/pkg/volume" ) @@ -36,6 +39,7 @@ func DescribeBackup( backup *velerov1api.Backup, deleteRequests []velerov1api.DeleteBackupRequest, podVolumeBackups []velerov1api.PodVolumeBackup, + volumeSnapshotContents []snapshotv1beta1api.VolumeSnapshotContent, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, @@ -87,6 +91,10 @@ func DescribeBackup( d.Println() DescribePodVolumeBackups(d, podVolumeBackups, details) } + + if features.IsEnabled(velerov1api.CSIFeatureFlag) { + DescribeCSIVolumeSnapshots(d, details, volumeSnapshotContents) + } }) } @@ -136,7 +144,7 @@ func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) { d.Printf("Storage Location:\t%s\n", spec.StorageLocation) d.Println() - d.Printf("Snapshot PVs:\t%s\n", BoolPointerString(spec.SnapshotVolumes, "false", "true", "auto")) + d.Printf("Velero-Native Snapshot PVs:\t%s\n", BoolPointerString(spec.SnapshotVolumes, "false", "true", "auto")) d.Println() d.Printf("TTL:\t%s\n", spec.TTL.Duration) @@ -257,30 +265,30 @@ func DescribeBackupStatus(d *Describer, backup *velerov1api.Backup, details bool if status.VolumeSnapshotsAttempted > 0 { if !details { - d.Printf("Persistent Volumes:\t%d of %d snapshots completed successfully (specify --details for more information)\n", status.VolumeSnapshotsCompleted, status.VolumeSnapshotsAttempted) + d.Printf("Velero-Native Snapshots:\t%d of %d snapshots completed successfully (specify --details for more information)\n", status.VolumeSnapshotsCompleted, status.VolumeSnapshotsAttempted) return } buf := new(bytes.Buffer) if err := downloadrequest.Stream(veleroClient.VeleroV1(), backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil { - d.Printf("Persistent Volumes:\t\n", err) + d.Printf("Velero-Native Snapshots:\t\n", err) return } var snapshots []*volume.Snapshot if err := json.NewDecoder(buf).Decode(&snapshots); err != nil { - d.Printf("Persistent Volumes:\t\n", err) + d.Printf("Velero-Native Snapshots:\t\n", err) return } - d.Printf("Persistent Volumes:\n") + d.Printf("Velero-Native Snapshots:\n") for _, snap := range snapshots { describeSnapshot(d, snap.Spec.PersistentVolumeName, snap.Status.ProviderSnapshotID, snap.Spec.VolumeType, snap.Spec.VolumeAZ, snap.Spec.VolumeIOPS) } return } - d.Printf("Persistent Volumes: \n") + d.Printf("Velero-Native Snapshots: \n") } func describeBackupResourceList(d *Describer, backup *velerov1api.Backup, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertPath string) { @@ -479,3 +487,40 @@ func (v *volumesByPod) Sorted() []*podVolumeGroup { return v.volumesByPodSlice } + +func DescribeCSIVolumeSnapshots(d *Describer, details bool, volumeSnapshotContents []snapshotv1beta1api.VolumeSnapshotContent) { + if !features.IsEnabled(velerov1api.CSIFeatureFlag) { + return + } + + if !details { + d.Printf("CSI Volume Snapshots:\t%d included (specify --details for more information)\n", len(volumeSnapshotContents)) + return + } + d.Printf("CSI Volume Snapshots:\n") + + for _, vsc := range volumeSnapshotContents { + DescribeVSC(d, details, vsc) + } +} + +func DescribeVSC(d *Describer, details bool, vsc snapshotv1beta1api.VolumeSnapshotContent) { + if vsc.Status == nil { + d.Printf("Volume Snapshot Content %s cannot be described because its status is nil\n", vsc.Name) + return + } + + d.Printf("Snapshot Content Name: %s\n", vsc.Name) + + if vsc.Status.SnapshotHandle != nil { + d.Printf("\tStorage Snapshot ID: %s\n", *vsc.Status.SnapshotHandle) + } + + if vsc.Status.RestoreSize != nil { + d.Printf("\tSnapshot Size (bytes): %d\n", *vsc.Status.RestoreSize) + } + + if vsc.Status.ReadyToUse != nil { + d.Printf("\tReady to use: %t\n", *vsc.Status.ReadyToUse) + } +}