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