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 +}