diff --git a/Makefile b/Makefile index 5fc8bac2c..df736b332 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ CONTROLLER_GEN=$(shell which controller-gen) endif .PHONY: test -test: +test: go test -cover `go list ./... | grep -v 'pkg/client'` .PHONY: mocks @@ -129,7 +129,7 @@ CUSTOM_RESOURCE_NAME=radix CUSTOM_RESOURCE_VERSION=v1 .PHONY: code-gen -code-gen: +code-gen: $(GOPATH)/pkg/mod/k8s.io/code-generator@v0.25.3/generate-groups.sh all $(ROOT_PACKAGE)/pkg/client $(ROOT_PACKAGE)/pkg/apis $(CUSTOM_RESOURCE_NAME):$(CUSTOM_RESOURCE_VERSION) --go-header-file $(GOPATH)/pkg/mod/k8s.io/code-generator@v0.25.3/hack/boilerplate.go.txt .PHONY: crds diff --git a/pkg/apis/batch/kubejob.go b/pkg/apis/batch/kubejob.go index daf584493..042023179 100644 --- a/pkg/apis/batch/kubejob.go +++ b/pkg/apis/batch/kubejob.go @@ -15,6 +15,7 @@ import ( operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/apis/utils/annotations" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" + "github.com/equinor/radix-operator/pkg/apis/utils/resources" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -269,10 +270,10 @@ func (s *syncer) getContainerEnvironmentVariables(rd *radixv1.RadixDeployment, j func (s *syncer) getContainerResources(batchJob *radixv1.RadixBatchJob, jobComponent *radixv1.RadixDeployJobComponent) corev1.ResourceRequirements { if batchJob.Resources != nil { - return operatorUtils.BuildResourceRequirement(batchJob.Resources) + return resources.New(resources.WithDefaults(), resources.WithSource(batchJob.Resources)) } - return operatorUtils.GetResourceRequirements(jobComponent) + return resources.GetResourceRequirements(jobComponent) } func getContainerPorts(radixJobComponent *radixv1.RadixDeployJobComponent) []corev1.ContainerPort { diff --git a/pkg/apis/batch/syncer_test.go b/pkg/apis/batch/syncer_test.go index f260d2179..5d8799840 100644 --- a/pkg/apis/batch/syncer_test.go +++ b/pkg/apis/batch/syncer_test.go @@ -491,8 +491,8 @@ func (s *syncerTestSuite) Test_BatchStaticConfiguration() { s.Equal(securitycontext.Pod(securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault)), kubejob.Spec.Template.Spec.SecurityContext) s.Equal(imageName, kubejob.Spec.Template.Spec.Containers[0].Image) s.Equal(securitycontext.Container(securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault)), kubejob.Spec.Template.Spec.Containers[0].SecurityContext) - s.Len(kubejob.Spec.Template.Spec.Containers[0].Resources.Limits, 0) - s.Len(kubejob.Spec.Template.Spec.Containers[0].Resources.Requests, 0) + s.Len(kubejob.Spec.Template.Spec.Containers[0].Resources.Limits, 1) + s.Len(kubejob.Spec.Template.Spec.Containers[0].Resources.Requests, 1) s.Len(kubejob.Spec.Template.Spec.Containers[0].Env, 5) s.True(slice.Any(kubejob.Spec.Template.Spec.Containers[0].Env, func(env corev1.EnvVar) bool { return env.Name == "VAR1" && env.ValueFrom.ConfigMapKeyRef.Key == "VAR1" && env.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name == kube.GetEnvVarsConfigMapName(componentName) diff --git a/pkg/apis/deployment/jobschedulercomponent.go b/pkg/apis/deployment/jobschedulercomponent.go index fd0a8482e..469b89402 100644 --- a/pkg/apis/deployment/jobschedulercomponent.go +++ b/pkg/apis/deployment/jobschedulercomponent.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" ) @@ -59,7 +60,19 @@ func (js *jobSchedulerComponent) GetMonitoring() bool { } func (js *jobSchedulerComponent) GetResources() *v1.ResourceRequirements { - return &v1.ResourceRequirements{} + return &v1.ResourceRequirements{ + Limits: map[string]string{ + "memory": "500M", + }, + Requests: map[string]string{ + "cpu": "20m", + "memory": "500M", + }, + } +} + +func (js *jobSchedulerComponent) GetReadOnlyFileSystem() *bool { + return pointers.Ptr(true) } func (js *jobSchedulerComponent) IsAlwaysPullImageOnDeploy() bool { diff --git a/pkg/apis/deployment/kubedeployment.go b/pkg/apis/deployment/kubedeployment.go index e8cdacc47..edd862d99 100644 --- a/pkg/apis/deployment/kubedeployment.go +++ b/pkg/apis/deployment/kubedeployment.go @@ -8,11 +8,11 @@ import ( "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - "github.com/equinor/radix-operator/pkg/apis/resources" "github.com/equinor/radix-operator/pkg/apis/securitycontext" "github.com/equinor/radix-operator/pkg/apis/utils" radixannotations "github.com/equinor/radix-operator/pkg/apis/utils/annotations" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" + "github.com/equinor/radix-operator/pkg/apis/utils/resources" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" @@ -270,7 +270,7 @@ func (deploy *Deployment) setDesiredDeploymentProperties(deployComponent v1.Radi desiredDeployment.Spec.Template.Spec.Containers[0].Ports = getContainerPorts(deployComponent) desiredDeployment.Spec.Template.Spec.Containers[0].ImagePullPolicy = corev1.PullAlways desiredDeployment.Spec.Template.Spec.Containers[0].SecurityContext = containerSecurityCtx - desiredDeployment.Spec.Template.Spec.Containers[0].Resources = utils.GetResourceRequirements(deployComponent) + desiredDeployment.Spec.Template.Spec.Containers[0].Resources = resources.GetResourceRequirements(deployComponent) volumeMounts, err := GetRadixDeployComponentVolumeMounts(deployComponent, deploy.radixDeployment.GetName()) if err != nil { diff --git a/pkg/apis/deployment/kubedeployment_test.go b/pkg/apis/deployment/kubedeployment_test.go index 10905bec7..4a513e59f 100644 --- a/pkg/apis/deployment/kubedeployment_test.go +++ b/pkg/apis/deployment/kubedeployment_test.go @@ -4,10 +4,12 @@ import ( "os" "testing" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/test" "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/equinor/radix-operator/pkg/apis/utils/resources" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -34,7 +36,7 @@ func TestGetResourceRequirements_BothProvided_BothReturned(t *testing.T) { component := utils.NewDeployComponentBuilder(). WithResource(request, limit). BuildComponent() - requirements := utils.GetResourceRequirements(&component) + requirements := resources.GetResourceRequirements(&component) assert.Equal(t, 0, requirements.Requests.Cpu().Cmp(resource.MustParse("0.1")), "CPU request should be included") assert.Equal(t, 0, requirements.Requests.Memory().Cmp(resource.MustParse("32Mi")), "Memory request should be included") @@ -54,13 +56,13 @@ func TestGetResourceRequirements_ProvideRequests_OnlyRequestsReturned(t *testing component := utils.NewDeployComponentBuilder(). WithResourceRequestsOnly(request). BuildComponent() - requirements := utils.GetResourceRequirements(&component) + requirements := resources.GetResourceRequirements(&component) assert.Equal(t, 0, requirements.Requests.Cpu().Cmp(resource.MustParse("0.2")), "CPU request should be included") assert.Equal(t, 0, requirements.Requests.Memory().Cmp(resource.MustParse("128Mi")), "Memory request should be included") assert.Equal(t, 0, requirements.Limits.Cpu().Cmp(resource.MustParse("0")), "Missing CPU limit should be 0") - assert.Equal(t, 0, requirements.Limits.Memory().Cmp(resource.MustParse("0")), "Missing memory limit should be 0") + assert.Equal(t, 0, requirements.Limits.Memory().Cmp(resource.MustParse("300M")), "Missing memory limit should default to 300M") } func TestGetResourceRequirements_ProvideRequestsCpu_OnlyRequestsCpuReturned(t *testing.T) { @@ -73,13 +75,13 @@ func TestGetResourceRequirements_ProvideRequestsCpu_OnlyRequestsCpuReturned(t *t component := utils.NewDeployComponentBuilder(). WithResourceRequestsOnly(request). BuildComponent() - requirements := utils.GetResourceRequirements(&component) + requirements := resources.GetResourceRequirements(&component) - assert.Equal(t, 0, requirements.Requests.Cpu().Cmp(resource.MustParse("0.3")), "CPU request should be included") - assert.Equal(t, 0, requirements.Requests.Memory().Cmp(resource.MustParse("0")), "Missing memory request should be 0") + assert.Equal(t, "300m", requirements.Requests.Cpu().String(), "CPU request should be included") + assert.Equal(t, "300M", requirements.Requests.Memory().String(), "Missing memory request should be 0") - assert.Equal(t, 0, requirements.Limits.Cpu().Cmp(resource.MustParse("0")), "Missing CPU limit should be 0") - assert.Equal(t, 0, requirements.Limits.Memory().Cmp(resource.MustParse("0")), "Missing memory limit should be 0") + assert.Equal(t, "0", requirements.Limits.Cpu().String(), "Missing CPU limit should be 0") + assert.Equal(t, "300M", requirements.Limits.Memory().String(), "Missing memory limit should be default") } func TestGetResourceRequirements_BothProvided_OverDefaultLimits(t *testing.T) { @@ -93,7 +95,7 @@ func TestGetResourceRequirements_BothProvided_OverDefaultLimits(t *testing.T) { component := utils.NewDeployComponentBuilder(). WithResourceRequestsOnly(request). BuildComponent() - requirements := utils.GetResourceRequirements(&component) + requirements := resources.GetResourceRequirements(&component) assert.Equal(t, 0, requirements.Requests.Cpu().Cmp(resource.MustParse("5")), "CPU request should be included") assert.Equal(t, 0, requirements.Requests.Memory().Cmp(resource.MustParse("5Gi")), "Memory request should be included") @@ -111,13 +113,13 @@ func TestGetResourceRequirements_ProvideRequestsCpu_OverDefaultLimits(t *testing component := utils.NewDeployComponentBuilder(). WithResourceRequestsOnly(request). BuildComponent() - requirements := utils.GetResourceRequirements(&component) + requirements := resources.GetResourceRequirements(&component) - assert.Equal(t, 0, requirements.Requests.Cpu().Cmp(resource.MustParse("6")), "CPU request should be included") - assert.Equal(t, 0, requirements.Requests.Memory().Cmp(resource.MustParse("0")), "Missing memory request should be 0") + assert.Equal(t, "6", requirements.Requests.Cpu().String(), "CPU request should be included") + assert.Equal(t, "300M", requirements.Requests.Memory().String(), "Missing memory request should be default") - assert.True(t, requirements.Limits.Cpu().IsZero()) - assert.Equal(t, 0, requirements.Limits.Memory().Cmp(resource.MustParse("0")), "Missing memory limit should be 0") + assert.True(t, requirements.Limits.Cpu().IsZero(), "Missing CPU limit should be Zero") + assert.Equal(t, "300M", requirements.Limits.Memory().String(), "Missing memory limit should be default") } func TestGetReadinessProbe_MissingDefaultEnvVars(t *testing.T) { @@ -260,20 +262,20 @@ func Test_UpdateResourcesInDeployment(t *testing.T) { desiredDeployment, _ := deployment.getDesiredCreatedDeploymentConfig(&component) desiredRes := desiredDeployment.Spec.Template.Spec.Containers[0].Resources - assert.Equal(t, 0, len(desiredRes.Requests)) - assert.Equal(t, 0, len(desiredRes.Limits)) + assert.Equal(t, 1, len(desiredRes.Requests), "Should have default values") + assert.Equal(t, 1, len(desiredRes.Limits), "Should have default values") }) t.Run("update requests and remove limit", func(t *testing.T) { deployment := applyDeploymentWithSyncWithComponentResources(t, origRequests, origLimits) - expectedRequests := map[string]string{"cpu": "20mi", "memory": "200M"} + expectedRequests := map[string]string{"cpu": "20m", "memory": "200M"} component := utils.NewDeployComponentBuilder().WithName("comp1").WithResource(expectedRequests, nil).BuildComponent() desiredDeployment, _ := deployment.getDesiredCreatedDeploymentConfig(&component) desiredRes := desiredDeployment.Spec.Template.Spec.Containers[0].Resources - assert.Equal(t, parseQuantity(expectedRequests["cpu"]), desiredRes.Requests["cpu"]) - assert.Equal(t, parseQuantity(expectedRequests["memory"]), desiredRes.Requests["memory"]) - assert.Equal(t, 0, len(desiredRes.Limits)) + assert.Equal(t, expectedRequests["cpu"], pointers.Ptr(desiredRes.Requests["cpu"]).String()) + assert.Equal(t, expectedRequests["memory"], pointers.Ptr(desiredRes.Requests["memory"]).String()) + assert.Equal(t, 1, len(desiredRes.Limits)) }) t.Run("remove requests and update limit", func(t *testing.T) { deployment := applyDeploymentWithSyncWithComponentResources(t, origRequests, origLimits) @@ -284,7 +286,7 @@ func Test_UpdateResourcesInDeployment(t *testing.T) { desiredDeployment, _ := deployment.getDesiredCreatedDeploymentConfig(&component) desiredRes := desiredDeployment.Spec.Template.Spec.Containers[0].Resources - assert.Equal(t, 0, len(desiredRes.Requests)) + assert.Equal(t, 1, len(desiredRes.Requests)) assert.Equal(t, parseQuantity(expectedLimits["cpu"]), desiredRes.Limits["cpu"]) assert.Equal(t, parseQuantity(expectedLimits["memory"]), desiredRes.Limits["memory"]) }) @@ -311,8 +313,8 @@ func TestDeployment_createJobAuxDeployment(t *testing.T) { assert.Equal(t, "job1-aux", jobAuxDeployment.GetName()) resources := jobAuxDeployment.Spec.Template.Spec.Containers[0].Resources s := resources.Requests.Cpu().String() - assert.Equal(t, "50m", s) - assert.Equal(t, "50M", resources.Requests.Memory().String()) - assert.Equal(t, "50m", resources.Limits.Cpu().String()) - assert.Equal(t, "50M", resources.Limits.Memory().String()) + assert.Equal(t, "1m", s) + assert.Equal(t, "10M", resources.Requests.Memory().String()) + assert.Equal(t, "0", resources.Limits.Cpu().String()) + assert.Equal(t, "10M", resources.Limits.Memory().String()) } diff --git a/pkg/apis/deployment/oauthproxyresourcemanager.go b/pkg/apis/deployment/oauthproxyresourcemanager.go index 83c023513..57d9fbdde 100644 --- a/pkg/apis/deployment/oauthproxyresourcemanager.go +++ b/pkg/apis/deployment/oauthproxyresourcemanager.go @@ -13,11 +13,11 @@ import ( "github.com/equinor/radix-operator/pkg/apis/ingress" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - "github.com/equinor/radix-operator/pkg/apis/resources" "github.com/equinor/radix-operator/pkg/apis/securitycontext" "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" oauthutil "github.com/equinor/radix-operator/pkg/apis/utils/oauth" + "github.com/equinor/radix-operator/pkg/apis/utils/resources" "github.com/rs/zerolog" "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" diff --git a/pkg/apis/metrics/custom_metrics.go b/pkg/apis/metrics/custom_metrics.go index 152a87551..577ddc4cb 100644 --- a/pkg/apis/metrics/custom_metrics.go +++ b/pkg/apis/metrics/custom_metrics.go @@ -3,14 +3,14 @@ package metrics import ( "time" - "github.com/equinor/radix-operator/pkg/apis/utils" + resourceutils "github.com/equinor/radix-operator/pkg/apis/utils/resources" + "k8s.io/apimachinery/pkg/api/resource" "github.com/equinor/radix-operator/pkg/apis/defaults" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" ) var ( @@ -72,7 +72,7 @@ func RequestedResources(rr *v1.RadixRegistration, rd *v1.RadixDeployment) { defaultMemory := defaults.GetDefaultMemoryRequest() for _, comp := range rd.Spec.Components { - resources := utils.GetResourceRequirements(&comp) + resources := resourceutils.GetResourceRequirements(&comp) nrReplicas := float64(comp.GetNrOfReplicas()) var cpu, memory resource.Quantity diff --git a/pkg/apis/resources/resources.go b/pkg/apis/resources/resources.go deleted file mode 100644 index 167c70254..000000000 --- a/pkg/apis/resources/resources.go +++ /dev/null @@ -1,65 +0,0 @@ -package resources - -import ( - "github.com/rs/zerolog/log" - corev1 "k8s.io/api/core/v1" - resourcev1 "k8s.io/apimachinery/pkg/api/resource" -) - -type ResourceOption func(resources *corev1.ResourceRequirements) - -func WithMemory(memory string) ResourceOption { - mem, err := resourcev1.ParseQuantity(memory) - if err != nil { - log.Error().Err(err).Str("memory", memory).Stack().Msg("failed to parse memory") - } - - return func(resources *corev1.ResourceRequirements) { - if resources.Limits == nil { - resources.Limits = corev1.ResourceList{} - } - - resources.Limits[corev1.ResourceMemory] = mem - } -} -func WithCPU(cpu string) ResourceOption { - c, err := resourcev1.ParseQuantity(cpu) - if err != nil { - log.Error().Err(err).Str("cpu", cpu).Stack().Msg("failed to parse cpu") - } - - return func(resources *corev1.ResourceRequirements) { - if resources.Requests == nil { - resources.Requests = corev1.ResourceList{} - } - - resources.Requests[corev1.ResourceCPU] = c - } -} - -func New(options ...ResourceOption) corev1.ResourceRequirements { - resources := corev1.ResourceRequirements{ - Requests: corev1.ResourceList{}, - Limits: corev1.ResourceList{}, - } - - for _, o := range options { - o(&resources) - } - - // If request is higher than limit, set limit equal to request - for key, requestVal := range resources.Requests { - if limitVal, hasLimit := resources.Limits[key]; hasLimit { - if requestVal.Cmp(limitVal) == 1 { - resources.Limits[key] = requestVal.DeepCopy() - } - } - } - - // If memory limit is set, enforce equal memory requests - if limitVal, hasLimit := resources.Limits[corev1.ResourceMemory]; hasLimit { - resources.Requests[corev1.ResourceMemory] = limitVal.DeepCopy() - } - - return resources -} diff --git a/pkg/apis/utils/resources.go b/pkg/apis/utils/resources.go deleted file mode 100644 index f0ca7df10..000000000 --- a/pkg/apis/utils/resources.go +++ /dev/null @@ -1,64 +0,0 @@ -package utils - -import ( - "github.com/equinor/radix-operator/pkg/apis/defaults" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" -) - -func GetResourceRequirements(deployComponent v1.RadixCommonDeployComponent) corev1.ResourceRequirements { - return BuildResourceRequirement(deployComponent.GetResources()) -} - -func BuildResourceRequirement(source *v1.ResourceRequirements) corev1.ResourceRequirements { - defaultLimits := map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceName("memory"): *defaults.GetDefaultMemoryLimit(), - } - - // if you only set limit, it will use the same values for request - limits := corev1.ResourceList{} - requests := corev1.ResourceList{} - - for name, limit := range source.Limits { - if limit != "" { - resName := corev1.ResourceName(name) - limits[resName], _ = resource.ParseQuantity(limit) - } - - // TODO: We probably should check some hard limit that cannot by exceeded here - } - - for name, req := range source.Requests { - if req != "" { - resName := corev1.ResourceName(name) - requests[resName], _ = resource.ParseQuantity(req) - - if _, hasLimit := limits[resName]; !hasLimit { - if _, ok := defaultLimits[resName]; !ok { - continue // No default limit for this resource - } - // There is no defined limit, but there is a request - reqQuantity := requests[resName] - if reqQuantity.Cmp(defaultLimits[resName]) == 1 { - // Requested quantity is larger than the default limit - // We use the requested value as the limit - limits[resName] = requests[resName].DeepCopy() - - // TODO: If we introduce a hard limit, that should not be exceeded here - } - } - } - } - - if len(limits) <= 0 && len(requests) <= 0 { - return corev1.ResourceRequirements{} - } - - req := corev1.ResourceRequirements{ - Limits: limits, - Requests: requests, - } - - return req -} diff --git a/pkg/apis/utils/resources/resources.go b/pkg/apis/utils/resources/resources.go new file mode 100644 index 000000000..0bfecaf48 --- /dev/null +++ b/pkg/apis/utils/resources/resources.go @@ -0,0 +1,142 @@ +package resources + +import ( + "github.com/equinor/radix-operator/pkg/apis/defaults" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/rs/zerolog/log" + corev1 "k8s.io/api/core/v1" + resourcev1 "k8s.io/apimachinery/pkg/api/resource" +) + +func GetResourceRequirements(deployComponent radixv1.RadixCommonDeployComponent) corev1.ResourceRequirements { + return New(WithDefaults(), WithSource(deployComponent.GetResources())) +} + +type ResourceOption func(resources *corev1.ResourceRequirements) + +func WithMemory(memory string) ResourceOption { + return func(resources *corev1.ResourceRequirements) { + mem, err := resourcev1.ParseQuantity(memory) + if err != nil { + log.Warn().Err(err).Str("memory", memory).Stack().Msg("failed to parse memory") + return + } + + if resources.Limits == nil { + resources.Limits = corev1.ResourceList{} + } + if resources.Requests == nil { + resources.Requests = corev1.ResourceList{} + } + + resources.Limits[corev1.ResourceMemory] = mem + resources.Requests[corev1.ResourceMemory] = mem + } +} + +func WithCPU(cpu string) ResourceOption { + return func(resources *corev1.ResourceRequirements) { + c, err := resourcev1.ParseQuantity(cpu) + if err != nil { + log.Warn().Err(err).Str("cpu", cpu).Stack().Msg("failed to parse cpu") + return + } + + if resources.Requests == nil { + resources.Requests = corev1.ResourceList{} + } + + resources.Requests[corev1.ResourceCPU] = c + } +} + +func WithDefaults() ResourceOption { + return func(resources *corev1.ResourceRequirements) { + if resources.Limits == nil { + resources.Limits = corev1.ResourceList{} + } + if resources.Requests == nil { + resources.Requests = corev1.ResourceList{} + } + + // TODO: Only set memory limit if request is set, and the deault is higher? + + if defaultMemory := defaults.GetDefaultMemoryLimit(); defaultMemory != nil { + resources.Limits[corev1.ResourceMemory] = *defaultMemory + resources.Requests[corev1.ResourceMemory] = *defaultMemory + } + + // TODO: Only set default CPU if missing? + if defaultCPU := defaults.GetDefaultCPURequest(); defaultCPU != nil { + resources.Requests[corev1.ResourceCPU] = *defaultCPU + } + } +} + +func WithSource(source *radixv1.ResourceRequirements) ResourceOption { + return func(resources *corev1.ResourceRequirements) { + if source == nil { + return + } + + if source.Limits != nil { + if resources.Limits == nil { + resources.Limits = corev1.ResourceList{} + } + + for key, value := range source.Limits { + v, err := resourcev1.ParseQuantity(value) + if err != nil { + log.Warn().Err(err).Str(key, value).Stack().Msg("failed to parse value") + continue + } + + resources.Limits[corev1.ResourceName(key)] = v.DeepCopy() + } + } + + if source.Requests != nil { + if resources.Requests == nil { + resources.Requests = corev1.ResourceList{} + } + + for key, value := range source.Requests { + v, err := resourcev1.ParseQuantity(value) + if err != nil { + log.Warn().Err(err).Str(key, value).Stack().Msg("failed to parse value") + continue + } + + resources.Requests[corev1.ResourceName(key)] = v.DeepCopy() + } + } + } +} + +func New(options ...ResourceOption) corev1.ResourceRequirements { + resources := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{}, + Limits: corev1.ResourceList{}, + } + + for _, o := range options { + o(&resources) + } + + // If request is higher than limit, set limit equal to request + for key, requestVal := range resources.Requests { + if limitVal, hasLimit := resources.Limits[key]; hasLimit { + if requestVal.Cmp(limitVal) == 1 { + resources.Limits[key] = requestVal.DeepCopy() + } + } + } + + // TODO: Enforce equal memory requests and limits when all RadixDeployments are newer + // If memory limit is set, enforce equal memory requests + // if limitVal, hasLimit := resources.Limits[corev1.ResourceMemory]; hasLimit { + // resources.Requests[corev1.ResourceMemory] = limitVal.DeepCopy() + // } + + return resources +}