diff --git a/.tekton/hypershift-operator-main-tag.yaml b/.tekton/hypershift-operator-main-tag.yaml index d6c21a719d9..1446a0d0e6c 100644 --- a/.tekton/hypershift-operator-main-tag.yaml +++ b/.tekton/hypershift-operator-main-tag.yaml @@ -185,6 +185,8 @@ spec: value: $(params.output-image).prefetch - name: ociArtifactExpiresAfter value: $(params.image-expires-after) + - name: enable-package-registry-proxy + value: "true" runAfter: - clone-repository taskRef: diff --git a/.tekton/pipelines/common-operator-build.yaml b/.tekton/pipelines/common-operator-build.yaml index fc77e0cd8e7..c221e279fd2 100644 --- a/.tekton/pipelines/common-operator-build.yaml +++ b/.tekton/pipelines/common-operator-build.yaml @@ -130,6 +130,8 @@ spec: value: $(params.image-expires-after) - name: dev-package-managers value: $(params.dev-package-managers) + - name: enable-package-registry-proxy + value: "true" runAfter: - clone-repository taskRef: diff --git a/support/controlplane-component/defaults.go b/support/controlplane-component/defaults.go index a1f0f2fcc8f..1fbd39f0308 100644 --- a/support/controlplane-component/defaults.go +++ b/support/controlplane-component/defaults.go @@ -425,15 +425,32 @@ func (c *controlPlaneWorkload[T]) applyRequestsOverrides(podTemplate *corev1.Pod for i, c := range podTemplate.Spec.InitContainers { if res, ok := requestsOverrides[c.Name]; ok { maps.Copy(podTemplate.Spec.InitContainers[i].Resources.Requests, res) + applyNonOvercommitableResourceLimits(&podTemplate.Spec.InitContainers[i], res) } } for i, c := range podTemplate.Spec.Containers { if res, ok := requestsOverrides[c.Name]; ok { maps.Copy(podTemplate.Spec.Containers[i].Resources.Requests, res) + applyNonOvercommitableResourceLimits(&podTemplate.Spec.Containers[i], res) } } } +const aroSwiftNICResource corev1.ResourceName = "aro.openshift.io/swift-nic" + +// applyNonOvercommitableResourceLimits sets limits equal to requests for extended +// resources that cannot be overcommitted, specifically "aro.openshift.io/swift-nic". +// The API server requires limits == requests for these resources. +// https://github.com/kubernetes/kubernetes/blob/621e250502ddeeab8274836e88b506c0c4f57232/pkg/apis/core/validation/validation.go#L7975-L7976 +func applyNonOvercommitableResourceLimits(container *corev1.Container, overrides corev1.ResourceList) { + if quantity, ok := overrides[aroSwiftNICResource]; ok { + if container.Resources.Limits == nil { + container.Resources.Limits = corev1.ResourceList{} + } + container.Resources.Limits[aroSwiftNICResource] = quantity + } +} + func parseResourceRequestOverrideAnnotation(value string) corev1.ResourceList { result := corev1.ResourceList{} resourceRequests := strings.Split(value, ",") diff --git a/support/controlplane-component/defaults_test.go b/support/controlplane-component/defaults_test.go index f1c726fe2af..5859e66fd54 100644 --- a/support/controlplane-component/defaults_test.go +++ b/support/controlplane-component/defaults_test.go @@ -10,6 +10,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/utils/ptr" @@ -285,6 +286,240 @@ func generateResources() (map[string]*corev1.Secret, map[string]*corev1.ConfigMa return secrets, configMaps } +func TestApplyRequestsOverrides(t *testing.T) { + tests := []struct { + name string + annotations map[string]string + containers []corev1.Container + initContainers []corev1.Container + expectedContainers []corev1.Container + expectedInitContainers []corev1.Container + }{ + { + name: "When overriding cpu and memory it should only update requests", + annotations: map[string]string{ + "resource-request-override.hypershift.openshift.io/router.router": "cpu=500m,memory=1Gi", + }, + containers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + }, + }, + }, + expectedContainers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + }, + }, + }, + { + name: "When overriding aro.openshift.io/swift-nic it should set both requests and limits", + annotations: map[string]string{ + "resource-request-override.hypershift.openshift.io/router.router": "aro.openshift.io/swift-nic=1", + }, + containers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{}, + }, + }, + }, + expectedContainers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + aroSwiftNICResource: resource.MustParse("1"), + }, + Limits: corev1.ResourceList{ + aroSwiftNICResource: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + name: "When overriding mixed resources it should set limits only for swift-nic", + annotations: map[string]string{ + "resource-request-override.hypershift.openshift.io/router.router": "cpu=500m,aro.openshift.io/swift-nic=1", + }, + containers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, + }, + expectedContainers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + aroSwiftNICResource: resource.MustParse("1"), + }, + Limits: corev1.ResourceList{ + aroSwiftNICResource: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + name: "When overriding an init container with swift-nic it should set both requests and limits", + annotations: map[string]string{ + "resource-request-override.hypershift.openshift.io/router.init-router": "aro.openshift.io/swift-nic=2", + }, + initContainers: []corev1.Container{ + { + Name: "init-router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{}, + }, + }, + }, + expectedInitContainers: []corev1.Container{ + { + Name: "init-router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + aroSwiftNICResource: resource.MustParse("2"), + }, + Limits: corev1.ResourceList{ + aroSwiftNICResource: resource.MustParse("2"), + }, + }, + }, + }, + }, + { + name: "When annotation targets a different deployment it should not apply overrides", + annotations: map[string]string{ + "resource-request-override.hypershift.openshift.io/kube-apiserver.kube-apiserver": "cpu=500m", + }, + containers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, + }, + expectedContainers: []corev1.Container{ + { + Name: "router", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewGomegaWithT(t) + + workload := &controlPlaneWorkload[*appsv1.Deployment]{ + name: "router", + workloadProvider: &deploymentProvider{}, + ComponentOptions: &testComponent{}, + } + hcp := &hyperv1.HostedControlPlane{} + hcp.Annotations = test.annotations + + podTemplate := &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: test.containers, + InitContainers: test.initContainers, + }, + } + + workload.applyRequestsOverrides(podTemplate, hcp) + + if test.expectedContainers != nil { + g.Expect(podTemplate.Spec.Containers).To(Equal(test.expectedContainers)) + } + if test.expectedInitContainers != nil { + g.Expect(podTemplate.Spec.InitContainers).To(Equal(test.expectedInitContainers)) + } + }) + } +} + +func TestApplyNonOvercommitableResourceLimits(t *testing.T) { + tests := []struct { + name string + overrides corev1.ResourceList + existingLimits corev1.ResourceList + expectedLimits corev1.ResourceList + }{ + { + name: "When overriding aro.openshift.io/swift-nic it should set the limit to the same value", + overrides: corev1.ResourceList{ + aroSwiftNICResource: resource.MustParse("1"), + }, + expectedLimits: corev1.ResourceList{ + aroSwiftNICResource: resource.MustParse("1"), + }, + }, + { + name: "When overriding standard resources it should not set limits", + overrides: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + expectedLimits: nil, + }, + { + name: "When overriding a mix of standard and swift-nic resources it should only set limits for swift-nic", + overrides: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + aroSwiftNICResource: resource.MustParse("2"), + }, + existingLimits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + }, + expectedLimits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + aroSwiftNICResource: resource.MustParse("2"), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewGomegaWithT(t) + container := &corev1.Container{ + Resources: corev1.ResourceRequirements{ + Limits: test.existingLimits, + }, + } + applyNonOvercommitableResourceLimits(container, test.overrides) + g.Expect(container.Resources.Limits).To(Equal(test.expectedLimits)) + }) + } +} + func TestSetDefaultOptions(t *testing.T) { g := NewGomegaWithT(t) scheme := runtime.NewScheme()