diff --git a/changelogs/unreleased/6940-Lyndon-Li b/changelogs/unreleased/6940-Lyndon-Li new file mode 100644 index 0000000000..6f614cb681 --- /dev/null +++ b/changelogs/unreleased/6940-Lyndon-Li @@ -0,0 +1 @@ +Fix issue #6647, add the --default-snapshot-move-data parameter to Velero install, so that users don't need to specify --snapshot-move-data per backup when they want to move snapshot data for all backups \ No newline at end of file diff --git a/pkg/cmd/cli/install/install.go b/pkg/cmd/cli/install/install.go index 43698af45c..ca6b5d997b 100644 --- a/pkg/cmd/cli/install/install.go +++ b/pkg/cmd/cli/install/install.go @@ -80,6 +80,7 @@ type Options struct { Features string DefaultVolumesToFsBackup bool UploaderType string + DefaultSnapshotMoveData bool } // BindFlags adds command line values to the options struct. @@ -120,6 +121,7 @@ func (o *Options) BindFlags(flags *pflag.FlagSet) { flags.StringVar(&o.Features, "features", o.Features, "Comma separated list of Velero feature flags to be set on the Velero deployment and the node-agent daemonset, if node-agent is enabled") flags.BoolVar(&o.DefaultVolumesToFsBackup, "default-volumes-to-fs-backup", o.DefaultVolumesToFsBackup, "Bool flag to configure Velero server to use pod volume file system backup by default for all volumes on all backups. Optional.") flags.StringVar(&o.UploaderType, "uploader-type", o.UploaderType, fmt.Sprintf("The type of uploader to transfer the data of pod volumes, the supported values are '%s', '%s'", uploader.ResticType, uploader.KopiaType)) + flags.BoolVar(&o.DefaultSnapshotMoveData, "default-snapshot-move-data", o.DefaultSnapshotMoveData, "Bool flag to configure Velero server to move data by default for all snapshots supporting data movement. Optional.") } // NewInstallOptions instantiates a new, default InstallOptions struct. @@ -146,6 +148,7 @@ func NewInstallOptions() *Options { CRDsOnly: false, DefaultVolumesToFsBackup: false, UploaderType: uploader.KopiaType, + DefaultSnapshotMoveData: false, } } @@ -209,6 +212,7 @@ func (o *Options) AsVeleroOptions() (*install.VeleroOptions, error) { Features: strings.Split(o.Features, ","), DefaultVolumesToFsBackup: o.DefaultVolumesToFsBackup, UploaderType: o.UploaderType, + DefaultSnapshotMoveData: o.DefaultSnapshotMoveData, }, nil } diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 20e14e3686..0e06392bef 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -135,6 +135,7 @@ type serverConfig struct { defaultVolumesToFsBackup bool uploaderType string maxConcurrentK8SConnections int + defaultSnapshotMoveData bool } func NewCommand(f client.Factory) *cobra.Command { @@ -163,6 +164,7 @@ func NewCommand(f client.Factory) *cobra.Command { defaultVolumesToFsBackup: podvolume.DefaultVolumesToFsBackup, uploaderType: uploader.ResticType, maxConcurrentK8SConnections: defaultMaxConcurrentK8SConnections, + defaultSnapshotMoveData: false, } ) @@ -233,6 +235,7 @@ func NewCommand(f client.Factory) *cobra.Command { command.Flags().DurationVar(&config.defaultItemOperationTimeout, "default-item-operation-timeout", config.defaultItemOperationTimeout, "How long to wait on asynchronous BackupItemActions and RestoreItemActions to complete before timing out. Default is 4 hours") command.Flags().DurationVar(&config.resourceTimeout, "resource-timeout", config.resourceTimeout, "How long to wait for resource processes which are not covered by other specific timeout parameters. Default is 10 minutes.") command.Flags().IntVar(&config.maxConcurrentK8SConnections, "max-concurrent-k8s-connections", config.maxConcurrentK8SConnections, "Max concurrent connections number that Velero can create with kube-apiserver. Default is 30.") + command.Flags().BoolVar(&config.defaultSnapshotMoveData, "default-snapshot-move-data", config.defaultSnapshotMoveData, "Move data by default for all snapshots supporting data movement.") return command } @@ -757,6 +760,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string s.csiSnapshotClient, s.credentialFileStore, s.config.maxConcurrentK8SConnections, + s.config.defaultSnapshotMoveData, ).SetupWithManager(s.mgr); err != nil { s.logger.Fatal(err, "unable to create controller", "controller", controller.Backup) } diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 2650f2e1a6..a55571a8d1 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -88,6 +88,7 @@ type backupReconciler struct { volumeSnapshotClient snapshotterClientSet.Interface credentialFileStore credentials.FileStore maxConcurrentK8SConnections int + defaultSnapshotMoveData bool } func NewBackupReconciler( @@ -113,6 +114,7 @@ func NewBackupReconciler( volumeSnapshotClient snapshotterClientSet.Interface, credentialStore credentials.FileStore, maxConcurrentK8SConnections int, + defaultSnapshotMoveData bool, ) *backupReconciler { b := &backupReconciler{ ctx: ctx, @@ -138,6 +140,7 @@ func NewBackupReconciler( volumeSnapshotClient: volumeSnapshotClient, credentialFileStore: credentialStore, maxConcurrentK8SConnections: maxConcurrentK8SConnections, + defaultSnapshotMoveData: defaultSnapshotMoveData, } b.updateTotalBackupMetric() return b @@ -353,6 +356,10 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg request.Spec.DefaultVolumesToFsBackup = &b.defaultVolumesToFsBackup } + if request.Spec.SnapshotMoveData == nil { + request.Spec.SnapshotMoveData = &b.defaultSnapshotMoveData + } + // find which storage location to use var serverSpecified bool if request.Spec.StorageLocation == "" { diff --git a/pkg/controller/backup_controller_test.go b/pkg/controller/backup_controller_test.go index 30296b7e5a..f187877330 100644 --- a/pkg/controller/backup_controller_test.go +++ b/pkg/controller/backup_controller_test.go @@ -583,6 +583,7 @@ func TestProcessBackupCompletions(t *testing.T) { backup *velerov1api.Backup backupLocation *velerov1api.BackupStorageLocation defaultVolumesToFsBackup bool + defaultSnapshotMoveData bool expectedResult *velerov1api.Backup backupExists bool existenceCheckError error @@ -615,6 +616,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.True(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -651,6 +653,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: "alt-loc", DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -690,6 +693,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: "read-write", DefaultVolumesToFsBackup: boolptr.True(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -727,6 +731,7 @@ func TestProcessBackupCompletions(t *testing.T) { TTL: metav1.Duration{Duration: 10 * time.Minute}, StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -764,6 +769,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.True(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -802,6 +808,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -840,6 +847,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.True(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -878,6 +886,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.True(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -916,6 +925,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -955,6 +965,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.True(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFailed, @@ -994,6 +1005,7 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.True(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFailed, @@ -1113,6 +1125,89 @@ func TestProcessBackupCompletions(t *testing.T) { Spec: velerov1api.BackupSpec{ StorageLocation: defaultBackupLocation.Name, DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.False(), + }, + Status: velerov1api.BackupStatus{ + Phase: velerov1api.BackupPhaseFinalizing, + Version: 1, + FormatVersion: "1.1.0", + StartTimestamp: ×tamp, + Expiration: ×tamp, + CSIVolumeSnapshotsAttempted: 1, + CSIVolumeSnapshotsCompleted: 0, + }, + }, + volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(), + }, + { + name: "backup with snapshot data movement set to true and defaultSnapshotMoveData set to false", + backup: defaultBackup().SnapshotMoveData(true).Result(), + backupLocation: defaultBackupLocation, + defaultVolumesToFsBackup: false, + defaultSnapshotMoveData: false, + expectedResult: &velerov1api.Backup{ + TypeMeta: metav1.TypeMeta{ + Kind: "Backup", + APIVersion: "velero.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: velerov1api.DefaultNamespace, + Name: "backup-1", + Annotations: map[string]string{ + "velero.io/source-cluster-k8s-major-version": "1", + "velero.io/source-cluster-k8s-minor-version": "16", + "velero.io/source-cluster-k8s-gitversion": "v1.16.4", + "velero.io/resource-timeout": "0s", + }, + Labels: map[string]string{ + "velero.io/storage-location": "loc-1", + }, + }, + Spec: velerov1api.BackupSpec{ + StorageLocation: defaultBackupLocation.Name, + DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.True(), + }, + Status: velerov1api.BackupStatus{ + Phase: velerov1api.BackupPhaseFinalizing, + Version: 1, + FormatVersion: "1.1.0", + StartTimestamp: ×tamp, + Expiration: ×tamp, + CSIVolumeSnapshotsAttempted: 0, + CSIVolumeSnapshotsCompleted: 0, + }, + }, + volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(), + }, + { + name: "backup with snapshot data movement set to false and defaultSnapshotMoveData set to true", + backup: defaultBackup().SnapshotMoveData(false).Result(), + backupLocation: defaultBackupLocation, + defaultVolumesToFsBackup: false, + defaultSnapshotMoveData: true, + expectedResult: &velerov1api.Backup{ + TypeMeta: metav1.TypeMeta{ + Kind: "Backup", + APIVersion: "velero.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: velerov1api.DefaultNamespace, + Name: "backup-1", + Annotations: map[string]string{ + "velero.io/source-cluster-k8s-major-version": "1", + "velero.io/source-cluster-k8s-minor-version": "16", + "velero.io/source-cluster-k8s-gitversion": "v1.16.4", + "velero.io/resource-timeout": "0s", + }, + Labels: map[string]string{ + "velero.io/storage-location": "loc-1", + }, + }, + Spec: velerov1api.BackupSpec{ + StorageLocation: defaultBackupLocation.Name, + DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.False(), }, Status: velerov1api.BackupStatus{ Phase: velerov1api.BackupPhaseFinalizing, @@ -1126,6 +1221,47 @@ func TestProcessBackupCompletions(t *testing.T) { }, volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(), }, + { + name: "backup with snapshot data movement not set and defaultSnapshotMoveData set to true", + backup: defaultBackup().Result(), + backupLocation: defaultBackupLocation, + defaultVolumesToFsBackup: false, + defaultSnapshotMoveData: true, + expectedResult: &velerov1api.Backup{ + TypeMeta: metav1.TypeMeta{ + Kind: "Backup", + APIVersion: "velero.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: velerov1api.DefaultNamespace, + Name: "backup-1", + Annotations: map[string]string{ + "velero.io/source-cluster-k8s-major-version": "1", + "velero.io/source-cluster-k8s-minor-version": "16", + "velero.io/source-cluster-k8s-gitversion": "v1.16.4", + "velero.io/resource-timeout": "0s", + }, + Labels: map[string]string{ + "velero.io/storage-location": "loc-1", + }, + }, + Spec: velerov1api.BackupSpec{ + StorageLocation: defaultBackupLocation.Name, + DefaultVolumesToFsBackup: boolptr.False(), + SnapshotMoveData: boolptr.True(), + }, + Status: velerov1api.BackupStatus{ + Phase: velerov1api.BackupPhaseFinalizing, + Version: 1, + FormatVersion: "1.1.0", + StartTimestamp: ×tamp, + Expiration: ×tamp, + CSIVolumeSnapshotsAttempted: 0, + CSIVolumeSnapshotsCompleted: 0, + }, + }, + volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(), + }, } for _, test := range tests { @@ -1178,6 +1314,7 @@ func TestProcessBackupCompletions(t *testing.T) { kbClient: fakeClient, defaultBackupLocation: defaultBackupLocation.Name, defaultVolumesToFsBackup: test.defaultVolumesToFsBackup, + defaultSnapshotMoveData: test.defaultSnapshotMoveData, backupTracker: NewBackupTracker(), metrics: metrics.NewServerMetrics(), clock: testclocks.NewFakeClock(now), diff --git a/pkg/install/deployment.go b/pkg/install/deployment.go index 0d076e3b45..9d61733a02 100644 --- a/pkg/install/deployment.go +++ b/pkg/install/deployment.go @@ -47,6 +47,7 @@ type podTemplateConfig struct { serviceAccountName string uploaderType string privilegedNodeAgent bool + defaultSnapshotMoveData bool } func WithImage(image string) podTemplateOption { @@ -137,6 +138,12 @@ func WithDefaultVolumesToFsBackup() podTemplateOption { } } +func WithDefaultSnapshotMoveData() podTemplateOption { + return func(c *podTemplateConfig) { + c.defaultSnapshotMoveData = true + } +} + func WithServiceAccountName(sa string) podTemplateOption { return func(c *podTemplateConfig) { c.serviceAccountName = sa @@ -174,6 +181,10 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment args = append(args, "--default-volumes-to-fs-backup=true") } + if c.defaultSnapshotMoveData { + args = append(args, "--default-snapshot-move-data=true") + } + if len(c.uploaderType) > 0 { args = append(args, fmt.Sprintf("--uploader-type=%s", c.uploaderType)) } diff --git a/pkg/install/resources.go b/pkg/install/resources.go index 9cf7b901c8..9e2e8e4dab 100644 --- a/pkg/install/resources.go +++ b/pkg/install/resources.go @@ -252,6 +252,7 @@ type VeleroOptions struct { Features []string DefaultVolumesToFsBackup bool UploaderType string + DefaultSnapshotMoveData bool } func AllCRDs() *unstructured.UnstructuredList { @@ -352,6 +353,10 @@ func AllResources(o *VeleroOptions) *unstructured.UnstructuredList { deployOpts = append(deployOpts, WithDefaultVolumesToFsBackup()) } + if o.DefaultSnapshotMoveData { + deployOpts = append(deployOpts, WithDefaultSnapshotMoveData()) + } + deploy := Deployment(o.Namespace, deployOpts...) if err := appendUnstructured(resources, deploy); err != nil {