diff --git a/api/v1alpha1/gitopsservice_types.go b/api/v1alpha1/gitopsservice_types.go index 7cca5136a..7c76b65ec 100644 --- a/api/v1alpha1/gitopsservice_types.go +++ b/api/v1alpha1/gitopsservice_types.go @@ -32,6 +32,28 @@ type GitopsServiceSpec struct { Tolerations []corev1.Toleration `json:"tolerations,omitempty"` // NodeSelector is a map of key value pairs used for node selection in the default workloads NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // ConsolePlugin defines the Resource configuration for the Console Plugin components + ConsolePlugin *ConsolePluginStruct `json:"consolePlugin,omitempty"` +} + +// ConsolePluginStruct defines the resource configuration for the Console Plugin components +type ConsolePluginStruct struct { + // Backend defines the resource requests and limits for the backend service + Backend *BackendStruct `json:"backend,omitempty"` + // GitopsPlugin defines the resource requests and limits for the gitops plugin service + GitopsPlugin *GitopsPluginStruct `json:"gitopsPlugin,omitempty"` +} + +// BackendStruct defines the resource configuration for the Backend components +type BackendStruct struct { + // Resources defines the resource requests and limits for the backend service + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` +} + +// GitopsPluginStruct defines the resource configuration for the Gitops Plugin components +type GitopsPluginStruct struct { + // Resources defines the resource requests and limits for the gitops plugin service + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } // GitopsServiceStatus defines the observed state of GitopsService diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fe3bf2289..e653c3054 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -25,6 +25,71 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendStruct) DeepCopyInto(out *BackendStruct) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendStruct. +func (in *BackendStruct) DeepCopy() *BackendStruct { + if in == nil { + return nil + } + out := new(BackendStruct) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsolePluginStruct) DeepCopyInto(out *ConsolePluginStruct) { + *out = *in + if in.Backend != nil { + in, out := &in.Backend, &out.Backend + *out = new(BackendStruct) + (*in).DeepCopyInto(*out) + } + if in.GitopsPlugin != nil { + in, out := &in.GitopsPlugin, &out.GitopsPlugin + *out = new(GitopsPluginStruct) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsolePluginStruct. +func (in *ConsolePluginStruct) DeepCopy() *ConsolePluginStruct { + if in == nil { + return nil + } + out := new(ConsolePluginStruct) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitopsPluginStruct) DeepCopyInto(out *GitopsPluginStruct) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitopsPluginStruct. +func (in *GitopsPluginStruct) DeepCopy() *GitopsPluginStruct { + if in == nil { + return nil + } + out := new(GitopsPluginStruct) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitopsService) DeepCopyInto(out *GitopsService) { *out = *in @@ -101,6 +166,11 @@ func (in *GitopsServiceSpec) DeepCopyInto(out *GitopsServiceSpec) { (*out)[key] = val } } + if in.ConsolePlugin != nil { + in, out := &in.ConsolePlugin, &out.ConsolePlugin + *out = new(ConsolePluginStruct) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitopsServiceSpec. diff --git a/bundle/manifests/gitops-operator.clusterserviceversion.yaml b/bundle/manifests/gitops-operator.clusterserviceversion.yaml index 3fb173e82..e758cdf85 100644 --- a/bundle/manifests/gitops-operator.clusterserviceversion.yaml +++ b/bundle/manifests/gitops-operator.clusterserviceversion.yaml @@ -180,7 +180,7 @@ metadata: capabilities: Deep Insights console.openshift.io/plugins: '["gitops-plugin"]' containerImage: quay.io/redhat-developer/gitops-operator - createdAt: "2025-09-30T08:46:55Z" + createdAt: "2025-10-01T09:01:02Z" description: Enables teams to adopt GitOps principles for managing cluster configurations and application delivery across hybrid multi-cluster Kubernetes environments. features.operators.openshift.io/disconnected: "true" diff --git a/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml b/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml index 1f9d1ed66..792cc853b 100644 --- a/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml +++ b/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml @@ -39,6 +39,141 @@ spec: spec: description: GitopsServiceSpec defines the desired state of GitopsService properties: + consolePlugin: + description: ConsolePlugin defines the Resource configuration for + the Console Plugin components + properties: + backend: + description: Backend defines the resource requests and limits + for the backend service + properties: + resources: + description: Resources defines the resource requests and limits + for the backend service + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + gitopsPlugin: + description: GitopsPlugin defines the resource requests and limits + for the gitops plugin service + properties: + resources: + description: Resources defines the resource requests and limits + for the gitops plugin service + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + type: object nodeSelector: additionalProperties: type: string diff --git a/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml b/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml index 539fb45bf..1dd2f77a9 100644 --- a/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml +++ b/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml @@ -39,6 +39,141 @@ spec: spec: description: GitopsServiceSpec defines the desired state of GitopsService properties: + consolePlugin: + description: ConsolePlugin defines the Resource configuration for + the Console Plugin components + properties: + backend: + description: Backend defines the resource requests and limits + for the backend service + properties: + resources: + description: Resources defines the resource requests and limits + for the backend service + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + gitopsPlugin: + description: GitopsPlugin defines the resource requests and limits + for the gitops plugin service + properties: + resources: + description: Resources defines the resource requests and limits + for the gitops plugin service + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + type: object nodeSelector: additionalProperties: type: string diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go index 168384289..4ad49ad88 100644 --- a/controllers/consoleplugin.go +++ b/controllers/consoleplugin.go @@ -284,6 +284,10 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop newPluginDeployment.Spec.Template.Spec.NodeSelector = argocdutil.AppendStringMap(newPluginDeployment.Spec.Template.Spec.NodeSelector, cr.Spec.NodeSelector) } + if cr.Spec.ConsolePlugin != nil && cr.Spec.ConsolePlugin.GitopsPlugin != nil && cr.Spec.ConsolePlugin.GitopsPlugin.Resources != nil { + newPluginDeployment.Spec.Template.Spec.Containers[0].Resources = *cr.Spec.ConsolePlugin.GitopsPlugin.Resources + } + if len(cr.Spec.Tolerations) > 0 { newPluginDeployment.Spec.Template.Spec.Tolerations = cr.Spec.Tolerations } @@ -315,7 +319,7 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop !reflect.DeepEqual(existingSpecTemplate.Spec.DNSPolicy, newSpecTemplate.Spec.DNSPolicy) || !reflect.DeepEqual(existingPluginDeployment.Spec.Template.Spec.NodeSelector, newPluginDeployment.Spec.Template.Spec.NodeSelector) || !reflect.DeepEqual(existingPluginDeployment.Spec.Template.Spec.Tolerations, newPluginDeployment.Spec.Template.Spec.Tolerations) || - !reflect.DeepEqual(existingSpecTemplate.Spec.SecurityContext, existingSpecTemplate.Spec.SecurityContext) + !reflect.DeepEqual(existingSpecTemplate.Spec.SecurityContext, existingSpecTemplate.Spec.SecurityContext) || !reflect.DeepEqual(existingSpecTemplate.Spec.Containers[0].Resources, newSpecTemplate.Spec.Containers[0].Resources) if changed { reqLogger.Info("Reconciling plugin deployment", "Namespace", existingPluginDeployment.Namespace, "Name", existingPluginDeployment.Name) @@ -330,6 +334,7 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop existingSpecTemplate.Spec.DNSPolicy = newSpecTemplate.Spec.DNSPolicy existingPluginDeployment.Spec.Template.Spec.NodeSelector = newPluginDeployment.Spec.Template.Spec.NodeSelector existingPluginDeployment.Spec.Template.Spec.Tolerations = newPluginDeployment.Spec.Template.Spec.Tolerations + existingSpecTemplate.Spec.Containers[0].Resources = newSpecTemplate.Spec.Containers[0].Resources return reconcile.Result{}, r.Client.Update(context.TODO(), existingPluginDeployment) } } diff --git a/controllers/consoleplugin_test.go b/controllers/consoleplugin_test.go index e990850f4..872e3d0c8 100644 --- a/controllers/consoleplugin_test.go +++ b/controllers/consoleplugin_test.go @@ -1005,6 +1005,132 @@ func TestPlugin_reconcileDeployment(t *testing.T) { assertNoError(t, err) } +// Test to verify if the resource values are updated when the resource values are changed in the CR +func TestPlugin_reconcileDeployment_ChangedResources(t *testing.T) { + s := scheme.Scheme + addKnownTypesToScheme(s) + + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + instance := &pipelinesv1alpha1.GitopsService{ + Spec: pipelinesv1alpha1.GitopsServiceSpec{}, + } + Resources := &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("123Mi"), + corev1.ResourceCPU: resourcev1.MustParse("234m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("456Mi"), + corev1.ResourceCPU: resourcev1.MustParse("5"), + }, + } + instance.Spec.ConsolePlugin = &pipelinesv1alpha1.ConsolePluginStruct{ + Backend: &pipelinesv1alpha1.BackendStruct{ + Resources: Resources, + }, + GitopsPlugin: &pipelinesv1alpha1.GitopsPluginStruct{ + Resources: Resources, + }, + } + + _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName)) + assertNoError(t, err) + + deployment := &appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("123Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("234m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("456Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("5")) +} + +// Test to verify if the default resource values are set when no resource values are provided in the CR +func TestPlugin_ReconcileDeployment_DefaultResourceValues(t *testing.T) { + s := scheme.Scheme + addKnownTypesToScheme(s) + + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + instance := &pipelinesv1alpha1.GitopsService{} + _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName)) + assertNoError(t, err) + + deployment := &appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("128Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("250m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("256Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("500m")) +} + +// Test to verify if the resource values are updated when the resource values are changed in the CR +func TestPlugin_ReconcileDeployment_ChangeExistingResourceValues(t *testing.T) { + s := scheme.Scheme + addKnownTypesToScheme(s) + + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + instance := &pipelinesv1alpha1.GitopsService{ + Spec: pipelinesv1alpha1.GitopsServiceSpec{}, + } + Resources := &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("123Mi"), + corev1.ResourceCPU: resourcev1.MustParse("234m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("456Mi"), + corev1.ResourceCPU: resourcev1.MustParse("5"), + }, + } + instance.Spec.ConsolePlugin = &pipelinesv1alpha1.ConsolePluginStruct{ + Backend: &pipelinesv1alpha1.BackendStruct{ + Resources: Resources, + }, + GitopsPlugin: &pipelinesv1alpha1.GitopsPluginStruct{ + Resources: Resources, + }, + } + _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName)) + assertNoError(t, err) + + deployment := &appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("123Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("234m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("456Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("5")) + + // Update the resource values again + updatedResources := &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("100Mi"), + corev1.ResourceCPU: resourcev1.MustParse("200m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("300Mi"), + corev1.ResourceCPU: resourcev1.MustParse("400m"), + }, + } + instance.Spec.ConsolePlugin.Backend.Resources, instance.Spec.ConsolePlugin.GitopsPlugin.Resources = updatedResources, updatedResources + + _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName)) + assertNoError(t, err) + + deployment = &appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("100Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("200m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("300Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("400m")) + +} + func TestPlugin_reconcileService(t *testing.T) { s := scheme.Scheme addKnownTypesToScheme(s) diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index 1b38047e3..119e8364f 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -626,6 +626,9 @@ func (r *ReconcileGitopsService) reconcileBackend(gitopsserviceNamespacedName ty if len(instance.Spec.Tolerations) > 0 { deploymentObj.Spec.Template.Spec.Tolerations = instance.Spec.Tolerations } + if instance.Spec.ConsolePlugin != nil && instance.Spec.ConsolePlugin.Backend != nil && instance.Spec.ConsolePlugin.Backend.Resources != nil { + deploymentObj.Spec.Template.Spec.Containers[0].Resources = *instance.Spec.ConsolePlugin.Backend.Resources + } // Check if this Deployment already exists found := &appsv1.Deployment{} if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: deploymentObj.Name, Namespace: deploymentObj.Namespace}, @@ -651,6 +654,10 @@ func (r *ReconcileGitopsService) reconcileBackend(gitopsserviceNamespacedName ty found.Spec.Template.Spec.Containers[0].Env = deploymentObj.Spec.Template.Spec.Containers[0].Env changed = true } + if !reflect.DeepEqual(found.Spec.Template.Spec.Containers[0].Resources, deploymentObj.Spec.Template.Spec.Containers[0].Resources) { + found.Spec.Template.Spec.Containers[0].Resources = deploymentObj.Spec.Template.Spec.Containers[0].Resources + changed = true + } if !reflect.DeepEqual(found.Spec.Template.Spec.Containers[0].Args, deploymentObj.Spec.Template.Spec.Containers[0].Args) { found.Spec.Template.Spec.Containers[0].Args = deploymentObj.Spec.Template.Spec.Containers[0].Args changed = true diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index e1bbc3005..f1cb37426 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -741,6 +741,154 @@ func TestReconcile_PSSLabels(t *testing.T) { } } +// TestReconcileBackend_ResourceRequestsAndLimits tests whether backend deployment is created with user defined resource requests and limits +func TestReconcileBackend_ResourceRequestsAndLimits(t *testing.T) { + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + instance := &pipelinesv1alpha1.GitopsService{ + Spec: pipelinesv1alpha1.GitopsServiceSpec{}, + } + Resources := &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("123Mi"), + corev1.ResourceCPU: resourcev1.MustParse("234m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("456Mi"), + corev1.ResourceCPU: resourcev1.MustParse("5"), + }, + } + instance.Spec.ConsolePlugin = &pipelinesv1alpha1.ConsolePluginStruct{ + Backend: &pipelinesv1alpha1.BackendStruct{ + Resources: Resources, + }, + GitopsPlugin: &pipelinesv1alpha1.GitopsPluginStruct{ + Resources: Resources, + }, + } + + gitopsserviceNamespacedName := types.NamespacedName{ + Name: serviceName, + Namespace: serviceNamespace, + } + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + _, err := reconciler.reconcileBackend(gitopsserviceNamespacedName, instance, reqLogger) + assertNoError(t, err) + + // verify whether backend deployment is created with user defined resource requests and limits + deployment := &appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("123Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("234m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("456Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("5")) +} + +// TestReconcileBackend_ModifyExistingValuesOfResourceRequestsAndLimits tests whether backend deployment is updated with new resource requests and limits +func TestReconcileBackend_ModifyExistingValuesOfResourceRequestsAndLimits(t *testing.T) { + + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + instance := &pipelinesv1alpha1.GitopsService{ + Spec: pipelinesv1alpha1.GitopsServiceSpec{}, + } + Resources := &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("123Mi"), + corev1.ResourceCPU: resourcev1.MustParse("234m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("456Mi"), + corev1.ResourceCPU: resourcev1.MustParse("5"), + }, + } + instance.Spec.ConsolePlugin = &pipelinesv1alpha1.ConsolePluginStruct{ + Backend: &pipelinesv1alpha1.BackendStruct{ + Resources: Resources, + }, + GitopsPlugin: &pipelinesv1alpha1.GitopsPluginStruct{ + Resources: Resources, + }, + } + gitopsserviceNamespacedName := types.NamespacedName{ + Name: serviceName, + Namespace: serviceNamespace, + } + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + _, err := reconciler.reconcileBackend(gitopsserviceNamespacedName, instance, reqLogger) + assertNoError(t, err) + + // verify whether backend deployment is created with user defined resource requests and limits + deployment := &appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("123Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("234m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("456Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("5")) + + // Update resource requests and limits in GitopsService CR + updatedResource := &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("100Mi"), + corev1.ResourceCPU: resourcev1.MustParse("200m"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resourcev1.MustParse("300Mi"), + corev1.ResourceCPU: resourcev1.MustParse("400m"), + }, + } + instance.Spec.ConsolePlugin = &pipelinesv1alpha1.ConsolePluginStruct{ + Backend: &pipelinesv1alpha1.BackendStruct{ + Resources: updatedResource, + }, + GitopsPlugin: &pipelinesv1alpha1.GitopsPluginStruct{ + Resources: updatedResource, + }, + } + _, err = reconciler.reconcileBackend(gitopsserviceNamespacedName, instance, reqLogger) + assertNoError(t, err) + + // verify whether backend deployment is updated with new resource requests and limits + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("100Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("200m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("300Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("400m")) +} + +func TestReconcileBackend_DefaultRequestsAndLimits(t *testing.T) { + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + instance := &pipelinesv1alpha1.GitopsService{} + gitopsserviceNamespacedName := types.NamespacedName{ + Name: serviceName, + Namespace: serviceNamespace, + } + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + _, err := reconciler.reconcileBackend(gitopsserviceNamespacedName, instance, reqLogger) + assertNoError(t, err) + // verify whether backend deployment is created with default resource requests and limits + deployment := &appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, deployment) + assertNoError(t, err) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("128Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("250m")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("256Mi")) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("500m")) +} + func addKnownTypesToScheme(scheme *runtime.Scheme) { scheme.AddKnownTypes(configv1.GroupVersion, &configv1.ClusterVersion{}) scheme.AddKnownTypes(pipelinesv1alpha1.GroupVersion, &pipelinesv1alpha1.GitopsService{}) diff --git a/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go b/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go index 9d81ade4f..5053074ff 100644 --- a/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go +++ b/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go @@ -397,6 +397,14 @@ func HaveServiceAccountName(expectedServiceAccountName string) matcher.GomegaMat }) } +// HaveResourceRequirements validates if the deployment object contains the given resource requirements. +func HaveResourceRequirements(requirements *corev1.ResourceRequirements) matcher.GomegaMatcher { + return fetchDeployment(func(depl *appsv1.Deployment) bool { + GinkgoWriter.Println("Deployment HaveResourceRequirements:", "expected: ", requirements.String(), "actual: ", depl.Spec.Template.Spec.Containers[0].Resources.String()) + return reflect.DeepEqual(requirements, depl.Spec.Template.Spec.Containers[0].Resources) + }) +} + // This is intentionally NOT exported, for now. Create another function in this file/package that calls this function, and export that. func fetchDeployment(f func(*appsv1.Deployment) bool) matcher.GomegaMatcher { diff --git a/test/openshift/e2e/ginkgo/sequential/1-121-valiate_resource_constraints_gitopsservice_test.go b/test/openshift/e2e/ginkgo/sequential/1-121-valiate_resource_constraints_gitopsservice_test.go new file mode 100644 index 000000000..611315e58 --- /dev/null +++ b/test/openshift/e2e/ginkgo/sequential/1-121-valiate_resource_constraints_gitopsservice_test.go @@ -0,0 +1,320 @@ +package sequential + +import ( + "context" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + gitopsoperatorv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + clusterserviceversionFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/clusterserviceversion" + deploymentFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/deployment" + gitopsserviceFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/gitopsservice" + k8sFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/k8s" + osFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/os" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/utils" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// --- Helper Functions --- + +func getCSV(ctx context.Context, k8sClient client.Client) *olmv1alpha1.ClusterServiceVersion { + var csvList olmv1alpha1.ClusterServiceVersionList + Expect(k8sClient.List(ctx, &csvList, client.InNamespace("openshift-gitops-operator"))).To(Succeed()) + for idx := range csvList.Items { + idxCSV := csvList.Items[idx] + if strings.Contains(idxCSV.Name, "gitops-operator") { + return &idxCSV + } + } + return nil +} + +func getOCPVersion() string { + output, err := osFixture.ExecCommand("oc", "version") + Expect(err).ToNot(HaveOccurred()) + for _, line := range strings.Split(output, "\n") { + if strings.Contains(line, "Server Version:") { + return strings.TrimSpace(line[strings.Index(line, ":")+1:]) + } + } + return "" +} + +func addDynamicPluginEnv(csv *olmv1alpha1.ClusterServiceVersion, ocVersion string) { + clusterserviceversionFixture.Update(csv, func(csv *olmv1alpha1.ClusterServiceVersion) { + envList := csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Env + envList = append(envList, corev1.EnvVar{Name: "DYNAMIC_PLUGIN_START_OCP_VERSION", Value: ocVersion}) + csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Env = envList + }) +} + +func verifyResourceConstraints(k8sClient client.Client, deplName string, expectedReq, expectedLim corev1.ResourceList) { + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: "openshift-gitops"}} + Eventually(func() corev1.ResourceRequirements { + _ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(depl), depl) + containers := depl.Spec.Template.Spec.Containers + if len(containers) == 0 { + return corev1.ResourceRequirements{} + } + return containers[0].Resources + }, "2m", "5s").Should(SatisfyAll( + WithTransform(func(r corev1.ResourceRequirements) corev1.ResourceList { return r.Requests }, Equal(expectedReq)), + WithTransform(func(r corev1.ResourceRequirements) corev1.ResourceList { return r.Limits }, Equal(expectedLim)), + )) +} + +// --- Test Suite --- + +var _ = Describe("GitOps Operator Sequential E2E Tests", func() { + + Context("1-121-validate_resource_constraints_gitopsservice_test", func() { + var ( + ctx context.Context + k8sClient client.Client + ) + BeforeEach(func() { + fixture.EnsureSequentialCleanSlate() + k8sClient, _ = utils.GetE2ETestKubeClient() + ctx = context.Background() + }) + + It("validates that GitOpsService can take in custom resource constraints", func() { + csv := getCSV(ctx, k8sClient) + Expect(csv).ToNot(BeNil()) + defer func() { Expect(fixture.RemoveDynamicPluginFromCSV(ctx, k8sClient)).To(Succeed()) }() + + ocVersion := getOCPVersion() + Expect(ocVersion).ToNot(BeEmpty()) + if strings.Contains(ocVersion, "4.15.") { + Skip("skipping this test as OCP version is 4.15") + return + } + addDynamicPluginEnv(csv, ocVersion) + + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) + Eventually(depl, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) + + Expect(k8sClient.Delete(context.Background(), &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}, + })).To(Succeed()) + Eventually(func() bool { + gitopsService := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}, + } + err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(gitopsService), gitopsService) + return err != nil + }).Should(BeTrue()) + + gitops := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}, + Spec: gitopsoperatorv1alpha1.GitopsServiceSpec{ + ConsolePlugin: &gitopsoperatorv1alpha1.ConsolePluginStruct{ + Backend: &gitopsoperatorv1alpha1.BackendStruct{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("200Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("300m"), + corev1.ResourceMemory: resource.MustParse("400Mi"), + }, + }, + }, + GitopsPlugin: &gitopsoperatorv1alpha1.GitopsPluginStruct{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("200Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("300m"), + corev1.ResourceMemory: resource.MustParse("400Mi"), + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(context.Background(), gitops)).To(Succeed()) + Expect(gitops).To(k8sFixture.ExistByName()) + + defer func() { + gitopsserviceFixture.Update(gitops, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ConsolePlugin.Backend.Resources = nil + gs.Spec.ConsolePlugin.GitopsPlugin.Resources = nil + }) + }() + + expectedReq := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("200Mi"), + } + expectedLim := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("300m"), + corev1.ResourceMemory: resource.MustParse("400Mi"), + } + verifyResourceConstraints(k8sClient, "gitops-plugin", expectedReq, expectedLim) + verifyResourceConstraints(k8sClient, "cluster", expectedReq, expectedLim) + }) + + It("validates that GitOpsService can update resource constraints", func() { + csv := getCSV(ctx, k8sClient) + Expect(csv).ToNot(BeNil()) + defer func() { Expect(fixture.RemoveDynamicPluginFromCSV(ctx, k8sClient)).To(Succeed()) }() + + ocVersion := getOCPVersion() + Expect(ocVersion).ToNot(BeEmpty()) + if strings.Contains(ocVersion, "4.15.") { + Skip("skipping this test as OCP version is 4.15") + return + } + addDynamicPluginEnv(csv, ocVersion) + + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) + Eventually(depl, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) + + gitopsService := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}, + } + Expect(gitopsService).To(k8sFixture.ExistByName()) + + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ConsolePlugin = &gitopsoperatorv1alpha1.ConsolePluginStruct{ + Backend: &gitopsoperatorv1alpha1.BackendStruct{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("123m"), + corev1.ResourceMemory: resource.MustParse("234Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("345m"), + corev1.ResourceMemory: resource.MustParse("456Mi"), + }, + }, + }, + GitopsPlugin: &gitopsoperatorv1alpha1.GitopsPluginStruct{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("123m"), + corev1.ResourceMemory: resource.MustParse("234Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("345m"), + corev1.ResourceMemory: resource.MustParse("456Mi"), + }, + }, + }, + } + }) + + defer func() { + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ConsolePlugin.Backend.Resources = nil + gs.Spec.ConsolePlugin.GitopsPlugin.Resources = nil + }) + }() + + k8sClient, _ := utils.GetE2ETestKubeClient() + expectedReq := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("123m"), + corev1.ResourceMemory: resource.MustParse("234Mi"), + } + expectedLim := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("345m"), + corev1.ResourceMemory: resource.MustParse("456Mi"), + } + verifyResourceConstraints(k8sClient, "gitops-plugin", expectedReq, expectedLim) + verifyResourceConstraints(k8sClient, "cluster", expectedReq, expectedLim) + }) + + It("validates gitops plugin and backend can have different resource constraints", func() { + csv := getCSV(ctx, k8sClient) + Expect(csv).ToNot(BeNil()) + defer func() { Expect(fixture.RemoveDynamicPluginFromCSV(ctx, k8sClient)).To(Succeed()) }() + + ocVersion := getOCPVersion() + Expect(ocVersion).ToNot(BeEmpty()) + if strings.Contains(ocVersion, "4.15.") { + Skip("skipping this test as OCP version is 4.15") + return + } + addDynamicPluginEnv(csv, ocVersion) + + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) + Eventually(depl, "60s", "5s").Should(deploymentFixture.HaveReadyReplicas(1)) + + gitopsService := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}, + } + Expect(gitopsService).To(k8sFixture.ExistByName()) + + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ConsolePlugin = &gitopsoperatorv1alpha1.ConsolePluginStruct{ + Backend: &gitopsoperatorv1alpha1.BackendStruct{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("123m"), + corev1.ResourceMemory: resource.MustParse("234Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("345m"), + corev1.ResourceMemory: resource.MustParse("456Mi"), + }, + }, + }, + GitopsPlugin: &gitopsoperatorv1alpha1.GitopsPluginStruct{ + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("223m"), + corev1.ResourceMemory: resource.MustParse("334Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("445m"), + corev1.ResourceMemory: resource.MustParse("556Mi"), + }, + }, + }, + } + }) + + defer func() { + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ConsolePlugin.Backend.Resources = nil + gs.Spec.ConsolePlugin.GitopsPlugin.Resources = nil + }) + }() + k8sClient, _ := utils.GetE2ETestKubeClient() + verifyResourceConstraints(k8sClient, "gitops-plugin", + corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("223m"), + corev1.ResourceMemory: resource.MustParse("334Mi"), + }, + corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("445m"), + corev1.ResourceMemory: resource.MustParse("556Mi"), + }, + ) + verifyResourceConstraints(k8sClient, "cluster", + corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("123m"), + corev1.ResourceMemory: resource.MustParse("234Mi"), + }, + corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("345m"), + corev1.ResourceMemory: resource.MustParse("456Mi"), + }, + ) + }) + }) +})