From 70701ce2a3b16b6e099f88870a45c1d0340a6535 Mon Sep 17 00:00:00 2001 From: Kensei Nakada Date: Tue, 3 Oct 2023 19:05:46 +0900 Subject: [PATCH] feature: Tortoise goes to beta with some changes (#133) --- PROJECT | 16 + README.md | 4 +- api/v1alpha1/service.go | 43 -- api/v1alpha1/tortoise_conversion.go | 307 ++++++++ api/v1alpha1/tortoise_webhook.go | 175 ----- api/v1alpha1/zz_generated.deepcopy.go | 2 +- api/v1beta1/groupversion_info.go | 45 ++ api/v1beta1/service.go | 30 + api/v1beta1/tortoise_conversion.go | 29 + api/v1beta1/tortoise_types.go | 297 ++++++++ api/v1beta1/tortoise_webhook.go | 227 ++++++ api/v1beta1/webhook_suite_test.go | 141 ++++ api/v1beta1/zz_generated.deepcopy.go | 443 ++++++++++++ .../autoscaling.mercari.com_tortoises.yaml | 351 ++++++++++ config/crd/kustomization.yaml | 4 +- .../autoscaling_v1alpha2_tortoise.yaml | 12 + config/webhook/manifests.yaml | 8 +- controllers/suite_test.go | 4 +- controllers/tortoise_controller.go | 18 +- controllers/tortoise_controller_test.go | 658 ++++++++++-------- go.mod | 2 +- main.go | 6 + ...ion_tortoises.autoscaling.mercari.com.yaml | 294 ++++++++ ...rtoise-mutating-webhook-configuration.yaml | 4 +- ...oise-validating-webhook-configuration.yaml | 4 +- ...ion_tortoises.autoscaling.mercari.com.yaml | 294 ++++++++ pkg/deployment/service.go | 6 +- pkg/hpa/service.go | 45 +- pkg/hpa/service_test.go | 139 ++-- pkg/recommender/recommender.go | 36 +- pkg/recommender/recommender_test.go | 294 ++++---- pkg/tortoise/tortoise.go | 79 +-- pkg/tortoise/tortoise_test.go | 158 ++--- pkg/utils/tortoise_builder.go | 29 +- pkg/vpa/service.go | 40 +- 35 files changed, 3278 insertions(+), 966 deletions(-) delete mode 100644 api/v1alpha1/service.go create mode 100644 api/v1alpha1/tortoise_conversion.go create mode 100644 api/v1beta1/groupversion_info.go create mode 100644 api/v1beta1/service.go create mode 100644 api/v1beta1/tortoise_conversion.go create mode 100644 api/v1beta1/tortoise_types.go create mode 100644 api/v1beta1/tortoise_webhook.go create mode 100644 api/v1beta1/webhook_suite_test.go create mode 100644 api/v1beta1/zz_generated.deepcopy.go create mode 100644 config/samples/autoscaling_v1alpha2_tortoise.yaml diff --git a/PROJECT b/PROJECT index 42055855..94bb9261 100644 --- a/PROJECT +++ b/PROJECT @@ -1,3 +1,7 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: mercari.com layout: - go.kubebuilder.io/v3 @@ -14,7 +18,19 @@ resources: path: github.com/mercari/tortoise/api/v1alpha1 version: v1alpha1 webhooks: + conversion: true defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + domain: mercari.com + group: autoscaling + kind: Tortoise + path: github.com/mercari/tortoise/api/v1beta1 + version: v1beta1 + webhooks: + conversion: true + webhookVersion: v1 version: "3" diff --git a/README.md b/README.md index d1214d7f..54467b74 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Tortoise, you don't need a rearing cage, but need VPA in your Kubernetes cluster Tortoise, they only need the deployment name basically. ```yaml -apiVersion: autoscaling.mercari.com/v1alpha1 +apiVersion: autoscaling.mercari.com/v1beta1 kind: Tortoise metadata: name: lovely-tortoise @@ -55,7 +55,7 @@ Tortoise, then they'll prepare/keep adjusting HPA and VPA to achieve efficient a ## API definition -- [Tortoise](./api/v1alpha1/tortoise_types.go) +- [Tortoise](./api/v1beta1/tortoise_types.go) ## Contribution diff --git a/api/v1alpha1/service.go b/api/v1alpha1/service.go deleted file mode 100644 index 0134350b..00000000 --- a/api/v1alpha1/service.go +++ /dev/null @@ -1,43 +0,0 @@ -package v1alpha1 - -import ( - "context" - "fmt" - - v1 "k8s.io/api/apps/v1" - v2 "k8s.io/api/autoscaling/v2" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type service struct { - c client.Client -} - -func newService(c client.Client) *service { - return &service{c: c} -} - -func (c *service) GetDeploymentOnTortoise(ctx context.Context, tortoise *Tortoise) (*v1.Deployment, error) { - d := &v1.Deployment{} - if err := c.c.Get(ctx, types.NamespacedName{Namespace: tortoise.Namespace, Name: tortoise.Spec.TargetRefs.DeploymentName}, d); err != nil { - return nil, fmt.Errorf("failed to get deployment on tortoise: %w", err) - } - return d, nil -} - -func (c *service) GetHPAFromUser(ctx context.Context, tortoise *Tortoise) (*v2.HorizontalPodAutoscaler, error) { - if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName == nil { - // user doesn't specify HPA. - return nil, nil - } - - hpa := &v2.HorizontalPodAutoscaler{} - if err := c.c.Get(ctx, client.ObjectKey{ - Namespace: tortoise.Namespace, - Name: *tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName, - }, hpa); err != nil { - return nil, fmt.Errorf("get hpa: %w", err) - } - return hpa, nil -} diff --git a/api/v1alpha1/tortoise_conversion.go b/api/v1alpha1/tortoise_conversion.go new file mode 100644 index 00000000..da8ec5ad --- /dev/null +++ b/api/v1alpha1/tortoise_conversion.go @@ -0,0 +1,307 @@ +/* +MIT License + +Copyright (c) 2023 mercari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +package v1alpha1 + +import ( + "fmt" + + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "github.com/mercari/tortoise/api/v1beta1" +) + +// ConvertTo converts this CronJob to the Hub version (v1beta1). +func (src *Tortoise) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*v1beta1.Tortoise) + dst.ObjectMeta = src.ObjectMeta + + dst.Spec = v1beta1.TortoiseSpec{ + TargetRefs: v1beta1.TargetRefs{ + HorizontalPodAutoscalerName: src.Spec.TargetRefs.HorizontalPodAutoscalerName, + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: src.Spec.TargetRefs.DeploymentName, + }, + }, + UpdateMode: v1beta1.UpdateMode(src.Spec.UpdateMode), + ResourcePolicy: containerResourcePolicyConversionToV1Beta1(src.Spec.ResourcePolicy), + DeletionPolicy: v1beta1.DeletionPolicy(src.Spec.DeletionPolicy), + } + + dst.Status = v1beta1.TortoiseStatus{ + TortoisePhase: v1beta1.TortoisePhase(src.Status.TortoisePhase), + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: containerRecommendationFromVPAConversionToV1Beta1(src.Status.Conditions.ContainerRecommendationFromVPA), + }, + Targets: v1beta1.TargetsStatus{ + HorizontalPodAutoscaler: src.Status.Targets.HorizontalPodAutoscaler, + Deployment: src.Status.Targets.Deployment, + VerticalPodAutoscalers: verticalPodAutoscalersConversionToV1Beta1(src.Status.Targets.VerticalPodAutoscalers), + }, + } + if src.Status.Recommendations.Horizontal != nil { + dst.Status = v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: hPATargetUtilizationRecommendationPerContainerConversionToV1Beta1(src.Status.Recommendations.Horizontal.TargetUtilizations), + MaxReplicas: replicasRecommendationConversionToV1Beta1(src.Status.Recommendations.Horizontal.MaxReplicas), + MinReplicas: replicasRecommendationConversionToV1Beta1(src.Status.Recommendations.Horizontal.MinReplicas), + }, + }, + } + } + if src.Status.Recommendations.Vertical != nil { + dst.Status = v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: containerResourceRecommendationConversionToV1Beta1(src.Status.Recommendations.Vertical.ContainerResourceRecommendation), + }, + }, + } + } + + return nil +} + +// ConvertFrom converts from the Hub version (v1beta1) to this version. +func (dst *Tortoise) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*v1beta1.Tortoise) + if src.Spec.TargetRefs.ScaleTargetRef.Kind != "Deployment" { + return fmt.Errorf("scaleTargetRef is not Deployment, but %s which isn't supported in v1alpha1", src.Spec.TargetRefs.ScaleTargetRef.Kind) + } + + dst.ObjectMeta = src.ObjectMeta + dst.Spec = TortoiseSpec{ + TargetRefs: TargetRefs{ + HorizontalPodAutoscalerName: src.Spec.TargetRefs.HorizontalPodAutoscalerName, + DeploymentName: src.Spec.TargetRefs.ScaleTargetRef.Name, + }, + UpdateMode: UpdateMode(src.Spec.UpdateMode), + ResourcePolicy: containerResourcePolicyConversionFromV1Beta1(src.Spec.ResourcePolicy), + DeletionPolicy: DeletionPolicy(src.Spec.DeletionPolicy), + } + + dst.Status = TortoiseStatus{ + TortoisePhase: TortoisePhase(src.Status.TortoisePhase), + Conditions: Conditions{ + ContainerRecommendationFromVPA: containerRecommendationFromVPAConversionFromV1Beta1(src.Status.Conditions.ContainerRecommendationFromVPA), + }, + Targets: TargetsStatus{ + HorizontalPodAutoscaler: src.Status.Targets.HorizontalPodAutoscaler, + Deployment: src.Status.Targets.Deployment, + VerticalPodAutoscalers: verticalPodAutoscalersConversionFromV1Beta1(src.Status.Targets.VerticalPodAutoscalers), + }, + Recommendations: Recommendations{ + Horizontal: &HorizontalRecommendations{ + TargetUtilizations: hPATargetUtilizationRecommendationPerContainerConversionFromV1Beta1(src.Status.Recommendations.Horizontal.TargetUtilizations), + MaxReplicas: replicasRecommendationConversionFromV1Beta1(src.Status.Recommendations.Horizontal.MaxReplicas), + MinReplicas: replicasRecommendationConversionFromV1Beta1(src.Status.Recommendations.Horizontal.MinReplicas), + }, + Vertical: &VerticalRecommendations{ + ContainerResourceRecommendation: containerResourceRecommendationConversionFromV1Beta1(src.Status.Recommendations.Vertical.ContainerResourceRecommendation), + }, + }, + } + return nil +} + +func verticalPodAutoscalersConversionFromV1Beta1(vpas []v1beta1.TargetStatusVerticalPodAutoscaler) []TargetStatusVerticalPodAutoscaler { + converted := make([]TargetStatusVerticalPodAutoscaler, 0, len(vpas)) + for _, vpa := range vpas { + converted = append(converted, TargetStatusVerticalPodAutoscaler{ + Name: vpa.Name, + Role: VerticalPodAutoscalerRole(vpa.Role), + }) + } + return converted +} + +func verticalPodAutoscalersConversionToV1Beta1(vpas []TargetStatusVerticalPodAutoscaler) []v1beta1.TargetStatusVerticalPodAutoscaler { + converted := make([]v1beta1.TargetStatusVerticalPodAutoscaler, 0, len(vpas)) + for _, vpa := range vpas { + converted = append(converted, v1beta1.TargetStatusVerticalPodAutoscaler{ + Name: vpa.Name, + Role: v1beta1.VerticalPodAutoscalerRole(vpa.Role), + }) + } + return converted +} + +func containerResourceRecommendationConversionFromV1Beta1(resources []v1beta1.RecommendedContainerResources) []RecommendedContainerResources { + converted := make([]RecommendedContainerResources, 0, len(resources)) + for _, resource := range resources { + converted = append(converted, RecommendedContainerResources{ + ContainerName: resource.ContainerName, + RecommendedResource: resource.RecommendedResource, + }) + } + return converted +} + +func containerResourceRecommendationConversionToV1Beta1(resources []RecommendedContainerResources) []v1beta1.RecommendedContainerResources { + converted := make([]v1beta1.RecommendedContainerResources, 0, len(resources)) + for _, resource := range resources { + converted = append(converted, v1beta1.RecommendedContainerResources{ + ContainerName: resource.ContainerName, + RecommendedResource: resource.RecommendedResource, + }) + } + return converted +} + +func replicasRecommendationConversionFromV1Beta1(recommendations []v1beta1.ReplicasRecommendation) []ReplicasRecommendation { + converted := make([]ReplicasRecommendation, 0, len(recommendations)) + for _, recommendation := range recommendations { + converted = append(converted, ReplicasRecommendation{ + From: recommendation.From, + To: recommendation.To, + WeekDay: recommendation.WeekDay, + TimeZone: recommendation.TimeZone, + Value: recommendation.Value, + UpdatedAt: recommendation.UpdatedAt, + }) + } + return converted +} + +func replicasRecommendationConversionToV1Beta1(recommendations []ReplicasRecommendation) []v1beta1.ReplicasRecommendation { + converted := make([]v1beta1.ReplicasRecommendation, 0, len(recommendations)) + for _, recommendation := range recommendations { + converted = append(converted, v1beta1.ReplicasRecommendation{ + From: recommendation.From, + To: recommendation.To, + WeekDay: recommendation.WeekDay, + TimeZone: recommendation.TimeZone, + Value: recommendation.Value, + UpdatedAt: recommendation.UpdatedAt, + }) + } + return converted +} + +func hPATargetUtilizationRecommendationPerContainerConversionFromV1Beta1(recommendations []v1beta1.HPATargetUtilizationRecommendationPerContainer) []HPATargetUtilizationRecommendationPerContainer { + converted := make([]HPATargetUtilizationRecommendationPerContainer, 0, len(recommendations)) + for _, recommendation := range recommendations { + converted = append(converted, HPATargetUtilizationRecommendationPerContainer{ + ContainerName: recommendation.ContainerName, + TargetUtilization: recommendation.TargetUtilization, + }) + } + return converted +} + +func hPATargetUtilizationRecommendationPerContainerConversionToV1Beta1(recommendations []HPATargetUtilizationRecommendationPerContainer) []v1beta1.HPATargetUtilizationRecommendationPerContainer { + converted := make([]v1beta1.HPATargetUtilizationRecommendationPerContainer, 0, len(recommendations)) + for _, recommendation := range recommendations { + converted = append(converted, v1beta1.HPATargetUtilizationRecommendationPerContainer{ + ContainerName: recommendation.ContainerName, + TargetUtilization: recommendation.TargetUtilization, + }) + } + return converted +} + +func containerRecommendationFromVPAConversionFromV1Beta1(conditions []v1beta1.ContainerRecommendationFromVPA) []ContainerRecommendationFromVPA { + converted := make([]ContainerRecommendationFromVPA, 0, len(conditions)) + for _, condition := range conditions { + converted = append(converted, ContainerRecommendationFromVPA{ + ContainerName: condition.ContainerName, + MaxRecommendation: resourceQuantityMapConversionFromV1Beta1(condition.MaxRecommendation), + Recommendation: resourceQuantityMapConversionFromV1Beta1(condition.Recommendation), + }) + } + return converted +} + +func containerRecommendationFromVPAConversionToV1Beta1(conditions []ContainerRecommendationFromVPA) []v1beta1.ContainerRecommendationFromVPA { + converted := make([]v1beta1.ContainerRecommendationFromVPA, 0, len(conditions)) + for _, condition := range conditions { + converted = append(converted, v1beta1.ContainerRecommendationFromVPA{ + ContainerName: condition.ContainerName, + MaxRecommendation: resourceQuantityMapConversionToV1Beta1(condition.MaxRecommendation), + Recommendation: resourceQuantityMapConversionToV1Beta1(condition.Recommendation), + }) + } + return converted +} + +func resourceQuantityMapConversionFromV1Beta1(resources map[v1.ResourceName]v1beta1.ResourceQuantity) map[v1.ResourceName]ResourceQuantity { + converted := make(map[v1.ResourceName]ResourceQuantity, len(resources)) + for k, v := range resources { + converted[k] = ResourceQuantity(v) + } + return converted +} + +func resourceQuantityMapConversionToV1Beta1(resources map[v1.ResourceName]ResourceQuantity) map[v1.ResourceName]v1beta1.ResourceQuantity { + converted := make(map[v1.ResourceName]v1beta1.ResourceQuantity, len(resources)) + for k, v := range resources { + converted[k] = v1beta1.ResourceQuantity(v) + } + return converted +} + +func containerResourcePolicyConversionFromV1Beta1(policies []v1beta1.ContainerResourcePolicy) []ContainerResourcePolicy { + converted := make([]ContainerResourcePolicy, 0, len(policies)) + for _, policy := range policies { + converted = append(converted, ContainerResourcePolicy{ + ContainerName: policy.ContainerName, + MinAllocatedResources: policy.MinAllocatedResources, + AutoscalingPolicy: autoscalingPolicyConversionFromV1Beta1(policy.AutoscalingPolicy), + }) + } + return converted +} + +func containerResourcePolicyConversionToV1Beta1(policies []ContainerResourcePolicy) []v1beta1.ContainerResourcePolicy { + converted := make([]v1beta1.ContainerResourcePolicy, 0, len(policies)) + for _, policy := range policies { + converted = append(converted, v1beta1.ContainerResourcePolicy{ + ContainerName: policy.ContainerName, + MinAllocatedResources: policy.MinAllocatedResources, + AutoscalingPolicy: autoscalingPolicyConversionToV1Beta1(policy.AutoscalingPolicy), + }) + } + return converted +} + +func autoscalingPolicyConversionFromV1Beta1(policies map[v1.ResourceName]v1beta1.AutoscalingType) map[v1.ResourceName]AutoscalingType { + converted := make(map[v1.ResourceName]AutoscalingType, len(policies)) + for k, v := range policies { + converted[k] = AutoscalingType(v) + } + return converted +} + +func autoscalingPolicyConversionToV1Beta1(policies map[v1.ResourceName]AutoscalingType) map[v1.ResourceName]v1beta1.AutoscalingType { + converted := make(map[v1.ResourceName]v1beta1.AutoscalingType, len(policies)) + for k, v := range policies { + converted[k] = v1beta1.AutoscalingType(v) + } + return converted +} diff --git a/api/v1alpha1/tortoise_webhook.go b/api/v1alpha1/tortoise_webhook.go index 0dbc46da..cb4f8bdb 100644 --- a/api/v1alpha1/tortoise_webhook.go +++ b/api/v1alpha1/tortoise_webhook.go @@ -26,188 +26,13 @@ SOFTWARE. package v1alpha1 import ( - "context" - "errors" - "fmt" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -// log is for logging in this package. -var tortoiselog = logf.Log.WithName("tortoise-resource") -var ClientService *service - func (r *Tortoise) SetupWebhookWithManager(mgr ctrl.Manager) error { - ClientService = newService(mgr.GetClient()) return ctrl.NewWebhookManagedBy(mgr). For(r). Complete() } // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -//+kubebuilder:webhook:path=/mutate-autoscaling-mercari-com-v1alpha1-tortoise,mutating=true,failurePolicy=fail,sideEffects=None,groups=autoscaling.mercari.com,resources=tortoises,verbs=create;update,versions=v1alpha1,name=mtortoise.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &Tortoise{} - -const TortoiseDefaultHPANamePrefix = "tortoise-hpa-" - -func TortoiseDefaultHPAName(tortoiseName string) string { - return TortoiseDefaultHPANamePrefix + tortoiseName -} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Tortoise) Default() { - tortoiselog.Info("default", "name", r.Name) - - if r.Spec.UpdateMode == "" { - r.Spec.UpdateMode = UpdateModeOff - } - if r.Spec.DeletionPolicy == "" { - r.Spec.DeletionPolicy = DeletionPolicyDeleteAll - } - - d, err := ClientService.GetDeploymentOnTortoise(context.Background(), r) - if err != nil { - tortoiselog.Error(err, "failed to get deployment") - return - } - - if len(d.Spec.Template.Spec.Containers) != len(r.Spec.ResourcePolicy) { - for _, c := range d.Spec.Template.Spec.Containers { - policyExist := false - for _, p := range r.Spec.ResourcePolicy { - if c.Name == p.ContainerName { - policyExist = true - break - } - } - if !policyExist { - r.Spec.ResourcePolicy = append(r.Spec.ResourcePolicy, ContainerResourcePolicy{ - ContainerName: c.Name, - AutoscalingPolicy: map[v1.ResourceName]AutoscalingType{}, - }) - } - } - } - - for i := range r.Spec.ResourcePolicy { - _, ok := r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceCPU] - if !ok { - r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceCPU] = AutoscalingTypeHorizontal - } - _, ok = r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceMemory] - if !ok { - r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceMemory] = AutoscalingTypeVertical - } - } -} - -//+kubebuilder:webhook:path=/validate-autoscaling-mercari-com-v1alpha1-tortoise,mutating=false,failurePolicy=fail,sideEffects=None,groups=autoscaling.mercari.com,resources=tortoises,verbs=create;update,versions=v1alpha1,name=vtortoise.kb.io,admissionReviewVersions=v1 - -var _ webhook.Validator = &Tortoise{} - -func validateTortoise(t *Tortoise) error { - fieldPath := field.NewPath("spec") - - if t.Spec.TargetRefs.DeploymentName == "" { - return fmt.Errorf("%s: shouldn't be empty", fieldPath.Child("targetRefs", "deploymentName")) - } - - for _, p := range t.Spec.ResourcePolicy { - for _, ap := range p.AutoscalingPolicy { - if ap == AutoscalingTypeHorizontal { - return nil - } - } - } - - if t.Spec.UpdateMode == UpdateModeEmergency && - t.Status.TortoisePhase != TortoisePhaseWorking && t.Status.TortoisePhase != TortoisePhaseEmergency && t.Status.TortoisePhase != TortoisePhaseBackToNormal { - return fmt.Errorf("%s: emergency mode is only available for tortoises with Running phase", fieldPath.Child("updateMode")) - } - - return fmt.Errorf("%s: at least one policy should be Horizontal", fieldPath.Child("resourcePolicy", "autoscalingPolicy")) -} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Tortoise) ValidateCreate() (admission.Warnings, error) { - ctx := context.Background() - tortoiselog.Info("validate create", "name", r.Name) - if err := validateTortoise(r); err != nil { - return nil, err - } - - fieldPath := field.NewPath("spec") - - d, err := ClientService.GetDeploymentOnTortoise(ctx, r) - if err != nil { - return nil, fmt.Errorf("failed to get the deployment defined in %s: %w", fieldPath.Child("targetRefs", "deploymentName"), err) - } - - containers := sets.NewString() - for _, c := range d.Spec.Template.Spec.Containers { - containers.Insert(c.Name) - } - - policies := sets.NewString() - for _, p := range r.Spec.ResourcePolicy { - policies.Insert(p.ContainerName) - } - - noPolicyContainers := containers.Difference(policies) - if noPolicyContainers.Len() != 0 { - return nil, fmt.Errorf("%s: tortoise should have the policies for all containers defined in the deployment, but, it doesn't have the policy for the container(s) %v", fieldPath.Child("resourcePolicy"), noPolicyContainers) - } - uselessPolicies := policies.Difference(containers) - if uselessPolicies.Len() != 0 { - return nil, fmt.Errorf("%s: tortoise should not have the policies for the container(s) which isn't defined in the deployment, but, it have the policy for the container(s) %v", fieldPath.Child("resourcePolicy"), uselessPolicies) - } - - return nil, validateTortoise(r) -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Tortoise) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - tortoiselog.Info("validate update", "name", r.Name) - if err := validateTortoise(r); err != nil { - return nil, err - } - - oldTortoise, ok := old.(*Tortoise) - if !ok { - return nil, errors.New("failed to parse old object to Tortoise") - } - - fieldPath := field.NewPath("spec") - if r.Spec.TargetRefs.DeploymentName != oldTortoise.Spec.TargetRefs.DeploymentName { - return nil, fmt.Errorf("%s: immutable field get changed", fieldPath.Child("targetRefs", "deploymentNames")) - } - if r.Spec.TargetRefs.HorizontalPodAutoscalerName != nil { - if oldTortoise.Spec.TargetRefs.HorizontalPodAutoscalerName == nil || *oldTortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != *r.Spec.TargetRefs.HorizontalPodAutoscalerName { - // removed or updated. - return nil, fmt.Errorf("%s: immutable field get changed", fieldPath.Child("targetRefs", "horizontalPodAutoscalerName")) - } - } else { - if oldTortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != nil { - // newly specified. - return nil, fmt.Errorf("%s: immutable field get changed", fieldPath.Child("targetRefs", "horizontalPodAutoscalerName")) - } - } - - return nil, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -func (r *Tortoise) ValidateDelete() (admission.Warnings, error) { - tortoiselog.Info("validate delete", "name", r.Name) - return nil, nil -} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index cf3dce84..d59c0d3d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -31,7 +31,7 @@ package v1alpha1 import ( "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go new file mode 100644 index 00000000..5372540e --- /dev/null +++ b/api/v1beta1/groupversion_info.go @@ -0,0 +1,45 @@ +/* +MIT License + +Copyright (c) 2023 mercari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// Package v1beta1 contains API Schema definitions for the autoscaling v1beta1 API group +// +kubebuilder:object:generate=true +// +groupName=autoscaling.mercari.com +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "autoscaling.mercari.com", Version: "v1beta1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1beta1/service.go b/api/v1beta1/service.go new file mode 100644 index 00000000..264ed138 --- /dev/null +++ b/api/v1beta1/service.go @@ -0,0 +1,30 @@ +package v1beta1 + +import ( + "context" + "fmt" + + v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type service struct { + c client.Client +} + +func newService(c client.Client) *service { + return &service{c: c} +} + +func (c *service) GetDeploymentOnTortoise(ctx context.Context, tortoise *Tortoise) (*v1.Deployment, error) { + if tortoise.Spec.TargetRefs.ScaleTargetRef.Kind != "Deployment" { + return nil, fmt.Errorf("target kind is not deployment: %s", tortoise.Spec.TargetRefs.ScaleTargetRef.Kind) + } + + d := &v1.Deployment{} + if err := c.c.Get(ctx, types.NamespacedName{Namespace: tortoise.Namespace, Name: tortoise.Spec.TargetRefs.ScaleTargetRef.Name}, d); err != nil { + return nil, fmt.Errorf("failed to get deployment on tortoise: %w", err) + } + return d, nil +} diff --git a/api/v1beta1/tortoise_conversion.go b/api/v1beta1/tortoise_conversion.go new file mode 100644 index 00000000..7c2f8c0a --- /dev/null +++ b/api/v1beta1/tortoise_conversion.go @@ -0,0 +1,29 @@ +/* +MIT License + +Copyright (c) 2023 mercari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +package v1beta1 + +// Hub marks this type as a conversion hub. +func (*Tortoise) Hub() {} diff --git a/api/v1beta1/tortoise_types.go b/api/v1beta1/tortoise_types.go new file mode 100644 index 00000000..df845693 --- /dev/null +++ b/api/v1beta1/tortoise_types.go @@ -0,0 +1,297 @@ +/* +MIT License + +Copyright (c) 2023 mercari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +package v1beta1 + +import ( + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster +// Important: Run "make" to regenerate code after modifying this file + +// TortoiseSpec defines the desired state of Tortoise +type TortoiseSpec struct { + // TargetRefs has reference to involved resources. + TargetRefs TargetRefs `json:"targetRefs" protobuf:"bytes,1,name=targetRefs"` + // UpdateMode is how tortoise update resources. + // If "Off", tortoise generates the recommendations in .Status, but doesn't apply it actually. + // If "Auto", tortoise generates the recommendations in .Status, and apply it to resources. + // If "Emergency", tortoise generates the recommendations in .Status as usual, but increase replica number high enough value. + // "Emergency" is useful when something unexpected happens in workloads, and you want to scale up the workload with high enough resources. + // See https://github.com/mercari/tortoise/blob/main/docs/emergency.md to know more about emergency mode. + // + // "Off" is the default value. + // +optional + UpdateMode UpdateMode `json:"updateMode,omitempty" protobuf:"bytes,2,opt,name=updateMode"` + // ResourcePolicy contains the policy how each resource is updated. + // +optional + ResourcePolicy []ContainerResourcePolicy `json:"resourcePolicy,omitempty" protobuf:"bytes,3,opt,name=resourcePolicy"` + // DeletionPolicy is the policy how the controller deletes associated HPA and VPAs when tortoise is removed. + // If "DeleteAll", tortoise deletes all associated HPA and VPAs, created by tortoise. If the associated HPA is not created by tortoise, + // which is associated by spec.targetRefs.horizontalPodAutoscalerName, tortoise never delete the HPA. + // If "NoDelete", tortoise doesn't delete any associated HPA and VPAs. + // + // "DeleteAll" is the default value. + // +optional + DeletionPolicy DeletionPolicy `json:"deletionPolicy,omitempty" protobuf:"bytes,4,opt,name=deletionPolicy"` +} + +type ContainerResourcePolicy struct { + // ContainerName is the name of target container. + ContainerName string `json:"containerName" protobuf:"bytes,1,name=containerName"` + // MinAllocatedResources is the minimum amount of resources which is given to the container. + // Tortoise never set the resources request on the container than MinAllocatedResources. + // + // If empty, tortoise may reduce the resource request to the value which is suggested from VPA. + // Leaving this field empty is basically safe, but you may consider using MinAllocatedResources when maybe your application will consume resources more than usual, + // given the VPA suggests values based on the historical resource usage. + // For example, your application will soon have new feature which leads to increase in the resource usage, + // it is expected that your application will soon get more requests than usual, etc. + // +optional + MinAllocatedResources v1.ResourceList `json:"minAllocatedResources,omitempty" protobuf:"bytes,2,opt,name=minAllocatedResources"` + // AutoscalingPolicy specifies how each resource is scaled. + // If "Horizontal", the resource is horizontally scaled. + // If "Vertical", the resource is vertically scaled. + // Now, at least one container in Pod should be Horizontal. + // + // The default value is "Horizontal" for cpu, and "Vertical" for memory. + // +optional + AutoscalingPolicy map[v1.ResourceName]AutoscalingType `json:"autoscalingPolicy,omitempty" protobuf:"bytes,3,opt,name=autoscalingPolicy"` +} + +// +kubebuilder:validation:Enum=DeleteAll;NoDelete +type DeletionPolicy string + +const ( + DeletionPolicyDeleteAll DeletionPolicy = "DeleteAll" + DeletionPolicyNoDelete DeletionPolicy = "NoDelete" +) + +// +kubebuilder:validation:Enum=Off;Auto;Emergency +type UpdateMode string + +const ( + UpdateModeOff UpdateMode = "Off" + UpdateModeEmergency UpdateMode = "Emergency" + AutoUpdateMode UpdateMode = "Auto" +) + +// +kubebuilder:validation:Enum=Horizontal;Vertical +type AutoscalingType string + +const ( + AutoscalingTypeHorizontal AutoscalingType = "Horizontal" + AutoscalingTypeVertical AutoscalingType = "Vertical" +) + +type TargetRefs struct { + // ScaleTargetRef is the target of scaling. + // It should be the same as the target of HPA. + ScaleTargetRef CrossVersionObjectReference `json:"scaleTargetRef" protobuf:"bytes,1,name=scaleTargetRef"` + // HorizontalPodAutoscalerName is the name of the target HPA. + // The target of this HPA should be the same as the DeploymentName above. + // The target HPA should have the ContainerResource type metric or the external metric refers to the container resource utilization. + // Please check out the document for more detail: https://github.com/mercari/tortoise/blob/master/docs/horizontal.md#supported-metrics-in-hpa + // + // You can specify either of existing HPA only. + // This is an optional field, and if you don't specify this field, tortoise will create a new default HPA named `tortoise-hpa-{tortoise name}`. + // +optional + HorizontalPodAutoscalerName *string `json:"horizontalPodAutoscalerName,omitempty" protobuf:"bytes,2,opt,name=horizontalPodAutoscalerName"` +} + +// CrossVersionObjectReference contains enough information toet identify the referred resource. +type CrossVersionObjectReference struct { + // kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` + + // name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Name string `json:"name" protobuf:"bytes,2,opt,name=name"` + + // apiVersion is the API version of the referent + // +optional + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,3,opt,name=apiVersion"` +} + +// TortoiseStatus defines the observed state of Tortoise +type TortoiseStatus struct { + TortoisePhase TortoisePhase `json:"tortoisePhase" protobuf:"bytes,1,name=tortoisePhase"` + Conditions Conditions `json:"conditions" protobuf:"bytes,2,name=conditions"` + Recommendations Recommendations `json:"recommendations" protobuf:"bytes,3,name=recommendations"` + Targets TargetsStatus `json:"targets" protobuf:"bytes,4,name=targets"` +} + +type TargetsStatus struct { + HorizontalPodAutoscaler string `json:"horizontalPodAutoscaler" protobuf:"bytes,1,name=horizontalPodAutoscaler"` + Deployment string `json:"deployment" protobuf:"bytes,2,name=deployment"` + VerticalPodAutoscalers []TargetStatusVerticalPodAutoscaler `json:"verticalPodAutoscalers" protobuf:"bytes,3,name=verticalPodAutoscalers"` +} + +type TargetStatusVerticalPodAutoscaler struct { + Name string `json:"name" protobuf:"bytes,1,name=name"` + Role VerticalPodAutoscalerRole `json:"role" protobuf:"bytes,2,name=role"` +} + +// +kubebuilder:validation:Enum=Updater;Monitor +type VerticalPodAutoscalerRole string + +const ( + VerticalPodAutoscalerRoleUpdater = "Updater" + VerticalPodAutoscalerRoleMonitor = "Monitor" +) + +type Recommendations struct { + // +optional + Horizontal HorizontalRecommendations `json:"horizontal,omitempty" protobuf:"bytes,1,opt,name=horizontal"` + // +optional + Vertical VerticalRecommendations `json:"vertical,omitempty" protobuf:"bytes,2,opt,name=vertical"` +} + +type VerticalRecommendations struct { + // +optional + ContainerResourceRecommendation []RecommendedContainerResources `json:"containerResourceRecommendation" protobuf:"bytes,1,name=containerResourceRecommendation"` +} + +type RecommendedContainerResources struct { + // ContainerName is the name of target container. + ContainerName string `json:"containerName" protobuf:"bytes,1,name=containerName"` + // RecommendedResource is the recommendation calculated by the tortoise. + // If AutoscalingPolicy is vertical, it's the same value as the VPA suggests. + // If AutoscalingPolicy is horizontal, it's basically the same value as the current resource request. + // But, when the number of replicas are too small or too large, + // tortoise may try to increase/decrease the amount of resources given to the container, + // so that the number of replicas won't be very small or very large. + RecommendedResource v1.ResourceList `json:"RecommendedResource" protobuf:"bytes,2,name=containerName"` +} + +type HorizontalRecommendations struct { + // +optional + TargetUtilizations []HPATargetUtilizationRecommendationPerContainer `json:"targetUtilizations,omitempty" protobuf:"bytes,1,opt,name=targetUtilizations"` + // MaxReplicas has the recommendation of maxReplicas. + // It contains the recommendations for each time slot. + // +optional + MaxReplicas []ReplicasRecommendation `json:"maxReplicas,omitempty" protobuf:"bytes,2,opt,name=maxReplicas"` + // MinReplicas has the recommendation of minReplicas. + // It contains the recommendations for each time slot. + // +optional + MinReplicas []ReplicasRecommendation `json:"minReplicas,omitempty" protobuf:"bytes,3,opt,name=minReplicas"` +} + +type ReplicasRecommendation struct { + // From represented in hour. + From int `json:"from" protobuf:"variant,1,name=from"` + // To represented in hour. + To int `json:"to" protobuf:"variant,2,name=to"` + // WeekDay is the day of the week. + // If empty, it means it applies to all days of the week. + WeekDay *string `json:"weekday,omitempty" protobuf:"bytes,3,opt,name=weekday"` + TimeZone string `json:"timezone" protobuf:"bytes,4,name=timezone"` + // Value is the recommendation value. + // It's calculated every reconciliation, + // and updated if the calculated recommendation value is more than the current recommendation value on tortoise. + Value int32 `json:"value" protobuf:"variant,5,name=value"` + // +optional + UpdatedAt metav1.Time `json:"updatedAt,omitempty" protobuf:"bytes,6,opt,name=updatedAt"` +} + +type TortoisePhase string + +const ( + // TortoisePhaseInitializing means tortoise is just created and initializing some components (HPA and VPAs), + // and wait for those components to be ready. + TortoisePhaseInitializing TortoisePhase = "Initializing" + // TortoisePhaseGatheringData means tortoise is now gathering data and cannot make the accurate recommendations. + TortoisePhaseGatheringData TortoisePhase = "GatheringData" + // TortoisePhaseWorking means tortoise is making the recommendations, + // and applying the recommendation values. + TortoisePhaseWorking TortoisePhase = "Working" + // TortoisePhaseEmergency means tortoise is in the emergency mode. + TortoisePhaseEmergency TortoisePhase = "Emergency" + // TortoisePhaseBackToNormal means tortoise was in the emergency mode, and now it's coming back to the normal operation. + // During TortoisePhaseBackToNormal, the number of replicas of workloads are gradually reduced to the usual value. + TortoisePhaseBackToNormal TortoisePhase = "BackToNormal" +) + +type HPATargetUtilizationRecommendationPerContainer struct { + // ContainerName is the name of target container. + ContainerName string `json:"containerName" protobuf:"bytes,1,name=containerName"` + // TargetUtilization is the recommendation of targetUtilization of HPA. + TargetUtilization map[v1.ResourceName]int32 `json:"targetUtilization" protobuf:"bytes,2,name=targetUtilization"` +} + +type Conditions struct { + // +optional + ContainerRecommendationFromVPA []ContainerRecommendationFromVPA `json:"containerRecommendationFromVPA,omitempty" protobuf:"bytes,1,opt,name=containerRecommendationFromVPA"` +} + +type ContainerRecommendationFromVPA struct { + // ContainerName is the name of target container. + ContainerName string `json:"containerName" protobuf:"bytes,1,name=containerName"` + // MaxRecommendation is the max recommendation value from VPA among certain period (1 week). + // Tortoise generates all recommendation based on this MaxRecommendation. + MaxRecommendation map[v1.ResourceName]ResourceQuantity `json:"maxRecommendation" protobuf:"bytes,2,name=maxRecommendation"` + // Recommendation is the latest recommendation value from VPA. + Recommendation map[v1.ResourceName]ResourceQuantity `json:"recommendation" protobuf:"bytes,3,name=recommendation"` +} + +type ResourceQuantity struct { + // +optional + Quantity resource.Quantity `json:"quantity,omitempty" protobuf:"bytes,1,opt,name=quantity"` + // +optional + UpdatedAt metav1.Time `json:"updatedAt,omitempty" protobuf:"bytes,2,opt,name=updatedAt"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:storageversion +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="MODE",type="string",JSONPath=".spec.updateMode" +//+kubebuilder:printcolumn:name="PHASE",type="string",JSONPath=".status.tortoisePhase" + +// Tortoise is the Schema for the tortoises API +type Tortoise struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TortoiseSpec `json:"spec,omitempty"` + Status TortoiseStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TortoiseList contains a list of Tortoise +type TortoiseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Tortoise `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Tortoise{}, &TortoiseList{}) +} diff --git a/api/v1beta1/tortoise_webhook.go b/api/v1beta1/tortoise_webhook.go new file mode 100644 index 00000000..51934404 --- /dev/null +++ b/api/v1beta1/tortoise_webhook.go @@ -0,0 +1,227 @@ +/* +MIT License + +Copyright (c) 2023 mercari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +package v1beta1 + +import ( + "context" + "errors" + "fmt" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var tortoiselog = logf.Log.WithName("tortoise-resource") +var ClientService *service + +func (r *Tortoise) SetupWebhookWithManager(mgr ctrl.Manager) error { + ClientService = newService(mgr.GetClient()) + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +//+kubebuilder:webhook:path=/mutate-autoscaling-mercari-com-v1beta1-tortoise,mutating=true,failurePolicy=fail,sideEffects=None,groups=autoscaling.mercari.com,resources=tortoises,verbs=create;update,versions=v1beta1,name=mtortoise.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &Tortoise{} + +const TortoiseDefaultHPANamePrefix = "tortoise-hpa-" + +func TortoiseDefaultHPAName(tortoiseName string) string { + return TortoiseDefaultHPANamePrefix + tortoiseName +} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *Tortoise) Default() { + tortoiselog.Info("default", "name", r.Name) + + if r.Spec.UpdateMode == "" { + r.Spec.UpdateMode = UpdateModeOff + } + if r.Spec.DeletionPolicy == "" { + r.Spec.DeletionPolicy = DeletionPolicyDeleteAll + } + + if r.Spec.TargetRefs.ScaleTargetRef.Kind == "Deployment" { + // TODO: do the same validation for other resources. + d, err := ClientService.GetDeploymentOnTortoise(context.Background(), r) + if err != nil { + tortoiselog.Error(err, "failed to get deployment") + return + } + + if len(d.Spec.Template.Spec.Containers) != len(r.Spec.ResourcePolicy) { + for _, c := range d.Spec.Template.Spec.Containers { + policyExist := false + for _, p := range r.Spec.ResourcePolicy { + if c.Name == p.ContainerName { + policyExist = true + break + } + } + if !policyExist { + r.Spec.ResourcePolicy = append(r.Spec.ResourcePolicy, ContainerResourcePolicy{ + ContainerName: c.Name, + AutoscalingPolicy: map[v1.ResourceName]AutoscalingType{}, + }) + } + } + } + } + + for i := range r.Spec.ResourcePolicy { + _, ok := r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceCPU] + if !ok { + r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceCPU] = AutoscalingTypeHorizontal + } + _, ok = r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceMemory] + if !ok { + r.Spec.ResourcePolicy[i].AutoscalingPolicy[v1.ResourceMemory] = AutoscalingTypeVertical + } + } +} + +//+kubebuilder:webhook:path=/validate-autoscaling-mercari-com-v1beta1-tortoise,mutating=false,failurePolicy=fail,sideEffects=None,groups=autoscaling.mercari.com,resources=tortoises,verbs=create;update,versions=v1beta1,name=vtortoise.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &Tortoise{} + +func validateTortoise(t *Tortoise) error { + fieldPath := field.NewPath("spec") + + if t.Spec.TargetRefs.ScaleTargetRef.Name == "" { + return fmt.Errorf("%s: shouldn't be empty", fieldPath.Child("targetRefs", "scaleTargetRef", "name")) + } + if t.Spec.TargetRefs.ScaleTargetRef.Kind == "" { + return fmt.Errorf("%s: shouldn't be empty", fieldPath.Child("targetRefs", "scaleTargetRef", "kind")) + } + + for _, p := range t.Spec.ResourcePolicy { + for _, ap := range p.AutoscalingPolicy { + if ap == AutoscalingTypeHorizontal { + return nil + } + } + } + + if t.Spec.UpdateMode == UpdateModeEmergency && + t.Status.TortoisePhase != TortoisePhaseWorking && t.Status.TortoisePhase != TortoisePhaseEmergency && t.Status.TortoisePhase != TortoisePhaseBackToNormal { + return fmt.Errorf("%s: emergency mode is only available for tortoises with Running phase", fieldPath.Child("updateMode")) + } + + return fmt.Errorf("%s: at least one policy should be Horizontal", fieldPath.Child("resourcePolicy", "autoscalingPolicy")) +} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *Tortoise) ValidateCreate() (admission.Warnings, error) { + ctx := context.Background() + tortoiselog.Info("validate create", "name", r.Name) + fieldPath := field.NewPath("spec") + if r.Spec.TargetRefs.ScaleTargetRef.Kind != "Deployment" { + return nil, fmt.Errorf("only deployment is supported in %s", fieldPath.Child("targetRefs", "scaleTargetRef", "kind")) + } + if err := validateTortoise(r); err != nil { + return nil, err + } + + if r.Spec.TargetRefs.ScaleTargetRef.Kind == "Deployment" { + // TODO: do the same validation for other resources. + d, err := ClientService.GetDeploymentOnTortoise(ctx, r) + if err != nil { + return nil, fmt.Errorf("failed to get the deployment defined in %s: %w", fieldPath.Child("targetRefs", "scaleTargetRef"), err) + } + + containers := sets.NewString() + for _, c := range d.Spec.Template.Spec.Containers { + containers.Insert(c.Name) + } + + policies := sets.NewString() + for _, p := range r.Spec.ResourcePolicy { + policies.Insert(p.ContainerName) + } + + noPolicyContainers := containers.Difference(policies) + if noPolicyContainers.Len() != 0 { + return nil, fmt.Errorf("%s: tortoise should have the policies for all containers defined in the deployment, but, it doesn't have the policy for the container(s) %v", fieldPath.Child("resourcePolicy"), noPolicyContainers) + } + uselessPolicies := policies.Difference(containers) + if uselessPolicies.Len() != 0 { + return nil, fmt.Errorf("%s: tortoise should not have the policies for the container(s) which isn't defined in the deployment, but, it have the policy for the container(s) %v", fieldPath.Child("resourcePolicy"), uselessPolicies) + } + } + + return nil, validateTortoise(r) +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *Tortoise) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + tortoiselog.Info("validate update", "name", r.Name) + if err := validateTortoise(r); err != nil { + return nil, err + } + + oldTortoise, ok := old.(*Tortoise) + if !ok { + return nil, errors.New("failed to parse old object to Tortoise") + } + + fieldPath := field.NewPath("spec") + if r.Spec.TargetRefs.ScaleTargetRef.Name != oldTortoise.Spec.TargetRefs.ScaleTargetRef.Name { + return nil, fmt.Errorf("%s: immutable field get changed", fieldPath.Child("targetRefs", "scaleTargetRef", "name")) + } + if r.Spec.TargetRefs.ScaleTargetRef.Kind != oldTortoise.Spec.TargetRefs.ScaleTargetRef.Kind { + return nil, fmt.Errorf("%s: immutable field get changed", fieldPath.Child("targetRefs", "scaleTargetRef", "kind")) + } + if r.Spec.TargetRefs.HorizontalPodAutoscalerName != nil { + if oldTortoise.Spec.TargetRefs.HorizontalPodAutoscalerName == nil || *oldTortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != *r.Spec.TargetRefs.HorizontalPodAutoscalerName { + // removed or updated. + return nil, fmt.Errorf("%s: immutable field get changed", fieldPath.Child("targetRefs", "horizontalPodAutoscalerName")) + } + } else { + if oldTortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != nil { + // newly specified. + return nil, fmt.Errorf("%s: immutable field get changed", fieldPath.Child("targetRefs", "horizontalPodAutoscalerName")) + } + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +func (r *Tortoise) ValidateDelete() (admission.Warnings, error) { + tortoiselog.Info("validate delete", "name", r.Name) + return nil, nil +} diff --git a/api/v1beta1/webhook_suite_test.go b/api/v1beta1/webhook_suite_test.go new file mode 100644 index 00000000..13a4fea1 --- /dev/null +++ b/api/v1beta1/webhook_suite_test.go @@ -0,0 +1,141 @@ +/* +MIT License + +Copyright (c) 2023 mercari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +package v1beta1 + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + //+kubebuilder:scaffold:imports + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&Tortoise{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:webhook + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000..a71c55bf --- /dev/null +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,443 @@ +//go:build !ignore_autogenerated + +/* +MIT License + +Copyright (c) 2023 mercari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Conditions) DeepCopyInto(out *Conditions) { + *out = *in + if in.ContainerRecommendationFromVPA != nil { + in, out := &in.ContainerRecommendationFromVPA, &out.ContainerRecommendationFromVPA + *out = make([]ContainerRecommendationFromVPA, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. +func (in *Conditions) DeepCopy() *Conditions { + if in == nil { + return nil + } + out := new(Conditions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerRecommendationFromVPA) DeepCopyInto(out *ContainerRecommendationFromVPA) { + *out = *in + if in.MaxRecommendation != nil { + in, out := &in.MaxRecommendation, &out.MaxRecommendation + *out = make(map[v1.ResourceName]ResourceQuantity, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + if in.Recommendation != nil { + in, out := &in.Recommendation, &out.Recommendation + *out = make(map[v1.ResourceName]ResourceQuantity, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerRecommendationFromVPA. +func (in *ContainerRecommendationFromVPA) DeepCopy() *ContainerRecommendationFromVPA { + if in == nil { + return nil + } + out := new(ContainerRecommendationFromVPA) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerResourcePolicy) DeepCopyInto(out *ContainerResourcePolicy) { + *out = *in + if in.MinAllocatedResources != nil { + in, out := &in.MinAllocatedResources, &out.MinAllocatedResources + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.AutoscalingPolicy != nil { + in, out := &in.AutoscalingPolicy, &out.AutoscalingPolicy + *out = make(map[v1.ResourceName]AutoscalingType, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerResourcePolicy. +func (in *ContainerResourcePolicy) DeepCopy() *ContainerResourcePolicy { + if in == nil { + return nil + } + out := new(ContainerResourcePolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CrossVersionObjectReference) DeepCopyInto(out *CrossVersionObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossVersionObjectReference. +func (in *CrossVersionObjectReference) DeepCopy() *CrossVersionObjectReference { + if in == nil { + return nil + } + out := new(CrossVersionObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HPATargetUtilizationRecommendationPerContainer) DeepCopyInto(out *HPATargetUtilizationRecommendationPerContainer) { + *out = *in + if in.TargetUtilization != nil { + in, out := &in.TargetUtilization, &out.TargetUtilization + *out = make(map[v1.ResourceName]int32, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HPATargetUtilizationRecommendationPerContainer. +func (in *HPATargetUtilizationRecommendationPerContainer) DeepCopy() *HPATargetUtilizationRecommendationPerContainer { + if in == nil { + return nil + } + out := new(HPATargetUtilizationRecommendationPerContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HorizontalRecommendations) DeepCopyInto(out *HorizontalRecommendations) { + *out = *in + if in.TargetUtilizations != nil { + in, out := &in.TargetUtilizations, &out.TargetUtilizations + *out = make([]HPATargetUtilizationRecommendationPerContainer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.MaxReplicas != nil { + in, out := &in.MaxReplicas, &out.MaxReplicas + *out = make([]ReplicasRecommendation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.MinReplicas != nil { + in, out := &in.MinReplicas, &out.MinReplicas + *out = make([]ReplicasRecommendation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalRecommendations. +func (in *HorizontalRecommendations) DeepCopy() *HorizontalRecommendations { + if in == nil { + return nil + } + out := new(HorizontalRecommendations) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Recommendations) DeepCopyInto(out *Recommendations) { + *out = *in + in.Horizontal.DeepCopyInto(&out.Horizontal) + in.Vertical.DeepCopyInto(&out.Vertical) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Recommendations. +func (in *Recommendations) DeepCopy() *Recommendations { + if in == nil { + return nil + } + out := new(Recommendations) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RecommendedContainerResources) DeepCopyInto(out *RecommendedContainerResources) { + *out = *in + if in.RecommendedResource != nil { + in, out := &in.RecommendedResource, &out.RecommendedResource + *out = make(v1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecommendedContainerResources. +func (in *RecommendedContainerResources) DeepCopy() *RecommendedContainerResources { + if in == nil { + return nil + } + out := new(RecommendedContainerResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicasRecommendation) DeepCopyInto(out *ReplicasRecommendation) { + *out = *in + if in.WeekDay != nil { + in, out := &in.WeekDay, &out.WeekDay + *out = new(string) + **out = **in + } + in.UpdatedAt.DeepCopyInto(&out.UpdatedAt) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicasRecommendation. +func (in *ReplicasRecommendation) DeepCopy() *ReplicasRecommendation { + if in == nil { + return nil + } + out := new(ReplicasRecommendation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuantity) DeepCopyInto(out *ResourceQuantity) { + *out = *in + out.Quantity = in.Quantity.DeepCopy() + in.UpdatedAt.DeepCopyInto(&out.UpdatedAt) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuantity. +func (in *ResourceQuantity) DeepCopy() *ResourceQuantity { + if in == nil { + return nil + } + out := new(ResourceQuantity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetRefs) DeepCopyInto(out *TargetRefs) { + *out = *in + out.ScaleTargetRef = in.ScaleTargetRef + if in.HorizontalPodAutoscalerName != nil { + in, out := &in.HorizontalPodAutoscalerName, &out.HorizontalPodAutoscalerName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetRefs. +func (in *TargetRefs) DeepCopy() *TargetRefs { + if in == nil { + return nil + } + out := new(TargetRefs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetStatusVerticalPodAutoscaler) DeepCopyInto(out *TargetStatusVerticalPodAutoscaler) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetStatusVerticalPodAutoscaler. +func (in *TargetStatusVerticalPodAutoscaler) DeepCopy() *TargetStatusVerticalPodAutoscaler { + if in == nil { + return nil + } + out := new(TargetStatusVerticalPodAutoscaler) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetsStatus) DeepCopyInto(out *TargetsStatus) { + *out = *in + if in.VerticalPodAutoscalers != nil { + in, out := &in.VerticalPodAutoscalers, &out.VerticalPodAutoscalers + *out = make([]TargetStatusVerticalPodAutoscaler, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetsStatus. +func (in *TargetsStatus) DeepCopy() *TargetsStatus { + if in == nil { + return nil + } + out := new(TargetsStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tortoise) DeepCopyInto(out *Tortoise) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tortoise. +func (in *Tortoise) DeepCopy() *Tortoise { + if in == nil { + return nil + } + out := new(Tortoise) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Tortoise) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TortoiseList) DeepCopyInto(out *TortoiseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Tortoise, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TortoiseList. +func (in *TortoiseList) DeepCopy() *TortoiseList { + if in == nil { + return nil + } + out := new(TortoiseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TortoiseList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TortoiseSpec) DeepCopyInto(out *TortoiseSpec) { + *out = *in + in.TargetRefs.DeepCopyInto(&out.TargetRefs) + if in.ResourcePolicy != nil { + in, out := &in.ResourcePolicy, &out.ResourcePolicy + *out = make([]ContainerResourcePolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TortoiseSpec. +func (in *TortoiseSpec) DeepCopy() *TortoiseSpec { + if in == nil { + return nil + } + out := new(TortoiseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TortoiseStatus) DeepCopyInto(out *TortoiseStatus) { + *out = *in + in.Conditions.DeepCopyInto(&out.Conditions) + in.Recommendations.DeepCopyInto(&out.Recommendations) + in.Targets.DeepCopyInto(&out.Targets) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TortoiseStatus. +func (in *TortoiseStatus) DeepCopy() *TortoiseStatus { + if in == nil { + return nil + } + out := new(TortoiseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VerticalRecommendations) DeepCopyInto(out *VerticalRecommendations) { + *out = *in + if in.ContainerResourceRecommendation != nil { + in, out := &in.ContainerResourceRecommendation, &out.ContainerResourceRecommendation + *out = make([]RecommendedContainerResources, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VerticalRecommendations. +func (in *VerticalRecommendations) DeepCopy() *VerticalRecommendations { + if in == nil { + return nil + } + out := new(VerticalRecommendations) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/autoscaling.mercari.com_tortoises.yaml b/config/crd/bases/autoscaling.mercari.com_tortoises.yaml index dd051e39..83f40740 100644 --- a/config/crd/bases/autoscaling.mercari.com_tortoises.yaml +++ b/config/crd/bases/autoscaling.mercari.com_tortoises.yaml @@ -354,6 +354,357 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.updateMode + name: MODE + type: string + - jsonPath: .status.tortoisePhase + name: PHASE + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Tortoise is the Schema for the tortoises API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TortoiseSpec defines the desired state of Tortoise + properties: + deletionPolicy: + description: "DeletionPolicy is the policy how the controller deletes + associated HPA and VPAs when tortoise is removed. If \"DeleteAll\", + tortoise deletes all associated HPA and VPAs, created by tortoise. + If the associated HPA is not created by tortoise, which is associated + by spec.targetRefs.horizontalPodAutoscalerName, tortoise never delete + the HPA. If \"NoDelete\", tortoise doesn't delete any associated + HPA and VPAs. \n \"DeleteAll\" is the default value." + enum: + - DeleteAll + - NoDelete + type: string + resourcePolicy: + description: ResourcePolicy contains the policy how each resource + is updated. + items: + properties: + autoscalingPolicy: + additionalProperties: + enum: + - Horizontal + - Vertical + type: string + description: "AutoscalingPolicy specifies how each resource + is scaled. If \"Horizontal\", the resource is horizontally + scaled. If \"Vertical\", the resource is vertically scaled. + Now, at least one container in Pod should be Horizontal. \n + The default value is \"Horizontal\" for cpu, and \"Vertical\" + for memory." + type: object + containerName: + description: ContainerName is the name of target container. + type: string + minAllocatedResources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: "MinAllocatedResources is the minimum amount of + resources which is given to the container. Tortoise never + set the resources request on the container than MinAllocatedResources. + \n If empty, tortoise may reduce the resource request to the + value which is suggested from VPA. Leaving this field empty + is basically safe, but you may consider using MinAllocatedResources + when maybe your application will consume resources more than + usual, given the VPA suggests values based on the historical + resource usage. For example, your application will soon have + new feature which leads to increase in the resource usage, + it is expected that your application will soon get more requests + than usual, etc." + type: object + required: + - containerName + type: object + type: array + targetRefs: + description: TargetRefs has reference to involved resources. + properties: + horizontalPodAutoscalerName: + description: "HorizontalPodAutoscalerName is the name of the target + HPA. The target of this HPA should be the same as the DeploymentName + above. The target HPA should have the ContainerResource type + metric or the external metric refers to the container resource + utilization. Please check out the document for more detail: + https://github.com/mercari/tortoise/blob/master/docs/horizontal.md#supported-metrics-in-hpa + \n You can specify either of existing HPA only. This is an optional + field, and if you don't specify this field, tortoise will create + a new default HPA named `tortoise-hpa-{tortoise name}`." + type: string + scaleTargetRef: + description: ScaleTargetRef is the target of scaling. It should + be the same as the target of HPA. + properties: + apiVersion: + description: apiVersion is the API version of the referent + type: string + kind: + description: 'kind is the kind of the referent; More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'name is the name of the referent; More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + required: + - kind + - name + type: object + required: + - scaleTargetRef + type: object + updateMode: + description: "UpdateMode is how tortoise update resources. If \"Off\", + tortoise generates the recommendations in .Status, but doesn't apply + it actually. If \"Auto\", tortoise generates the recommendations + in .Status, and apply it to resources. If \"Emergency\", tortoise + generates the recommendations in .Status as usual, but increase + replica number high enough value. \"Emergency\" is useful when something + unexpected happens in workloads, and you want to scale up the workload + with high enough resources. See https://github.com/mercari/tortoise/blob/main/docs/emergency.md + to know more about emergency mode. \n \"Off\" is the default value." + enum: + - "Off" + - Auto + - Emergency + type: string + required: + - targetRefs + type: object + status: + description: TortoiseStatus defines the observed state of Tortoise + properties: + conditions: + properties: + containerRecommendationFromVPA: + items: + properties: + containerName: + description: ContainerName is the name of target container. + type: string + maxRecommendation: + additionalProperties: + properties: + quantity: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + updatedAt: + format: date-time + type: string + type: object + description: MaxRecommendation is the max recommendation + value from VPA among certain period (1 week). Tortoise + generates all recommendation based on this MaxRecommendation. + type: object + recommendation: + additionalProperties: + properties: + quantity: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + updatedAt: + format: date-time + type: string + type: object + description: Recommendation is the latest recommendation + value from VPA. + type: object + required: + - containerName + - maxRecommendation + - recommendation + type: object + type: array + type: object + recommendations: + properties: + horizontal: + properties: + maxReplicas: + description: MaxReplicas has the recommendation of maxReplicas. + It contains the recommendations for each time slot. + items: + properties: + from: + description: From represented in hour. + type: integer + timezone: + type: string + to: + description: To represented in hour. + type: integer + updatedAt: + format: date-time + type: string + value: + description: Value is the recommendation value. It's + calculated every reconciliation, and updated if the + calculated recommendation value is more than the current + recommendation value on tortoise. + format: int32 + type: integer + weekday: + description: WeekDay is the day of the week. If empty, + it means it applies to all days of the week. + type: string + required: + - from + - timezone + - to + - value + type: object + type: array + minReplicas: + description: MinReplicas has the recommendation of minReplicas. + It contains the recommendations for each time slot. + items: + properties: + from: + description: From represented in hour. + type: integer + timezone: + type: string + to: + description: To represented in hour. + type: integer + updatedAt: + format: date-time + type: string + value: + description: Value is the recommendation value. It's + calculated every reconciliation, and updated if the + calculated recommendation value is more than the current + recommendation value on tortoise. + format: int32 + type: integer + weekday: + description: WeekDay is the day of the week. If empty, + it means it applies to all days of the week. + type: string + required: + - from + - timezone + - to + - value + type: object + type: array + targetUtilizations: + items: + properties: + containerName: + description: ContainerName is the name of target container. + type: string + targetUtilization: + additionalProperties: + format: int32 + type: integer + description: TargetUtilization is the recommendation + of targetUtilization of HPA. + type: object + required: + - containerName + - targetUtilization + type: object + type: array + type: object + vertical: + properties: + containerResourceRecommendation: + items: + properties: + RecommendedResource: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: RecommendedResource is the recommendation + calculated by the tortoise. If AutoscalingPolicy is + vertical, it's the same value as the VPA suggests. + If AutoscalingPolicy is horizontal, it's basically + the same value as the current resource request. But, + when the number of replicas are too small or too large, + tortoise may try to increase/decrease the amount of + resources given to the container, so that the number + of replicas won't be very small or very large. + type: object + containerName: + description: ContainerName is the name of target container. + type: string + required: + - RecommendedResource + - containerName + type: object + type: array + type: object + type: object + targets: + properties: + deployment: + type: string + horizontalPodAutoscaler: + type: string + verticalPodAutoscalers: + items: + properties: + name: + type: string + role: + enum: + - Updater + - Monitor + type: string + required: + - name + - role + type: object + type: array + required: + - deployment + - horizontalPodAutoscaler + - verticalPodAutoscalers + type: object + tortoisePhase: + type: string + required: + - conditions + - recommendations + - targets + - tortoisePhase + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index fa02b797..4f90cb6f 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -8,12 +8,12 @@ resources: patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD -#- patches/webhook_in_tortoises.yaml +- patches/webhook_in_tortoises.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD -#- patches/cainjection_in_tortoises.yaml +- patches/cainjection_in_tortoises.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/samples/autoscaling_v1alpha2_tortoise.yaml b/config/samples/autoscaling_v1alpha2_tortoise.yaml new file mode 100644 index 00000000..ce341249 --- /dev/null +++ b/config/samples/autoscaling_v1alpha2_tortoise.yaml @@ -0,0 +1,12 @@ +apiVersion: autoscaling.mercari.com/v1beta1 +kind: Tortoise +metadata: + labels: + app.kubernetes.io/name: tortoise + app.kubernetes.io/instance: tortoise-sample + app.kubernetes.io/part-of: tortoise + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: tortoise + name: tortoise-sample +spec: + # TODO(user): Add fields here diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 9d3078be..a60f01a0 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -10,14 +10,14 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-autoscaling-mercari-com-v1alpha1-tortoise + path: /mutate-autoscaling-mercari-com-v1beta1-tortoise failurePolicy: Fail name: mtortoise.kb.io rules: - apiGroups: - autoscaling.mercari.com apiVersions: - - v1alpha1 + - v1beta1 operations: - CREATE - UPDATE @@ -56,14 +56,14 @@ webhooks: service: name: webhook-service namespace: system - path: /validate-autoscaling-mercari-com-v1alpha1-tortoise + path: /validate-autoscaling-mercari-com-v1beta1-tortoise failurePolicy: Fail name: vtortoise.kb.io rules: - apiGroups: - autoscaling.mercari.com apiVersions: - - v1alpha1 + - v1beta1 operations: - CREATE - UPDATE diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 0b86bb5f..180df446 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -43,7 +43,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - autoscalingv1alpha1 "github.com/mercari/tortoise/api/v1alpha1" + autoscalingv1beta1 "github.com/mercari/tortoise/api/v1beta1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -82,7 +82,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - err = autoscalingv1alpha1.AddToScheme(scheme) + err = autoscalingv1beta1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = v1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) diff --git a/controllers/tortoise_controller.go b/controllers/tortoise_controller.go index d8f3825f..452f55e6 100644 --- a/controllers/tortoise_controller.go +++ b/controllers/tortoise_controller.go @@ -37,7 +37,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log" - autoscalingv1alpha1 "github.com/mercari/tortoise/api/v1alpha1" + autoscalingv1beta1 "github.com/mercari/tortoise/api/v1beta1" "github.com/mercari/tortoise/pkg/deployment" "github.com/mercari/tortoise/pkg/hpa" "github.com/mercari/tortoise/pkg/recommender" @@ -71,7 +71,7 @@ type TortoiseReconciler struct { func (r *TortoiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { defer func() { if reterr != nil { - r.EventRecorder.Event(&autoscalingv1alpha1.Tortoise{}, "Warning", "ReconcileError", reterr.Error()) + r.EventRecorder.Event(&autoscalingv1beta1.Tortoise{}, "Warning", "ReconcileError", reterr.Error()) } }() @@ -119,7 +119,7 @@ func (r *TortoiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ } tortoise = r.TortoiseService.UpdateTortoisePhase(tortoise, dm) - if tortoise.Status.TortoisePhase == autoscalingv1alpha1.TortoisePhaseInitializing { + if tortoise.Status.TortoisePhase == autoscalingv1beta1.TortoisePhaseInitializing { logger.V(4).Info("initializing tortoise", "tortoise", req.NamespacedName) // need to initialize HPA and VPA. if err := r.initializeVPAAndHPA(ctx, tortoise, dm, now); err != nil { @@ -136,7 +136,7 @@ func (r *TortoiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ } if !ready { logger.V(4).Info("VPA created by tortoise isn't ready yet", "tortoise", req.NamespacedName) - tortoise.Status.TortoisePhase = autoscalingv1alpha1.TortoisePhaseInitializing + tortoise.Status.TortoisePhase = autoscalingv1beta1.TortoisePhaseInitializing _, err = r.TortoiseService.UpdateTortoiseStatus(ctx, tortoise, now) if err != nil { logger.Error(err, "update Tortoise status", "tortoise", req.NamespacedName) @@ -166,7 +166,7 @@ func (r *TortoiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ return ctrl.Result{}, err } - if tortoise.Status.TortoisePhase == autoscalingv1alpha1.TortoisePhaseGatheringData { + if tortoise.Status.TortoisePhase == autoscalingv1beta1.TortoisePhaseGatheringData { logger.V(4).Info("tortoise is GatheringData phase; skip applying the recommendation to HPA or VPAs") return ctrl.Result{RequeueAfter: r.Interval}, nil } @@ -192,8 +192,8 @@ func (r *TortoiseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ return ctrl.Result{RequeueAfter: r.Interval}, nil } -func (r *TortoiseReconciler) deleteVPAAndHPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise, now time.Time) error { - if tortoise.Spec.DeletionPolicy == autoscalingv1alpha1.DeletionPolicyNoDelete { +func (r *TortoiseReconciler) deleteVPAAndHPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise, now time.Time) error { + if tortoise.Spec.DeletionPolicy == autoscalingv1beta1.DeletionPolicyNoDelete { // don't delete anything. return nil } @@ -218,7 +218,7 @@ func (r *TortoiseReconciler) deleteVPAAndHPA(ctx context.Context, tortoise *auto return nil } -func (r *TortoiseReconciler) initializeVPAAndHPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise, dm *v1.Deployment, now time.Time) error { +func (r *TortoiseReconciler) initializeVPAAndHPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise, dm *v1.Deployment, now time.Time) error { // need to initialize HPA and VPA. tortoise, err := r.HpaService.InitializeHPA(ctx, tortoise, dm) if err != nil { @@ -243,6 +243,6 @@ func (r *TortoiseReconciler) initializeVPAAndHPA(ctx context.Context, tortoise * // SetupWithManager sets up the controller with the Manager. func (r *TortoiseReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&autoscalingv1alpha1.Tortoise{}). + For(&autoscalingv1beta1.Tortoise{}). Complete(r) } diff --git a/controllers/tortoise_controller_test.go b/controllers/tortoise_controller_test.go index 5b25542a..2c9ffe1c 100644 --- a/controllers/tortoise_controller_test.go +++ b/controllers/tortoise_controller_test.go @@ -22,7 +22,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/mercari/tortoise/api/v1alpha1" + "github.com/mercari/tortoise/api/v1beta1" "github.com/mercari/tortoise/pkg/deployment" "github.com/mercari/tortoise/pkg/hpa" "github.com/mercari/tortoise/pkg/recommender" @@ -38,7 +38,7 @@ var _ = Describe("Test TortoiseController", func() { ctx := context.Background() var stopFunc func() cleanUp := func() { - err := deleteObj(ctx, &v1alpha1.Tortoise{}, "mercari") + err := deleteObj(ctx, &v1beta1.Tortoise{}, "mercari") if err != nil { Expect(apierrors.IsNotFound(err)).To(Equal(true)) } @@ -103,7 +103,7 @@ var _ = Describe("Test TortoiseController", func() { cleanUp() for { // make sure all resources are deleted - t := &v1alpha1.Tortoise{} + t := &v1beta1.Tortoise{} err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, t) if apierrors.IsNotFound(err) { break @@ -123,20 +123,24 @@ var _ = Describe("Test TortoiseController", func() { tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetRecommendations(v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetRecommendations(v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -144,7 +148,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -154,7 +158,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -166,13 +170,13 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -196,8 +200,8 @@ var _ = Describe("Test TortoiseController", func() { } // create the desired VPA from the created definition. - wantVPA := map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} - wantUpdater := tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleUpdater].DeepCopy() + wantVPA := map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} + wantUpdater := tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleUpdater].DeepCopy() wantUpdater.Status.Recommendation = &autoscalingv1.RecommendedPodResources{} wantUpdater.Status.Recommendation.ContainerRecommendations = []autoscalingv1.RecommendedContainerResources{ { @@ -220,34 +224,38 @@ var _ = Describe("Test TortoiseController", func() { }, }, } - wantVPA[v1alpha1.VerticalPodAutoscalerRoleUpdater] = wantUpdater - wantVPA[v1alpha1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].DeepCopy() + wantVPA[v1beta1.VerticalPodAutoscalerRoleUpdater] = wantUpdater + wantVPA[v1beta1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].DeepCopy() want := resources{ tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetTargetsStatus(v1alpha1.TargetsStatus{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetTargetsStatus(v1beta1.TargetsStatus{ HorizontalPodAutoscaler: "tortoise-hpa-mercari", - VerticalPodAutoscalers: []v1alpha1.TargetStatusVerticalPodAutoscaler{ + VerticalPodAutoscalers: []v1beta1.TargetStatusVerticalPodAutoscaler{ {Name: "tortoise-updater-mercari", Role: "Updater"}, {Name: "tortoise-monitor-mercari", Role: "Monitor"}, }, }). - SetRecommendations(v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + SetRecommendations(v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "app", RecommendedResource: map[corev1.ResourceName]resource.Quantity{ @@ -257,8 +265,8 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -267,7 +275,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -277,7 +285,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -289,9 +297,9 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -299,7 +307,7 @@ var _ = Describe("Test TortoiseController", func() { Quantity: resource.MustParse("3Gi"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -314,7 +322,7 @@ var _ = Describe("Test TortoiseController", func() { } tc.want = want Eventually(func(g Gomega) { - gotTortoise := &v1alpha1.Tortoise{} + gotTortoise := &v1beta1.Tortoise{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, gotTortoise) g.Expect(err).ShouldNot(HaveOccurred()) gotHPA := &v2.HorizontalPodAutoscaler{} @@ -327,9 +335,9 @@ var _ = Describe("Test TortoiseController", func() { err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "tortoise-monitor-mercari"}, gotMonitorVPA) g.Expect(err).ShouldNot(HaveOccurred()) - err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ - v1alpha1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, - v1alpha1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, + err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ + v1beta1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, + v1beta1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, }}) g.Expect(err).ShouldNot(HaveOccurred()) }).Should(Succeed()) @@ -343,21 +351,25 @@ var _ = Describe("Test TortoiseController", func() { tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetUpdateMode(v1alpha1.UpdateModeOff). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetUpdateMode(v1beta1.UpdateModeOff). + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetRecommendations(v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetRecommendations(v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -365,7 +377,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -375,7 +387,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -387,13 +399,13 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -411,37 +423,41 @@ var _ = Describe("Test TortoiseController", func() { wantHPA := tc.before.hpa.DeepCopy() // create the desired VPA from the created definition. // (no difference from "before") - wantVPA := map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} - wantUpdater := tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleUpdater].DeepCopy() - wantVPA[v1alpha1.VerticalPodAutoscalerRoleUpdater] = wantUpdater - wantVPA[v1alpha1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].DeepCopy() + wantVPA := map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} + wantUpdater := tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleUpdater].DeepCopy() + wantVPA[v1beta1.VerticalPodAutoscalerRoleUpdater] = wantUpdater + wantVPA[v1beta1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].DeepCopy() want := resources{ tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetUpdateMode(v1alpha1.UpdateModeOff). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetUpdateMode(v1beta1.UpdateModeOff). + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetTargetsStatus(v1alpha1.TargetsStatus{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetTargetsStatus(v1beta1.TargetsStatus{ HorizontalPodAutoscaler: "tortoise-hpa-mercari", - VerticalPodAutoscalers: []v1alpha1.TargetStatusVerticalPodAutoscaler{ + VerticalPodAutoscalers: []v1beta1.TargetStatusVerticalPodAutoscaler{ {Name: "tortoise-updater-mercari", Role: "Updater"}, {Name: "tortoise-monitor-mercari", Role: "Monitor"}, }, }). - SetRecommendations(v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + SetRecommendations(v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "app", RecommendedResource: map[corev1.ResourceName]resource.Quantity{ @@ -451,8 +467,8 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -461,7 +477,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -471,7 +487,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -483,9 +499,9 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -493,7 +509,7 @@ var _ = Describe("Test TortoiseController", func() { Quantity: resource.MustParse("3Gi"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -508,7 +524,7 @@ var _ = Describe("Test TortoiseController", func() { } tc.want = want Eventually(func(g Gomega) { - gotTortoise := &v1alpha1.Tortoise{} + gotTortoise := &v1beta1.Tortoise{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, gotTortoise) g.Expect(err).ShouldNot(HaveOccurred()) gotHPA := &v2.HorizontalPodAutoscaler{} @@ -521,9 +537,9 @@ var _ = Describe("Test TortoiseController", func() { err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "tortoise-monitor-mercari"}, gotMonitorVPA) g.Expect(err).ShouldNot(HaveOccurred()) - err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ - v1alpha1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, - v1alpha1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, + err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ + v1beta1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, + v1beta1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, }}) g.Expect(err).ShouldNot(HaveOccurred()) }).Should(Succeed()) @@ -535,21 +551,25 @@ var _ = Describe("Test TortoiseController", func() { tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - SetUpdateMode(v1alpha1.UpdateModeEmergency). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + SetUpdateMode(v1beta1.UpdateModeEmergency). + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). // will be updated. - SetRecommendations(v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). // will be updated. + SetRecommendations(v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -557,7 +577,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -567,7 +587,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -579,13 +599,13 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -609,8 +629,8 @@ var _ = Describe("Test TortoiseController", func() { } // create the desired VPA from the created definition. - wantVPA := map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} - wantUpdater := tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleUpdater].DeepCopy() + wantVPA := map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} + wantUpdater := tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleUpdater].DeepCopy() wantUpdater.Status.Recommendation = &autoscalingv1.RecommendedPodResources{} wantUpdater.Status.Recommendation.ContainerRecommendations = []autoscalingv1.RecommendedContainerResources{ { @@ -633,35 +653,39 @@ var _ = Describe("Test TortoiseController", func() { }, }, } - wantVPA[v1alpha1.VerticalPodAutoscalerRoleUpdater] = wantUpdater - wantVPA[v1alpha1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].DeepCopy() + wantVPA[v1beta1.VerticalPodAutoscalerRoleUpdater] = wantUpdater + wantVPA[v1beta1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].DeepCopy() want := resources{ tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - SetUpdateMode(v1alpha1.UpdateModeEmergency). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + SetUpdateMode(v1beta1.UpdateModeEmergency). + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseEmergency). - SetTargetsStatus(v1alpha1.TargetsStatus{ + SetTortoisePhase(v1beta1.TortoisePhaseEmergency). + SetTargetsStatus(v1beta1.TargetsStatus{ HorizontalPodAutoscaler: "tortoise-hpa-mercari", - VerticalPodAutoscalers: []v1alpha1.TargetStatusVerticalPodAutoscaler{ + VerticalPodAutoscalers: []v1beta1.TargetStatusVerticalPodAutoscaler{ {Name: "tortoise-updater-mercari", Role: "Updater"}, {Name: "tortoise-monitor-mercari", Role: "Monitor"}, }, }). - SetRecommendations(v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + SetRecommendations(v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "app", RecommendedResource: map[corev1.ResourceName]resource.Quantity{ @@ -671,8 +695,8 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -681,7 +705,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -691,7 +715,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -703,9 +727,9 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -713,7 +737,7 @@ var _ = Describe("Test TortoiseController", func() { Quantity: resource.MustParse("3Gi"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -728,7 +752,7 @@ var _ = Describe("Test TortoiseController", func() { } tc.want = want Eventually(func(g Gomega) { - gotTortoise := &v1alpha1.Tortoise{} + gotTortoise := &v1beta1.Tortoise{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, gotTortoise) g.Expect(err).ShouldNot(HaveOccurred()) gotHPA := &v2.HorizontalPodAutoscaler{} @@ -741,9 +765,9 @@ var _ = Describe("Test TortoiseController", func() { err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "tortoise-monitor-mercari"}, gotMonitorVPA) g.Expect(err).ShouldNot(HaveOccurred()) - err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ - v1alpha1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, - v1alpha1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, + err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ + v1beta1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, + v1beta1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, }}) g.Expect(err).ShouldNot(HaveOccurred()) }).Should(Succeed()) @@ -757,27 +781,31 @@ var _ = Describe("Test TortoiseController", func() { tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetRecommendations(v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetRecommendations(v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -791,7 +819,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -801,7 +829,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -813,24 +841,24 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "istio-proxy", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -857,8 +885,8 @@ var _ = Describe("Test TortoiseController", func() { } // create the desired VPA from the created definition. - wantVPA := map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} - wantUpdater := tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleUpdater].DeepCopy() + wantVPA := map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} + wantUpdater := tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleUpdater].DeepCopy() wantUpdater.Status.Recommendation = &autoscalingv1.RecommendedPodResources{} wantUpdater.Status.Recommendation.ContainerRecommendations = []autoscalingv1.RecommendedContainerResources{ { @@ -900,41 +928,45 @@ var _ = Describe("Test TortoiseController", func() { }, }, } - wantVPA[v1alpha1.VerticalPodAutoscalerRoleUpdater] = wantUpdater - wantVPA[v1alpha1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].DeepCopy() + wantVPA[v1beta1.VerticalPodAutoscalerRoleUpdater] = wantUpdater + wantVPA[v1beta1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].DeepCopy() want := resources{ tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetTargetsStatus(v1alpha1.TargetsStatus{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetTargetsStatus(v1beta1.TargetsStatus{ HorizontalPodAutoscaler: "tortoise-hpa-mercari", - VerticalPodAutoscalers: []v1alpha1.TargetStatusVerticalPodAutoscaler{ + VerticalPodAutoscalers: []v1beta1.TargetStatusVerticalPodAutoscaler{ {Name: "tortoise-updater-mercari", Role: "Updater"}, {Name: "tortoise-monitor-mercari", Role: "Monitor"}, }, }). - SetRecommendations(v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + SetRecommendations(v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "app", RecommendedResource: map[corev1.ResourceName]resource.Quantity{ @@ -951,8 +983,8 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -968,7 +1000,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -978,7 +1010,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -990,9 +1022,9 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1000,7 +1032,7 @@ var _ = Describe("Test TortoiseController", func() { Quantity: resource.MustParse("3Gi"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1009,9 +1041,9 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "istio-proxy", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1019,7 +1051,7 @@ var _ = Describe("Test TortoiseController", func() { Quantity: resource.MustParse("3Gi"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1034,7 +1066,7 @@ var _ = Describe("Test TortoiseController", func() { } tc.want = want Eventually(func(g Gomega) { - gotTortoise := &v1alpha1.Tortoise{} + gotTortoise := &v1beta1.Tortoise{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, gotTortoise) g.Expect(err).ShouldNot(HaveOccurred()) gotHPA := &v2.HorizontalPodAutoscaler{} @@ -1047,9 +1079,9 @@ var _ = Describe("Test TortoiseController", func() { err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "tortoise-monitor-mercari"}, gotMonitorVPA) g.Expect(err).ShouldNot(HaveOccurred()) - err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ - v1alpha1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, - v1alpha1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, + err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ + v1beta1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, + v1beta1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, }}) g.Expect(err).ShouldNot(HaveOccurred()) }).Should(Succeed()) @@ -1061,28 +1093,32 @@ var _ = Describe("Test TortoiseController", func() { tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetUpdateMode(v1alpha1.UpdateModeEmergency). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetRecommendations(v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + SetUpdateMode(v1beta1.UpdateModeEmergency). + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetRecommendations(v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -1096,7 +1132,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1106,7 +1142,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1118,24 +1154,24 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "istio-proxy", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -1162,8 +1198,8 @@ var _ = Describe("Test TortoiseController", func() { } // create the desired VPA from the created definition. - wantVPA := map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} - wantUpdater := tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleUpdater].DeepCopy() + wantVPA := map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} + wantUpdater := tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleUpdater].DeepCopy() wantUpdater.Status.Recommendation = &autoscalingv1.RecommendedPodResources{} wantUpdater.Status.Recommendation.ContainerRecommendations = []autoscalingv1.RecommendedContainerResources{ { @@ -1205,42 +1241,46 @@ var _ = Describe("Test TortoiseController", func() { }, }, } - wantVPA[v1alpha1.VerticalPodAutoscalerRoleUpdater] = wantUpdater - wantVPA[v1alpha1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].DeepCopy() + wantVPA[v1beta1.VerticalPodAutoscalerRoleUpdater] = wantUpdater + wantVPA[v1beta1.VerticalPodAutoscalerRoleMonitor] = tc.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].DeepCopy() want := resources{ tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetUpdateMode(v1alpha1.UpdateModeEmergency). - SetTortoisePhase(v1alpha1.TortoisePhaseEmergency). - SetTargetsStatus(v1alpha1.TargetsStatus{ + SetUpdateMode(v1beta1.UpdateModeEmergency). + SetTortoisePhase(v1beta1.TortoisePhaseEmergency). + SetTargetsStatus(v1beta1.TargetsStatus{ HorizontalPodAutoscaler: "tortoise-hpa-mercari", - VerticalPodAutoscalers: []v1alpha1.TargetStatusVerticalPodAutoscaler{ + VerticalPodAutoscalers: []v1beta1.TargetStatusVerticalPodAutoscaler{ {Name: "tortoise-updater-mercari", Role: "Updater"}, {Name: "tortoise-monitor-mercari", Role: "Monitor"}, }, }). - SetRecommendations(v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + SetRecommendations(v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "app", RecommendedResource: map[corev1.ResourceName]resource.Quantity{ @@ -1257,8 +1297,8 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -1274,7 +1314,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1284,7 +1324,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1296,9 +1336,9 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1306,7 +1346,7 @@ var _ = Describe("Test TortoiseController", func() { Quantity: resource.MustParse("3Gi"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1315,9 +1355,9 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "istio-proxy", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1325,7 +1365,7 @@ var _ = Describe("Test TortoiseController", func() { Quantity: resource.MustParse("3Gi"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("3"), }, @@ -1340,7 +1380,7 @@ var _ = Describe("Test TortoiseController", func() { } tc.want = want Eventually(func(g Gomega) { - gotTortoise := &v1alpha1.Tortoise{} + gotTortoise := &v1beta1.Tortoise{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, gotTortoise) g.Expect(err).ShouldNot(HaveOccurred()) gotHPA := &v2.HorizontalPodAutoscaler{} @@ -1353,9 +1393,9 @@ var _ = Describe("Test TortoiseController", func() { err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "tortoise-monitor-mercari"}, gotMonitorVPA) g.Expect(err).ShouldNot(HaveOccurred()) - err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ - v1alpha1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, - v1alpha1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, + err = tc.compare(resources{tortoise: gotTortoise, hpa: gotHPA, vpa: map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{ + v1beta1.VerticalPodAutoscalerRoleUpdater: gotUpdaterVPA, + v1beta1.VerticalPodAutoscalerRoleMonitor: gotMonitorVPA, }}) g.Expect(err).ShouldNot(HaveOccurred()) }).Should(Succeed()) @@ -1369,28 +1409,32 @@ var _ = Describe("Test TortoiseController", func() { tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetDeletionPolicy(v1alpha1.DeletionPolicyDeleteAll). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetDeletionPolicy(v1beta1.DeletionPolicyDeleteAll). + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetRecommendations(v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetRecommendations(v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -1404,7 +1448,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1414,7 +1458,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1426,24 +1470,24 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "istio-proxy", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -1463,7 +1507,7 @@ var _ = Describe("Test TortoiseController", func() { Eventually(func(g Gomega) { // make sure all resources are deleted - err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, &v1alpha1.Tortoise{}) + err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, &v1beta1.Tortoise{}) g.Expect(apierrors.IsNotFound(err)).To(Equal(true)) err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "tortoise-hpa-mercari"}, &v2.HorizontalPodAutoscaler{}) g.Expect(apierrors.IsNotFound(err)).To(Equal(true)) @@ -1480,28 +1524,32 @@ var _ = Describe("Test TortoiseController", func() { tortoise: utils.NewTortoiseBuilder(). SetName("mercari"). SetNamespace("default"). - SetDeletionPolicy(v1alpha1.DeletionPolicyNoDelete). - SetTargetRefs(v1alpha1.TargetRefs{ - DeploymentName: "mercari-app", + SetDeletionPolicy(v1beta1.DeletionPolicyNoDelete). + SetTargetRefs(v1beta1.TargetRefs{ + ScaleTargetRef: v1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "mercari-app", + APIVersion: "apps/v1", + }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - AddResourcePolicy(v1alpha1.ContainerResourcePolicy{ + AddResourcePolicy(v1beta1.ContainerResourcePolicy{ ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, }). - SetTortoisePhase(v1alpha1.TortoisePhaseWorking). - SetRecommendations(v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + SetTortoisePhase(v1beta1.TortoisePhaseWorking). + SetRecommendations(v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -1515,7 +1563,7 @@ var _ = Describe("Test TortoiseController", func() { }, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1525,7 +1573,7 @@ var _ = Describe("Test TortoiseController", func() { UpdatedAt: metav1.NewTime(now), }, }, - MinReplicas: []v1alpha1.ReplicasRecommendation{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 24, @@ -1537,24 +1585,24 @@ var _ = Describe("Test TortoiseController", func() { }, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, }). - AddCondition(v1alpha1.ContainerRecommendationFromVPA{ + AddCondition(v1beta1.ContainerRecommendationFromVPA{ ContainerName: "istio-proxy", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -1581,7 +1629,7 @@ var _ = Describe("Test TortoiseController", func() { Expect(err).ShouldNot(HaveOccurred()) err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "tortoise-monitor-mercari"}, &autoscalingv1.VerticalPodAutoscaler{}) Expect(err).ShouldNot(HaveOccurred()) - err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, &v1alpha1.Tortoise{}) + err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "mercari"}, &v1beta1.Tortoise{}) g.Expect(apierrors.IsNotFound(err)).To(Equal(true)) }).Should(Succeed()) }) @@ -1594,14 +1642,14 @@ type testCase struct { } type resources struct { - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise deployment *v1.Deployment hpa *v2.HorizontalPodAutoscaler - vpa map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler + vpa map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler } func (t *testCase) compare(got resources) error { - if d := cmp.Diff(t.want.tortoise, got.tortoise, cmpopts.IgnoreFields(v1alpha1.Tortoise{}, "ObjectMeta"), cmpopts.IgnoreTypes(metav1.Time{})); d != "" { + if d := cmp.Diff(t.want.tortoise, got.tortoise, cmpopts.IgnoreFields(v1beta1.Tortoise{}, "ObjectMeta"), cmpopts.IgnoreTypes(metav1.Time{})); d != "" { return fmt.Errorf("unexpected tortoise: diff = %s", d) } if d := cmp.Diff(t.want.hpa, got.hpa, cmpopts.IgnoreFields(v2.HorizontalPodAutoscaler{}, "ObjectMeta"), cmpopts.IgnoreTypes(metav1.Time{})); d != "" { @@ -1642,7 +1690,7 @@ func (t *testCase) initializeResources(ctx context.Context, k8sClient client.Cli } if t.before.vpa == nil { // create default VPAs. - t.before.vpa = map[v1alpha1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} + t.before.vpa = map[v1beta1.VerticalPodAutoscalerRole]*autoscalingv1.VerticalPodAutoscaler{} VpaClient, err := vpa.New(config, record.NewFakeRecorder(10)) if err != nil { return err @@ -1651,11 +1699,11 @@ func (t *testCase) initializeResources(ctx context.Context, k8sClient client.Cli if err != nil { return err } - t.before.vpa[v1alpha1.VerticalPodAutoscalerRoleUpdater], t.before.tortoise, err = VpaClient.CreateTortoiseUpdaterVPA(ctx, t.before.tortoise) + t.before.vpa[v1beta1.VerticalPodAutoscalerRoleUpdater], t.before.tortoise, err = VpaClient.CreateTortoiseUpdaterVPA(ctx, t.before.tortoise) if err != nil { return err } - t.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor], t.before.tortoise, err = VpaClient.CreateTortoiseMonitorVPA(ctx, t.before.tortoise) + t.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor], t.before.tortoise, err = VpaClient.CreateTortoiseMonitorVPA(ctx, t.before.tortoise) if err != nil { return err } @@ -1677,17 +1725,17 @@ func (t *testCase) initializeResources(ctx context.Context, k8sClient client.Cli }, } } - t.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].Status.Recommendation = &autoscalingv1.RecommendedPodResources{ + t.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].Status.Recommendation = &autoscalingv1.RecommendedPodResources{ ContainerRecommendations: r, } - t.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].Status.Conditions = []autoscalingv1.VerticalPodAutoscalerCondition{ + t.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].Status.Conditions = []autoscalingv1.VerticalPodAutoscalerCondition{ { Type: autoscalingv1.RecommendationProvided, Status: corev1.ConditionTrue, }, } - _, err = vpacli.AutoscalingV1().VerticalPodAutoscalers(t.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor].Namespace).UpdateStatus(ctx, t.before.vpa[v1alpha1.VerticalPodAutoscalerRoleMonitor], metav1.UpdateOptions{}) + _, err = vpacli.AutoscalingV1().VerticalPodAutoscalers(t.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor].Namespace).UpdateStatus(ctx, t.before.vpa[v1beta1.VerticalPodAutoscalerRoleMonitor], metav1.UpdateOptions{}) if err != nil { return err } @@ -1699,7 +1747,7 @@ func (t *testCase) initializeResources(ctx context.Context, k8sClient client.Cli } return retry.RetryOnConflict(retry.DefaultRetry, func() error { - tortoise := &v1alpha1.Tortoise{} + tortoise := &v1beta1.Tortoise{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: t.before.tortoise.Namespace, Name: t.before.tortoise.Name}, tortoise) if err != nil { return fmt.Errorf("failed to get tortoise: %w", err) diff --git a/go.mod b/go.mod index 43853bcb..5802acb1 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.7 github.com/prometheus/client_golang v1.15.1 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.27.2 k8s.io/apimachinery v0.27.2 k8s.io/autoscaler/vertical-pod-autoscaler v0.14.0 @@ -66,7 +67,6 @@ require ( google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.27.2 // indirect k8s.io/component-base v0.27.2 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect diff --git a/main.go b/main.go index dac957a0..d2fb1de8 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ import ( autoscalingv2 "github.com/mercari/tortoise/api/autoscaling/v2" autoscalingv1alpha1 "github.com/mercari/tortoise/api/v1alpha1" + autoscalingv1beta1 "github.com/mercari/tortoise/api/v1beta1" "github.com/mercari/tortoise/controllers" "github.com/mercari/tortoise/pkg/config" "github.com/mercari/tortoise/pkg/deployment" @@ -61,6 +62,7 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(autoscalingv1alpha1.AddToScheme(scheme)) + utilruntime.Must(autoscalingv1beta1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -146,6 +148,10 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "Tortoise") os.Exit(1) } + if err = (&autoscalingv1beta1.Tortoise{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Tortoise") + os.Exit(1) + } //+kubebuilder:scaffold:builder hpaWebhook := autoscalingv2.New(tortoiseService, hpaService) diff --git a/manifests/crd/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml b/manifests/crd/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml index 867abf67..72773184 100644 --- a/manifests/crd/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml +++ b/manifests/crd/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml @@ -2,9 +2,20 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) controller-gen.kubebuilder.io/version: v0.13.0 name: tortoises.autoscaling.mercari.com spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: webhook-service + namespace: system + path: /convert + conversionReviewVersions: + - v1 group: autoscaling.mercari.com names: kind: Tortoise @@ -287,6 +298,289 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.updateMode + name: MODE + type: string + - jsonPath: .status.tortoisePhase + name: PHASE + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Tortoise is the Schema for the tortoises API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TortoiseSpec defines the desired state of Tortoise + properties: + deletionPolicy: + description: "DeletionPolicy is the policy how the controller deletes associated HPA and VPAs when tortoise is removed. If \"DeleteAll\", tortoise deletes all associated HPA and VPAs, created by tortoise. If the associated HPA is not created by tortoise, which is associated by spec.targetRefs.horizontalPodAutoscalerName, tortoise never delete the HPA. If \"NoDelete\", tortoise doesn't delete any associated HPA and VPAs. \n \"DeleteAll\" is the default value." + enum: + - DeleteAll + - NoDelete + type: string + resourcePolicy: + description: ResourcePolicy contains the policy how each resource is updated. + items: + properties: + autoscalingPolicy: + additionalProperties: + enum: + - Horizontal + - Vertical + type: string + description: "AutoscalingPolicy specifies how each resource is scaled. If \"Horizontal\", the resource is horizontally scaled. If \"Vertical\", the resource is vertically scaled. Now, at least one container in Pod should be Horizontal. \n The default value is \"Horizontal\" for cpu, and \"Vertical\" for memory." + type: object + containerName: + description: ContainerName is the name of target container. + type: string + minAllocatedResources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: "MinAllocatedResources is the minimum amount of resources which is given to the container. Tortoise never set the resources request on the container than MinAllocatedResources. \n If empty, tortoise may reduce the resource request to the value which is suggested from VPA. Leaving this field empty is basically safe, but you may consider using MinAllocatedResources when maybe your application will consume resources more than usual, given the VPA suggests values based on the historical resource usage. For example, your application will soon have new feature which leads to increase in the resource usage, it is expected that your application will soon get more requests than usual, etc." + type: object + required: + - containerName + type: object + type: array + targetRefs: + description: TargetRefs has reference to involved resources. + properties: + horizontalPodAutoscalerName: + description: "HorizontalPodAutoscalerName is the name of the target HPA. The target of this HPA should be the same as the DeploymentName above. The target HPA should have the ContainerResource type metric or the external metric refers to the container resource utilization. Please check out the document for more detail: https://github.com/mercari/tortoise/blob/master/docs/horizontal.md#supported-metrics-in-hpa \n You can specify either of existing HPA only. This is an optional field, and if you don't specify this field, tortoise will create a new default HPA named `tortoise-hpa-{tortoise name}`." + type: string + scaleTargetRef: + description: ScaleTargetRef is the target of scaling. It should be the same as the target of HPA. + properties: + apiVersion: + description: apiVersion is the API version of the referent + type: string + kind: + description: 'kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + required: + - kind + - name + type: object + required: + - scaleTargetRef + type: object + updateMode: + description: "UpdateMode is how tortoise update resources. If \"Off\", tortoise generates the recommendations in .Status, but doesn't apply it actually. If \"Auto\", tortoise generates the recommendations in .Status, and apply it to resources. If \"Emergency\", tortoise generates the recommendations in .Status as usual, but increase replica number high enough value. \"Emergency\" is useful when something unexpected happens in workloads, and you want to scale up the workload with high enough resources. See https://github.com/mercari/tortoise/blob/main/docs/emergency.md to know more about emergency mode. \n \"Off\" is the default value." + enum: + - "Off" + - Auto + - Emergency + type: string + required: + - targetRefs + type: object + status: + description: TortoiseStatus defines the observed state of Tortoise + properties: + conditions: + properties: + containerRecommendationFromVPA: + items: + properties: + containerName: + description: ContainerName is the name of target container. + type: string + maxRecommendation: + additionalProperties: + properties: + quantity: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + updatedAt: + format: date-time + type: string + type: object + description: MaxRecommendation is the max recommendation value from VPA among certain period (1 week). Tortoise generates all recommendation based on this MaxRecommendation. + type: object + recommendation: + additionalProperties: + properties: + quantity: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + updatedAt: + format: date-time + type: string + type: object + description: Recommendation is the latest recommendation value from VPA. + type: object + required: + - containerName + - maxRecommendation + - recommendation + type: object + type: array + type: object + recommendations: + properties: + horizontal: + properties: + maxReplicas: + description: MaxReplicas has the recommendation of maxReplicas. It contains the recommendations for each time slot. + items: + properties: + from: + description: From represented in hour. + type: integer + timezone: + type: string + to: + description: To represented in hour. + type: integer + updatedAt: + format: date-time + type: string + value: + description: Value is the recommendation value. It's calculated every reconciliation, and updated if the calculated recommendation value is more than the current recommendation value on tortoise. + format: int32 + type: integer + weekday: + description: WeekDay is the day of the week. If empty, it means it applies to all days of the week. + type: string + required: + - from + - timezone + - to + - value + type: object + type: array + minReplicas: + description: MinReplicas has the recommendation of minReplicas. It contains the recommendations for each time slot. + items: + properties: + from: + description: From represented in hour. + type: integer + timezone: + type: string + to: + description: To represented in hour. + type: integer + updatedAt: + format: date-time + type: string + value: + description: Value is the recommendation value. It's calculated every reconciliation, and updated if the calculated recommendation value is more than the current recommendation value on tortoise. + format: int32 + type: integer + weekday: + description: WeekDay is the day of the week. If empty, it means it applies to all days of the week. + type: string + required: + - from + - timezone + - to + - value + type: object + type: array + targetUtilizations: + items: + properties: + containerName: + description: ContainerName is the name of target container. + type: string + targetUtilization: + additionalProperties: + format: int32 + type: integer + description: TargetUtilization is the recommendation of targetUtilization of HPA. + type: object + required: + - containerName + - targetUtilization + type: object + type: array + type: object + vertical: + properties: + containerResourceRecommendation: + items: + properties: + RecommendedResource: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: RecommendedResource is the recommendation calculated by the tortoise. If AutoscalingPolicy is vertical, it's the same value as the VPA suggests. If AutoscalingPolicy is horizontal, it's basically the same value as the current resource request. But, when the number of replicas are too small or too large, tortoise may try to increase/decrease the amount of resources given to the container, so that the number of replicas won't be very small or very large. + type: object + containerName: + description: ContainerName is the name of target container. + type: string + required: + - RecommendedResource + - containerName + type: object + type: array + type: object + type: object + targets: + properties: + deployment: + type: string + horizontalPodAutoscaler: + type: string + verticalPodAutoscalers: + items: + properties: + name: + type: string + role: + enum: + - Updater + - Monitor + type: string + required: + - name + - role + type: object + type: array + required: + - deployment + - horizontalPodAutoscaler + - verticalPodAutoscalers + type: object + tortoisePhase: + type: string + required: + - conditions + - recommendations + - targets + - tortoisePhase + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/manifests/default/admissionregistration.k8s.io_v1_mutatingwebhookconfiguration_tortoise-mutating-webhook-configuration.yaml b/manifests/default/admissionregistration.k8s.io_v1_mutatingwebhookconfiguration_tortoise-mutating-webhook-configuration.yaml index a610bae1..6d81114f 100644 --- a/manifests/default/admissionregistration.k8s.io_v1_mutatingwebhookconfiguration_tortoise-mutating-webhook-configuration.yaml +++ b/manifests/default/admissionregistration.k8s.io_v1_mutatingwebhookconfiguration_tortoise-mutating-webhook-configuration.yaml @@ -11,14 +11,14 @@ webhooks: service: name: tortoise-webhook-service namespace: tortoise-system - path: /mutate-autoscaling-mercari-com-v1alpha1-tortoise + path: /mutate-autoscaling-mercari-com-v1beta1-tortoise failurePolicy: Fail name: mtortoise.kb.io rules: - apiGroups: - autoscaling.mercari.com apiVersions: - - v1alpha1 + - v1beta1 operations: - CREATE - UPDATE diff --git a/manifests/default/admissionregistration.k8s.io_v1_validatingwebhookconfiguration_tortoise-validating-webhook-configuration.yaml b/manifests/default/admissionregistration.k8s.io_v1_validatingwebhookconfiguration_tortoise-validating-webhook-configuration.yaml index 20f83b42..a6a2f2df 100644 --- a/manifests/default/admissionregistration.k8s.io_v1_validatingwebhookconfiguration_tortoise-validating-webhook-configuration.yaml +++ b/manifests/default/admissionregistration.k8s.io_v1_validatingwebhookconfiguration_tortoise-validating-webhook-configuration.yaml @@ -11,14 +11,14 @@ webhooks: service: name: tortoise-webhook-service namespace: tortoise-system - path: /validate-autoscaling-mercari-com-v1alpha1-tortoise + path: /validate-autoscaling-mercari-com-v1beta1-tortoise failurePolicy: Fail name: vtortoise.kb.io rules: - apiGroups: - autoscaling.mercari.com apiVersions: - - v1alpha1 + - v1beta1 operations: - CREATE - UPDATE diff --git a/manifests/default/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml b/manifests/default/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml index 867abf67..7f470f1f 100644 --- a/manifests/default/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml +++ b/manifests/default/apiextensions.k8s.io_v1_customresourcedefinition_tortoises.autoscaling.mercari.com.yaml @@ -2,9 +2,20 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + cert-manager.io/inject-ca-from: tortoise-system/tortoise-serving-cert controller-gen.kubebuilder.io/version: v0.13.0 name: tortoises.autoscaling.mercari.com spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: tortoise-webhook-service + namespace: tortoise-system + path: /convert + conversionReviewVersions: + - v1 group: autoscaling.mercari.com names: kind: Tortoise @@ -287,6 +298,289 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.updateMode + name: MODE + type: string + - jsonPath: .status.tortoisePhase + name: PHASE + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Tortoise is the Schema for the tortoises API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TortoiseSpec defines the desired state of Tortoise + properties: + deletionPolicy: + description: "DeletionPolicy is the policy how the controller deletes associated HPA and VPAs when tortoise is removed. If \"DeleteAll\", tortoise deletes all associated HPA and VPAs, created by tortoise. If the associated HPA is not created by tortoise, which is associated by spec.targetRefs.horizontalPodAutoscalerName, tortoise never delete the HPA. If \"NoDelete\", tortoise doesn't delete any associated HPA and VPAs. \n \"DeleteAll\" is the default value." + enum: + - DeleteAll + - NoDelete + type: string + resourcePolicy: + description: ResourcePolicy contains the policy how each resource is updated. + items: + properties: + autoscalingPolicy: + additionalProperties: + enum: + - Horizontal + - Vertical + type: string + description: "AutoscalingPolicy specifies how each resource is scaled. If \"Horizontal\", the resource is horizontally scaled. If \"Vertical\", the resource is vertically scaled. Now, at least one container in Pod should be Horizontal. \n The default value is \"Horizontal\" for cpu, and \"Vertical\" for memory." + type: object + containerName: + description: ContainerName is the name of target container. + type: string + minAllocatedResources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: "MinAllocatedResources is the minimum amount of resources which is given to the container. Tortoise never set the resources request on the container than MinAllocatedResources. \n If empty, tortoise may reduce the resource request to the value which is suggested from VPA. Leaving this field empty is basically safe, but you may consider using MinAllocatedResources when maybe your application will consume resources more than usual, given the VPA suggests values based on the historical resource usage. For example, your application will soon have new feature which leads to increase in the resource usage, it is expected that your application will soon get more requests than usual, etc." + type: object + required: + - containerName + type: object + type: array + targetRefs: + description: TargetRefs has reference to involved resources. + properties: + horizontalPodAutoscalerName: + description: "HorizontalPodAutoscalerName is the name of the target HPA. The target of this HPA should be the same as the DeploymentName above. The target HPA should have the ContainerResource type metric or the external metric refers to the container resource utilization. Please check out the document for more detail: https://github.com/mercari/tortoise/blob/master/docs/horizontal.md#supported-metrics-in-hpa \n You can specify either of existing HPA only. This is an optional field, and if you don't specify this field, tortoise will create a new default HPA named `tortoise-hpa-{tortoise name}`." + type: string + scaleTargetRef: + description: ScaleTargetRef is the target of scaling. It should be the same as the target of HPA. + properties: + apiVersion: + description: apiVersion is the API version of the referent + type: string + kind: + description: 'kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + required: + - kind + - name + type: object + required: + - scaleTargetRef + type: object + updateMode: + description: "UpdateMode is how tortoise update resources. If \"Off\", tortoise generates the recommendations in .Status, but doesn't apply it actually. If \"Auto\", tortoise generates the recommendations in .Status, and apply it to resources. If \"Emergency\", tortoise generates the recommendations in .Status as usual, but increase replica number high enough value. \"Emergency\" is useful when something unexpected happens in workloads, and you want to scale up the workload with high enough resources. See https://github.com/mercari/tortoise/blob/main/docs/emergency.md to know more about emergency mode. \n \"Off\" is the default value." + enum: + - "Off" + - Auto + - Emergency + type: string + required: + - targetRefs + type: object + status: + description: TortoiseStatus defines the observed state of Tortoise + properties: + conditions: + properties: + containerRecommendationFromVPA: + items: + properties: + containerName: + description: ContainerName is the name of target container. + type: string + maxRecommendation: + additionalProperties: + properties: + quantity: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + updatedAt: + format: date-time + type: string + type: object + description: MaxRecommendation is the max recommendation value from VPA among certain period (1 week). Tortoise generates all recommendation based on this MaxRecommendation. + type: object + recommendation: + additionalProperties: + properties: + quantity: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + updatedAt: + format: date-time + type: string + type: object + description: Recommendation is the latest recommendation value from VPA. + type: object + required: + - containerName + - maxRecommendation + - recommendation + type: object + type: array + type: object + recommendations: + properties: + horizontal: + properties: + maxReplicas: + description: MaxReplicas has the recommendation of maxReplicas. It contains the recommendations for each time slot. + items: + properties: + from: + description: From represented in hour. + type: integer + timezone: + type: string + to: + description: To represented in hour. + type: integer + updatedAt: + format: date-time + type: string + value: + description: Value is the recommendation value. It's calculated every reconciliation, and updated if the calculated recommendation value is more than the current recommendation value on tortoise. + format: int32 + type: integer + weekday: + description: WeekDay is the day of the week. If empty, it means it applies to all days of the week. + type: string + required: + - from + - timezone + - to + - value + type: object + type: array + minReplicas: + description: MinReplicas has the recommendation of minReplicas. It contains the recommendations for each time slot. + items: + properties: + from: + description: From represented in hour. + type: integer + timezone: + type: string + to: + description: To represented in hour. + type: integer + updatedAt: + format: date-time + type: string + value: + description: Value is the recommendation value. It's calculated every reconciliation, and updated if the calculated recommendation value is more than the current recommendation value on tortoise. + format: int32 + type: integer + weekday: + description: WeekDay is the day of the week. If empty, it means it applies to all days of the week. + type: string + required: + - from + - timezone + - to + - value + type: object + type: array + targetUtilizations: + items: + properties: + containerName: + description: ContainerName is the name of target container. + type: string + targetUtilization: + additionalProperties: + format: int32 + type: integer + description: TargetUtilization is the recommendation of targetUtilization of HPA. + type: object + required: + - containerName + - targetUtilization + type: object + type: array + type: object + vertical: + properties: + containerResourceRecommendation: + items: + properties: + RecommendedResource: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: RecommendedResource is the recommendation calculated by the tortoise. If AutoscalingPolicy is vertical, it's the same value as the VPA suggests. If AutoscalingPolicy is horizontal, it's basically the same value as the current resource request. But, when the number of replicas are too small or too large, tortoise may try to increase/decrease the amount of resources given to the container, so that the number of replicas won't be very small or very large. + type: object + containerName: + description: ContainerName is the name of target container. + type: string + required: + - RecommendedResource + - containerName + type: object + type: array + type: object + type: object + targets: + properties: + deployment: + type: string + horizontalPodAutoscaler: + type: string + verticalPodAutoscalers: + items: + properties: + name: + type: string + role: + enum: + - Updater + - Monitor + type: string + required: + - name + - role + type: object + type: array + required: + - deployment + - horizontalPodAutoscaler + - verticalPodAutoscalers + type: object + tortoisePhase: + type: string + required: + - conditions + - recommendations + - targets + - tortoisePhase + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/pkg/deployment/service.go b/pkg/deployment/service.go index 46b1ae3b..ab83e92f 100644 --- a/pkg/deployment/service.go +++ b/pkg/deployment/service.go @@ -8,7 +8,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - autoscalingv1alpha1 "github.com/mercari/tortoise/api/v1alpha1" + autoscalingv1beta1 "github.com/mercari/tortoise/api/v1beta1" ) type Service struct { @@ -19,9 +19,9 @@ func New(c client.Client) *Service { return &Service{c: c} } -func (c *Service) GetDeploymentOnTortoise(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*v1.Deployment, error) { +func (c *Service) GetDeploymentOnTortoise(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*v1.Deployment, error) { d := &v1.Deployment{} - if err := c.c.Get(ctx, types.NamespacedName{Namespace: tortoise.Namespace, Name: tortoise.Spec.TargetRefs.DeploymentName}, d); err != nil { + if err := c.c.Get(ctx, types.NamespacedName{Namespace: tortoise.Namespace, Name: tortoise.Spec.TargetRefs.ScaleTargetRef.Name}, d); err != nil { return nil, fmt.Errorf("failed to get deployment on tortoise: %w", err) } return d, nil diff --git a/pkg/hpa/service.go b/pkg/hpa/service.go index 787de86c..a7d37bc9 100644 --- a/pkg/hpa/service.go +++ b/pkg/hpa/service.go @@ -18,7 +18,7 @@ import ( "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" - autoscalingv1alpha1 "github.com/mercari/tortoise/api/v1alpha1" + autoscalingv1beta1 "github.com/mercari/tortoise/api/v1beta1" "github.com/mercari/tortoise/pkg/annotation" "github.com/mercari/tortoise/pkg/metrics" ) @@ -40,7 +40,7 @@ func New(c client.Client, recorder record.EventRecorder, replicaReductionFactor } } -func (c *Service) InitializeHPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise, dm *v1.Deployment) (*autoscalingv1alpha1.Tortoise, error) { +func (c *Service) InitializeHPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise, dm *v1.Deployment) (*autoscalingv1beta1.Tortoise, error) { if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != nil { // update the existing HPA that the user set on tortoise. tortoise, err := c.giveAnnotationsOnHPA(ctx, tortoise) @@ -64,7 +64,7 @@ func (c *Service) InitializeHPA(ctx context.Context, tortoise *autoscalingv1alph return tortoise, nil } -func (c *Service) giveAnnotationsOnHPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*autoscalingv1alpha1.Tortoise, error) { +func (c *Service) giveAnnotationsOnHPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*autoscalingv1beta1.Tortoise, error) { if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName == nil { // shouldn't reach here since the caller should check this. return tortoise, fmt.Errorf("tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName is nil") @@ -89,8 +89,8 @@ func (c *Service) giveAnnotationsOnHPA(ctx context.Context, tortoise *autoscalin return tortoise, retry.RetryOnConflict(retry.DefaultRetry, updateFn) } -func (c *Service) DeleteHPACreatedByTortoise(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) error { - if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != nil || tortoise.Spec.DeletionPolicy == autoscalingv1alpha1.DeletionPolicyNoDelete { +func (c *Service) DeleteHPACreatedByTortoise(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) error { + if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != nil || tortoise.Spec.DeletionPolicy == autoscalingv1beta1.DeletionPolicyNoDelete { // A user specified the existing HPA and tortoise didn't create HPA by itself. return nil } @@ -120,7 +120,7 @@ func (c *Service) DeleteHPACreatedByTortoise(ctx context.Context, tortoise *auto return nil } -func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise, dm *v1.Deployment) (*v2.HorizontalPodAutoscaler, *autoscalingv1alpha1.Tortoise, error) { +func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise, dm *v1.Deployment) (*v2.HorizontalPodAutoscaler, *autoscalingv1beta1.Tortoise, error) { if tortoise.Spec.TargetRefs.HorizontalPodAutoscalerName != nil { // we don't have to create HPA as the user specified the existing HPA. return nil, tortoise, nil @@ -128,7 +128,7 @@ func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1alpha1.T hpa := &v2.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ - Name: autoscalingv1alpha1.TortoiseDefaultHPAName(tortoise.Name), + Name: autoscalingv1beta1.TortoiseDefaultHPAName(tortoise.Name), Namespace: tortoise.Namespace, Annotations: map[string]string{ annotation.TortoiseNameAnnotation: tortoise.Name, @@ -137,9 +137,9 @@ func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1alpha1.T }, Spec: v2.HorizontalPodAutoscalerSpec{ ScaleTargetRef: v2.CrossVersionObjectReference{ - Kind: "Deployment", - Name: tortoise.Spec.TargetRefs.DeploymentName, - APIVersion: "apps/v1", + Kind: tortoise.Spec.TargetRefs.ScaleTargetRef.Kind, + Name: tortoise.Spec.TargetRefs.ScaleTargetRef.Name, + APIVersion: tortoise.Spec.TargetRefs.ScaleTargetRef.APIVersion, }, MinReplicas: pointer.Int32(int32(math.Ceil(float64(dm.Status.Replicas) / 2.0))), MaxReplicas: dm.Status.Replicas * 2, @@ -170,7 +170,7 @@ func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1alpha1.T for _, policy := range tortoise.Spec.ResourcePolicy { for r, p := range policy.AutoscalingPolicy { value := pointer.Int32(50) - if p != autoscalingv1alpha1.AutoscalingTypeHorizontal { + if p != autoscalingv1beta1.AutoscalingTypeHorizontal { value = pointer.Int32(c.upperTargetResourceUtilization) } m = append(m, v2.MetricSpec{ @@ -193,7 +193,7 @@ func (c *Service) CreateHPA(ctx context.Context, tortoise *autoscalingv1alpha1.T return hpa.DeepCopy(), tortoise, err } -func (c *Service) GetHPAOnTortoise(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*v2.HorizontalPodAutoscaler, error) { +func (c *Service) GetHPAOnTortoise(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*v2.HorizontalPodAutoscaler, error) { hpa := &v2.HorizontalPodAutoscaler{} if err := c.c.Get(ctx, types.NamespacedName{Namespace: tortoise.Namespace, Name: tortoise.Status.Targets.HorizontalPodAutoscaler}, hpa); err != nil { return nil, fmt.Errorf("failed to get hpa on tortoise: %w", err) @@ -201,10 +201,7 @@ func (c *Service) GetHPAOnTortoise(ctx context.Context, tortoise *autoscalingv1a return hpa, nil } -func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1alpha1.Tortoise, hpa *v2.HorizontalPodAutoscaler, now time.Time, recordMetrics bool) (*v2.HorizontalPodAutoscaler, *autoscalingv1alpha1.Tortoise, error) { - if tortoise.Status.Recommendations.Horizontal == nil { - return hpa, nil, nil - } +func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1beta1.Tortoise, hpa *v2.HorizontalPodAutoscaler, now time.Time, recordMetrics bool) (*v2.HorizontalPodAutoscaler, *autoscalingv1beta1.Tortoise, error) { for _, t := range tortoise.Status.Recommendations.Horizontal.TargetUtilizations { for resourcename, targetutil := range t.TargetUtilization { metrics.ProposedHPATargetUtilization.WithLabelValues(tortoise.Name, tortoise.Namespace, t.ContainerName, resourcename.String(), hpa.Name).Set(float64(targetutil)) @@ -222,10 +219,10 @@ func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1alp var min int32 switch tortoise.Status.TortoisePhase { - case autoscalingv1alpha1.TortoisePhaseEmergency: + case autoscalingv1beta1.TortoisePhaseEmergency: // when emergency mode, we set the same value on minReplicas. min = max - case autoscalingv1alpha1.TortoisePhaseBackToNormal: + case autoscalingv1beta1.TortoisePhaseBackToNormal: idealMin, err := GetReplicasRecommendation(tortoise.Status.Recommendations.Horizontal.MinReplicas, now) if err != nil { return nil, tortoise, fmt.Errorf("get minReplicas recommendation: %w", err) @@ -235,7 +232,7 @@ func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1alp if idealMin > reduced { min = idealMin // BackToNormal is finished - tortoise.Status.TortoisePhase = autoscalingv1alpha1.TortoisePhaseWorking + tortoise.Status.TortoisePhase = autoscalingv1beta1.TortoisePhaseWorking } else { min = reduced } @@ -252,8 +249,8 @@ func (c *Service) ChangeHPAFromTortoiseRecommendation(tortoise *autoscalingv1alp return hpa, tortoise, nil } -func (c *Service) UpdateHPAFromTortoiseRecommendation(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise, now time.Time) (*v2.HorizontalPodAutoscaler, *autoscalingv1alpha1.Tortoise, error) { - retTortoise := &autoscalingv1alpha1.Tortoise{} +func (c *Service) UpdateHPAFromTortoiseRecommendation(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise, now time.Time) (*v2.HorizontalPodAutoscaler, *autoscalingv1beta1.Tortoise, error) { + retTortoise := &autoscalingv1beta1.Tortoise{} retHPA := &v2.HorizontalPodAutoscaler{} // we only want to record metric once in every reconcile loop. @@ -271,7 +268,7 @@ func (c *Service) UpdateHPAFromTortoiseRecommendation(ctx context.Context, torto } metricsRecorded = true retTortoise = tortoise - if tortoise.Spec.UpdateMode == autoscalingv1alpha1.UpdateModeOff { + if tortoise.Spec.UpdateMode == autoscalingv1beta1.UpdateModeOff { // don't update status if update mode is off. (= dryrun) return nil } @@ -283,7 +280,7 @@ func (c *Service) UpdateHPAFromTortoiseRecommendation(ctx context.Context, torto return nil, nil, err } - if tortoise.Spec.UpdateMode != autoscalingv1alpha1.UpdateModeOff { + if tortoise.Spec.UpdateMode != autoscalingv1beta1.UpdateModeOff { c.recorder.Event(tortoise, corev1.EventTypeNormal, "HPAUpdated", fmt.Sprintf("HPA %s/%s is updated by the recommendation", retHPA.Namespace, retHPA.Name)) } @@ -291,7 +288,7 @@ func (c *Service) UpdateHPAFromTortoiseRecommendation(ctx context.Context, torto } // GetReplicasRecommendation finds the corresponding recommendations. -func GetReplicasRecommendation(recommendations []autoscalingv1alpha1.ReplicasRecommendation, now time.Time) (int32, error) { +func GetReplicasRecommendation(recommendations []autoscalingv1beta1.ReplicasRecommendation, now time.Time) (int32, error) { for _, r := range recommendations { if now.Hour() < r.To && now.Hour() >= r.From && (r.WeekDay == nil || now.Weekday().String() == *r.WeekDay) { return r.Value, nil diff --git a/pkg/hpa/service_test.go b/pkg/hpa/service_test.go index 637948c3..0e367856 100644 --- a/pkg/hpa/service_test.go +++ b/pkg/hpa/service_test.go @@ -16,7 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - autoscalingv1alpha1 "github.com/mercari/tortoise/api/v1alpha1" + autoscalingv1beta1 "github.com/mercari/tortoise/api/v1beta1" "github.com/mercari/tortoise/pkg/annotation" ) @@ -25,7 +25,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { type args struct { ctx context.Context - tortoise *autoscalingv1alpha1.Tortoise + tortoise *autoscalingv1beta1.Tortoise now time.Time } tests := []struct { @@ -33,21 +33,21 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { args args initialHPA *v2.HorizontalPodAutoscaler want *v2.HorizontalPodAutoscaler - wantTortoise *autoscalingv1alpha1.Tortoise + wantTortoise *autoscalingv1beta1.Tortoise wantErr bool }{ { name: "Basic test case with container resource metrics", args: args{ ctx: context.Background(), - tortoise: &autoscalingv1alpha1.Tortoise{ - Status: autoscalingv1alpha1.TortoiseStatus{ - Targets: autoscalingv1alpha1.TargetsStatus{ + tortoise: &autoscalingv1beta1.Tortoise{ + Status: autoscalingv1beta1.TortoiseStatus{ + Targets: autoscalingv1beta1.TargetsStatus{ HorizontalPodAutoscaler: "hpa", }, - Recommendations: autoscalingv1alpha1.Recommendations{ - Horizontal: &autoscalingv1alpha1.HorizontalRecommendations{ - TargetUtilizations: []autoscalingv1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Recommendations: autoscalingv1beta1.Recommendations{ + Horizontal: autoscalingv1beta1.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[v1.ResourceName]int32{ @@ -61,7 +61,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, - MaxReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MaxReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -70,7 +70,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { WeekDay: pointer.String(now.Weekday().String()), }, }, - MinReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MinReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -161,17 +161,17 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { name: "no update preformed when updateMode is Off", args: args{ ctx: context.Background(), - tortoise: &autoscalingv1alpha1.Tortoise{ - Spec: autoscalingv1alpha1.TortoiseSpec{ - UpdateMode: autoscalingv1alpha1.UpdateModeOff, + tortoise: &autoscalingv1beta1.Tortoise{ + Spec: autoscalingv1beta1.TortoiseSpec{ + UpdateMode: autoscalingv1beta1.UpdateModeOff, }, - Status: autoscalingv1alpha1.TortoiseStatus{ - Targets: autoscalingv1alpha1.TargetsStatus{ + Status: autoscalingv1beta1.TortoiseStatus{ + Targets: autoscalingv1beta1.TargetsStatus{ HorizontalPodAutoscaler: "hpa", }, - Recommendations: autoscalingv1alpha1.Recommendations{ - Horizontal: &autoscalingv1alpha1.HorizontalRecommendations{ - TargetUtilizations: []autoscalingv1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Recommendations: autoscalingv1beta1.Recommendations{ + Horizontal: autoscalingv1beta1.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[v1.ResourceName]int32{ @@ -185,7 +185,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, - MaxReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MaxReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -194,7 +194,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { WeekDay: pointer.String(now.Weekday().String()), }, }, - MinReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MinReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -285,15 +285,15 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { name: "emergency mode", args: args{ ctx: context.Background(), - tortoise: &autoscalingv1alpha1.Tortoise{ - Status: autoscalingv1alpha1.TortoiseStatus{ - Targets: autoscalingv1alpha1.TargetsStatus{ + tortoise: &autoscalingv1beta1.Tortoise{ + Status: autoscalingv1beta1.TortoiseStatus{ + Targets: autoscalingv1beta1.TargetsStatus{ HorizontalPodAutoscaler: "hpa", }, - TortoisePhase: autoscalingv1alpha1.TortoisePhaseEmergency, - Recommendations: autoscalingv1alpha1.Recommendations{ - Horizontal: &autoscalingv1alpha1.HorizontalRecommendations{ - TargetUtilizations: []autoscalingv1alpha1.HPATargetUtilizationRecommendationPerContainer{ + TortoisePhase: autoscalingv1beta1.TortoisePhaseEmergency, + Recommendations: autoscalingv1beta1.Recommendations{ + Horizontal: autoscalingv1beta1.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[v1.ResourceName]int32{ @@ -307,7 +307,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, - MaxReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MaxReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -316,7 +316,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { WeekDay: pointer.String(now.Weekday().String()), }, }, - MinReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MinReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -407,15 +407,15 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { name: "minReplicas are reduced gradually during BackToNormal", args: args{ ctx: context.Background(), - tortoise: &autoscalingv1alpha1.Tortoise{ - Status: autoscalingv1alpha1.TortoiseStatus{ - Targets: autoscalingv1alpha1.TargetsStatus{ + tortoise: &autoscalingv1beta1.Tortoise{ + Status: autoscalingv1beta1.TortoiseStatus{ + Targets: autoscalingv1beta1.TargetsStatus{ HorizontalPodAutoscaler: "hpa", }, - TortoisePhase: autoscalingv1alpha1.TortoisePhaseBackToNormal, - Recommendations: autoscalingv1alpha1.Recommendations{ - Horizontal: &autoscalingv1alpha1.HorizontalRecommendations{ - TargetUtilizations: []autoscalingv1alpha1.HPATargetUtilizationRecommendationPerContainer{ + TortoisePhase: autoscalingv1beta1.TortoisePhaseBackToNormal, + Recommendations: autoscalingv1beta1.Recommendations{ + Horizontal: autoscalingv1beta1.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[v1.ResourceName]int32{ @@ -429,7 +429,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, - MaxReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MaxReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -438,7 +438,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { WeekDay: pointer.String(now.Weekday().String()), }, }, - MinReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MinReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -529,15 +529,15 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { name: "BackToNormal finishes when minReplicas reaches the ideal value", args: args{ ctx: context.Background(), - tortoise: &autoscalingv1alpha1.Tortoise{ - Status: autoscalingv1alpha1.TortoiseStatus{ - Targets: autoscalingv1alpha1.TargetsStatus{ + tortoise: &autoscalingv1beta1.Tortoise{ + Status: autoscalingv1beta1.TortoiseStatus{ + Targets: autoscalingv1beta1.TargetsStatus{ HorizontalPodAutoscaler: "hpa", }, - TortoisePhase: autoscalingv1alpha1.TortoisePhaseBackToNormal, - Recommendations: autoscalingv1alpha1.Recommendations{ - Horizontal: &autoscalingv1alpha1.HorizontalRecommendations{ - TargetUtilizations: []autoscalingv1alpha1.HPATargetUtilizationRecommendationPerContainer{ + TortoisePhase: autoscalingv1beta1.TortoisePhaseBackToNormal, + Recommendations: autoscalingv1beta1.Recommendations{ + Horizontal: autoscalingv1beta1.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[v1.ResourceName]int32{ @@ -551,7 +551,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, - MaxReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MaxReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -560,7 +560,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { WeekDay: pointer.String(now.Weekday().String()), }, }, - MinReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MinReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -645,15 +645,15 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, - wantTortoise: &autoscalingv1alpha1.Tortoise{ - Status: autoscalingv1alpha1.TortoiseStatus{ - Targets: autoscalingv1alpha1.TargetsStatus{ + wantTortoise: &autoscalingv1beta1.Tortoise{ + Status: autoscalingv1beta1.TortoiseStatus{ + Targets: autoscalingv1beta1.TargetsStatus{ HorizontalPodAutoscaler: "hpa", }, - TortoisePhase: autoscalingv1alpha1.TortoisePhaseWorking, - Recommendations: autoscalingv1alpha1.Recommendations{ - Horizontal: &autoscalingv1alpha1.HorizontalRecommendations{ - TargetUtilizations: []autoscalingv1alpha1.HPATargetUtilizationRecommendationPerContainer{ + TortoisePhase: autoscalingv1beta1.TortoisePhaseWorking, + Recommendations: autoscalingv1beta1.Recommendations{ + Horizontal: autoscalingv1beta1.HorizontalRecommendations{ + TargetUtilizations: []autoscalingv1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[v1.ResourceName]int32{ @@ -667,7 +667,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { }, }, }, - MaxReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MaxReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -676,7 +676,7 @@ func TestClient_UpdateHPAFromTortoiseRecommendation(t *testing.T) { WeekDay: pointer.String(now.Weekday().String()), }, }, - MinReplicas: []autoscalingv1alpha1.ReplicasRecommendation{ + MinReplicas: []autoscalingv1beta1.ReplicasRecommendation{ { From: 0, To: 2, @@ -719,7 +719,7 @@ func ptrInt32(i int32) *int32 { func TestService_InitializeHPA(t *testing.T) { type args struct { - tortoise *autoscalingv1alpha1.Tortoise + tortoise *autoscalingv1beta1.Tortoise dm *appv1.Deployment } tests := []struct { @@ -732,14 +732,18 @@ func TestService_InitializeHPA(t *testing.T) { { name: "should create new hpa", args: args{ - tortoise: &autoscalingv1alpha1.Tortoise{ + tortoise: &autoscalingv1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{ Name: "tortoise", Namespace: "default", }, - Spec: autoscalingv1alpha1.TortoiseSpec{ - TargetRefs: autoscalingv1alpha1.TargetRefs{ - DeploymentName: "deployment", + Spec: autoscalingv1beta1.TortoiseSpec{ + TargetRefs: autoscalingv1beta1.TargetRefs{ + ScaleTargetRef: autoscalingv1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + APIVersion: "apps/v1", + }, }, }, }, @@ -808,15 +812,18 @@ func TestService_InitializeHPA(t *testing.T) { { name: "just give annotation to existing hpa", args: args{ - tortoise: &autoscalingv1alpha1.Tortoise{ + tortoise: &autoscalingv1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{ Name: "tortoise", Namespace: "default", }, - Spec: autoscalingv1alpha1.TortoiseSpec{ - TargetRefs: autoscalingv1alpha1.TargetRefs{ + Spec: autoscalingv1beta1.TortoiseSpec{ + TargetRefs: autoscalingv1beta1.TargetRefs{ HorizontalPodAutoscalerName: pointer.String("existing-hpa"), - DeploymentName: "deployment", + ScaleTargetRef: autoscalingv1beta1.CrossVersionObjectReference{ + Kind: "Deployment", + Name: "deployment", + }, }, }, }, diff --git a/pkg/recommender/recommender.go b/pkg/recommender/recommender.go index 67b749fe..ac87f9a0 100644 --- a/pkg/recommender/recommender.go +++ b/pkg/recommender/recommender.go @@ -16,7 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/mercari/tortoise/api/v1alpha1" + "github.com/mercari/tortoise/api/v1beta1" ) type Service struct { @@ -55,7 +55,7 @@ func New( } } -func (s *Service) updateVPARecommendation(tortoise *v1alpha1.Tortoise, deployment *v1.Deployment, hpa *v2.HorizontalPodAutoscaler) (*v1alpha1.Tortoise, error) { +func (s *Service) updateVPARecommendation(tortoise *v1beta1.Tortoise, deployment *v1.Deployment, hpa *v2.HorizontalPodAutoscaler) (*v1beta1.Tortoise, error) { requestMap := map[string]map[corev1.ResourceName]resource.Quantity{} for _, c := range deployment.Spec.Template.Spec.Containers { requestMap[c.Name] = map[corev1.ResourceName]resource.Quantity{} @@ -72,9 +72,9 @@ func (s *Service) updateVPARecommendation(tortoise *v1alpha1.Tortoise, deploymen } } - newRecommendations := []v1alpha1.RecommendedContainerResources{} + newRecommendations := []v1beta1.RecommendedContainerResources{} for _, r := range tortoise.Spec.ResourcePolicy { - recommendation := v1alpha1.RecommendedContainerResources{ + recommendation := v1beta1.RecommendedContainerResources{ ContainerName: r.ContainerName, RecommendedResource: map[corev1.ResourceName]resource.Quantity{}, } @@ -109,16 +109,13 @@ func (s *Service) updateVPARecommendation(tortoise *v1alpha1.Tortoise, deploymen } newRecommendations = append(newRecommendations, recommendation) } - if tortoise.Status.Recommendations.Vertical == nil { - tortoise.Status.Recommendations.Vertical = &v1alpha1.VerticalRecommendations{} - } tortoise.Status.Recommendations.Vertical.ContainerResourceRecommendation = newRecommendations return tortoise, nil } -func (s *Service) calculateBestNewSize(p v1alpha1.AutoscalingType, containerName string, recom resource.Quantity, k corev1.ResourceName, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment, req resource.Quantity, minAllocatedResources corev1.ResourceList) (int64, error) { +func (s *Service) calculateBestNewSize(p v1beta1.AutoscalingType, containerName string, recom resource.Quantity, k corev1.ResourceName, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment, req resource.Quantity, minAllocatedResources corev1.ResourceList) (int64, error) { // Make the container size bigger (just multiple by s.preferredReplicaNumUpperLimit) // when the current replica num is more than or equal to the preferredReplicaNumUpperLimit. if deployment.Status.Replicas >= s.preferredReplicaNumUpperLimit { @@ -129,12 +126,12 @@ func (s *Service) calculateBestNewSize(p v1alpha1.AutoscalingType, containerName // change the container size based on the VPA recommendation when: // - user configure Vertical on this container's resource // - the current replica num is less than or equal to the minimumMinReplicas. - if deployment.Status.Replicas <= s.minimumMinReplicas || p == v1alpha1.AutoscalingTypeVertical { + if deployment.Status.Replicas <= s.minimumMinReplicas || p == v1beta1.AutoscalingTypeVertical { newSize := recom.MilliValue() return s.justifyNewSizeByMaxMin(newSize, k, req, minAllocatedResources), nil } - if p == v1alpha1.AutoscalingTypeHorizontal { + if p == v1beta1.AutoscalingTypeHorizontal { targetUtilizationValue, err := getHPATargetValue(hpa, containerName, k, len(deployment.Spec.Template.Spec.Containers) == 1) if err != nil { return 0, fmt.Errorf("get the target value from HPA: %w", err) @@ -171,7 +168,7 @@ func (s *Service) justifyNewSizeByMaxMin(newSize int64, k corev1.ResourceName, r return newSize } -func (s *Service) updateHPARecommendation(ctx context.Context, tortoise *v1alpha1.Tortoise, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment, now time.Time) (*v1alpha1.Tortoise, error) { +func (s *Service) updateHPARecommendation(ctx context.Context, tortoise *v1beta1.Tortoise, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment, now time.Time) (*v1beta1.Tortoise, error) { var err error tortoise, err = s.updateHPATargetUtilizationRecommendations(ctx, tortoise, hpa, deployment) if err != nil { @@ -185,7 +182,7 @@ func (s *Service) updateHPARecommendation(ctx context.Context, tortoise *v1alpha return tortoise, nil } -func (s *Service) UpdateRecommendations(ctx context.Context, tortoise *v1alpha1.Tortoise, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment, now time.Time) (*v1alpha1.Tortoise, error) { +func (s *Service) UpdateRecommendations(ctx context.Context, tortoise *v1beta1.Tortoise, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment, now time.Time) (*v1beta1.Tortoise, error) { var err error tortoise, err = s.updateHPARecommendation(ctx, tortoise, hpa, deployment, now) if err != nil { @@ -199,7 +196,7 @@ func (s *Service) UpdateRecommendations(ctx context.Context, tortoise *v1alpha1. return tortoise, nil } -func (s *Service) updateHPAMinMaxReplicasRecommendations(tortoise *v1alpha1.Tortoise, deployment *v1.Deployment, now time.Time) (*v1alpha1.Tortoise, error) { +func (s *Service) updateHPAMinMaxReplicasRecommendations(tortoise *v1beta1.Tortoise, deployment *v1.Deployment, now time.Time) (*v1beta1.Tortoise, error) { currentReplicaNum := float64(deployment.Status.Replicas) min, err := s.updateMaxMinReplicasRecommendation(int32(math.Ceil(currentReplicaNum*s.minReplicasFactor)), tortoise.Status.Recommendations.Horizontal.MinReplicas, now, s.minimumMinReplicas) if err != nil { @@ -216,7 +213,7 @@ func (s *Service) updateHPAMinMaxReplicasRecommendations(tortoise *v1alpha1.Tort } // updateMaxMinReplicasRecommendation replaces value if the value is higher than the current value. -func (s *Service) updateMaxMinReplicasRecommendation(value int32, recommendations []v1alpha1.ReplicasRecommendation, now time.Time, minimum int32) ([]v1alpha1.ReplicasRecommendation, error) { +func (s *Service) updateMaxMinReplicasRecommendation(value int32, recommendations []v1beta1.ReplicasRecommendation, now time.Time, minimum int32) ([]v1beta1.ReplicasRecommendation, error) { // find the corresponding recommendations. index := -1 for i, r := range recommendations { @@ -240,7 +237,7 @@ func (s *Service) updateMaxMinReplicasRecommendation(value int32, recommendation return recommendations, nil } -func (s *Service) updateHPATargetUtilizationRecommendations(ctx context.Context, tortoise *v1alpha1.Tortoise, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment) (*v1alpha1.Tortoise, error) { +func (s *Service) updateHPATargetUtilizationRecommendations(ctx context.Context, tortoise *v1beta1.Tortoise, hpa *v2.HorizontalPodAutoscaler, deployment *v1.Deployment) (*v1beta1.Tortoise, error) { logger := log.FromContext(ctx) requestMap := map[string]map[corev1.ResourceName]resource.Quantity{} for _, c := range deployment.Spec.Template.Spec.Containers { @@ -258,7 +255,7 @@ func (s *Service) updateHPATargetUtilizationRecommendations(ctx context.Context, } } - newHPATargetUtilizationRecommendationPerContainer := []v1alpha1.HPATargetUtilizationRecommendationPerContainer{} + newHPATargetUtilizationRecommendationPerContainer := []v1beta1.HPATargetUtilizationRecommendationPerContainer{} for _, r := range tortoise.Spec.ResourcePolicy { targetMap := map[corev1.ResourceName]int32{} reqmap, ok := requestMap[r.ContainerName] @@ -267,7 +264,7 @@ func (s *Service) updateHPATargetUtilizationRecommendations(ctx context.Context, continue } for k, p := range r.AutoscalingPolicy { - if p == v1alpha1.AutoscalingTypeVertical { + if p == v1beta1.AutoscalingTypeVertical { targetMap[k] = s.upperTargetResourceUtilization continue } @@ -307,15 +304,12 @@ func (s *Service) updateHPATargetUtilizationRecommendations(ctx context.Context, } logger.Info("HPA target utilization recommendation is created", "current target utilization", targetValue, "recommended target utilization", targetMap[k], "upper usage", upperUsage) } - newHPATargetUtilizationRecommendationPerContainer = append(newHPATargetUtilizationRecommendationPerContainer, v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + newHPATargetUtilizationRecommendationPerContainer = append(newHPATargetUtilizationRecommendationPerContainer, v1beta1.HPATargetUtilizationRecommendationPerContainer{ ContainerName: r.ContainerName, TargetUtilization: targetMap, }) } - if tortoise.Status.Recommendations.Horizontal == nil { - tortoise.Status.Recommendations.Horizontal = &v1alpha1.HorizontalRecommendations{} - } tortoise.Status.Recommendations.Horizontal.TargetUtilizations = newHPATargetUtilizationRecommendationPerContainer return tortoise, nil diff --git a/pkg/recommender/recommender_test.go b/pkg/recommender/recommender_test.go index e7c064c5..6009ae1c 100644 --- a/pkg/recommender/recommender_test.go +++ b/pkg/recommender/recommender_test.go @@ -14,48 +14,48 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - "github.com/mercari/tortoise/api/v1alpha1" + "github.com/mercari/tortoise/api/v1beta1" ) func TestUpdateRecommendation(t *testing.T) { type args struct { - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise hpa *v2.HorizontalPodAutoscaler deployment *v1.Deployment } tests := []struct { name string args args - want *v1alpha1.Tortoise + want *v1beta1.Tortoise }{ { name: "if HPA has the container resource metrics, then it has higher priority than external metrics", args: args{ - tortoise: &v1alpha1.Tortoise{ - Spec: v1alpha1.TortoiseSpec{ - ResourcePolicy: []v1alpha1.ContainerResourcePolicy{ + tortoise: &v1beta1.Tortoise{ + Spec: v1beta1.TortoiseSpec{ + ResourcePolicy: []v1beta1.ContainerResourcePolicy{ { ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceMemory: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceCPU: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceMemory: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceCPU: v1beta1.AutoscalingTypeVertical, }, }, { ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, }, }, }, }, - Status: v1alpha1.TortoiseStatus{ - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + Status: v1beta1.TortoiseStatus{ + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceMemory: { Quantity: resource.MustParse("4Gi"), }, @@ -66,7 +66,7 @@ func TestUpdateRecommendation(t *testing.T) { }, { ContainerName: "istio-proxy", - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceMemory: { Quantity: resource.MustParse("0.6Gi"), }, @@ -151,29 +151,29 @@ func TestUpdateRecommendation(t *testing.T) { }, }, }, - want: &v1alpha1.Tortoise{ - Spec: v1alpha1.TortoiseSpec{ - ResourcePolicy: []v1alpha1.ContainerResourcePolicy{ + want: &v1beta1.Tortoise{ + Spec: v1beta1.TortoiseSpec{ + ResourcePolicy: []v1beta1.ContainerResourcePolicy{ { ContainerName: "app", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceMemory: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceCPU: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceMemory: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceCPU: v1beta1.AutoscalingTypeVertical, }, }, { ContainerName: "istio-proxy", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, }, }, }, }, - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - TargetUtilizations: []v1alpha1.HPATargetUtilizationRecommendationPerContainer{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + TargetUtilizations: []v1beta1.HPATargetUtilizationRecommendationPerContainer{ { ContainerName: "app", TargetUtilization: map[corev1.ResourceName]int32{ @@ -191,11 +191,11 @@ func TestUpdateRecommendation(t *testing.T) { }, }, }, - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceMemory: { Quantity: resource.MustParse("4Gi"), }, @@ -206,7 +206,7 @@ func TestUpdateRecommendation(t *testing.T) { }, { ContainerName: "istio-proxy", - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceMemory: { Quantity: resource.MustParse("0.6Gi"), }, @@ -246,24 +246,24 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { t.Fatal(err) } type args struct { - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise deployment *v1.Deployment now time.Time } tests := []struct { name string args args - want *v1alpha1.Tortoise + want *v1beta1.Tortoise wantErr bool }{ { name: "Basic case", args: args{ - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -272,7 +272,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -292,11 +292,11 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { }, now: time.Date(2023, 3, 19, 0, 0, 0, 0, jst), }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -305,7 +305,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -323,12 +323,12 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { { name: "No recommendation slot", args: args{ - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{}, - MaxReplicas: []v1alpha1.ReplicasRecommendation{}, + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{}, + MaxReplicas: []v1beta1.ReplicasRecommendation{}, }, }, }, @@ -340,12 +340,12 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { }, now: time.Date(2023, 3, 19, 0, 0, 0, 0, jst), }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{}, - MaxReplicas: []v1alpha1.ReplicasRecommendation{}, + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{}, + MaxReplicas: []v1beta1.ReplicasRecommendation{}, }, }, }, @@ -355,11 +355,11 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { { name: "Lower recommendation value", args: args{ - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -368,7 +368,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -388,11 +388,11 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { }, now: time.Date(2023, 3, 19, 0, 0, 0, 0, jst), }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -402,7 +402,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -421,11 +421,11 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { { name: "Same recommendation value", args: args{ - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -434,7 +434,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -454,11 +454,11 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { }, now: time.Date(2023, 3, 19, 0, 0, 0, 0, jst), }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -467,7 +467,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -485,11 +485,11 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { { name: "minimum MinReplicas and minimum MaxReplicas", args: args{ - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -498,7 +498,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -518,11 +518,11 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { }, now: time.Date(2023, 3, 19, 0, 0, 0, 0, jst), }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -531,7 +531,7 @@ func Test_updateHPAMinMaxReplicasRecommendations(t *testing.T) { WeekDay: pointer.String(time.Sunday.String()), }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 1, @@ -569,7 +569,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { suggestedResourceSizeAtMax corev1.ResourceList } type args struct { - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise deployment *v1.Deployment hpa *v2.HorizontalPodAutoscaler } @@ -577,7 +577,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { name string fields fields args args - want *v1alpha1.Tortoise + want *v1beta1.Tortoise wantErr bool }{ { @@ -587,7 +587,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { suggestedResourceSizeAtMax: createResourceList("1000m", "1Gi"), }, args: args{ - tortoise: createTortoiseWithCondition(map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + tortoise: createTortoiseWithCondition(map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("500m"), }, @@ -607,7 +607,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { suggestedResourceSizeAtMax: createResourceList("1000m", "1Gi"), }, args: args{ - tortoise: createTortoiseWithCondition(map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + tortoise: createTortoiseWithCondition(map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("1500m"), }, @@ -628,7 +628,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { minimumMinReplicas: 3, }, args: args{ - tortoise: createTortoiseWithCondition(map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + tortoise: createTortoiseWithCondition(map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("120m"), }, @@ -648,7 +648,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { suggestedResourceSizeAtMax: createResourceList("1000m", "1Gi"), }, args: args{ - tortoise: createVerticalTortoiseWithCondition(map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + tortoise: createVerticalTortoiseWithCondition(map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("120m"), }, @@ -669,7 +669,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { }, args: args{ tortoise: createTortoiseWithMultipleContainersWithCondition( - map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("80m"), }, @@ -677,7 +677,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { Quantity: resource.MustParse("9Gi"), }, }, - map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: { Quantity: resource.MustParse("800m"), }, @@ -749,7 +749,7 @@ func TestService_UpdateVPARecommendation(t *testing.T) { t.Errorf("updateVPARecommendation() error = %v, wantErr %v", err, tt.wantErr) return } - if d := cmp.Diff(got, tt.want, cmpopts.IgnoreTypes(metav1.Time{}, v1alpha1.Conditions{})); d != "" { + if d := cmp.Diff(got, tt.want, cmpopts.IgnoreTypes(metav1.Time{}, v1beta1.Conditions{})); d != "" { t.Errorf("updateVPARecommendation() diff = %s", d) } }) @@ -763,15 +763,15 @@ func createResourceList(cpu, memory string) corev1.ResourceList { corev1.ResourceMemory: resource.MustParse(memory), } } -func createVerticalTortoise() *v1alpha1.Tortoise { - return &v1alpha1.Tortoise{ - Spec: v1alpha1.TortoiseSpec{ - ResourcePolicy: []v1alpha1.ContainerResourcePolicy{ +func createVerticalTortoise() *v1beta1.Tortoise { + return &v1beta1.Tortoise{ + Spec: v1beta1.TortoiseSpec{ + ResourcePolicy: []v1beta1.ContainerResourcePolicy{ { ContainerName: "test-container", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeVertical, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeVertical, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeVertical, + corev1.ResourceMemory: v1beta1.AutoscalingTypeVertical, }, MinAllocatedResources: createResourceList("100m", "100Mi"), }, @@ -779,9 +779,9 @@ func createVerticalTortoise() *v1alpha1.Tortoise { }, } } -func createVerticalTortoiseWithCondition(vpaRecommendation map[corev1.ResourceName]v1alpha1.ResourceQuantity) *v1alpha1.Tortoise { +func createVerticalTortoiseWithCondition(vpaRecommendation map[corev1.ResourceName]v1beta1.ResourceQuantity) *v1beta1.Tortoise { tortoise := createVerticalTortoise() - tortoise.Status.Conditions.ContainerRecommendationFromVPA = []v1alpha1.ContainerRecommendationFromVPA{ + tortoise.Status.Conditions.ContainerRecommendationFromVPA = []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "test-container", Recommendation: vpaRecommendation, @@ -789,15 +789,15 @@ func createVerticalTortoiseWithCondition(vpaRecommendation map[corev1.ResourceNa } return tortoise } -func createTortoise() *v1alpha1.Tortoise { - return &v1alpha1.Tortoise{ - Spec: v1alpha1.TortoiseSpec{ - ResourcePolicy: []v1alpha1.ContainerResourcePolicy{ +func createTortoise() *v1beta1.Tortoise { + return &v1beta1.Tortoise{ + Spec: v1beta1.TortoiseSpec{ + ResourcePolicy: []v1beta1.ContainerResourcePolicy{ { ContainerName: "test-container", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeHorizontal, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeHorizontal, }, MinAllocatedResources: createResourceList("100m", "100Mi"), }, @@ -826,23 +826,23 @@ func createDeployment(replicas int32, cpu, memory string) *v1.Deployment { }, } } -func createTortoiseWithMultipleContainers() *v1alpha1.Tortoise { - return &v1alpha1.Tortoise{ - Spec: v1alpha1.TortoiseSpec{ - ResourcePolicy: []v1alpha1.ContainerResourcePolicy{ +func createTortoiseWithMultipleContainers() *v1beta1.Tortoise { + return &v1beta1.Tortoise{ + Spec: v1beta1.TortoiseSpec{ + ResourcePolicy: []v1beta1.ContainerResourcePolicy{ { ContainerName: "test-container", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeHorizontal, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeHorizontal, }, MinAllocatedResources: createResourceList("100m", "100Mi"), }, { ContainerName: "test-container2", - AutoscalingPolicy: map[corev1.ResourceName]v1alpha1.AutoscalingType{ - corev1.ResourceCPU: v1alpha1.AutoscalingTypeHorizontal, - corev1.ResourceMemory: v1alpha1.AutoscalingTypeHorizontal, + AutoscalingPolicy: map[corev1.ResourceName]v1beta1.AutoscalingType{ + corev1.ResourceCPU: v1beta1.AutoscalingTypeHorizontal, + corev1.ResourceMemory: v1beta1.AutoscalingTypeHorizontal, }, MinAllocatedResources: createResourceList("100m", "100Mi"), }, @@ -878,9 +878,9 @@ func createMultipleContainersDeployment(replicas int32, cpu1, cpu2, memory1, mem } } -func createTortoiseWithMultipleContainersWithCondition(vpaRecommendation1, vpaRecommendation2 map[corev1.ResourceName]v1alpha1.ResourceQuantity) *v1alpha1.Tortoise { +func createTortoiseWithMultipleContainersWithCondition(vpaRecommendation1, vpaRecommendation2 map[corev1.ResourceName]v1beta1.ResourceQuantity) *v1beta1.Tortoise { tortoise := createTortoiseWithMultipleContainers() - tortoise.Status.Conditions.ContainerRecommendationFromVPA = []v1alpha1.ContainerRecommendationFromVPA{ + tortoise.Status.Conditions.ContainerRecommendationFromVPA = []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "test-container", Recommendation: vpaRecommendation1, @@ -893,9 +893,9 @@ func createTortoiseWithMultipleContainersWithCondition(vpaRecommendation1, vpaRe return tortoise } -func createTortoiseWithCondition(vpaRecommendation map[corev1.ResourceName]v1alpha1.ResourceQuantity) *v1alpha1.Tortoise { +func createTortoiseWithCondition(vpaRecommendation map[corev1.ResourceName]v1beta1.ResourceQuantity) *v1beta1.Tortoise { tortoise := createTortoise() - tortoise.Status.Conditions.ContainerRecommendationFromVPA = []v1alpha1.ContainerRecommendationFromVPA{ + tortoise.Status.Conditions.ContainerRecommendationFromVPA = []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "test-container", Recommendation: vpaRecommendation, @@ -904,11 +904,11 @@ func createTortoiseWithCondition(vpaRecommendation map[corev1.ResourceName]v1alp return tortoise } -func createTortoiseWithVPARecommendation(cpu, memory string) *v1alpha1.Tortoise { +func createTortoiseWithVPARecommendation(cpu, memory string) *v1beta1.Tortoise { tortoise := createTortoise() - tortoise.Status.Recommendations = v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + tortoise.Status.Recommendations = v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "test-container", RecommendedResource: createResourceList(cpu, memory), @@ -919,11 +919,11 @@ func createTortoiseWithVPARecommendation(cpu, memory string) *v1alpha1.Tortoise return tortoise } -func createVerticalTortoiseWithVPARecommendation(cpu, memory string) *v1alpha1.Tortoise { +func createVerticalTortoiseWithVPARecommendation(cpu, memory string) *v1beta1.Tortoise { tortoise := createVerticalTortoise() - tortoise.Status.Recommendations = v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + tortoise.Status.Recommendations = v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "test-container", RecommendedResource: createResourceList(cpu, memory), @@ -933,11 +933,11 @@ func createVerticalTortoiseWithVPARecommendation(cpu, memory string) *v1alpha1.T } return tortoise } -func createTortoiseWithMultipleContainersWithVPARecommendation(cpu1, cpu2, memory1, memory2 string) *v1alpha1.Tortoise { +func createTortoiseWithMultipleContainersWithVPARecommendation(cpu1, cpu2, memory1, memory2 string) *v1beta1.Tortoise { tortoise := createTortoiseWithMultipleContainers() - tortoise.Status.Recommendations = v1alpha1.Recommendations{ - Vertical: &v1alpha1.VerticalRecommendations{ - ContainerResourceRecommendation: []v1alpha1.RecommendedContainerResources{ + tortoise.Status.Recommendations = v1beta1.Recommendations{ + Vertical: v1beta1.VerticalRecommendations{ + ContainerResourceRecommendation: []v1beta1.RecommendedContainerResources{ { ContainerName: "test-container", RecommendedResource: createResourceList(cpu1, memory1), diff --git a/pkg/tortoise/tortoise.go b/pkg/tortoise/tortoise.go index f47d3179..4e9c02f7 100644 --- a/pkg/tortoise/tortoise.go +++ b/pkg/tortoise/tortoise.go @@ -20,7 +20,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/mercari/tortoise/api/v1alpha1" + "github.com/mercari/tortoise/api/v1beta1" ) const tortoiseFinalizer = "tortoise.autoscaling.mercari.com/finalizer" @@ -59,8 +59,8 @@ func New(c client.Client, recorder record.EventRecorder, rangeOfMinMaxReplicasRe }, nil } -func (s *Service) ShouldReconcileTortoiseNow(tortoise *v1alpha1.Tortoise, now time.Time) (bool, time.Duration) { - if tortoise.Spec.UpdateMode == v1alpha1.UpdateModeEmergency && tortoise.Status.TortoisePhase != v1alpha1.TortoisePhaseEmergency { +func (s *Service) ShouldReconcileTortoiseNow(tortoise *v1beta1.Tortoise, now time.Time) (bool, time.Duration) { + if tortoise.Spec.UpdateMode == v1beta1.UpdateModeEmergency && tortoise.Status.TortoisePhase != v1beta1.TortoisePhaseEmergency { // Tortoise which is emergency mode, but hasn't been handled by the controller yet. It should be updated ASAP. return true, 0 } @@ -75,7 +75,7 @@ func (s *Service) ShouldReconcileTortoiseNow(tortoise *v1alpha1.Tortoise, now ti return false, lastTime.Add(s.tortoiseUpdateInterval).Sub(now) } -func (s *Service) UpdateTortoisePhase(tortoise *v1alpha1.Tortoise, dm *appv1.Deployment) *v1alpha1.Tortoise { +func (s *Service) UpdateTortoisePhase(tortoise *v1beta1.Tortoise, dm *appv1.Deployment) *v1beta1.Tortoise { switch tortoise.Status.TortoisePhase { case "": tortoise = s.initializeTortoise(tortoise, dm) @@ -85,33 +85,33 @@ func (s *Service) UpdateTortoisePhase(tortoise *v1alpha1.Tortoise, dm *appv1.Dep } s.recorder.Event(tortoise, corev1.EventTypeNormal, "Initialized", fmt.Sprintf("Tortoise is initialized and starts to gather data to make recommendations. It will take %s to finish gathering data and then tortoise starts to work actually", r)) - case v1alpha1.TortoisePhaseInitializing: + case v1beta1.TortoisePhaseInitializing: // change it to GatheringData anyway. Later the controller may change it back to initialize if VPA isn't ready. - tortoise.Status.TortoisePhase = v1alpha1.TortoisePhaseGatheringData - case v1alpha1.TortoisePhaseGatheringData: + tortoise.Status.TortoisePhase = v1beta1.TortoisePhaseGatheringData + case v1beta1.TortoisePhaseGatheringData: tortoise = s.checkIfTortoiseFinishedGatheringData(tortoise) - if tortoise.Status.TortoisePhase == v1alpha1.TortoisePhaseWorking { + if tortoise.Status.TortoisePhase == v1beta1.TortoisePhaseWorking { s.recorder.Event(tortoise, corev1.EventTypeNormal, "Working", "Tortoise finishes gathering data and it starts to work on autoscaling") } - case v1alpha1.TortoisePhaseEmergency: - if tortoise.Spec.UpdateMode != v1alpha1.UpdateModeEmergency { + case v1beta1.TortoisePhaseEmergency: + if tortoise.Spec.UpdateMode != v1beta1.UpdateModeEmergency { // Emergency mode is turned off. s.recorder.Event(tortoise, corev1.EventTypeNormal, "Working", "Emergency mode is turned off. Tortoise starts to work on autoscaling normally") - tortoise.Status.TortoisePhase = v1alpha1.TortoisePhaseEmergency + tortoise.Status.TortoisePhase = v1beta1.TortoisePhaseEmergency } } - if tortoise.Spec.UpdateMode == v1alpha1.UpdateModeEmergency { - if tortoise.Status.TortoisePhase != v1alpha1.TortoisePhaseEmergency { + if tortoise.Spec.UpdateMode == v1beta1.UpdateModeEmergency { + if tortoise.Status.TortoisePhase != v1beta1.TortoisePhaseEmergency { s.recorder.Event(tortoise, corev1.EventTypeNormal, "Emergency", "Tortoise is in Emergency mode") - tortoise.Status.TortoisePhase = v1alpha1.TortoisePhaseEmergency + tortoise.Status.TortoisePhase = v1beta1.TortoisePhaseEmergency } } return tortoise } -func (s *Service) checkIfTortoiseFinishedGatheringData(tortoise *v1alpha1.Tortoise) *v1alpha1.Tortoise { +func (s *Service) checkIfTortoiseFinishedGatheringData(tortoise *v1beta1.Tortoise) *v1beta1.Tortoise { for _, r := range tortoise.Status.Recommendations.Horizontal.MinReplicas { if r.Value == 0 { return tortoise @@ -123,24 +123,24 @@ func (s *Service) checkIfTortoiseFinishedGatheringData(tortoise *v1alpha1.Tortoi } } - tortoise.Status.TortoisePhase = v1alpha1.TortoisePhaseWorking + tortoise.Status.TortoisePhase = v1beta1.TortoisePhaseWorking return tortoise } -func (s *Service) initializeMinMaxReplicas(tortoise *v1alpha1.Tortoise) *v1alpha1.Tortoise { - recommendations := []v1alpha1.ReplicasRecommendation{} +func (s *Service) initializeMinMaxReplicas(tortoise *v1beta1.Tortoise) *v1beta1.Tortoise { + recommendations := []v1beta1.ReplicasRecommendation{} from := 0 to := s.rangeOfMinMaxReplicasRecommendationHour weekDay := time.Sunday for { if s.minMaxReplicasRoutine == "daily" { - recommendations = append(recommendations, v1alpha1.ReplicasRecommendation{ + recommendations = append(recommendations, v1beta1.ReplicasRecommendation{ From: from, To: to, TimeZone: s.timeZone.String(), }) } else if s.minMaxReplicasRoutine == "weekly" { - recommendations = append(recommendations, v1alpha1.ReplicasRecommendation{ + recommendations = append(recommendations, v1beta1.ReplicasRecommendation{ From: from, To: to, TimeZone: s.timeZone.String(), @@ -161,28 +161,25 @@ func (s *Service) initializeMinMaxReplicas(tortoise *v1alpha1.Tortoise) *v1alpha from += s.rangeOfMinMaxReplicasRecommendationHour to += s.rangeOfMinMaxReplicasRecommendationHour } - if tortoise.Status.Recommendations.Horizontal == nil { - tortoise.Status.Recommendations.Horizontal = &v1alpha1.HorizontalRecommendations{} - } tortoise.Status.Recommendations.Horizontal.MinReplicas = recommendations tortoise.Status.Recommendations.Horizontal.MaxReplicas = recommendations return tortoise } -func (s *Service) initializeTortoise(tortoise *v1alpha1.Tortoise, dm *appv1.Deployment) *v1alpha1.Tortoise { +func (s *Service) initializeTortoise(tortoise *v1beta1.Tortoise, dm *appv1.Deployment) *v1beta1.Tortoise { tortoise = s.initializeMinMaxReplicas(tortoise) - tortoise.Status.TortoisePhase = v1alpha1.TortoisePhaseInitializing + tortoise.Status.TortoisePhase = v1beta1.TortoisePhaseInitializing - tortoise.Status.Conditions.ContainerRecommendationFromVPA = make([]v1alpha1.ContainerRecommendationFromVPA, len(dm.Spec.Template.Spec.Containers)) + tortoise.Status.Conditions.ContainerRecommendationFromVPA = make([]v1beta1.ContainerRecommendationFromVPA, len(dm.Spec.Template.Spec.Containers)) for i, c := range dm.Spec.Template.Spec.Containers { - tortoise.Status.Conditions.ContainerRecommendationFromVPA[i] = v1alpha1.ContainerRecommendationFromVPA{ + tortoise.Status.Conditions.ContainerRecommendationFromVPA[i] = v1beta1.ContainerRecommendationFromVPA{ ContainerName: c.Name, - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, @@ -193,7 +190,7 @@ func (s *Service) initializeTortoise(tortoise *v1alpha1.Tortoise, dm *appv1.Depl return tortoise.DeepCopy() } -func (s *Service) UpdateUpperRecommendation(tortoise *v1alpha1.Tortoise, vpa *v1.VerticalPodAutoscaler) *v1alpha1.Tortoise { +func (s *Service) UpdateUpperRecommendation(tortoise *v1beta1.Tortoise, vpa *v1.VerticalPodAutoscaler) *v1beta1.Tortoise { upperMap := make(map[string]map[corev1.ResourceName]resource.Quantity, len(vpa.Status.Recommendation.ContainerRecommendations)) for _, c := range vpa.Status.Recommendation.ContainerRecommendations { upperMap[c.ContainerName] = make(map[corev1.ResourceName]resource.Quantity, len(c.UpperBound)) @@ -216,7 +213,7 @@ func (s *Service) UpdateUpperRecommendation(tortoise *v1alpha1.Tortoise, vpa *v1 currentTarget := targetMap[r.ContainerName][rn] recommendation := max.Quantity - rq := v1alpha1.ResourceQuantity{ + rq := v1beta1.ResourceQuantity{ Quantity: currentTarget, UpdatedAt: metav1.Now(), } @@ -233,21 +230,21 @@ func (s *Service) UpdateUpperRecommendation(tortoise *v1alpha1.Tortoise, vpa *v1 return tortoise } -func (s *Service) GetTortoise(ctx context.Context, namespacedName types.NamespacedName) (*v1alpha1.Tortoise, error) { - t := &v1alpha1.Tortoise{} +func (s *Service) GetTortoise(ctx context.Context, namespacedName types.NamespacedName) (*v1beta1.Tortoise, error) { + t := &v1beta1.Tortoise{} if err := s.c.Get(ctx, namespacedName, t); err != nil { return nil, fmt.Errorf("failed to get tortoise: %w", err) } return t, nil } -func (s *Service) AddFinalizer(ctx context.Context, tortoise *v1alpha1.Tortoise) error { +func (s *Service) AddFinalizer(ctx context.Context, tortoise *v1beta1.Tortoise) error { if controllerutil.ContainsFinalizer(tortoise, tortoiseFinalizer) { return nil } updateFn := func() error { - retTortoise := &v1alpha1.Tortoise{} + retTortoise := &v1beta1.Tortoise{} err := s.c.Get(ctx, client.ObjectKeyFromObject(tortoise), retTortoise) if err != nil { return err @@ -264,13 +261,13 @@ func (s *Service) AddFinalizer(ctx context.Context, tortoise *v1alpha1.Tortoise) return nil } -func (s *Service) RemoveFinalizer(ctx context.Context, tortoise *v1alpha1.Tortoise) error { +func (s *Service) RemoveFinalizer(ctx context.Context, tortoise *v1beta1.Tortoise) error { if !controllerutil.ContainsFinalizer(tortoise, tortoiseFinalizer) { return nil } updateFn := func() error { - retTortoise := &v1alpha1.Tortoise{} + retTortoise := &v1beta1.Tortoise{} err := s.c.Get(ctx, client.ObjectKeyFromObject(tortoise), retTortoise) if err != nil { return err @@ -285,12 +282,12 @@ func (s *Service) RemoveFinalizer(ctx context.Context, tortoise *v1alpha1.Tortoi return nil } -func (s *Service) UpdateTortoiseStatus(ctx context.Context, originalTortoise *v1alpha1.Tortoise, now time.Time) (*v1alpha1.Tortoise, error) { +func (s *Service) UpdateTortoiseStatus(ctx context.Context, originalTortoise *v1beta1.Tortoise, now time.Time) (*v1beta1.Tortoise, error) { logger := log.FromContext(ctx) logger.V(4).Info("update tortoise status", "tortoise", klog.KObj(originalTortoise)) - retTortoise := &v1alpha1.Tortoise{} + retTortoise := &v1beta1.Tortoise{} updateFn := func() error { - retTortoise = &v1alpha1.Tortoise{} + retTortoise = &v1beta1.Tortoise{} err := s.c.Get(ctx, client.ObjectKeyFromObject(originalTortoise), retTortoise) if err != nil { return fmt.Errorf("get tortoise to update status: %w", err) @@ -317,7 +314,7 @@ func (s *Service) UpdateTortoiseStatus(ctx context.Context, originalTortoise *v1 return originalTortoise, nil } -func (s *Service) updateLastTimeUpdateTortoise(tortoise *v1alpha1.Tortoise, now time.Time) { +func (s *Service) updateLastTimeUpdateTortoise(tortoise *v1beta1.Tortoise, now time.Time) { s.mu.Lock() defer s.mu.Unlock() diff --git a/pkg/tortoise/tortoise_test.go b/pkg/tortoise/tortoise_test.go index 729fe170..974aa132 100644 --- a/pkg/tortoise/tortoise_test.go +++ b/pkg/tortoise/tortoise_test.go @@ -17,25 +17,25 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/mercari/tortoise/api/v1alpha1" + "github.com/mercari/tortoise/api/v1beta1" ) func TestService_updateUpperRecommendation(t *testing.T) { tests := []struct { name string - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise vpa *v1.VerticalPodAutoscaler - want *v1alpha1.Tortoise + want *v1beta1.Tortoise }{ { name: "success: the current recommendation on tortoise is less than the one on the current VPA", - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("1"), }, @@ -43,7 +43,7 @@ func TestService_updateUpperRecommendation(t *testing.T) { Quantity: resource.MustParse("1"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("5"), }, @@ -79,13 +79,13 @@ func TestService_updateUpperRecommendation(t *testing.T) { }, }, }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("5"), }, @@ -93,7 +93,7 @@ func TestService_updateUpperRecommendation(t *testing.T) { Quantity: resource.MustParse("8"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("5"), }, @@ -109,13 +109,13 @@ func TestService_updateUpperRecommendation(t *testing.T) { }, { name: "success: the current recommendation on tortoise is more than the upper bound on the current VPA", - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("1"), }, @@ -123,7 +123,7 @@ func TestService_updateUpperRecommendation(t *testing.T) { Quantity: resource.MustParse("1"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("5"), }, @@ -159,13 +159,13 @@ func TestService_updateUpperRecommendation(t *testing.T) { }, }, }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("2"), }, @@ -173,7 +173,7 @@ func TestService_updateUpperRecommendation(t *testing.T) { Quantity: resource.MustParse("2"), }, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ "cpu": { Quantity: resource.MustParse("5"), }, @@ -213,9 +213,9 @@ func TestService_InitializeTortoise(t *testing.T) { tests := []struct { name string fields fields - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise deployment *appv1.Deployment - want *v1alpha1.Tortoise + want *v1beta1.Tortoise }{ { fields: fields{ @@ -223,8 +223,8 @@ func TestService_InitializeTortoise(t *testing.T) { minMaxReplicasRoutine: "weekly", timeZone: jst, }, - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{}, + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{}, }, deployment: &appv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ @@ -242,28 +242,28 @@ func TestService_InitializeTortoise(t *testing.T) { }, }, }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - TortoisePhase: v1alpha1.TortoisePhaseInitializing, - Targets: v1alpha1.TargetsStatus{Deployment: "deployment"}, - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + TortoisePhase: v1beta1.TortoisePhaseInitializing, + Targets: v1beta1.TargetsStatus{Deployment: "deployment"}, + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, }, }, }, - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 8, @@ -391,7 +391,7 @@ func TestService_InitializeTortoise(t *testing.T) { TimeZone: timeZone, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 8, @@ -530,8 +530,8 @@ func TestService_InitializeTortoise(t *testing.T) { rangeOfMinMaxReplicasRecommendationHour: 8, timeZone: jst, }, - tortoise: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{}, + tortoise: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{}, }, deployment: &appv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ @@ -549,28 +549,28 @@ func TestService_InitializeTortoise(t *testing.T) { }, }, }, - want: &v1alpha1.Tortoise{ - Status: v1alpha1.TortoiseStatus{ - TortoisePhase: v1alpha1.TortoisePhaseInitializing, - Targets: v1alpha1.TargetsStatus{Deployment: "deployment"}, - Conditions: v1alpha1.Conditions{ - ContainerRecommendationFromVPA: []v1alpha1.ContainerRecommendationFromVPA{ + want: &v1beta1.Tortoise{ + Status: v1beta1.TortoiseStatus{ + TortoisePhase: v1beta1.TortoisePhaseInitializing, + Targets: v1beta1.TargetsStatus{Deployment: "deployment"}, + Conditions: v1beta1.Conditions{ + ContainerRecommendationFromVPA: []v1beta1.ContainerRecommendationFromVPA{ { ContainerName: "app", - Recommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + Recommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, - MaxRecommendation: map[corev1.ResourceName]v1alpha1.ResourceQuantity{ + MaxRecommendation: map[corev1.ResourceName]v1beta1.ResourceQuantity{ corev1.ResourceCPU: {}, corev1.ResourceMemory: {}, }, }, }, }, - Recommendations: v1alpha1.Recommendations{ - Horizontal: &v1alpha1.HorizontalRecommendations{ - MinReplicas: []v1alpha1.ReplicasRecommendation{ + Recommendations: v1beta1.Recommendations{ + Horizontal: v1beta1.HorizontalRecommendations{ + MinReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 8, @@ -587,7 +587,7 @@ func TestService_InitializeTortoise(t *testing.T) { TimeZone: timeZone, }, }, - MaxReplicas: []v1alpha1.ReplicasRecommendation{ + MaxReplicas: []v1beta1.ReplicasRecommendation{ { From: 0, To: 8, @@ -630,7 +630,7 @@ func TestService_ShouldReconcileTortoiseNow(t *testing.T) { tests := []struct { name string lastTimeUpdateTortoise map[client.ObjectKey]time.Time - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise want bool wantDuration time.Duration }{ @@ -639,13 +639,13 @@ func TestService_ShouldReconcileTortoiseNow(t *testing.T) { lastTimeUpdateTortoise: map[client.ObjectKey]time.Time{ client.ObjectKey{Name: "t2", Namespace: "default"}: now.Add(-1 * time.Second), }, - tortoise: &v1alpha1.Tortoise{ + tortoise: &v1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{ Name: "t", Namespace: "default", }, - Spec: v1alpha1.TortoiseSpec{ - UpdateMode: v1alpha1.AutoUpdateMode, + Spec: v1beta1.TortoiseSpec{ + UpdateMode: v1beta1.AutoUpdateMode, }, }, want: true, @@ -655,13 +655,13 @@ func TestService_ShouldReconcileTortoiseNow(t *testing.T) { lastTimeUpdateTortoise: map[client.ObjectKey]time.Time{ client.ObjectKey{Name: "t", Namespace: "default"}: now.Add(-1 * time.Second), }, - tortoise: &v1alpha1.Tortoise{ + tortoise: &v1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{ Name: "t", Namespace: "default", }, - Spec: v1alpha1.TortoiseSpec{ - UpdateMode: v1alpha1.AutoUpdateMode, + Spec: v1beta1.TortoiseSpec{ + UpdateMode: v1beta1.AutoUpdateMode, }, }, want: false, @@ -672,13 +672,13 @@ func TestService_ShouldReconcileTortoiseNow(t *testing.T) { lastTimeUpdateTortoise: map[client.ObjectKey]time.Time{ client.ObjectKey{Name: "t", Namespace: "default"}: now.Add(-1 * time.Second), }, - tortoise: &v1alpha1.Tortoise{ + tortoise: &v1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{ Name: "t", Namespace: "default", }, - Spec: v1alpha1.TortoiseSpec{ - UpdateMode: v1alpha1.UpdateModeEmergency, + Spec: v1beta1.TortoiseSpec{ + UpdateMode: v1beta1.UpdateModeEmergency, }, }, want: true, @@ -688,16 +688,16 @@ func TestService_ShouldReconcileTortoiseNow(t *testing.T) { lastTimeUpdateTortoise: map[client.ObjectKey]time.Time{ client.ObjectKey{Name: "t", Namespace: "default"}: now.Add(-1 * time.Second), }, - tortoise: &v1alpha1.Tortoise{ + tortoise: &v1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{ Name: "t", Namespace: "default", }, - Spec: v1alpha1.TortoiseSpec{ - UpdateMode: v1alpha1.UpdateModeEmergency, + Spec: v1beta1.TortoiseSpec{ + UpdateMode: v1beta1.UpdateModeEmergency, }, - Status: v1alpha1.TortoiseStatus{ - TortoisePhase: v1alpha1.TortoisePhaseEmergency, + Status: v1beta1.TortoiseStatus{ + TortoisePhase: v1beta1.TortoisePhaseEmergency, }, }, want: false, @@ -723,25 +723,25 @@ func TestService_ShouldReconcileTortoiseNow(t *testing.T) { func TestService_UpdateTortoiseStatus(t *testing.T) { now := time.Now() type args struct { - t *v1alpha1.Tortoise + t *v1beta1.Tortoise now time.Time } tests := []struct { name string - originalTortoise *v1alpha1.Tortoise + originalTortoise *v1beta1.Tortoise args args wantLastTimeUpdateTortoise map[client.ObjectKey]time.Time }{ { name: "success", - originalTortoise: &v1alpha1.Tortoise{ + originalTortoise: &v1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{Name: "t", Namespace: "test"}, }, args: args{ - t: &v1alpha1.Tortoise{ + t: &v1beta1.Tortoise{ ObjectMeta: metav1.ObjectMeta{Name: "t", Namespace: "test"}, - Status: v1alpha1.TortoiseStatus{ - TortoisePhase: v1alpha1.TortoisePhaseInitializing, + Status: v1beta1.TortoiseStatus{ + TortoisePhase: v1beta1.TortoisePhaseInitializing, }, }, now: now, @@ -754,7 +754,7 @@ func TestService_UpdateTortoiseStatus(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scheme := runtime.NewScheme() - err := v1alpha1.AddToScheme(scheme) + err := v1beta1.AddToScheme(scheme) if err != nil { t.Fatalf("failed to add to scheme: %v", err) } diff --git a/pkg/utils/tortoise_builder.go b/pkg/utils/tortoise_builder.go index 78f3cb93..c39c0497 100644 --- a/pkg/utils/tortoise_builder.go +++ b/pkg/utils/tortoise_builder.go @@ -1,16 +1,16 @@ package utils import ( - "github.com/mercari/tortoise/api/v1alpha1" + "github.com/mercari/tortoise/api/v1beta1" ) type TortoiseBuilder struct { - tortoise *v1alpha1.Tortoise + tortoise *v1beta1.Tortoise } func NewTortoiseBuilder() *TortoiseBuilder { return &TortoiseBuilder{ - tortoise: &v1alpha1.Tortoise{}, + tortoise: &v1beta1.Tortoise{}, } } @@ -24,50 +24,45 @@ func (b *TortoiseBuilder) SetNamespace(namespace string) *TortoiseBuilder { return b } -func (b *TortoiseBuilder) SetTargetRefs(targetRefs v1alpha1.TargetRefs) *TortoiseBuilder { +func (b *TortoiseBuilder) SetTargetRefs(targetRefs v1beta1.TargetRefs) *TortoiseBuilder { b.tortoise.Spec.TargetRefs = targetRefs return b } -func (b *TortoiseBuilder) SetDeletionPolicy(policy v1alpha1.DeletionPolicy) *TortoiseBuilder { +func (b *TortoiseBuilder) SetDeletionPolicy(policy v1beta1.DeletionPolicy) *TortoiseBuilder { b.tortoise.Spec.DeletionPolicy = policy return b } -func (b *TortoiseBuilder) SetUpdateMode(updateMode v1alpha1.UpdateMode) *TortoiseBuilder { +func (b *TortoiseBuilder) SetUpdateMode(updateMode v1beta1.UpdateMode) *TortoiseBuilder { b.tortoise.Spec.UpdateMode = updateMode return b } -func (b *TortoiseBuilder) AddResourcePolicy(resourcePolicy v1alpha1.ContainerResourcePolicy) *TortoiseBuilder { +func (b *TortoiseBuilder) AddResourcePolicy(resourcePolicy v1beta1.ContainerResourcePolicy) *TortoiseBuilder { b.tortoise.Spec.ResourcePolicy = append(b.tortoise.Spec.ResourcePolicy, resourcePolicy) return b } -func (b *TortoiseBuilder) AddFeatureGate(featureGate string) *TortoiseBuilder { - b.tortoise.Spec.FeatureGates = append(b.tortoise.Spec.FeatureGates, featureGate) - return b -} - -func (b *TortoiseBuilder) SetTortoisePhase(phase v1alpha1.TortoisePhase) *TortoiseBuilder { +func (b *TortoiseBuilder) SetTortoisePhase(phase v1beta1.TortoisePhase) *TortoiseBuilder { b.tortoise.Status.TortoisePhase = phase return b } -func (b *TortoiseBuilder) AddCondition(condition v1alpha1.ContainerRecommendationFromVPA) *TortoiseBuilder { +func (b *TortoiseBuilder) AddCondition(condition v1beta1.ContainerRecommendationFromVPA) *TortoiseBuilder { b.tortoise.Status.Conditions.ContainerRecommendationFromVPA = append(b.tortoise.Status.Conditions.ContainerRecommendationFromVPA, condition) return b } -func (b *TortoiseBuilder) SetRecommendations(recommendations v1alpha1.Recommendations) *TortoiseBuilder { +func (b *TortoiseBuilder) SetRecommendations(recommendations v1beta1.Recommendations) *TortoiseBuilder { b.tortoise.Status.Recommendations = recommendations return b } -func (b *TortoiseBuilder) SetTargetsStatus(targetsStatus v1alpha1.TargetsStatus) *TortoiseBuilder { +func (b *TortoiseBuilder) SetTargetsStatus(targetsStatus v1beta1.TargetsStatus) *TortoiseBuilder { b.tortoise.Status.Targets = targetsStatus return b } -func (b *TortoiseBuilder) Build() *v1alpha1.Tortoise { +func (b *TortoiseBuilder) Build() *v1beta1.Tortoise { return b.tortoise } diff --git a/pkg/vpa/service.go b/pkg/vpa/service.go index 24f65ef6..1d7ef12a 100644 --- a/pkg/vpa/service.go +++ b/pkg/vpa/service.go @@ -14,7 +14,7 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" - autoscalingv1alpha1 "github.com/mercari/tortoise/api/v1alpha1" + autoscalingv1beta1 "github.com/mercari/tortoise/api/v1beta1" "github.com/mercari/tortoise/pkg/annotation" "github.com/mercari/tortoise/pkg/metrics" ) @@ -44,8 +44,8 @@ func TortoiseUpdaterVPAName(tortoiseName string) string { return TortoiseUpdaterVPANamePrefix + tortoiseName } -func (c *Service) DeleteTortoiseMonitorVPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) error { - if tortoise.Spec.DeletionPolicy == autoscalingv1alpha1.DeletionPolicyNoDelete { +func (c *Service) DeleteTortoiseMonitorVPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) error { + if tortoise.Spec.DeletionPolicy == autoscalingv1beta1.DeletionPolicyNoDelete { return nil } @@ -70,8 +70,8 @@ func (c *Service) DeleteTortoiseMonitorVPA(ctx context.Context, tortoise *autosc return nil } -func (c *Service) DeleteTortoiseUpdaterVPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) error { - if tortoise.Spec.DeletionPolicy == autoscalingv1alpha1.DeletionPolicyNoDelete { +func (c *Service) DeleteTortoiseUpdaterVPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) error { + if tortoise.Spec.DeletionPolicy == autoscalingv1beta1.DeletionPolicyNoDelete { return nil } @@ -96,7 +96,7 @@ func (c *Service) DeleteTortoiseUpdaterVPA(ctx context.Context, tortoise *autosc return nil } -func (c *Service) CreateTortoiseUpdaterVPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*v1.VerticalPodAutoscaler, *autoscalingv1alpha1.Tortoise, error) { +func (c *Service) CreateTortoiseUpdaterVPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*v1.VerticalPodAutoscaler, *autoscalingv1beta1.Tortoise, error) { auto := v1.UpdateModeAuto vpa := &v1.VerticalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ @@ -115,7 +115,7 @@ func (c *Service) CreateTortoiseUpdaterVPA(ctx context.Context, tortoise *autosc }, TargetRef: &autoscaling.CrossVersionObjectReference{ Kind: "Deployment", - Name: tortoise.Spec.TargetRefs.DeploymentName, + Name: tortoise.Spec.TargetRefs.ScaleTargetRef.Name, APIVersion: "apps/v1", }, UpdatePolicy: &v1.PodUpdatePolicy{ @@ -133,9 +133,9 @@ func (c *Service) CreateTortoiseUpdaterVPA(ctx context.Context, tortoise *autosc } vpa.Spec.ResourcePolicy.ContainerPolicies = crp - tortoise.Status.Targets.VerticalPodAutoscalers = append(tortoise.Status.Targets.VerticalPodAutoscalers, autoscalingv1alpha1.TargetStatusVerticalPodAutoscaler{ + tortoise.Status.Targets.VerticalPodAutoscalers = append(tortoise.Status.Targets.VerticalPodAutoscalers, autoscalingv1beta1.TargetStatusVerticalPodAutoscaler{ Name: vpa.Name, - Role: autoscalingv1alpha1.VerticalPodAutoscalerRoleUpdater, + Role: autoscalingv1beta1.VerticalPodAutoscalerRoleUpdater, }) vpa, err := c.c.AutoscalingV1().VerticalPodAutoscalers(vpa.Namespace).Create(ctx, vpa, metav1.CreateOptions{}) if err != nil { @@ -147,7 +147,7 @@ func (c *Service) CreateTortoiseUpdaterVPA(ctx context.Context, tortoise *autosc return vpa, tortoise, nil } -func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*v1.VerticalPodAutoscaler, *autoscalingv1alpha1.Tortoise, error) { +func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*v1.VerticalPodAutoscaler, *autoscalingv1beta1.Tortoise, error) { off := v1.UpdateModeOff vpa := &v1.VerticalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ @@ -161,7 +161,7 @@ func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autosc Spec: v1.VerticalPodAutoscalerSpec{ TargetRef: &autoscaling.CrossVersionObjectReference{ Kind: "Deployment", - Name: tortoise.Spec.TargetRefs.DeploymentName, + Name: tortoise.Spec.TargetRefs.ScaleTargetRef.Name, APIVersion: "apps/v1", }, UpdatePolicy: &v1.PodUpdatePolicy{ @@ -179,9 +179,9 @@ func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autosc } vpa.Spec.ResourcePolicy.ContainerPolicies = crp - tortoise.Status.Targets.VerticalPodAutoscalers = append(tortoise.Status.Targets.VerticalPodAutoscalers, autoscalingv1alpha1.TargetStatusVerticalPodAutoscaler{ + tortoise.Status.Targets.VerticalPodAutoscalers = append(tortoise.Status.Targets.VerticalPodAutoscalers, autoscalingv1beta1.TargetStatusVerticalPodAutoscaler{ Name: vpa.Name, - Role: autoscalingv1alpha1.VerticalPodAutoscalerRoleMonitor, + Role: autoscalingv1beta1.VerticalPodAutoscalerRoleMonitor, }) vpa, err := c.c.AutoscalingV1().VerticalPodAutoscalers(vpa.Namespace).Create(ctx, vpa, metav1.CreateOptions{}) @@ -194,7 +194,7 @@ func (c *Service) CreateTortoiseMonitorVPA(ctx context.Context, tortoise *autosc return vpa, tortoise, nil } -func (c *Service) UpdateVPAFromTortoiseRecommendation(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*v1.VerticalPodAutoscaler, error) { +func (c *Service) UpdateVPAFromTortoiseRecommendation(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*v1.VerticalPodAutoscaler, error) { retVPA := &v1.VerticalPodAutoscaler{} // we only want to record metric once in every reconcile loop. @@ -209,10 +209,10 @@ func (c *Service) UpdateVPAFromTortoiseRecommendation(ctx context.Context, torto if !metricsRecorded { for resourcename, value := range r.RecommendedResource { if resourcename == corev1.ResourceCPU { - metrics.ProposedCPURequest.WithLabelValues(tortoise.Name, tortoise.Namespace, r.ContainerName, tortoise.Spec.TargetRefs.DeploymentName, "Deployment").Set(float64(value.MilliValue())) + metrics.ProposedCPURequest.WithLabelValues(tortoise.Name, tortoise.Namespace, r.ContainerName, tortoise.Spec.TargetRefs.ScaleTargetRef.Name, tortoise.Spec.TargetRefs.ScaleTargetRef.Kind).Set(float64(value.MilliValue())) } if resourcename == corev1.ResourceMemory { - metrics.ProposedMemoryRequest.WithLabelValues(tortoise.Name, tortoise.Namespace, r.ContainerName, tortoise.Spec.TargetRefs.DeploymentName, "Deployment").Set(float64(value.Value())) + metrics.ProposedMemoryRequest.WithLabelValues(tortoise.Name, tortoise.Namespace, r.ContainerName, tortoise.Spec.TargetRefs.ScaleTargetRef.Name, tortoise.Spec.TargetRefs.ScaleTargetRef.Kind).Set(float64(value.Value())) } } metricsRecorded = true @@ -231,7 +231,7 @@ func (c *Service) UpdateVPAFromTortoiseRecommendation(ctx context.Context, torto } vpa.Status.Recommendation.ContainerRecommendations = newRecommendations retVPA = vpa - if tortoise.Spec.UpdateMode == autoscalingv1alpha1.UpdateModeOff { + if tortoise.Spec.UpdateMode == autoscalingv1beta1.UpdateModeOff { // don't update status if update mode is off. (= dryrun) return nil } @@ -243,14 +243,14 @@ func (c *Service) UpdateVPAFromTortoiseRecommendation(ctx context.Context, torto return retVPA, fmt.Errorf("update VPA status: %w", err) } - if tortoise.Spec.UpdateMode != autoscalingv1alpha1.UpdateModeOff { + if tortoise.Spec.UpdateMode != autoscalingv1beta1.UpdateModeOff { c.recorder.Event(tortoise, corev1.EventTypeNormal, "VPAUpdated", fmt.Sprintf("VPA %s/%s is updated by the recommendation. The Pods should also be updated with new resources soon by VPA if needed", retVPA.Namespace, retVPA.Name)) } return retVPA, nil } -func (c *Service) GetTortoiseUpdaterVPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*v1.VerticalPodAutoscaler, error) { +func (c *Service) GetTortoiseUpdaterVPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*v1.VerticalPodAutoscaler, error) { vpa, err := c.c.AutoscalingV1().VerticalPodAutoscalers(tortoise.Namespace).Get(ctx, TortoiseUpdaterVPAName(tortoise.Name), metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("failed to get updater vpa on tortoise: %w", err) @@ -258,7 +258,7 @@ func (c *Service) GetTortoiseUpdaterVPA(ctx context.Context, tortoise *autoscali return vpa, nil } -func (c *Service) GetTortoiseMonitorVPA(ctx context.Context, tortoise *autoscalingv1alpha1.Tortoise) (*v1.VerticalPodAutoscaler, bool, error) { +func (c *Service) GetTortoiseMonitorVPA(ctx context.Context, tortoise *autoscalingv1beta1.Tortoise) (*v1.VerticalPodAutoscaler, bool, error) { vpa, err := c.c.AutoscalingV1().VerticalPodAutoscalers(tortoise.Namespace).Get(ctx, TortoiseMonitorVPAName(tortoise.Name), metav1.GetOptions{}) if err != nil { return nil, false, fmt.Errorf("failed to get updater vpa on tortoise: %w", err)