/* Copyright 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 backup import ( "context" "fmt" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" flag "github.com/spf13/pflag" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake" factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test" "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/fake" versionedmocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/mocks" "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme" velerov1mocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1/mocks" "github.com/vmware-tanzu/velero/pkg/test" ) func TestCreateOptions_BuildBackup(t *testing.T) { o := NewCreateOptions() o.Labels.Set("velero.io/test=true") o.OrderedResources = "pods=p1,p2;persistentvolumeclaims=pvc1,pvc2" orders, err := ParseOrderedResources(o.OrderedResources) o.CSISnapshotTimeout = 20 * time.Minute o.ItemOperationTimeout = 20 * time.Minute assert.NoError(t, err) backup, err := o.BuildBackup(cmdtest.VeleroNameSpace) assert.NoError(t, err) assert.Equal(t, velerov1api.BackupSpec{ TTL: metav1.Duration{Duration: o.TTL}, IncludedNamespaces: []string(o.IncludeNamespaces), SnapshotVolumes: o.SnapshotVolumes.Value, IncludeClusterResources: o.IncludeClusterResources.Value, OrderedResources: orders, CSISnapshotTimeout: metav1.Duration{Duration: o.CSISnapshotTimeout}, ItemOperationTimeout: metav1.Duration{Duration: o.ItemOperationTimeout}, }, backup.Spec) assert.Equal(t, map[string]string{ "velero.io/test": "true", }, backup.GetLabels()) assert.Equal(t, map[string]string{ "pods": "p1,p2", "persistentvolumeclaims": "pvc1,pvc2", }, backup.Spec.OrderedResources) } func TestCreateOptions_BuildBackupFromSchedule(t *testing.T) { o := NewCreateOptions() o.FromSchedule = "test" o.client = fake.NewSimpleClientset() t.Run("inexistent schedule", func(t *testing.T) { _, err := o.BuildBackup(cmdtest.VeleroNameSpace) assert.Error(t, err) }) expectedBackupSpec := builder.ForBackup("test", cmdtest.VeleroNameSpace).IncludedNamespaces("test").Result().Spec schedule := builder.ForSchedule(cmdtest.VeleroNameSpace, "test").Template(expectedBackupSpec).ObjectMeta(builder.WithLabels("velero.io/test", "true"), builder.WithAnnotations("velero.io/test", "true")).Result() o.client.VeleroV1().Schedules(cmdtest.VeleroNameSpace).Create(context.TODO(), schedule, metav1.CreateOptions{}) t.Run("existing schedule", func(t *testing.T) { backup, err := o.BuildBackup(cmdtest.VeleroNameSpace) assert.NoError(t, err) assert.Equal(t, expectedBackupSpec, backup.Spec) assert.Equal(t, map[string]string{ "velero.io/test": "true", velerov1api.ScheduleNameLabel: "test", }, backup.GetLabels()) assert.Equal(t, map[string]string{ "velero.io/test": "true", }, backup.GetAnnotations()) }) t.Run("command line labels take precedence over schedule labels", func(t *testing.T) { o.Labels.Set("velero.io/test=yes,custom-label=true") backup, err := o.BuildBackup(cmdtest.VeleroNameSpace) assert.NoError(t, err) assert.Equal(t, expectedBackupSpec, backup.Spec) assert.Equal(t, map[string]string{ "velero.io/test": "yes", velerov1api.ScheduleNameLabel: "test", "custom-label": "true", }, backup.GetLabels()) }) } func TestCreateOptions_OrderedResources(t *testing.T) { _, err := ParseOrderedResources("pods= ns1/p1; ns1/p2; persistentvolumeclaims=ns2/pvc1, ns2/pvc2") assert.NotNil(t, err) orderedResources, err := ParseOrderedResources("pods= ns1/p1,ns1/p2 ; persistentvolumeclaims=ns2/pvc1,ns2/pvc2") assert.NoError(t, err) expectedResources := map[string]string{ "pods": "ns1/p1,ns1/p2", "persistentvolumeclaims": "ns2/pvc1,ns2/pvc2", } assert.Equal(t, orderedResources, expectedResources) orderedResources, err = ParseOrderedResources("pods= ns1/p1,ns1/p2 ; persistentvolumes=pv1,pv2") assert.NoError(t, err) expectedMixedResources := map[string]string{ "pods": "ns1/p1,ns1/p2", "persistentvolumes": "pv1,pv2", } assert.Equal(t, orderedResources, expectedMixedResources) } func TestCreateCommand(t *testing.T) { name := "nameToBeCreated" args := []string{name} t.Run("create a backup create command with full options except fromSchedule and wait, then run by create option", func(t *testing.T) { // create a factory f := &factorymocks.Factory{} // create command cmd := NewCreateCommand(f, "") assert.Equal(t, "Create a backup", cmd.Short) includeNamespaces := "app1,app2" excludeNamespaces := "pod1,pod2,pod3" includeResources := "sc,sts" excludeResources := "job" includeClusterScopedResources := "pv,ComponentStatus" excludeClusterScopedResources := "MutatingWebhookConfiguration,APIService" includeNamespaceScopedResources := "Endpoints,Event,PodTemplate" excludeNamespaceScopedResources := "Secret,MultiClusterIngress" labels := "c=foo,b=woo" storageLocation := "bsl-name-1" snapshotLocations := "region=minio" selector := "a=pod" orderedResources := "pod=pod1,pod2,pod3" csiSnapshotTimeout := "8m30s" itemOperationTimeout := "99h1m6s" snapshotVolumes := "false" snapshotMoveData := "true" includeClusterResources := "true" defaultVolumesToFsBackup := "true" resPoliciesConfigmap := "cm-name-2" dataMover := "velero" flags := new(flag.FlagSet) o := NewCreateOptions() o.BindFlags(flags) o.BindWait(flags) o.BindFromSchedule(flags) flags.Parse([]string{"--include-namespaces", includeNamespaces}) flags.Parse([]string{"--exclude-namespaces", excludeNamespaces}) flags.Parse([]string{"--include-resources", includeResources}) flags.Parse([]string{"--exclude-resources", excludeResources}) flags.Parse([]string{"--include-cluster-scoped-resources", includeClusterScopedResources}) flags.Parse([]string{"--exclude-cluster-scoped-resources", excludeClusterScopedResources}) flags.Parse([]string{"--include-namespace-scoped-resources", includeNamespaceScopedResources}) flags.Parse([]string{"--exclude-namespace-scoped-resources", excludeNamespaceScopedResources}) flags.Parse([]string{"--labels", labels}) flags.Parse([]string{"--storage-location", storageLocation}) flags.Parse([]string{"--volume-snapshot-locations", snapshotLocations}) flags.Parse([]string{"--selector", selector}) flags.Parse([]string{"--ordered-resources", orderedResources}) flags.Parse([]string{"--csi-snapshot-timeout", csiSnapshotTimeout}) flags.Parse([]string{"--item-operation-timeout", itemOperationTimeout}) flags.Parse([]string{fmt.Sprintf("--snapshot-volumes=%s", snapshotVolumes)}) flags.Parse([]string{fmt.Sprintf("--snapshot-move-data=%s", snapshotMoveData)}) flags.Parse([]string{"--include-cluster-resources", includeClusterResources}) flags.Parse([]string{"--default-volumes-to-fs-backup", defaultVolumesToFsBackup}) flags.Parse([]string{"--resource-policies-configmap", resPoliciesConfigmap}) flags.Parse([]string{"--data-mover", dataMover}) //flags.Parse([]string{"--wait"}) backups := &velerov1mocks.BackupInterface{} veleroV1 := &velerov1mocks.VeleroV1Interface{} client := &versionedmocks.Interface{} bk := &velerov1api.Backup{} backups.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) veleroV1.On("Backups", mock.Anything).Return(backups, nil) client.On("VeleroV1").Return(veleroV1, nil) f.On("Client").Return(client, nil) f.On("Namespace").Return(mock.Anything) f.On("KubebuilderClient").Return(nil, nil) //Complete e := o.Complete(args, f) assert.NoError(t, e) //Validate e = o.Validate(cmd, args, f) assert.Contains(t, e.Error(), "include-resources, exclude-resources and include-cluster-resources are old filter parameters") assert.Contains(t, e.Error(), "include-cluster-scoped-resources, exclude-cluster-scoped-resources, include-namespace-scoped-resources and exclude-namespace-scoped-resources are new filter parameters.\nThey cannot be used together") //cmd e = o.Run(cmd, f) assert.NoError(t, e) //Execute cmd.SetArgs([]string{"bk-name-exe"}) e = cmd.Execute() assert.NoError(t, e) // verify all options are set as expected assert.Equal(t, name, o.Name) assert.Equal(t, includeNamespaces, o.IncludeNamespaces.String()) assert.Equal(t, excludeNamespaces, o.ExcludeNamespaces.String()) assert.Equal(t, includeResources, o.IncludeResources.String()) assert.Equal(t, excludeResources, o.ExcludeResources.String()) assert.Equal(t, includeClusterScopedResources, o.IncludeClusterScopedResources.String()) assert.Equal(t, excludeClusterScopedResources, o.ExcludeClusterScopedResources.String()) assert.Equal(t, includeNamespaceScopedResources, o.IncludeNamespaceScopedResources.String()) assert.Equal(t, excludeNamespaceScopedResources, o.ExcludeNamespaceScopedResources.String()) assert.Equal(t, true, test.CompareSlice(strings.Split(labels, ","), strings.Split(o.Labels.String(), ","))) assert.Equal(t, storageLocation, o.StorageLocation) assert.Equal(t, snapshotLocations, strings.Split(o.SnapshotLocations[0], ",")[0]) assert.Equal(t, selector, o.Selector.String()) assert.Equal(t, orderedResources, o.OrderedResources) assert.Equal(t, csiSnapshotTimeout, o.CSISnapshotTimeout.String()) assert.Equal(t, itemOperationTimeout, o.ItemOperationTimeout.String()) assert.Equal(t, snapshotVolumes, o.SnapshotVolumes.String()) assert.Equal(t, snapshotMoveData, o.SnapshotMoveData.String()) assert.Equal(t, includeClusterResources, o.IncludeClusterResources.String()) assert.Equal(t, defaultVolumesToFsBackup, o.DefaultVolumesToFsBackup.String()) assert.Equal(t, resPoliciesConfigmap, o.ResPoliciesConfigmap) assert.Equal(t, dataMover, o.DataMover) //assert.Equal(t, true, o.Wait) // verify oldAndNewFilterParametersUsedTogether mix := o.oldAndNewFilterParametersUsedTogether() assert.Equal(t, true, mix) }) t.Run("create a backup create command with specific storage-location setting", func(t *testing.T) { bsl := "bsl-1" // create a factory f := &factorymocks.Factory{} cmd := NewCreateCommand(f, "") backups := &velerov1mocks.BackupInterface{} veleroV1 := &velerov1mocks.VeleroV1Interface{} client := &versionedmocks.Interface{} kbclient := clientfake.NewClientBuilder().WithScheme(scheme.Scheme).Build() bk := &velerov1api.Backup{} backups.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) veleroV1.On("Backups", mock.Anything).Return(backups, nil) client.On("VeleroV1").Return(veleroV1, nil) f.On("Client").Return(client, nil) f.On("Namespace").Return(mock.Anything) f.On("KubebuilderClient").Return(kbclient, nil) flags := new(flag.FlagSet) o := NewCreateOptions() o.BindFlags(flags) o.BindWait(flags) o.BindFromSchedule(flags) flags.Parse([]string{"--include-namespaces", "ns-1"}) flags.Parse([]string{"--storage-location", bsl}) // Complete e := o.Complete(args, f) assert.NoError(t, e) // Validate e = o.Validate(cmd, args, f) assert.Contains(t, e.Error(), fmt.Sprintf("backupstoragelocations.velero.io \"%s\" not found", bsl)) }) t.Run("create a backup create command with specific volume-snapshot-locations setting", func(t *testing.T) { vslName := "vsl-1" // create a factory f := &factorymocks.Factory{} cmd := NewCreateCommand(f, "") vsls := &velerov1mocks.VolumeSnapshotLocationInterface{} veleroV1 := &velerov1mocks.VeleroV1Interface{} client := &versionedmocks.Interface{} kbclient := clientfake.NewClientBuilder().WithScheme(scheme.Scheme).Build() vsl := &velerov1api.VolumeSnapshotLocation{} vsls.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(vsl, nil) veleroV1.On("VolumeSnapshotLocations", mock.Anything).Return(vsls, nil) client.On("VeleroV1").Return(veleroV1, nil) f.On("Client").Return(client, nil) f.On("Namespace").Return(mock.Anything) f.On("KubebuilderClient").Return(kbclient, nil) flags := new(flag.FlagSet) o := NewCreateOptions() o.BindFlags(flags) o.BindWait(flags) o.BindFromSchedule(flags) flags.Parse([]string{"--include-namespaces", "ns-1"}) flags.Parse([]string{"--volume-snapshot-locations", vslName}) // Complete e := o.Complete(args, f) assert.NoError(t, e) // Validate e = o.Validate(cmd, args, f) assert.NoError(t, e) }) t.Run("create the other create command with fromSchedule option for Run() other branches", func(t *testing.T) { f := &factorymocks.Factory{} c := NewCreateCommand(f, "") assert.Equal(t, "Create a backup", c.Short) flags := new(flag.FlagSet) o := NewCreateOptions() o.BindFlags(flags) o.BindWait(flags) o.BindFromSchedule(flags) fromSchedule := "schedule-name-1" flags.Parse([]string{"--from-schedule", fromSchedule}) backups := &velerov1mocks.BackupInterface{} bk := &velerov1api.Backup{} schedules := &velerov1mocks.ScheduleInterface{} veleroV1 := &velerov1mocks.VeleroV1Interface{} client := &versionedmocks.Interface{} kbclient := clientfake.NewClientBuilder().WithScheme(scheme.Scheme).Build() sd := &velerov1api.Schedule{} backups.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) veleroV1.On("Backups", mock.Anything).Return(backups, nil) schedules.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(sd, nil) veleroV1.On("Schedules", mock.Anything).Return(schedules, nil) client.On("VeleroV1").Return(veleroV1, nil) f.On("Client").Return(client, nil) f.On("Namespace").Return(mock.Anything) f.On("KubebuilderClient").Return(kbclient, nil) e := o.Complete(args, f) assert.NoError(t, e) e = o.Run(c, f) assert.NoError(t, e) c.SetArgs([]string{"bk-1"}) e = c.Execute() assert.NoError(t, e) }) }