diff --git a/api/autoscaling/v2/webhook_suite_test.go b/api/autoscaling/v2/webhook_suite_test.go index 8a5dab13..2a332f6d 100644 --- a/api/autoscaling/v2/webhook_suite_test.go +++ b/api/autoscaling/v2/webhook_suite_test.go @@ -149,7 +149,7 @@ var _ = BeforeSuite(func() { eventRecorder := mgr.GetEventRecorderFor("tortoise-controller") tortoiseService, err := tortoise.New(mgr.GetClient(), eventRecorder, config.RangeOfMinMaxReplicasRecommendationHours, config.TimeZone, config.TortoiseUpdateInterval, config.MinMaxReplicasRecommendationType) Expect(err).NotTo(HaveOccurred()) - hpaService := hpa.New(mgr.GetClient(), eventRecorder, config.ReplicaReductionFactor, config.UpperTargetResourceUtilization, 100) + hpaService := hpa.New(mgr.GetClient(), eventRecorder, config.ReplicaReductionFactor, config.UpperTargetResourceUtilization, 100, time.Hour) Expect(err).NotTo(HaveOccurred()) hpaWebhook := New(tortoiseService, hpaService) diff --git a/api/v1beta2/tortoise_types.go b/api/v1beta2/tortoise_types.go index 8ebfda0c..db84a1d5 100644 --- a/api/v1beta2/tortoise_types.go +++ b/api/v1beta2/tortoise_types.go @@ -294,7 +294,8 @@ type TortoiseConditionType string const ( // TortoiseConditionTypeFailedToReconcile means tortoise failed to reconcile due to some reasons. - TortoiseConditionTypeFailedToReconcile TortoiseConditionType = "FailedToReconcile" + TortoiseConditionTypeFailedToReconcile TortoiseConditionType = "FailedToReconcile" + TortoiseConditionTypeHPATargetUtilizationUpdated TortoiseConditionType = "HPATargetUtilizationUpdated" ) type TortoiseCondition struct { diff --git a/controllers/testdata/deletion-no-delete/before/tortoise.yaml b/controllers/testdata/deletion-no-delete/before/tortoise.yaml index 2e264a87..4f3aedc6 100644 --- a/controllers/testdata/deletion-no-delete/before/tortoise.yaml +++ b/controllers/testdata/deletion-no-delete/before/tortoise.yaml @@ -50,7 +50,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/deletion-policy-all/before/tortoise.yaml b/controllers/testdata/deletion-policy-all/before/tortoise.yaml index 69658265..3dafec6f 100644 --- a/controllers/testdata/deletion-policy-all/before/tortoise.yaml +++ b/controllers/testdata/deletion-policy-all/before/tortoise.yaml @@ -50,7 +50,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/after/tortoise.yaml b/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/after/tortoise.yaml index 7dc9a618..bae0d837 100644 --- a/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/after/tortoise.yaml +++ b/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/after/tortoise.yaml @@ -18,6 +18,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -49,7 +54,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/before/tortoise.yaml b/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/before/tortoise.yaml index f01426db..c8e52163 100644 --- a/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/before/tortoise.yaml +++ b/controllers/testdata/mutable-autoscalingpolicy-add-another-horizontal/before/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/tortoise.yaml b/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/tortoise.yaml index f8bee479..87010089 100644 --- a/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/tortoise.yaml +++ b/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/after/tortoise.yaml @@ -18,6 +18,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -49,7 +54,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml b/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml index 2f628d3e..829f024c 100644 --- a/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml +++ b/controllers/testdata/mutable-autoscalingpolicy-no-hpa-and-add-horizontal/before/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/after/tortoise.yaml b/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/after/tortoise.yaml index 8444175b..d61b9fca 100644 --- a/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/after/tortoise.yaml +++ b/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/after/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/before/tortoise.yaml b/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/before/tortoise.yaml index d69babba..f27c57c1 100644 --- a/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/before/tortoise.yaml +++ b/controllers/testdata/mutable-autoscalingpolicy-remove-horizontal/before/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/after/tortoise.yaml index da130bd8..82871e46 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/after/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/before/tortoise.yaml index f041f0e4..0493f3a1 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-off/before/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/after/tortoise.yaml index 36bfdbac..ef3f6e90 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/after/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/before/tortoise.yaml index 147dcf1d..cc5b26e3 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-all-vertical/before/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/after/tortoise.yaml index 79f32bc0..4c4bb9a9 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/after/tortoise.yaml @@ -19,6 +19,11 @@ spec: updateMode: Emergency status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -50,7 +55,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/before/tortoise.yaml index 002d8b30..fa7fa6c1 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-emergency/before/tortoise.yaml @@ -50,7 +50,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/after/tortoise.yaml index 1b9655e7..829a61d0 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/after/tortoise.yaml @@ -18,6 +18,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -49,7 +54,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/before/tortoise.yaml index 5d4e8b2f..93c15634 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-one-off/before/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/after/tortoise.yaml index be3f16f8..0aba545c 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/after/tortoise.yaml @@ -18,6 +18,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -49,7 +54,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/before/tortoise.yaml index 8d798ea5..75f3c4e2 100644 --- a/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-multiple-containers-pod-working/before/tortoise.yaml @@ -49,7 +49,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/after/tortoise.yaml index d1038313..f311e3b8 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/after/tortoise.yaml @@ -31,7 +31,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/before/tortoise.yaml index 3e5fa3f6..0b375b79 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-dryrun/before/tortoise.yaml @@ -31,7 +31,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-emergency/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-emergency/after/tortoise.yaml index d46102d5..d996882d 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-emergency/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-emergency/after/tortoise.yaml @@ -15,6 +15,11 @@ spec: updateMode: Emergency status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -31,7 +36,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-emergency/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-emergency/before/tortoise.yaml index a994b6e7..4ea140fd 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-emergency/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-emergency/before/tortoise.yaml @@ -31,7 +31,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/after/tortoise.yaml index c1cd2452..e28eca49 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/after/tortoise.yaml @@ -14,6 +14,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -30,7 +35,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/before/tortoise.yaml index f1e3c939..feb9adba 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data-finished/before/tortoise.yaml @@ -30,7 +30,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/after/tortoise.yaml index b5e36375..1686510d 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/after/tortoise.yaml @@ -30,7 +30,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/before/tortoise.yaml index be7be9e5..79b231ed 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-gathering-data/before/tortoise.yaml @@ -30,7 +30,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml index be7be9e5..64be4281 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-initializing/after/tortoise.yaml @@ -14,6 +14,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -30,7 +35,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml index be7be9e5..79b231ed 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-initializing/before/tortoise.yaml @@ -30,7 +30,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/after/tortoise.yaml index 63768cf8..d2ed653c 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/after/tortoise.yaml @@ -14,6 +14,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -30,7 +35,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/before/tortoise.yaml index 63768cf8..00ee7d75 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-partly-working/before/tortoise.yaml @@ -30,7 +30,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-working/after/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-working/after/tortoise.yaml index c1cd2452..e28eca49 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-working/after/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-working/after/tortoise.yaml @@ -14,6 +14,11 @@ spec: name: mercari-app status: conditions: + tortoiseConditions: + - message: "HPA target utilization is updated" + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated containerRecommendationFromVPA: - containerName: app maxRecommendation: @@ -30,7 +35,6 @@ status: memory: quantity: 3Gi updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/testdata/reconcile-for-the-single-container-pod-working/before/tortoise.yaml b/controllers/testdata/reconcile-for-the-single-container-pod-working/before/tortoise.yaml index 97c37a87..25a68876 100644 --- a/controllers/testdata/reconcile-for-the-single-container-pod-working/before/tortoise.yaml +++ b/controllers/testdata/reconcile-for-the-single-container-pod-working/before/tortoise.yaml @@ -30,7 +30,6 @@ status: memory: quantity: "0" updatedAt: null - tortoiseConditions: null recommendations: horizontal: maxReplicas: diff --git a/controllers/tortoise_controller_test.go b/controllers/tortoise_controller_test.go index c868f01b..7cfcdb1a 100644 --- a/controllers/tortoise_controller_test.go +++ b/controllers/tortoise_controller_test.go @@ -157,7 +157,7 @@ func startController(ctx context.Context) func() { Expect(err).ShouldNot(HaveOccurred()) reconciler := &TortoiseReconciler{ Scheme: scheme, - HpaService: hpa.New(mgr.GetClient(), record.NewFakeRecorder(10), 0.95, 90, 25), + HpaService: hpa.New(mgr.GetClient(), record.NewFakeRecorder(10), 0.95, 90, 25, time.Hour), EventRecorder: record.NewFakeRecorder(10), VpaService: cli, DeploymentService: deployment.New(mgr.GetClient()), diff --git a/main.go b/main.go index 2dcdcc30..976f08eb 100644 --- a/main.go +++ b/main.go @@ -134,7 +134,7 @@ func main() { os.Exit(1) } - hpaService := hpa.New(mgr.GetClient(), eventRecorder, config.ReplicaReductionFactor, config.UpperTargetResourceUtilization, config.TortoiseHPATargetUtilizationMaxIncrease) + hpaService := hpa.New(mgr.GetClient(), eventRecorder, config.ReplicaReductionFactor, config.UpperTargetResourceUtilization, config.TortoiseHPATargetUtilizationMaxIncrease, config.TortoiseHPATargetUtilizationUpdateInterval) if err = (&controllers.TortoiseReconciler{ Scheme: mgr.GetScheme(), diff --git a/pkg/config/config.go b/pkg/config/config.go index c472332c..22ec5810 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -37,25 +37,28 @@ type Config struct { TortoiseUpdateInterval time.Duration `yaml:"TortoiseUpdateInterval"` // TortoiseHPATargetUtilizationMaxIncrease is the max increase of target utilization that tortoise can give to the HPA (default: 5) TortoiseHPATargetUtilizationMaxIncrease int `yaml:"TortoiseHPATargetUtilizationMaxIncrease"` + // TortoiseHPATargetUtilizationUpdateInterval is the interval of updating target utilization of each HPA (default: 1h) + TortoiseHPATargetUtilizationUpdateInterval time.Duration `yaml:"TortoiseHPATargetUtilizationUpdateInterval"` } // ParseConfig parses the config file (yaml) and returns Config. func ParseConfig(path string) (*Config, error) { config := &Config{ - RangeOfMinMaxReplicasRecommendationHours: 1, - MinMaxReplicasRecommendationType: "weekly", - TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, - MaxReplicasFactor: 2.0, - MinReplicasFactor: 0.5, - ReplicaReductionFactor: 0.95, - UpperTargetResourceUtilization: 90, - MinimumMinReplicas: 3, - PreferredReplicaNumUpperLimit: 30, - MaximumCPUCores: "10", - MaximumMemoryBytes: "10Gi", - TimeZone: "Asia/Tokyo", - TortoiseUpdateInterval: 15 * time.Second, - TortoiseHPATargetUtilizationMaxIncrease: 5, + RangeOfMinMaxReplicasRecommendationHours: 1, + MinMaxReplicasRecommendationType: "weekly", + TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, + MaxReplicasFactor: 2.0, + MinReplicasFactor: 0.5, + ReplicaReductionFactor: 0.95, + UpperTargetResourceUtilization: 90, + MinimumMinReplicas: 3, + PreferredReplicaNumUpperLimit: 30, + MaximumCPUCores: "10", + MaximumMemoryBytes: "10Gi", + TimeZone: "Asia/Tokyo", + TortoiseUpdateInterval: 15 * time.Second, + TortoiseHPATargetUtilizationMaxIncrease: 5, + TortoiseHPATargetUtilizationUpdateInterval: time.Hour, } if path == "" { return config, nil @@ -87,5 +90,9 @@ func validate(config *Config) error { return fmt.Errorf("MinMaxReplicasRecommendationType should be either \"daily\" or \"weekly\"") } + if config.TortoiseHPATargetUtilizationMaxIncrease > 100 || config.TortoiseHPATargetUtilizationMaxIncrease <= 0 { + return fmt.Errorf("TortoiseHPATargetUtilizationMaxIncrease should be between 1 and 100") + } + return nil } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 2428bddc..6afb148a 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -22,20 +22,21 @@ func TestParseConfig(t *testing.T) { path: "./testdata/config.yaml", }, want: &Config{ - RangeOfMinMaxReplicasRecommendationHours: 2, - MinMaxReplicasRecommendationType: "daily", - TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, - MaxReplicasFactor: 2.0, - MinReplicasFactor: 0.5, - ReplicaReductionFactor: 0.95, - UpperTargetResourceUtilization: 90, - MinimumMinReplicas: 3, - PreferredReplicaNumUpperLimit: 30, - MaximumCPUCores: "10", - MaximumMemoryBytes: "10Gi", - TimeZone: "Asia/Tokyo", - TortoiseUpdateInterval: 1 * time.Hour, - TortoiseHPATargetUtilizationMaxIncrease: 5, + RangeOfMinMaxReplicasRecommendationHours: 2, + MinMaxReplicasRecommendationType: "daily", + TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, + MaxReplicasFactor: 2.0, + MinReplicasFactor: 0.5, + ReplicaReductionFactor: 0.95, + UpperTargetResourceUtilization: 90, + MinimumMinReplicas: 3, + PreferredReplicaNumUpperLimit: 30, + MaximumCPUCores: "10", + MaximumMemoryBytes: "10Gi", + TimeZone: "Asia/Tokyo", + TortoiseUpdateInterval: 1 * time.Hour, + TortoiseHPATargetUtilizationMaxIncrease: 10, + TortoiseHPATargetUtilizationUpdateInterval: 3 * time.Hour, }, }, { @@ -44,20 +45,21 @@ func TestParseConfig(t *testing.T) { path: "./testdata/config-partly-override.yaml", }, want: &Config{ - RangeOfMinMaxReplicasRecommendationHours: 6, - MinMaxReplicasRecommendationType: "weekly", - TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, - MaxReplicasFactor: 2.0, - MinReplicasFactor: 0.5, - ReplicaReductionFactor: 0.95, - UpperTargetResourceUtilization: 90, - MinimumMinReplicas: 3, - PreferredReplicaNumUpperLimit: 30, - MaximumCPUCores: "10", - MaximumMemoryBytes: "10Gi", - TimeZone: "Asia/Tokyo", - TortoiseUpdateInterval: 15 * time.Second, - TortoiseHPATargetUtilizationMaxIncrease: 5, + RangeOfMinMaxReplicasRecommendationHours: 6, + MinMaxReplicasRecommendationType: "weekly", + TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, + MaxReplicasFactor: 2.0, + MinReplicasFactor: 0.5, + ReplicaReductionFactor: 0.95, + UpperTargetResourceUtilization: 90, + MinimumMinReplicas: 3, + PreferredReplicaNumUpperLimit: 30, + MaximumCPUCores: "10", + MaximumMemoryBytes: "10Gi", + TimeZone: "Asia/Tokyo", + TortoiseUpdateInterval: 15 * time.Second, + TortoiseHPATargetUtilizationMaxIncrease: 5, + TortoiseHPATargetUtilizationUpdateInterval: 1 * time.Hour, }, }, { @@ -73,20 +75,21 @@ func TestParseConfig(t *testing.T) { path: "", }, want: &Config{ - RangeOfMinMaxReplicasRecommendationHours: 1, - MinMaxReplicasRecommendationType: "weekly", - TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, - MaxReplicasFactor: 2.0, - MinReplicasFactor: 0.5, - ReplicaReductionFactor: 0.95, - UpperTargetResourceUtilization: 90, - MinimumMinReplicas: 3, - PreferredReplicaNumUpperLimit: 30, - MaximumCPUCores: "10", - MaximumMemoryBytes: "10Gi", - TimeZone: "Asia/Tokyo", - TortoiseUpdateInterval: 15 * time.Second, - TortoiseHPATargetUtilizationMaxIncrease: 5, + RangeOfMinMaxReplicasRecommendationHours: 1, + MinMaxReplicasRecommendationType: "weekly", + TTLHoursOfMinMaxReplicasRecommendation: 24 * 30, + MaxReplicasFactor: 2.0, + MinReplicasFactor: 0.5, + ReplicaReductionFactor: 0.95, + UpperTargetResourceUtilization: 90, + MinimumMinReplicas: 3, + PreferredReplicaNumUpperLimit: 30, + MaximumCPUCores: "10", + MaximumMemoryBytes: "10Gi", + TimeZone: "Asia/Tokyo", + TortoiseUpdateInterval: 15 * time.Second, + TortoiseHPATargetUtilizationMaxIncrease: 5, + TortoiseHPATargetUtilizationUpdateInterval: time.Hour, }, }, } diff --git a/pkg/config/testdata/config.yaml b/pkg/config/testdata/config.yaml index c48ba448..c77bc692 100644 --- a/pkg/config/testdata/config.yaml +++ b/pkg/config/testdata/config.yaml @@ -7,7 +7,9 @@ ReplicaReductionFactor: 0.95 UpperTargetResourceUtilization: 90 MinimumMinReplicas: 3 PreferredReplicaNumUpperLimit: 30 -MaximumCPUCores: "10" -MaximumMemoryBytes: "10Gi" +MaximumCPUCores: "10" +MaximumMemoryBytes: "10Gi" TimeZone: "Asia/Tokyo" -TortoiseUpdateInterval: "1h" \ No newline at end of file +TortoiseUpdateInterval: "1h" +TortoiseHPATargetUtilizationMaxIncrease: 10 +TortoiseHPATargetUtilizationUpdateInterval: "3h" \ No newline at end of file diff --git a/pkg/hpa/service.go b/pkg/hpa/service.go index d2068a38..c6c002a2 100644 --- a/pkg/hpa/service.go +++ b/pkg/hpa/service.go @@ -29,19 +29,21 @@ import ( type Service struct { c client.Client - replicaReductionFactor float64 - upperTargetResourceUtilization int32 - tortoiseHPATargetUtilizationMaxIncrease int - recorder record.EventRecorder + replicaReductionFactor float64 + upperTargetResourceUtilization int32 + tortoiseHPATargetUtilizationMaxIncrease int + recorder record.EventRecorder + tortoiseHPATargetUtilizationUpdateInterval time.Duration } -func New(c client.Client, recorder record.EventRecorder, replicaReductionFactor float64, upperTargetResourceUtilization, tortoiseHPATargetUtilizationMaxIncrease int) *Service { +func New(c client.Client, recorder record.EventRecorder, replicaReductionFactor float64, upperTargetResourceUtilization, tortoiseHPATargetUtilizationMaxIncrease int, tortoiseHPATargetUtilizationUpdateInterval time.Duration) *Service { return &Service{ c: c, replicaReductionFactor: replicaReductionFactor, upperTargetResourceUtilization: int32(upperTargetResourceUtilization), tortoiseHPATargetUtilizationMaxIncrease: tortoiseHPATargetUtilizationMaxIncrease, recorder: recorder, + tortoiseHPATargetUtilizationUpdateInterval: tortoiseHPATargetUtilizationUpdateInterval, } } @@ -300,6 +302,39 @@ func (c *Service) GetHPAOnTortoise(ctx context.Context, tortoise *autoscalingv1b return hpa, nil } +func (s *Service) UpdatingHPATargetUtilizationAllowed(tortoise *autoscalingv1beta2.Tortoise, now time.Time) (*autoscalingv1beta2.Tortoise, bool) { + for i, c := range tortoise.Status.Conditions.TortoiseConditions { + if c.Type == autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated { + tortoise.Status.Conditions.TortoiseConditions[i].LastUpdateTime = metav1.NewTime(now) + // if the last update is within the interval, we don't update the HPA. + return tortoise, c.LastTransitionTime.Add(s.tortoiseHPATargetUtilizationUpdateInterval).Before(now) + } + } + // It's the first time to update the HPA. (Or someone modified the status) + return tortoise, true +} + +func (s *Service) RecordHPATargetUtilizationUpdate(tortoise *autoscalingv1beta2.Tortoise, now time.Time) *autoscalingv1beta2.Tortoise { + for i, c := range tortoise.Status.Conditions.TortoiseConditions { + if c.Type == autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated { + tortoise.Status.Conditions.TortoiseConditions[i].LastTransitionTime = metav1.NewTime(now) + tortoise.Status.Conditions.TortoiseConditions[i].LastUpdateTime = metav1.NewTime(now) + return tortoise + } + } + + // It's the first time to update the HPA. (Or someone modified the status) + tortoise.Status.Conditions.TortoiseConditions = append(tortoise.Status.Conditions.TortoiseConditions, autoscalingv1beta2.TortoiseCondition{ + Type: autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated, + Status: corev1.ConditionTrue, + LastUpdateTime: metav1.NewTime(now), + LastTransitionTime: metav1.NewTime(now), + Reason: "HPATargetUtilizationUpdated", + Message: "HPA target utilization is updated", + }) + return tortoise +} + func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1beta2.Tortoise, hpa *v2.HorizontalPodAutoscaler, now time.Time, recordMetrics bool) (*v2.HorizontalPodAutoscaler, *autoscalingv1beta2.Tortoise, error) { readyHorizontalResourceAndContainer := sets.New[resourceNameAndContainerName]() for _, p := range tortoise.Spec.ResourcePolicy { @@ -317,6 +352,8 @@ func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1bet } } + var allowed bool + tortoise, allowed = c.UpdatingHPATargetUtilizationAllowed(tortoise, now) for _, t := range tortoise.Status.Recommendations.Horizontal.TargetUtilizations { for resourcename, proposedTarget := range t.TargetUtilization { if !readyHorizontalResourceAndContainer.Has(resourceNameAndContainerName{resourcename, t.ContainerName}) { @@ -325,11 +362,21 @@ func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1bet } metrics.ProposedHPATargetUtilization.WithLabelValues(tortoise.Name, tortoise.Namespace, t.ContainerName, resourcename.String(), hpa.Name).Set(float64(proposedTarget)) + if !allowed { + // we don't want to update the HPA too frequently. + // But, we record the proposed HPA target utilization in metrics. + continue + } + if err := c.updateHPATargetValue(hpa, t.ContainerName, resourcename, proposedTarget); err != nil { return nil, tortoise, fmt.Errorf("update HPA from the recommendation from tortoise") } + } } + if allowed && tortoise.Spec.UpdateMode != autoscalingv1beta2.UpdateModeOff { + tortoise = c.RecordHPATargetUtilizationUpdate(tortoise, now) + } max, err := GetReplicasRecommendation(tortoise.Status.Recommendations.Horizontal.MaxReplicas, now) if err != nil { diff --git a/pkg/hpa/service_test.go b/pkg/hpa/service_test.go index 2e56c6e2..eb863fa8 100644 --- a/pkg/hpa/service_test.go +++ b/pkg/hpa/service_test.go @@ -60,6 +60,18 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, Status: autoscalingv1beta2.TortoiseStatus{ + Conditions: autoscalingv1beta2.Conditions{ + TortoiseConditions: []autoscalingv1beta2.TortoiseCondition{ + { + Type: autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated, + Status: v1.ConditionTrue, + LastUpdateTime: metav1.NewTime(now.Add(-3 * time.Hour)), + LastTransitionTime: metav1.NewTime(now.Add(-3 * time.Hour)), + Reason: "HPATargetUtilizationUpdated", + Message: "HPA target utilization is updated", + }, + }, + }, ContainerResourcePhases: []autoscalingv1beta2.ContainerResourcePhases{ { ContainerName: "app", @@ -191,6 +203,95 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, + wantTortoise: &autoscalingv1beta2.Tortoise{ + Spec: autoscalingv1beta2.TortoiseSpec{ + ResourcePolicy: []autoscalingv1beta2.ContainerResourcePolicy{ + { + ContainerName: "app", + AutoscalingPolicy: map[v1.ResourceName]v1beta2.AutoscalingType{ + v1.ResourceMemory: v1beta2.AutoscalingTypeHorizontal, + }, + }, + { + ContainerName: "istio-proxy", + AutoscalingPolicy: map[v1.ResourceName]v1beta2.AutoscalingType{ + v1.ResourceCPU: v1beta2.AutoscalingTypeHorizontal, + }, + }, + }, + }, + Status: autoscalingv1beta2.TortoiseStatus{ + Conditions: autoscalingv1beta2.Conditions{ + TortoiseConditions: []autoscalingv1beta2.TortoiseCondition{ + { + Type: autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated, + Status: v1.ConditionTrue, + LastUpdateTime: now, + LastTransitionTime: now, + Reason: "HPATargetUtilizationUpdated", + Message: "HPA target utilization is updated", + }, + }, + }, + ContainerResourcePhases: []autoscalingv1beta2.ContainerResourcePhases{ + { + ContainerName: "app", + ResourcePhases: map[v1.ResourceName]autoscalingv1beta2.ResourcePhase{ + v1.ResourceMemory: { + Phase: autoscalingv1beta2.ContainerResourcePhaseWorking, + }, + }, + }, + { + ContainerName: "istio-proxy", + ResourcePhases: map[v1.ResourceName]autoscalingv1beta2.ResourcePhase{ + v1.ResourceCPU: { + Phase: autoscalingv1beta2.ContainerResourcePhaseWorking, + }, + }, + }, + }, + Targets: autoscalingv1beta2.TargetsStatus{ + HorizontalPodAutoscaler: "hpa", + }, + Recommendations: autoscalingv1beta2.Recommendations{ + Horizontal: autoscalingv1beta2.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta2.HPATargetUtilizationRecommendationPerContainer{ + { + ContainerName: "app", + TargetUtilization: map[v1.ResourceName]int32{ + v1.ResourceMemory: 90, + }, + }, + { + ContainerName: "istio-proxy", + TargetUtilization: map[v1.ResourceName]int32{ + v1.ResourceCPU: 80, + }, + }, + }, + MaxReplicas: []autoscalingv1beta2.ReplicasRecommendation{ + { + From: 0, + To: 2, + Value: 6, + UpdatedAt: now, + WeekDay: pointer.String(now.Weekday().String()), + }, + }, + MinReplicas: []autoscalingv1beta2.ReplicasRecommendation{ + { + From: 0, + To: 2, + Value: 3, + UpdatedAt: now, + WeekDay: pointer.String(now.Weekday().String()), + }, + }, + }, + }, + }, + }, wantErr: false, }, { @@ -348,6 +449,264 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, wantErr: false, }, + { + name: "no update preformed when we recently updated it", + args: args{ + ctx: context.Background(), + tortoise: &autoscalingv1beta2.Tortoise{ + Spec: autoscalingv1beta2.TortoiseSpec{ + ResourcePolicy: []autoscalingv1beta2.ContainerResourcePolicy{ + { + ContainerName: "app", + AutoscalingPolicy: map[v1.ResourceName]v1beta2.AutoscalingType{ + v1.ResourceMemory: v1beta2.AutoscalingTypeHorizontal, + }, + }, + { + ContainerName: "istio-proxy", + AutoscalingPolicy: map[v1.ResourceName]v1beta2.AutoscalingType{ + v1.ResourceCPU: v1beta2.AutoscalingTypeHorizontal, + }, + }, + }, + UpdateMode: autoscalingv1beta2.UpdateModeAuto, + }, + Status: autoscalingv1beta2.TortoiseStatus{ + Conditions: autoscalingv1beta2.Conditions{ + TortoiseConditions: []autoscalingv1beta2.TortoiseCondition{ + { + Type: autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated, + Status: v1.ConditionTrue, + LastUpdateTime: metav1.NewTime(now.Add(-1 * time.Minute)), + LastTransitionTime: metav1.NewTime(now.Add(-1 * time.Minute)), + Reason: "HPATargetUtilizationUpdated", + Message: "HPA target utilization is updated", + }, + }, + }, + ContainerResourcePhases: []autoscalingv1beta2.ContainerResourcePhases{ + { + ContainerName: "app", + ResourcePhases: map[v1.ResourceName]autoscalingv1beta2.ResourcePhase{ + v1.ResourceMemory: { + Phase: autoscalingv1beta2.ContainerResourcePhaseWorking, + }, + }, + }, + { + ContainerName: "istio-proxy", + ResourcePhases: map[v1.ResourceName]autoscalingv1beta2.ResourcePhase{ + v1.ResourceCPU: { + Phase: autoscalingv1beta2.ContainerResourcePhaseWorking, + }, + }, + }, + }, + Targets: autoscalingv1beta2.TargetsStatus{ + HorizontalPodAutoscaler: "hpa", + }, + Recommendations: autoscalingv1beta2.Recommendations{ + Horizontal: autoscalingv1beta2.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta2.HPATargetUtilizationRecommendationPerContainer{ + { + ContainerName: "app", + TargetUtilization: map[v1.ResourceName]int32{ + v1.ResourceMemory: 90, + }, + }, + { + ContainerName: "istio-proxy", + TargetUtilization: map[v1.ResourceName]int32{ + v1.ResourceCPU: 80, + }, + }, + }, + MaxReplicas: []autoscalingv1beta2.ReplicasRecommendation{ + { + From: 0, + To: 2, + Value: 6, + UpdatedAt: now, + WeekDay: pointer.String(now.Weekday().String()), + }, + }, + MinReplicas: []autoscalingv1beta2.ReplicasRecommendation{ + { + From: 0, + To: 2, + Value: 3, + UpdatedAt: now, + WeekDay: pointer.String(now.Weekday().String()), + }, + }, + }, + }, + }, + }, + now: now.Time, + }, + initialHPA: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + }, + Spec: v2.HorizontalPodAutoscalerSpec{ + MinReplicas: ptrInt32(1), + MaxReplicas: 2, + Metrics: []v2.MetricSpec{ + { + Type: v2.ObjectMetricSourceType, + // should be ignored + }, + { + Type: v2.ContainerResourceMetricSourceType, + ContainerResource: &v2.ContainerResourceMetricSource{ + Name: v1.ResourceMemory, + Target: v2.MetricTarget{ + AverageUtilization: pointer.Int32(60), + }, + Container: "app", + }, + }, + { + Type: v2.ContainerResourceMetricSourceType, + ContainerResource: &v2.ContainerResourceMetricSource{ + Name: v1.ResourceCPU, + Target: v2.MetricTarget{ + AverageUtilization: pointer.Int32(50), + }, + Container: "istio-proxy", + }, + }, + }, + }, + }, + want: &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpa", + }, + Spec: v2.HorizontalPodAutoscalerSpec{ + MinReplicas: ptrInt32(3), + MaxReplicas: 6, + Metrics: []v2.MetricSpec{ + { + Type: v2.ObjectMetricSourceType, + // should be ignored + }, + { + Type: v2.ContainerResourceMetricSourceType, + ContainerResource: &v2.ContainerResourceMetricSource{ + Name: v1.ResourceMemory, + Target: v2.MetricTarget{ + AverageUtilization: pointer.Int32(60), + }, + Container: "app", + }, + }, + { + Type: v2.ContainerResourceMetricSourceType, + ContainerResource: &v2.ContainerResourceMetricSource{ + Name: v1.ResourceCPU, + Target: v2.MetricTarget{ + AverageUtilization: pointer.Int32(50), + }, + Container: "istio-proxy", + }, + }, + }, + }, + }, + wantTortoise: &autoscalingv1beta2.Tortoise{ + Spec: autoscalingv1beta2.TortoiseSpec{ + ResourcePolicy: []autoscalingv1beta2.ContainerResourcePolicy{ + { + ContainerName: "app", + AutoscalingPolicy: map[v1.ResourceName]v1beta2.AutoscalingType{ + v1.ResourceMemory: v1beta2.AutoscalingTypeHorizontal, + }, + }, + { + ContainerName: "istio-proxy", + AutoscalingPolicy: map[v1.ResourceName]v1beta2.AutoscalingType{ + v1.ResourceCPU: v1beta2.AutoscalingTypeHorizontal, + }, + }, + }, + UpdateMode: autoscalingv1beta2.UpdateModeAuto, + }, + Status: autoscalingv1beta2.TortoiseStatus{ + Conditions: autoscalingv1beta2.Conditions{ + TortoiseConditions: []autoscalingv1beta2.TortoiseCondition{ + { + Type: autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated, + Status: v1.ConditionTrue, + LastUpdateTime: now, + LastTransitionTime: metav1.NewTime(now.Add(-1 * time.Minute)), + Reason: "HPATargetUtilizationUpdated", + Message: "HPA target utilization is updated", + }, + }, + }, + ContainerResourcePhases: []autoscalingv1beta2.ContainerResourcePhases{ + { + ContainerName: "app", + ResourcePhases: map[v1.ResourceName]autoscalingv1beta2.ResourcePhase{ + v1.ResourceMemory: { + Phase: autoscalingv1beta2.ContainerResourcePhaseWorking, + }, + }, + }, + { + ContainerName: "istio-proxy", + ResourcePhases: map[v1.ResourceName]autoscalingv1beta2.ResourcePhase{ + v1.ResourceCPU: { + Phase: autoscalingv1beta2.ContainerResourcePhaseWorking, + }, + }, + }, + }, + Targets: autoscalingv1beta2.TargetsStatus{ + HorizontalPodAutoscaler: "hpa", + }, + Recommendations: autoscalingv1beta2.Recommendations{ + Horizontal: autoscalingv1beta2.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta2.HPATargetUtilizationRecommendationPerContainer{ + { + ContainerName: "app", + TargetUtilization: map[v1.ResourceName]int32{ + v1.ResourceMemory: 90, + }, + }, + { + ContainerName: "istio-proxy", + TargetUtilization: map[v1.ResourceName]int32{ + v1.ResourceCPU: 80, + }, + }, + }, + MaxReplicas: []autoscalingv1beta2.ReplicasRecommendation{ + { + From: 0, + To: 2, + Value: 6, + UpdatedAt: now, + WeekDay: pointer.String(now.Weekday().String()), + }, + }, + MinReplicas: []autoscalingv1beta2.ReplicasRecommendation{ + { + From: 0, + To: 2, + Value: 3, + UpdatedAt: now, + WeekDay: pointer.String(now.Weekday().String()), + }, + }, + }, + }, + }, + }, + wantErr: false, + }, { name: "no update preformed when updateMode is Off", args: args{ @@ -1090,6 +1449,18 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, Status: autoscalingv1beta2.TortoiseStatus{ + Conditions: autoscalingv1beta2.Conditions{ + TortoiseConditions: []autoscalingv1beta2.TortoiseCondition{ + { + Type: autoscalingv1beta2.TortoiseConditionTypeHPATargetUtilizationUpdated, + Status: v1.ConditionTrue, + LastUpdateTime: now, + LastTransitionTime: now, + Reason: "HPATargetUtilizationUpdated", + Message: "HPA target utilization is updated", + }, + }, + }, Targets: autoscalingv1beta2.TargetsStatus{ HorizontalPodAutoscaler: "hpa", }, @@ -1138,7 +1509,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := New(fake.NewClientBuilder().WithRuntimeObjects(tt.initialHPA).Build(), record.NewFakeRecorder(10), 0.95, 90, 50) + c := New(fake.NewClientBuilder().WithRuntimeObjects(tt.initialHPA).Build(), record.NewFakeRecorder(10), 0.95, 90, 50, time.Hour) got, tortoise, err := c.UpdateHPAFromTortoiseRecommendation(tt.args.ctx, tt.args.tortoise, tt.args.now) if (err != nil) != tt.wantErr { t.Errorf("UpdateHPAFromTortoiseRecommendation() error = %v, wantErr %v", err, tt.wantErr) @@ -1405,9 +1776,9 @@ func TestService_InitializeHPA(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := New(fake.NewClientBuilder().Build(), record.NewFakeRecorder(10), 0.95, 90, 100) + c := New(fake.NewClientBuilder().Build(), record.NewFakeRecorder(10), 0.95, 90, 100, time.Hour) if tt.initialHPA != nil { - c = New(fake.NewClientBuilder().WithRuntimeObjects(tt.initialHPA).Build(), record.NewFakeRecorder(10), 0.95, 90, 100) + c = New(fake.NewClientBuilder().WithRuntimeObjects(tt.initialHPA).Build(), record.NewFakeRecorder(10), 0.95, 90, 100, time.Hour) } _, err := c.InitializeHPA(context.Background(), tt.args.tortoise, tt.args.dm, time.Now()) if (err != nil) != tt.wantErr { @@ -2127,9 +2498,9 @@ func TestService_UpdateHPASpecFromTortoiseAutoscalingPolicy(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := New(fake.NewClientBuilder().Build(), record.NewFakeRecorder(10), 0.95, 90, 100) + c := New(fake.NewClientBuilder().Build(), record.NewFakeRecorder(10), 0.95, 90, 100, time.Hour) if tt.initialHPA != nil { - c = New(fake.NewClientBuilder().WithRuntimeObjects(tt.initialHPA).Build(), record.NewFakeRecorder(10), 0.95, 90, 100) + c = New(fake.NewClientBuilder().WithRuntimeObjects(tt.initialHPA).Build(), record.NewFakeRecorder(10), 0.95, 90, 100, time.Hour) } tortoise, err := c.UpdateHPASpecFromTortoiseAutoscalingPolicy(context.Background(), tt.args.tortoise, tt.args.dm, time.Now()) if (err != nil) != tt.wantErr {