From 90000c56112465590b60fa83ad299236a7e6a9f0 Mon Sep 17 00:00:00 2001 From: Anand Francis Joseph Date: Fri, 8 Aug 2025 08:34:45 +0530 Subject: [PATCH 1/4] Added support for resource requirements in GitOpsService Signed-off-by: Anand Francis Joseph --- api/v1alpha1/gitopsservice_types.go | 3 ++ controllers/consoleplugin.go | 4 ++ controllers/gitopsservice_controller.go | 5 +++ controllers/gitopsservice_controller_test.go | 46 ++++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/api/v1alpha1/gitopsservice_types.go b/api/v1alpha1/gitopsservice_types.go index 7cca5136a..d2cd0c77a 100644 --- a/api/v1alpha1/gitopsservice_types.go +++ b/api/v1alpha1/gitopsservice_types.go @@ -32,6 +32,9 @@ 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"` + + // Resources defines the Compute Resources required by the console-plugin and gitops-backend pods. + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } // GitopsServiceStatus defines the observed state of GitopsService diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go index 168384289..f32705865 100644 --- a/controllers/consoleplugin.go +++ b/controllers/consoleplugin.go @@ -288,6 +288,10 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop newPluginDeployment.Spec.Template.Spec.Tolerations = cr.Spec.Tolerations } + if cr.Spec.Resources != nil { + newPluginDeployment.Spec.Template.Spec.Resources = cr.Spec.Resources + } + // Check if this Deployment already exists existingPluginDeployment := &appsv1.Deployment{} diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index 4961a1695..f2f4875b6 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -626,6 +626,11 @@ func (r *ReconcileGitopsService) reconcileBackend(gitopsserviceNamespacedName ty if len(instance.Spec.Tolerations) > 0 { deploymentObj.Spec.Template.Spec.Tolerations = instance.Spec.Tolerations } + + if instance.Spec.Resources != nil { + deploymentObj.Spec.Template.Spec.Resources = instance.Spec.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}, diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index e1bbc3005..1376484a9 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -741,6 +741,52 @@ func TestReconcile_PSSLabels(t *testing.T) { } } +func TestReconcile_Resources(t *testing.T) { + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + gitopsService := &pipelinesv1alpha1.GitopsService{ + ObjectMeta: v1.ObjectMeta{ + Name: serviceName, + }, + Spec: pipelinesv1alpha1.GitopsServiceSpec{ + RunOnInfra: true, + Tolerations: deploymentDefaultTolerations(), + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resourcev1.MustParse("100m"), + corev1.ResourceMemory: resourcev1.MustParse("64Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resourcev1.MustParse("200m"), + corev1.ResourceMemory: resourcev1.MustParse("128Mi"), + }, + }, + }, + } + fakeClient := fake.NewFakeClient(gitopsService) + reconciler := newReconcileGitOpsService(fakeClient, s) + + _, err := reconciler.Reconcile(context.TODO(), newRequest("test", "test")) + assertNoError(t, err) + + deployment := appsv1.Deployment{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: serviceNamespace}, &deployment) + assertNoError(t, err) + nSelector := common.InfraNodeSelector() + argoutil.AppendStringMap(nSelector, argocommon.DefaultNodeSelector()) + assert.DeepEqual(t, deployment.Spec.Template.Spec.NodeSelector, nSelector) + assert.DeepEqual(t, deployment.Spec.Template.Spec.Tolerations, deploymentDefaultTolerations()) + assert.DeepEqual(t, deployment.Spec.Template.Spec.Resources.Requests, corev1.ResourceList{ + corev1.ResourceCPU: resourcev1.MustParse("100m"), + corev1.ResourceMemory: resourcev1.MustParse("64Mi"), + }) + assert.DeepEqual(t, deployment.Spec.Template.Spec.Resources.Limits, corev1.ResourceList{ + corev1.ResourceCPU: resourcev1.MustParse("200m"), + corev1.ResourceMemory: resourcev1.MustParse("128Mi"), + }) +} + func addKnownTypesToScheme(scheme *runtime.Scheme) { scheme.AddKnownTypes(configv1.GroupVersion, &configv1.ClusterVersion{}) scheme.AddKnownTypes(pipelinesv1alpha1.GroupVersion, &pipelinesv1alpha1.GitopsService{}) From 6c230e278199cc25fa2c859b6a9f36d259fcb1c6 Mon Sep 17 00:00:00 2001 From: Anand Francis Joseph Date: Tue, 16 Sep 2025 12:25:56 +0530 Subject: [PATCH 2/4] Added initial version of e2e test which needs improvment and additional tests Signed-off-by: Anand Francis Joseph --- .../e2e/ginkgo/fixture/deployment/fixture.go | 8 +++ ...esource_constraints_gitopsservicee_test.go | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 test/openshift/e2e/ginkgo/parallel/1-120-validate_resource_constraints_gitopsservicee_test.go diff --git a/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go b/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go index fa51bcfc6..4d5559b87 100644 --- a/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go +++ b/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go @@ -364,6 +364,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.Resources.String()) + return reflect.DeepEqual(requirements, depl.Spec.Template.Spec.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/parallel/1-120-validate_resource_constraints_gitopsservicee_test.go b/test/openshift/e2e/ginkgo/parallel/1-120-validate_resource_constraints_gitopsservicee_test.go new file mode 100644 index 000000000..4f523bc63 --- /dev/null +++ b/test/openshift/e2e/ginkgo/parallel/1-120-validate_resource_constraints_gitopsservicee_test.go @@ -0,0 +1,70 @@ +package parallel + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gitopsoperatorv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + 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" + 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" +) + +var _ = Describe("GitOps Operator Parallel E2E Tests", func() { + + Context("1-120-validate_resource_constraints_gitopsservice_test", func() { + BeforeEach(func() { + fixture.EnsureSequentialCleanSlate() + + }) + + It("validates that GitOpsService can take in custom resource constraints", func() { + + By("enabling resource constraints on GitOpsService CR") + gitopsService := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + } + Expect(gitopsService).To(k8sFixture.ExistByName()) + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.Resources = &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("200m"), + "memory": resource.MustParse("256Mi"), + }, + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("500m"), + "memory": resource.MustParse("512Mi"), + }, + } + }) + + // Ensure the change is reverted when the test exits + defer func() { + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.Resources = nil + }) + }() + + By("verifying the openshift-gitops resources have honoured the resource constraints") + clusterDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}} + Eventually(clusterDepl).Should( + And( + deploymentFixture.HaveResourceRequirements(&corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("200m"), + "memory": resource.MustParse("256Mi"), + }, + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("500m"), + "memory": resource.MustParse("512Mi"), + }, + }), + ), + ) + }) + }) +}) From fbdc05bfc48b588a525a68e65cec846a5dd92c37 Mon Sep 17 00:00:00 2001 From: Anand Francis Joseph Date: Tue, 16 Sep 2025 12:30:42 +0530 Subject: [PATCH 3/4] Added generated file Signed-off-by: Anand Francis Joseph --- api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fe3bf2289..a5d7ce72b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -101,6 +101,11 @@ func (in *GitopsServiceSpec) DeepCopyInto(out *GitopsServiceSpec) { (*out)[key] = val } } + 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 GitopsServiceSpec. From 87327e17eadf09caed73fb4294c21fb2b890b173 Mon Sep 17 00:00:00 2001 From: Anand Francis Joseph Date: Tue, 16 Sep 2025 14:04:37 +0530 Subject: [PATCH 4/4] Added missing changes for crds and manifests Signed-off-by: Anand Francis Joseph --- ...gitops-operator.clusterserviceversion.yaml | 2 +- ...pipelines.openshift.io_gitopsservices.yaml | 60 +++++++++++++++++++ ...pipelines.openshift.io_gitopsservices.yaml | 60 +++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/bundle/manifests/gitops-operator.clusterserviceversion.yaml b/bundle/manifests/gitops-operator.clusterserviceversion.yaml index 1ef532fae..8eab92b97 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-08-21T01:20:45Z" + createdAt: "2025-09-16T07:12:34Z" 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..275d8dfb6 100644 --- a/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml +++ b/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml @@ -45,6 +45,66 @@ spec: description: NodeSelector is a map of key value pairs used for node selection in the default workloads type: object + resources: + description: Resources defines the Compute Resources required by the + console-plugin and gitops-backend pods. + 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 runOnInfra: description: InfraNodeEnabled will add infra NodeSelector to all the default workloads of gitops operator diff --git a/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml b/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml index 539fb45bf..16d39d37a 100644 --- a/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml +++ b/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml @@ -45,6 +45,66 @@ spec: description: NodeSelector is a map of key value pairs used for node selection in the default workloads type: object + resources: + description: Resources defines the Compute Resources required by the + console-plugin and gitops-backend pods. + 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 runOnInfra: description: InfraNodeEnabled will add infra NodeSelector to all the default workloads of gitops operator