From a38f7f9ea36cd27c1aed98de156ebcfcfaa70f32 Mon Sep 17 00:00:00 2001 From: jose luis <2064537+sosan@users.noreply.github.com> Date: Tue, 26 Mar 2024 20:58:57 +0100 Subject: [PATCH 1/4] added this labels: kompose.hpa.minreplicas kompose.hpa.maxreplicas kompose.hpa.cpu kompose.hpa.memory documentated labels user_guide.md generate custom metrics from cpu, memory and set min/max replicas Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> --- docs/user-guide.md | 61 +++ pkg/loader/compose/utils.go | 8 + pkg/transformer/kubernetes/k8sutils.go | 128 +++++ pkg/transformer/kubernetes/k8sutils_test.go | 578 ++++++++++++++++++++ pkg/transformer/kubernetes/kubernetes.go | 17 + 5 files changed, 792 insertions(+) diff --git a/docs/user-guide.md b/docs/user-guide.md index 1b8e9ac2..fa8e507e 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -211,6 +211,10 @@ The currently supported options are: | kompose.cronjob.schedule | kubernetes cronjob schedule (for example: '1 * * * *') | | kompose.cronjob.concurrency_policy | 'Forbid' / 'Allow' / 'Never' / '' | | kompose.cronjob.backoff_limit | kubernetes cronjob backoff limit (for example: '6') | +| kompose.hpa.minreplicas | defines Horizontal Pod Autoscaler min pod replicas | +| kompose.hpa.maxreplicas | defines Horizontal Pod Autoscaler max pod replicas | +| kompose.hpa.cpu | defines Horizontal Pod Autoscaler cpu utilization trigger | +| kompose.hpa.memory | defines Horizontal Pod Autoscaler memory utilization trigger | **Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail. @@ -467,6 +471,63 @@ services: labels: kompose.volume.sub-path: pg-data ``` + +- `kompose.hpa.minreplicas` defines minimum replicas from Horizontal Pod Autoscaler. Default value 1 [HPA Min Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). + +For example: + +```yaml +version: '3.8' + +services: + pgadmin: + image: postgres + labels: + kompose.hpa.minreplicas: 1 +``` + +- `kompose.hpa.maxreplicas` defines maximum replicas from Horizontal Pod Autoscaler. Default value 10 [HPA Max Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). + +For example: + +```yaml +version: '3.8' + +services: + pgadmin: + image: postgres + labels: + kompose.hpa.maxreplicas: 10 +``` + +- `kompose.hpa.cpu` defines % cpu utilization trigger scale from Horizontal Pod Autoscaler. It is represented as a percentage of a resource. Default value: 50 [HPA CPU Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). + +For example: + +```yaml +version: '3.8' + +services: + pgadmin: + image: postgres + labels: + kompose.hpa.cpu: 50 +``` + +- `kompose.hpa.memory` defines memory utilization trigger scale from Horizontal Pod Autoscaler. It is represented as a percentage of a resource. Default value: 70 [HPA Memory Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). + +For example: + +```yaml +version: '3.8' + +services: + pgadmin: + image: postgres + labels: + kompose.hpa.memory: 50 +``` + ## Restart If you want to create normal pods without controller you can use `restart` construct of compose to define that. Follow table below to see what happens on the `restart` value. diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index a101458c..cdf8acb5 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -87,6 +87,14 @@ const ( LabelCronJobConcurrencyPolicy = "kompose.cronjob.concurrency_policy" // LabelCronJobBackoffLimit defines the job backoff limit LabelCronJobBackoffLimit = "kompose.cronjob.backoff_limit" + // LabelHpaMinReplicas defines min pod replicas + LabelHpaMinReplicas = "kompose.hpa.minreplicas" + // LabelHpaMaxReplicas defines max pod replicas + LabelHpaMaxReplicas = "kompose.hpa.maxreplicas" + // LabelHpaCpu defines scaling decisions based on CPU utilization + LabelHpaCPU = "kompose.hpa.cpu" + // LabelHpaMemory defines scaling decisions based on memory utilization + LabelHpaMemory = "kompose.hpa.memory" ) // load environment variables from compose file diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 890e3455..528d8613 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -41,6 +41,7 @@ import ( log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" appsv1 "k8s.io/api/apps/v1" + hpa "k8s.io/api/autoscaling/v2beta2" api "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,6 +49,29 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// Default values for Horizontal Pod Autoscaler (HPA) +const ( + DefaultMinReplicas = 1 + DefaultMaxReplicas = 10 + DefaultCpuUtilization = 50 + DefaultMemoryUtilization = 70 +) + +// LabelKeys are the keys for HPA related labels in the service +var LabelKeys = []string{ + compose.LabelHpaCPU, + compose.LabelHpaMemory, + compose.LabelHpaMinReplicas, + compose.LabelHpaMaxReplicas, +} + +type HpaValues struct { + MinReplicas int32 + MaxReplicas int32 + CPUtilization int32 + MemoryUtilization int32 +} + /** * Generate Helm Chart configuration */ @@ -985,3 +1009,107 @@ func reformatSecretConfigUnderscoreWithDash(secretConfig types.ServiceSecretConf return newSecretConfig } + +// searchHPAValues is useful to check if labels +// contains any labels related to Horizontal Pod Autoscaler +func searchHPAValues(labels map[string]string) bool { + found := true + for _, value := range LabelKeys { + if _, ok := labels[value]; ok { + return found + } + } + return !found +} + +// createHPAResources creates a HorizontalPodAutoscaler (HPA) resource +// It sets the number of replicas in the service to 0 because +// the number of replicas will be managed by the HPA +func createHPAResources(name string, service *kobject.ServiceConfig) hpa.HorizontalPodAutoscaler { + valuesHpa := getResourceHpaValues(service) + service.Replicas = 0 + metrics := getHpaMetricSpec(valuesHpa) + scalerSpecs := hpa.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: hpa.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: hpa.CrossVersionObjectReference{ + Kind: "Deployment", + Name: name, + APIVersion: "apps/v1", + }, + MinReplicas: &valuesHpa.MinReplicas, + MaxReplicas: valuesHpa.MaxReplicas, + Metrics: metrics, + }, + } + + return scalerSpecs +} + +// getResourceHpaValues retrieves the min/max replicas and CPU/memory utilization values +// control if maxReplicas is less than minReplicas +func getResourceHpaValues(service *kobject.ServiceConfig) HpaValues { + minReplicas := getHpaValue(service, compose.LabelHpaMinReplicas, DefaultMinReplicas) + maxReplicas := getHpaValue(service, compose.LabelHpaMaxReplicas, DefaultMaxReplicas) + + if maxReplicas < minReplicas { + maxReplicas = minReplicas + } + + return HpaValues{ + MinReplicas: minReplicas, + MaxReplicas: maxReplicas, + CPUtilization: getHpaValue(service, compose.LabelHpaCPU, DefaultCpuUtilization), + MemoryUtilization: getHpaValue(service, compose.LabelHpaMemory, DefaultMemoryUtilization), + } +} + +// getHpaValue convert the label value to integer +// If the label is not present or the conversion fails +// it returns the provided default value +func getHpaValue(service *kobject.ServiceConfig, label string, defaultValue int32) int32 { + valueFromLabel, err := strconv.Atoi(service.Labels[label]) + if err != nil || valueFromLabel <= 0 { + return defaultValue + } + return int32(valueFromLabel) +} + +// getHpaMetricSpec returns a list of metric specs for the HPA resource +// Target type is hardcoded to hpa.UtilizationMetricType +// Each MetricSpec specifies the type metric CPU/memory and average utilization value +// to trigger scaling +func getHpaMetricSpec(hpaValues HpaValues) []hpa.MetricSpec { + var metrics []hpa.MetricSpec + if hpaValues.CPUtilization > 0 { + metrics = append(metrics, hpa.MetricSpec{ + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: api.ResourceCPU, + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &hpaValues.CPUtilization, + }, + }, + }) + } + if hpaValues.MemoryUtilization > 0 { + metrics = append(metrics, hpa.MetricSpec{ + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: api.ResourceMemory, + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &hpaValues.MemoryUtilization, + }, + }, + }) + } + return metrics +} diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index 637d7f2b..1456c563 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -29,7 +29,9 @@ import ( "github.com/kubernetes/kompose/pkg/testutils" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" + hpa "k8s.io/api/autoscaling/v2beta2" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) /* @@ -738,3 +740,579 @@ func TestRemoveEmptyInterfaces(t *testing.T) { }) } } + +func Test_getHpaValue(t *testing.T) { + type args struct { + service *kobject.ServiceConfig + label string + defaultValue int32 + } + tests := []struct { + name string + args args + want int32 + }{ + // LabelHpaMinReplicas + { + name: "LabelHpaMinReplicas with 1 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaMinReplicas, + defaultValue: 1, + }, + want: 1, + }, + { + name: "LabelHpaMinReplicas with 0 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "0", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaMinReplicas, + defaultValue: 1, + }, + want: 1, + }, + { + name: "LabelHpaMinReplicas with error value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "cannot transform", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaMinReplicas, + defaultValue: 1, + }, + want: 1, + }, + // LabelHpaMaxReplicas + { + name: "LabelHpaMaxReplicas with 10 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaMaxReplicas, + defaultValue: 30, + }, + want: 10, + }, + { + name: "LabelHpaMaxReplicas with 0 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "0", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaMaxReplicas, + defaultValue: 10, + }, + want: 10, + }, + { + name: "LabelHpaMaxReplicas with error value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "cannot transform", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaMaxReplicas, + defaultValue: 10, + }, + want: 10, + }, + // LabelHpaCPU + { + name: "LabelHpaCPU with 50 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaCPU, + defaultValue: 30, + }, + want: 50, + }, + { + name: "LabelHpaCPU with 0 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "0", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaCPU, + defaultValue: 50, + }, + want: 50, + }, + { + name: "LabelHpaCPU with error value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "cannot transform", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaCPU, + defaultValue: 50, + }, + want: 50, + }, + // LabelHpaMemory + { + name: "LabelHpaMemory with 70 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + label: compose.LabelHpaMemory, + defaultValue: 30, + }, + want: 70, + }, + { + name: "LabelHpaMemory with 0 value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "0", + }, + }, + label: compose.LabelHpaMemory, + defaultValue: 70, + }, + want: 70, + }, + { + name: "LabelHpaMemory with error value", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "cannot transform", + }, + }, + label: compose.LabelHpaMemory, + defaultValue: 70, + }, + want: 70, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getHpaValue(tt.args.service, tt.args.label, tt.args.defaultValue); got != tt.want { + t.Errorf("getHpaValue() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getResourceHpaValues(t *testing.T) { + type args struct { + service *kobject.ServiceConfig + } + tests := []struct { + name string + args args + want HpaValues + }{ + { + name: "check same values", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, + MaxReplicas: 10, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "check same values", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "5", + compose.LabelHpaMaxReplicas: "3", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + }, + want: HpaValues{ + MinReplicas: 5, + MaxReplicas: 5, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "with error values and use default values from LabelHpaMinReplicas", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "cannot transform", + compose.LabelHpaMaxReplicas: "3", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, + MaxReplicas: 3, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "LabelHpaMaxReplicas is minor to LabelHpaMinReplicas", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "6", + compose.LabelHpaMaxReplicas: "5", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + }, + want: HpaValues{ + MinReplicas: 6, + MaxReplicas: 6, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "error label and LabelHpaMaxReplicas is minor to LabelHpaMinReplicas", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "6", + compose.LabelHpaMaxReplicas: "5", + compose.LabelHpaCPU: "cannot transform", + compose.LabelHpaMemory: "70", + }, + }, + }, + want: HpaValues{ + MinReplicas: 6, + MaxReplicas: 6, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "error label and LabelHpaMaxReplicas is minor to LabelHpaMinReplicas", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "6", + compose.LabelHpaMaxReplicas: "5", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "cannot transform", + }, + }, + }, + want: HpaValues{ + MinReplicas: 6, + MaxReplicas: 6, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "all error label", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "cannot transform", + compose.LabelHpaMaxReplicas: "cannot transform", + compose.LabelHpaCPU: "cannot transform", + compose.LabelHpaMemory: "cannot transform", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, + MaxReplicas: 10, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "error label without some labels", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "cannot transform", + compose.LabelHpaMaxReplicas: "cannot transform", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, + MaxReplicas: 10, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + { + name: "without labels, should return default values", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{}, + }, + }, + want: HpaValues{ + MinReplicas: 1, + MaxReplicas: 10, + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getResourceHpaValues(tt.args.service); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getResourceHpaValues() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getHpaMetricSpec(t *testing.T) { + valueCPUFixed := int32(50) + valueMemoryFixed := int32(70) + type args struct { + hpaValues HpaValues + } + tests := []struct { + name string + args args + want []hpa.MetricSpec + }{ + { + name: "no values", + args: args{ + hpaValues: HpaValues{}, + }, + want: nil, + }, + { + name: "only cpu", + args: args{ + hpaValues: HpaValues{ + CPUtilization: 50, + }, + }, + want: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + }, + }, + { + name: "only memory", + args: args{ + hpaValues: HpaValues{ + MemoryUtilization: 70, + }, + }, + want: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + { + name: "cpu and memory", + args: args{ + hpaValues: HpaValues{ + CPUtilization: 50, + MemoryUtilization: 70, + }, + }, + want: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getHpaMetricSpec(tt.args.hpaValues); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getHpaMetricSpec() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_createHPAResources(t *testing.T) { + valueCPUFixed := int32(50) + valueMemoryFixed := int32(70) + fixedMinReplicas := int32(1) + type args struct { + name string + service *kobject.ServiceConfig + } + tests := []struct { + name string + args args + want hpa.HorizontalPodAutoscaler + }{ + { + name: "all labels", + args: args{ + name: "web", + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaCPU: "50", + compose.LabelHpaMemory: "70", + }, + }, + }, + want: hpa.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "web", + }, + Spec: hpa.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: hpa.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "web", + APIVersion: "apps/v1", + }, + MinReplicas: &fixedMinReplicas, + MaxReplicas: 10, + Metrics: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := createHPAResources(tt.args.name, tt.args.service); !reflect.DeepEqual(got, tt.want) { + t.Errorf("createHPAResources() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 360ebc1d..71ba912a 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -1654,6 +1654,10 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. return nil, err } } + err = k.configHorizontalPodScaler(name, service, opt, &objects) + if err != nil { + return nil, errors.Wrap(err, "Error transforming Kubernetes objects") + } allobjects = append(allobjects, objects...) } @@ -1718,3 +1722,16 @@ func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*a } return nil } + +// configHorizontalPodScaler create Hpa resource also append to the objects +// first checks if the service labels contain any HPA labels using the searchHPAValues +func (k *Kubernetes) configHorizontalPodScaler(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) (err error) { + found := searchHPAValues(service.Labels) + if !found { + return nil + } + + hpa := createHPAResources(name, &service) + *objects = append(*objects, &hpa) + return nil +} From 516930cceea91f9bb5612917b6a26e89bc0cdb97 Mon Sep 17 00:00:00 2001 From: jose luis <2064537+sosan@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:28:27 +0200 Subject: [PATCH 2/4] added more tests e2e tests fixed name labels --- docs/user-guide.md | 20 +- pkg/loader/compose/utils.go | 4 +- pkg/transformer/kubernetes/k8sutils_test.go | 482 +++++++++++++++++++- script/test/cmd/tests_new.sh | 5 + script/test/fixtures/hpa/compose.yaml | 9 + script/test/fixtures/hpa/output-k8s.yaml | 48 ++ 6 files changed, 530 insertions(+), 38 deletions(-) create mode 100644 script/test/fixtures/hpa/compose.yaml create mode 100644 script/test/fixtures/hpa/output-k8s.yaml diff --git a/docs/user-guide.md b/docs/user-guide.md index fa8e507e..a25f2b9d 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -211,8 +211,8 @@ The currently supported options are: | kompose.cronjob.schedule | kubernetes cronjob schedule (for example: '1 * * * *') | | kompose.cronjob.concurrency_policy | 'Forbid' / 'Allow' / 'Never' / '' | | kompose.cronjob.backoff_limit | kubernetes cronjob backoff limit (for example: '6') | -| kompose.hpa.minreplicas | defines Horizontal Pod Autoscaler min pod replicas | -| kompose.hpa.maxreplicas | defines Horizontal Pod Autoscaler max pod replicas | +| kompose.hpa.replicas.min | defines Horizontal Pod Autoscaler minimum number of pod replicas | +| kompose.hpa.replicas.max | defines Horizontal Pod Autoscaler maximum number of pod replicas | | kompose.hpa.cpu | defines Horizontal Pod Autoscaler cpu utilization trigger | | kompose.hpa.memory | defines Horizontal Pod Autoscaler memory utilization trigger | @@ -472,32 +472,28 @@ services: kompose.volume.sub-path: pg-data ``` -- `kompose.hpa.minreplicas` defines minimum replicas from Horizontal Pod Autoscaler. Default value 1 [HPA Min Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). +- `kompose.hpa.replicas.min` defines minimum replicas from Horizontal Pod Autoscaler. Default value 1 [HPA Min Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). For example: ```yaml -version: '3.8' - services: pgadmin: image: postgres labels: - kompose.hpa.minreplicas: 1 + kompose.hpa.replicas.min: 1 ``` -- `kompose.hpa.maxreplicas` defines maximum replicas from Horizontal Pod Autoscaler. Default value 10 [HPA Max Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). +- `kompose.hpa.replicas.max` defines maximum replicas from Horizontal Pod Autoscaler. Default value 10 [HPA Max Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). For example: ```yaml -version: '3.8' - services: pgadmin: image: postgres labels: - kompose.hpa.maxreplicas: 10 + kompose.hpa.replicas.max: 10 ``` - `kompose.hpa.cpu` defines % cpu utilization trigger scale from Horizontal Pod Autoscaler. It is represented as a percentage of a resource. Default value: 50 [HPA CPU Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). @@ -505,8 +501,6 @@ services: For example: ```yaml -version: '3.8' - services: pgadmin: image: postgres @@ -519,8 +513,6 @@ services: For example: ```yaml -version: '3.8' - services: pgadmin: image: postgres diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index cdf8acb5..8087ca54 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -88,9 +88,9 @@ const ( // LabelCronJobBackoffLimit defines the job backoff limit LabelCronJobBackoffLimit = "kompose.cronjob.backoff_limit" // LabelHpaMinReplicas defines min pod replicas - LabelHpaMinReplicas = "kompose.hpa.minreplicas" + LabelHpaMinReplicas = "kompose.hpa.replicas.min" // LabelHpaMaxReplicas defines max pod replicas - LabelHpaMaxReplicas = "kompose.hpa.maxreplicas" + LabelHpaMaxReplicas = "kompose.hpa.replicas.max" // LabelHpaCpu defines scaling decisions based on CPU utilization LabelHpaCPU = "kompose.hpa.cpu" // LabelHpaMemory defines scaling decisions based on memory utilization diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index 1456c563..e781ecc4 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -968,7 +968,7 @@ func Test_getResourceHpaValues(t *testing.T) { want HpaValues }{ { - name: "check same values", + name: "check default values", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ @@ -987,7 +987,7 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, { - name: "check same values", + name: "check if max replicas are less than min replicas, and max replicas set to min replicas", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ @@ -1000,7 +1000,7 @@ func Test_getResourceHpaValues(t *testing.T) { }, want: HpaValues{ MinReplicas: 5, - MaxReplicas: 5, + MaxReplicas: 5, // same as min replicas CPUtilization: 50, MemoryUtilization: 70, }, @@ -1018,7 +1018,7 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, + MinReplicas: 1, // Default value MaxReplicas: 3, CPUtilization: 50, MemoryUtilization: 70, @@ -1038,7 +1038,7 @@ func Test_getResourceHpaValues(t *testing.T) { }, want: HpaValues{ MinReplicas: 6, - MaxReplicas: 6, + MaxReplicas: 6, // set min replicas number CPUtilization: 50, MemoryUtilization: 70, }, @@ -1057,8 +1057,8 @@ func Test_getResourceHpaValues(t *testing.T) { }, want: HpaValues{ MinReplicas: 6, - MaxReplicas: 6, - CPUtilization: 50, + MaxReplicas: 6, // same as min replicas number + CPUtilization: 50, // Default value MemoryUtilization: 70, }, }, @@ -1078,11 +1078,11 @@ func Test_getResourceHpaValues(t *testing.T) { MinReplicas: 6, MaxReplicas: 6, CPUtilization: 50, - MemoryUtilization: 70, + MemoryUtilization: 70, // Default value }, }, { - name: "all error label", + name: "all error label, set all default values", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ @@ -1094,14 +1094,14 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, - MaxReplicas: 10, - CPUtilization: 50, - MemoryUtilization: 70, + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value }, }, { - name: "error label without some labels", + name: "error label without some labels, missing labels set to default", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ @@ -1111,10 +1111,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, - MaxReplicas: 10, - CPUtilization: 50, - MemoryUtilization: 70, + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value }, }, { @@ -1125,10 +1125,186 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, - MaxReplicas: 10, - CPUtilization: 50, - MemoryUtilization: 70, + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value + }, + }, + { + name: "only min replicas label is provided", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "3", + }, + }, + }, + want: HpaValues{ + MinReplicas: 3, + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value + }, + }, + { + name: "only max replicas label is provided", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMaxReplicas: "5", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 5, + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value + }, + }, + { + name: "check default values when all labels contain invalid values", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "cannot transform", + compose.LabelHpaMaxReplicas: "cannot transform", + compose.LabelHpaCPU: "cannot transform", + compose.LabelHpaMemory: "cannot transform", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value + }, + }, + { + name: "only cpu utilization label is provided", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "80", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 80, + MemoryUtilization: 70, // Default value + }, + }, + { + name: "only memory utilization label is provided", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMemory: "90", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 90, + }, + }, + { + name: "only cpu and memory utilization labels are provided", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "80", + compose.LabelHpaMemory: "90", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 80, + MemoryUtilization: 90, + }, + }, + { + name: "check default values when labels are empty strings", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "", + compose.LabelHpaMaxReplicas: "", + compose.LabelHpaCPU: "", + compose.LabelHpaMemory: "", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value + }, + }, + { + name: "check default values when labels contain invalid characters", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "abc", + compose.LabelHpaMaxReplicas: "xyz", + compose.LabelHpaCPU: "-100", + compose.LabelHpaMemory: "invalid", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value + }, + }, + { + name: "check default values when labels are set to zero", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "0", + compose.LabelHpaMaxReplicas: "0", + compose.LabelHpaCPU: "0", + compose.LabelHpaMemory: "0", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value + }, + }, + { + name: "check default values when all labels are negative", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "-5", + compose.LabelHpaMaxReplicas: "-10", + compose.LabelHpaCPU: "-20", + compose.LabelHpaMemory: "-30", + }, + }, + }, + want: HpaValues{ + MinReplicas: 1, // Default value + MaxReplicas: 10, // Default value + CPUtilization: 50, // Default value + MemoryUtilization: 70, // Default value }, }, } @@ -1307,6 +1483,268 @@ func Test_createHPAResources(t *testing.T) { }, }, }, + { + name: "minimum labels", + args: args{ + name: "api", + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaCPU: "50", + }, + }, + }, + want: hpa.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "api", + }, + Spec: hpa.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: hpa.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "api", + APIVersion: "apps/v1", + }, + MinReplicas: &fixedMinReplicas, + MaxReplicas: 10, // Default value + Metrics: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + }, + }, + { + name: "missing CPU utilization label", + args: args{ + name: "app", + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "5", + compose.LabelHpaMemory: "70", + }, + }, + }, + want: hpa.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "app", + }, + Spec: hpa.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: hpa.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "app", + APIVersion: "apps/v1", + }, + MinReplicas: &fixedMinReplicas, + MaxReplicas: 5, + Metrics: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + }, + }, + { + name: "missing memory utilization label", + args: args{ + name: "db", + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "8", + compose.LabelHpaCPU: "50", + }, + }, + }, + want: hpa.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "db", + }, + Spec: hpa.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: hpa.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "db", + APIVersion: "apps/v1", + }, + MinReplicas: &fixedMinReplicas, + MaxReplicas: 8, + Metrics: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + }, + }, + { + name: "wrong labels", + args: args{ + name: "db", + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "not converted", + compose.LabelHpaMaxReplicas: "not converted", + }, + }, + }, + want: hpa.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "db", + }, + Spec: hpa.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: hpa.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "db", + APIVersion: "apps/v1", + }, + MinReplicas: &fixedMinReplicas, + MaxReplicas: 10, // Default value + Metrics: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + }, + }, + { + name: "missing both CPU and memory utilization labels", + args: args{ + name: "db", + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "1", + compose.LabelHpaMaxReplicas: "5", + }, + }, + }, + want: hpa.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + Kind: "HorizontalPodAutoscaler", + APIVersion: "autoscaling/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "db", + }, + Spec: hpa.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: hpa.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "db", + APIVersion: "apps/v1", + }, + MinReplicas: &fixedMinReplicas, + MaxReplicas: 5, + Metrics: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueCPUFixed, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueMemoryFixed, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index 61c1f7e1..c8c75b19 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -339,3 +339,8 @@ os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/compos os_output="$KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 + +# Test HPA +k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/hpa/compose.yaml convert --stdout --with-kompose-annotation=false" +k8s_output="$KOMPOSE_ROOT/script/test/fixtures/hpa/output-k8s.yaml" +convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 \ No newline at end of file diff --git a/script/test/fixtures/hpa/compose.yaml b/script/test/fixtures/hpa/compose.yaml new file mode 100644 index 00000000..9d86d666 --- /dev/null +++ b/script/test/fixtures/hpa/compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" +services: + web: + image: nginx + labels: + kompose.hpa.cpu: 50 + kompose.hap.memory: 70 + kompose.hap.replicas.min: 1 + kompose.hap.replicas.max: 10 \ No newline at end of file diff --git a/script/test/fixtures/hpa/output-k8s.yaml b/script/test/fixtures/hpa/output-k8s.yaml new file mode 100644 index 00000000..2b09904a --- /dev/null +++ b/script/test/fixtures/hpa/output-k8s.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + io.kompose.service: web + name: web +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: web + template: + metadata: + labels: + io.kompose.network/hpa-default: "true" + io.kompose.service: web + spec: + containers: + - image: nginx + name: web + restartPolicy: Always + +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: web +spec: + maxReplicas: 10 + metrics: + - resource: + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: Resource + - resource: + name: memory + target: + averageUtilization: 70 + type: Utilization + type: Resource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: web \ No newline at end of file From 8ff0f334eb98121ce2930c1b6e6fb6d457f3de4f Mon Sep 17 00:00:00 2001 From: jose luis <2064537+sosan@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:18:51 +0200 Subject: [PATCH 3/4] fix EOF, DefaultMaxReplicas set to 3, extended user guide about hpa, more tests add warning in situations like: - maxreplicas < minreplicas - value from label < 0 - validate percentage in cpu, memory metrics --- docs/user-guide.md | 8 +- pkg/transformer/kubernetes/k8sutils.go | 31 +- pkg/transformer/kubernetes/k8sutils_test.go | 405 ++++++++++++++++---- pkg/transformer/kubernetes/kubernetes.go | 2 +- script/test/cmd/tests_new.sh | 2 +- script/test/fixtures/hpa/compose.yaml | 7 +- script/test/fixtures/hpa/output-k8s.yaml | 3 +- 7 files changed, 368 insertions(+), 90 deletions(-) diff --git a/docs/user-guide.md b/docs/user-guide.md index a25f2b9d..b833dc80 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -472,7 +472,7 @@ services: kompose.volume.sub-path: pg-data ``` -- `kompose.hpa.replicas.min` defines minimum replicas from Horizontal Pod Autoscaler. Default value 1 [HPA Min Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). +- `kompose.hpa.replicas.min` defines the floor for the number of replicas that the HPA can scale down to during a scaling event. Default value is set to 1. This means that, regardless of the load on the system, the HPA will always maintain at least one replica. More info: [HPA Min Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). For example: @@ -484,7 +484,7 @@ services: kompose.hpa.replicas.min: 1 ``` -- `kompose.hpa.replicas.max` defines maximum replicas from Horizontal Pod Autoscaler. Default value 10 [HPA Max Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). +- `kompose.hpa.replicas.max` defines the upper limit for the number of replicas that the HPA can create during a scaling event. Default value is set to 3. This default value serves as a safeguard, providing a conservative starting point for your HPA configuration. More info: [HPA Max Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). For example: @@ -496,7 +496,7 @@ services: kompose.hpa.replicas.max: 10 ``` -- `kompose.hpa.cpu` defines % cpu utilization trigger scale from Horizontal Pod Autoscaler. It is represented as a percentage of a resource. Default value: 50 [HPA CPU Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). +- `kompose.hpa.cpu` defines % cpu utilization that triggers to scale the number of pods. It is represented as a percentage of a resource. Default value is set to 50. This default value serves as a safeguard, providing a conservative starting point for your HPA configuration. More info: [HPA CPU Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). For example: @@ -508,7 +508,7 @@ services: kompose.hpa.cpu: 50 ``` -- `kompose.hpa.memory` defines memory utilization trigger scale from Horizontal Pod Autoscaler. It is represented as a percentage of a resource. Default value: 70 [HPA Memory Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). +- `kompose.hpa.memory` defines memory utilization that triggers to scale the number of pods. It is represented as a percentage of a resource. Default value is set to 70. This default value serves as a safeguard, providing a conservative starting point for your HPA configuration. More info: [HPA Memory Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics). For example: diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 528d8613..e2796137 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -52,8 +52,8 @@ import ( // Default values for Horizontal Pod Autoscaler (HPA) const ( DefaultMinReplicas = 1 - DefaultMaxReplicas = 10 - DefaultCpuUtilization = 50 + DefaultMaxReplicas = 3 + DefaultCPUUtilization = 50 DefaultMemoryUtilization = 70 ) @@ -1013,13 +1013,12 @@ func reformatSecretConfigUnderscoreWithDash(secretConfig types.ServiceSecretConf // searchHPAValues is useful to check if labels // contains any labels related to Horizontal Pod Autoscaler func searchHPAValues(labels map[string]string) bool { - found := true for _, value := range LabelKeys { if _, ok := labels[value]; ok { - return found + return true } } - return !found + return false } // createHPAResources creates a HorizontalPodAutoscaler (HPA) resource @@ -1059,23 +1058,39 @@ func getResourceHpaValues(service *kobject.ServiceConfig) HpaValues { maxReplicas := getHpaValue(service, compose.LabelHpaMaxReplicas, DefaultMaxReplicas) if maxReplicas < minReplicas { + log.Warnf("maxReplicas %d is less than minReplicas %d. Using minReplicas value %d", maxReplicas, minReplicas, minReplicas) maxReplicas = minReplicas } + cpuUtilization := validatePercentageMetric(service, compose.LabelHpaCPU, DefaultCPUUtilization) + memoryUtilization := validatePercentageMetric(service, compose.LabelHpaMemory, DefaultMemoryUtilization) + return HpaValues{ MinReplicas: minReplicas, MaxReplicas: maxReplicas, - CPUtilization: getHpaValue(service, compose.LabelHpaCPU, DefaultCpuUtilization), - MemoryUtilization: getHpaValue(service, compose.LabelHpaMemory, DefaultMemoryUtilization), + CPUtilization: cpuUtilization, + MemoryUtilization: memoryUtilization, } } +// validatePercentageMetric validates the CPU or memory metrics value +// ensuring that it falls within the acceptable range [1, 100]. +func validatePercentageMetric(service *kobject.ServiceConfig, metricLabel string, defaultValue int32) int32 { + metricValue := getHpaValue(service, metricLabel, defaultValue) + if metricValue > 100 || metricValue < 1 { + log.Warnf("Metric value %d is not within the acceptable range [1, 100]. Using default value %d", metricValue, defaultValue) + return defaultValue + } + return metricValue +} + // getHpaValue convert the label value to integer // If the label is not present or the conversion fails // it returns the provided default value func getHpaValue(service *kobject.ServiceConfig, label string, defaultValue int32) int32 { valueFromLabel, err := strconv.Atoi(service.Labels[label]) - if err != nil || valueFromLabel <= 0 { + if err != nil || valueFromLabel < 0 { + log.Warnf("Error converting label %s. Using default value %d", label, defaultValue) return defaultValue } return int32(valueFromLabel) diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index e781ecc4..fb72183e 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -783,7 +783,7 @@ func Test_getHpaValue(t *testing.T) { label: compose.LabelHpaMinReplicas, defaultValue: 1, }, - want: 1, + want: 0, }, { name: "LabelHpaMinReplicas with error value", @@ -830,9 +830,9 @@ func Test_getHpaValue(t *testing.T) { }, }, label: compose.LabelHpaMaxReplicas, - defaultValue: 10, + defaultValue: DefaultMaxReplicas, }, - want: 10, + want: 0, }, { name: "LabelHpaMaxReplicas with error value", @@ -846,9 +846,9 @@ func Test_getHpaValue(t *testing.T) { }, }, label: compose.LabelHpaMaxReplicas, - defaultValue: 10, + defaultValue: DefaultMaxReplicas, }, - want: 10, + want: DefaultMaxReplicas, }, // LabelHpaCPU { @@ -879,9 +879,9 @@ func Test_getHpaValue(t *testing.T) { }, }, label: compose.LabelHpaCPU, - defaultValue: 50, + defaultValue: DefaultCPUUtilization, }, - want: 50, + want: 0, }, { name: "LabelHpaCPU with error value", @@ -895,9 +895,9 @@ func Test_getHpaValue(t *testing.T) { }, }, label: compose.LabelHpaCPU, - defaultValue: 50, + defaultValue: DefaultCPUUtilization, }, - want: 50, + want: DefaultCPUUtilization, }, // LabelHpaMemory { @@ -928,9 +928,9 @@ func Test_getHpaValue(t *testing.T) { }, }, label: compose.LabelHpaMemory, - defaultValue: 70, + defaultValue: DefaultMemoryUtilization, }, - want: 70, + want: 0, }, { name: "LabelHpaMemory with error value", @@ -944,9 +944,9 @@ func Test_getHpaValue(t *testing.T) { }, }, label: compose.LabelHpaMemory, - defaultValue: 70, + defaultValue: DefaultMemoryUtilization, }, - want: 70, + want: DefaultMemoryUtilization, }, } for _, tt := range tests { @@ -973,7 +973,7 @@ func Test_getResourceHpaValues(t *testing.T) { service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", - compose.LabelHpaMaxReplicas: "10", + compose.LabelHpaMaxReplicas: "3", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, @@ -981,7 +981,7 @@ func Test_getResourceHpaValues(t *testing.T) { }, want: HpaValues{ MinReplicas: 1, - MaxReplicas: 10, + MaxReplicas: 3, CPUtilization: 50, MemoryUtilization: 70, }, @@ -1018,7 +1018,7 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value + MinReplicas: DefaultMinReplicas, MaxReplicas: 3, CPUtilization: 50, MemoryUtilization: 70, @@ -1057,13 +1057,13 @@ func Test_getResourceHpaValues(t *testing.T) { }, want: HpaValues{ MinReplicas: 6, - MaxReplicas: 6, // same as min replicas number - CPUtilization: 50, // Default value + MaxReplicas: 6, // same as min replicas number + CPUtilization: DefaultCPUUtilization, MemoryUtilization: 70, }, }, { - name: "error label and LabelHpaMaxReplicas is minor to LabelHpaMinReplicas", + name: "error label and LabelHpaMaxReplicas is minor to LabelHpaMinReplicas and cannot transform hpa mmemor utilization", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ @@ -1078,7 +1078,7 @@ func Test_getResourceHpaValues(t *testing.T) { MinReplicas: 6, MaxReplicas: 6, CPUtilization: 50, - MemoryUtilization: 70, // Default value + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1094,10 +1094,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1111,10 +1111,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1125,10 +1125,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1142,9 +1142,9 @@ func Test_getResourceHpaValues(t *testing.T) { }, want: HpaValues{ MinReplicas: 3, - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1157,10 +1157,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value + MinReplicas: DefaultMinReplicas, MaxReplicas: 5, - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1176,10 +1176,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1192,10 +1192,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, CPUtilization: 80, - MemoryUtilization: 70, // Default value + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1208,9 +1208,9 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, MemoryUtilization: 90, }, }, @@ -1225,8 +1225,8 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, CPUtilization: 80, MemoryUtilization: 90, }, @@ -1244,10 +1244,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1263,10 +1263,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, { @@ -1282,10 +1282,10 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: 0, + MaxReplicas: 0, + CPUtilization: 50, + MemoryUtilization: 70, }, }, { @@ -1301,10 +1301,29 @@ func Test_getResourceHpaValues(t *testing.T) { }, }, want: HpaValues{ - MinReplicas: 1, // Default value - MaxReplicas: 10, // Default value - CPUtilization: 50, // Default value - MemoryUtilization: 70, // Default value + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, + }, + }, + { + name: "check default values when labels cpu and memory are over", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMinReplicas: "-2", + compose.LabelHpaMaxReplicas: "-2", + compose.LabelHpaCPU: "120", + compose.LabelHpaMemory: "120", + }, + }, + }, + want: HpaValues{ + MinReplicas: DefaultMinReplicas, + MaxReplicas: DefaultMaxReplicas, + CPUtilization: DefaultCPUUtilization, + MemoryUtilization: DefaultMemoryUtilization, }, }, } @@ -1317,9 +1336,189 @@ func Test_getResourceHpaValues(t *testing.T) { } } +func Test_validatePercentageMetric(t *testing.T) { + type args struct { + service *kobject.ServiceConfig + metricLabel string + defaultValue int32 + } + tests := []struct { + name string + args args + want int32 + }{ + { + name: "0 cpu utilization", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "0", + }, + }, + metricLabel: compose.LabelHpaCPU, + defaultValue: DefaultCPUUtilization, + }, + want: 50, + }, + { + name: "default cpu valid range", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "120", + }, + }, + metricLabel: compose.LabelHpaCPU, + defaultValue: DefaultCPUUtilization, + }, + want: DefaultCPUUtilization, + }, + { + name: "cpu invalid range", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "-120", + }, + }, + metricLabel: compose.LabelHpaCPU, + defaultValue: DefaultCPUUtilization, + }, + want: DefaultCPUUtilization, + }, + { + name: "cpu utilization set to 100", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "100", + }, + }, + metricLabel: compose.LabelHpaCPU, + defaultValue: DefaultCPUUtilization, + }, + want: 100, + }, + { + name: "cpu utlization set to 101", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "101", + }, + }, + metricLabel: compose.LabelHpaCPU, + defaultValue: DefaultCPUUtilization, + }, + want: DefaultCPUUtilization, + }, + { + name: "cannot convert value in cpu label", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaCPU: "not converted", + }, + }, + metricLabel: compose.LabelHpaCPU, + defaultValue: DefaultCPUUtilization, + }, + want: DefaultCPUUtilization, + }, + { + name: "0 memory utilization", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMemory: "0", + }, + }, + metricLabel: compose.LabelHpaMemory, + defaultValue: DefaultMemoryUtilization, + }, + want: 70, + }, + { + name: "memory over 100 utilization", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMemory: "120", + }, + }, + metricLabel: compose.LabelHpaMemory, + defaultValue: DefaultMemoryUtilization, + }, + want: DefaultMemoryUtilization, + }, + { + name: "-120 utilization memory wrong range", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMemory: "-120", + }, + }, + metricLabel: compose.LabelHpaMemory, + defaultValue: DefaultMemoryUtilization, + }, + want: DefaultMemoryUtilization, + }, + { + name: "memory 100 usage", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMemory: "100", + }, + }, + metricLabel: compose.LabelHpaMemory, + defaultValue: DefaultMemoryUtilization, + }, + want: 100, + }, + { + name: "101 memory utilization", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMemory: "101", + }, + }, + metricLabel: compose.LabelHpaMemory, + defaultValue: DefaultMemoryUtilization, + }, + want: DefaultMemoryUtilization, + }, + { + name: "cannot convert memory from label", + args: args{ + service: &kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelHpaMemory: "not converted", + }, + }, + metricLabel: compose.LabelHpaMemory, + defaultValue: DefaultMemoryUtilization, + }, + want: DefaultMemoryUtilization, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := validatePercentageMetric(tt.args.service, tt.args.metricLabel, tt.args.defaultValue); got != tt.want { + t.Errorf("validatePercentageMetric() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_getHpaMetricSpec(t *testing.T) { valueCPUFixed := int32(50) valueMemoryFixed := int32(70) + valueOver100 := int32(120) + valueUnderZero := int32(-120) + // valueZero := int32(0) type args struct { hpaValues HpaValues } @@ -1331,7 +1530,7 @@ func Test_getHpaMetricSpec(t *testing.T) { { name: "no values", args: args{ - hpaValues: HpaValues{}, + hpaValues: HpaValues{}, // set all values to 0 }, want: nil, }, @@ -1339,7 +1538,7 @@ func Test_getHpaMetricSpec(t *testing.T) { name: "only cpu", args: args{ hpaValues: HpaValues{ - CPUtilization: 50, + CPUtilization: valueCPUFixed, }, }, want: []hpa.MetricSpec{ @@ -1379,8 +1578,8 @@ func Test_getHpaMetricSpec(t *testing.T) { name: "cpu and memory", args: args{ hpaValues: HpaValues{ - CPUtilization: 50, - MemoryUtilization: 70, + CPUtilization: valueCPUFixed, + MemoryUtilization: valueMemoryFixed, }, }, want: []hpa.MetricSpec{ @@ -1406,7 +1605,69 @@ func Test_getHpaMetricSpec(t *testing.T) { }, }, }, + { + name: "memory over 100", + args: args{ + hpaValues: HpaValues{ + MemoryUtilization: valueOver100, + }, + }, + want: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueOver100, + }, + }, + }, + }, + }, + { + name: "cpu and memory over 100", + args: args{ + hpaValues: HpaValues{ + CPUtilization: valueOver100, + MemoryUtilization: valueOver100, + }, + }, + want: []hpa.MetricSpec{ + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "cpu", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueOver100, + }, + }, + }, + { + Type: hpa.ResourceMetricSourceType, + Resource: &hpa.ResourceMetricSource{ + Name: "memory", + Target: hpa.MetricTarget{ + Type: hpa.UtilizationMetricType, + AverageUtilization: &valueOver100, + }, + }, + }, + }, + }, + { + name: "cpu and memory under 0", + args: args{ + hpaValues: HpaValues{ + CPUtilization: valueUnderZero, + MemoryUtilization: valueUnderZero, + }, + }, + want: nil, + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getHpaMetricSpec(tt.args.hpaValues); !reflect.DeepEqual(got, tt.want) { @@ -1509,7 +1770,7 @@ func Test_createHPAResources(t *testing.T) { APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, - MaxReplicas: 10, // Default value + MaxReplicas: DefaultMaxReplicas, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, @@ -1667,7 +1928,7 @@ func Test_createHPAResources(t *testing.T) { APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, - MaxReplicas: 10, // Default value + MaxReplicas: DefaultMaxReplicas, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 71ba912a..ba5f2661 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -1656,7 +1656,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. } err = k.configHorizontalPodScaler(name, service, opt, &objects) if err != nil { - return nil, errors.Wrap(err, "Error transforming Kubernetes objects") + return nil, errors.Wrap(err, "Error creating Kubernetes HPA") } allobjects = append(allobjects, objects...) } diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index c8c75b19..3c3ac820 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -343,4 +343,4 @@ convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test HPA k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/hpa/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/hpa/output-k8s.yaml" -convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 \ No newline at end of file +convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 diff --git a/script/test/fixtures/hpa/compose.yaml b/script/test/fixtures/hpa/compose.yaml index 9d86d666..80a2c432 100644 --- a/script/test/fixtures/hpa/compose.yaml +++ b/script/test/fixtures/hpa/compose.yaml @@ -4,6 +4,7 @@ services: image: nginx labels: kompose.hpa.cpu: 50 - kompose.hap.memory: 70 - kompose.hap.replicas.min: 1 - kompose.hap.replicas.max: 10 \ No newline at end of file + kompose.hpa.memory: 70 + kompose.hpa.replicas.min: 1 + kompose.hpa.replicas.max: 10 + diff --git a/script/test/fixtures/hpa/output-k8s.yaml b/script/test/fixtures/hpa/output-k8s.yaml index 2b09904a..6f249d29 100644 --- a/script/test/fixtures/hpa/output-k8s.yaml +++ b/script/test/fixtures/hpa/output-k8s.yaml @@ -45,4 +45,5 @@ spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: web \ No newline at end of file + name: web + From e4ccf2edc9fda6ee2b295717e564f7659bf828ee Mon Sep 17 00:00:00 2001 From: jose luis <2064537+sosan@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:39:04 +0200 Subject: [PATCH 4/4] rectified markdown spacing on columns reformat k8sutils_tests.go Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> --- docs/user-guide.md | 6 +++--- pkg/transformer/kubernetes/k8sutils_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user-guide.md b/docs/user-guide.md index ae9c5a1a..4b97baa5 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -211,9 +211,9 @@ The currently supported options are: | kompose.cronjob.schedule | kubernetes cronjob schedule (for example: '1 * * * *') | | kompose.cronjob.concurrency_policy | 'Forbid' / 'Allow' / 'Never' / '' | | kompose.cronjob.backoff_limit | kubernetes cronjob backoff limit (for example: '6') | -| kompose.init.containers.name | kubernetes init container name | -| kompose.init.containers.image | kubernetes init container image | -| kompose.init.containers.command | kubernetes init container commands | +| kompose.init.containers.name | kubernetes init container name | +| kompose.init.containers.image | kubernetes init container image | +| kompose.init.containers.command | kubernetes init container commands | | kompose.hpa.replicas.min | defines Horizontal Pod Autoscaler minimum number of pod replicas | | kompose.hpa.replicas.max | defines Horizontal Pod Autoscaler maximum number of pod replicas | | kompose.hpa.cpu | defines Horizontal Pod Autoscaler cpu utilization trigger | diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index dfa4817a..a7a0f11e 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -29,8 +29,8 @@ import ( "github.com/kubernetes/kompose/pkg/testutils" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" - api "k8s.io/api/core/v1" hpa "k8s.io/api/autoscaling/v2beta2" + api "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" )