diff --git a/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/hpa.yaml b/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/hpa.yaml index 2d9ce3a5..c40e700f 100644 --- a/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/hpa.yaml +++ b/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/hpa.yaml @@ -1,6 +1,8 @@ metadata: name: tortoise-hpa-mercari namespace: default + labels: + app: tortoise spec: behavior: scaleDown: diff --git a/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/vpa-Monitor.yaml b/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/vpa-Monitor.yaml index 56e3a403..80c57854 100644 --- a/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/vpa-Monitor.yaml +++ b/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/vpa-Monitor.yaml @@ -3,6 +3,8 @@ metadata: tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" name: tortoise-monitor-mercari namespace: default + labels: + app: tortoise spec: targetRef: apiVersion: apps/v1 diff --git a/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml b/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml index ace001b9..521d689d 100644 --- a/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml +++ b/internal/controller/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml @@ -1,6 +1,8 @@ metadata: name: mercari namespace: default + labels: + app: tortoise spec: targetRefs: scaleTargetRef: diff --git a/internal/controller/testdata/mutable-labels-dryrun/after/deployment.yaml b/internal/controller/testdata/mutable-labels-dryrun/after/deployment.yaml new file mode 100644 index 00000000..a8a97e33 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/after/deployment.yaml @@ -0,0 +1,22 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/internal/controller/testdata/mutable-labels-dryrun/after/hpa.yaml b/internal/controller/testdata/mutable-labels-dryrun/after/hpa.yaml new file mode 100644 index 00000000..6a60e8e8 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/after/hpa.yaml @@ -0,0 +1,50 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-hpa-mercari + namespace: default + labels: + app: tortoise +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 90 + type: Percent + value: 2 + selectPolicy: Max + scaleUp: + policies: + - periodSeconds: 60 + type: Percent + value: 100 + selectPolicy: Max + stabilizationWindowSeconds: 0 + maxReplicas: 100 + metrics: + - containerResource: + container: app + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: ContainerResource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + conditions: + - status: "True" + type: AbleToScale + message: "recommended size matches current size" + - status: "True" + type: ScalingActive + message: "the HPA was able to compute the replica count" + currentMetrics: + - containerResource: + container: app + name: cpu + current: + value: 3 \ No newline at end of file diff --git a/internal/controller/testdata/mutable-labels-dryrun/after/tortoise.yaml b/internal/controller/testdata/mutable-labels-dryrun/after/tortoise.yaml new file mode 100644 index 00000000..4de3e89e --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/after/tortoise.yaml @@ -0,0 +1,101 @@ +metadata: + finalizers: + - tortoise.autoscaling.mercari.com/finalizer + name: mercari + namespace: default + labels: + app: new-tortoise +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "4" + memory: 4Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is not provided because it's Off mode + status: "False" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: app + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/internal/controller/testdata/mutable-labels-dryrun/after/vpa-Monitor.yaml b/internal/controller/testdata/mutable-labels-dryrun/after/vpa-Monitor.yaml new file mode 100644 index 00000000..4c70a1f2 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/after/vpa-Monitor.yaml @@ -0,0 +1,31 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-monitor-mercari + namespace: default + labels: + app: tortoise +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updatePolicy: + updateMode: "Off" +status: + conditions: + - lastTransitionTime: null + status: "True" + type: RecommendationProvided + recommendation: + containerRecommendations: + - containerName: app + lowerBound: + cpu: "3" + memory: 3Gi + target: + cpu: "3" + memory: 3Gi + upperBound: + cpu: "5" + memory: 5Gi diff --git a/internal/controller/testdata/mutable-labels-dryrun/before/deployment.yaml b/internal/controller/testdata/mutable-labels-dryrun/before/deployment.yaml new file mode 100644 index 00000000..22809372 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/before/deployment.yaml @@ -0,0 +1,22 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "4" + memory: 4Gi + replicas: 10 diff --git a/internal/controller/testdata/mutable-labels-dryrun/before/hpa.yaml b/internal/controller/testdata/mutable-labels-dryrun/before/hpa.yaml new file mode 100644 index 00000000..6a60e8e8 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/before/hpa.yaml @@ -0,0 +1,50 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-hpa-mercari + namespace: default + labels: + app: tortoise +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 90 + type: Percent + value: 2 + selectPolicy: Max + scaleUp: + policies: + - periodSeconds: 60 + type: Percent + value: 100 + selectPolicy: Max + stabilizationWindowSeconds: 0 + maxReplicas: 100 + metrics: + - containerResource: + container: app + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: ContainerResource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + conditions: + - status: "True" + type: AbleToScale + message: "recommended size matches current size" + - status: "True" + type: ScalingActive + message: "the HPA was able to compute the replica count" + currentMetrics: + - containerResource: + container: app + name: cpu + current: + value: 3 \ No newline at end of file diff --git a/internal/controller/testdata/mutable-labels-dryrun/before/tortoise.yaml b/internal/controller/testdata/mutable-labels-dryrun/before/tortoise.yaml new file mode 100644 index 00000000..4de3e89e --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/before/tortoise.yaml @@ -0,0 +1,101 @@ +metadata: + finalizers: + - tortoise.autoscaling.mercari.com/finalizer + name: mercari + namespace: default + labels: + app: new-tortoise +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "4" + memory: 4Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is not provided because it's Off mode + status: "False" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: app + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/internal/controller/testdata/mutable-labels-dryrun/before/vpa-Monitor.yaml b/internal/controller/testdata/mutable-labels-dryrun/before/vpa-Monitor.yaml new file mode 100644 index 00000000..4c70a1f2 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-dryrun/before/vpa-Monitor.yaml @@ -0,0 +1,31 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-monitor-mercari + namespace: default + labels: + app: tortoise +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updatePolicy: + updateMode: "Off" +status: + conditions: + - lastTransitionTime: null + status: "True" + type: RecommendationProvided + recommendation: + containerRecommendations: + - containerName: app + lowerBound: + cpu: "3" + memory: 3Gi + target: + cpu: "3" + memory: 3Gi + upperBound: + cpu: "5" + memory: 5Gi diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/after/deployment.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/after/deployment.yaml new file mode 100644 index 00000000..560b9bf9 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/after/deployment.yaml @@ -0,0 +1,24 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi +status: {} diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/after/hpa.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/after/hpa.yaml new file mode 100644 index 00000000..0ab6b845 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/after/hpa.yaml @@ -0,0 +1,50 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-hpa-mercari + namespace: default + labels: + app: existing-hpa +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 90 + type: Percent + value: 2 + selectPolicy: Max + scaleUp: + policies: + - periodSeconds: 60 + type: Percent + value: 100 + selectPolicy: Max + stabilizationWindowSeconds: 0 + maxReplicas: 20 + metrics: + - containerResource: + container: app + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: ContainerResource + minReplicas: 5 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + conditions: + - status: "True" + type: AbleToScale + message: "recommended size matches current size" + - status: "True" + type: ScalingActive + message: "the HPA was able to compute the replica count" + currentMetrics: + - containerResource: + container: app + name: cpu + current: + value: 3 diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/after/tortoise.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/after/tortoise.yaml new file mode 100644 index 00000000..5d9787b0 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/after/tortoise.yaml @@ -0,0 +1,107 @@ +metadata: + finalizers: + - tortoise.autoscaling.mercari.com/finalizer + name: mercari + namespace: default + labels: + app: tortoise +spec: + targetRefs: + horizontalPodAutoscalerName: tortoise-hpa-mercari + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 90 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/after/vpa-Monitor.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/after/vpa-Monitor.yaml new file mode 100644 index 00000000..4c70a1f2 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/after/vpa-Monitor.yaml @@ -0,0 +1,31 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-monitor-mercari + namespace: default + labels: + app: tortoise +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updatePolicy: + updateMode: "Off" +status: + conditions: + - lastTransitionTime: null + status: "True" + type: RecommendationProvided + recommendation: + containerRecommendations: + - containerName: app + lowerBound: + cpu: "3" + memory: 3Gi + target: + cpu: "3" + memory: 3Gi + upperBound: + cpu: "5" + memory: 5Gi diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/before/deployment.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/before/deployment.yaml new file mode 100644 index 00000000..b1599acc --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/before/deployment.yaml @@ -0,0 +1,24 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + replicas: 10 diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/before/hpa.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/before/hpa.yaml new file mode 100644 index 00000000..d6e8ac23 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/before/hpa.yaml @@ -0,0 +1,51 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-hpa-mercari + namespace: default + labels: + app: existing-hpa +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 90 + type: Percent + value: 2 + selectPolicy: Max + scaleUp: + policies: + - periodSeconds: 60 + type: Percent + value: 100 + selectPolicy: Max + stabilizationWindowSeconds: 0 + maxReplicas: 20 + metrics: + - containerResource: + container: app + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: ContainerResource + minReplicas: 5 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + conditions: + - status: "True" + type: AbleToScale + message: "recommended size matches current size" + - status: "True" + type: ScalingActive + message: "the HPA was able to compute the replica count" + currentMetrics: + - containerResource: + container: app + name: cpu + current: + value: 3 + diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/before/tortoise.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/before/tortoise.yaml new file mode 100644 index 00000000..5d9787b0 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/before/tortoise.yaml @@ -0,0 +1,107 @@ +metadata: + finalizers: + - tortoise.autoscaling.mercari.com/finalizer + name: mercari + namespace: default + labels: + app: tortoise +spec: + targetRefs: + horizontalPodAutoscalerName: tortoise-hpa-mercari + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 90 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/internal/controller/testdata/mutable-labels-existing-hpa/before/vpa-Monitor.yaml b/internal/controller/testdata/mutable-labels-existing-hpa/before/vpa-Monitor.yaml new file mode 100644 index 00000000..6b084a27 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-existing-hpa/before/vpa-Monitor.yaml @@ -0,0 +1,31 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-monitor-mercari + namespace: default + labels: + app: old-tortoise +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updatePolicy: + updateMode: "Off" +status: + conditions: + - lastTransitionTime: null + status: "True" + type: RecommendationProvided + recommendation: + containerRecommendations: + - containerName: app + lowerBound: + cpu: "3" + memory: 3Gi + target: + cpu: "3" + memory: 3Gi + upperBound: + cpu: "5" + memory: 5Gi diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/after/deployment.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/after/deployment.yaml new file mode 100644 index 00000000..1f8bac7a --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/after/deployment.yaml @@ -0,0 +1,24 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/after/hpa.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/after/hpa.yaml new file mode 100644 index 00000000..f8c0968d --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/after/hpa.yaml @@ -0,0 +1,50 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-hpa-mercari + namespace: default + labels: + app: tortoise +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 90 + type: Percent + value: 2 + selectPolicy: Max + scaleUp: + policies: + - periodSeconds: 60 + type: Percent + value: 100 + selectPolicy: Max + stabilizationWindowSeconds: 0 + maxReplicas: 20 + metrics: + - containerResource: + container: app + name: cpu + target: + averageUtilization: 75 + type: Utilization + type: ContainerResource + minReplicas: 5 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + conditions: + - status: "True" + type: AbleToScale + message: "recommended size matches current size" + - status: "True" + type: ScalingActive + message: "the HPA was able to compute the replica count" + currentMetrics: + - containerResource: + container: app + name: cpu + current: + value: 3 diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/after/tortoise.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/after/tortoise.yaml new file mode 100644 index 00000000..df5d7dd3 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/after/tortoise.yaml @@ -0,0 +1,106 @@ +metadata: + finalizers: + - tortoise.autoscaling.mercari.com/finalizer + name: mercari + namespace: default + labels: + app: tortoise +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "3" + memory: 4Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "3" + memory: 3Gi + containerName: app + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/after/vpa-Monitor.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/after/vpa-Monitor.yaml new file mode 100644 index 00000000..4c70a1f2 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/after/vpa-Monitor.yaml @@ -0,0 +1,31 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-monitor-mercari + namespace: default + labels: + app: tortoise +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updatePolicy: + updateMode: "Off" +status: + conditions: + - lastTransitionTime: null + status: "True" + type: RecommendationProvided + recommendation: + containerRecommendations: + - containerName: app + lowerBound: + cpu: "3" + memory: 3Gi + target: + cpu: "3" + memory: 3Gi + upperBound: + cpu: "5" + memory: 5Gi diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/before/deployment.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/before/deployment.yaml new file mode 100644 index 00000000..5a26f7c3 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/before/deployment.yaml @@ -0,0 +1,24 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "4" + memory: 4Gi + replicas: 10 diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/before/hpa.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/before/hpa.yaml new file mode 100644 index 00000000..450a8cd5 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/before/hpa.yaml @@ -0,0 +1,48 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-hpa-mercari + namespace: default +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 90 + type: Percent + value: 2 + selectPolicy: Max + scaleUp: + policies: + - periodSeconds: 60 + type: Percent + value: 100 + selectPolicy: Max + stabilizationWindowSeconds: 0 + maxReplicas: 20 + metrics: + - containerResource: + container: app + name: cpu + target: + averageUtilization: 75 + type: Utilization + type: ContainerResource + minReplicas: 5 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + conditions: + - status: "True" + type: AbleToScale + message: "recommended size matches current size" + - status: "True" + type: ScalingActive + message: "the HPA was able to compute the replica count" + currentMetrics: + - containerResource: + container: app + name: cpu + current: + value: 3 diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/before/tortoise.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/before/tortoise.yaml new file mode 100644 index 00000000..df5d7dd3 --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/before/tortoise.yaml @@ -0,0 +1,106 @@ +metadata: + finalizers: + - tortoise.autoscaling.mercari.com/finalizer + name: mercari + namespace: default + labels: + app: tortoise +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "3" + memory: 4Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "3" + memory: 3Gi + containerName: app + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/internal/controller/testdata/mutable-labels-managed-hpa/before/vpa-Monitor.yaml b/internal/controller/testdata/mutable-labels-managed-hpa/before/vpa-Monitor.yaml new file mode 100644 index 00000000..6648998b --- /dev/null +++ b/internal/controller/testdata/mutable-labels-managed-hpa/before/vpa-Monitor.yaml @@ -0,0 +1,29 @@ +metadata: + annotations: + tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" + name: tortoise-monitor-mercari + namespace: default +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updatePolicy: + updateMode: "Off" +status: + conditions: + - lastTransitionTime: null + status: "True" + type: RecommendationProvided + recommendation: + containerRecommendations: + - containerName: app + lowerBound: + cpu: "3" + memory: 3Gi + target: + cpu: "3" + memory: 3Gi + upperBound: + cpu: "5" + memory: 5Gi diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/hpa.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/hpa.yaml index a0c6bcf1..7ed1651e 100644 --- a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/hpa.yaml +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/hpa.yaml @@ -3,6 +3,8 @@ metadata: tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" name: tortoise-hpa-mercari namespace: default + labels: + app: "existing-hpa" spec: behavior: scaleDown: diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml index acad485a..fa0e4689 100644 --- a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml @@ -3,6 +3,8 @@ metadata: skip-status-update: "true" name: mercari namespace: default + labels: + app: tortoise spec: targetRefs: horizontalPodAutoscalerName: tortoise-hpa-mercari diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/vpa-Monitor.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/vpa-Monitor.yaml index ecaec3aa..20cb3f04 100644 --- a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/vpa-Monitor.yaml +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/after/vpa-Monitor.yaml @@ -1,6 +1,8 @@ metadata: name: tortoise-monitor-mercari namespace: default + labels: + app: tortoise spec: targetRef: apiVersion: apps/v1 diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/hpa.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/hpa.yaml index bdd7234f..75ddd1d6 100644 --- a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/hpa.yaml +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/hpa.yaml @@ -3,6 +3,8 @@ metadata: tortoise.autoscaling.mercari.com/managed-by-tortoise: "true" name: tortoise-hpa-mercari namespace: default + labels: + app: "existing-hpa" status: conditions: - status: "True" diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml index 2b5ce95d..6bdf02e7 100644 --- a/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml @@ -3,6 +3,8 @@ metadata: namespace: default annotations: "skip-status-update": "true" + labels: + app: tortoise spec: targetRefs: horizontalPodAutoscalerName: tortoise-hpa-mercari # Autoscaling policy will be generated from HPA diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/deployment.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/deployment.yaml new file mode 100644 index 00000000..a8a97e33 --- /dev/null +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/deployment.yaml @@ -0,0 +1,22 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/hpa.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/hpa.yaml new file mode 100644 index 00000000..c40e700f --- /dev/null +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/hpa.yaml @@ -0,0 +1,37 @@ +metadata: + name: tortoise-hpa-mercari + namespace: default + labels: + app: tortoise +spec: + behavior: + scaleDown: + policies: + - periodSeconds: 90 + type: Percent + value: 2 + selectPolicy: Max + scaleUp: + policies: + - periodSeconds: 60 + type: Percent + value: 100 + selectPolicy: Max + stabilizationWindowSeconds: 0 + maxReplicas: 10000 + metrics: + - containerResource: + container: app + name: cpu + target: + averageUtilization: 70 + type: Utilization + type: ContainerResource + minReplicas: 3 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + currentMetrics: null + desiredReplicas: 0 diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/tortoise.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/tortoise.yaml new file mode 100644 index 00000000..1d905935 --- /dev/null +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/tortoise.yaml @@ -0,0 +1,81 @@ +metadata: + annotations: + skip-status-update: "true" + name: mercari + namespace: default + labels: + app: tortoise +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "0" + updatedAt: null + memory: + quantity: "0" + updatedAt: null + recommendation: + cpu: + quantity: "0" + updatedAt: null + memory: + quantity: "0" + updatedAt: null + containerResourceRequests: + - containerName: app + resource: + cpu: "4" + memory: 4Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: GatheringData + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: GatheringData + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Asia/Tokyo + to: 24 + updatedAt: null + value: 0 + minReplicas: + - from: 0 + timezone: Asia/Tokyo + to: 24 + updatedAt: null + value: 0 + vertical: + containerResourceRecommendation: null + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Initializing diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/vpa-Monitor.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/vpa-Monitor.yaml new file mode 100644 index 00000000..20cb3f04 --- /dev/null +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/after/vpa-Monitor.yaml @@ -0,0 +1,13 @@ +metadata: + name: tortoise-monitor-mercari + namespace: default + labels: + app: tortoise +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updatePolicy: + updateMode: "Off" +status: {} diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/before/deployment.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/before/deployment.yaml new file mode 100644 index 00000000..22809372 --- /dev/null +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/before/deployment.yaml @@ -0,0 +1,22 @@ +metadata: + name: mercari-app + namespace: default +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "4" + memory: 4Gi + replicas: 10 diff --git a/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/before/tortoise.yaml b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/before/tortoise.yaml new file mode 100644 index 00000000..94294d3f --- /dev/null +++ b/internal/controller/testdata/reconcile-for-the-single-container-pod-managed-hpa-initializing/before/tortoise.yaml @@ -0,0 +1,13 @@ +metadata: + name: mercari + namespace: default + annotations: + "skip-status-update": "true" + labels: + app: tortoise +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app diff --git a/internal/controller/tortoise_controller.go b/internal/controller/tortoise_controller.go index 3ce632c8..6fccf286 100644 --- a/internal/controller/tortoise_controller.go +++ b/internal/controller/tortoise_controller.go @@ -209,6 +209,20 @@ func (r *TortoiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ return ctrl.Result{}, err } + // Update HPA labels to match Tortoise labels + err = r.HpaService.UpdateHPALabelsFromTortoise(ctx, tortoise) + if err != nil { + logger.Error(err, "update HPA labels from Tortoise", "tortoise", req.NamespacedName) + return ctrl.Result{}, err + } + + // Update VPA labels to match Tortoise labels + err = r.VpaService.UpdateVPALabelsFromTortoise(ctx, tortoise) + if err != nil { + logger.Error(err, "update VPA labels from Tortoise", "tortoise", req.NamespacedName) + return ctrl.Result{}, err + } + monitorvpa, ready, err := r.VpaService.GetTortoiseMonitorVPA(ctx, tortoise) if err != nil { logger.Error(err, "failed to get tortoise VPA", "tortoise", req.NamespacedName) diff --git a/internal/controller/tortoise_controller_test.go b/internal/controller/tortoise_controller_test.go index 2f7f01e4..717894a9 100644 --- a/internal/controller/tortoise_controller_test.go +++ b/internal/controller/tortoise_controller_test.go @@ -428,9 +428,12 @@ var _ = Describe("Test TortoiseController", func() { It("TortoisePhaseWorking (GatheringData)", func() { runTest(filepath.Join("testdata", "reconcile-for-the-single-container-pod-gathering-data")) }) - It("TortoisePhaseInitializing", func() { + It("TortoisePhaseInitializing with horizontalPodAutoscalerName configured", func() { runTest(filepath.Join("testdata", "reconcile-for-the-single-container-pod-initializing")) }) + It("TortoisePhaseInitializing without horizontalPodAutoscalerName configured", func() { + runTest(filepath.Join("testdata", "reconcile-for-the-single-container-pod-managed-hpa-initializing")) + }) It("TortoisePhaseWorking (GatheringData is just finished)", func() { runTest(filepath.Join("testdata", "reconcile-for-the-single-container-pod-gathering-data-finished")) }) @@ -474,7 +477,7 @@ var _ = Describe("Test TortoiseController", func() { }) }) Context("mutable AutoscalingPolicy", func() { - It("Tortoise get Horizontal and create HPA", func() { + It("Tortoise get Horizontal and create HPA (with the same labels)", func() { runTest(filepath.Join("testdata", "mutable-autoscalingpolicy-no-hpa-and-add-horizontal")) }) It("Tortoise get another Horizontal and modify the existing HPA", func() { @@ -538,6 +541,17 @@ var _ = Describe("Test TortoiseController", func() { }).Should(Succeed()) }) }) + Context("labels propagation", func() { + It("Labels is updated without horizontalPodAutoscalerName configured", func() { + runTest(filepath.Join("testdata", "mutable-labels-managed-hpa")) + }) + It("Labels is updated with horizontalPodAutoscalerName configured", func() { + runTest(filepath.Join("testdata", "mutable-labels-existing-hpa")) + }) + It("Labels is updated with Tortoise in dry-run mode", func() { + runTest(filepath.Join("testdata", "mutable-labels-dryrun")) + }) + }) }) type testCase struct { @@ -555,9 +569,16 @@ func (t *testCase) compare(got resources) error { if d := cmp.Diff(t.want.tortoise, got.tortoise, cmpopts.IgnoreFields(v1beta3.Tortoise{}, "ObjectMeta")); d != "" { return fmt.Errorf("unexpected tortoise: diff = %s", d) } + if d := cmp.Diff(t.want.hpa, got.hpa, cmpopts.IgnoreFields(v2.HorizontalPodAutoscaler{}, "ObjectMeta")); d != "" { return fmt.Errorf("unexpected hpa: diff = %s", d) } + if t.want.hpa != nil { + if d := cmp.Diff(t.want.hpa.ObjectMeta.Labels, got.hpa.ObjectMeta.Labels); d != "" { + return fmt.Errorf("unexpected hpa labels: diff = %s", d) + } + } + // Only restartedAt annotation could be modified by the reconciliation // We don't care about the value, but the existence of the annotation. if got.deployment.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] != t.want.deployment.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] { @@ -567,6 +588,11 @@ func (t *testCase) compare(got resources) error { if d := cmp.Diff(t.want.vpa, got.vpa, cmpopts.IgnoreFields(autoscalingv1.VerticalPodAutoscaler{}, "ObjectMeta")); d != "" { return fmt.Errorf("unexpected vpa: diff = %s", d) } + if t.want.vpa != nil { + if d := cmp.Diff(t.want.vpa.ObjectMeta.Labels, got.vpa.ObjectMeta.Labels); d != "" { + return fmt.Errorf("unexpected vpa labels: diff = %s", d) + } + } return nil } diff --git a/pkg/hpa/service.go b/pkg/hpa/service.go index 8574982a..762913d6 100644 --- a/pkg/hpa/service.go +++ b/pkg/hpa/service.go @@ -267,6 +267,8 @@ func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1beta3.To }, } + c.copyLabelsFromTortoiseToHPA(tortoise, hpa) + hpa, tortoise, _ = c.syncHPAMetricsWithTortoiseAutoscalingPolicy(ctx, tortoise, hpa, now) tortoise.Status.Targets.HorizontalPodAutoscaler = hpa.Name @@ -275,6 +277,63 @@ func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1beta3.To return hpa.DeepCopy(), tortoise, err } +func (c *Service) copyLabelsFromTortoiseToHPA(tortoise *autoscalingv1beta3.Tortoise, hpa *v2.HorizontalPodAutoscaler) { + if len(tortoise.ObjectMeta.Labels) == 0 { + hpa.ObjectMeta.Labels = nil + return + } + + hpa.ObjectMeta.Labels = make(map[string]string, len(tortoise.ObjectMeta.Labels)) + for k, v := range tortoise.ObjectMeta.Labels { + hpa.ObjectMeta.Labels[k] = v + } +} + +// UpdateHPA updates HPA labels from Tortoise. +// Note that it will not perform any update if the UpdateMode is Off, user specified the existing HPA, or there is no horizontal policy. +func (c *Service) UpdateHPALabelsFromTortoise(ctx context.Context, tortoise *autoscalingv1beta3.Tortoise) error { + if tortoise.Spec.UpdateMode == autoscalingv1beta3.UpdateModeOff { + // When UpdateMode is Off, we don't update HPA. + return nil + } + if !HasHorizontal(tortoise) { + // no need to handle when there is no horizontal policy. + return nil + } + if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != nil { + // no need to handle when the user specified existing HPA. + return nil + } + + retryNumber := -1 + updateFn := func() error { + retryNumber++ + hpa := &v2.HorizontalPodAutoscaler{} + if err := c.c.Get(ctx, types.NamespacedName{Namespace: tortoise.Namespace, Name: tortoise.Status.Targets.HorizontalPodAutoscaler}, hpa); err != nil { + return fmt.Errorf("failed to get hpa on tortoise: %w", err) + } + hpa = hpa.DeepCopy() + + if reflect.DeepEqual(hpa.ObjectMeta.Labels, tortoise.ObjectMeta.Labels) { + // Labels are already in sync, no need to update. + return nil + } + c.copyLabelsFromTortoiseToHPA(tortoise, hpa) + + if err := c.c.Update(ctx, hpa); err != nil { + return fmt.Errorf("failed to update hpa: %w", err) + } + + return nil + } + + if err := retry.RetryOnConflict(retry.DefaultRetry, updateFn); err != nil { + return fmt.Errorf("update hpa labels: %w (%v times retried)", err, retryNumber) + } + + return nil +} + func (c *Service) GetHPAOnTortoiseSpec(ctx context.Context, tortoise *autoscalingv1beta3.Tortoise) (*v2.HorizontalPodAutoscaler, error) { if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName == nil { return nil, nil diff --git a/pkg/hpa/service_test.go b/pkg/hpa/service_test.go index 248dc59b..3e3f1fef 100644 --- a/pkg/hpa/service_test.go +++ b/pkg/hpa/service_test.go @@ -2885,6 +2885,92 @@ func TestService_InitializeHPA(t *testing.T) { }, }, }, + { + name: "should create new hpa with the same labels as tortoise", + args: args{ + tortoise: &v1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + Spec: v1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: v1beta3.TortoiseStatus{ + AutoscalingPolicy: []v1beta3.ContainerAutoscalingPolicy{ + { + ContainerName: "app", + Policy: map[v1.ResourceName]v1beta3.AutoscalingType{ + v1.ResourceMemory: v1beta3.AutoscalingTypeVertical, + v1.ResourceCPU: v1beta3.AutoscalingTypeHorizontal, + }, + }, + }, + }, + }, + replicaNum: 4, + }, + afterHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-hpa-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + Spec: v2.HorizontalPodAutoscalerSpec{ + MinReplicas: ptrInt32(3), + MaxReplicas: 1000, + Metrics: []v2.MetricSpec{ + { + Type: v2.ContainerResourceMetricSourceType, + ContainerResource: &v2.ContainerResourceMetricSource{ + Name: v1.ResourceCPU, + Container: "app", + Target: v2.MetricTarget{ + AverageUtilization: ptr.To[int32](70), + Type: v2.UtilizationMetricType, + }, + }, + }, + }, + ScaleTargetRef: v2.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + Behavior: &v2.HorizontalPodAutoscalerBehavior{ + ScaleUp: &v2.HPAScalingRules{ + Policies: []v2.HPAScalingPolicy{ + { + Type: v2.PercentScalingPolicy, + Value: 100, + PeriodSeconds: 60, + }, + }, + }, + ScaleDown: &v2.HPAScalingRules{ + Policies: []v2.HPAScalingPolicy{ + { + Type: v2.PercentScalingPolicy, + Value: 2, + PeriodSeconds: 90, + }, + }, + }, + }, + }, + }, + }, { name: "just give annotation to existing hpa", args: args{ @@ -5013,3 +5099,346 @@ func TestService_IsHpaMetricAvailable(t *testing.T) { }) } } + +func TestService_UpdateHPALabelsFromTortoise(t *testing.T) { + type args struct { + tortoise *v1beta3.Tortoise + } + tests := []struct { + name string + // initialHPA is the initial state of the HPA in kube-apiserver + initialHPA *v2.HorizontalPodAutoscaler + args args + afterHPA *v2.HorizontalPodAutoscaler + wantErr bool + }{ + { + name: "should update HPA labels when HPA is created by tortoise", + args: args{ + tortoise: &v1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + Spec: v1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: v1beta3.TortoiseStatus{ + AutoscalingPolicy: []v1beta3.ContainerAutoscalingPolicy{ + { + ContainerName: "app", + Policy: map[v1.ResourceName]v1beta3.AutoscalingType{ + v1.ResourceMemory: v1beta3.AutoscalingTypeVertical, + v1.ResourceCPU: v1beta3.AutoscalingTypeHorizontal, + }, + }, + }, + Targets: v1beta3.TargetsStatus{ + HorizontalPodAutoscaler: "hpa", + }, + }, + }, + }, + initialHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + }, + }, + afterHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + }, + }, + { + name: "should remove HPA labels when tortoise labels are empty", + args: args{ + tortoise: &v1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + }, + Spec: v1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: v1beta3.TortoiseStatus{ + AutoscalingPolicy: []v1beta3.ContainerAutoscalingPolicy{ + { + ContainerName: "app", + Policy: map[v1.ResourceName]v1beta3.AutoscalingType{ + v1.ResourceMemory: v1beta3.AutoscalingTypeVertical, + v1.ResourceCPU: v1beta3.AutoscalingTypeHorizontal, + }, + }, + }, + Targets: v1beta3.TargetsStatus{ + HorizontalPodAutoscaler: "hpa", + }, + }, + }, + }, + initialHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + }, + afterHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + }, + }, + }, + { + name: "should overwrite existing labels on Tortoise-managed HPA", + args: args{ + tortoise: &v1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + Spec: v1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: v1beta3.TortoiseStatus{ + AutoscalingPolicy: []v1beta3.ContainerAutoscalingPolicy{ + { + ContainerName: "app", + Policy: map[v1.ResourceName]v1beta3.AutoscalingType{ + v1.ResourceMemory: v1beta3.AutoscalingTypeVertical, + v1.ResourceCPU: v1beta3.AutoscalingTypeHorizontal, + }, + }, + }, + Targets: v1beta3.TargetsStatus{ + HorizontalPodAutoscaler: "hpa", + }, + }, + }, + }, + initialHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + Labels: map[string]string{ + "app": "old-tortoise", + }, + }, + }, + afterHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + }, + { + name: "should NOT update HPA labels when user specified existing HPA", + args: args{ + tortoise: &v1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + Spec: v1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + HorizontalPodAutoscalerName: ptr.To("existing-hpa"), + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: v1beta3.TortoiseStatus{ + AutoscalingPolicy: []v1beta3.ContainerAutoscalingPolicy{ + { + ContainerName: "app", + Policy: map[v1.ResourceName]v1beta3.AutoscalingType{ + v1.ResourceMemory: v1beta3.AutoscalingTypeVertical, + v1.ResourceCPU: v1beta3.AutoscalingTypeHorizontal, + }, + }, + }, + }, + }, + }, + initialHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-hpa", + Namespace: "default", + Labels: map[string]string{ + "app": "existing-hpa", + }, + }, + }, + afterHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-hpa", + Namespace: "default", + Labels: map[string]string{ + "app": "existing-hpa", + }, + }, + }, + }, + { + name: "should NOT update when update mode is off", + args: args{ + tortoise: &v1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + Spec: v1beta3.TortoiseSpec{ + UpdateMode: v1beta3.UpdateModeOff, + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: v1beta3.TortoiseStatus{ + AutoscalingPolicy: []v1beta3.ContainerAutoscalingPolicy{ + { + ContainerName: "app", + Policy: map[v1.ResourceName]v1beta3.AutoscalingType{ + v1.ResourceMemory: v1beta3.AutoscalingTypeVertical, + v1.ResourceCPU: v1beta3.AutoscalingTypeHorizontal, + }, + }, + }, + }, + }, + }, + initialHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + }, + }, + afterHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + }, + }, + }, + { + name: "should NOT update when no horizontal policy is set", + args: args{ + tortoise: &v1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + Spec: v1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: v1beta3.TortoiseStatus{ + AutoscalingPolicy: []v1beta3.ContainerAutoscalingPolicy{ + { + ContainerName: "app", + Policy: map[v1.ResourceName]v1beta3.AutoscalingType{ + v1.ResourceMemory: v1beta3.AutoscalingTypeVertical, + v1.ResourceCPU: v1beta3.AutoscalingTypeVertical, + }, + }, + }, + }, + }, + }, + initialHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + }, + }, + afterHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + Namespace: "default", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, err := New(fake.NewClientBuilder().WithRuntimeObjects(tt.initialHPA).Build(), record.NewFakeRecorder(10), 0.95, 90, 100, time.Hour, 1000, 10000, 3, "") + if err != nil { + t.Fatalf("New() error = %v", err) + } + + err = c.UpdateHPALabelsFromTortoise(context.Background(), tt.args.tortoise) + if (err != nil) != tt.wantErr { + t.Errorf("Service.UpdateHPALabelsFromTortoise() error = %v, wantErr %v", err, tt.wantErr) + return + } + + hpa := &v2.HorizontalPodAutoscaler{} + err = c.c.Get(context.Background(), client.ObjectKey{Name: tt.afterHPA.Name, Namespace: tt.afterHPA.Namespace}, hpa) + if err != nil { + t.Errorf("get hpa error = %v", err) + } + if d := cmp.Diff(tt.afterHPA, hpa, cmpopts.IgnoreFields(v2.HorizontalPodAutoscaler{}, "TypeMeta"), cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ResourceVersion")); d != "" { + t.Errorf("Service.UpdateHPALabelsFromTortoise() hpa diff = %v", d) + } + }) + } +} diff --git a/pkg/vpa/service.go b/pkg/vpa/service.go index b5cb7067..e7bd72be 100644 --- a/pkg/vpa/service.go +++ b/pkg/vpa/service.go @@ -3,6 +3,7 @@ package vpa import ( "context" "fmt" + "reflect" "time" autoscaling "k8s.io/api/autoscaling/v1" @@ -104,6 +105,7 @@ func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autosc ResourcePolicy: &v1.PodResourcePolicy{}, }, } + crp := make([]v1.ContainerResourcePolicy, 0, len(tortoise.Spec.ResourcePolicy)) for _, c := range tortoise.Spec.ResourcePolicy { if c.MinAllocatedResources == nil { @@ -121,6 +123,8 @@ func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autosc Role: autoscalingv1beta3.VerticalPodAutoscalerRoleMonitor, }) + c.copyLabelsFromTortoiseToVPA(tortoise, vpa) + vpa, err := c.c.AutoscalingV1().VerticalPodAutoscalers(vpa.Namespace).Create(ctx, vpa, metav1.CreateOptions{}) if err != nil { return nil, tortoise, err @@ -131,6 +135,49 @@ func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autosc return vpa, tortoise, nil } +func (c *Service) copyLabelsFromTortoiseToVPA(tortoise *autoscalingv1beta3.Tortoise, vpa *v1.VerticalPodAutoscaler) { + if len(tortoise.ObjectMeta.Labels) == 0 { + vpa.ObjectMeta.Labels = nil + return + } + + vpa.ObjectMeta.Labels = make(map[string]string, len(tortoise.ObjectMeta.Labels)) + for k, v := range tortoise.ObjectMeta.Labels { + vpa.ObjectMeta.Labels[k] = v + } +} + +func (c *Service) UpdateVPALabelsFromTortoise(ctx context.Context, tortoise *autoscalingv1beta3.Tortoise) error { + if tortoise.Spec.UpdateMode == autoscalingv1beta3.UpdateModeOff { + // When UpdateMode is Off, we don't update VPA. + return nil + } + + retryNumber := -1 + updateFn := func() error { + retryNumber++ + vpa, _, err := c.GetTortoiseMonitorVPA(ctx, tortoise) + if err != nil { + return fmt.Errorf("failed to get vpa on tortoise: %w", err) + } + vpa = vpa.DeepCopy() + + if reflect.DeepEqual(vpa.ObjectMeta.Labels, tortoise.ObjectMeta.Labels) { + return nil + } + c.copyLabelsFromTortoiseToVPA(tortoise, vpa) + + _, err = c.c.AutoscalingV1().VerticalPodAutoscalers(vpa.Namespace).Update(ctx, vpa, metav1.UpdateOptions{}) + return err + } + + if err := retry.RetryOnConflict(retry.DefaultRetry, updateFn); err != nil { + return fmt.Errorf("update vpa labels: %w (%v times retried)", err, retryNumber) + } + + return nil +} + func (c *Service) GetTortoiseMonitorVPA(ctx context.Context, tortoise *autoscalingv1beta3.Tortoise) (*v1.VerticalPodAutoscaler, bool, error) { vpa, err := c.c.AutoscalingV1().VerticalPodAutoscalers(tortoise.Namespace).Get(ctx, TortoiseMonitorVPAName(tortoise.Name), metav1.GetOptions{}) if err != nil { diff --git a/pkg/vpa/service_test.go b/pkg/vpa/service_test.go index 5ef9e561..173b1f4d 100644 --- a/pkg/vpa/service_test.go +++ b/pkg/vpa/service_test.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + autoscalingv1 "k8s.io/api/autoscaling/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -679,3 +680,396 @@ func Test_isMonitorVPAReady(t *testing.T) { }) } } + +func TestService_CreateTortoiseMonitorVPA(t *testing.T) { + off := vpav1.UpdateModeOff + type args struct { + tortoise *autoscalingv1beta3.Tortoise + } + tests := []struct { + name string + args args + wantTortoise *autoscalingv1beta3.Tortoise + wantVPA *vpav1.VerticalPodAutoscaler + wantErr bool + }{ + { + name: "should create new vpa", + args: args{ + tortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + }, + Spec: autoscalingv1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + ResourcePolicy: []autoscalingv1beta3.ContainerResourcePolicy{ + { + ContainerName: "app", + MinAllocatedResources: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("0.5Gi"), + v1.ResourceCPU: resource.MustParse("0.5"), + }, + }, + }, + }, + }, + }, + wantTortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + }, + Spec: autoscalingv1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + ResourcePolicy: []autoscalingv1beta3.ContainerResourcePolicy{ + { + ContainerName: "app", + MinAllocatedResources: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("0.5Gi"), + v1.ResourceCPU: resource.MustParse("0.5"), + }, + }, + }, + }, + Status: autoscalingv1beta3.TortoiseStatus{ + Targets: autoscalingv1beta3.TargetsStatus{ + VerticalPodAutoscalers: []autoscalingv1beta3.TargetStatusVerticalPodAutoscaler{ + { + Name: "tortoise-monitor-tortoise", + Role: autoscalingv1beta3.VerticalPodAutoscalerRoleMonitor, + }, + }, + }, + }, + }, + wantVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + }, + Spec: vpav1.VerticalPodAutoscalerSpec{ + TargetRef: &autoscalingv1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + UpdatePolicy: &vpav1.PodUpdatePolicy{ + UpdateMode: &off, + }, + ResourcePolicy: &vpav1.PodResourcePolicy{ + ContainerPolicies: []vpav1.ContainerResourcePolicy{ + { + ContainerName: "app", + MinAllowed: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("0.5Gi"), + v1.ResourceCPU: resource.MustParse("0.5"), + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should create new vpa with the same labels as tortoise", + args: args{ + tortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + Spec: autoscalingv1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + }, + }, + wantTortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + Spec: autoscalingv1beta3.TortoiseSpec{ + TargetRefs: v1beta3.TargetRefs{ + ScaleTargetRef: v1beta3.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + }, + }, + Status: autoscalingv1beta3.TortoiseStatus{ + Targets: autoscalingv1beta3.TargetsStatus{ + VerticalPodAutoscalers: []autoscalingv1beta3.TargetStatusVerticalPodAutoscaler{ + { + Name: "tortoise-monitor-tortoise", + Role: autoscalingv1beta3.VerticalPodAutoscalerRoleMonitor, + }, + }, + }, + }, + }, + wantVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + Spec: vpav1.VerticalPodAutoscalerSpec{ + TargetRef: &autoscalingv1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, + UpdatePolicy: &vpav1.PodUpdatePolicy{ + UpdateMode: &off, + }, + ResourcePolicy: &vpav1.PodResourcePolicy{ + ContainerPolicies: []vpav1.ContainerResourcePolicy{}, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Service{ + c: fake.NewSimpleClientset(), + recorder: record.NewFakeRecorder(10), + } + vpa, tortoise, err := c.CreateTortoiseMonitorVPA(context.Background(), tt.args.tortoise) + if (err != nil) != tt.wantErr { + t.Errorf("Service.CreateTortoiseMonitorVPA() error = %v, wantErr %v", err, tt.wantErr) + } + + if d := cmp.Diff(tt.wantTortoise, tortoise); d != "" { + t.Errorf("Service.CreateTortoiseMonitorVPA() tortoise diff = %v", d) + } + + if d := cmp.Diff(tt.wantVPA, vpa); d != "" { + t.Errorf("Service.CreateTortoiseMonitorVPA() vpa diff = %v", d) + } + }) + } +} + +func TestService_UpdateVPALabelsFromTortoise(t *testing.T) { + type args struct { + tortoise *v1beta3.Tortoise + } + tests := []struct { + name string + initialVPA *vpav1.VerticalPodAutoscaler + args args + wantVPA *vpav1.VerticalPodAutoscaler + wantErr bool + }{ + { + name: "should update VPA labels", + args: args{ + tortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + }, + initialVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + }, + }, + wantVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + }, + { + name: "should overwrite VPA labels", + args: args{ + tortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + }, + initialVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "old-tortoise", + }, + }, + }, + wantVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + }, + { + name: "should remove VPA labels when tortoise labels are empty", + args: args{ + tortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + }, + }, + }, + initialVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + wantVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + }, + }, + }, + { + name: "should keep VPA labels empty", + args: args{ + tortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + }, + }, + }, + initialVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + }, + }, + wantVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + }, + }, + }, + { + name: "should NOT update when update mode is off", + args: args{ + tortoise: &autoscalingv1beta3.Tortoise{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + }, + }, + Spec: v1beta3.TortoiseSpec{ + UpdateMode: v1beta3.UpdateModeOff, + }, + }, + }, + initialVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + wantVPA: &vpav1.VerticalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tortoise-monitor-tortoise", + Namespace: "default", + Labels: map[string]string{ + "app": "tortoise", + "name": "tortoise", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Service{ + c: fake.NewSimpleClientset(tt.initialVPA), + recorder: record.NewFakeRecorder(10), + } + + err := c.UpdateVPALabelsFromTortoise(context.Background(), tt.args.tortoise) + if (err != nil) != tt.wantErr { + t.Errorf("UpdateVPALabelsFromTortoise() error = %v, wantErr %v", err, tt.wantErr) + return + } + + vpa, err := c.c.AutoscalingV1().VerticalPodAutoscalers(tt.args.tortoise.Namespace).Get(context.Background(), tt.initialVPA.Name, metav1.GetOptions{}) + if err != nil { + t.Errorf("get vpa error = %v", err) + } + + if d := cmp.Diff(tt.wantVPA, vpa); d != "" { + t.Errorf("UpdateVPALabelsFromTortoise() vpa diff = %v", d) + } + }) + } +}