mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-20 15:04:17 +00:00
issue 6695: add backup describe for CSI snapshot data movement
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
Reference in New Issue
Block a user