From b64e96e4432d266fe5a7680aa5406e160cded824 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Wed, 18 May 2022 00:55:43 -0400 Subject: [PATCH] OADP-524 mtc operator type (#701) * OADP-524 mtc operator type Enables alternative behavior when OADP is consumed by MTC * error case test name typo --- api/v1alpha1/oadp_types.go | 3 + controllers/validator.go | 4 + controllers/validator_test.go | 52 +++++ controllers/velero.go | 8 +- controllers/velero_test.go | 309 +++++++++++++++++++++++++ pkg/credentials/credentials.go | 4 +- tests/e2e/dpa_deployment_suite_test.go | 6 +- 7 files changed, 381 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/oadp_types.go b/api/v1alpha1/oadp_types.go index b6eab82fdd..47e9d67127 100644 --- a/api/v1alpha1/oadp_types.go +++ b/api/v1alpha1/oadp_types.go @@ -56,6 +56,9 @@ const CSIPluginImageKey UnsupportedImageKey = "csiPluginImageFqin" const ResticRestoreImageKey UnsupportedImageKey = "resticRestoreImageFqin" const RegistryImageKey UnsupportedImageKey = "registryImageFqin" const KubeVirtPluginImageKey UnsupportedImageKey = "kubevirtPluginImageFqin" +const OperatorTypeKey UnsupportedImageKey = "operator-type" + +const OperatorTypeMTC = "mtc" type VeleroConfig struct { // FeatureFlags defines the list of features to enable for Velero instance diff --git a/controllers/validator.go b/controllers/validator.go index b620933a68..9c4ea5d801 100644 --- a/controllers/validator.go +++ b/controllers/validator.go @@ -49,6 +49,10 @@ func (r *DPAReconciler) ValidateDataProtectionCR(log logr.Logger) (bool, error) } } + if val, found := dpa.Spec.UnsupportedOverrides[oadpv1alpha1.OperatorTypeKey]; found && val != oadpv1alpha1.OperatorTypeMTC { + return false, errors.New("only mtc operator type override is supported") + } + if _, err := r.ValidateVeleroPlugins(r.Log); err != nil { return false, err } diff --git a/controllers/validator_test.go b/controllers/validator_test.go index adc41fc541..e980196bae 100644 --- a/controllers/validator_test.go +++ b/controllers/validator_test.go @@ -45,6 +45,58 @@ func TestDPAReconciler_ValidateDataProtectionCR(t *testing.T) { wantErr: false, want: true, }, + { + name: "given valid DPA CR, no default backup location, no backup images, MTC type override, no error case", + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-DPA-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + NoDefaultBackupLocation: true, + }, + }, + BackupImages: pointer.Bool(false), + UnsupportedOverrides: map[oadpv1alpha1.UnsupportedImageKey]string{ + oadpv1alpha1.OperatorTypeKey: oadpv1alpha1.OperatorTypeMTC, + }, + }, + }, + objects: []client.Object{}, + wantErr: false, + want: true, + }, + { + name: "given valid DPA CR, no default backup location, no backup images, notMTC type override, error case", + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-DPA-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + NoDefaultBackupLocation: true, + }, + }, + BackupImages: pointer.Bool(false), + UnsupportedOverrides: map[oadpv1alpha1.UnsupportedImageKey]string{ + oadpv1alpha1.OperatorTypeKey: "not" + oadpv1alpha1.OperatorTypeMTC, + }, + }, + }, + objects: []client.Object{}, + wantErr: true, + want: false, + }, { name: "given valid DPA CR, no default backup location, backup images cannot be nil, error case", dpa: &oadpv1alpha1.DataProtectionApplication{ diff --git a/controllers/velero.go b/controllers/velero.go index f52dd4c1b8..2061e2f632 100644 --- a/controllers/velero.go +++ b/controllers/velero.go @@ -713,10 +713,16 @@ func (r DPAReconciler) noDefaultCredentials(dpa oadpv1alpha1.DataProtectionAppli providerNeedsDefaultCreds := map[string]bool{} hasCloudStorage := false if dpa.Spec.Configuration.Velero.NoDefaultBackupLocation { + needDefaultCred := false + + if dpa.Spec.UnsupportedOverrides[oadpv1alpha1.OperatorTypeKey] == oadpv1alpha1.OperatorTypeMTC { + // MTC requires default credentials + needDefaultCred = true + } // go through cloudprovider plugins and mark providerNeedsDefaultCreds to false for _, provider := range dpa.Spec.Configuration.Velero.DefaultPlugins { if psf, ok := credentials.PluginSpecificFields[provider]; ok && psf.IsCloudProvider { - providerNeedsDefaultCreds[psf.PluginName] = false + providerNeedsDefaultCreds[psf.PluginName] = needDefaultCred } } } else { diff --git a/controllers/velero_test.go b/controllers/velero_test.go index 762128b84b..3e980febf0 100644 --- a/controllers/velero_test.go +++ b/controllers/velero_test.go @@ -246,6 +246,315 @@ func TestDPAReconciler_buildVeleroDeployment(t *testing.T) { }, }, }, + { + name: "given valid DPA CR, noDefaultBackupLocation, unsupportedOverrides operatorType MTC, vel deployment has secret volumes", + veleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/name": common.Velero, + "app.kubernetes.io/instance": "test-Velero-CR", + "app.kubernetes.io/managed-by": common.OADPOperator, + "app.kubernetes.io/component": Server, + "component": "velero", + "deploy": "velero", + oadpv1alpha1.OadpOperatorLabel: "True", + }, + }, + }, + }, + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-Velero-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + NoDefaultBackupLocation: true, + DefaultPlugins: allDefaultPluginsList, + }, + }, + UnsupportedOverrides: map[oadpv1alpha1.UnsupportedImageKey]string{ + oadpv1alpha1.OperatorTypeKey: oadpv1alpha1.OperatorTypeMTC, + }, + }, + }, + wantErr: false, + wantVeleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + Labels: map[string]string{ + "app.kubernetes.io/name": common.Velero, + "app.kubernetes.io/instance": "test-Velero-CR", + "app.kubernetes.io/managed-by": common.OADPOperator, + "app.kubernetes.io/component": Server, + "component": "velero", + oadpv1alpha1.OadpOperatorLabel: "True", + }, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/name": common.Velero, + "app.kubernetes.io/instance": "test-Velero-CR", + "app.kubernetes.io/managed-by": common.OADPOperator, + "app.kubernetes.io/component": Server, + "component": "velero", + "deploy": "velero", + oadpv1alpha1.OadpOperatorLabel: "True", + }, + }, + Replicas: pointer.Int32(1), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/name": common.Velero, + "app.kubernetes.io/instance": "test-Velero-CR", + "app.kubernetes.io/managed-by": common.OADPOperator, + "app.kubernetes.io/component": Server, + "component": "velero", + "deploy": "velero", + oadpv1alpha1.OadpOperatorLabel: "True", + }, + Annotations: map[string]string{ + "prometheus.io/scrape": "true", + "prometheus.io/port": "8085", + "prometheus.io/path": "/metrics", + }, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyAlways, + ServiceAccountName: common.Velero, + Containers: []corev1.Container{ + { + Name: common.Velero, + Image: common.VeleroImage, + ImagePullPolicy: corev1.PullAlways, + Ports: []corev1.ContainerPort{ + { + Name: "metrics", + ContainerPort: 8085, + }, + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + Command: []string{"/velero"}, + Args: []string{ + "server", + "--features=EnableCSI", + "--restic-timeout=1h", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins", + MountPath: "/plugins", + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + { + Name: "cloud-credentials", + MountPath: "/credentials", + }, + { + Name: "cloud-credentials-gcp", + MountPath: "/credentials-gcp", + }, + { + Name: "cloud-credentials-azure", + MountPath: "/credentials-azure", + }, + }, + Env: []corev1.EnvVar{ + { + Name: common.VeleroScratchDirEnvKey, + Value: "/scratch", + }, + { + Name: common.VeleroNamespaceEnvKey, + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: common.LDLibraryPathEnvKey, + Value: "/plugins", + }, + { + Name: common.AWSSharedCredentialsFileEnvKey, + Value: "/credentials/cloud", + }, + { + Name: common.GCPCredentialsEnvKey, + Value: "/credentials-gcp/cloud", + }, + { + Name: common.AzureCredentialsFileEnvKey, + Value: "/credentials-azure/cloud", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "plugins", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "scratch", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "certs", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "cloud-credentials", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "cloud-credentials", + }, + }, + }, + { + Name: "cloud-credentials-gcp", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "cloud-credentials-gcp", + }, + }, + }, + { + Name: "cloud-credentials-azure", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "cloud-credentials-azure", + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Image: common.AWSPluginImage, + Name: common.VeleroPluginForAWS, + ImagePullPolicy: corev1.PullAlways, + Resources: corev1.ResourceRequirements{}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/target", + Name: "plugins", + }, + }, + }, + { + Image: common.GCPPluginImage, + Name: common.VeleroPluginForGCP, + ImagePullPolicy: corev1.PullAlways, + Resources: corev1.ResourceRequirements{}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/target", + Name: "plugins", + }, + }, + }, + { + Image: common.AzurePluginImage, + Name: common.VeleroPluginForAzure, + ImagePullPolicy: corev1.PullAlways, + Resources: corev1.ResourceRequirements{}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/target", + Name: "plugins", + }, + }, + }, + { + Image: common.KubeVirtPluginImage, + Name: common.KubeVirtPlugin, + ImagePullPolicy: corev1.PullAlways, + Resources: corev1.ResourceRequirements{}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/target", + Name: "plugins", + }, + }, + }, + { + Image: common.OpenshiftPluginImage, + Name: common.VeleroPluginForOpenshift, + ImagePullPolicy: corev1.PullAlways, + Resources: corev1.ResourceRequirements{}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/target", + Name: "plugins", + }, + }, + }, + { + Image: common.CSIPluginImage, + Name: common.VeleroPluginForCSI, + ImagePullPolicy: corev1.PullAlways, + Resources: corev1.ResourceRequirements{}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/target", + Name: "plugins", + }, + }, + }, + }, + }, + }, + }, + }, + }, { name: "given valid DPA CR with proxy env var, appropriate Velero Deployment is built", veleroDeployment: &appsv1.Deployment{ diff --git a/pkg/credentials/credentials.go b/pkg/credentials/credentials.go index c4edaf759e..612e30db6c 100644 --- a/pkg/credentials/credentials.go +++ b/pkg/credentials/credentials.go @@ -286,7 +286,9 @@ func AppendPluginSpecificSpecs(dpa *oadpv1alpha1.DataProtectionApplication, vele if !pluginSpecificMap.IsCloudProvider || !pluginNeedsCheck { continue } - if dpa.Spec.Configuration.Velero.NoDefaultBackupLocation && pluginSpecificMap.IsCloudProvider { + if dpa.Spec.Configuration.Velero.NoDefaultBackupLocation && + dpa.Spec.UnsupportedOverrides[oadpv1alpha1.OperatorTypeKey] != oadpv1alpha1.OperatorTypeMTC && + pluginSpecificMap.IsCloudProvider { continue } // set default secret name to use diff --git a/tests/e2e/dpa_deployment_suite_test.go b/tests/e2e/dpa_deployment_suite_test.go index 47d4402242..c68236e0ab 100644 --- a/tests/e2e/dpa_deployment_suite_test.go +++ b/tests/e2e/dpa_deployment_suite_test.go @@ -630,11 +630,11 @@ var _ = Describe("Configuration testing for DPA Custom Resource", func() { }, genericTests, ) - + type deletionCase struct { WantError bool } - DescribeTable("DPA Deletion test", + DescribeTable("DPA Deletion test", func(installCase deletionCase) { log.Printf("Building dpa") err := dpaCR.Build(RESTIC) @@ -661,5 +661,5 @@ var _ = Describe("Configuration testing for DPA Custom Resource", func() { } }, Entry("Should succeed", deletionCase{WantError: false}), - ) + ) })