diff --git a/api/v1/postgres_types.go b/api/v1/postgres_types.go index 612f213d..6db1df17 100644 --- a/api/v1/postgres_types.go +++ b/api/v1/postgres_types.go @@ -678,7 +678,7 @@ func (p *Postgres) ToPeripheralResourceLookupKey() types.NamespacedName { } } -func (p *Postgres) ToUnstructuredZalandoPostgresql(z *zalando.Postgresql, c *corev1.ConfigMap, sc string, pgParamBlockList map[string]bool, rbs *BackupConfig, srcDB *Postgres, patroniTTL, patroniLoopWait, patroniRetryTimeout uint32, dboIsSuperuser bool, enableTlsCert bool, image string, cpuRequestsPercentage int) (*unstructured.Unstructured, error) { +func (p *Postgres) ToUnstructuredZalandoPostgresql(z *zalando.Postgresql, c *corev1.ConfigMap, sc string, pgParamBlockList map[string]bool, rbs *BackupConfig, srcDB *Postgres, patroniTTL, patroniLoopWait, patroniRetryTimeout uint32, dboIsSuperuser bool, enableTlsCert bool, image string, cpuRequestsPercentage int, memoryRequestsPercentage int) (*unstructured.Unstructured, error) { if z == nil { z = &zalando.Postgresql{} } @@ -719,8 +719,12 @@ func (p *Postgres) ToUnstructuredZalandoPostgresql(z *zalando.Postgresql, c *cor if err != nil { return nil, fmt.Errorf("failed to convert to unstructured zalando postgresql: %w", err) } + memReq, err := p.calculateMemoryRequests(p.Spec.Size.Memory, memoryRequestsPercentage) + if err != nil { + return nil, fmt.Errorf("failed to convert to unstructured zalando postgresql: %w", err) + } z.Spec.Resources.ResourceRequests.CPU = ptr.To(cpuReq) - z.Spec.Resources.ResourceRequests.Memory = ptr.To(p.Spec.Size.Memory) + z.Spec.Resources.ResourceRequests.Memory = ptr.To(memReq) z.Spec.Resources.ResourceLimits.CPU = ptr.To(p.Spec.Size.CPU) z.Spec.Resources.ResourceLimits.Memory = ptr.To(p.Spec.Size.Memory) z.Spec.TeamID = p.generateTeamID() @@ -1129,6 +1133,19 @@ func (p *Postgres) calculateCPURequests(c string, percentage int) (string, error return resource.NewMilliQuantity(min(value, milliValue), resource.BinarySI).String(), nil } +func (p *Postgres) calculateMemoryRequests(m string, percentage int) (string, error) { + mem, err := resource.ParseQuantity(m) + if err != nil { + return "", err + } + + bytes := mem.Value() + value := (bytes * int64(percentage)) / int64(100) + + // make sure the request is not higher than the given input value + return resource.NewQuantity(min(value, bytes), resource.BinarySI).String(), nil +} + // sanitize a string so it can be used as a label value. if the string is valid, it // will be returned unmodified. otherwise all illegal character will be replace with "_" // where multiple "_" will be shrinked to a single one. the string must also start and end diff --git a/api/v1/postgres_types_test.go b/api/v1/postgres_types_test.go index a7874fd1..73622188 100644 --- a/api/v1/postgres_types_test.go +++ b/api/v1/postgres_types_test.go @@ -383,7 +383,7 @@ func TestPostgresRestoreTimestamp_ToUnstructuredZalandoPostgresql(t *testing.T) p := &Postgres{ Spec: tt.spec, } - got, _ := p.ToUnstructuredZalandoPostgresql(nil, tt.c, tt.sc, tt.pgParamBlockList, tt.rbs, tt.srcDB, 130, 10, 60, false, false, "dockerImage", 66) + got, _ := p.ToUnstructuredZalandoPostgresql(nil, tt.c, tt.sc, tt.pgParamBlockList, tt.rbs, tt.srcDB, 130, 10, 60, false, false, "dockerImage", 66, 90) jsonZ, err := runtime.DefaultUnstructuredConverter.ToUnstructured(got) if err != nil { @@ -466,6 +466,73 @@ func Test_calculateCPURequests(t *testing.T) { } } +func Test_calculateMemoryRequests(t *testing.T) { + + tests := []struct { + name string + inputValue string + inputPercentage int + expectedResult string + expectErr bool + }{ + { + name: "Normal", + inputValue: "1Gi", + inputPercentage: 90, + expectedResult: "966367641", + expectErr: false, + }, + { + name: "Mi", + inputValue: "512Mi", + inputPercentage: 50, + expectedResult: "256Mi", + expectErr: false, + }, + { + name: "MoreThan100", + inputValue: "1Gi", + inputPercentage: 150, + expectedResult: "1Gi", + expectErr: false, + }, + { + name: "EmptyValue", + inputValue: "", + inputPercentage: 90, + expectedResult: "", + expectErr: true, + }, + { + name: "Same", + inputValue: "1Gi", + inputPercentage: 100, + expectedResult: "1Gi", + expectErr: false, + }, + } + for _, tt := range tests { + tt := tt // pin! + t.Run(tt.name, func(t *testing.T) { + p := &Postgres{ + Spec: PostgresSpec{ + ProjectID: tt.name, + }, + } + + result, err := p.calculateMemoryRequests(tt.inputValue, tt.inputPercentage) + + if err != nil && !tt.expectErr { + t.Errorf("Unexpected error") + } + + if tt.expectedResult != result { + t.Errorf("Calculated memory request was %v, but expected %v", result, tt.expectedResult) + } + }) + } +} + func Test_sanitizeLabelValue(t *testing.T) { tests := []struct { name string // description of this test case diff --git a/controllers/postgres_controller.go b/controllers/postgres_controller.go index 230c9e44..562814c9 100644 --- a/controllers/postgres_controller.go +++ b/controllers/postgres_controller.go @@ -114,6 +114,7 @@ type PostgresReconciler struct { WalGExporterCPULimit string WalGExporterMemoryLimit string SpiloCpuRequestsPercentage int + SpiloMemoryRequestsPercentage int } type PatroniStandbyCluster struct { @@ -444,7 +445,7 @@ func (r *PostgresReconciler) createOrUpdateZalandoPostgresql(ctx context.Context return fmt.Errorf("failed to fetch zalando postgresql: %w", err) } - u, err := instance.ToUnstructuredZalandoPostgresql(nil, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSourceInstance, patroniTTL, patroniLoopWait, patroniRetryTimeout, r.EnableSuperUserForDBO, r.EnableCustomTLSCert, r.PostgresImage, r.SpiloCpuRequestsPercentage) + u, err := instance.ToUnstructuredZalandoPostgresql(nil, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSourceInstance, patroniTTL, patroniLoopWait, patroniRetryTimeout, r.EnableSuperUserForDBO, r.EnableCustomTLSCert, r.PostgresImage, r.SpiloCpuRequestsPercentage, r.SpiloMemoryRequestsPercentage) if err != nil { return fmt.Errorf("failed to convert to unstructured zalando postgresql: %w", err) } @@ -460,7 +461,7 @@ func (r *PostgresReconciler) createOrUpdateZalandoPostgresql(ctx context.Context // Update zalando postgresql mergeFrom := client.MergeFrom(rawZ.DeepCopy()) - u, err := instance.ToUnstructuredZalandoPostgresql(rawZ, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSourceInstance, patroniTTL, patroniLoopWait, patroniRetryTimeout, r.EnableSuperUserForDBO, r.EnableCustomTLSCert, r.PostgresImage, r.SpiloCpuRequestsPercentage) + u, err := instance.ToUnstructuredZalandoPostgresql(rawZ, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSourceInstance, patroniTTL, patroniLoopWait, patroniRetryTimeout, r.EnableSuperUserForDBO, r.EnableCustomTLSCert, r.PostgresImage, r.SpiloCpuRequestsPercentage, r.SpiloMemoryRequestsPercentage) if err != nil { return fmt.Errorf("failed to convert to unstructured zalando postgresql: %w", err) } diff --git a/main.go b/main.go index b42e7b46..33ebafca 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,7 @@ const ( enableSpiloReadinessProbeFlg = "enable-spilo-readiness-probe" enableKubernetesUseConfigMapsFlg = "enable-kubernetes-use-configmaps" spiloCpuRequestsPercentageFlag = "spilo-cpu-requests-percentage" + spiloMemoryRequestsPercentageFlag = "spilo-memory-requests-percentage" ) var ( @@ -182,6 +183,7 @@ func main() { podTopologySpreadConstraintMaxSkew int32 podTopologySpreadConstraintMinDomains int32 spiloCpuRequestsPercentage int + spiloMemoryRequestsPercentage int patroniTTL uint32 patroniLoopWait uint32 @@ -385,9 +387,12 @@ func main() { enableKubernetesUseConfigMaps = viper.GetBool(enableKubernetesUseConfigMapsFlg) // user defined value - viper.SetDefault(spiloCpuRequestsPercentageFlag, 50) + viper.SetDefault(spiloCpuRequestsPercentageFlag, 25) spiloCpuRequestsPercentage = viper.GetInt(spiloCpuRequestsPercentageFlag) + viper.SetDefault(spiloMemoryRequestsPercentageFlag, 90) + spiloMemoryRequestsPercentage = viper.GetInt(spiloMemoryRequestsPercentageFlag) + ctrl.Log.Info("flag", metricsAddrSvcMgrFlg, metricsAddrSvcMgr, metricsAddrCtrlMgrFlg, metricsAddrCtrlMgr, @@ -446,6 +451,7 @@ func main() { enableSpiloReadinessProbeFlg, enableSpiloReadinessProbe, enableKubernetesUseConfigMapsFlg, enableKubernetesUseConfigMaps, spiloCpuRequestsPercentageFlag, spiloCpuRequestsPercentage, + spiloMemoryRequestsPercentageFlag, spiloMemoryRequestsPercentage, ) svcClusterConf := ctrl.GetConfigOrDie() @@ -573,6 +579,7 @@ func main() { WalGExporterCPULimit: walGExporterCPULimit, WalGExporterMemoryLimit: walGExporterMemoryLimit, SpiloCpuRequestsPercentage: spiloCpuRequestsPercentage, + SpiloMemoryRequestsPercentage: spiloMemoryRequestsPercentage, }).SetupWithManager(ctrlPlaneClusterMgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Postgres") os.Exit(1)