diff --git a/changelogs/unreleased/10436-blackpiglet b/changelogs/unreleased/10436-blackpiglet new file mode 100644 index 000000000..ed6f0f206 --- /dev/null +++ b/changelogs/unreleased/10436-blackpiglet @@ -0,0 +1 @@ +Add block data mover support for Velero backup/restore describe CLI. \ No newline at end of file diff --git a/internal/volume/volumes_information.go b/internal/volume/volumes_information.go index cec2922d9..7f36168dd 100644 --- a/internal/volume/volumes_information.go +++ b/internal/volume/volumes_information.go @@ -29,13 +29,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kbclient "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/vmware-tanzu/velero/pkg/label" - + veleroshared "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" + "github.com/vmware-tanzu/velero/pkg/datamover" "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/itemoperation" "github.com/vmware-tanzu/velero/pkg/kuberesource" + "github.com/vmware-tanzu/velero/pkg/label" "github.com/vmware-tanzu/velero/pkg/util/stringptr" ) @@ -89,11 +90,14 @@ type BackupVolumeInfo struct { // Whether the volume data is backed up successfully. Result VolumeResult `json:"result,omitempty"` - CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` - SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"` - NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"` - PVBInfo *PodVolumeInfo `json:"pvbInfo,omitempty"` - PVInfo *PVInfo `json:"pvInfo,omitempty"` + // How volume data is backed up, with possible values including Full and Incremental. + BackupType velerov1api.BackupType `json:"backupType,omitempty"` + + CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` + SnapshotDataMovementInfo *BackupSnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"` + NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"` + PVBInfo *PodVolumeBackupInfo `json:"pvbInfo,omitempty"` + PVInfo *PVInfo `json:"pvInfo,omitempty"` } type VolumeResult string @@ -122,10 +126,10 @@ type RestoreVolumeInfo struct { // Whether the volume's data are restored via data movement SnapshotDataMoved bool `json:"snapshotDataMoved"` - CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` - SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"` - NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"` - PVRInfo *PodVolumeInfo `json:"pvrInfo,omitempty"` + CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` + SnapshotDataMovementInfo *RestoreSnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"` + NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"` + PVRInfo *PodVolumeRestoreInfo `json:"pvrInfo,omitempty"` } // CSISnapshotInfo is used for displaying the CSI snapshot status @@ -146,15 +150,15 @@ type CSISnapshotInfo struct { OperationID string `json:"operationID,omitempty"` // The VolumeSnapshot's Status.ReadyToUse value - ReadyToUse *bool + ReadyToUse *bool `json:"readyToUse,omitempty"` // The VolumeGroupSnapshotHandle from VSC status, used to create stub VGSC during restore // for CSI drivers that populate this field (e.g., Ceph RBD). VolumeGroupSnapshotHandle string `json:"volumeGroupSnapshotHandle,omitempty"` } -// SnapshotDataMovementInfo is used for displaying the snapshot data mover status. -type SnapshotDataMovementInfo struct { +// BackupSnapshotDataMovementInfo is used for displaying the backup snapshot data mover status. +type BackupSnapshotDataMovementInfo struct { // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`). DataMover string `json:"dataMover"` @@ -181,8 +185,49 @@ type SnapshotDataMovementInfo struct { // incremental and must stay distinguishable from "unknown". IncrementalSize *int64 `json:"incrementalSize,omitempty"` + // ParentSnapshot specifies the parent snapshot that current backup is based on. + // If its value is "" or "auto", the data mover finds the recent backup of the same volume as parent. + // If its value is "none", the data mover will do a full backup + // If its value is a specific snapshotID, the data mover finds the specific snapshot as parent. + ParentSnapshot string `json:"parentSnapshot,omitempty"` + // The DataUpload's Status.Phase value - Phase velerov2alpha1.DataUploadPhase + Phase velerov2alpha1.DataUploadPhase `json:"phase"` +} + +// RestoreSnapshotDataMovementInfo is used for displaying the restore snapshot data mover status. +type RestoreSnapshotDataMovementInfo 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`. + 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,omitempty"` + + // It's the filesystem repository's snapshot ID. + SnapshotHandle string `json:"snapshotHandle"` + + // The Async Operation's ID. + OperationID string `json:"operationID"` + + // Moved snapshot data size. + Size int64 `json:"size"` + + // Moved snapshot incremental size, i.e. the bytes actually transferred. Nil means + // the uploader reported no figure (including backups taken before this was + // recorded); a pointer to 0 means it transferred nothing, which is the ideal + // incremental and must stay distinguishable from "unknown". + IncrementalSize *int64 `json:"incrementalSize,omitempty"` + + // The DataDownload's Status.Phase value + Phase velerov2alpha1.DataDownloadPhase `json:"phase"` + + // Indicates the type of the restore, incremental or full. + RestoreType string `json:"restoreType,omitempty"` } // NativeSnapshotInfo is used for displaying the Velero native snapshot status. @@ -203,7 +248,7 @@ type NativeSnapshotInfo struct { IOPS string `json:"iops"` // The NativeSnapshot's Status.Phase value - Phase SnapshotPhase + Phase SnapshotPhase `json:"phase,omitempty"` } func newNativeSnapshotInfo(s *Snapshot) *NativeSnapshotInfo { @@ -220,9 +265,9 @@ func newNativeSnapshotInfo(s *Snapshot) *NativeSnapshotInfo { } } -// PodVolumeInfo is used for displaying the PodVolumeBackup/PodVolumeRestore snapshot status. -type PodVolumeInfo struct { - // It's the file-system uploader's snapshot ID for PodVolumeBackup/PodVolumeRestore. +// 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,omitempty"` // The snapshot corresponding volume size. @@ -250,11 +295,47 @@ type PodVolumeInfo struct { NodeName string `json:"nodeName,omitempty"` // The PVB's Status.Phase value - Phase velerov1api.PodVolumeBackupPhase + Phase velerov1api.PodVolumeBackupPhase `json:"phase,omitempty"` } -func newPodVolumeInfoFromPVB(pvb *velerov1api.PodVolumeBackup) *PodVolumeInfo { - return &PodVolumeInfo{ +// PodVolumeRestoreInfo is used for displaying the PodVolumeRestore snapshot status. +type PodVolumeRestoreInfo struct { + // It's the file-system uploader's snapshot ID for PodVolumeRestore. + SnapshotHandle string `json:"snapshotHandle,omitempty"` + + // The snapshot corresponding volume size. + Size int64 `json:"size,omitempty"` + + // The incremental snapshot size, i.e. the bytes actually transferred. Nil means + // the uploader reported no figure; a pointer to 0 means it transferred nothing. + IncrementalSize *int64 `json:"incrementalSize,omitempty"` + + // The type of the uploader that uploads the data. The valid values are `kopia` and `restic`. + 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. + PodName string `json:"podName"` + + // The Pod namespace + PodNamespace string `json:"podNamespace"` + + // The PVB-taken k8s node's name. + // This field will be empty when the struct is used to represent a podvolumerestore. + NodeName string `json:"nodeName,omitempty"` + + // The PVR's Status.Phase value + Phase velerov1api.PodVolumeRestorePhase `json:"phase,omitempty"` + + // Indicates the type of the restore, incremental or full. + RestoreType string `json:"restoreType,omitempty"` +} + +func newPodVolumeInfoFromPVB(pvb *velerov1api.PodVolumeBackup) *PodVolumeBackupInfo { + return &PodVolumeBackupInfo{ SnapshotHandle: pvb.Status.SnapshotID, Size: pvb.Status.Progress.TotalBytes, IncrementalSize: pvb.Status.IncrementalBytes, @@ -267,14 +348,16 @@ func newPodVolumeInfoFromPVB(pvb *velerov1api.PodVolumeBackup) *PodVolumeInfo { } } -func newPodVolumeInfoFromPVR(pvr *velerov1api.PodVolumeRestore) *PodVolumeInfo { - return &PodVolumeInfo{ +func newPodVolumeInfoFromPVR(pvr *velerov1api.PodVolumeRestore) *PodVolumeRestoreInfo { + return &PodVolumeRestoreInfo{ SnapshotHandle: pvr.Spec.SnapshotID, Size: pvr.Status.Progress.TotalBytes, UploaderType: pvr.Spec.UploaderType, VolumeName: pvr.Spec.Volume, PodName: pvr.Spec.Pod.Name, PodNamespace: pvr.Spec.Pod.Namespace, + Phase: pvr.Status.Phase, + RestoreType: pvr.Spec.RestoreType, } } @@ -470,6 +553,11 @@ func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() { volumeGroupSnapshotHandle = *volumeSnapshotContent.Status.VolumeGroupSnapshotHandle } if pvcPVInfo := v.pvMap.retrieve("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil { + volumeResult := VolumeResultFailed + if volumeSnapshot.Status != nil && volumeSnapshot.Status.ReadyToUse != nil && *volumeSnapshot.Status.ReadyToUse { + volumeResult = VolumeResultSucceeded + } + volumeInfo := &BackupVolumeInfo{ BackupMethod: CSISnapshot, PVCName: pvcPVInfo.PVCName, @@ -478,6 +566,7 @@ func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() { Skipped: false, SnapshotDataMoved: false, PreserveLocalSnapshot: true, + Result: volumeResult, CSISnapshotInfo: &CSISnapshotInfo{ VSCName: *volumeSnapshot.Status.BoundVolumeSnapshotContentName, Size: size, @@ -519,6 +608,11 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromPVB() { StartTimestamp: pvb.Status.StartTimestamp, CompletionTimestamp: pvb.Status.CompletionTimestamp, PVBInfo: newPodVolumeInfoFromPVB(pvb), + BackupType: velerov1api.BackupTypeIncremental, + } + + if pvb.Spec.ParentSnapshot == veleroshared.ParentSnapshotNone { + volumeInfo.BackupType = velerov1api.BackupTypeFull } // Only set Succeeded to true when the PVB's phase is Completed. @@ -622,6 +716,11 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() { dataMover = dataUpload.Spec.DataMover } + volumeResult := VolumeResultFailed + if dataUpload.Status.Phase == velerov2alpha1.DataUploadPhaseCompleted { + volumeResult = VolumeResultSucceeded + } + volumeInfo := &BackupVolumeInfo{ BackupMethod: CSISnapshot, PVCName: pvcPVInfo.PVCName, @@ -629,17 +728,22 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() { PVName: pvcPVInfo.PV.Name, SnapshotDataMoved: true, Skipped: false, + Result: volumeResult, + BackupType: velerov1api.BackupTypeIncremental, CSISnapshotInfo: &CSISnapshotInfo{ SnapshotHandle: FieldValueIsUnknown, VSCName: FieldValueIsUnknown, OperationID: FieldValueIsUnknown, Driver: dataUpload.Spec.CSISnapshot.Driver, }, - SnapshotDataMovementInfo: &SnapshotDataMovementInfo{ - DataMover: dataMover, - UploaderType: velerov1api.BackupRepositoryTypeKopia, - OperationID: operation.Spec.OperationID, - Phase: dataUpload.Status.Phase, + SnapshotDataMovementInfo: &BackupSnapshotDataMovementInfo{ + DataMover: dataMover, + UploaderType: datamover.GetUploaderType(dataMover), + OperationID: operation.Spec.OperationID, + Phase: dataUpload.Status.Phase, + ParentSnapshot: dataUpload.Spec.ParentSnapshot, + Size: dataUpload.Status.Progress.TotalBytes, + SnapshotHandle: dataUpload.Status.SnapshotID, }, PVInfo: &PVInfo{ ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy), @@ -647,10 +751,22 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() { }, } + if dataUpload.Spec.ParentSnapshot == veleroshared.ParentSnapshotNone { + volumeInfo.BackupType = velerov1api.BackupTypeFull + } + if dataUpload.Status.StartTimestamp != nil { volumeInfo.StartTimestamp = dataUpload.Status.StartTimestamp } + if dataUpload.Status.CompletionTimestamp != nil { + volumeInfo.CompletionTimestamp = dataUpload.Status.CompletionTimestamp + } + + if dataUpload.Status.IncrementalBytes != nil { + volumeInfo.SnapshotDataMovementInfo.IncrementalSize = dataUpload.Status.IncrementalBytes + } + tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo) } else { v.logger.Warnf("Cannot find info for PVC %s/%s", operation.Spec.ResourceIdentifier.Namespace, operation.Spec.ResourceIdentifier.Name) @@ -878,14 +994,21 @@ func (t *RestoreVolumeInfoTracker) Result() []*RestoreVolumeInfo { // the datadownload was initiated in CSI plugin // For the same reason, no CSI snapshot info will be populated into volumeInfo RestoreMethod: CSISnapshot, - SnapshotDataMovementInfo: &SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &RestoreSnapshotDataMovementInfo{ DataMover: dataMover, - UploaderType: velerov1api.BackupRepositoryTypeKopia, + UploaderType: datamover.GetUploaderType(dataMover), SnapshotHandle: dd.Spec.SnapshotID, OperationID: operationID, + RestoreType: dd.Spec.RestoreType, + Size: dd.Status.Progress.TotalBytes, + Phase: dd.Status.Phase, }, } + if dd.Status.IncrementalBytes != nil { + volumeInfo.SnapshotDataMovementInfo.IncrementalSize = dd.Status.IncrementalBytes + } + volumeInfos = append(volumeInfos, volumeInfo) } diff --git a/internal/volume/volumes_information_test.go b/internal/volume/volumes_information_test.go index b39ba82f8..4d31ad725 100644 --- a/internal/volume/volumes_information_test.go +++ b/internal/volume/volumes_information_test.go @@ -17,6 +17,7 @@ limitations under the License. package volume import ( + "encoding/json" "sync" "testing" @@ -30,7 +31,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" + veleroshared "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" "github.com/vmware-tanzu/velero/pkg/builder" @@ -474,6 +477,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) { PVCNamespace: "velero", PVName: "testPV", BackupMethod: CSISnapshot, + Result: VolumeResultSucceeded, StartTimestamp: &now, PreserveLocalSnapshot: true, CSISnapshotInfo: &CSISnapshotInfo{ @@ -562,8 +566,9 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) { PVCNamespace: "", PVName: "", BackupMethod: PodVolumeBackup, + BackupType: velerov1api.BackupTypeIncremental, Result: VolumeResultFailed, - PVBInfo: &PodVolumeInfo{ + PVBInfo: &PodVolumeBackupInfo{ PodName: "testPod", PodNamespace: "velero", }, @@ -641,10 +646,11 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) { PVCNamespace: "velero", PVName: "testPV", BackupMethod: PodVolumeBackup, + BackupType: velerov1api.BackupTypeIncremental, StartTimestamp: &now, CompletionTimestamp: &now, Result: VolumeResultFailed, - PVBInfo: &PodVolumeInfo{ + PVBInfo: &PodVolumeBackupInfo{ PodName: "testPod", PodNamespace: "velero", Phase: velerov1api.PodVolumeBackupPhaseFailed, @@ -704,10 +710,11 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) { PVCNamespace: "velero", PVName: "testPV", BackupMethod: PodVolumeBackup, + BackupType: velerov1api.BackupTypeIncremental, StartTimestamp: &now, CompletionTimestamp: &now, Result: VolumeResultSucceeded, - PVBInfo: &PodVolumeInfo{ + PVBInfo: &PodVolumeBackupInfo{ PodName: "testPod", PodNamespace: "velero", Phase: velerov1api.PodVolumeBackupPhaseCompleted, @@ -838,6 +845,9 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) { Driver: "pd.csi.storage.gke.io", }).SnapshotID("testSnapshotHandle"). StartTimestamp(&now). + CompletionTimestamp(&now). + TotalBytes(1024). + IncrementalBytes(512). Phase(velerov2alpha1.DataUploadPhaseCompleted). Result(), vs: builder.ForVolumeSnapshot(velerov1api.DefaultNamespace, "vs-01").Status().BoundVolumeSnapshotContentName("vsc-01").Result(), @@ -882,12 +892,15 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) { }, expectedVolumeInfos: []*BackupVolumeInfo{ { - PVCName: "testPVC", - PVCNamespace: "velero", - PVName: "testPV", - BackupMethod: CSISnapshot, - SnapshotDataMoved: true, - StartTimestamp: &now, + PVCName: "testPVC", + PVCNamespace: "velero", + PVName: "testPV", + BackupMethod: CSISnapshot, + SnapshotDataMoved: true, + BackupType: velerov1api.BackupTypeIncremental, + Result: VolumeResultSucceeded, + StartTimestamp: &now, + CompletionTimestamp: &now, CSISnapshotInfo: &CSISnapshotInfo{ VSCName: FieldValueIsUnknown, SnapshotHandle: FieldValueIsUnknown, @@ -895,11 +908,14 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) { Size: 0, Driver: "pd.csi.storage.gke.io", }, - SnapshotDataMovementInfo: &SnapshotDataMovementInfo{ - DataMover: "velero", - UploaderType: "kopia", - OperationID: "testOperation", - Phase: velerov2alpha1.DataUploadPhaseCompleted, + SnapshotDataMovementInfo: &BackupSnapshotDataMovementInfo{ + DataMover: "velero", + UploaderType: "kopia", + OperationID: "testOperation", + Phase: velerov2alpha1.DataUploadPhaseCompleted, + SnapshotHandle: "testSnapshotHandle", + Size: 1024, + IncrementalSize: ptr.To(int64(512)), }, PVInfo: &PVInfo{ ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete), @@ -947,6 +963,19 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) { require.Equal(t, tc.expectedVolumeInfos[0].PVInfo, volumesInfo.volumeInfos[0].PVInfo) require.Equal(t, tc.expectedVolumeInfos[0].SnapshotDataMovementInfo, volumesInfo.volumeInfos[0].SnapshotDataMovementInfo) require.Equal(t, tc.expectedVolumeInfos[0].CSISnapshotInfo, volumesInfo.volumeInfos[0].CSISnapshotInfo) + require.Equal(t, tc.expectedVolumeInfos[0].Result, volumesInfo.volumeInfos[0].Result) + if tc.expectedVolumeInfos[0].StartTimestamp != nil { + require.NotNil(t, volumesInfo.volumeInfos[0].StartTimestamp) + require.True(t, tc.expectedVolumeInfos[0].StartTimestamp.Equal(volumesInfo.volumeInfos[0].StartTimestamp)) + } else { + require.Nil(t, volumesInfo.volumeInfos[0].StartTimestamp) + } + if tc.expectedVolumeInfos[0].CompletionTimestamp != nil { + require.NotNil(t, volumesInfo.volumeInfos[0].CompletionTimestamp) + require.True(t, tc.expectedVolumeInfos[0].CompletionTimestamp.Equal(volumesInfo.volumeInfos[0].CompletionTimestamp)) + } else { + require.Nil(t, volumesInfo.volumeInfos[0].CompletionTimestamp) + } } }) } @@ -1057,7 +1086,7 @@ func TestRestoreVolumeInfoResult(t *testing.T) { PVName: "testPV2", RestoreMethod: PodVolumeRestore, SnapshotDataMoved: false, - PVRInfo: &PodVolumeInfo{ + PVRInfo: &PodVolumeRestoreInfo{ SnapshotHandle: "pvr-snap-001", PodName: "testPod", PodNamespace: "testNS", @@ -1127,7 +1156,7 @@ func TestRestoreVolumeInfoResult(t *testing.T) { PVName: "testPV2", RestoreMethod: PodVolumeRestore, SnapshotDataMoved: false, - PVRInfo: &PodVolumeInfo{ + PVRInfo: &PodVolumeRestoreInfo{ SnapshotHandle: "pvr-snap-001", PodName: "testPod", PodNamespace: "testNS", @@ -1182,6 +1211,9 @@ func TestRestoreVolumeInfoResult(t *testing.T) { PVC: "testPVC", Namespace: "testNS", }). + Phase(velerov2alpha1.DataDownloadPhaseCompleted). + Progress(veleroshared.DataMoveOperationProgress{TotalBytes: 2048}). + RestoreType("Incremental"). Result(), *builder.ForDataDownload("velero", "testDataDownload-2"). ObjectMeta(builder.WithLabels(velerov1api.AsyncOperationIDLabel, "dd-operation-002")). @@ -1190,6 +1222,9 @@ func TestRestoreVolumeInfoResult(t *testing.T) { PVC: "testPVC2", Namespace: "testNS", }). + Phase(velerov2alpha1.DataDownloadPhaseCompleted). + Progress(veleroshared.DataMoveOperationProgress{TotalBytes: 4096}). + RestoreType("Full"). Result(), }, }, @@ -1202,11 +1237,14 @@ func TestRestoreVolumeInfoResult(t *testing.T) { PVName: "testPV", RestoreMethod: CSISnapshot, SnapshotDataMoved: true, - SnapshotDataMovementInfo: &SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &RestoreSnapshotDataMovementInfo{ DataMover: "velero", UploaderType: velerov1api.BackupRepositoryTypeKopia, SnapshotHandle: "dd-snap-001", OperationID: "dd-operation-001", + Phase: velerov2alpha1.DataDownloadPhaseCompleted, + Size: 2048, + RestoreType: "Incremental", }, }, { @@ -1215,11 +1253,14 @@ func TestRestoreVolumeInfoResult(t *testing.T) { PVName: "testPV2", RestoreMethod: CSISnapshot, SnapshotDataMoved: true, - SnapshotDataMovementInfo: &SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &RestoreSnapshotDataMovementInfo{ DataMover: "velero", UploaderType: velerov1api.BackupRepositoryTypeKopia, SnapshotHandle: "dd-snap-002", OperationID: "dd-operation-002", + Phase: velerov2alpha1.DataDownloadPhaseCompleted, + Size: 4096, + RestoreType: "Full", }, }, }, @@ -1262,3 +1303,174 @@ func TestGetVolumeSnapshotClasses(t *testing.T) { require.NoError(t, err) require.Equal(t, []snapshotv1api.VolumeSnapshotClass{*class}, result) } + +func TestBackupVolumeInfoJSONRoundTrip(t *testing.T) { + orig := BackupVolumeInfo{ + PVCName: "pvc-1", + PVCNamespace: "ns-1", + PVName: "pv-1", + BackupMethod: CSISnapshot, + SnapshotDataMoved: true, + PreserveLocalSnapshot: false, + Skipped: false, + Result: VolumeResultSucceeded, + BackupType: velerov1api.BackupTypeIncremental, + CSISnapshotInfo: &CSISnapshotInfo{ + SnapshotHandle: "csi-snap-1", + Size: 2000, + Driver: "csi.driver.com", + VSCName: "vsc-1", + OperationID: "op-2", + VolumeGroupSnapshotHandle: "vgsh-1", + }, + SnapshotDataMovementInfo: &BackupSnapshotDataMovementInfo{ + DataMover: "velero", + UploaderType: "kopia", + RetainedSnapshot: "retain-1", + SnapshotHandle: "snap-1", + OperationID: "op-1", + Size: 1000, + IncrementalSize: int64Ptr(200), + ParentSnapshot: "parent-1", + Phase: velerov2alpha1.DataUploadPhaseCompleted, + }, + NativeSnapshotInfo: &NativeSnapshotInfo{ + SnapshotHandle: "native-snap-1", + VolumeType: "gp3", + VolumeAZ: "us-west-2a", + IOPS: "3000", + }, + PVBInfo: &PodVolumeBackupInfo{ + SnapshotHandle: "pvb-snap-1", + Size: 500, + IncrementalSize: int64Ptr(50), + UploaderType: "kopia", + VolumeName: "vol-1", + PodName: "pod-1", + PodNamespace: "ns-1", + NodeName: "node-1", + Phase: velerov1api.PodVolumeBackupPhaseCompleted, + }, + PVInfo: &PVInfo{ + ReclaimPolicy: "Delete", + Labels: map[string]string{"env": "test"}, + }, + } + + data, err := json.Marshal(orig) + require.NoError(t, err) + + jsonStr := string(data) + assert.Contains(t, jsonStr, `"pvcName":"pvc-1"`) + assert.Contains(t, jsonStr, `"pvcNamespace":"ns-1"`) + assert.Contains(t, jsonStr, `"pvName":"pv-1"`) + assert.Contains(t, jsonStr, `"backupMethod":"CSISnapshot"`) + assert.Contains(t, jsonStr, `"snapshotDataMoved":true`) + assert.Contains(t, jsonStr, `"result":"succeeded"`) + assert.Contains(t, jsonStr, `"backupType":"Incremental"`) + assert.Contains(t, jsonStr, `"snapshotDataMovementInfo":{`) + assert.Contains(t, jsonStr, `"dataMover":"velero"`) + assert.Contains(t, jsonStr, `"uploaderType":"kopia"`) + assert.Contains(t, jsonStr, `"retainedSnapshot":"retain-1"`) + assert.Contains(t, jsonStr, `"snapshotHandle":"snap-1"`) + assert.Contains(t, jsonStr, `"operationID":"op-1"`) + assert.Contains(t, jsonStr, `"size":1000`) + assert.Contains(t, jsonStr, `"incrementalSize":200`) + assert.Contains(t, jsonStr, `"parentSnapshot":"parent-1"`) + assert.Contains(t, jsonStr, `"phase":"Completed"`) + assert.Contains(t, jsonStr, `"pvbInfo":{`) + assert.Contains(t, jsonStr, `"podName":"pod-1"`) + assert.Contains(t, jsonStr, `"podNamespace":"ns-1"`) + assert.Contains(t, jsonStr, `"nodeName":"node-1"`) + assert.Contains(t, jsonStr, `"csiSnapshotInfo":{`) + assert.Contains(t, jsonStr, `"driver":"csi.driver.com"`) + assert.Contains(t, jsonStr, `"vscName":"vsc-1"`) + assert.Contains(t, jsonStr, `"volumeGroupSnapshotHandle":"vgsh-1"`) + assert.Contains(t, jsonStr, `"nativeSnapshotInfo":{`) + assert.Contains(t, jsonStr, `"volumeType":"gp3"`) + assert.Contains(t, jsonStr, `"volumeAZ":"us-west-2a"`) + assert.Contains(t, jsonStr, `"iops":"3000"`) + assert.Contains(t, jsonStr, `"pvInfo":{`) + assert.Contains(t, jsonStr, `"reclaimPolicy":"Delete"`) + + var unmarshaled BackupVolumeInfo + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, orig, unmarshaled) +} + +func TestRestoreVolumeInfoJSONRoundTrip(t *testing.T) { + orig := RestoreVolumeInfo{ + PVCName: "pvc-2", + PVCNamespace: "ns-2", + PVName: "pv-2", + RestoreMethod: CSISnapshot, + SnapshotDataMoved: true, + SnapshotDataMovementInfo: &RestoreSnapshotDataMovementInfo{ + DataMover: "velero", + UploaderType: "kopia", + RetainedSnapshot: "retain-2", + SnapshotHandle: "snap-2", + OperationID: "op-3", + Size: 3000, + IncrementalSize: int64Ptr(300), + Phase: velerov2alpha1.DataDownloadPhaseCompleted, + RestoreType: "Incremental", + }, + PVRInfo: &PodVolumeRestoreInfo{ + SnapshotHandle: "pvr-snap-1", + Size: 600, + IncrementalSize: int64Ptr(60), + UploaderType: "kopia", + VolumeName: "vol-2", + PodName: "pod-2", + PodNamespace: "ns-2", + NodeName: "node-2", + Phase: velerov1api.PodVolumeRestorePhaseCompleted, + RestoreType: "Incremental", + }, + CSISnapshotInfo: &CSISnapshotInfo{ + SnapshotHandle: "csi-snap-2", + Size: 4000, + Driver: "csi.driver.com", + VSCName: "vsc-2", + }, + NativeSnapshotInfo: &NativeSnapshotInfo{ + SnapshotHandle: "native-snap-2", + VolumeType: "ebs", + VolumeAZ: "us-east-1a", + IOPS: "1000", + }, + } + + data, err := json.Marshal(orig) + require.NoError(t, err) + + jsonStr := string(data) + assert.Contains(t, jsonStr, `"pvcName":"pvc-2"`) + assert.Contains(t, jsonStr, `"pvcNamespace":"ns-2"`) + assert.Contains(t, jsonStr, `"pvName":"pv-2"`) + assert.Contains(t, jsonStr, `"restoreMethod":"CSISnapshot"`) + assert.Contains(t, jsonStr, `"snapshotDataMoved":true`) + assert.Contains(t, jsonStr, `"snapshotDataMovementInfo":{`) + assert.Contains(t, jsonStr, `"dataMover":"velero"`) + assert.Contains(t, jsonStr, `"uploaderType":"kopia"`) + assert.Contains(t, jsonStr, `"retainedSnapshot":"retain-2"`) + assert.Contains(t, jsonStr, `"snapshotHandle":"snap-2"`) + assert.Contains(t, jsonStr, `"operationID":"op-3"`) + assert.Contains(t, jsonStr, `"size":3000`) + assert.Contains(t, jsonStr, `"incrementalSize":300`) + assert.Contains(t, jsonStr, `"phase":"Completed"`) + assert.Contains(t, jsonStr, `"restoreType":"Incremental"`) + assert.Contains(t, jsonStr, `"pvrInfo":{`) + assert.Contains(t, jsonStr, `"podName":"pod-2"`) + assert.Contains(t, jsonStr, `"podNamespace":"ns-2"`) + assert.Contains(t, jsonStr, `"nodeName":"node-2"`) + assert.Contains(t, jsonStr, `"csiSnapshotInfo":{`) + assert.Contains(t, jsonStr, `"nativeSnapshotInfo":{`) + + var unmarshaled RestoreVolumeInfo + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, orig, unmarshaled) +} diff --git a/pkg/backup/backup_test.go b/pkg/backup/backup_test.go index 5d1ed1da2..8d3e75837 100644 --- a/pkg/backup/backup_test.go +++ b/pkg/backup/backup_test.go @@ -5666,7 +5666,7 @@ func TestUpdateVolumeInfos(t *testing.T) { PVCName: "pvc-1", PVCNamespace: "ns-1", CompletionTimestamp: &metav1.Time{}, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", }, }, @@ -5677,7 +5677,7 @@ func TestUpdateVolumeInfos(t *testing.T) { PVCNamespace: "ns-1", CompletionTimestamp: &now, Result: volume.VolumeResultFailed, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", RetainedSnapshot: "vs-1", SnapshotHandle: "snapshot-id", @@ -5706,7 +5706,7 @@ func TestUpdateVolumeInfos(t *testing.T) { PVCName: "pvc-1", PVCNamespace: "ns-1", CompletionTimestamp: &metav1.Time{}, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", }, }, @@ -5717,7 +5717,7 @@ func TestUpdateVolumeInfos(t *testing.T) { PVCNamespace: "ns-1", CompletionTimestamp: &now, Result: volume.VolumeResultSucceeded, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", RetainedSnapshot: "vs-1", SnapshotHandle: "snapshot-id", diff --git a/pkg/builder/data_download_builder.go b/pkg/builder/data_download_builder.go index 20cea868f..cd0d15837 100644 --- a/pkg/builder/data_download_builder.go +++ b/pkg/builder/data_download_builder.go @@ -61,6 +61,12 @@ func (d *DataDownloadBuilder) Phase(phase velerov2alpha1api.DataDownloadPhase) * return d } +// RestoreType sets the DataDownload's RestoreType. +func (d *DataDownloadBuilder) RestoreType(restoreType string) *DataDownloadBuilder { + d.object.Spec.RestoreType = restoreType + return d +} + // SnapshotID sets the DataDownload's SnapshotID. func (d *DataDownloadBuilder) SnapshotID(id string) *DataDownloadBuilder { d.object.Spec.SnapshotID = id diff --git a/pkg/cmd/util/output/backup_describer.go b/pkg/cmd/util/output/backup_describer.go index a8d43b89f..6c8d5860d 100644 --- a/pkg/cmd/util/output/backup_describer.go +++ b/pkg/cmd/util/output/backup_describer.go @@ -249,6 +249,10 @@ func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) { } d.Printf("Data Mover:\t%s\n", s) + if string(spec.BackupType) != "" { + d.Printf("Backup Type:\t%s\n", spec.BackupType) + } + d.Println() d.Printf("TTL:\t%s\n", spec.TTL.Duration) @@ -746,6 +750,10 @@ func describeDataMovement(d *Describer, details bool, info *volume.BackupVolumeI if info.SnapshotDataMovementInfo.IncrementalSize != nil { d.Printf("\t\t\t\tIncremental data Size (bytes): %d\n", *info.SnapshotDataMovementInfo.IncrementalSize) } + if info.SnapshotDataMovementInfo.ParentSnapshot != "" { + d.Printf("\t\t\t\tParent Snapshot: %s\n", info.SnapshotDataMovementInfo.ParentSnapshot) + } + d.Printf("\t\t\t\tResult: %s\n", info.Result) } else { d.Printf("\t\t\tData Movement: %s\n", "included, specify --details for more information") diff --git a/pkg/cmd/util/output/backup_describer_test.go b/pkg/cmd/util/output/backup_describer_test.go index c64ae04cb..4578d1301 100644 --- a/pkg/cmd/util/output/backup_describer_test.go +++ b/pkg/cmd/util/output/backup_describer_test.go @@ -108,6 +108,7 @@ func TestDescribeBackupSpec(t *testing.T) { TTL(72 * time.Hour). CSISnapshotTimeout(10 * time.Minute). DataMover("mover"). + BackupType(velerov1api.BackupTypeFull). Hooks(velerov1api.BackupHooks{ Resources: []velerov1api.BackupResourceHookSpec{ { @@ -156,6 +157,7 @@ Storage Location: backup-location Velero-Native Snapshot PVs: auto Snapshot Move Data: auto Data Mover: mover +Backup Type: Full TTL: 72h0m0s @@ -575,7 +577,7 @@ func TestCSISnapshots(t *testing.T) { PVCNamespace: "pvc-ns-3", PVCName: "pvc-3", SnapshotDataMoved: true, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", UploaderType: "fake-uploader", SnapshotHandle: "fake-repo-id-3", @@ -597,7 +599,7 @@ func TestCSISnapshots(t *testing.T) { PVCName: "pvc-4", SnapshotDataMoved: true, Result: volume.VolumeResultSucceeded, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", UploaderType: "fake-uploader", SnapshotHandle: "fake-repo-id-4", @@ -625,13 +627,15 @@ func TestCSISnapshots(t *testing.T) { PVCName: "pvc-5", Result: volume.VolumeResultFailed, SnapshotDataMoved: true, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + BackupType: velerov1api.BackupTypeIncremental, + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ UploaderType: "fake-uploader", SnapshotHandle: "fake-repo-id-5", OperationID: "fake-operation-5", Size: 100, IncrementalSize: ptr.To(int64(50)), Phase: velerov2alpha1.DataUploadPhaseFailed, + ParentSnapshot: "fake-parent-snapshot", }, }, }, @@ -644,6 +648,7 @@ func TestCSISnapshots(t *testing.T) { Uploader Type: fake-uploader Moved data Size (bytes): 100 Incremental data Size (bytes): 50 + Parent Snapshot: fake-parent-snapshot Result: failed `, }, diff --git a/pkg/cmd/util/output/backup_structured_describer.go b/pkg/cmd/util/output/backup_structured_describer.go index 1c0aefa34..d8fa66020 100644 --- a/pkg/cmd/util/output/backup_structured_describer.go +++ b/pkg/cmd/util/output/backup_structured_describer.go @@ -136,6 +136,9 @@ func DescribeBackupSpecInSF(d *StructuredDescriber, spec velerov1api.BackupSpec) s = spec.DataMover } backupSpecInfo["dataMover"] = s + if string(spec.BackupType) != "" { + backupSpecInfo["backupType"] = spec.BackupType + } // describe TTL backupSpecInfo["TTL"] = spec.TTL.Duration.String() @@ -475,6 +478,9 @@ func describeDataMovementInSF(details bool, info *volume.BackupVolumeInfo, snaps if info.SnapshotDataMovementInfo.IncrementalSize != nil { dataMovement["incrementalSize"] = *info.SnapshotDataMovementInfo.IncrementalSize } + if info.SnapshotDataMovementInfo.ParentSnapshot != "" { + dataMovement["parentSnapshot"] = info.SnapshotDataMovementInfo.ParentSnapshot + } snapshotDetail["dataMovement"] = dataMovement } else { diff --git a/pkg/cmd/util/output/backup_structured_describer_test.go b/pkg/cmd/util/output/backup_structured_describer_test.go index 88af0f95f..f7065590b 100644 --- a/pkg/cmd/util/output/backup_structured_describer_test.go +++ b/pkg/cmd/util/output/backup_structured_describer_test.go @@ -24,9 +24,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1api "k8s.io/api/core/v1" + "k8s.io/utils/ptr" "github.com/vmware-tanzu/velero/internal/volume" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/util/results" ) @@ -45,6 +47,7 @@ func TestDescribeBackupInSF(t *testing.T) { TTL(72 * time.Hour). CSISnapshotTimeout(10 * time.Minute). DataMover("mover"). + BackupType(velerov1api.BackupTypeFull). Hooks(velerov1api.BackupHooks{ Resources: []velerov1api.BackupResourceHookSpec{ { @@ -87,6 +90,7 @@ func TestDescribeBackupInSF(t *testing.T) { "clusterScoped": "auto", }, "dataMover": "mover", + "backupType": velerov1api.BackupTypeFull, "labelSelector": emptyDisplay, "storageLocation": "backup-location", "veleroNativeSnapshotPVs": "auto", @@ -517,7 +521,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) { PVCNamespace: "pvc-ns-3", PVCName: "pvc-3", SnapshotDataMoved: true, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", UploaderType: "fake-uploader", SnapshotHandle: "fake-repo-id-3", @@ -542,7 +546,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) { PVCName: "pvc-4", SnapshotDataMoved: true, Result: volume.VolumeResultSucceeded, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", UploaderType: "fake-uploader", SnapshotHandle: "fake-repo-id-4", @@ -573,10 +577,15 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) { Result: volume.VolumeResultFailed, PVCName: "pvc-4", SnapshotDataMoved: true, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ - UploaderType: "fake-uploader", - SnapshotHandle: "fake-repo-id-4", - OperationID: "fake-operation-4", + BackupType: velerov1api.BackupTypeIncremental, + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ + UploaderType: "fake-uploader", + SnapshotHandle: "fake-repo-id-4", + OperationID: "fake-operation-4", + Size: 100, + IncrementalSize: ptr.To(int64(50)), + Phase: velerov2alpha1.DataUploadPhaseFailed, + ParentSnapshot: "fake-parent-snapshot", }, }, }, @@ -585,10 +594,13 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) { "csiSnapshots": map[string]any{ "pvc-ns-4/pvc-4": map[string]any{ "dataMovement": map[string]any{ - "operationID": "fake-operation-4", - "dataMover": "velero", - "uploaderType": "fake-uploader", - "result": "failed", + "operationID": "fake-operation-4", + "dataMover": "velero", + "uploaderType": "fake-uploader", + "size": int64(100), + "incrementalSize": int64(50), + "result": "failed", + "parentSnapshot": "fake-parent-snapshot", }, }, }, diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index 11e8ff4e4..b5c2dba45 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -209,6 +209,11 @@ func DescribeRestore( s = string(restore.Spec.ExistingResourcePolicy) } d.Printf("Existing Resource Policy: \t%s\n", s) + s = emptyDisplay + if restore.Spec.ExistingVolumeDataPolicy != "" { + s = string(restore.Spec.ExistingVolumeDataPolicy) + } + d.Printf("Existing Volume Data Policy: \t%s\n", s) d.Printf("ItemOperationTimeout:\t%s\n", restore.Spec.ItemOperationTimeout.Duration) d.Println() @@ -468,6 +473,15 @@ func describeCSISnapshotsRestores(d *Describer, restoreVolInfo []volume.RestoreV d.Printf("\t\t\tOperation ID: %s\n", info.SnapshotDataMovementInfo.OperationID) d.Printf("\t\t\tData Mover: %s\n", info.SnapshotDataMovementInfo.DataMover) d.Printf("\t\t\tUploader Type: %s\n", info.SnapshotDataMovementInfo.UploaderType) + if info.SnapshotDataMovementInfo.RestoreType != "" { + d.Printf("\t\t\tRestore Type: %s\n", info.SnapshotDataMovementInfo.RestoreType) + } + if info.SnapshotDataMovementInfo.Size > 0 { + d.Printf("\t\t\tRestored data Size (bytes): %d\n", info.SnapshotDataMovementInfo.Size) + } + if info.SnapshotDataMovementInfo.IncrementalSize != nil { + d.Printf("\t\t\tIncremental data Size (bytes): %d\n", *info.SnapshotDataMovementInfo.IncrementalSize) + } } else { d.Printf("\t\tData Movement: specify --details for more information\n") } diff --git a/pkg/cmd/util/output/restore_describer_test.go b/pkg/cmd/util/output/restore_describer_test.go index 6348dfaf0..cb2827315 100644 --- a/pkg/cmd/util/output/restore_describer_test.go +++ b/pkg/cmd/util/output/restore_describer_test.go @@ -2,6 +2,7 @@ package output import ( "bytes" + "context" "fmt" "testing" "text/tabwriter" @@ -10,11 +11,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1api "k8s.io/api/core/v1" + "k8s.io/utils/ptr" "github.com/vmware-tanzu/velero/internal/volume" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/itemoperation" + velerotest "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/results" ) @@ -309,11 +312,13 @@ CSI Snapshot Restores: PVName: "pv-3", RestoreMethod: volume.CSISnapshot, SnapshotDataMoved: true, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ - OperationID: "op-3", - DataMover: "velero", - UploaderType: "kopia", - Size: 1234, + SnapshotDataMovementInfo: &volume.RestoreSnapshotDataMovementInfo{ + OperationID: "op-3", + DataMover: "velero", + UploaderType: "kopia", + Size: 1234, + IncrementalSize: ptr.To(int64(500)), + RestoreType: "Incremental", }, }, }, @@ -325,6 +330,9 @@ CSI Snapshot Restores: Operation ID: op-3 Data Mover: velero Uploader Type: kopia + Restore Type: Incremental + Restored data Size (bytes): 1234 + Incremental data Size (bytes): 500 `, }, { @@ -336,7 +344,7 @@ CSI Snapshot Restores: PVName: "pv-3", RestoreMethod: volume.CSISnapshot, SnapshotDataMoved: true, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.RestoreSnapshotDataMovementInfo{ OperationID: "op-3", DataMover: "velero", UploaderType: "kopia", @@ -415,3 +423,16 @@ func TestDescribeResourceModifier(t *testing.T) { fmt.Println(d.buf.String()) require.Equal(t, expectOutput, d.buf.String()) } + +func TestDescribeRestore(t *testing.T) { + kbClient := velerotest.NewFakeControllerRuntimeClient(t) + restore := builder.ForRestore("velero", "test-restore"). + Backup("test-backup"). + ExistingResourcePolicy(string(velerov1api.ResourcePolicyTypeUpdate)). + ExistingVolumeDataPolicy(string(velerov1api.VolumeDataPolicyTypeFull)). + Result() + + out := DescribeRestore(context.Background(), kbClient, restore, nil, false, false, "") + assert.Contains(t, out, "Existing Resource Policy: update") + assert.Contains(t, out, "Existing Volume Data Policy: full") +} diff --git a/pkg/cmd/util/output/restore_structured_describer.go b/pkg/cmd/util/output/restore_structured_describer.go index 106a43541..31a71b68c 100644 --- a/pkg/cmd/util/output/restore_structured_describer.go +++ b/pkg/cmd/util/output/restore_structured_describer.go @@ -196,6 +196,13 @@ func describeRestoreSpecInSF(d *StructuredDescriber, spec velerov1api.RestoreSpe specInfo["existingResourcePolicy"] = emptyDisplay } + // existing volume data policy + if spec.ExistingVolumeDataPolicy != "" { + specInfo["existingVolumeDataPolicy"] = string(spec.ExistingVolumeDataPolicy) + } else { + specInfo["existingVolumeDataPolicy"] = emptyDisplay + } + specInfo["itemOperationTimeout"] = spec.ItemOperationTimeout.Duration.String() specInfo["preserveNodePorts"] = BoolPointerString(spec.PreserveNodePorts, "false", "true", "auto") @@ -360,12 +367,22 @@ func describeCSISnapshotsRestoresInSF(d *StructuredDescriber, restoreVolInfo []v } continue } + dmInfo := map[string]any{ + "operationID": info.SnapshotDataMovementInfo.OperationID, + "dataMover": info.SnapshotDataMovementInfo.DataMover, + "uploaderType": info.SnapshotDataMovementInfo.UploaderType, + } + if info.SnapshotDataMovementInfo.RestoreType != "" { + dmInfo["restoreType"] = info.SnapshotDataMovementInfo.RestoreType + } + if info.SnapshotDataMovementInfo.Size > 0 { + dmInfo["size"] = info.SnapshotDataMovementInfo.Size + } + if info.SnapshotDataMovementInfo.IncrementalSize != nil { + dmInfo["incrementalSize"] = *info.SnapshotDataMovementInfo.IncrementalSize + } csiRestores[key] = map[string]any{ - "dataMovement": map[string]any{ - "operationID": info.SnapshotDataMovementInfo.OperationID, - "dataMover": info.SnapshotDataMovementInfo.DataMover, - "uploaderType": info.SnapshotDataMovementInfo.UploaderType, - }, + "dataMovement": dmInfo, } } else { csiRestores[key] = map[string]any{ diff --git a/pkg/cmd/util/output/restore_structured_describer_test.go b/pkg/cmd/util/output/restore_structured_describer_test.go index 42fbb4d44..c6b1de2b7 100644 --- a/pkg/cmd/util/output/restore_structured_describer_test.go +++ b/pkg/cmd/util/output/restore_structured_describer_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/require" corev1api "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" "github.com/vmware-tanzu/velero/internal/volume" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" @@ -156,25 +157,27 @@ func TestDescribeRestoreSpecInSF(t *testing.T) { "excluded": emptyDisplay, "clusterScoped": "auto", }, - "namespaceMappings": emptyDisplay, - "labelSelector": emptyDisplay, - "orLabelSelectors": emptyDisplay, - "restorePVs": "auto", - "existingResourcePolicy": emptyDisplay, - "itemOperationTimeout": "0s", - "preserveNodePorts": "auto", + "namespaceMappings": emptyDisplay, + "labelSelector": emptyDisplay, + "orLabelSelectors": emptyDisplay, + "restorePVs": "auto", + "existingResourcePolicy": emptyDisplay, + "existingVolumeDataPolicy": emptyDisplay, + "itemOperationTimeout": "0s", + "preserveNodePorts": "auto", }, }, }, { name: "included namespaces wildcard treated as all", spec: velerov1api.RestoreSpec{ - BackupName: "backup-2", - IncludedNamespaces: []string{"*"}, - ExcludedNamespaces: []string{"kube-system"}, - IncludedResources: []string{"pods", "configmaps"}, - ExcludedResources: []string{"secrets"}, - ExistingResourcePolicy: velerov1api.ResourcePolicyTypeUpdate, + BackupName: "backup-2", + IncludedNamespaces: []string{"*"}, + ExcludedNamespaces: []string{"kube-system"}, + IncludedResources: []string{"pods", "configmaps"}, + ExcludedResources: []string{"secrets"}, + ExistingResourcePolicy: velerov1api.ResourcePolicyTypeUpdate, + ExistingVolumeDataPolicy: velerov1api.VolumeDataPolicyTypeFull, }, expect: map[string]any{ "spec": map[string]any{ @@ -188,13 +191,14 @@ func TestDescribeRestoreSpecInSF(t *testing.T) { "excluded": "secrets", "clusterScoped": "auto", }, - "namespaceMappings": emptyDisplay, - "labelSelector": emptyDisplay, - "orLabelSelectors": emptyDisplay, - "restorePVs": "auto", - "existingResourcePolicy": string(velerov1api.ResourcePolicyTypeUpdate), - "itemOperationTimeout": "0s", - "preserveNodePorts": "auto", + "namespaceMappings": emptyDisplay, + "labelSelector": emptyDisplay, + "orLabelSelectors": emptyDisplay, + "restorePVs": "auto", + "existingResourcePolicy": string(velerov1api.ResourcePolicyTypeUpdate), + "existingVolumeDataPolicy": string(velerov1api.VolumeDataPolicyTypeFull), + "itemOperationTimeout": "0s", + "preserveNodePorts": "auto", }, }, }, @@ -223,13 +227,14 @@ func TestDescribeRestoreSpecInSF(t *testing.T) { "excluded": emptyDisplay, "clusterScoped": "auto", }, - "namespaceMappings": emptyDisplay, - "labelSelector": emptyDisplay, - "orLabelSelectors": emptyDisplay, - "restorePVs": "auto", - "existingResourcePolicy": emptyDisplay, - "itemOperationTimeout": "0s", - "preserveNodePorts": "auto", + "namespaceMappings": emptyDisplay, + "labelSelector": emptyDisplay, + "orLabelSelectors": emptyDisplay, + "restorePVs": "auto", + "existingResourcePolicy": emptyDisplay, + "existingVolumeDataPolicy": emptyDisplay, + "itemOperationTimeout": "0s", + "preserveNodePorts": "auto", "resourceModifier": map[string]any{ "type": "ConfigMap", "name": "my-modifier", @@ -272,13 +277,14 @@ func TestDescribeRestoreSpecInSF(t *testing.T) { "excluded": emptyDisplay, "clusterScoped": "included", }, - "namespaceMappings": map[string]string{"ns-a": "ns-a-new"}, - "labelSelector": "app=nginx", - "orLabelSelectors": "env=prod or env=stage", - "restorePVs": "true", - "existingResourcePolicy": emptyDisplay, - "itemOperationTimeout": "0s", - "preserveNodePorts": "false", + "namespaceMappings": map[string]string{"ns-a": "ns-a-new"}, + "labelSelector": "app=nginx", + "orLabelSelectors": "env=prod or env=stage", + "restorePVs": "true", + "existingResourcePolicy": emptyDisplay, + "existingVolumeDataPolicy": emptyDisplay, + "itemOperationTimeout": "0s", + "preserveNodePorts": "false", "resourcePolicy": map[string]any{ "type": "configmap", "name": "volume-policy", @@ -467,10 +473,13 @@ func TestDescribeRestoreCSISnapshotsInSF_NoData(t *testing.T) { SnapshotDataMoved: true, PVCName: "pvc-3", PVCNamespace: "ns-3", - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ - OperationID: "op-3", - DataMover: "velero", - UploaderType: "kopia", + SnapshotDataMovementInfo: &volume.RestoreSnapshotDataMovementInfo{ + OperationID: "op-3", + DataMover: "velero", + UploaderType: "kopia", + Size: 1234, + IncrementalSize: ptr.To(int64(500)), + RestoreType: "Incremental", }, }, }, @@ -479,9 +488,12 @@ func TestDescribeRestoreCSISnapshotsInSF_NoData(t *testing.T) { "csiSnapshotRestores": map[string]any{ "ns-3/pvc-3": map[string]any{ "dataMovement": map[string]any{ - "operationID": "op-3", - "dataMover": "velero", - "uploaderType": "kopia", + "operationID": "op-3", + "dataMover": "velero", + "uploaderType": "kopia", + "size": int64(1234), + "incrementalSize": int64(500), + "restoreType": "Incremental", }, }, }, @@ -495,7 +507,7 @@ func TestDescribeRestoreCSISnapshotsInSF_NoData(t *testing.T) { SnapshotDataMoved: true, PVCName: "pvc-3", PVCNamespace: "ns-3", - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.RestoreSnapshotDataMovementInfo{ OperationID: "op-3", DataMover: "velero", UploaderType: "kopia", diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index 074053444..b1c1475b6 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -118,7 +118,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) { "pv-1": { BackupMethod: volume.PodVolumeBackup, PVName: "pv-1", - PVBInfo: &volume.PodVolumeInfo{ + PVBInfo: &volume.PodVolumeBackupInfo{ SnapshotHandle: "testSnapshotHandle", Size: 100, NodeName: "testNode", @@ -173,7 +173,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) { CSISnapshotInfo: &volume.CSISnapshotInfo{ Driver: "pd.csi.storage.gke.io", }, - SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{ + SnapshotDataMovementInfo: &volume.BackupSnapshotDataMovementInfo{ DataMover: "velero", }, },