velero/pkg/cmd/util/output/restore_describer_test.go

418 lines
11 KiB
Go

package output
import (
"bytes"
"fmt"
"testing"
"text/tabwriter"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
"github.com/vmware-tanzu/velero/internal/volume"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/itemoperation"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/results"
)
func TestDescribeResult(t *testing.T) {
testcases := []struct {
name string
inputName string
inputResult results.Result
expect string
}{
{
name: "result without ns warns",
inputName: "restore-1",
inputResult: results.Result{
Velero: []string{"velero-msg-1", "velero-msg-2"},
Cluster: []string{"cluster-msg-1", "cluster-msg-2"},
Namespaces: map[string][]string{},
},
expect: `restore-1:
Velero: velero-msg-1
velero-msg-2
Cluster: cluster-msg-1
cluster-msg-2
Namespaces: <none>
`,
},
{
name: "result with ns warns",
inputName: "restore-2",
inputResult: results.Result{
Velero: []string{"velero-msg-1", "velero-msg-2"},
Cluster: []string{"cluster-msg-1", "cluster-msg-2"},
Namespaces: map[string][]string{
"ns-1": {"ns-1-warn-1", "ns-1-warn-2"},
},
},
expect: `restore-2:
Velero: velero-msg-1
velero-msg-2
Cluster: cluster-msg-1
cluster-msg-2
Namespaces:
ns-1: ns-1-warn-1
ns-1-warn-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)
describeResult(d, tc.inputName, tc.inputResult)
d.out.Flush()
assert.Equal(tt, tc.expect, d.buf.String())
})
}
}
func TestDescribeRestoreItemOperation(t *testing.T) {
t1, err1 := time.Parse("2006-Jan-02", "2023-Jun-26")
require.NoError(t, err1)
t2, err2 := time.Parse("2006-Jan-02", "2023-Jun-25")
require.NoError(t, err2)
t3, err3 := time.Parse("2006-Jan-02", "2023-Jun-24")
require.NoError(t, err3)
input := builder.ForRestoreOperation().
RestoreName("restore-1").
OperationID("op-1").
RestoreItemAction("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:
Restore 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)
describeRestoreItemOperation(d, input)
d.out.Flush()
assert.Equal(t, expected, d.buf.String())
}
func TestDescribePodVolumeRestores(t *testing.T) {
pvr1 := builder.ForPodVolumeRestore("velero", "pvr-1").
UploaderType("kopia").
Phase(velerov1api.PodVolumeRestorePhaseCompleted).
BackupStorageLocation("bsl-1").
Volume("vol-1").
PodName("pod-1").
PodNamespace("pod-ns-1").
SnapshotID("snap-1").Result()
pvr2 := builder.ForPodVolumeRestore("velero", "pvr-2").
UploaderType("kopia").
Phase(velerov1api.PodVolumeRestorePhaseCompleted).
BackupStorageLocation("bsl-1").
Volume("vol-2").
PodName("pod-2").
PodNamespace("pod-ns-1").
SnapshotID("snap-2").Result()
testcases := []struct {
name string
inputPVRList []velerov1api.PodVolumeRestore
inputDetails bool
expect string
}{
{
name: "empty list",
inputPVRList: []velerov1api.PodVolumeRestore{},
inputDetails: true,
expect: ``,
},
{
name: "2 completed pvrs no details",
inputPVRList: []velerov1api.PodVolumeRestore{*pvr1, *pvr2},
inputDetails: false,
expect: `kopia Restores (specify --details for more information):
Completed: 2
`,
},
{
name: "2 completed pvrs with details",
inputPVRList: []velerov1api.PodVolumeRestore{*pvr1, *pvr2},
inputDetails: true,
expect: `kopia Restores:
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)
describePodVolumeRestores(d, tc.inputPVRList, tc.inputDetails)
d.out.Flush()
assert.Equal(tt, tc.expect, d.buf.String())
})
}
}
func TestDescribeUploaderConfigForRestore(t *testing.T) {
cases := []struct {
name string
spec velerov1api.RestoreSpec
expected string
}{
{
name: "UploaderConfigNil",
spec: velerov1api.RestoreSpec{}, // Create a RestoreSpec with nil UploaderConfig
expected: "",
},
{
name: "test",
spec: velerov1api.RestoreSpec{
UploaderConfig: &velerov1api.UploaderConfigForRestore{
WriteSparseFiles: boolptr.True(),
ParallelFilesDownload: 4,
},
},
expected: "\nUploader config:\n Write Sparse Files: true\n Parallel Restore: 4\n",
},
{
name: "WriteSparseFiles test",
spec: velerov1api.RestoreSpec{
UploaderConfig: &velerov1api.UploaderConfigForRestore{
WriteSparseFiles: boolptr.True(),
},
},
expected: "\nUploader config:\n Write Sparse Files: true\n",
},
{
name: "ParallelFilesDownload test",
spec: velerov1api.RestoreSpec{
UploaderConfig: &velerov1api.UploaderConfigForRestore{
ParallelFilesDownload: 4,
},
},
expected: "\nUploader config:\n Parallel Restore: 4\n",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
d := &Describer{
Prefix: "",
out: &tabwriter.Writer{},
buf: &bytes.Buffer{},
}
d.out.Init(d.buf, 0, 8, 2, ' ', 0)
describeUploaderConfigForRestore(d, tc.spec)
d.out.Flush()
assert.Equal(t, tc.expected, d.buf.String(), "Output should match expected")
})
}
}
func TestDescribeCSISnapshotsRestore(t *testing.T) {
cases := []struct {
name string
inputVolInfoList []volume.RestoreVolumeInfo
inputDetail bool
expect string
}{
{
name: "empty list",
inputVolInfoList: []volume.RestoreVolumeInfo{},
inputDetail: true,
expect: `
CSI Snapshot Restores: <none included>
`,
},
{
name: "list with non CSI snapshot",
inputVolInfoList: []volume.RestoreVolumeInfo{
{
PVCName: "pvc-2",
PVCNamespace: "ns-2",
PVName: "pv-2",
RestoreMethod: volume.NativeSnapshot,
NativeSnapshotInfo: &volume.NativeSnapshotInfo{
SnapshotHandle: "snap-1",
},
},
},
inputDetail: true,
expect: `
CSI Snapshot Restores: <none included>
`,
},
{
name: "CSI restore without data movement, detailed",
inputVolInfoList: []volume.RestoreVolumeInfo{
{
PVCName: "pvc-1",
PVCNamespace: "ns-1",
PVName: "pv-1",
RestoreMethod: volume.CSISnapshot,
CSISnapshotInfo: &volume.CSISnapshotInfo{
SnapshotHandle: "snapshot-handle-1",
Size: 1234,
Driver: "csi.test.driver",
VSCName: "content-1",
},
},
},
inputDetail: true,
expect: `
CSI Snapshot Restores:
ns-1/pvc-1:
Snapshot:
Snapshot Content Name: content-1
Storage Snapshot ID: snapshot-handle-1
CSI Driver: csi.test.driver
`,
},
{
name: "CSI restore with data movement, detailed",
inputVolInfoList: []volume.RestoreVolumeInfo{
{
PVCName: "pvc-3",
PVCNamespace: "ns-3",
PVName: "pv-3",
RestoreMethod: volume.CSISnapshot,
SnapshotDataMoved: true,
SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{
OperationID: "op-3",
DataMover: "velero",
UploaderType: "kopia",
Size: 1234,
},
},
},
inputDetail: true,
expect: `
CSI Snapshot Restores:
ns-3/pvc-3:
Data Movement:
Operation ID: op-3
Data Mover: velero
Uploader Type: kopia
`,
},
{
name: "vol info with different entries, without details",
inputVolInfoList: []volume.RestoreVolumeInfo{
{
PVCName: "pvc-3",
PVCNamespace: "ns-3",
PVName: "pv-3",
RestoreMethod: volume.CSISnapshot,
SnapshotDataMoved: true,
SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{
OperationID: "op-3",
DataMover: "velero",
UploaderType: "kopia",
Size: 1234,
},
},
{
PVCName: "pvc-2",
PVCNamespace: "ns-2",
PVName: "pv-2",
RestoreMethod: volume.NativeSnapshot,
NativeSnapshotInfo: &volume.NativeSnapshotInfo{
SnapshotHandle: "snap-1",
},
},
{
PVCName: "pvc-1",
PVCNamespace: "ns-1",
PVName: "pv-1",
RestoreMethod: volume.CSISnapshot,
CSISnapshotInfo: &volume.CSISnapshotInfo{
SnapshotHandle: "snapshot-handle-1",
Size: 1234,
Driver: "csi.test.driver",
VSCName: "content-1",
},
},
},
inputDetail: false,
expect: `
CSI Snapshot Restores:
ns-1/pvc-1:
Snapshot: specify --details for more information
ns-3/pvc-3:
Data Movement: specify --details for more information
`,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
d := &Describer{
Prefix: "",
out: &tabwriter.Writer{},
buf: &bytes.Buffer{},
}
d.out.Init(d.buf, 0, 8, 2, ' ', 0)
describeCSISnapshotsRestores(d, tc.inputVolInfoList, tc.inputDetail)
d.out.Flush()
assert.Equal(t, tc.expect, d.buf.String())
})
}
}
func TestDescribeResourceModifier(t *testing.T) {
d := &Describer{
Prefix: "",
out: &tabwriter.Writer{},
buf: &bytes.Buffer{},
}
d.out.Init(d.buf, 0, 8, 2, ' ', 0)
DescribeResourceModifier(d, &corev1api.TypedLocalObjectReference{
APIGroup: &corev1api.SchemeGroupVersion.Group,
Kind: "ConfigMap",
Name: "resourceModifier",
})
d.out.Flush()
expectOutput := `Resource modifier:
Type: ConfigMap
Name: resourceModifier
`
fmt.Println(d.buf.String())
require.Equal(t, expectOutput, d.buf.String())
}