issue 6695: add backup describe for CSI snapshot data movement

Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
This commit is contained in:
Lyndon-Li
2023-11-18 00:11:29 +08:00
parent c283edf4a5
commit b99ac448ae
8 changed files with 780 additions and 349 deletions
+2 -1
View File
@@ -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
+128 -100
View File
@@ -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<error getting snapshot info: %v>\n", err)
return
}
var snapshots []*volume.Snapshot
if err := json.NewDecoder(buf).Decode(&snapshots); err != nil {
d.Printf("Velero-Native Snapshots:\t<error reading snapshot info: %v>\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: <none included>\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 := "<N/A>"
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<error getting backup volume info: %v>\n", err)
return
}
var volumeInfos *volume.VolumeInfos
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
d.Printf("\t<error reading backup volume info: %v>\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: <none included>\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: <none included>\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 <none> 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: <none included>\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 {
+195 -85
View File
@@ -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: <N/A>
`
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: <none included>
`,
},
{
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())
})
+133 -103
View File
@@ -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("<error getting snapshot info: %v>", err)
return
}
var snapshots []*volume.Snapshot
if err := json.NewDecoder(buf).Decode(&snapshots); err != nil {
backupStatusInfo["errorGettingSnapshots"] = fmt.Sprintf("<error reading snapshot info: %v>", 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 := "<N/A>"
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"] = "<none included>"
}
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: <none included>"
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 <none> 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.
@@ -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))
}
+2
View File
@@ -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)
}
+4
View File
@@ -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))
}
+147
View File
@@ -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 <namespace-name>/<pod-name>.
PodName string `json:"podName"`
// The PVB-taken k8s node's name.
NodeName string `json:"nodeName"`
}