From ff9b33e84986abb8e3d348d3799199224e71874b Mon Sep 17 00:00:00 2001 From: Shubham Pampattiwar Date: Thu, 19 May 2022 10:21:52 -0400 Subject: [PATCH 1/2] fix OADP 526 --- controllers/restic.go | 5 +- controllers/restic_test.go | 1138 +++++++++++++++++++++++++++++---- controllers/validator.go | 8 + controllers/validator_test.go | 110 ++++ controllers/velero.go | 85 ++- controllers/velero_test.go | 724 +++++++++++++++++++++ 6 files changed, 1933 insertions(+), 137 deletions(-) diff --git a/controllers/restic.go b/controllers/restic.go index 6cac20548a..1a5f81de24 100644 --- a/controllers/restic.go +++ b/controllers/restic.go @@ -171,8 +171,11 @@ func (r *DPAReconciler) buildResticDaemonset(dpa *oadpv1alpha1.DataProtectionApp return nil, fmt.Errorf("ds cannot be nil") } + // get resource requirements for restic ds + resticResourceReqs, _ := r.getResticResourceReqs(dpa) + installDs := install.DaemonSet(ds.Namespace, - install.WithResources(r.getResticResourceReqs(dpa)), + install.WithResources(resticResourceReqs), install.WithImage(getVeleroImage(dpa)), install.WithAnnotations(dpa.Spec.PodAnnotations), install.WithSecret(false)) diff --git a/controllers/restic_test.go b/controllers/restic_test.go index 567a121b9c..8f6ccfc97b 100644 --- a/controllers/restic_test.go +++ b/controllers/restic_test.go @@ -3,6 +3,7 @@ package controllers import ( "context" "fmt" + "k8s.io/apimachinery/pkg/api/resource" "os" "reflect" "testing" @@ -13,13 +14,13 @@ import ( oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1" "github.com/openshift/oadp-operator/pkg/common" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/pointer" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -189,7 +190,6 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, Image: getVeleroImage(&dpa), ImagePullPolicy: v1.PullAlways, - Resources: r.getResticResourceReqs(&dpa), //setting default. Command: []string{ "/velero", }, @@ -212,6 +212,16 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { MountPath: "/etc/ssl/certs", }, }, + 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"), + }, + }, Env: []v1.EnvVar{ { Name: "NODE_NAME", @@ -326,7 +336,6 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, Image: getVeleroImage(&dpa), ImagePullPolicy: v1.PullAlways, - Resources: r.getResticResourceReqs(&dpa), //setting default. Command: []string{ "/velero", }, @@ -349,6 +358,16 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { MountPath: "/etc/ssl/certs", }, }, + 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"), + }, + }, Env: []v1.EnvVar{ { Name: "NODE_NAME", @@ -500,7 +519,6 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, Image: getVeleroImage(&dpa), ImagePullPolicy: v1.PullAlways, - Resources: r.getResticResourceReqs(&dpa), //setting default. Command: []string{ "/velero", }, @@ -527,6 +545,16 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { MountPath: "/credentials", }, }, + 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"), + }, + }, Env: []v1.EnvVar{ { Name: "NODE_NAME", @@ -561,19 +589,24 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, }, { - name: "test restic tolerations customization via dpa", + name: "test restic resource reqs customization via dpa", args: args{ &oadpv1alpha1.DataProtectionApplication{ Spec: oadpv1alpha1.DataProtectionApplicationSpec{ Configuration: &oadpv1alpha1.ApplicationConfig{ Restic: &oadpv1alpha1.ResticConfig{ PodConfig: &oadpv1alpha1.PodConfig{ - Tolerations: []v1.Toleration{ - { - Key: "key1", - Operator: "Equal", - Value: "value1", - Effect: "NoSchedule", + NodeSelector: map[string]string{ + "foo": "bar", + }, + ResourceAllocations: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("256Mi"), }, }, }, @@ -610,6 +643,9 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, }, Spec: v1.PodSpec{ + NodeSelector: map[string]string{ + "foo": "bar", + }, ServiceAccountName: common.Velero, SecurityContext: &v1.PodSecurityContext{ RunAsUser: pointer.Int64(0), @@ -646,14 +682,7 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, }, }, - Tolerations: []v1.Toleration{ - { - Key: "key1", - Operator: "Equal", - Value: "value1", - Effect: "NoSchedule", - }, - }, + Tolerations: dpa.Spec.Configuration.Restic.PodConfig.Tolerations, Containers: []v1.Container{ { Name: common.Restic, @@ -662,7 +691,6 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, Image: getVeleroImage(&dpa), ImagePullPolicy: v1.PullAlways, - Resources: r.getResticResourceReqs(&dpa), //setting default. Command: []string{ "/velero", }, @@ -689,6 +717,16 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { MountPath: "/credentials", }, }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, Env: []v1.EnvVar{ { Name: "NODE_NAME", @@ -723,13 +761,22 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, }, { - name: "Valid velero and daemonset for aws as bsl", + name: "test restic resource reqs only restic cpu limit customization via dpa", args: args{ &oadpv1alpha1.DataProtectionApplication{ Spec: oadpv1alpha1.DataProtectionApplicationSpec{ Configuration: &oadpv1alpha1.ApplicationConfig{ Restic: &oadpv1alpha1.ResticConfig{ - PodConfig: &oadpv1alpha1.PodConfig{}, + PodConfig: &oadpv1alpha1.PodConfig{ + NodeSelector: map[string]string{ + "foo": "bar", + }, + ResourceAllocations: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, }, Velero: &oadpv1alpha1.VeleroConfig{ PodConfig: &oadpv1alpha1.PodConfig{}, @@ -763,7 +810,9 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, }, Spec: v1.PodSpec{ - NodeSelector: dpa.Spec.Configuration.Restic.PodConfig.NodeSelector, + NodeSelector: map[string]string{ + "foo": "bar", + }, ServiceAccountName: common.Velero, SecurityContext: &v1.PodSecurityContext{ RunAsUser: pointer.Int64(0), @@ -809,7 +858,6 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, Image: getVeleroImage(&dpa), ImagePullPolicy: v1.PullAlways, - Resources: r.getResticResourceReqs(&dpa), //setting default. Command: []string{ "/velero", }, @@ -836,6 +884,16 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { MountPath: "/credentials", }, }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, Env: []v1.EnvVar{ { Name: "NODE_NAME", @@ -870,42 +928,29 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, }, { - name: "Valid velero with annotation and daemonset for aws as bsl with default secret name", + name: "test restic resource reqs only restic cpu request customization via dpa", args: args{ &oadpv1alpha1.DataProtectionApplication{ Spec: oadpv1alpha1.DataProtectionApplicationSpec{ Configuration: &oadpv1alpha1.ApplicationConfig{ - Velero: &oadpv1alpha1.VeleroConfig{ - DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ - oadpv1alpha1.DefaultPluginAWS, - }, - }, - Restic: &oadpv1alpha1.ResticConfig{}, - }, - BackupLocations: []oadpv1alpha1.BackupLocation{ - { - Velero: &velerov1.BackupStorageLocationSpec{ - Provider: AWSProvider, - StorageType: velerov1.StorageType{ - ObjectStorage: &velerov1.ObjectStorageLocation{ - Bucket: "aws-bucket", - }, - }, - Config: map[string]string{ - Region: "aws-region", - S3URL: "https://sr-url-aws-domain.com", - InsecureSkipTLSVerify: "false", + Restic: &oadpv1alpha1.ResticConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + NodeSelector: map[string]string{ + "foo": "bar", }, - Credential: &v1.SecretKeySelector{ - LocalObjectReference: v1.LocalObjectReference{ - Name: "cloud-credentials", + ResourceAllocations: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), }, }, }, }, - }, - PodAnnotations: map[string]string{ - "test-annotation": "awesome annotation", + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{}, + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + }, }, }, }, &appsv1.DaemonSet{ @@ -930,12 +975,11 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { "component": common.Velero, "name": common.Restic, }, - Annotations: map[string]string{ - "test-annotation": "awesome annotation", - }, }, Spec: v1.PodSpec{ - NodeSelector: dpa.Spec.Configuration.Restic.PodConfig.NodeSelector, + NodeSelector: map[string]string{ + "foo": "bar", + }, ServiceAccountName: common.Velero, SecurityContext: &v1.PodSecurityContext{ RunAsUser: pointer.Int64(0), @@ -944,7 +988,7 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { Volumes: []v1.Volume{ // Cloud Provider volumes are dynamically added in the for loop below { - Name: HostPods, + Name: "host-pods", VolumeSource: v1.VolumeSource{ HostPath: &v1.HostPathVolumeSource{ Path: resticPvHostPath, @@ -963,6 +1007,14 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { EmptyDir: &v1.EmptyDirVolumeSource{}, }, }, + { + Name: "cloud-credentials", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: "cloud-credentials", + }, + }, + }, }, Tolerations: dpa.Spec.Configuration.Restic.PodConfig.Tolerations, Containers: []v1.Container{ @@ -973,7 +1025,6 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, Image: getVeleroImage(&dpa), ImagePullPolicy: v1.PullAlways, - Resources: r.getResticResourceReqs(&dpa), //setting default. Command: []string{ "/velero", }, @@ -995,6 +1046,20 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { Name: "certs", MountPath: "/etc/ssl/certs", }, + { + Name: "cloud-credentials", + MountPath: "/credentials", + }, + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, }, Env: []v1.EnvVar{ { @@ -1017,6 +1082,10 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { Name: "VELERO_SCRATCH_DIR", Value: "/scratch", }, + { + Name: common.AWSSharedCredentialsFileEnvKey, + Value: "/credentials/cloud", + }, }, }, }, @@ -1026,51 +1095,27 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, }, { - name: "Valid velero with DNS Policy/Config with annotation and daemonset for aws as bsl with default secret name not specified", + name: "test restic resource reqs only restic memory limit customization via dpa", args: args{ &oadpv1alpha1.DataProtectionApplication{ Spec: oadpv1alpha1.DataProtectionApplicationSpec{ Configuration: &oadpv1alpha1.ApplicationConfig{ - Velero: &oadpv1alpha1.VeleroConfig{ - DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ - oadpv1alpha1.DefaultPluginAWS, - }, - }, - Restic: &oadpv1alpha1.ResticConfig{}, - }, - BackupLocations: []oadpv1alpha1.BackupLocation{ - { - Velero: &velerov1.BackupStorageLocationSpec{ - Provider: AWSProvider, - StorageType: velerov1.StorageType{ - ObjectStorage: &velerov1.ObjectStorageLocation{ - Bucket: "aws-bucket", - }, + Restic: &oadpv1alpha1.ResticConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + NodeSelector: map[string]string{ + "foo": "bar", }, - Config: map[string]string{ - Region: "aws-region", - S3URL: "https://sr-url-aws-domain.com", - InsecureSkipTLSVerify: "false", + ResourceAllocations: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, }, }, }, - }, - PodAnnotations: map[string]string{ - "test-annotation": "awesome annotation", - }, - PodDnsPolicy: "None", - PodDnsConfig: v1.PodDNSConfig{ - Nameservers: []string{ - "1.1.1.1", - "8.8.8.8", - }, - Options: []v1.PodDNSConfigOption{ - { - Name: "ndots", - Value: pointer.String("2"), - }, - { - Name: "edns0", + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{}, + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, }, }, }, @@ -1097,37 +1142,20 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { "component": common.Velero, "name": common.Restic, }, - Annotations: map[string]string{ - "test-annotation": "awesome annotation", - }, }, Spec: v1.PodSpec{ - NodeSelector: dpa.Spec.Configuration.Restic.PodConfig.NodeSelector, + NodeSelector: map[string]string{ + "foo": "bar", + }, ServiceAccountName: common.Velero, SecurityContext: &v1.PodSecurityContext{ RunAsUser: pointer.Int64(0), SupplementalGroups: dpa.Spec.Configuration.Restic.SupplementalGroups, }, - DNSPolicy: "None", - DNSConfig: &v1.PodDNSConfig{ - Nameservers: []string{ - "1.1.1.1", - "8.8.8.8", - }, - Options: []v1.PodDNSConfigOption{ - { - Name: "ndots", - Value: pointer.String("2"), - }, - { - Name: "edns0", - }, - }, - }, Volumes: []v1.Volume{ // Cloud Provider volumes are dynamically added in the for loop below { - Name: HostPods, + Name: "host-pods", VolumeSource: v1.VolumeSource{ HostPath: &v1.HostPathVolumeSource{ Path: resticPvHostPath, @@ -1164,7 +1192,6 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { }, Image: getVeleroImage(&dpa), ImagePullPolicy: v1.PullAlways, - Resources: r.getResticResourceReqs(&dpa), //setting default. Command: []string{ "/velero", }, @@ -1191,6 +1218,883 @@ func TestDPAReconciler_buildResticDaemonset(t *testing.T) { MountPath: "/credentials", }, }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + Env: []v1.EnvVar{ + { + Name: "NODE_NAME", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "VELERO_NAMESPACE", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "VELERO_SCRATCH_DIR", + Value: "/scratch", + }, + { + Name: common.AWSSharedCredentialsFileEnvKey, + Value: "/credentials/cloud", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "test restic resource reqs only restic memory request customization via dpa", + args: args{ + &oadpv1alpha1.DataProtectionApplication{ + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Restic: &oadpv1alpha1.ResticConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + NodeSelector: map[string]string{ + "foo": "bar", + }, + ResourceAllocations: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, + }, + }, + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{}, + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + }, + }, + }, + }, &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + }, + }, + wantErr: false, + want: &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{ + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + }, + Selector: resticLabelSelector, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "component": common.Velero, + "name": common.Restic, + }, + }, + Spec: v1.PodSpec{ + NodeSelector: map[string]string{ + "foo": "bar", + }, + ServiceAccountName: common.Velero, + SecurityContext: &v1.PodSecurityContext{ + RunAsUser: pointer.Int64(0), + SupplementalGroups: dpa.Spec.Configuration.Restic.SupplementalGroups, + }, + Volumes: []v1.Volume{ + // Cloud Provider volumes are dynamically added in the for loop below + { + Name: "host-pods", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: resticPvHostPath, + }, + }, + }, + { + Name: "scratch", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "certs", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "cloud-credentials", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: "cloud-credentials", + }, + }, + }, + }, + Tolerations: dpa.Spec.Configuration.Restic.PodConfig.Tolerations, + Containers: []v1.Container{ + { + Name: common.Restic, + SecurityContext: &v1.SecurityContext{ + Privileged: pointer.Bool(true), + }, + Image: getVeleroImage(&dpa), + ImagePullPolicy: v1.PullAlways, + Command: []string{ + "/velero", + }, + Args: []string{ + "restic", + "server", + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "host-pods", + MountPath: "/host_pods", + MountPropagation: &mountPropagationToHostContainer, + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + { + Name: "cloud-credentials", + MountPath: "/credentials", + }, + }, + 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("256Mi"), + }, + }, + Env: []v1.EnvVar{ + { + Name: "NODE_NAME", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "VELERO_NAMESPACE", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "VELERO_SCRATCH_DIR", + Value: "/scratch", + }, + { + Name: common.AWSSharedCredentialsFileEnvKey, + Value: "/credentials/cloud", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "test restic tolerations customization via dpa", + args: args{ + &oadpv1alpha1.DataProtectionApplication{ + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Restic: &oadpv1alpha1.ResticConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + Tolerations: []v1.Toleration{ + { + Key: "key1", + Operator: "Equal", + Value: "value1", + Effect: "NoSchedule", + }, + }, + }, + }, + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{}, + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + }, + }, + }, + }, &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + }, + }, + wantErr: false, + want: &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{ + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + }, + Selector: resticLabelSelector, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "component": common.Velero, + "name": common.Restic, + }, + }, + Spec: v1.PodSpec{ + ServiceAccountName: common.Velero, + SecurityContext: &v1.PodSecurityContext{ + RunAsUser: pointer.Int64(0), + SupplementalGroups: dpa.Spec.Configuration.Restic.SupplementalGroups, + }, + Volumes: []v1.Volume{ + // Cloud Provider volumes are dynamically added in the for loop below + { + Name: "host-pods", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: resticPvHostPath, + }, + }, + }, + { + Name: "scratch", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "certs", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "cloud-credentials", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: "cloud-credentials", + }, + }, + }, + }, + Tolerations: []v1.Toleration{ + { + Key: "key1", + Operator: "Equal", + Value: "value1", + Effect: "NoSchedule", + }, + }, + Containers: []v1.Container{ + { + Name: common.Restic, + SecurityContext: &v1.SecurityContext{ + Privileged: pointer.Bool(true), + }, + Image: getVeleroImage(&dpa), + ImagePullPolicy: v1.PullAlways, + Command: []string{ + "/velero", + }, + Args: []string{ + "restic", + "server", + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "host-pods", + MountPath: "/host_pods", + MountPropagation: &mountPropagationToHostContainer, + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + { + Name: "cloud-credentials", + MountPath: "/credentials", + }, + }, + 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"), + }, + }, + Env: []v1.EnvVar{ + { + Name: "NODE_NAME", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "VELERO_NAMESPACE", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "VELERO_SCRATCH_DIR", + Value: "/scratch", + }, + { + Name: common.AWSSharedCredentialsFileEnvKey, + Value: "/credentials/cloud", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Valid velero and daemonset for aws as bsl", + args: args{ + &oadpv1alpha1.DataProtectionApplication{ + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Restic: &oadpv1alpha1.ResticConfig{ + PodConfig: &oadpv1alpha1.PodConfig{}, + }, + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{}, + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + }, + }, + }, + }, &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + }, + }, + wantErr: false, + want: &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{ + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + }, + Selector: resticLabelSelector, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "component": common.Velero, + "name": common.Restic, + }, + }, + Spec: v1.PodSpec{ + NodeSelector: dpa.Spec.Configuration.Restic.PodConfig.NodeSelector, + ServiceAccountName: common.Velero, + SecurityContext: &v1.PodSecurityContext{ + RunAsUser: pointer.Int64(0), + SupplementalGroups: dpa.Spec.Configuration.Restic.SupplementalGroups, + }, + Volumes: []v1.Volume{ + // Cloud Provider volumes are dynamically added in the for loop below + { + Name: "host-pods", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: resticPvHostPath, + }, + }, + }, + { + Name: "scratch", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "certs", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "cloud-credentials", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: "cloud-credentials", + }, + }, + }, + }, + Tolerations: dpa.Spec.Configuration.Restic.PodConfig.Tolerations, + Containers: []v1.Container{ + { + Name: common.Restic, + SecurityContext: &v1.SecurityContext{ + Privileged: pointer.Bool(true), + }, + Image: getVeleroImage(&dpa), + ImagePullPolicy: v1.PullAlways, + Command: []string{ + "/velero", + }, + Args: []string{ + "restic", + "server", + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "host-pods", + MountPath: "/host_pods", + MountPropagation: &mountPropagationToHostContainer, + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + { + Name: "cloud-credentials", + MountPath: "/credentials", + }, + }, + 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"), + }, + }, + Env: []v1.EnvVar{ + { + Name: "NODE_NAME", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "VELERO_NAMESPACE", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "VELERO_SCRATCH_DIR", + Value: "/scratch", + }, + { + Name: common.AWSSharedCredentialsFileEnvKey, + Value: "/credentials/cloud", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Valid velero with annotation and daemonset for aws as bsl with default secret name", + args: args{ + &oadpv1alpha1.DataProtectionApplication{ + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + }, + Restic: &oadpv1alpha1.ResticConfig{}, + }, + BackupLocations: []oadpv1alpha1.BackupLocation{ + { + Velero: &velerov1.BackupStorageLocationSpec{ + Provider: AWSProvider, + StorageType: velerov1.StorageType{ + ObjectStorage: &velerov1.ObjectStorageLocation{ + Bucket: "aws-bucket", + }, + }, + Config: map[string]string{ + Region: "aws-region", + S3URL: "https://sr-url-aws-domain.com", + InsecureSkipTLSVerify: "false", + }, + Credential: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "cloud-credentials", + }, + }, + }, + }, + }, + PodAnnotations: map[string]string{ + "test-annotation": "awesome annotation", + }, + }, + }, &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + }, + }, + wantErr: false, + want: &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{ + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + }, + Selector: resticLabelSelector, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "component": common.Velero, + "name": common.Restic, + }, + Annotations: map[string]string{ + "test-annotation": "awesome annotation", + }, + }, + Spec: v1.PodSpec{ + NodeSelector: dpa.Spec.Configuration.Restic.PodConfig.NodeSelector, + ServiceAccountName: common.Velero, + SecurityContext: &v1.PodSecurityContext{ + RunAsUser: pointer.Int64(0), + SupplementalGroups: dpa.Spec.Configuration.Restic.SupplementalGroups, + }, + Volumes: []v1.Volume{ + // Cloud Provider volumes are dynamically added in the for loop below + { + Name: HostPods, + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: resticPvHostPath, + }, + }, + }, + { + Name: "scratch", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "certs", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + }, + Tolerations: dpa.Spec.Configuration.Restic.PodConfig.Tolerations, + Containers: []v1.Container{ + { + Name: common.Restic, + SecurityContext: &v1.SecurityContext{ + Privileged: pointer.Bool(true), + }, + Image: getVeleroImage(&dpa), + ImagePullPolicy: v1.PullAlways, + Command: []string{ + "/velero", + }, + Args: []string{ + "restic", + "server", + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "host-pods", + MountPath: "/host_pods", + MountPropagation: &mountPropagationToHostContainer, + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + }, + 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"), + }, + }, + Env: []v1.EnvVar{ + { + Name: "NODE_NAME", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "VELERO_NAMESPACE", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "VELERO_SCRATCH_DIR", + Value: "/scratch", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Valid velero with DNS Policy/Config with annotation and daemonset for aws as bsl with default secret name not specified", + args: args{ + &oadpv1alpha1.DataProtectionApplication{ + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + }, + Restic: &oadpv1alpha1.ResticConfig{}, + }, + BackupLocations: []oadpv1alpha1.BackupLocation{ + { + Velero: &velerov1.BackupStorageLocationSpec{ + Provider: AWSProvider, + StorageType: velerov1.StorageType{ + ObjectStorage: &velerov1.ObjectStorageLocation{ + Bucket: "aws-bucket", + }, + }, + Config: map[string]string{ + Region: "aws-region", + S3URL: "https://sr-url-aws-domain.com", + InsecureSkipTLSVerify: "false", + }, + }, + }, + }, + PodAnnotations: map[string]string{ + "test-annotation": "awesome annotation", + }, + PodDnsPolicy: "None", + PodDnsConfig: v1.PodDNSConfig{ + Nameservers: []string{ + "1.1.1.1", + "8.8.8.8", + }, + Options: []v1.PodDNSConfigOption{ + { + Name: "ndots", + Value: pointer.String("2"), + }, + { + Name: "edns0", + }, + }, + }, + }, + }, &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + }, + }, + wantErr: false, + want: &appsv1.DaemonSet{ + ObjectMeta: getResticObjectMeta(r), + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{ + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + }, + Selector: resticLabelSelector, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "component": common.Velero, + "name": common.Restic, + }, + Annotations: map[string]string{ + "test-annotation": "awesome annotation", + }, + }, + Spec: v1.PodSpec{ + NodeSelector: dpa.Spec.Configuration.Restic.PodConfig.NodeSelector, + ServiceAccountName: common.Velero, + SecurityContext: &v1.PodSecurityContext{ + RunAsUser: pointer.Int64(0), + SupplementalGroups: dpa.Spec.Configuration.Restic.SupplementalGroups, + }, + DNSPolicy: "None", + DNSConfig: &v1.PodDNSConfig{ + Nameservers: []string{ + "1.1.1.1", + "8.8.8.8", + }, + Options: []v1.PodDNSConfigOption{ + { + Name: "ndots", + Value: pointer.String("2"), + }, + { + Name: "edns0", + }, + }, + }, + Volumes: []v1.Volume{ + // Cloud Provider volumes are dynamically added in the for loop below + { + Name: HostPods, + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: resticPvHostPath, + }, + }, + }, + { + Name: "scratch", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "certs", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "cloud-credentials", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: "cloud-credentials", + }, + }, + }, + }, + Tolerations: dpa.Spec.Configuration.Restic.PodConfig.Tolerations, + Containers: []v1.Container{ + { + Name: common.Restic, + SecurityContext: &v1.SecurityContext{ + Privileged: pointer.Bool(true), + }, + Image: getVeleroImage(&dpa), + ImagePullPolicy: v1.PullAlways, + Command: []string{ + "/velero", + }, + Args: []string{ + "restic", + "server", + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "host-pods", + MountPath: "/host_pods", + MountPropagation: &mountPropagationToHostContainer, + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + { + Name: "cloud-credentials", + MountPath: "/credentials", + }, + }, + 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"), + }, + }, Env: []v1.EnvVar{ { Name: "NODE_NAME", diff --git a/controllers/validator.go b/controllers/validator.go index 6e2eaa798e..d1eccf3afc 100644 --- a/controllers/validator.go +++ b/controllers/validator.go @@ -57,6 +57,14 @@ func (r *DPAReconciler) ValidateDataProtectionCR(log logr.Logger) (bool, error) return false, err } + if _, err := r.getVeleroResourceReqs(&dpa); err != nil { + return false, err + } + + if _, err := r.getResticResourceReqs(&dpa); err != nil { + return false, err + } + return true, nil } diff --git a/controllers/validator_test.go b/controllers/validator_test.go index d078026473..3347340858 100644 --- a/controllers/validator_test.go +++ b/controllers/validator_test.go @@ -1,6 +1,7 @@ package controllers import ( + "k8s.io/apimachinery/pkg/api/resource" "testing" "github.com/go-logr/logr" @@ -251,6 +252,115 @@ func TestDPAReconciler_ValidateDataProtectionCR(t *testing.T) { wantErr: false, want: true, }, + { + name: "given valid DPA CR with valid velero resource requirements ", + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-DPA-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + BackupLocations: []oadpv1alpha1.BackupLocation{ + { + CloudStorage: &oadpv1alpha1.CloudStorageLocation{ + CloudStorageRef: corev1.LocalObjectReference{ + Name: "testing", + }, + Credential: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "testing", + }, + Key: "credentials", + }, + }, + }, + }, + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + PodConfig: &oadpv1alpha1.PodConfig{ + ResourceAllocations: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cloud-credentials", + Namespace: "test-ns", + }, + }, + }, + wantErr: false, + want: true, + }, + { + name: "given valid DPA CR with valid restic resource requirements ", + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-DPA-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + BackupLocations: []oadpv1alpha1.BackupLocation{ + { + CloudStorage: &oadpv1alpha1.CloudStorageLocation{ + CloudStorageRef: corev1.LocalObjectReference{ + Name: "testing", + }, + Credential: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "testing", + }, + Key: "credentials", + }, + }, + }, + }, + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + PodConfig: &oadpv1alpha1.PodConfig{ + ResourceAllocations: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + Restic: &oadpv1alpha1.ResticConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + ResourceAllocations: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cloud-credentials", + Namespace: "test-ns", + }, + }, + }, + wantErr: false, + want: true, + }, { name: "given valid DPA CR bucket BSL configured with creds and VSL and AWS Default Plugin with no secret", dpa: &oadpv1alpha1.DataProtectionApplication{ diff --git a/controllers/velero.go b/controllers/velero.go index 90477ffc18..4df8643b3f 100644 --- a/controllers/velero.go +++ b/controllers/velero.go @@ -392,11 +392,14 @@ func (r *DPAReconciler) buildVeleroDeployment(veleroDeployment *appsv1.Deploymen } r.ReconcileRestoreResourcesVersionPriority(dpa) + // get resource requirements for velero deployment + veleroResourceReqs, _ := r.getVeleroResourceReqs(dpa) + // TODO! Reuse removeDuplicateValues with interface type dpa.Spec.Configuration.Velero.DefaultPlugins = removeDuplicatePluginValues(dpa.Spec.Configuration.Velero.DefaultPlugins) dpa.Spec.Configuration.Velero.FeatureFlags = removeDuplicateValues(dpa.Spec.Configuration.Velero.FeatureFlags) installDeployment := install.Deployment(veleroDeployment.Namespace, - install.WithResources(r.getVeleroResourceReqs(dpa)), + install.WithResources(veleroResourceReqs), install.WithImage(getVeleroImage(dpa)), install.WithFeatures(dpa.Spec.Configuration.Velero.FeatureFlags), install.WithAnnotations(dpa.Spec.PodAnnotations), @@ -643,7 +646,7 @@ func getAppLabels(instanceName string) map[string]string { } // Get Velero Resource Requirements -func (r *DPAReconciler) getVeleroResourceReqs(dpa *oadpv1alpha1.DataProtectionApplication) corev1.ResourceRequirements { +func (r *DPAReconciler) getVeleroResourceReqs(dpa *oadpv1alpha1.DataProtectionApplication) (corev1.ResourceRequirements, error) { // Set default values ResourcesReqs := corev1.ResourceRequirements{ @@ -657,25 +660,47 @@ func (r *DPAReconciler) getVeleroResourceReqs(dpa *oadpv1alpha1.DataProtectionAp }, } - if dpa != nil && dpa.Spec.Configuration.Velero.PodConfig != nil { + if dpa != nil && dpa.Spec.Configuration != nil && dpa.Spec.Configuration.Velero != nil && dpa.Spec.Configuration.Velero.PodConfig != nil { // Set custom limits and requests values if defined on VELERO Spec - if dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests != nil { - ResourcesReqs.Requests[corev1.ResourceCPU] = resource.MustParse(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Cpu().String()) - ResourcesReqs.Requests[corev1.ResourceMemory] = resource.MustParse(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Memory().String()) + if dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Cpu() != nil && dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Cpu().Value() != 0 { + parsedQuantity, err := resource.ParseQuantity(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Cpu().String()) + ResourcesReqs.Requests[corev1.ResourceCPU] = parsedQuantity + if err != nil { + return ResourcesReqs, err + } + } + + if dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Memory() != nil && dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Memory().Value() != 0 { + parsedQuantity, err := resource.ParseQuantity(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Requests.Memory().String()) + ResourcesReqs.Requests[corev1.ResourceMemory] = parsedQuantity + if err != nil { + return ResourcesReqs, err + } } - if dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits != nil { - ResourcesReqs.Limits[corev1.ResourceCPU] = resource.MustParse(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Cpu().String()) - ResourcesReqs.Limits[corev1.ResourceMemory] = resource.MustParse(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Memory().String()) + if dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Cpu() != nil && dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Cpu().Value() != 0 { + parsedQuantity, err := resource.ParseQuantity(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Cpu().String()) + ResourcesReqs.Limits[corev1.ResourceCPU] = parsedQuantity + if err != nil { + return ResourcesReqs, err + } + } + + if dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Memory() != nil && dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Memory().Value() != 0 { + parsedQuantiy, err := resource.ParseQuantity(dpa.Spec.Configuration.Velero.PodConfig.ResourceAllocations.Limits.Memory().String()) + ResourcesReqs.Limits[corev1.ResourceMemory] = parsedQuantiy + if err != nil { + return ResourcesReqs, err + } } } - return ResourcesReqs + return ResourcesReqs, nil } // Get Restic Resource Requirements -func (r *DPAReconciler) getResticResourceReqs(dpa *oadpv1alpha1.DataProtectionApplication) corev1.ResourceRequirements { +func (r *DPAReconciler) getResticResourceReqs(dpa *oadpv1alpha1.DataProtectionApplication) (corev1.ResourceRequirements, error) { // Set default values ResourcesReqs := corev1.ResourceRequirements{ @@ -690,20 +715,42 @@ func (r *DPAReconciler) getResticResourceReqs(dpa *oadpv1alpha1.DataProtectionAp } if dpa != nil && dpa.Spec.Configuration != nil && dpa.Spec.Configuration.Restic != nil && dpa.Spec.Configuration.Restic.PodConfig != nil { - // Set custom limits and requests values if defined on VELERO Spec - if dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests != nil { - ResourcesReqs.Requests[corev1.ResourceCPU] = resource.MustParse(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Cpu().String()) - ResourcesReqs.Requests[corev1.ResourceMemory] = resource.MustParse(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Memory().String()) + // Set custom limits and requests values if defined on Restic Spec + if dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Cpu() != nil && dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Cpu().Value() != 0 { + parsedQuantity, err := resource.ParseQuantity(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Cpu().String()) + ResourcesReqs.Requests[corev1.ResourceCPU] = parsedQuantity + if err != nil { + return ResourcesReqs, err + } + } + + if dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Memory() != nil && dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Memory().Value() != 0 { + parsedQuantity, err := resource.ParseQuantity(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Requests.Memory().String()) + ResourcesReqs.Requests[corev1.ResourceMemory] = parsedQuantity + if err != nil { + return ResourcesReqs, err + } } - if dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits != nil { - ResourcesReqs.Limits[corev1.ResourceCPU] = resource.MustParse(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Cpu().String()) - ResourcesReqs.Limits[corev1.ResourceMemory] = resource.MustParse(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Memory().String()) + if dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Cpu() != nil && dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Cpu().Value() != 0 { + parsedQuantity, err := resource.ParseQuantity(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Cpu().String()) + ResourcesReqs.Limits[corev1.ResourceCPU] = parsedQuantity + if err != nil { + return ResourcesReqs, err + } + } + + if dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Memory() != nil && dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Memory().Value() != 0 { + parsedQuantiy, err := resource.ParseQuantity(dpa.Spec.Configuration.Restic.PodConfig.ResourceAllocations.Limits.Memory().String()) + ResourcesReqs.Limits[corev1.ResourceMemory] = parsedQuantiy + if err != nil { + return ResourcesReqs, err + } } } - return ResourcesReqs + return ResourcesReqs, nil } // noDefaultCredentials determines if a provider needs the default credentials. diff --git a/controllers/velero_test.go b/controllers/velero_test.go index 2143b9fc09..c198b9669a 100644 --- a/controllers/velero_test.go +++ b/controllers/velero_test.go @@ -1551,6 +1551,730 @@ func TestDPAReconciler_buildVeleroDeployment(t *testing.T) { }, }, }, + { + name: "given valid DPA CR, velero deployment resource customization only cpu limit", + veleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + }, + 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", + }, + }, + }, + }, + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-Velero-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + ResourceAllocations: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + }, + }, + }, + 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("2"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + Command: []string{"/velero"}, + Args: []string{ + "server", + "--restic-timeout=1h", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins", + MountPath: "/plugins", + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + }, + 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", + }, + }, + }, + }, + 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{}, + }, + }, + }, + InitContainers: []corev1.Container{}, + }, + }, + }, + }, + }, + { + name: "given valid DPA CR, velero deployment resource customization only cpu request", + veleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + }, + 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", + }, + }, + }, + }, + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-Velero-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + ResourceAllocations: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + }, + }, + }, + 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("2"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + Command: []string{"/velero"}, + Args: []string{ + "server", + "--restic-timeout=1h", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins", + MountPath: "/plugins", + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + }, + 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", + }, + }, + }, + }, + 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{}, + }, + }, + }, + InitContainers: []corev1.Container{}, + }, + }, + }, + }, + }, + { + name: "given valid DPA CR, velero deployment resource customization only memory request", + veleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + }, + 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", + }, + }, + }, + }, + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-Velero-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + ResourceAllocations: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, + }, + }, + }, + }, + }, + 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("256Mi"), + }, + }, + Command: []string{"/velero"}, + Args: []string{ + "server", + "--restic-timeout=1h", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins", + MountPath: "/plugins", + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + }, + 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", + }, + }, + }, + }, + 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{}, + }, + }, + }, + InitContainers: []corev1.Container{}, + }, + }, + }, + }, + }, + { + name: "given valid DPA CR, velero deployment resource customization only memory limit", + veleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + }, + 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", + }, + }, + }, + }, + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-Velero-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + PodConfig: &oadpv1alpha1.PodConfig{ + ResourceAllocations: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + }, + }, + }, + }, + }, + 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("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + Command: []string{"/velero"}, + Args: []string{ + "server", + "--restic-timeout=1h", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins", + MountPath: "/plugins", + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + }, + 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", + }, + }, + }, + }, + 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{}, + }, + }, + }, + InitContainers: []corev1.Container{}, + }, + }, + }, + }, + }, { name: "given valid DPA CR, velero deployment tolerations", veleroDeployment: &appsv1.Deployment{ From 66b36e32fdf2517266f905eefc65824fef91e8b5 Mon Sep 17 00:00:00 2001 From: Shubham Pampattiwar Date: Thu, 19 May 2022 12:02:32 -0400 Subject: [PATCH 2/2] add ignore err comments --- controllers/restic.go | 1 + controllers/velero.go | 1 + 2 files changed, 2 insertions(+) diff --git a/controllers/restic.go b/controllers/restic.go index 1a5f81de24..2bed40ffb3 100644 --- a/controllers/restic.go +++ b/controllers/restic.go @@ -172,6 +172,7 @@ func (r *DPAReconciler) buildResticDaemonset(dpa *oadpv1alpha1.DataProtectionApp } // get resource requirements for restic ds + // ignoring err here as it is checked in validator.go resticResourceReqs, _ := r.getResticResourceReqs(dpa) installDs := install.DaemonSet(ds.Namespace, diff --git a/controllers/velero.go b/controllers/velero.go index 4df8643b3f..8d7a7ff955 100644 --- a/controllers/velero.go +++ b/controllers/velero.go @@ -393,6 +393,7 @@ func (r *DPAReconciler) buildVeleroDeployment(veleroDeployment *appsv1.Deploymen r.ReconcileRestoreResourcesVersionPriority(dpa) // get resource requirements for velero deployment + // ignoring err here as it is checked in validator.go veleroResourceReqs, _ := r.getVeleroResourceReqs(dpa) // TODO! Reuse removeDuplicateValues with interface type