diff --git a/pkg/apis/velero/v1/download_request_types.go b/pkg/apis/velero/v1/download_request_types.go index 7c7be5337..07f44b38d 100644 --- a/pkg/apis/velero/v1/download_request_types.go +++ b/pkg/apis/velero/v1/download_request_types.go @@ -25,7 +25,7 @@ type DownloadRequestSpec struct { } // DownloadTargetKind represents what type of file to download. -// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;BackupResults;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents +// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;BackupResults;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents;BackupVolumeInfos type DownloadTargetKind string const ( @@ -41,6 +41,7 @@ const ( DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations" DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots" DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents" + DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos" ) // DownloadTarget is the specification for what kind of file to download, and the name of the diff --git a/pkg/cmd/util/output/backup_describer.go b/pkg/cmd/util/output/backup_describer.go index 039f9d653..ca9146f14 100644 --- a/pkg/cmd/util/output/backup_describer.go +++ b/pkg/cmd/util/output/backup_describer.go @@ -104,22 +104,12 @@ func DescribeBackup( DescribeBackupSpec(d, backup.Spec) d.Println() - DescribeBackupStatus(ctx, kbClient, d, backup, details, insecureSkipTLSVerify, caCertFile) + DescribeBackupStatus(ctx, kbClient, d, backup, details, insecureSkipTLSVerify, caCertFile, podVolumeBackups) if len(deleteRequests) > 0 { d.Println() DescribeDeleteBackupRequests(d, deleteRequests) } - - if features.IsEnabled(velerov1api.CSIFeatureFlag) { - d.Println() - DescribeCSIVolumeSnapshots(d, details, volumeSnapshotContents) - } - - if len(podVolumeBackups) > 0 { - d.Println() - DescribePodVolumeBackups(d, podVolumeBackups, details) - } }) } @@ -310,7 +300,8 @@ func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) { } // DescribeBackupStatus describes a backup status in human-readable format. -func DescribeBackupStatus(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, details bool, insecureSkipTLSVerify bool, caCertPath string) { +func DescribeBackupStatus(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, details bool, + insecureSkipTLSVerify bool, caCertPath string, podVolumeBackups []velerov1api.PodVolumeBackup) { status := backup.Status // Status.Version has been deprecated, use Status.FormatVersion @@ -355,32 +346,8 @@ func DescribeBackupStatus(ctx context.Context, kbClient kbclient.Client, d *Desc d.Println() } - if status.VolumeSnapshotsAttempted > 0 { - if !details { - 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(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil { - d.Printf("Velero-Native Snapshots:\t\n", err) - return - } - - var snapshots []*volume.Snapshot - if err := json.NewDecoder(buf).Decode(&snapshots); err != nil { - d.Printf("Velero-Native Snapshots:\t\n", err) - return - } - - 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("Velero-Native Snapshots: \n") + describeBackupVolumes(ctx, kbClient, d, backup, details, insecureSkipTLSVerify, caCertPath, podVolumeBackups) + d.Println() } func describeBackupItemOperations(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, details bool, insecureSkipTLSVerify bool, caCertPath string) { @@ -446,16 +413,120 @@ func describeBackupResourceList(ctx context.Context, kbClient kbclient.Client, d } } -func describeSnapshot(d *Describer, pvName, snapshotID, volumeType, volumeAZ string, iops *int64) { - d.Printf("\t%s:\n", pvName) - d.Printf("\t\tSnapshot ID:\t%s\n", snapshotID) - d.Printf("\t\tType:\t%s\n", volumeType) - d.Printf("\t\tAvailability Zone:\t%s\n", volumeAZ) - iopsString := "" - if iops != nil { - iopsString = fmt.Sprintf("%d", *iops) +func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, details bool, + insecureSkipTLSVerify bool, caCertPath string, podVolumeBackupCRs []velerov1api.PodVolumeBackup) { + d.Println("Backup Volumes:") + + buf := new(bytes.Buffer) + if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeInfos, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil { + d.Printf("\t\n", err) + return + } + + var volumeInfos *volume.VolumeInfos + if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil { + d.Printf("\t\n", err) + return + } + + nativeSnapshots := []*volume.VolumeInfo{} + csiSnapshots := []*volume.VolumeInfo{} + for _, info := range volumeInfos.VolumeInfos { + switch info.BackupMethod { + case volume.NativeSnapshot: + nativeSnapshots = append(nativeSnapshots, &info) + case volume.CSISnapshot: + csiSnapshots = append(csiSnapshots, &info) + } + } + + describeNativeSnapshots(d, details, nativeSnapshots) + d.Println() + + describeCSISnapshots(d, details, csiSnapshots) + d.Println() + + describePodVolumeBackups(d, details, podVolumeBackupCRs) + d.Println() +} + +func describeNativeSnapshots(d *Describer, details bool, infos []*volume.VolumeInfo) { + if len(infos) == 0 { + d.Printf("\tVelero-Native Snapshots: \n") + return + } + + d.Println("\tVelero-Native Snapshots:") + for _, info := range infos { + describNativeSnapshot(d, details, info) + } +} + +func describNativeSnapshot(d *Describer, details bool, info *volume.VolumeInfo) { + if details { + d.Printf("\t\t%s:\n", info.PVName) + d.Printf("\t\t\tSnapshot ID:\t%s\n", info.NativeSnapshotInfo.SnapshotHandle) + d.Printf("\t\t\tType:\t%s\n", info.NativeSnapshotInfo.VolumeType) + d.Printf("\t\t\tAvailability Zone:\t%s\n", info.NativeSnapshotInfo.VolumeAZ) + d.Printf("\t\t\tIOPS:\t%s\n", info.NativeSnapshotInfo.IOPS) + } else { + d.Printf("\t\t%s: specify --details for more information\n", info.PVName) + } +} + +func describeCSISnapshots(d *Describer, details bool, infos []*volume.VolumeInfo) { + if !features.IsEnabled(velerov1api.CSIFeatureFlag) { + return + } + + if len(infos) == 0 { + d.Printf("\tCSI Snapshots: \n") + return + } + + d.Println("\tCSI Snapshots:") + for _, info := range infos { + describeCSISnapshot(d, details, info) + } +} + +func describeCSISnapshot(d *Describer, details bool, info *volume.VolumeInfo) { + d.Printf("\t\t%s:\n", info.PVCName) + d.Printf("\t\t\tOperation ID: %s\n", info.OperationID) + + describeLocalSnapshot(d, details, info) + describeDataMovement(d, details, info) +} + +func describeLocalSnapshot(d *Describer, details bool, info *volume.VolumeInfo) { + if !info.PreserveLocalSnapshot { + return + } + + if details { + d.Printf("\t\t\tSnapshot:\n") + d.Printf("\t\t\t\tSnapshot Content Name: %s\n", info.CSISnapshotInfo.VSCName) + d.Printf("\t\t\t\tStorage Snapshot ID: %s\n", info.CSISnapshotInfo.SnapshotHandle) + d.Printf("\t\t\t\tSnapshot Size (bytes): %d\n", info.CSISnapshotInfo.Size) + d.Printf("\t\t\t\tCSI Driver: %s\n", info.CSISnapshotInfo.Driver) + } else { + d.Printf("\t\t\tSnapshot: %s\n", "specify --details for more information") + } +} + +func describeDataMovement(d *Describer, details bool, info *volume.VolumeInfo) { + if !info.SnapshotDataMoved { + return + } + + if details { + d.Printf("\t\t\tData Movement:\n") + d.Printf("\t\t\t\tData Mover: %s\n", info.SnapshotDataMovementInfo.DataMover) + d.Printf("\t\t\t\tUploader Type: %s\n", info.SnapshotDataMovementInfo.UploaderType) + d.Printf("\t\t\t\tRepository Snapshot ID: %s\n", info.SnapshotDataMovementInfo.SnapshotHandle) + } else { + d.Printf("\t\t\tData Movement: %s\n", "specify --details for more information") } - d.Printf("\t\tIOPS:\t%s\n", iopsString) } func describeBackupItemOperation(d *Describer, operation *itemoperation.BackupOperation) { @@ -528,25 +599,25 @@ func failedDeletionCount(requests []velerov1api.DeleteBackupRequest) int { return count } -// DescribePodVolumeBackups describes pod volume backups in human-readable format. -func DescribePodVolumeBackups(d *Describer, backups []velerov1api.PodVolumeBackup, details bool) { +// describePodVolumeBackups describes pod volume backups in human-readable format. +func describePodVolumeBackups(d *Describer, details bool, podVolumeBackups []velerov1api.PodVolumeBackup) { // Get the type of pod volume uploader. Since the uploader only comes from a single source, we can // take the uploader type from the first element of the array. var uploaderType string - if len(backups) > 0 { - uploaderType = backups[0].Spec.UploaderType + if len(podVolumeBackups) > 0 { + uploaderType = podVolumeBackups[0].Spec.UploaderType } else { return } if details { - d.Printf("%s Backups:\n", uploaderType) + d.Printf("\tPod Volume Backups - %s:\n", uploaderType) } else { - d.Printf("%s Backups (specify --details for more information):\n", uploaderType) + d.Printf("\tPod Volume Backups - %s (specify --details for more information):\n", uploaderType) } // separate backups by phase (combining and New into a single group) - backupsByPhase := groupByPhase(backups) + backupsByPhase := groupByPhase(podVolumeBackups) // go through phases in a specific order for _, phase := range []string{ @@ -561,7 +632,7 @@ func DescribePodVolumeBackups(d *Describer, backups []velerov1api.PodVolumeBacku // if we're not printing details, just report the phase and count if !details { - d.Printf("\t%s:\t%d\n", phase, len(backupsByPhase[phase])) + d.Printf("\t\t%s:\t%d\n", phase, len(backupsByPhase[phase])) continue } @@ -572,12 +643,12 @@ func DescribePodVolumeBackups(d *Describer, backups []velerov1api.PodVolumeBacku backupsByPod.Add(backup.Spec.Pod.Namespace, backup.Spec.Pod.Name, backup.Spec.Volume, phase, backup.Status.Progress) } - d.Printf("\t%s:\n", phase) + d.Printf("\t\t%s:\n", phase) for _, backupGroup := range backupsByPod.Sorted() { sort.Strings(backupGroup.volumes) // print volumes backed up for this pod - d.Printf("\t\t%s: %s\n", backupGroup.label, strings.Join(backupGroup.volumes, ", ")) + d.Printf("\t\t\t%s: %s\n", backupGroup.label, strings.Join(backupGroup.volumes, ", ")) } } } @@ -650,49 +721,6 @@ func (v *volumesByPod) Sorted() []*podVolumeGroup { return v.volumesByPodSlice } -func DescribeCSIVolumeSnapshots(d *Describer, details bool, volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent) { - if !features.IsEnabled(velerov1api.CSIFeatureFlag) { - return - } - - if len(volumeSnapshotContents) == 0 { - d.Printf("CSI Volume Snapshots: \n") - 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 snapshotv1api.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) - } -} - // DescribeBackupResults describes errors and warnings in human-readable format. func DescribeBackupResults(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) { if backup.Status.Warnings == 0 && backup.Status.Errors == 0 { diff --git a/pkg/cmd/util/output/backup_describer_test.go b/pkg/cmd/util/output/backup_describer_test.go index b0f78a917..533ceb655 100644 --- a/pkg/cmd/util/output/backup_describer_test.go +++ b/pkg/cmd/util/output/backup_describer_test.go @@ -6,16 +6,16 @@ import ( "text/tabwriter" "time" + "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/itemoperation" + "github.com/vmware-tanzu/velero/pkg/volume" "github.com/stretchr/testify/require" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" "github.com/vmware-tanzu/velero/pkg/builder" - "github.com/vmware-tanzu/velero/pkg/features" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" ) @@ -291,22 +291,193 @@ OrderedResources: } } -func TestDescribeSnapshot(t *testing.T) { - d := &Describer{ - Prefix: "", - out: &tabwriter.Writer{}, - buf: &bytes.Buffer{}, +func TestDescribeNativeSnapshots(t *testing.T) { + testcases := []struct { + name string + volumeInfo []*volume.VolumeInfo + inputDetails bool + expect string + }{ + { + name: "no details", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.NativeSnapshot, + PVName: "pv-1", + NativeSnapshotInfo: volume.NativeSnapshotInfo{ + SnapshotHandle: "snapshot-1", + VolumeType: "ebs", + VolumeAZ: "us-east-2", + IOPS: "1000 mbps", + }, + }, + }, + expect: ` Velero-Native Snapshots: + pv-1: specify --details for more information +`, + }, + { + name: "details", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.NativeSnapshot, + PVName: "pv-1", + NativeSnapshotInfo: volume.NativeSnapshotInfo{ + SnapshotHandle: "snapshot-1", + VolumeType: "ebs", + VolumeAZ: "us-east-2", + IOPS: "1000 mbps", + }, + }, + }, + inputDetails: true, + expect: ` Velero-Native Snapshots: + pv-1: + Snapshot ID: snapshot-1 + Type: ebs + Availability Zone: us-east-2 + IOPS: 1000 mbps +`, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(tt *testing.T) { + d := &Describer{ + Prefix: "", + out: &tabwriter.Writer{}, + buf: &bytes.Buffer{}, + } + d.out.Init(d.buf, 0, 8, 2, ' ', 0) + describeNativeSnapshots(d, tc.inputDetails, tc.volumeInfo) + d.out.Flush() + assert.Equal(t, tc.expect, d.buf.String()) + }) + } +} + +func TestCSISnapshots(t *testing.T) { + features.Enable(velerov1api.CSIFeatureFlag) + defer func() { + features.Disable(velerov1api.CSIFeatureFlag) + }() + + testcases := []struct { + name string + volumeInfo []*volume.VolumeInfo + inputDetails bool + expect string + }{ + { + name: "no details, local snapshot", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-1", + PreserveLocalSnapshot: true, + OperationID: "fake-operation-1", + CSISnapshotInfo: volume.CSISnapshotInfo{ + SnapshotHandle: "snapshot-1", + Size: 1024, + Driver: "fake-driver", + VSCName: "vsc-1", + }, + }, + }, + expect: ` CSI Snapshots: + pvc-1: + Operation ID: fake-operation-1 + Snapshot: specify --details for more information +`, + }, + { + name: "details, local snapshot", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-2", + PreserveLocalSnapshot: true, + OperationID: "fake-operation-2", + CSISnapshotInfo: volume.CSISnapshotInfo{ + SnapshotHandle: "snapshot-2", + Size: 1024, + Driver: "fake-driver", + VSCName: "vsc-2", + }, + }, + }, + inputDetails: true, + expect: ` CSI Snapshots: + pvc-2: + Operation ID: fake-operation-2 + Snapshot: + Snapshot Content Name: vsc-2 + Storage Snapshot ID: snapshot-2 + Snapshot Size (bytes): 1024 + CSI Driver: fake-driver +`, + }, + { + name: "no details, data movement", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-3", + SnapshotDataMoved: true, + OperationID: "fake-operation-3", + SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{ + DataMover: "velero", + UploaderType: "fake-uploader", + SnapshotHandle: "fake-repo-id-3", + }, + }, + }, + expect: ` CSI Snapshots: + pvc-3: + Operation ID: fake-operation-3 + Data Movement: specify --details for more information +`, + }, + { + name: "details, data movement", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-4", + SnapshotDataMoved: true, + OperationID: "fake-operation-4", + SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{ + DataMover: "velero", + UploaderType: "fake-uploader", + SnapshotHandle: "fake-repo-id-4", + }, + }, + }, + inputDetails: true, + expect: ` CSI Snapshots: + pvc-4: + Operation ID: fake-operation-4 + Data Movement: + Data Mover: velero + Uploader Type: fake-uploader + Repository Snapshot ID: fake-repo-id-4 +`, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(tt *testing.T) { + d := &Describer{ + Prefix: "", + out: &tabwriter.Writer{}, + buf: &bytes.Buffer{}, + } + d.out.Init(d.buf, 0, 8, 2, ' ', 0) + describeCSISnapshots(d, tc.inputDetails, tc.volumeInfo) + d.out.Flush() + assert.Equal(t, tc.expect, d.buf.String()) + }) } - d.out.Init(d.buf, 0, 8, 2, ' ', 0) - describeSnapshot(d, "pv-1", "snapshot-1", "ebs", "us-east-2", nil) - expect1 := ` pv-1: - Snapshot ID: snapshot-1 - Type: ebs - Availability Zone: us-east-2 - IOPS: -` - d.out.Flush() - assert.Equal(t, expect1, d.buf.String()) } func TestDescribePodVolumeBackups(t *testing.T) { @@ -343,18 +514,18 @@ func TestDescribePodVolumeBackups(t *testing.T) { name: "2 completed pvbs no details", inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2}, inputDetails: false, - expect: `kopia Backups (specify --details for more information): - Completed: 2 + expect: ` Pod Volume Backups - kopia (specify --details for more information): + Completed: 2 `, }, { name: "2 completed pvbs with details", inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2}, inputDetails: true, - expect: `kopia Backups: - Completed: - pod-ns-1/pod-1: vol-1 - pod-ns-1/pod-2: vol-2 + expect: ` Pod Volume Backups - kopia: + Completed: + pod-ns-1/pod-1: vol-1 + pod-ns-1/pod-2: vol-2 `, }, } @@ -367,68 +538,7 @@ func TestDescribePodVolumeBackups(t *testing.T) { buf: &bytes.Buffer{}, } d.out.Init(d.buf, 0, 8, 2, ' ', 0) - DescribePodVolumeBackups(d, tc.inputPVBList, tc.inputDetails) - d.out.Flush() - assert.Equal(tt, tc.expect, d.buf.String()) - }) - } -} - -func TestDescribeCSIVolumeSnapshots(t *testing.T) { - features.Enable(velerov1api.CSIFeatureFlag) - defer func() { - features.Disable(velerov1api.CSIFeatureFlag) - }() - handle := "handle-1" - readyToUse := true - size := int64(1024) - vsc1 := builder.ForVolumeSnapshotContent("vsc-1"). - Status(&snapshotv1api.VolumeSnapshotContentStatus{ - SnapshotHandle: &handle, - ReadyToUse: &readyToUse, - RestoreSize: &size, - }).Result() - testcases := []struct { - name string - inputVSCList []snapshotv1api.VolumeSnapshotContent - inputDetails bool - expect string - }{ - { - name: "empty list", - inputVSCList: []snapshotv1api.VolumeSnapshotContent{}, - inputDetails: false, - expect: `CSI Volume Snapshots: -`, - }, - { - name: "1 vsc no details", - inputVSCList: []snapshotv1api.VolumeSnapshotContent{*vsc1}, - inputDetails: false, - expect: `CSI Volume Snapshots: 1 included (specify --details for more information) -`, - }, - { - name: "1 vsc with details", - inputVSCList: []snapshotv1api.VolumeSnapshotContent{*vsc1}, - inputDetails: true, - expect: `CSI Volume Snapshots: -Snapshot Content Name: vsc-1 - Storage Snapshot ID: handle-1 - Snapshot Size (bytes): 1024 - Ready to use: true -`, - }, - } - for _, tc := range testcases { - t.Run(tc.name, func(tt *testing.T) { - d := &Describer{ - Prefix: "", - out: &tabwriter.Writer{}, - buf: &bytes.Buffer{}, - } - d.out.Init(d.buf, 0, 8, 2, ' ', 0) - DescribeCSIVolumeSnapshots(d, tc.inputDetails, tc.inputVSCList) + describePodVolumeBackups(d, tc.inputDetails, tc.inputPVBList) d.out.Flush() assert.Equal(tt, tc.expect, d.buf.String()) }) diff --git a/pkg/cmd/util/output/backup_structured_describer.go b/pkg/cmd/util/output/backup_structured_describer.go index 4a69fc057..fe251ad72 100644 --- a/pkg/cmd/util/output/backup_structured_describer.go +++ b/pkg/cmd/util/output/backup_structured_describer.go @@ -68,19 +68,11 @@ func DescribeBackupInSF( DescribeBackupSpecInSF(d, backup.Spec) - DescribeBackupStatusInSF(ctx, kbClient, d, backup, details, insecureSkipTLSVerify, caCertFile) + DescribeBackupStatusInSF(ctx, kbClient, d, backup, details, insecureSkipTLSVerify, caCertFile, podVolumeBackups) if len(deleteRequests) > 0 { DescribeDeleteBackupRequestsInSF(d, deleteRequests) } - - if features.IsEnabled(velerov1api.CSIFeatureFlag) { - DescribeCSIVolumeSnapshotsInSF(d, details, volumeSnapshotContents) - } - - if len(podVolumeBackups) > 0 { - DescribePodVolumeBackupsInSF(d, podVolumeBackups, details) - } }, outputFormat) } @@ -234,7 +226,8 @@ func DescribeBackupSpecInSF(d *StructuredDescriber, spec velerov1api.BackupSpec) } // DescribeBackupStatusInSF describes a backup status in structured format. -func DescribeBackupStatusInSF(ctx context.Context, kbClient kbclient.Client, d *StructuredDescriber, backup *velerov1api.Backup, details bool, insecureSkipTLSVerify bool, caCertPath string) { +func DescribeBackupStatusInSF(ctx context.Context, kbClient kbclient.Client, d *StructuredDescriber, backup *velerov1api.Backup, details bool, + insecureSkipTLSVerify bool, caCertPath string, podVolumeBackups []velerov1api.PodVolumeBackup) { status := backup.Status backupStatusInfo := make(map[string]interface{}) @@ -274,35 +267,9 @@ func DescribeBackupStatusInSF(ctx context.Context, kbClient kbclient.Client, d * describeBackupResourceListInSF(ctx, kbClient, backupStatusInfo, backup, insecureSkipTLSVerify, caCertPath) } - // In consideration of decoding structured output conveniently, the three separate fields were created here - // the field of "veleroNativeSnapshots" displays the brief snapshots info - // the field of "errorGettingSnapshots" displays the error message if it fails to get snapshot info - // the field of "veleroNativeSnapshotsDetail" displays the detailed snapshots info - if status.VolumeSnapshotsAttempted > 0 { - if !details { - backupStatusInfo["veleroNativeSnapshots"] = fmt.Sprintf("%d of %d snapshots completed successfully", status.VolumeSnapshotsCompleted, status.VolumeSnapshotsAttempted) - return - } - - buf := new(bytes.Buffer) - if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil { - backupStatusInfo["errorGettingSnapshots"] = fmt.Sprintf("", err) - return - } - - var snapshots []*volume.Snapshot - if err := json.NewDecoder(buf).Decode(&snapshots); err != nil { - backupStatusInfo["errorGettingSnapshots"] = fmt.Sprintf("", err) - return - } - - snapshotDetails := make(map[string]interface{}) - for _, snap := range snapshots { - describeSnapshotInSF(snap.Spec.PersistentVolumeName, snap.Status.ProviderSnapshotID, snap.Spec.VolumeType, snap.Spec.VolumeAZ, snap.Spec.VolumeIOPS, snapshotDetails) - } - backupStatusInfo["veleroNativeSnapshotsDetail"] = snapshotDetails - return - } + backupVolumes := make(map[string]interface{}) + describeBackupVolumesInSF(ctx, kbClient, backup, details, insecureSkipTLSVerify, caCertPath, podVolumeBackups, backupVolumes) + backupStatusInfo["backupVolumes"] = backupVolumes } func describeBackupResourceListInSF(ctx context.Context, kbClient kbclient.Client, backupStatusInfo map[string]interface{}, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) { @@ -332,18 +299,129 @@ func describeBackupResourceListInSF(ctx context.Context, kbClient kbclient.Clien backupStatusInfo["resourceList"] = resourceList } -func describeSnapshotInSF(pvName, snapshotID, volumeType, volumeAZ string, iops *int64, snapshotDetails map[string]interface{}) { - snapshotInfo := make(map[string]string) - iopsString := "" - if iops != nil { - iopsString = fmt.Sprintf("%d", *iops) +func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, details bool, + insecureSkipTLSVerify bool, caCertPath string, podVolumeBackupCRs []velerov1api.PodVolumeBackup, backupVolumes map[string]interface{}) { + + buf := new(bytes.Buffer) + if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeInfos, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil { + backupVolumes["errorGetBackupVolumeInfo"] = fmt.Sprintf("error getting backup volume info: %v", err) + return } - snapshotInfo["snapshotID"] = snapshotID - snapshotInfo["type"] = volumeType - snapshotInfo["availabilityZone"] = volumeAZ - snapshotInfo["IOPS"] = iopsString - snapshotDetails[pvName] = snapshotInfo + var volumeInfos *volume.VolumeInfos + if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil { + backupVolumes["errorReadBackupVolumeInfo"] = fmt.Sprintf("error reading backup volume info: %v", err) + return + } + + nativeSnapshots := []*volume.VolumeInfo{} + csiSnapshots := []*volume.VolumeInfo{} + for _, info := range volumeInfos.VolumeInfos { + switch info.BackupMethod { + case volume.NativeSnapshot: + nativeSnapshots = append(nativeSnapshots, &info) + case volume.CSISnapshot: + csiSnapshots = append(csiSnapshots, &info) + } + } + + describeNativeSnapshotsInSF(details, nativeSnapshots, backupVolumes) + + csiSnapshotDetails := make(map[string]interface{}) + describeCSISnapshotsInSF(details, csiSnapshots, csiSnapshotDetails) + backupVolumes["csiSnapshots"] = csiSnapshotDetails + + describePodVolumeBackupsInSF(podVolumeBackupCRs, details, backupVolumes) +} + +func describeNativeSnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVolumes map[string]interface{}) { + if len(infos) == 0 { + backupVolumes["nativeSnapshots"] = "" + } + + snapshotDetails := make(map[string]interface{}) + for _, info := range infos { + describNativeSnapshotInSF(details, info, snapshotDetails) + } + backupVolumes["nativeSnapshots"] = snapshotDetails +} + +func describNativeSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetails map[string]interface{}) { + + if details { + snapshotInfo := make(map[string]string) + snapshotInfo["snapshotID"] = info.NativeSnapshotInfo.SnapshotHandle + snapshotInfo["type"] = info.NativeSnapshotInfo.VolumeType + snapshotInfo["availabilityZone"] = info.NativeSnapshotInfo.VolumeAZ + snapshotInfo["IOPS"] = info.NativeSnapshotInfo.IOPS + + snapshotDetails[info.PVName] = snapshotInfo + } else { + snapshotDetails[info.PVName] = "specify --details for more information" + } +} + +func describeCSISnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVolumes map[string]interface{}) { + if !features.IsEnabled(velerov1api.CSIFeatureFlag) { + return + } + + if len(infos) == 0 { + backupVolumes["csiSnapshots"] = "CSI Snapshots: " + return + } + + snapshotDetails := make(map[string]interface{}) + for _, info := range infos { + describeCSISnapshotInSF(details, info, snapshotDetails) + } + backupVolumes["csiSnapshots"] = snapshotDetails +} + +func describeCSISnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetails map[string]interface{}) { + snapshotDetail := make(map[string]interface{}) + snapshotDetail["operationID"] = info.OperationID + + describeLocalSnapshotInSF(details, info, snapshotDetail) + describeDataMovementInSF(details, info, snapshotDetail) + + snapshotDetails[info.PVCName] = snapshotDetail +} + +// describeVSCInSF describes CSI volume snapshot contents in structured format. +func describeLocalSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetail map[string]interface{}) { + if !info.PreserveLocalSnapshot { + return + } + + if details { + localSnapshot := make(map[string]interface{}) + localSnapshot["snapshotContentName"] = info.CSISnapshotInfo.VSCName + localSnapshot["storageSnapshotID"] = info.CSISnapshotInfo.SnapshotHandle + localSnapshot["snapshotSize(bytes)"] = info.CSISnapshotInfo.Size + localSnapshot["csiDriver"] = info.CSISnapshotInfo.Driver + + snapshotDetail["snapshot"] = localSnapshot + } else { + snapshotDetail["snapshot"] = "specify --details for more information" + } +} + +func describeDataMovementInSF(details bool, info *volume.VolumeInfo, snapshotDetail map[string]interface{}) { + if !info.SnapshotDataMoved { + return + } + + if details { + dataMovement := make(map[string]interface{}) + dataMovement["dataMover"] = info.SnapshotDataMovementInfo.DataMover + dataMovement["uploaderType"] = info.SnapshotDataMovementInfo.UploaderType + dataMovement["repositorySnapshotID"] = info.SnapshotDataMovementInfo.SnapshotHandle + + snapshotDetail["dataMovement"] = dataMovement + } else { + snapshotDetail["dataMovement"] = "specify --details for more information" + } } // DescribeDeleteBackupRequestsInSF describes delete backup requests in structured format. @@ -368,9 +446,9 @@ func DescribeDeleteBackupRequestsInSF(d *StructuredDescriber, requests []velerov d.Describe("deletionAttempts", deletionAttempts) } -// DescribePodVolumeBackupsInSF describes pod volume backups in structured format. -func DescribePodVolumeBackupsInSF(d *StructuredDescriber, backups []velerov1api.PodVolumeBackup, details bool) { - PodVolumeBackupsInfo := make(map[string]interface{}) +// describePodVolumeBackupsInSF describes pod volume backups in structured format. +func describePodVolumeBackupsInSF(backups []velerov1api.PodVolumeBackup, details bool, backupVolumes map[string]interface{}) { + podVolumeBackupsInfo := make(map[string]interface{}) // Get the type of pod volume uploader. Since the uploader only comes from a single source, we can // take the uploader type from the first element of the array. var uploaderType string @@ -380,7 +458,7 @@ func DescribePodVolumeBackupsInSF(d *StructuredDescriber, backups []velerov1api. return } // type display the type of pod volume backups - PodVolumeBackupsInfo["type"] = uploaderType + podVolumeBackupsInfo["uploderType"] = uploaderType podVolumeBackupsDetails := make(map[string]interface{}) // separate backups by phase (combining and New into a single group) @@ -415,56 +493,8 @@ func DescribePodVolumeBackupsInSF(d *StructuredDescriber, backups []velerov1api. podVolumeBackupsDetails[phase] = backupsByPods } // Pod Volume Backups Details display the detailed pod volume backups info - PodVolumeBackupsInfo["podVolumeBackupsDetails"] = podVolumeBackupsDetails - d.Describe("podVolumeBackups", PodVolumeBackupsInfo) -} - -// DescribeCSIVolumeSnapshotsInSF describes CSI volume snapshots in structured format. -func DescribeCSIVolumeSnapshotsInSF(d *StructuredDescriber, details bool, volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent) { - CSIVolumeSnapshotsInfo := make(map[string]interface{}) - if !features.IsEnabled(velerov1api.CSIFeatureFlag) { - return - } - - if len(volumeSnapshotContents) == 0 { - return - } - - // In consideration of decoding structured output conveniently, the two separate fields were created here - // the field of 'CSI Volume Snapshots Count' displays the count of CSI Volume Snapshots - // the field of 'CSI Volume Snapshots Details' displays the content of CSI Volume Snapshots - if !details { - CSIVolumeSnapshotsInfo["CSIVolumeSnapshotsCount"] = len(volumeSnapshotContents) - } else { - vscDetails := make(map[string]interface{}) - for _, vsc := range volumeSnapshotContents { - DescribeVSCInSF(details, vsc, vscDetails) - } - CSIVolumeSnapshotsInfo["CSIVolumeSnapshotsDetails"] = vscDetails - } - d.Describe("CSIVolumeSnapshots", CSIVolumeSnapshotsInfo) -} - -// DescribeVSCInSF describes CSI volume snapshot contents in structured format. -func DescribeVSCInSF(details bool, vsc snapshotv1api.VolumeSnapshotContent, vscDetails map[string]interface{}) { - content := make(map[string]interface{}) - if vsc.Status == nil { - vscDetails[vsc.Name] = content - return - } - - if vsc.Status.SnapshotHandle != nil { - content["storageSnapshotID"] = *vsc.Status.SnapshotHandle - } - - if vsc.Status.RestoreSize != nil { - content["snapshotSize(bytes)"] = *vsc.Status.RestoreSize - } - - if vsc.Status.ReadyToUse != nil { - content["readyToUse"] = *vsc.Status.ReadyToUse - } - vscDetails[vsc.Name] = content + podVolumeBackupsInfo["podVolumeBackupsDetails"] = podVolumeBackupsDetails + backupVolumes["podVolumeBackups"] = podVolumeBackupsInfo } // DescribeBackupResultsInSF describes errors and warnings in structured format. diff --git a/pkg/cmd/util/output/backup_structured_describer_test.go b/pkg/cmd/util/output/backup_structured_describer_test.go index 2a0247bf7..9bfce946e 100644 --- a/pkg/cmd/util/output/backup_structured_describer_test.go +++ b/pkg/cmd/util/output/backup_structured_describer_test.go @@ -11,13 +11,12 @@ import ( "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/util/results" + "github.com/vmware-tanzu/velero/pkg/volume" "github.com/stretchr/testify/assert" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" - - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" ) func TestDescribeBackupInSF(t *testing.T) { @@ -253,72 +252,200 @@ func TestDescribePodVolumeBackupsInSF(t *testing.T) { {"pod-ns-1/pod-2": "vol-2"}, }, }, - "type": "kopia", + "uploderType": "kopia", }, }, }, } for _, tc := range testcases { t.Run(tc.name, func(tt *testing.T) { - sd := &StructuredDescriber{ - output: make(map[string]interface{}), - format: "", - } - DescribePodVolumeBackupsInSF(sd, tc.inputPVBList, tc.inputDetails) - assert.True(tt, reflect.DeepEqual(sd.output, tc.expect)) + output := make(map[string]interface{}) + describePodVolumeBackupsInSF(tc.inputPVBList, tc.inputDetails, output) + assert.True(tt, reflect.DeepEqual(output, tc.expect)) }) } } -func TestDescribeCSIVolumeSnapshotsInSF(t *testing.T) { +func TestDescribeSnapshotInSF(t *testing.T) { + testcases := []struct { + name string + volumeInfo []*volume.VolumeInfo + inputDetails bool + expect map[string]interface{} + }{ + { + name: "no details", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.NativeSnapshot, + PVName: "pv-1", + NativeSnapshotInfo: volume.NativeSnapshotInfo{ + SnapshotHandle: "snapshot-1", + VolumeType: "ebs", + VolumeAZ: "us-east-2", + IOPS: "1000 mbps", + }, + }, + }, + expect: map[string]interface{}{ + "nativeSnapshots": map[string]interface{}{ + "pv-1": "specify --details for more information", + }, + }, + }, + { + name: "details", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.NativeSnapshot, + PVName: "pv-1", + NativeSnapshotInfo: volume.NativeSnapshotInfo{ + SnapshotHandle: "snapshot-1", + VolumeType: "ebs", + VolumeAZ: "us-east-2", + IOPS: "1000 mbps", + }, + }, + }, + inputDetails: true, + expect: map[string]interface{}{ + "nativeSnapshots": map[string]interface{}{ + "pv-1": map[string]string{ + "snapshotID": "snapshot-1", + "type": "ebs", + "availabilityZone": "us-east-2", + "IOPS": "1000 mbps", + }, + }, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(tt *testing.T) { + output := make(map[string]interface{}) + describeNativeSnapshotsInSF(tc.inputDetails, tc.volumeInfo, output) + assert.True(tt, reflect.DeepEqual(output, tc.expect)) + }) + } +} + +func TestDescribeCSISnapshotsInSF(t *testing.T) { features.Enable(velerov1api.CSIFeatureFlag) defer func() { features.Disable(velerov1api.CSIFeatureFlag) }() - vscBuilder1 := builder.ForVolumeSnapshotContent("vsc-1") - handle := "handle-1" - readyToUse := true - size := int64(1024) - vsc1 := vscBuilder1.Status(&snapshotv1api.VolumeSnapshotContentStatus{ - SnapshotHandle: &handle, - ReadyToUse: &readyToUse, - RestoreSize: &size, - }).Result() - testcases := []struct { name string - inputVSCList []snapshotv1api.VolumeSnapshotContent + volumeInfo []*volume.VolumeInfo inputDetails bool expect map[string]interface{} }{ { - name: "empty list", - inputVSCList: []snapshotv1api.VolumeSnapshotContent{}, - inputDetails: false, - expect: map[string]interface{}{}, - }, - { - name: "1 vsc no detail", - inputVSCList: []snapshotv1api.VolumeSnapshotContent{*vsc1}, - inputDetails: false, + name: "no details, local snapshot", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-1", + PreserveLocalSnapshot: true, + OperationID: "fake-operation-1", + CSISnapshotInfo: volume.CSISnapshotInfo{ + SnapshotHandle: "snapshot-1", + Size: 1024, + Driver: "fake-driver", + VSCName: "vsc-1", + }, + }, + }, expect: map[string]interface{}{ - "CSIVolumeSnapshots": map[string]interface{}{ - "CSIVolumeSnapshotsCount": 1, + "csiSnapshots": map[string]interface{}{ + "pvc-1": map[string]interface{}{ + "operationID": "fake-operation-1", + "snapshot": "specify --details for more information", + }, }, }, }, { - name: "1 vsc with detail", - inputVSCList: []snapshotv1api.VolumeSnapshotContent{*vsc1}, + name: "details, local snapshot", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-2", + PreserveLocalSnapshot: true, + OperationID: "fake-operation-2", + CSISnapshotInfo: volume.CSISnapshotInfo{ + SnapshotHandle: "snapshot-2", + Size: 1024, + Driver: "fake-driver", + VSCName: "vsc-2", + }, + }, + }, inputDetails: true, expect: map[string]interface{}{ - "CSIVolumeSnapshots": map[string]interface{}{ - "CSIVolumeSnapshotsDetails": map[string]interface{}{ - "vsc-1": map[string]interface{}{ - "readyToUse": true, + "csiSnapshots": map[string]interface{}{ + "pvc-2": map[string]interface{}{ + "operationID": "fake-operation-2", + "snapshot": map[string]interface{}{ + "snapshotContentName": "vsc-2", + "storageSnapshotID": "snapshot-2", "snapshotSize(bytes)": int64(1024), - "storageSnapshotID": "handle-1", + "csiDriver": "fake-driver", + }, + }, + }, + }, + }, + { + name: "no details, data movement", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-3", + SnapshotDataMoved: true, + OperationID: "fake-operation-3", + SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{ + DataMover: "velero", + UploaderType: "fake-uploader", + SnapshotHandle: "fake-repo-id-3", + }, + }, + }, + expect: map[string]interface{}{ + "csiSnapshots": map[string]interface{}{ + "pvc-3": map[string]interface{}{ + "operationID": "fake-operation-3", + "dataMovement": "specify --details for more information", + }, + }, + }, + }, + { + name: "details, data movement", + volumeInfo: []*volume.VolumeInfo{ + { + BackupMethod: volume.CSISnapshot, + PVCName: "pvc-4", + SnapshotDataMoved: true, + OperationID: "fake-operation-4", + SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{ + DataMover: "velero", + UploaderType: "fake-uploader", + SnapshotHandle: "fake-repo-id-4", + }, + }, + }, + inputDetails: true, + expect: map[string]interface{}{ + "csiSnapshots": map[string]interface{}{ + "pvc-4": map[string]interface{}{ + "operationID": "fake-operation-4", + "dataMovement": map[string]interface{}{ + "dataMover": "velero", + "uploaderType": "fake-uploader", + "repositorySnapshotID": "fake-repo-id-4", }, }, }, @@ -328,12 +455,9 @@ func TestDescribeCSIVolumeSnapshotsInSF(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(tt *testing.T) { - sd := &StructuredDescriber{ - output: make(map[string]interface{}), - format: "", - } - DescribeCSIVolumeSnapshotsInSF(sd, tc.inputDetails, tc.inputVSCList) - assert.True(tt, reflect.DeepEqual(sd.output, tc.expect)) + output := make(map[string]interface{}) + describeCSISnapshotsInSF(tc.inputDetails, tc.volumeInfo, output) + assert.True(tt, reflect.DeepEqual(output, tc.expect)) }) } } @@ -441,18 +565,3 @@ func TestDescribeDeleteBackupRequestsInSF(t *testing.T) { } } - -func TestDescribeSnapshotInSF(t *testing.T) { - res := map[string]interface{}{} - iops := int64(100) - describeSnapshotInSF("pv-1", "snapshot-1", "ebs", "us-east-2", &iops, res) - expect := map[string]interface{}{ - "pv-1": map[string]string{ - "snapshotID": "snapshot-1", - "type": "ebs", - "availabilityZone": "us-east-2", - "IOPS": "100", - }, - } - assert.True(t, reflect.DeepEqual(expect, res)) -} diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index 4e0ddbe91..0ea1b0f69 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -587,6 +587,8 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s return s.objectStore.CreateSignedURL(s.bucket, s.layout.getCSIVolumeSnapshotContentsKey(target.Name), DownloadURLTTL) case velerov1api.DownloadTargetKindBackupResults: return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResultsKey(target.Name), DownloadURLTTL) + case velerov1api.DownloadTargetKindBackupVolumeInfos: + return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeInfoKey(target.Name), DownloadURLTTL) default: return "", errors.Errorf("unsupported download target kind %q", target.Kind) } diff --git a/pkg/persistence/object_store_layout.go b/pkg/persistence/object_store_layout.go index 134562337..fff920d0a 100644 --- a/pkg/persistence/object_store_layout.go +++ b/pkg/persistence/object_store_layout.go @@ -128,3 +128,7 @@ func (l *ObjectStoreLayout) getCSIVolumeSnapshotClassesKey(backup string) string func (l *ObjectStoreLayout) getBackupResultsKey(backup string) string { return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-results.gz", backup)) } + +func (l *ObjectStoreLayout) getBackupVolumeInfoKey(backup string) string { + return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumeinfos.json.gz", backup)) +} diff --git a/pkg/volume/volume_info_common.go b/pkg/volume/volume_info_common.go new file mode 100644 index 000000000..cfca31df9 --- /dev/null +++ b/pkg/volume/volume_info_common.go @@ -0,0 +1,147 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package volume + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +type VolumeBackupMethod string + +const ( + NativeSnapshot VolumeBackupMethod = "NativeSnapshot" + PodVolumeBackup VolumeBackupMethod = "PodVolumeBackup" + CSISnapshot VolumeBackupMethod = "CSISnapshot" +) + +type VolumeInfoVersion struct { + Version string `json:"version"` +} + +type VolumeInfos struct { + VolumeInfos []VolumeInfo `json:"volumeInfos"` +} + +type VolumeInfo struct { + // The PVC's name. + PVCName string `json:"pvcName,omitempty"` + + // The PVC's namespace + PVCNamespace string `json:"pvcNamespace,omitempty"` + + // The PV name. + PVName string `json:"pvName,omitempty"` + + // The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup` and `CSISnapshot`. + BackupMethod VolumeBackupMethod `json:"backupMethod,omitempty"` + + // Whether the volume's snapshot data is moved to specified storage. + SnapshotDataMoved bool `json:"snapshotDataMoved"` + + // Whether the local snapshot is preserved after snapshot is moved. + PreserveLocalSnapshot bool `json:"preserveLocalSnapshot"` + + // Whether the Volume is skipped in this backup. + Skipped bool `json:"skipped"` + + // The reason for the volume is skipped in the backup. + SkippedReason string `json:"skippedReason,omitempty"` + + // Snapshot starts timestamp. + StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"` + + // The Async Operation's ID. + OperationID string `json:"operationID,omitempty"` + + CSISnapshotInfo CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` + SnapshotDataMovementInfo SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"` + NativeSnapshotInfo NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"` + PVBInfo PodVolumeBackupInfo `json:"pvbInfo,omitempty"` +} + +// CSISnapshotInfo is used for displaying the CSI snapshot status +type CSISnapshotInfo struct { + // It's the storage provider's snapshot ID for CSI. + SnapshotHandle string `json:"snapshotHandle"` + + // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. + Size int64 `json:"size"` + + // The name of the CSI driver. + Driver string `json:"driver"` + + // The name of the VolumeSnapshotContent. + VSCName string `json:"vscName"` +} + +// SnapshotDataMovementInfo is used for displaying the snapshot data mover status. +type SnapshotDataMovementInfo struct { + // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`). + DataMover string `json:"dataMover"` + + // The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover. + UploaderType string `json:"uploaderType"` + + // The name or ID of the snapshot associated object(SAO). + // SAO is used to support local snapshots for the snapshot data mover, + // e.g. it could be a VolumeSnapshot for CSI snapshot data movement. + RetainedSnapshot string `json:"retainedSnapshot"` + + // It's the filesystem repository's snapshot ID. + SnapshotHandle string `json:"snapshotHandle"` +} + +// NativeSnapshotInfo is used for displaying the Velero native snapshot status. +// A Velero Native Snapshot is a cloud storage snapshot taken by the Velero native +// plugins, e.g. velero-plugin-for-aws, velero-plugin-for-gcp, and +// velero-plugin-for-microsoft-azure. +type NativeSnapshotInfo struct { + // It's the storage provider's snapshot ID for the Velero-native snapshot. + SnapshotHandle string `json:"snapshotHandle"` + + // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. + Size int64 `json:"size"` + + // The cloud provider snapshot volume type. + VolumeType string `json:"volumeType"` + + // The cloud provider snapshot volume's availability zones. + VolumeAZ string `json:"volumeAZ"` + + // The cloud provider snapshot volume's IOPS. + IOPS string `json:"iops"` +} + +// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status. +type PodVolumeBackupInfo struct { + // It's the file-system uploader's snapshot ID for PodVolumeBackup. + SnapshotHandle string `json:"snapshotHandle"` + + // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. + Size int64 `json:"size"` + + // The type of the uploader that uploads the data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover. + UploaderType string `json:"uploaderType"` + + // The PVC's corresponding volume name used by Pod + // https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48 + VolumeName string `json:"volumeName"` + + // The Pod name mounting this PVC. The format should be /. + PodName string `json:"podName"` + + // The PVB-taken k8s node's name. + NodeName string `json:"nodeName"` +}