Files
velero/pkg/cmd/util/output/backup_describer_test.go
2023-11-18 00:11:29 +08:00

643 lines
16 KiB
Go

package output
import (
"bytes"
"testing"
"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"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
)
func TestDescribeResourcePolicies(t *testing.T) {
input := &v1.TypedLocalObjectReference{
Kind: "configmap",
Name: "test-resource-policy",
}
d := &Describer{
Prefix: "",
out: &tabwriter.Writer{},
buf: &bytes.Buffer{},
}
d.out.Init(d.buf, 0, 8, 2, ' ', 0)
DescribeResourcePolicies(d, input)
d.out.Flush()
expect := `Resource policies:
Type: configmap
Name: test-resource-policy
`
assert.Equal(t, expect, d.buf.String())
}
func TestDescribeBackupSpec(t *testing.T) {
input1 := builder.ForBackup("test-ns", "test-backup-1").
IncludedNamespaces("inc-ns-1", "inc-ns-2").
ExcludedNamespaces("exc-ns-1", "exc-ns-2").
IncludedResources("inc-res-1", "inc-res-2").
ExcludedResources("exc-res-1", "exc-res-2").
StorageLocation("backup-location").
TTL(72 * time.Hour).
CSISnapshotTimeout(10 * time.Minute).
DataMover("mover").
Hooks(velerov1api.BackupHooks{
Resources: []velerov1api.BackupResourceHookSpec{
{
Name: "hook-1",
PreHooks: []velerov1api.BackupResourceHook{
{
Exec: &velerov1api.ExecHook{
Container: "hook-container-1",
Command: []string{"pre"},
OnError: velerov1api.HookErrorModeContinue,
},
},
},
PostHooks: []velerov1api.BackupResourceHook{
{
Exec: &velerov1api.ExecHook{
Container: "hook-container-1",
Command: []string{"post"},
OnError: velerov1api.HookErrorModeContinue,
},
},
},
IncludedNamespaces: []string{"hook-inc-ns-1", "hook-inc-ns-2"},
ExcludedNamespaces: []string{"hook-exc-ns-1", "hook-exc-ns-2"},
IncludedResources: []string{"hook-inc-res-1", "hook-inc-res-2"},
ExcludedResources: []string{"hook-exc-res-1", "hook-exc-res-2"},
},
},
}).Result().Spec
expect1 := `Namespaces:
Included: inc-ns-1, inc-ns-2
Excluded: exc-ns-1, exc-ns-2
Resources:
Included: inc-res-1, inc-res-2
Excluded: exc-res-1, exc-res-2
Cluster-scoped: auto
Label selector: <none>
Or label selector: <none>
Storage Location: backup-location
Velero-Native Snapshot PVs: auto
Snapshot Move Data: auto
Data Mover: mover
TTL: 72h0m0s
CSISnapshotTimeout: 10m0s
ItemOperationTimeout: 0s
Hooks:
Resources:
hook-1:
Namespaces:
Included: hook-inc-ns-1, hook-inc-ns-2
Excluded: hook-exc-ns-1, hook-exc-ns-2
Resources:
Included: hook-inc-res-1, hook-inc-res-2
Excluded: hook-exc-res-1, hook-exc-res-2
Label selector: <none>
Pre Exec Hook:
Container: hook-container-1
Command: pre
On Error: Continue
Timeout: 0s
Post Exec Hook:
Container: hook-container-1
Command: post
On Error: Continue
Timeout: 0s
`
input2 := builder.ForBackup("test-ns", "test-backup-2").
IncludedNamespaces("inc-ns-1", "inc-ns-2").
ExcludedNamespaces("exc-ns-1", "exc-ns-2").
IncludedClusterScopedResources("inc-cluster-res-1", "inc-cluster-res-2").
IncludedNamespaceScopedResources("inc-ns-res-1", "inc-ns-res-2").
ExcludedClusterScopedResources("exc-cluster-res-1", "exc-cluster-res-2").
ExcludedNamespaceScopedResources("exc-ns-res-1", "exc-ns-res-2").
StorageLocation("backup-location").
TTL(72 * time.Hour).
CSISnapshotTimeout(10 * time.Minute).
DataMover("mover").
Result().Spec
expect2 := `Namespaces:
Included: inc-ns-1, inc-ns-2
Excluded: exc-ns-1, exc-ns-2
Resources:
Included cluster-scoped: inc-cluster-res-1, inc-cluster-res-2
Excluded cluster-scoped: exc-cluster-res-1, exc-cluster-res-2
Included namespace-scoped: inc-ns-res-1, inc-ns-res-2
Excluded namespace-scoped: exc-ns-res-1, exc-ns-res-2
Label selector: <none>
Or label selector: <none>
Storage Location: backup-location
Velero-Native Snapshot PVs: auto
Snapshot Move Data: auto
Data Mover: mover
TTL: 72h0m0s
CSISnapshotTimeout: 10m0s
ItemOperationTimeout: 0s
Hooks: <none>
`
input3 := builder.ForBackup("test-ns", "test-backup-3").
StorageLocation("backup-location").
OrderedResources(map[string]string{
"kind1": "rs1-1, rs1-2",
}).Hooks(velerov1api.BackupHooks{
Resources: []velerov1api.BackupResourceHookSpec{
{
Name: "hook-1",
PreHooks: []velerov1api.BackupResourceHook{
{
Exec: &velerov1api.ExecHook{
Container: "hook-container-1",
Command: []string{"pre"},
OnError: velerov1api.HookErrorModeContinue,
},
},
},
PostHooks: []velerov1api.BackupResourceHook{
{
Exec: &velerov1api.ExecHook{
Container: "hook-container-1",
Command: []string{"post"},
OnError: velerov1api.HookErrorModeContinue,
},
},
},
},
},
}).Result().Spec
expect3 := `Namespaces:
Included: *
Excluded: <none>
Resources:
Included: *
Excluded: <none>
Cluster-scoped: auto
Label selector: <none>
Or label selector: <none>
Storage Location: backup-location
Velero-Native Snapshot PVs: auto
Snapshot Move Data: auto
Data Mover: velero
TTL: 0s
CSISnapshotTimeout: 0s
ItemOperationTimeout: 0s
Hooks:
Resources:
hook-1:
Namespaces:
Included: *
Excluded: <none>
Resources:
Included: *
Excluded: <none>
Label selector: <none>
Pre Exec Hook:
Container: hook-container-1
Command: pre
On Error: Continue
Timeout: 0s
Post Exec Hook:
Container: hook-container-1
Command: post
On Error: Continue
Timeout: 0s
OrderedResources:
kind1: rs1-1, rs1-2
`
testcases := []struct {
name string
input velerov1api.BackupSpec
expect string
}{
{
name: "old resource filter with hooks",
input: input1,
expect: expect1,
},
{
name: "new resource filter",
input: input2,
expect: expect2,
},
{
name: "old resource filter with hooks and ordered resources",
input: input3,
expect: expect3,
},
}
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)
DescribeBackupSpec(d, tc.input)
d.out.Flush()
assert.Equal(tt, tc.expect, d.buf.String())
})
}
}
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())
})
}
}
func TestDescribePodVolumeBackups(t *testing.T) {
pvb1 := builder.ForPodVolumeBackup("test-ns", "test-pvb1").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseCompleted).
BackupStorageLocation("bsl-1").
Volume("vol-1").
PodName("pod-1").
PodNamespace("pod-ns-1").
SnapshotID("snap-1").Result()
pvb2 := builder.ForPodVolumeBackup("test-ns1", "test-pvb2").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseCompleted).
BackupStorageLocation("bsl-1").
Volume("vol-2").
PodName("pod-2").
PodNamespace("pod-ns-1").
SnapshotID("snap-2").Result()
testcases := []struct {
name string
inputPVBList []velerov1api.PodVolumeBackup
inputDetails bool
expect string
}{
{
name: "empty list",
inputPVBList: []velerov1api.PodVolumeBackup{},
inputDetails: true,
expect: ``,
},
{
name: "2 completed pvbs no details",
inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2},
inputDetails: false,
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: ` Pod Volume Backups - kopia:
Completed:
pod-ns-1/pod-1: vol-1
pod-ns-1/pod-2: vol-2
`,
},
}
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)
describePodVolumeBackups(d, tc.inputDetails, tc.inputPVBList)
d.out.Flush()
assert.Equal(tt, tc.expect, d.buf.String())
})
}
}
func TestDescribeDeleteBackupRequests(t *testing.T) {
t1, err1 := time.Parse("2006-Jan-02", "2023-Jun-26")
require.Nil(t, err1)
dbr1 := builder.ForDeleteBackupRequest("velero", "dbr1").
ObjectMeta(builder.WithCreationTimestamp(t1)).
BackupName("bak-1").
Phase(velerov1api.DeleteBackupRequestPhaseProcessed).
Errors("some error").Result()
t2, err2 := time.Parse("2006-Jan-02", "2023-Jun-25")
require.Nil(t, err2)
dbr2 := builder.ForDeleteBackupRequest("velero", "dbr2").
ObjectMeta(builder.WithCreationTimestamp(t2)).
BackupName("bak-2").
Phase(velerov1api.DeleteBackupRequestPhaseInProgress).Result()
testcases := []struct {
name string
input []velerov1api.DeleteBackupRequest
expect string
}{
{
name: "empty list",
input: []velerov1api.DeleteBackupRequest{},
expect: `Deletion Attempts:
`,
},
{
name: "list with one failed and one in-progress request",
input: []velerov1api.DeleteBackupRequest{*dbr1, *dbr2},
expect: `Deletion Attempts (1 failed):
2023-06-26 00:00:00 +0000 UTC: Processed
Errors:
some error
2023-06-25 00:00:00 +0000 UTC: InProgress
`,
},
}
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)
DescribeDeleteBackupRequests(d, tc.input)
d.out.Flush()
assert.Equal(tt, tc.expect, d.buf.String())
})
}
}
func TestDescribeBackupItemOperation(t *testing.T) {
t1, err1 := time.Parse("2006-Jan-02", "2023-Jun-26")
require.Nil(t, err1)
t2, err2 := time.Parse("2006-Jan-02", "2023-Jun-25")
require.Nil(t, err2)
t3, err3 := time.Parse("2006-Jan-02", "2023-Jun-24")
require.Nil(t, err3)
input := builder.ForBackupOperation().
BackupName("backup-1").
OperationID("op-1").
BackupItemAction("action-1").
ResourceIdentifier("group", "rs-type", "ns", "rs-name").
Status(*builder.ForOperationStatus().
Phase(itemoperation.OperationPhaseFailed).
Error("operation error").
Progress(50, 100, "bytes").
Description("operation description").
Created(t3).
Started(t2).
Updated(t1).
Result()).Result()
expected := ` Operation for rs-type.group ns/rs-name:
Backup Item Action Plugin: action-1
Operation ID: op-1
Phase: Failed
Operation Error: operation error
Progress: 50 of 100 complete (bytes)
Progress description: operation description
Created: 2023-06-24 00:00:00 +0000 UTC
Started: 2023-06-25 00:00:00 +0000 UTC
Updated: 2023-06-26 00:00:00 +0000 UTC
`
d := &Describer{
Prefix: "",
out: &tabwriter.Writer{},
buf: &bytes.Buffer{},
}
d.out.Init(d.buf, 0, 8, 2, ' ', 0)
describeBackupItemOperation(d, input)
d.out.Flush()
assert.Equal(t, expected, d.buf.String())
}