From 1cab3eec1bd097e65d434310857a99d315828db2 Mon Sep 17 00:00:00 2001 From: Kjeld Schouten Date: Sat, 7 Jun 2025 14:47:01 +0200 Subject: [PATCH] feat(common): refactor horizontal pod autoscaler (#34625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Description** The HPA wasn't connected nor working anymore. This refactors it, so we can offer both VPA and HPA capabilities in common ⚒️ Fixes #34503 **⚙️ Type of change** - [x] ⚙️ Feature/App addition - [ ] 🪛 Bugfix - [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to not work as expected) - [x] 🔃 Refactor of current code - [ ] 📜 Documentation Changes **🧪 How Has This Been Tested?** **📃 Notes:** **✔️ Checklist:** - [ ] ⚖️ My code follows the style guidelines of this project - [ ] 👀 I have performed a self-review of my own code - [ ] #️⃣ I have commented my code, particularly in hard-to-understand areas - [ ] 📄 I have made changes to the documentation - [ ] 🧪 I have added tests to this description that prove my fix is effective or that my feature works - [ ] ⬆️ I increased versions for any altered app according to semantic versioning - [ ] I made sure the title starts with `feat(chart-name):`, `fix(chart-name):`, `chore(chart-name):`, `docs(chart-name):` or `fix(docs):` **➕ App addition** If this PR is an app addition please make sure you have done the following. - [ ] 🖼️ I have added an icon in the Chart's root directory called `icon.png` --- _Please don't blindly check all the boxes. Read them and only check those that apply. Those checkboxes are there for the reviewer to see what is this all about and the status of this PR with a quick glance._ --------- Signed-off-by: Alfred Göppel <43101280+alfi0812@users.noreply.github.com> Signed-off-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Co-authored-by: Alfred Göppel <43101280+alfi0812@users.noreply.github.com> Co-authored-by: Stavros Kois Co-authored-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com> --- .../common-test/tests/hpa/names_test.yaml | 63 + .../common-test/tests/hpa/spec_test.yaml | 265 ++++ .../tests/hpa/validation_test.yaml | 1179 +++++++++++++++++ charts/library/common/Chart.yaml | 3 +- .../class/_horizontalPodAutoscaler.tpl | 210 ++- .../common/templates/lib/_tc_capabilities.tpl | 5 - .../common/templates/lib/hpa/_validation.tpl | 360 +++++ .../spawner/_horizontalPodAutoscaler.tpl | 74 +- charts/library/common/values.yaml | 38 + 9 files changed, 2140 insertions(+), 57 deletions(-) create mode 100644 charts/library/common-test/tests/hpa/names_test.yaml create mode 100644 charts/library/common-test/tests/hpa/spec_test.yaml create mode 100644 charts/library/common-test/tests/hpa/validation_test.yaml create mode 100644 charts/library/common/templates/lib/hpa/_validation.tpl diff --git a/charts/library/common-test/tests/hpa/names_test.yaml b/charts/library/common-test/tests/hpa/names_test.yaml new file mode 100644 index 00000000000..8590c53b989 --- /dev/null +++ b/charts/library/common-test/tests/hpa/names_test.yaml @@ -0,0 +1,63 @@ +suite: hpa name test +templates: + - common.yaml +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should generate correct hpa name + set: + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: {} + workload-name-2: + enabled: true + type: Deployment + podSpec: {} + hpa: + vpa-name: + enabled: true + asserts: + - documentIndex: &deploymentDoc 0 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: &deploymentDoc 1 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + equal: + path: metadata.name + value: test-release-name-common-test-workload-name-2 + - documentIndex: &hpaDoc 2 + isKind: + of: HorizontalPodAutoscaler + - documentIndex: *hpaDoc + isAPIVersion: + of: autoscaling/v2 + - documentIndex: *hpaDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: &otherHPADoc 3 + isKind: + of: HorizontalPodAutoscaler + - documentIndex: *otherHPADoc + isAPIVersion: + of: autoscaling/v2 + - documentIndex: *otherHPADoc + equal: + path: metadata.name + value: test-release-name-common-test-workload-name-2 diff --git a/charts/library/common-test/tests/hpa/spec_test.yaml b/charts/library/common-test/tests/hpa/spec_test.yaml new file mode 100644 index 00000000000..713b38c7d5e --- /dev/null +++ b/charts/library/common-test/tests/hpa/spec_test.yaml @@ -0,0 +1,265 @@ +suite: hpa spec test +templates: + - common.yaml +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should create hpa + set: + workload: &workload + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name: + enabled: true + primary: true + probes: + readiness: + enabled: false + liveness: + enabled: false + startup: + enabled: false + hpa: + hpa-name: + enabled: true + asserts: + - documentIndex: &hpaDoc 1 + isKind: + of: HorizontalPodAutoscaler + - documentIndex: *hpaDoc + isAPIVersion: + of: autoscaling/v2 + - documentIndex: *hpaDoc + isSubset: + path: spec + content: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: test-release-name-common-test + minReplicas: 1 + maxReplicas: 3 + + - it: should create hpa with targetSelector + set: + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: {} + workload-name-2: + enabled: true + type: Deployment + podSpec: {} + hpa: + hpa-name: + enabled: true + targetSelector: + - workload-name-2 + asserts: + - hasDocuments: + count: 3 # 2 Deployments, 1 HPA + - documentIndex: &hpaDoc 2 + isKind: + of: HorizontalPodAutoscaler + - documentIndex: *hpaDoc + isAPIVersion: + of: autoscaling/v2 + - documentIndex: *hpaDoc + isSubset: + path: spec + content: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: test-release-name-common-test-workload-name-2 + minReplicas: 1 + maxReplicas: 3 + + - it: should create hpa with options + set: + workload: *workload + hpa: + hpa-name: + enabled: true + minReplicas: 50 + maxReplicas: 100 + behavior: + scaleUp: + stabilizationWindowSeconds: 50 + policies: + - type: Pods + value: 10 + periodSeconds: 60 + scaleDown: + selectPolicy: Min + stabilizationWindowSeconds: 150 + policies: + - type: Percent + value: 20 + periodSeconds: 60 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: cpu + target: + type: AverageValue + averageValue: 1000m + value: 1000m + - type: ContainerResource + containerResource: + name: memory + container: container-name + target: + type: Utilization + averageUtilization: 80 + - type: Pods + pods: + metric: + name: custom-metric + selector: + matchLabels: + custom-label: custom-value + target: + type: AverageValue + averageValue: 1000m + - type: Object + object: + metric: + name: object-metric + selector: + matchLabels: + custom-label: custom-value + target: + type: AverageValue + averageValue: 1000m + describedObject: + apiVersion: apps/v1 + kind: Deployment + name: some-name + - type: External + external: + metric: + name: external-metric + selector: + matchLabels: + custom-label: custom-value + target: + type: AverageValue + averageValue: 1000m + asserts: + - documentIndex: &hpaDoc 1 + isKind: + of: HorizontalPodAutoscaler + - documentIndex: *hpaDoc + isAPIVersion: + of: autoscaling/v2 + - documentIndex: *hpaDoc + equal: + path: spec.minReplicas + value: 50 + - documentIndex: *hpaDoc + equal: + path: spec.maxReplicas + value: 100 + - documentIndex: *hpaDoc + equal: + path: spec.behavior + value: + scaleUp: + selectPolicy: Max + stabilizationWindowSeconds: 50 + policies: + - type: Pods + value: 10 + periodSeconds: 60 + scaleDown: + selectPolicy: Min + stabilizationWindowSeconds: 150 + policies: + - type: Percent + value: 20 + periodSeconds: 60 + - documentIndex: *hpaDoc + isSubset: + path: spec + content: + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + - type: Resource + resource: + name: cpu + target: + type: AverageValue + averageValue: 1000m + value: 1000m + - type: ContainerResource + containerResource: + name: memory + container: container-name + target: + type: Utilization + averageUtilization: 80 + - type: Pods + pods: + metric: + name: custom-metric + selector: + matchLabels: + custom-label: custom-value + target: + type: AverageValue + averageValue: 1000m + - type: Object + object: + metric: + name: object-metric + selector: + matchLabels: + custom-label: custom-value + target: + type: AverageValue + averageValue: 1000m + describedObject: + apiVersion: apps/v1 + kind: Deployment + name: some-name + - type: External + external: + metric: + name: external-metric + selector: + matchLabels: + custom-label: custom-value + target: + type: AverageValue + averageValue: 1000m diff --git a/charts/library/common-test/tests/hpa/validation_test.yaml b/charts/library/common-test/tests/hpa/validation_test.yaml new file mode 100644 index 00000000000..0dd1a9a846a --- /dev/null +++ b/charts/library/common-test/tests/hpa/validation_test.yaml @@ -0,0 +1,1179 @@ +suite: hpa validation test +templates: + - common.yaml +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should fail with name longer than 63 characters + set: + workload: &workload + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name: + enabled: true + primary: true + probes: + readiness: + enabled: false + liveness: + enabled: false + startup: + enabled: false + hpa: + other-horizontal-pod-autoscale-name-super-long-name-that-is-longer-than-63-characters: + enabled: true + asserts: + - failedTemplate: + errorMessage: Name [other-horizontal-pod-autoscale-name-super-long-name-that-is-longer-than-63-characters] is not valid. Must start and end with an alphanumeric lowercase character. It can contain '-'. And must be at most 63 characters. + + - it: should fail with name starting with underscore + set: + workload: *workload + hpa: + _other-hpa-name: + enabled: true + asserts: + - failedTemplate: + errorMessage: Name [_other-hpa-name] is not valid. Must start and end with an alphanumeric lowercase character. It can contain '-'. And must be at most 63 characters. + + - it: should fail with labels not a dict + set: + workload: *workload + hpa: + hpa-name: + enabled: true + labels: "not a dict" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [labels] to be a dictionary, but got [string] + + - it: should fail with annotations not a dict + set: + workload: *workload + hpa: + hpa-name: + enabled: true + annotations: "not a dict" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [annotations] to be a dictionary, but got [string] + - it: should fail with minReplicas not an integer + set: + workload: *workload + hpa: + hpa-name: + enabled: true + minReplicas: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.minReplicas] to be an integer, but got [string] + - it: should fail with maxReplicas not an integer + set: + workload: *workload + hpa: + hpa-name: + enabled: true + maxReplicas: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.maxReplicas] to be an integer, but got [string] + - it: should fail with minReplicas greater than maxReplicas + set: + workload: *workload + hpa: + hpa-name: + enabled: true + minReplicas: 5 + maxReplicas: 3 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.minReplicas] to be less than [hpa.hpa-name.maxReplicas], but got [5] and [3] + + - it: should fail with invalid selectPolicy (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + selectPolicy: "not a valid policy" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.selectPolicy] to be one of [Max, Min, Disabled], but got [not a valid policy] + + - it: should fail with invalid stabilityWindowSeconds (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + stabilizationWindowSeconds: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.stabilizationWindowSeconds] to be an integer, but got [string] + + - it: should fail with invalid policies (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + policies: "not a valid policies" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.policies] to be a list, but got [string] + + - it: should fail with invalid policy type (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + policies: + - type: "not a valid type" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.policies.0.type] to be one of [Pods, Percent], but got [not a valid type] + + - it: should fail with invalid policy value (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + policies: + - type: Percent + value: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.policies.0.value] to be an integer, but got [string] + + - it: should fail with invalid policy periodSeconds (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + policies: + - type: Percent + value: 50 + periodSeconds: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.policies.0.periodSeconds] to be an integer, but got [string] + + - it: should fail with number less than 1 in periodSeconds (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + policies: + - type: Percent + periodSeconds: 0 + value: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.policies.0.periodSeconds] to be between 1 and 1800, but got [0] + + - it: should fail with number greater than 1800 in periodSeconds (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + policies: + - type: Percent + periodSeconds: 1801 + value: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.policies.0.periodSeconds] to be between 1 and 1800, but got [1801] + + - it: should fail with invalid number in value (scaleUp) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleUp: + policies: + - type: Percent + periodSeconds: 60 + value: -1 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleUp.policies.0.value] to be greater than 0, but got [-1] + + - it: should fail with invalid selectPolicy (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + selectPolicy: "not a valid policy" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.selectPolicy] to be one of [Max, Min, Disabled], but got [not a valid policy] + + - it: should fail with invalid stabilityWindowSeconds (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + stabilizationWindowSeconds: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.stabilizationWindowSeconds] to be an integer, but got [string] + + - it: should fail with invalid policies (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + policies: "not a valid policies" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.policies] to be a list, but got [string] + + - it: should fail with invalid policy type (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + policies: + - type: "not a valid type" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.policies.0.type] to be one of [Pods, Percent], but got [not a valid type] + + - it: should fail with invalid policy value (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + policies: + - type: Percent + value: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.policies.0.value] to be an integer, but got [string] + + - it: should fail with invalid policy periodSeconds (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + policies: + - type: Percent + value: 50 + periodSeconds: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.policies.0.periodSeconds] to be an integer, but got [string] + + - it: should fail with number less than 1 in periodSeconds (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + policies: + - type: Percent + periodSeconds: 0 + value: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.policies.0.periodSeconds] to be between 1 and 1800, but got [0] + + - it: should fail with number greater than 1800 in periodSeconds (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + policies: + - type: Percent + periodSeconds: 1801 + value: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.policies.0.periodSeconds] to be between 1 and 1800, but got [1801] + + - it: should fail with invalid number in value (scaleDown) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + behavior: + scaleDown: + policies: + - type: Percent + periodSeconds: 60 + value: -1 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.behavior.scaleDown.policies.0.value] to be greater than 0, but got [-1] + + - it: should fail with invalid metrics + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: "not a valid metrics" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics] to be a list, but got [string] + + - it: should fail with invalid metric type + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: "not a valid type" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.type] to be one of [Resource, Pods, Object, External, ContainerResource], but got [not a valid type] + + # Resource metric + - it: should fail with invalid resource (resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Resource + resource: "not a valid resource" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.resource] to be a map, but got [string] + + - it: should fail with invalid resource name (resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Resource + resource: + name: invalid-name-123 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.resource.name] to be one of [cpu, memory], but got [invalid-name-123] + + - it: should fail with invalid resource target (resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Resource + resource: + name: cpu + target: "not a valid target" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.resource.target] to be a map, but got [string] + + - it: should fail with invalid resource target type (resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Resource + resource: + name: cpu + target: + type: "not a valid type" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.resource.target.type] to be one of [AverageValue, Utilization], but got [not a valid type] + + - it: should fail with invalid resource target averageValue (resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Resource + resource: + name: cpu + target: + type: AverageValue + averageValue: {} + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.resource.target.averageValue] to be an integer or string, but got [map] + + - it: should fail with invalid resource target utilization (resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.resource.target.averageUtilization] to be an integer, but got [string] + + - it: should fail with invalid resource target value (resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 100 + value: ["not an integer"] + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.resource.target.value] to be an integer or string, but got [slice] + + # ContainerResource metric + - it: should fail with invalid containerResource (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: "not a valid containerResource" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource] to be a map, but got [string] + + - it: should fail with invalid containerResource name (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: + name: invalid-name-123 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource.name] to be one of [cpu, memory], but got [invalid-name-123] + + - it: should fail with invalid containerResource target (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: container-name + target: "not a valid target" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource.target] to be a map, but got [string] + + - it: should fail with invalid containerResource target type (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: container-name + target: + type: "not a valid type" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource.target.type] to be one of [AverageValue, Utilization], but got [not a valid type] + + - it: should fail with invalid containerResource target averageValue (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: container-name + target: + type: AverageValue + averageValue: {} + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource.target.averageValue] to be an integer or string, but got [map] + + - it: should fail with invalid containerResource target utilization (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: container-name + target: + type: Utilization + averageUtilization: "not an integer" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource.target.averageUtilization] to be an integer, but got [string] + + - it: should fail with invalid containerResource target value (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: container-name + target: + type: Utilization + averageUtilization: 100 + value: ["not an integer"] + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource.target.value] to be an integer or string, but got [slice] + + - it: should fail with invalid containerResource container (container resource metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: "not a valid container" + target: + type: Utilization + averageUtilization: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.containerResource.container] to be one of [container-name], but got [not a valid container] + + - it: should fail with invalid pods metric + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: "not a valid pods" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods] to be a map, but got [string] + + - it: should fail with invalid pods metric metric (pods metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: + metric: "not a valid metric" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods.metric] to be a map, but got [string] + + - it: should fail with invalid pods metric name (pods metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: + metric: + name: 123 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods.metric.name] to be a string, but got [float64] + + - it: should fail with invalid pods target (pods metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: + metric: + name: pod-metric + target: "not a valid target" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods.target] to be a map, but got [string] + + - it: should fail with invalid pods target averageValue (pods metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: + metric: + name: pod-metric + target: + type: AverageValue + averageValue: {} + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods.target.averageValue] to be an integer or string, but got [map] + + - it: should fail with invalid pods metric selector (pods metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: + metric: + name: pod-metric + selector: "not a valid selector" + target: + type: AverageValue + averageValue: 100m + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods.metric.selector] to be a map, but got [string] + + - it: should fail with invalid pods metric selector matchLabels (pods metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: + metric: + name: pod-metric + selector: + matchLabels: "not a valid matchLabels" + target: + type: AverageValue + averageValue: 100m + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods.metric.selector.matchLabels] to be a map, but got [string] + + - it: should fail with invalid pods metric selector matchLabels value (pods metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Pods + pods: + metric: + name: pod-metric + selector: + matchLabels: + key: {} + target: + type: AverageValue + averageValue: 100m + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.pods.metric.selector.matchLabels.key] to be a string, but got [map] + + - it: should fail with invalid object metric + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: "not a valid object" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object] to be a map, but got [string] + + - it: should fail with invalid object metric (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: "not a valid metric" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.metric] to be a map, but got [string] + - it: should fail with invalid object metric name (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: 123 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.metric.name] to be a string, but got [float64] + - it: should fail with invalid object target (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: "not a valid target" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.target] to be a map, but got [string] + - it: should fail with invalid object target type (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: + type: "not a valid type" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.target.type] to be one of [AverageValue, Value], but got [not a valid type] + - it: should fail with invalid object target averageValue (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: + type: AverageValue + averageValue: {} + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.target.averageValue] to be an integer or string, but got [map] + - it: should fail with invalid object target value (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: + type: Value + value: ["not an integer"] + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.target.value] to be an integer or string, but got [slice] + - it: should fail with invalid object metric selector (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + selector: "not a valid selector" + target: + type: AverageValue + averageValue: 100m + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.metric.selector] to be a map, but got [string] + - it: should fail with invalid object metric selector matchLabels (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + selector: + matchLabels: "not a valid matchLabels" + target: + type: AverageValue + averageValue: 100m + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.metric.selector.matchLabels] to be a map, but got [string] + - it: should fail with invalid object metric selector matchLabels value (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + selector: + matchLabels: + key: {} + target: + type: AverageValue + averageValue: 100m + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.metric.selector.matchLabels.key] to be a string, but got [map] + - it: should fail with invalid object describedObject (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: + type: AverageValue + averageValue: 100m + describedObject: "not a valid describedObject" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.describedObject] to be a map, but got [string] + + - it: should fail with invalid object describedObject name (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: + type: AverageValue + averageValue: 100m + describedObject: + apiVersion: apps/v1 + kind: Deployment + name: 123 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.describedObject.name] to be a string, but got [float64] + + - it: should fail with invalid object describedObject kind (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: + type: AverageValue + averageValue: 100m + describedObject: + name: some-name + kind: 1 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.describedObject.kind] to be a string, but got [float64] + + - it: should fail with invalid object describedObject apiVersion (object metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: Object + object: + metric: + name: object-metric + target: + type: AverageValue + averageValue: 100m + describedObject: + name: some-name + kind: Deployment + apiVersion: 1 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.object.describedObject.apiVersion] to be a string, but got [float64] + + - it: should fail with invalid external metric + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: "not a valid external" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external] to be a map, but got [string] + + - it: should fail with invalid external metric metric (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: "not a valid metric" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.metric] to be a map, but got [string] + + - it: should fail with invalid external metric name (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: 123 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.metric.name] to be a string, but got [float64] + + - it: should fail with invalid external target (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: external-metric + target: "not a valid target" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.target] to be a map, but got [string] + + - it: should fail with invalid external target type (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: external-metric + target: + type: "not a valid type" + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.target.type] to be one of [AverageValue, Value], but got [not a valid type] + + - it: should fail with invalid external target averageValue (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: external-metric + target: + type: AverageValue + averageValue: {} + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.target.averageValue] to be an integer or string, but got [map] + - it: should fail with invalid external target value (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: external-metric + target: + type: Value + value: ["not an integer"] + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.target.value] to be an integer or string, but got [slice] + + - it: should fail with invalid external metric selector (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: external-metric + selector: "not a valid selector" + target: + type: Value + value: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.metric.selector] to be a map, but got [string] + + - it: should fail with invalid external metric selector matchLabels (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: external-metric + selector: + matchLabels: "not a valid matchLabels" + target: + type: Value + value: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.metric.selector.matchLabels] to be a map, but got [string] + + - it: should fail with invalid external metric selector matchLabels value (external metric) + set: + workload: *workload + hpa: + hpa-name: + enabled: true + metrics: + - type: External + external: + metric: + name: external-metric + selector: + matchLabels: + key: {} + target: + type: Value + value: 100 + asserts: + - failedTemplate: + errorMessage: Horizontal Pod Autoscaler - Expected [hpa.hpa-name.metrics.0.external.metric.selector.matchLabels.key] to be a string, but got [map] diff --git a/charts/library/common/Chart.yaml b/charts/library/common/Chart.yaml index f37d6a825fb..14493523227 100644 --- a/charts/library/common/Chart.yaml +++ b/charts/library/common/Chart.yaml @@ -48,5 +48,4 @@ sources: - https://hub.docker.com/_/ - https://hub.docker.com/r/mikefarah/yq type: library -version: 28.8.1 - +version: 28.9.0 diff --git a/charts/library/common/templates/class/_horizontalPodAutoscaler.tpl b/charts/library/common/templates/class/_horizontalPodAutoscaler.tpl index 92abf3ff4e1..4f6b6354234 100644 --- a/charts/library/common/templates/class/_horizontalPodAutoscaler.tpl +++ b/charts/library/common/templates/class/_horizontalPodAutoscaler.tpl @@ -3,56 +3,190 @@ This template serves as a blueprint for horizontal pod autoscaler objects that a using the common library. */}} {{- define "tc.v1.common.class.hpa" -}} - {{- $targetName := include "tc.v1.common.lib.chart.names.fullname" . -}} - {{- $fullName := include "tc.v1.common.lib.chart.names.fullname" . -}} - {{- $hpaName := $fullName -}} - {{- $values := .Values.hpa -}} - - {{- if hasKey . "ObjectValues" -}} - {{- with .ObjectValues.hpa -}} - {{- $values = . -}} - {{- end -}} - {{- end -}} - {{- $hpaLabels := $values.labels -}} - {{- $hpaAnnotations := $values.annotations -}} - - {{- if and (hasKey $values "nameOverride") $values.nameOverride -}} - {{- $hpaName = printf "%v-%v" $hpaName $values.nameOverride -}} - {{- end }} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData }} --- -apiVersion: {{ include "tc.v1.common.capabilities.hpa.apiVersion" $ }} +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ $hpaName }} - namespace: {{ $.Values.namespace | default $.Values.global.namespace | default $.Release.Namespace }} - {{- $labels := (mustMerge ($hpaLabels | default dict) (include "tc.v1.common.lib.metadata.allLabels" $ | fromYaml)) -}} - {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $ "labels" $labels) | trim) }} + name: {{ $objectData.name }} + namespace: {{ include "tc.v1.common.lib.metadata.namespace" (dict "rootCtx" $rootCtx "objectData" $objectData "caller" "VPA") }} + {{- $labels := (mustMerge ($objectData.labels | default dict) (include "tc.v1.common.lib.metadata.allLabels" $rootCtx | fromYaml)) -}} + {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "labels" $labels) | trim) }} labels: {{- . | nindent 4 }} {{- end -}} - {{- $annotations := (mustMerge ($hpaAnnotations | default dict) (include "tc.v1.common.lib.metadata.allAnnotations" $ | fromYaml)) -}} - {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $ "annotations" $annotations) | trim) }} + {{- $annotations := (mustMerge ($objectData.annotations | default dict) (include "tc.v1.common.lib.metadata.allAnnotations" $rootCtx | fromYaml)) -}} + {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "annotations" $annotations) | trim) }} annotations: {{- . | nindent 4 }} - {{- end -}} + {{- end }} spec: scaleTargetRef: apiVersion: apps/v1 - kind: {{ $values.targetKind | default ( include "tc.v1.common.names.controllerType" . ) }} - name: {{ $values.target | default $targetName }} - minReplicas: {{ $values.minReplicas | default 1 }} - maxReplicas: {{ $values.maxReplicas | default 3 }} + kind: {{ $objectData.workload.type }} + name: {{ $objectData.name }} + minReplicas: {{ $objectData.minReplicas }} + maxReplicas: {{ $objectData.maxReplicas }} + {{- if $objectData.metrics }} metrics: - {{- if $values.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - targetAverageUtilization: {{ $values.targetCPUUtilizationPercentage }} + {{- include "tc.v1.common.class.hpa.metrics" (dict "objectData" $objectData "rootCtx" $rootCtx) | nindent 4 }} + {{- end -}} + {{- if $objectData.behavior }} + behavior: + {{- if $objectData.behavior.scaleUp }} + scaleUp: + {{- include "tc.v1.common.class.hpa.behavior" (dict "objectData" $objectData "rootCtx" $rootCtx "mode" "up") | nindent 4 }} {{- end -}} - {{- if $values.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - targetAverageUtilization: {{ $values.targetMemoryUtilizationPercentage }} + {{- if $objectData.behavior.scaleDown }} + scaleDown: + {{- include "tc.v1.common.class.hpa.behavior" (dict "objectData" $objectData "rootCtx" $rootCtx "mode" "down") | nindent 4 }} {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.class.hpa.behavior" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $mode := .mode -}} + + {{- $key := ternary "scaleUp" "scaleDown" (eq $mode "up") -}} + {{- $behavior := get $objectData.behavior $key -}} + + {{- $defaultStabilizationWindowSeconds := ternary 0 300 (eq $mode "up") }} + selectPolicy: {{ $behavior.selectPolicy | default "Max" }} + stabilizationWindowSeconds: {{ $behavior.stabilizationWindowSeconds | default $defaultStabilizationWindowSeconds }} + {{- if $behavior.policies }} + policies: + {{- range $idx, $policy := $behavior.policies }} + - type: {{ $policy.type }} + value: {{ $policy.value }} + periodSeconds: {{ $policy.periodSeconds }} + {{- end }} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.class.hpa.metrics" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + + {{- range $idx, $metric := $objectData.metrics }} + {{- if eq $metric.type "Resource" }} + {{- include "tc.v1.common.class.hpa.metrics.resource" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric) | nindent 6 }} + {{- else if eq $metric.type "ContainerResource" }} + {{- include "tc.v1.common.class.hpa.metrics.containerResource" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric) | nindent 6 }} + {{- else if eq $metric.type "Pods" }} + {{- include "tc.v1.common.class.hpa.metrics.pods" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric) | nindent 6 }} + {{- else if eq $metric.type "Object" }} + {{- include "tc.v1.common.class.hpa.metrics.object" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric) | nindent 6 }} + {{- else if eq $metric.type "External" }} + {{- include "tc.v1.common.class.hpa.metrics.external" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric) | nindent 6 }} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.class.hpa.metrics.resource" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx }} + - type: Resource + resource: + name: {{ .metric.resource.name }} + target: + type: {{ .metric.resource.target.type }} + {{- if eq .metric.resource.target.type "AverageValue" }} + averageValue: {{ .metric.resource.target.averageValue | quote }} + {{- else if eq .metric.resource.target.type "Utilization" }} + averageUtilization: {{ .metric.resource.target.averageUtilization }} + {{- end -}} + {{- with .metric.resource.target.value }} + value: {{ . | quote }} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.class.hpa.metrics.containerResource" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx }} + - type: ContainerResource + containerResource: + name: {{ .metric.containerResource.name }} + container: {{ .metric.containerResource.container}} + target: + type: {{ .metric.containerResource.target.type }} + {{- if eq .metric.containerResource.target.type "AverageValue" }} + averageValue: {{ .metric.containerResource.target.averageValue | quote }} + {{- else if eq .metric.containerResource.target.type "Utilization" }} + averageUtilization: {{ .metric.containerResource.target.averageUtilization }} + {{- end -}} + {{- with .metric.containerResource.target.value }} + value: {{ . | quote }} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.class.hpa.metrics.pods" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx }} + - type: Pods + pods: + target: + type: AverageValue + averageValue: {{ .metric.pods.target.averageValue | quote }} + metric: + name: {{ .metric.pods.metric.name }} + {{- if .metric.pods.metric.selector }} + selector: + matchLabels: + {{- range $key, $value := .metric.pods.metric.selector.matchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.class.hpa.metrics.object" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx }} + - type: Object + object: + target: + type: {{ .metric.object.target.type }} + {{- if eq .metric.object.target.type "Value" }} + value: {{ .metric.object.target.value | quote }} + {{- else if eq .metric.object.target.type "AverageValue" }} + averageValue: {{ .metric.object.target.averageValue | quote }} + {{- end }} + describedObject: + apiVersion: {{ .metric.object.describedObject.apiVersion }} + kind: {{ .metric.object.describedObject.kind }} + name: {{ .metric.object.describedObject.name }} + metric: + name: {{ .metric.object.metric.name }} + {{- if .metric.object.metric.selector }} + selector: + matchLabels: + {{- range $key, $value := .metric.object.metric.selector.matchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.class.hpa.metrics.external" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx }} + - type: External + external: + metric: + name: {{ .metric.external.metric.name }} + {{- if .metric.external.metric.selector }} + selector: + matchLabels: + {{- range $key, $value := .metric.external.metric.selector.matchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end -}} + {{- end }} + target: + type: {{ .metric.external.target.type }} + {{- if eq .metric.external.target.type "Value" }} + value: {{ .metric.external.target.value | quote }} + {{- else if eq .metric.external.target.type "AverageValue" }} + averageValue: {{ .metric.external.target.averageValue | quote }} + {{- end -}} {{- end -}} diff --git a/charts/library/common/templates/lib/_tc_capabilities.tpl b/charts/library/common/templates/lib/_tc_capabilities.tpl index c3e6170670f..df9c5d54856 100644 --- a/charts/library/common/templates/lib/_tc_capabilities.tpl +++ b/charts/library/common/templates/lib/_tc_capabilities.tpl @@ -17,8 +17,3 @@ {{- define "tc.v1.common.capabilities.networkpolicy.apiVersion" -}} {{- print "networking.k8s.io/v1" -}} {{- end -}} - -{{/* Return the appropriate apiVersion for HorizontalPodAutoscaler aka HPA*/}} -{{- define "tc.v1.common.capabilities.hpa.apiVersion" -}} - {{- print "autoscaling/v2" -}} -{{- end -}} diff --git a/charts/library/common/templates/lib/hpa/_validation.tpl b/charts/library/common/templates/lib/hpa/_validation.tpl new file mode 100644 index 00000000000..a8195df3a6e --- /dev/null +++ b/charts/library/common/templates/lib/hpa/_validation.tpl @@ -0,0 +1,360 @@ +{{- define "tc.v1.common.lib.hpa.validation" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + + {{- $minReplicas := 1 -}} + {{- with $objectData.minReplicas -}} + {{- if not (mustHas (kindOf $objectData.minReplicas) (list "int" "int64" "float64")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.minReplicas] to be an integer, but got [%s]" $objectData.hpaName (kindOf $objectData.minReplicas)) -}} + {{- end -}} + {{- $minReplicas = $objectData.minReplicas -}} + {{- end -}} + + {{- $maxReplicas := 3 -}} + {{- with $objectData.maxReplicas -}} + {{- if not (mustHas (kindOf $objectData.maxReplicas) (list "int" "int64" "float64")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.maxReplicas] to be an integer, but got [%s]" $objectData.hpaName (kindOf $objectData.maxReplicas)) -}} + {{- end -}} + {{- $maxReplicas = $objectData.maxReplicas -}} + {{- end -}} + + {{- $_ := set $objectData "minReplicas" $minReplicas -}} + {{- $_ := set $objectData "maxReplicas" $maxReplicas -}} + + {{- if lt $maxReplicas $minReplicas -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.minReplicas] to be less than [hpa.%s.maxReplicas], but got [%d] and [%d]" $objectData.hpaName $objectData.hpaName ($minReplicas | int) ($maxReplicas | int)) -}} + {{- end -}} + + {{- if $objectData.behavior -}} + {{- if $objectData.behavior.scaleUp -}} + {{- include "tc.v1.common.lib.hpa.validation.behavior" (dict "objectData" $objectData "rootCtx" $rootCtx "data" $objectData.behavior.scaleUp "key" "scaleUp") -}} + {{- end -}} + {{- if $objectData.behavior.scaleDown -}} + {{- include "tc.v1.common.lib.hpa.validation.behavior" (dict "objectData" $objectData "rootCtx" $rootCtx "data" $objectData.behavior.scaleDown "key" "scaleDown") -}} + {{- end -}} + {{- end -}} + + {{- if $objectData.metrics -}} + {{- include "tc.v1.common.lib.hpa.validation.metrics" (dict "objectData" $objectData "rootCtx" $rootCtx "data" $objectData.metrics) -}} + {{- end -}} + +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.behavior" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $data := .data -}} + {{- $key := .key -}} + + {{- if $data.selectPolicy -}} + {{- $validSelectPolicies := list "Max" "Min" "Disabled" -}} + {{- if not (mustHas $data.selectPolicy $validSelectPolicies) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.selectPolicy] to be one of [%s], but got [%s]" $objectData.hpaName $key (join ", " $validSelectPolicies) $data.selectPolicy) -}} + {{- end -}} + {{- end -}} + + {{- if $data.stabilizationWindowSeconds -}} + {{- if not (mustHas (kindOf $data.stabilizationWindowSeconds) (list "int" "int64" "float64")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.stabilizationWindowSeconds] to be an integer, but got [%s]" $objectData.hpaName $key (kindOf $data.stabilizationWindowSeconds)) -}} + {{- end -}} + {{- end -}} + + {{- if $data.policies -}} + {{- if not (kindIs "slice" $data.policies) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.policies] to be a list, but got [%s]" $objectData.hpaName $key (kindOf $data.policies)) -}} + {{- end -}} + + {{- $validPolicies := list "Pods" "Percent" -}} + {{- range $idx, $policy := $data.policies -}} + {{- if not (kindIs "map" $policy) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.policies.%d] to be a map, but got [%s]" $objectData.hpaName $key $idx (kindOf $policy)) -}} + {{- end -}} + + {{- if not (mustHas $policy.type $validPolicies) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.policies.%d.type] to be one of [%s], but got [%s]" $objectData.hpaName $key $idx (join ", " $validPolicies) $policy.type) -}} + {{- end -}} + + {{- if not (mustHas (kindOf $policy.value) (list "int" "int64" "float64")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.policies.%d.value] to be an integer, but got [%s]" $objectData.hpaName $key $idx (kindOf $policy.value)) -}} + {{- end -}} + + {{- if not (mustHas (kindOf $policy.periodSeconds) (list "int" "int64" "float64")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.policies.%d.periodSeconds] to be an integer, but got [%s]" $objectData.hpaName $key $idx (kindOf $policy.periodSeconds)) -}} + {{- end -}} + + {{- if le ($policy.value | int) 0 -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.policies.%d.value] to be greater than 0, but got [%v]" $objectData.hpaName $key $idx $policy.value) -}} + {{- end -}} + + {{- if or (lt ($policy.periodSeconds | int) 1) (gt ($policy.periodSeconds | int) 1800) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.behavior.%s.policies.%d.periodSeconds] to be between 1 and 1800, but got [%v]" $objectData.hpaName $key $idx $policy.periodSeconds) -}} + {{- end -}} + + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metrics" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + + {{- if not (kindIs "slice" $objectData.metrics) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics] to be a list, but got [%s]" $objectData.hpaName (kindOf $objectData.metrics)) -}} + {{- end -}} + + {{- range $idx, $metric := $objectData.metrics -}} + {{- if not (kindIs "map" $metric) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric)) -}} + {{- end -}} + + {{- if not (mustHas $metric.type (list "Resource" "Pods" "Object" "External" "ContainerResource")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.type] to be one of [Resource, Pods, Object, External, ContainerResource], but got [%s]" $objectData.hpaName $idx $metric.type) -}} + {{- end -}} + + {{- if eq $metric.type "Resource" -}} + {{- include "tc.v1.common.lib.hpa.validation.metrics.resource" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric "idx" $idx) -}} + {{- else if eq $metric.type "Pods" -}} + {{- include "tc.v1.common.lib.hpa.validation.metrics.pods" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric "idx" $idx) -}} + {{- else if eq $metric.type "Object" -}} + {{- include "tc.v1.common.lib.hpa.validation.metrics.object" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric "idx" $idx) -}} + {{- else if eq $metric.type "External" -}} + {{- include "tc.v1.common.lib.hpa.validation.metrics.external" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric "idx" $idx) -}} + {{- else if eq $metric.type "ContainerResource" -}} + {{- include "tc.v1.common.lib.hpa.validation.metrics.containerResource" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric "idx" $idx) -}} + {{- end -}} + + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metrics.resource" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $metric := .metric -}} + {{- $idx := .idx -}} + + {{- if not (kindIs "map" $metric.resource) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.resource] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.resource)) -}} + {{- end -}} + + {{- $validNames := list "cpu" "memory" -}} + {{- if not (mustHas $metric.resource.name $validNames) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.resource.name] to be one of [%s], but got [%s]" $objectData.hpaName $idx (join ", " $validNames) $metric.resource.name) -}} + {{- end -}} + + {{- include "tc.v1.common.lib.hpa.validation.metrics.metric.target" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric.resource "key" "resource" "idx" $idx) -}} +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metrics.containerResource" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $metric := .metric -}} + {{- $idx := .idx -}} + + {{- if not (kindIs "map" $metric.containerResource) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.containerResource] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.containerResource)) -}} + {{- end -}} + + {{- $validNames := list "cpu" "memory" -}} + {{- if not (mustHas $metric.containerResource.name $validNames) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.containerResource.name] to be one of [%s], but got [%s]" $objectData.hpaName $idx (join ", " $validNames) $metric.containerResource.name) -}} + {{- end -}} + + {{- if not (mustHas $metric.containerResource.container $objectData.containerNames) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.containerResource.container] to be one of [%s], but got [%s]" $objectData.hpaName $idx (join ", " $objectData.containerNames) $metric.containerResource.container) -}} + {{- end -}} + + {{- include "tc.v1.common.lib.hpa.validation.metrics.metric.target" (dict "objectData" $objectData "rootCtx" $rootCtx "metric" $metric.containerResource "key" "containerResource" "idx" $idx) -}} +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metrics.pods" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $metric := .metric -}} + {{- $idx := .idx -}} + + {{- if not (kindIs "map" $metric.pods) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.pods] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.pods)) -}} + {{- end -}} + + {{- if not (kindIs "map" $metric.pods.metric) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.pods.metric] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.pods.metric)) -}} + {{- end -}} + + {{- if or (not $metric.pods.metric.name) (not (kindIs "string" $metric.pods.metric.name)) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.pods.metric.name] to be a string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.pods.metric.name)) -}} + {{- end -}} + + {{- if not (kindIs "map" $metric.pods.target) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.pods.target] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.pods.target)) -}} + {{- end -}} + + {{- if not (mustHas (kindOf $metric.pods.target.averageValue) (list "int" "int64" "float64" "string")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.pods.target.averageValue] to be an integer or string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.pods.target.averageValue)) -}} + {{- end -}} + + {{- if $metric.pods.metric.selector -}} + {{- include "tc.v1.common.lib.hpa.validation.metric.selector" (dict "objectData" $objectData "rootCtx" $rootCtx "data" $metric.pods "key" "pods" "idx" $idx) -}} + {{- end -}} + +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metric.selector" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $data := .data -}} + {{- $key := .key -}} + {{- $idx := .idx -}} + + {{- if not (kindIs "map" $data.metric.selector) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.metric.selector] to be a map, but got [%s]" $objectData.hpaName $idx $key (kindOf $data.metric.selector)) -}} + {{- end -}} + + {{- if not (kindIs "map" $data.metric.selector.matchLabels) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.metric.selector.matchLabels] to be a map, but got [%s]" $objectData.hpaName $idx $key (kindOf $data.metric.selector.matchLabels)) -}} + {{- end -}} + + {{- range $k, $v := $data.metric.selector.matchLabels -}} + {{- if not (kindIs "string" $k) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.metric.selector.matchLabels] to have string keys, but got [%s]" $objectData.hpaName $idx $key (kindOf $k)) -}} + {{- end -}} + + {{- if not (kindIs "string" $v) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.metric.selector.matchLabels.%s] to be a string, but got [%s]" $objectData.hpaName $idx $key $k (kindOf $v)) -}} + {{- end -}} + {{- end -}} + +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metrics.object" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $metric := .metric -}} + {{- $idx := .idx -}} + + {{- if not (kindIs "map" $metric.object) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object)) -}} + {{- end -}} + + {{- if not (kindIs "map" $metric.object.metric) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.metric] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.metric)) -}} + {{- end -}} + + {{- if or (not $metric.object.metric.name) (not (kindIs "string" $metric.object.metric.name)) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.metric.name] to be a string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.metric.name)) -}} + {{- end -}} + + {{- if not (kindIs "map" $metric.object.target) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.target] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.target)) -}} + {{- end -}} + + {{- $validTypes := list "AverageValue" "Value" -}} + {{- if not (mustHas $metric.object.target.type $validTypes) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.target.type] to be one of [%s], but got [%s]" $objectData.hpaName $idx (join ", " $validTypes) $metric.object.target.type) -}} + {{- end -}} + + {{- if eq $metric.object.target.type "AverageValue" -}} + {{- if not (mustHas (kindOf $metric.object.target.averageValue) (list "int" "int64" "float64" "string")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.target.averageValue] to be an integer or string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.target.averageValue)) -}} + {{- end -}} + {{- else if eq $metric.object.target.type "Value" -}} + {{- if not (mustHas (kindOf $metric.object.target.value) (list "int" "int64" "float64" "string")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.target.value] to be an integer or string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.target.value)) -}} + {{- end -}} + {{- end -}} + + {{- if $metric.object.metric.selector -}} + {{- include "tc.v1.common.lib.hpa.validation.metric.selector" (dict "objectData" $objectData "rootCtx" $rootCtx "data" $metric.object "key" "object" "idx" $idx) -}} + {{- end -}} + + {{- if not (kindIs "map" $metric.object.describedObject) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.describedObject] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.describedObject)) -}} + {{- end -}} + + {{- if or (not $metric.object.describedObject.name) (not (kindIs "string" $metric.object.describedObject.name)) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.describedObject.name] to be a string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.describedObject.name)) -}} + {{- end -}} + + {{- if or (not $metric.object.describedObject.kind) (not (kindIs "string" $metric.object.describedObject.kind)) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.describedObject.kind] to be a string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.describedObject.kind)) -}} + {{- end -}} + + {{- if or (not $metric.object.describedObject.apiVersion) (not (kindIs "string" $metric.object.describedObject.apiVersion)) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.object.describedObject.apiVersion] to be a string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.object.describedObject.apiVersion)) -}} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metrics.external" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $metric := .metric -}} + {{- $idx := .idx -}} + + {{- if not (kindIs "map" $metric.external) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.external] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.external)) -}} + {{- end -}} + + {{- if not (kindIs "map" $metric.external.metric) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.external.metric] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.external.metric)) -}} + {{- end -}} + + {{- if or (not $metric.external.metric.name) (not (kindIs "string" $metric.external.metric.name)) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.external.metric.name] to be a string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.external.metric.name)) -}} + {{- end -}} + + {{- if not (kindIs "map" $metric.external.target) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.external.target] to be a map, but got [%s]" $objectData.hpaName $idx (kindOf $metric.external.target)) -}} + {{- end -}} + + {{- $validTypes := list "AverageValue" "Value" -}} + {{- if not (mustHas $metric.external.target.type $validTypes) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.external.target.type] to be one of [%s], but got [%s]" $objectData.hpaName $idx (join ", " $validTypes) $metric.external.target.type) -}} + {{- end -}} + + {{- if eq $metric.external.target.type "AverageValue" -}} + {{- if not (mustHas (kindOf $metric.external.target.averageValue) (list "int" "int64" "float64" "string")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.external.target.averageValue] to be an integer or string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.external.target.averageValue)) -}} + {{- end -}} + {{- else if eq $metric.external.target.type "Value" -}} + {{- if not (mustHas (kindOf $metric.external.target.value) (list "int" "int64" "float64" "string")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.external.target.value] to be an integer or string, but got [%s]" $objectData.hpaName $idx (kindOf $metric.external.target.value)) -}} + {{- end -}} + {{- end -}} + + {{- if $metric.external.metric.selector -}} + {{- include "tc.v1.common.lib.hpa.validation.metric.selector" (dict "objectData" $objectData "rootCtx" $rootCtx "data" $metric.external "key" "external" "idx" $idx) -}} + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.lib.hpa.validation.metrics.metric.target" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + {{- $data := .metric -}} + {{- $key := .key -}} + {{- $idx := .idx -}} + + {{- if not (kindIs "map" $data.target) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.target] to be a map, but got [%s]" $objectData.hpaName $idx $key (kindOf $data.target)) -}} + {{- end -}} + + {{- $validTargetTypes := list "AverageValue" "Utilization" -}} + {{- if not (mustHas $data.target.type $validTargetTypes) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.target.type] to be one of [%s], but got [%s]" $objectData.hpaName $idx $key (join ", " $validTargetTypes) $data.target.type) -}} + {{- end -}} + + {{- if eq $data.target.type "AverageValue" -}} + {{- if not (mustHas (kindOf $data.target.averageValue) (list "int" "int64" "float64" "string")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.target.averageValue] to be an integer or string, but got [%s]" $objectData.hpaName $idx $key (kindOf $data.target.averageValue)) -}} + {{- end -}} + {{- else if eq $data.target.type "Utilization" -}} + {{- if not (mustHas (kindOf $data.target.averageUtilization) (list "int" "int64" "float64")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.target.averageUtilization] to be an integer, but got [%s]" $objectData.hpaName $idx $key (kindOf $data.target.averageUtilization)) -}} + {{- end -}} + {{- end -}} + + {{- if $data.target.value -}} + {{- if not (mustHas (kindOf $data.target.value) (list "int" "int64" "float64" "string")) -}} + {{- fail (printf "Horizontal Pod Autoscaler - Expected [hpa.%s.metrics.%d.%s.target.value] to be an integer or string, but got [%s]" $objectData.hpaName $idx $key (kindOf $data.target.value)) -}} + {{- end -}} + {{- end -}} + +{{- end -}} diff --git a/charts/library/common/templates/spawner/_horizontalPodAutoscaler.tpl b/charts/library/common/templates/spawner/_horizontalPodAutoscaler.tpl index 1a14dbc0a47..a88ebaa279a 100644 --- a/charts/library/common/templates/spawner/_horizontalPodAutoscaler.tpl +++ b/charts/library/common/templates/spawner/_horizontalPodAutoscaler.tpl @@ -1,19 +1,69 @@ -{{/* -Renders the configMap objects required by the chart. +{{/* horizontal Pod Autoscaler Spawner */}} +{{/* Call this template: +{{ include "tc.v1.common.spawner.hpa" $ -}} */}} -{{- define "tc.v1.common.spawner.hpa" -}} - {{/* Generate named configMaps as required */}} - {{- range $name, $hpa := .Values.horizontalPodAutoscaler -}} - {{- if $hpa.enabled -}} - {{- $hpaValues := $hpa -}} - {{/* set the default nameOverride to the hpa name */}} - {{- if not $hpaValues.nameOverride -}} - {{- $_ := set $hpaValues "nameOverride" $name -}} +{{- define "tc.v1.common.spawner.hpa" -}} + {{- $fullname := include "tc.v1.common.lib.chart.names.fullname" $ -}} + {{- range $name, $hpa := .Values.hpa -}} + {{- $enabledHPA := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $ "objectData" $hpa + "name" $name "caller" "Horizontal Pod Autoscaler" + "key" "hpa")) -}} + + {{- if ne $enabledHPA "true" -}}{{- continue -}}{{- end -}} + + {{- $objectData := (mustDeepCopy $hpa) -}} + {{- $_ := set $objectData "hpaName" $name -}} + {{- include "tc.v1.common.lib.chart.names.validation" (dict "name" $name) -}} + + {{- range $workloadName, $workload := $.Values.workload -}} + + {{- $enabled := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $ "objectData" $workload + "name" $name "caller" "hpa" + "key" "workload")) -}} + + {{- if ne $enabled "true" -}}{{- continue -}}{{- end -}} + {{- $containerNames := list -}} + {{- range $cName, $c := $workload.podSpec.containers -}} + {{- $enabledContainer := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $ "objectData" $c + "name" $cName "caller" "Vertical Pod Autoscaler" + "key" "workload.podSpec.containers")) -}} + {{- if ne $enabledContainer "true" -}}{{- continue -}}{{- end -}} + {{- $containerNames = mustAppend $containerNames $cName -}} + {{- end -}} + {{- $_ := set $objectData "containerNames" $containerNames -}} + {{- include "tc.v1.common.lib.hpa.validation" (dict "objectData" $objectData "rootCtx" $) -}} + + {{/* Create a copy of the workload */}} + {{- $_ := set $objectData "workload" (mustDeepCopy $workload) -}} + + {{/* Generate the name of the hpa */}} + {{- $objectName := $fullname -}} + {{- if not $objectData.workload.primary -}} + {{- $objectName = printf "%s-%s" $fullname $workloadName -}} + {{- end -}} + + {{/* Perform validations */}} + {{- include "tc.v1.common.lib.chart.names.validation" (dict "name" $objectName) -}} + {{- include "tc.v1.common.lib.metadata.validation" (dict "objectData" $objectData "caller" "Horizontal Pod Autoscaler") -}} + + {{/* Set the name of the workload */}} + {{- $_ := set $objectData "name" $objectName -}} + + {{/* Short name is the one that defined on the chart, used on selectors */}} + {{- $_ := set $objectData "shortName" $workloadName -}} + + {{- if or (not $objectData.targetSelector) (mustHas $workloadName $objectData.targetSelector) -}} + {{/* Call class to create the object */}} + {{- $types := (list "Deployment" "StatefulSet" "DaemonSet") -}} + {{- if (mustHas $objectData.workload.type $types) -}} + {{- include "tc.v1.common.class.hpa" (dict "rootCtx" $ "objectData" $objectData) -}} + {{- end -}} {{- end -}} - {{- $_ := set $ "ObjectValues" (dict "hpa" $hpaValues) -}} - {{- include "tc.v1.common.class.hpa" $ -}} {{- end -}} {{- end -}} {{- end -}} diff --git a/charts/library/common/values.yaml b/charts/library/common/values.yaml index 435dd80e264..bfd6c3a615f 100644 --- a/charts/library/common/values.yaml +++ b/charts/library/common/values.yaml @@ -213,6 +213,44 @@ vpa: memory: 20Gi controlledResources: ["cpu", "memory"] +# -- Horizontal pod autoscaler +hpa: + main: + enabled: false + targetSelector: [] + # minReplicas: 1 + # maxReplicas: 3 + + # metrics: # Optional, list of metric specs + # - type: Resource # Can be Resource, Pods, Object, External, or ContainerResource + # resource: + # name: cpu + # target: + # type: Utilization # Or Value / AverageValue + # averageUtilization: 50 + + # - type: Resource + # resource: + # name: memory + # target: + # type: AverageValue + # averageValue: 500Mi + + # behavior: # Optional: controls scaling behavior + # scaleUp: + # stabilizationWindowSeconds: 0 + # policies: + # - type: Percent + # value: 100 + # periodSeconds: 15 + # scaleDown: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 4 + # periodSeconds: 60 + + # -- (docs/service/README.md) service: main: