diff --git a/Makefile b/Makefile index 8db4c7e2..d3741033 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,10 @@ test-debug: envtest ginkgo test-update: envtest ginkgo KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" UPDATE_TESTCASES=true $(GINKGO) -r --fail-fast +.PHONY: test-tortoisectl +test-tortoisectl: envtest + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -timeout 30s -v -run Test_TortoiseCtlStop ./cmd/tortoisectl/test/... + GINKGO ?= $(LOCALBIN)/ginkgo GINKGO_VERSION ?= v2.1.4 @@ -81,6 +85,7 @@ $(GINKGO): $(LOCALBIN) .PHONY: build build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go + go build -o bin/tortoisectl cmd/tortoisectl/main.go .PHONY: run run: manifests generate fmt vet ## Run a controller from your host. diff --git a/api/core/v1/pod_webhook.go b/api/core/v1/pod_webhook.go index 8b337af3..0affe213 100644 --- a/api/core/v1/pod_webhook.go +++ b/api/core/v1/pod_webhook.go @@ -118,7 +118,7 @@ func (h *PodWebhook) Default(ctx context.Context, obj runtime.Object) error { return nil } - h.podService.ModifyPodResource(pod, tortoise) + h.podService.ModifyPodSpecResource(&pod.Spec, tortoise) pod.Annotations[annotation.PodMutationAnnotation] = fmt.Sprintf("this pod is mutated by tortoise (%s)", tortoise.Name) return nil diff --git a/cmd/tortoisectl/commands/root.go b/cmd/tortoisectl/commands/root.go new file mode 100644 index 00000000..29d693f1 --- /dev/null +++ b/cmd/tortoisectl/commands/root.go @@ -0,0 +1,21 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "tortoisectl [COMMANDS]", + Short: "tortoisectl is a CLI for managing Tortoise", + Long: `tortoisectl is a CLI for managing Tortoise.`, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/tortoisectl/commands/stop.go b/cmd/tortoisectl/commands/stop.go new file mode 100644 index 00000000..f730c621 --- /dev/null +++ b/cmd/tortoisectl/commands/stop.go @@ -0,0 +1,121 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/homedir" + "sigs.k8s.io/controller-runtime/pkg/client" + + autoscalingv1beta3 "github.com/mercari/tortoise/api/v1beta3" + "github.com/mercari/tortoise/pkg/deployment" + "github.com/mercari/tortoise/pkg/pod" + "github.com/mercari/tortoise/pkg/stoper" +) + +var stopCmd = &cobra.Command{ + Use: "stop tortoise1 tortoise2...", + Short: "stop tortoise(s) safely", + Long: ` +stop is the command to temporarily turn off tortoise(s) easily and safely. + +It's intended to be used when your application is facing issues that might be caused by tortoise. +Specifically, it changes the tortoise updateMode to "Off" and restarts the deployment to bring the pods back to the original resource requests. + +Also, with the --no-lowering-resources flag, it patches the deployment directly +so that changing tortoise to Off won't result in lowering the resource request(s), damaging the service. +e.g., if the Deployment declares 1 CPU request, and the current Pods' request is 2 CPU mutated by Tortoise, +it'd patch the deployment to 2 CPU request to prevent a possible negative impact on the service. +`, + RunE: func(cmd *cobra.Command, args []string) error { + // validation + if stopAll { + if len(args) != 0 { + return fmt.Errorf("tortoise name shouldn't be specified because of --all flag") + } + } else { + if stopNamespace == "" { + return fmt.Errorf("namespace must be specified") + } + if len(args) == 0 { + return fmt.Errorf("tortoise name must be specified") + } + } + + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return fmt.Errorf("failed to build config: %v", err) + } + + client, err := client.New(config, client.Options{ + Scheme: scheme, + }) + if err != nil { + return fmt.Errorf("failed to create client: %v", err) + } + + recorder := record.NewBroadcaster().NewRecorder(scheme, corev1.EventSource{Component: "tortoisectl"}) + deploymentService := deployment.New(client, "", "", recorder) + podService, err := pod.New(map[string]int64{}, "", nil, nil) + if err != nil { + return fmt.Errorf("failed to create pod service: %v", err) + } + + stoperService := stoper.New(client, deploymentService, podService) + + opts := []stoper.StoprOption{} + if noLoweringResources { + opts = append(opts, stoper.NoLoweringResource) + } + + err = stoperService.Stop(cmd.Context(), args, stopNamespace, stopAll, os.Stdout, opts...) + if err != nil { + return fmt.Errorf("failed to stop tortoise(s): %v", err) + } + + return nil + }, +} + +var ( + // namespace to stop tortoise(s) in + stopNamespace string + // stop all tortoises in the specified namespace, or in all namespaces if no namespace is specified. + stopAll bool + // Stop tortoise without lowering resource requests. + // If this flag is specified and the current Deployment's resource request(s) is lower than the current Pods' request mutated by Tortoise, + // this CLI patches the deployment so that changing tortoise to Off won't result in lowering the resource request(s), damaging the service. + noLoweringResources bool + + // Path to KUBECONFIG + kubeconfig string + + scheme = runtime.NewScheme() +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(autoscalingv1beta3.AddToScheme(scheme)) + + rootCmd.AddCommand(stopCmd) + + if home := homedir.HomeDir(); home != "" { + stopCmd.Flags().StringVar(&kubeconfig, "kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") + } else { + stopCmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file") + } + + stopCmd.Flags().StringVarP(&stopNamespace, "namespace", "n", "", "namespace to stop tortoise(s) in") + stopCmd.Flags().BoolVarP(&stopAll, "all", "A", false, "stop all tortoises in the specified namespace, or in all namespaces if no namespace is specified.") + stopCmd.Flags().BoolVar(&noLoweringResources, "no-lowering-resources", false, `Stop tortoise without lowering resource requests. + If this flag is specified and the current Deployment's resource request(s) is lower than the current Pods' request mutated by Tortoise, + this CLI patches the deployment so that changing tortoise to Off won't result in lowering the resource request(s), damaging the service.`) +} diff --git a/cmd/tortoisectl/main.go b/cmd/tortoisectl/main.go new file mode 100644 index 00000000..a03dd8ae --- /dev/null +++ b/cmd/tortoisectl/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/mercari/tortoise/cmd/tortoisectl/commands" + +func main() { + commands.Execute() +} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-a.yaml new file mode 100644 index 00000000..46c2275a --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-a.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app-a + namespace: success-all-in-all-namespace +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-another-ns.yaml new file mode 100644 index 00000000..f8ff036d --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-another-ns.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app + namespace: success-all-in-all-namespace-2 +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-b.yaml new file mode 100644 index 00000000..0c03e231 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-b.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app-b + namespace: success-all-in-all-namespace +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-c.yaml new file mode 100644 index 00000000..89371713 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/deployments/deployment-c.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app-c + namespace: success-all-in-all-namespace +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-a.yaml new file mode 100644 index 00000000..9b26605e --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-a.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-a + namespace: success-all-in-all-namespace +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-a + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-another-ns.yaml new file mode 100644 index 00000000..7b8f0894 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-another-ns.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-all-in-all-namespace-2 +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-b.yaml new file mode 100644 index 00000000..6cabfb7f --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-b.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-b + namespace: success-all-in-all-namespace +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-b + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-c.yaml new file mode 100644 index 00000000..b2302b99 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/after/tortoises/tortoise-c.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-c + namespace: success-all-in-all-namespace +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-c + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-a.yaml new file mode 100644 index 00000000..e586cc79 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-a.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app-a + namespace: success-all-in-all-namespace +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-another-ns.yaml new file mode 100644 index 00000000..6133789a --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-another-ns.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app + namespace: success-all-in-all-namespace-2 +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-b.yaml new file mode 100644 index 00000000..726a34bc --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-b.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app-b + namespace: success-all-in-all-namespace +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-c.yaml new file mode 100644 index 00000000..7079fa9d --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/deployments/deployment-c.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app-c + namespace: success-all-in-all-namespace +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-a.yaml new file mode 100644 index 00000000..a874795f --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-a.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-a + namespace: success-all-in-all-namespace +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-a +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-another-ns.yaml new file mode 100644 index 00000000..d47f27d2 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-another-ns.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-all-in-all-namespace-2 +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + UpdatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-b.yaml new file mode 100644 index 00000000..abb49f7b --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-b.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-b + namespace: success-all-in-all-namespace +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-b +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-c.yaml new file mode 100644 index 00000000..d2848f62 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-all-namespace/before/tortoises/tortoise-c.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-c + namespace: success-all-in-all-namespace +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-c +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-a.yaml new file mode 100644 index 00000000..7d2052e0 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-a.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app-a + namespace: success-all-in-namespace +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-another-ns.yaml new file mode 100644 index 00000000..f4796763 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-another-ns.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app + namespace: success-all-in-namespace-2 +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-b.yaml new file mode 100644 index 00000000..b7d4ace7 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-b.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app-b + namespace: success-all-in-namespace +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-c.yaml new file mode 100644 index 00000000..85c2a982 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/deployments/deployment-c.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app-c + namespace: success-all-in-namespace +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-a.yaml new file mode 100644 index 00000000..c18cd7c0 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-a.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-a + namespace: success-all-in-namespace +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-a + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-another-ns.yaml new file mode 100644 index 00000000..fe8b03fd --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-another-ns.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-all-in-namespace-2 +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updateMode: Auto +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-b.yaml new file mode 100644 index 00000000..ebb625f0 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-b.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-b + namespace: success-all-in-namespace +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-b + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-c.yaml new file mode 100644 index 00000000..79622982 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/after/tortoises/tortoise-c.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-c + namespace: success-all-in-namespace +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-c + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-a.yaml new file mode 100644 index 00000000..d3c325a6 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-a.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app-a + namespace: success-all-in-namespace +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-another-ns.yaml new file mode 100644 index 00000000..b0e0b498 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-another-ns.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app + namespace: success-all-in-namespace-2 +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-b.yaml new file mode 100644 index 00000000..63604fdd --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-b.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app-b + namespace: success-all-in-namespace +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-c.yaml new file mode 100644 index 00000000..98a00948 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/deployments/deployment-c.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app-c + namespace: success-all-in-namespace +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-a.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-a.yaml new file mode 100644 index 00000000..77f5393b --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-a.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-a + namespace: success-all-in-namespace +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-a +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-another-ns.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-another-ns.yaml new file mode 100644 index 00000000..dc34c69c --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-another-ns.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-all-in-namespace-2 +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + UpdatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-b.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-b.yaml new file mode 100644 index 00000000..578e03a0 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-b.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-b + namespace: success-all-in-namespace +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-b +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-c.yaml b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-c.yaml new file mode 100644 index 00000000..3c544d57 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-all-in-namespace/before/tortoises/tortoise-c.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise-c + namespace: success-all-in-namespace +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app-c +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/after/deployments/deployment.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/after/deployments/deployment.yaml new file mode 100644 index 00000000..f86a5895 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/after/deployments/deployment.yaml @@ -0,0 +1,44 @@ +metadata: + name: mercari-app + namespace: success-no-lowering-resources +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + sidecar.istio.io/inject: "true" + sidecar.istio.io/proxyCPU: 4 + sidecar.istio.io/proxyCPULimit: 12 + sidecar.istio.io/proxyMemory: 3Gi + sidecar.istio.io/proxyMemoryLimit: 3Gi + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "6" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/after/tortoises/tortoise.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/after/tortoises/tortoise.yaml new file mode 100644 index 00000000..38b7fb19 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/after/tortoises/tortoise.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-no-lowering-resources +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/before/deployments/deployment.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/before/deployments/deployment.yaml new file mode 100644 index 00000000..86e31522 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/before/deployments/deployment.yaml @@ -0,0 +1,29 @@ +metadata: + name: mercari-app + namespace: success-no-lowering-resources +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + sidecar.istio.io/inject: "true" + sidecar.istio.io/proxyCPU: 100m + sidecar.istio.io/proxyCPULimit: 300m + sidecar.istio.io/proxyMemory: 500Mi + sidecar.istio.io/proxyMemoryLimit: 500Mi + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "1" + memory: 10Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/before/tortoises/tortoise.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/before/tortoises/tortoise.yaml new file mode 100644 index 00000000..f5ac4721 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources-w-istio/before/tortoises/tortoise.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-no-lowering-resources +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources/after/deployments/deployment.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/after/deployments/deployment.yaml new file mode 100644 index 00000000..e6a9e182 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/after/deployments/deployment.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app + namespace: success-no-lowering-resources +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "6" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 3Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources/after/tortoises/tortoise.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/after/tortoises/tortoise.yaml new file mode 100644 index 00000000..38b7fb19 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/after/tortoises/tortoise.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-no-lowering-resources +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources/before/deployments/deployment.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/before/deployments/deployment.yaml new file mode 100644 index 00000000..08a7cb98 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/before/deployments/deployment.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app + namespace: success-no-lowering-resources +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "1" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "4" + memory: 2Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success-no-lowering-resources/before/tortoises/tortoise.yaml b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/before/tortoises/tortoise.yaml new file mode 100644 index 00000000..f5ac4721 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success-no-lowering-resources/before/tortoises/tortoise.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success-no-lowering-resources +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success/after/deployments/deployment.yaml b/cmd/tortoisectl/test/testdata/success/after/deployments/deployment.yaml new file mode 100644 index 00000000..8f528673 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success/after/deployments/deployment.yaml @@ -0,0 +1,48 @@ +metadata: + name: mercari-app + namespace: success +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mercari + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: updated + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + imagePullPolicy: Always + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - image: awesome-istio-proxy-image + imagePullPolicy: Always + name: istio-proxy + resources: + requests: + cpu: "1" + memory: 4Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: {} diff --git a/cmd/tortoisectl/test/testdata/success/after/tortoises/tortoise.yaml b/cmd/tortoisectl/test/testdata/success/after/tortoises/tortoise.yaml new file mode 100644 index 00000000..4da81b0e --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success/after/tortoises/tortoise.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success +spec: + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app + updateMode: "Off" +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/testdata/success/before/deployments/deployment.yaml b/cmd/tortoisectl/test/testdata/success/before/deployments/deployment.yaml new file mode 100644 index 00000000..eb2640e8 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success/before/deployments/deployment.yaml @@ -0,0 +1,30 @@ +metadata: + name: mercari-app + namespace: success +spec: + selector: + matchLabels: + app: mercari + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z" + creationTimestamp: null + labels: + app: mercari + spec: + containers: + - image: awesome-mercari-app-image + name: app + resources: + requests: + cpu: "10" + memory: 10Gi + - image: awesome-istio-proxy-image + name: istio-proxy + resources: + requests: + cpu: "1" + memory: 4Gi +status: {} diff --git a/cmd/tortoisectl/test/testdata/success/before/tortoises/tortoise.yaml b/cmd/tortoisectl/test/testdata/success/before/tortoises/tortoise.yaml new file mode 100644 index 00000000..ebc964c7 --- /dev/null +++ b/cmd/tortoisectl/test/testdata/success/before/tortoises/tortoise.yaml @@ -0,0 +1,141 @@ +metadata: + name: mercaritortoise + namespace: success +spec: + updateMode: "Auto" + targetRefs: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: mercari-app +status: + autoscalingPolicy: + - containerName: app + policy: + cpu: Horizontal + memory: Vertical + - containerName: istio-proxy + policy: + cpu: Horizontal + memory: Vertical + conditions: + containerRecommendationFromVPA: + - containerName: app + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + - containerName: istio-proxy + maxRecommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + recommendation: + cpu: + quantity: "3" + updatedAt: "2023-01-01T00:00:00Z" + memory: + quantity: 3Gi + updatedAt: "2023-01-01T00:00:00Z" + containerResourceRequests: + - containerName: app + resource: + cpu: "6" + memory: 3Gi + - containerName: istio-proxy + resource: + cpu: "4" + memory: 3Gi + tortoiseConditions: + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: the current number of replicas is not bigger than the preferred max + replica number + reason: ScaledUpBasedOnPreferredMaxReplicas + status: "False" + type: ScaledUpBasedOnPreferredMaxReplicas + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: HPA target utilization is updated + reason: HPATargetUtilizationUpdated + status: "True" + type: HPATargetUtilizationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + message: The recommendation is provided + status: "True" + type: VerticalRecommendationUpdated + - lastTransitionTime: "2023-01-01T00:00:00Z" + lastUpdateTime: "2023-01-01T00:00:00Z" + status: "False" + type: FailedToReconcile + containerResourcePhases: + - containerName: app + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + - containerName: istio-proxy + resourcePhases: + cpu: + lastTransitionTime: null + phase: Working + memory: + lastTransitionTime: "2023-01-01T00:00:00Z" + phase: Working + recommendations: + horizontal: + maxReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 20 + minReplicas: + - from: 0 + timezone: Local + to: 24 + updatedAt: "2023-01-01T00:00:00Z" + value: 5 + targetUtilizations: + - containerName: app + targetUtilization: + cpu: 50 + - containerName: istio-proxy + targetUtilization: + cpu: 75 + vertical: + containerResourceRecommendation: + - RecommendedResource: + cpu: "6" + memory: 3Gi + containerName: app + - RecommendedResource: + cpu: "4" + memory: 3Gi + containerName: istio-proxy + targets: + horizontalPodAutoscaler: tortoise-hpa-mercari + scaleTargetRef: + kind: "" + name: "" + verticalPodAutoscalers: + - name: tortoise-monitor-mercari + role: Monitor + tortoisePhase: Working diff --git a/cmd/tortoisectl/test/tortoisectl_test.go b/cmd/tortoisectl/test/tortoisectl_test.go new file mode 100644 index 00000000..319e7090 --- /dev/null +++ b/cmd/tortoisectl/test/tortoisectl_test.go @@ -0,0 +1,504 @@ +package tortoisectl_test + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + appv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/yaml" + + "github.com/mercari/tortoise/api/v1beta3" + "github.com/mercari/tortoise/pkg/annotation" +) + +func buildTortoiseCtl(t *testing.T) { + t.Helper() + + _, _, err := execCommand(t, "go", "build", "-o", "./testdata/bin/tortoisectl", "../main.go") + if err != nil { + t.Fatalf("Failed to build tortoisectl: %v", err) + } +} + +func prepareCluster(t *testing.T) (*envtest.Environment, *rest.Config) { + t.Helper() + + testEnv := &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + // cfg is defined in this file globally. + cfg, err := testEnv.Start() + if err != nil { + t.Fatalf("Failed to start test environment: %v", err) + } + + return testEnv, cfg +} + +func destryCluster(t *testing.T, testEnv *envtest.Environment) { + t.Helper() + + err := testEnv.Stop() + if err != nil { + t.Fatalf("Failed to destroy test environment: %v", err) + } +} + +func Test_TortoiseCtlStop(t *testing.T) { + update := false + if os.Getenv("UPDATE_TESTCASES") == "true" { + update = true + } + + // Build the latest binary. + buildTortoiseCtl(t) + testEnv, cfg := prepareCluster(t) + defer destryCluster(t, testEnv) + + // create the clientset + clientset, err := kubernetes.NewForConfig(cfg) + if err != nil { + t.Fatalf("Failed to create clientset: %v", err) + } + + scheme := runtime.NewScheme() + err = v1beta3.AddToScheme(scheme) + if err != nil { + t.Fatalf("Failed to add scheme: %v", err) + } + kubeconfig := strings.Split(testEnv.ControlPlane.KubeCtl().Opts[0], "=")[1] //nolint:staticcheck + + tortoiseclient, err := client.New(cfg, client.Options{ + Scheme: scheme, + }) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + tests := []struct { + name string + // tortoisectl stop + options []string + dir string // dir is also the namespace + expectedFailure bool + }{ + { + name: "stop tortoise successfully", + options: []string{ + "--namespace", "success", "mercaritortoise", + }, + dir: "success", + }, + { + name: "stop tortoise successfully with --no-lowering-resources", + options: []string{ + "--namespace", "success-no-lowering-resources", "--no-lowering-resources", "mercaritortoise", + }, + dir: "success-no-lowering-resources", + }, + { + name: "stop tortoise successfully with --no-lowering-resources (istio annotation)", + options: []string{ + "--namespace", "success-no-lowering-resources", "--no-lowering-resources", "mercaritortoise", + }, + dir: "success-no-lowering-resources-w-istio", + }, + { + name: "stop all tortoises in a namespace successfully with --all", + options: []string{ + "--namespace", "success-all-in-namespace", "--all", + }, + dir: "success-all-in-namespace", + }, + { + name: "stop all tortoises in all namespace successfully with --all", + options: []string{ + "--all", + }, + dir: "success-all-in-all-namespace", + }, + { + name: "cannot use --all and specify a tortoise name at the same time", + options: []string{ + "--namespace", "success", "--all", "mercaritortoise", + }, + dir: "success", + expectedFailure: true, + }, + { + name: "have to specify a tortoise name if no --all", + options: []string{ + "--namespace", "success", + }, + dir: "success", + expectedFailure: true, + }, + { + name: "have to specify a namespace if no --all", + options: []string{ + "hoge", + }, + dir: "success", + expectedFailure: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + namespaces := map[string]struct{}{} + t.Cleanup(func() { + // remove all namespaces + for namespace := range namespaces { + deployments, err := clientset.AppsV1().Deployments(namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + t.Fatalf("Failed to list deployments: %v", err) + } + for _, deploy := range deployments.Items { + err = clientset.AppsV1().Deployments(namespace).Delete(context.Background(), deploy.Name, metav1.DeleteOptions{}) + if err != nil { + t.Fatalf("Failed to delete deployment: %v", err) + } + } + + tortoises := &v1beta3.TortoiseList{} + err = tortoiseclient.List(context.Background(), tortoises, &client.ListOptions{Namespace: namespace}) + if err != nil { + t.Fatalf("Failed to list tortoises: %v", err) + } + for _, tortoise := range tortoises.Items { + err = tortoiseclient.Delete(context.Background(), &tortoise) + if err != nil { + t.Fatalf("Failed to delete tortoise: %v", err) + } + } + + err = clientset.CoreV1().Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{}) + if err != nil { + t.Fatalf("Failed to delete namespace: %v", err) + } + + ns, err := clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get namespace: %v", err) + } + + ns.TypeMeta = metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + } + ns.Spec.Finalizers = nil + patch, err := json.Marshal(ns) + if err != nil { + t.Fatalf("Failed to marshal namespace: %v", err) + } + result := clientset.RESTClient().Put().AbsPath(fmt.Sprintf("/api/v1/namespaces/%s/finalize", namespace)).Body(patch).Do(context.Background()) + if result.Error() != nil { + t.Fatalf("Failed to update namespace: %v", result.Error()) + } + + } + + for namespace := range namespaces { + err := wait.PollUntilContextCancel(context.Background(), 500*time.Millisecond, true, func(ctx context.Context) (bool, error) { + _, err := clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) + return err != nil, nil + }) + if err != nil { + t.Fatalf("Failed to wait for namespace deletion: %v", err) + } + } + }) + + deploymentNameToFileName := map[client.ObjectKey]string{} + originalDeployments := map[client.ObjectKey]appv1.Deployment{} + deploymentDir := fmt.Sprintf("./testdata/%s/before/deployments", tt.dir) + err = filepath.Walk(deploymentDir, func(deploymentYaml string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + y, err := os.ReadFile(deploymentYaml) + if err != nil { + t.Fatalf("Failed to read deployment yaml: %v", err) + } + deploy := &appv1.Deployment{} + err = yaml.Unmarshal(y, deploy) + if err != nil { + t.Fatalf("Failed to unmarshal deployment yaml: %v", err) + } + + namespace := deploy.Namespace + if _, ok := namespaces[namespace]; !ok { + // Create a namespace + _, err = clientset.CoreV1().Namespaces().Create(context.Background(), &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Failed to create namespace: %v", err) + } + + namespaces[namespace] = struct{}{} + } + + // Create a deployment + _, err = clientset.AppsV1().Deployments(namespace).Create(context.Background(), deploy, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Failed to create deployment: %v", err) + } + + originalDeployments[client.ObjectKey{ + Namespace: deploy.Namespace, + Name: deploy.Name, + }] = *deploy + + deploymentNameToFileName[client.ObjectKey{ + Namespace: deploy.Namespace, + Name: deploy.Name, + }] = filepath.Base(deploymentYaml) + + return nil + }) + + tortoiseNameToFileName := map[client.ObjectKey]string{} + tortoiseDir := fmt.Sprintf("./testdata/%s/before/tortoises", tt.dir) + err = filepath.Walk(tortoiseDir, func(tortoiseYaml string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + y, err := os.ReadFile(tortoiseYaml) + if err != nil { + t.Fatalf("Failed to read tortoise yaml: %v", err) + } + + tortoise := &v1beta3.Tortoise{} + err = yaml.Unmarshal(y, tortoise) + if err != nil { + t.Fatalf("Failed to unmarshal tortoise yaml: %v", err) + } + + status := tortoise.DeepCopy().Status + err = tortoiseclient.Create(context.Background(), tortoise) + if err != nil { + t.Fatalf("Failed to create tortoise: %v", err) + } + + v := &v1beta3.Tortoise{} + err = tortoiseclient.Get(context.Background(), client.ObjectKey{Namespace: tortoise.Namespace, Name: tortoise.Name}, v) + if err != nil { + t.Fatalf("Failed to get tortoise: %v", err) + } + v.Status = status + err = tortoiseclient.Status().Update(context.Background(), v) + if err != nil { + t.Fatalf("Failed to update tortoise status: %v", err) + } + + tortoiseNameToFileName[client.ObjectKey{ + Namespace: tortoise.Namespace, + Name: tortoise.Name, + }] = filepath.Base(tortoiseYaml) + + return nil + }) + + stdout, stderr, err := execCommand(t, append([]string{"./testdata/bin/tortoisectl", "stop", "--kubeconfig", kubeconfig}, tt.options...)...) + if err != nil { + if !tt.expectedFailure { + t.Fatalf("Failed to run tortoisectl stop: %v\nstdout: %s\nstderr: %s", err, stdout, stderr) + } + + // If the test is expected to fail, we don't need to check the result of the resources. + return + } + + t.Log(stdout) + + gotDeployments := map[client.ObjectKey]appv1.Deployment{} + for k := range deploymentNameToFileName { + deploy, err := clientset.AppsV1().Deployments(k.Namespace).Get(context.Background(), k.Name, metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get deployment: %v", err) + } + gotDeployments[k] = *deploy + } + + gotTortoises := map[client.ObjectKey]v1beta3.Tortoise{} + for k := range tortoiseNameToFileName { + tortoise := &v1beta3.Tortoise{} + err = tortoiseclient.Get(context.Background(), k, tortoise) + if err != nil { + t.Fatalf("Failed to get tortoise: %v", err) + } + + gotTortoises[k] = *tortoise + } + + if update { + deploymentDir := fmt.Sprintf("./testdata/%s/after/deployments", tt.dir) + for key, filename := range deploymentNameToFileName { + if gotDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] != + originalDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] { + gotDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] = "updated" + } + + err = writeToFile(filepath.Join(deploymentDir, filename), gotDeployments[key]) + if err != nil { + t.Fatalf("Failed to write deployment yaml: %v", err) + } + } + + tortoiseDir := fmt.Sprintf("./testdata/%s/after/tortoises", tt.dir) + for key, filename := range tortoiseNameToFileName { + err = writeToFile(filepath.Join(tortoiseDir, filename), gotTortoises[key]) + if err != nil { + t.Fatalf("Failed to write tortoise yaml: %v", err) + } + } + + return + } + + for key, filename := range tortoiseNameToFileName { + tortoisePath := filepath.Join(fmt.Sprintf("./testdata/%s/after/tortoises", tt.dir), filename) + y, err := os.ReadFile(tortoisePath) + if err != nil { + t.Fatalf("Failed to read tortoise yaml: %v", err) + } + wantTortoise := &v1beta3.Tortoise{} + err = yaml.Unmarshal(y, wantTortoise) + if err != nil { + t.Fatalf("Failed to decode tortoise yaml: %v", err) + } + + diff := cmp.Diff(*wantTortoise, gotTortoises[key], cmpopts.IgnoreFields(v1beta3.Tortoise{}, "ObjectMeta")) + if diff != "" { + t.Fatalf("Tortoise %v mismatch (-want +got):\n%s", key, diff) + } + } + + for key, filename := range deploymentNameToFileName { + deploymentPath := filepath.Join(fmt.Sprintf("./testdata/%s/after/deployments", tt.dir), filename) + y, err := os.ReadFile(deploymentPath) + if err != nil { + t.Fatalf("Failed to read deployment yaml: %v", err) + } + wantDeployment := &appv1.Deployment{} + err = yaml.Unmarshal(y, wantDeployment) + if err != nil { + t.Fatalf("Failed to decode deployment yaml: %v", err) + } + + switch wantDeployment.Spec.Template.Annotations[annotation.UpdatedAtAnnotation] { + case "updated": + // Check if the deployment is restarted (i.e., the annotation is changed). + if gotDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] == + originalDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] { + t.Fatalf("the target deployment %s is not restarted even though it should be", gotDeployments[key].Name) + } + + wantDeployment.Spec.Template.Annotations[annotation.UpdatedAtAnnotation] = gotDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] // Update the annotation for comparison of Diff. + default: + // Check if the deployment is NOT restarted (i.e., the annotation is NOT changed). + if gotDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] != + originalDeployments[key].Spec.Template.Annotations[annotation.UpdatedAtAnnotation] { + t.Fatalf("the target deployment %s is restarted even though it should not be", gotDeployments[key].Name) + } + } + + diff := cmp.Diff(*wantDeployment, gotDeployments[key], cmpopts.IgnoreFields(appv1.Deployment{}, "ObjectMeta")) + if diff != "" { + t.Fatalf("Deployment %v mismatch (-want +got):\n%s", key, diff) + } + + diff = cmp.Diff(wantDeployment.ObjectMeta.Annotations, gotDeployments[key].ObjectMeta.Annotations) + if diff != "" { + t.Fatalf("Deployment %v annotations mismatch (-want +got):\n%s", key, diff) + } + } + }) + } +} + +func writeToFile(path string, r any) error { + y, err := yaml.Marshal(r) + if err != nil { + return err + } + y, err = removeUnnecessaryFields(y) + if err != nil { + return err + } + err = os.WriteFile(path, y, 0644) + if err != nil { + return err + } + + return nil +} + +func removeUnnecessaryFields(rawdata []byte) ([]byte, error) { + data := make(map[string]interface{}) + err := yaml.Unmarshal(rawdata, &data) + if err != nil { + return nil, err + } + meta, ok := data["metadata"] + if !ok { + return nil, errors.New("no metadata") + } + typed, ok := meta.(map[string]any) + if !ok { + return nil, fmt.Errorf("metadata is unexpected type: %T", meta) + } + + delete(typed, "creationTimestamp") + delete(typed, "managedFields") + delete(typed, "resourceVersion") + delete(typed, "uid") + delete(typed, "generation") + + return yaml.Marshal(data) +} + +func execCommand(t *testing.T, s ...string) (string, string, error) { + t.Helper() + cmd := exec.Command(s[0], s[1:]...) + t.Log(cmd.String()) + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + t.Logf("Running command: %s\n", cmd.String()) + err := cmd.Run() + return stdout.String(), stderr.String(), err +} diff --git a/docs/images/tortoise-hello.jpg b/docs/images/tortoise-hello.jpg new file mode 100644 index 00000000..0df5e109 Binary files /dev/null and b/docs/images/tortoise-hello.jpg differ diff --git a/docs/tortoisectl.md b/docs/tortoisectl.md new file mode 100644 index 00000000..f81b4264 --- /dev/null +++ b/docs/tortoisectl.md @@ -0,0 +1,27 @@ +## Tortoise CTL + +Tortoise + +### Installation + +```sh +go install github.com/mercari/tortoise/cmd/tortoisectl@latest +``` + +### `tortoisectl stop` + +stop is the command to temporarily turn off tortoise(s) easily and safely. + +It's intended to be used when your application is facing issues that might be caused by tortoise. +Specifically, it changes the tortoise updateMode to "Off" and restarts the deployment to bring the pods back to the original resource requests. + +Also, with the `--no-lowering-resources` flag, it patches the deployment directly +so that changing tortoise to Off won't result in lowering the resource request(s), damaging the service. +e.g., if the Deployment declares 1 CPU request, and the current Pods' request is 2 CPU mutated by Tortoise, +it'd patch the deployment to 2 CPU request to prevent a possible negative impact on the service. + +See full explanation by: + +```sh +tortoisectl stop -h +``` \ No newline at end of file diff --git a/go.mod b/go.mod index be38279f..bcba95cd 100644 --- a/go.mod +++ b/go.mod @@ -23,11 +23,16 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +require ( + github.com/kyokomi/emoji/v2 v2.2.12 + github.com/spf13/cobra v1.7.0 +) + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -43,7 +48,8 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/imdario/mergo v0.3.15 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -59,16 +65,16 @@ require ( github.com/stretchr/testify v1.8.3 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.4.0 // indirect golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.27.2 // indirect diff --git a/go.sum b/go.sum index 36149e0f..7f340175 100644 --- a/go.sum +++ b/go.sum @@ -7,12 +7,13 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= +github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -54,8 +55,10 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -71,6 +74,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit60= +github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= @@ -101,6 +106,9 @@ github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwa github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -144,8 +152,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -161,16 +169,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -191,8 +199,8 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/main.go b/main.go index ee66638a..e74eb562 100644 --- a/main.go +++ b/main.go @@ -70,7 +70,6 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(autoscalingv1beta3.AddToScheme(scheme)) utilruntime.Must(autoscalingv1beta3.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/pkg/annotation/external_annotation.go b/pkg/annotation/external_annotation.go index c9b60eb8..799799b3 100644 --- a/pkg/annotation/external_annotation.go +++ b/pkg/annotation/external_annotation.go @@ -4,8 +4,10 @@ const ( // IstioSidecarInjectionAnnotation - If this annotation is set to "true", it means that the sidecar injection is enabled. IstioSidecarInjectionAnnotation = "sidecar.istio.io/inject" - IstioSidecarProxyCPUAnnotation = "sidecar.istio.io/proxyCPU" - IstioSidecarProxyMemoryAnnotation = "sidecar.istio.io/proxyMemory" + IstioSidecarProxyCPUAnnotation = "sidecar.istio.io/proxyCPU" + IstioSidecarProxyCPULimitAnnotation = "sidecar.istio.io/proxyCPULimit" + IstioSidecarProxyMemoryAnnotation = "sidecar.istio.io/proxyMemory" + IstioSidecarProxyMemoryLimitAnnotation = "sidecar.istio.io/proxyMemoryLimit" UpdatedAtAnnotation = "kubectl.kubernetes.io/restartedAt" ) diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go index 07c409ec..ae5ccaeb 100644 --- a/pkg/pod/pod.go +++ b/pkg/pod/pod.go @@ -12,6 +12,7 @@ import ( "k8s.io/utils/ptr" "github.com/mercari/tortoise/api/v1beta3" + "github.com/mercari/tortoise/pkg/annotation" "github.com/mercari/tortoise/pkg/features" "github.com/mercari/tortoise/pkg/utils" ) @@ -42,7 +43,84 @@ func New( }, nil } -func (s *Service) ModifyPodResource(pod *v1.Pod, t *v1beta3.Tortoise) { +func (s *Service) ModifyPodTemplateResource(podTemplate *v1.PodTemplateSpec, t *v1beta3.Tortoise, opts ...ModifyPodSpecResourceOption) { + s.ModifyPodSpecResource(&podTemplate.Spec, t, opts...) + + // Update istio sidecar resource requests based on the tortoise.Status.Conditions.ContainerResourceRequests + // since ModifyPodSpecResource doesn't update the istio annotations. + if podTemplate.Annotations == nil { + return + } + if podTemplate.Annotations[annotation.IstioSidecarInjectionAnnotation] != "true" { + return + } + + // Update resource requests based on the tortoise.Status.Conditions.ContainerResourceRequests + for _, k := range []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory} { + newReq, ok := utils.GetRequestFromTortoise(t, "istio-proxy", k) + if !ok { + continue + } + + if k == v1.ResourceCPU { + oldCPUReq, ok := podTemplate.Annotations[annotation.IstioSidecarProxyCPUAnnotation] + oldCPULim, ok2 := podTemplate.Annotations[annotation.IstioSidecarProxyCPULimitAnnotation] + if ok && ok2 { + oldCPUReqQuantity, err := resource.ParseQuantity(oldCPUReq) + if err != nil { + continue + } + + oldCPULimQuantity, err := resource.ParseQuantity(oldCPULim) + if err != nil { + continue + } + + if containsOption(opts, NoScaleDown) && newReq.Cmp(oldCPUReqQuantity) < 0 { + // If NoScaleDown option is specified, don't scale down the resource request. + continue + } + + ratio := float64(newReq.MilliValue()) / float64(oldCPUReqQuantity.MilliValue()) + podTemplate.Annotations[annotation.IstioSidecarProxyCPUAnnotation] = newReq.String() + podTemplate.Annotations[annotation.IstioSidecarProxyCPULimitAnnotation] = resource.NewMilliQuantity(int64(float64(oldCPULimQuantity.MilliValue())*ratio), oldCPULimQuantity.Format).String() + } + } + + if k == v1.ResourceMemory { + oldMemReq, ok := podTemplate.Annotations[annotation.IstioSidecarProxyMemoryAnnotation] + oldMemLim, ok2 := podTemplate.Annotations[annotation.IstioSidecarProxyMemoryLimitAnnotation] + if ok && ok2 { + oldMemReqQuantity, err := resource.ParseQuantity(oldMemReq) + if err != nil { + continue + } + + oldMemLimQuantity, err := resource.ParseQuantity(oldMemLim) + if err != nil { + continue + } + + if containsOption(opts, NoScaleDown) && newReq.Cmp(oldMemReqQuantity) < 0 { + // If NoScaleDown option is specified, don't scale down the resource request. + continue + } + + ratio := float64(newReq.MilliValue()) / float64(oldMemReqQuantity.MilliValue()) + podTemplate.Annotations[annotation.IstioSidecarProxyMemoryAnnotation] = newReq.String() + podTemplate.Annotations[annotation.IstioSidecarProxyMemoryLimitAnnotation] = resource.NewMilliQuantity(int64(float64(oldMemLimQuantity.MilliValue())*ratio), oldMemLimQuantity.Format).String() + } + } + } +} + +type ModifyPodSpecResourceOption string + +var ( + NoScaleDown ModifyPodSpecResourceOption = "NoScaleDown" +) + +func (s *Service) ModifyPodSpecResource(podSpec *v1.PodSpec, t *v1beta3.Tortoise, opts ...ModifyPodSpecResourceOption) { if t.Spec.UpdateMode == v1beta3.UpdateModeOff || t.Status.TortoisePhase == "" || t.Status.TortoisePhase == v1beta3.TortoisePhaseInitializing || @@ -56,22 +134,26 @@ func (s *Service) ModifyPodResource(pod *v1.Pod, t *v1beta3.Tortoise) { newRequestsMap := map[containerNameAndResource]resource.Quantity{} // Update resource requests based on the tortoise.Status.Conditions.ContainerResourceRequests - for i, container := range pod.Spec.Containers { + for i, container := range podSpec.Containers { for k, oldReq := range container.Resources.Requests { newReq, ok := utils.GetRequestFromTortoise(t, container.Name, k) if !ok { // Unchange, just store the old value as a new value newReq = oldReq } + if containsOption(opts, NoScaleDown) && newReq.Cmp(oldReq) < 0 { + // If NoScaleDown option is specified, don't scale down the resource request. + newReq = oldReq + } oldRequestsMap[containerNameAndResource{containerName: container.Name, resourceName: k}] = oldReq newRequestsMap[containerNameAndResource{containerName: container.Name, resourceName: k}] = newReq - pod.Spec.Containers[i].Resources.Requests[k] = newReq + podSpec.Containers[i].Resources.Requests[k] = newReq requestChangeRatio[containerNameAndResource{containerName: container.Name, resourceName: k}] = float64(newReq.MilliValue()) / float64(oldReq.MilliValue()) } } // Update resource limits - for i, container := range pod.Spec.Containers { + for i, container := range podSpec.Containers { if container.Resources.Limits == nil { container.Resources.Limits = make(v1.ResourceList) } @@ -98,12 +180,12 @@ func (s *Service) ModifyPodResource(pod *v1.Pod, t *v1beta3.Tortoise) { if k == v1.ResourceCPU && newLim.Cmp(s.minimumCPULimit) < 0 { newLim = ptr.To(s.minimumCPULimit.DeepCopy()) } - pod.Spec.Containers[i].Resources.Limits[k] = *newLim + podSpec.Containers[i].Resources.Limits[k] = *newLim } } // Update GOMEMLIMIT and GOMAXPROCS - for i, container := range pod.Spec.Containers { + for i, container := range podSpec.Containers { for j, env := range container.Env { if env.Name == "GOMAXPROCS" { // e.g., If CPU is increased twice, GOMAXPROCS should be doubled. @@ -126,7 +208,7 @@ func (s *Service) ModifyPodResource(pod *v1.Pod, t *v1beta3.Tortoise) { newUncapedNum := float64(oldNum) * changeRatio // GOMAXPROCS should be an integer. newNum := int(math.Ceil(newUncapedNum)) - pod.Spec.Containers[i].Env[j].Value = strconv.Itoa(newNum) + podSpec.Containers[i].Env[j].Value = strconv.Itoa(newNum) } @@ -166,7 +248,7 @@ func (s *Service) ModifyPodResource(pod *v1.Pod, t *v1beta3.Tortoise) { } // See GOMEMLIMIT's format: https://pkg.go.dev/runtime#hdr-Environment_Variables newNum := int(float64(oldNum.Value()) * changeRatio) - pod.Spec.Containers[i].Env[j].Value = strconv.Itoa(newNum) + podSpec.Containers[i].Env[j].Value = strconv.Itoa(newNum) } } } @@ -216,3 +298,12 @@ type containerNameAndResource struct { containerName string resourceName v1.ResourceName } + +func containsOption(opts []ModifyPodSpecResourceOption, opt ModifyPodSpecResourceOption) bool { + for _, o := range opts { + if o == opt { + return true + } + } + return false +} diff --git a/pkg/pod/pod_test.go b/pkg/pod/pod_test.go index 63b0610e..885b2d37 100644 --- a/pkg/pod/pod_test.go +++ b/pkg/pod/pod_test.go @@ -7,13 +7,219 @@ import ( "github.com/google/go-cmp/cmp" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" "github.com/mercari/tortoise/api/v1beta3" + "github.com/mercari/tortoise/pkg/annotation" "github.com/mercari/tortoise/pkg/features" ) -func TestService_ModifyPodResource(t *testing.T) { +func TestService_ModifyPodTemplateResource(t *testing.T) { + type args struct { + podTemplate *v1.PodTemplateSpec + tortoise *v1beta3.Tortoise + opts []ModifyPodSpecResourceOption + } + tests := []struct { + name string + args args + want *v1.PodTemplateSpec + }{ + { + name: "Tortoise is Auto; NoScaleDown option; istio CPU is changed", + args: args{ + opts: []ModifyPodSpecResourceOption{NoScaleDown}, + podTemplate: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotation.IstioSidecarInjectionAnnotation: "true", + annotation.IstioSidecarProxyCPUAnnotation: "100m", + annotation.IstioSidecarProxyCPULimitAnnotation: "300m", + annotation.IstioSidecarProxyMemoryAnnotation: "100Mi", + annotation.IstioSidecarProxyMemoryLimitAnnotation: "100Mi", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("300m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + tortoise: &v1beta3.Tortoise{ + Spec: v1beta3.TortoiseSpec{ + UpdateMode: v1beta3.UpdateModeAuto, + }, + Status: v1beta3.TortoiseStatus{ + TortoisePhase: v1beta3.TortoisePhaseWorking, + Conditions: v1beta3.Conditions{ + ContainerResourceRequests: []v1beta3.ContainerResourceRequests{ + { + ContainerName: "container", + Resource: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), // scale up + v1.ResourceMemory: resource.MustParse("1Mi"), // scale down + }, + }, + { + ContainerName: "istio-proxy", + Resource: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("300m"), // scale up + v1.ResourceMemory: resource.MustParse("1Mi"), // scale down + }, + }, + }, + }, + }, + }, + }, + want: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotation.IstioSidecarInjectionAnnotation: "true", + annotation.IstioSidecarProxyCPUAnnotation: "300m", + annotation.IstioSidecarProxyCPULimitAnnotation: "900m", + annotation.IstioSidecarProxyMemoryAnnotation: "100Mi", + annotation.IstioSidecarProxyMemoryLimitAnnotation: "100Mi", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("600m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + }, + { + name: "Tortoise is Auto; NoScaleDown option; istio Memory is changed", + args: args{ + opts: []ModifyPodSpecResourceOption{NoScaleDown}, + podTemplate: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotation.IstioSidecarInjectionAnnotation: "true", + annotation.IstioSidecarProxyCPUAnnotation: "100m", + annotation.IstioSidecarProxyCPULimitAnnotation: "300m", + annotation.IstioSidecarProxyMemoryAnnotation: "100Mi", + annotation.IstioSidecarProxyMemoryLimitAnnotation: "100Mi", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("300m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + tortoise: &v1beta3.Tortoise{ + Spec: v1beta3.TortoiseSpec{ + UpdateMode: v1beta3.UpdateModeAuto, + }, + Status: v1beta3.TortoiseStatus{ + TortoisePhase: v1beta3.TortoisePhaseWorking, + Conditions: v1beta3.Conditions{ + ContainerResourceRequests: []v1beta3.ContainerResourceRequests{ + { + ContainerName: "container", + Resource: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), // scale up + v1.ResourceMemory: resource.MustParse("1Mi"), // scale down + }, + }, + { + ContainerName: "istio-proxy", + Resource: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("20m"), // scale down + v1.ResourceMemory: resource.MustParse("300Mi"), // scale up + }, + }, + }, + }, + }, + }, + }, + want: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotation.IstioSidecarInjectionAnnotation: "true", + annotation.IstioSidecarProxyCPUAnnotation: "100m", + annotation.IstioSidecarProxyCPULimitAnnotation: "300m", + annotation.IstioSidecarProxyMemoryAnnotation: "300Mi", + annotation.IstioSidecarProxyMemoryLimitAnnotation: "300Mi", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("600m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, err := New(nil, "", nil, nil) + if err != nil { + t.Fatalf("New() error = %v", err) + } + + got := tt.args.podTemplate.DeepCopy() + s.ModifyPodTemplateResource(got, tt.args.tortoise, tt.args.opts...) + if d := cmp.Diff(got, tt.want); d != "" { + t.Errorf("ModifyPodResource() mismatch (-want +got):\n%s", d) + } + }) + } +} + +func TestService_ModifyPodSpecResource(t *testing.T) { type fields struct { resourceLimitMultiplier map[string]int64 minimumCPULimit string @@ -22,6 +228,7 @@ func TestService_ModifyPodResource(t *testing.T) { type args struct { pod *v1.Pod tortoise *v1beta3.Tortoise + opts []ModifyPodSpecResourceOption } tests := []struct { name string @@ -415,6 +622,69 @@ func TestService_ModifyPodResource(t *testing.T) { }, }, }, + { + name: "Tortoise is Auto; NoScaleDown option", + args: args{ + opts: []ModifyPodSpecResourceOption{NoScaleDown}, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("300m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + tortoise: &v1beta3.Tortoise{ + Spec: v1beta3.TortoiseSpec{ + UpdateMode: v1beta3.UpdateModeAuto, + }, + Status: v1beta3.TortoiseStatus{ + TortoisePhase: v1beta3.TortoisePhaseWorking, + Conditions: v1beta3.Conditions{ + ContainerResourceRequests: []v1beta3.ContainerResourceRequests{ + { + ContainerName: "container", + Resource: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), // scale up + v1.ResourceMemory: resource.MustParse("1Mi"), // scale down + }, + }, + }, + }, + }, + }, + }, + want: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), // scale up + v1.ResourceMemory: resource.MustParse("100Mi"), // scale down is ignored + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("600m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + }, { name: "Tortoise is Auto; hits resourceLimitMultiplier", fields: fields{ @@ -895,8 +1165,8 @@ func TestService_ModifyPodResource(t *testing.T) { t.Fatalf("New() error = %v", err) } got := tt.args.pod.DeepCopy() - s.ModifyPodResource(got, tt.args.tortoise) - if d := cmp.Diff(got, tt.want); d != "" { + s.ModifyPodSpecResource(&got.Spec, tt.args.tortoise, tt.args.opts...) + if d := cmp.Diff(got.Spec, tt.want.Spec); d != "" { t.Errorf("ModifyPodResource() mismatch (-want +got):\n%s", d) } }) diff --git a/pkg/stoper/stoper.go b/pkg/stoper/stoper.go new file mode 100644 index 00000000..b3a466fc --- /dev/null +++ b/pkg/stoper/stoper.go @@ -0,0 +1,180 @@ +package stoper + +import ( + "context" + "errors" + "fmt" + "io" + "reflect" + "time" + + "github.com/kyokomi/emoji/v2" + v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/mercari/tortoise/api/v1beta3" + "github.com/mercari/tortoise/pkg/deployment" + "github.com/mercari/tortoise/pkg/pod" +) + +// Stopr is the struct for stopping tortoise safely. +type Stopr struct { + c client.Client + + deploymentService *deployment.Service + podService *pod.Service +} + +func New(c client.Client, ds *deployment.Service, ps *pod.Service) *Stopr { + return &Stopr{ + c: c, + deploymentService: ds, + podService: ps, + } +} + +type StoprOption string + +var ( + NoLoweringResource StoprOption = "NoLoweringResource" +) + +func (s *Stopr) Stop(ctx context.Context, tortoiseNames []string, namespace string, all bool, writer io.Writer, opts ...StoprOption) error { + // It assumes the validation is already done in the CLI layer. + + targets := []types.NamespacedName{} + if all { + tortoises := &v1beta3.TortoiseList{} + opt := &client.ListOptions{} + if namespace != "" { + // stop all tortoises in the namespace + opt.Namespace = namespace + } + + if err := s.c.List(ctx, tortoises, opt); err != nil { + return fmt.Errorf("failed to list tortoises: %w", err) + } + + for _, t := range tortoises.Items { + targets = append(targets, types.NamespacedName{Name: t.Name, Namespace: t.Namespace}) + } + } else { + for _, name := range tortoiseNames { + targets = append(targets, types.NamespacedName{Name: name, Namespace: namespace}) + } + } + + var finalerr error + for _, target := range targets { + write(writer, fmt.Sprintf("\n%s stopping your tortoise %s ... ", emoji.Sprint(":turtle:"), &target)) + + // 1. Stop Tortoise. + tortoise, err := s.stopOne(ctx, target) + if err != nil { + if errors.Is(err, errTortoiseAlreadyStopped) { + write(writer, fmt.Sprintf("this tortoise is already stopped %s\n", emoji.Sprint(":sleeping:"))) + continue + } + finalerr = errors.Join(finalerr, err) + write(writer, fmt.Sprintf("failed to stop your tortoise %s.\nError: %v\n", emoji.Sprint(":face_with_spiral_eyes:"), err)) + continue + } + write(writer, fmt.Sprintf("Done %s\n", emoji.Sprint(":sleeping:"))) + + // 2. Get the target deployment. + dp, err := s.deploymentService.GetDeploymentOnTortoise(ctx, tortoise) + if err != nil { + finalerr = errors.Join(finalerr, err) + write(writer, fmt.Sprintf("%s failed to get deployment on your tortoise %s.\nError: %v\n", emoji.Sprint(":face_with_spiral_eyes:"), &target, err)) + continue + } + + // 3. [when NoLoweringResource is true] Patch the deployment to keep the resource requests high. + if containsOption(opts, NoLoweringResource) { + write(writer, fmt.Sprintf("%s patching your deployment to keep the resource requests high ... ", emoji.Sprint(":hammer_and_wrench:"))) + updated, err := s.patchDeploymentToKeepResources(ctx, dp, tortoise) + if err != nil { + finalerr = errors.Join(finalerr, err) + write(writer, fmt.Sprintf("%s failed to patch your deployment %s.\nError: %v\n", emoji.Sprint(":face_with_spiral_eyes:"), &target, err)) + continue + } + + write(writer, fmt.Sprintf("Done %s\n", emoji.Sprint(":hammer_and_wrench:"))) + + if updated { + // If the deployment is updated, we don't need to restart the deployment. + continue + } + } + + // 4. Restart the deployment to get back the original resource requests. + write(writer, fmt.Sprintf("%s restarting your deployment to get back the original resource ... ", emoji.Sprint(":arrows_counterclockwise:"))) + if err := s.deploymentService.RolloutRestart(ctx, dp, tortoise, time.Now()); err != nil { + finalerr = errors.Join(finalerr, err) + write(writer, fmt.Sprintf("%s failed to restart your deployment %s.\nError: %v\n", emoji.Sprint(":face_with_spiral_eyes:"), &target, err)) + continue + } + write(writer, fmt.Sprintf("Done, your Pods should get the original resources soon %s\n", emoji.Sprint(":muscle:"))) + } + + return finalerr +} + +func write(writer io.Writer, msg string) { + //nolint:errcheck // intentionally ignore the error because it's not critical + writer.Write([]byte(msg)) +} + +func containsOption(opts []StoprOption, opt StoprOption) bool { + for _, o := range opts { + if o == opt { + return true + } + } + return false +} + +// patchDeploymentToKeepResources patches the deployment to keep the resource requests high. +// The first return value indicates whether the deployment is updated or not. +func (s *Stopr) patchDeploymentToKeepResources(ctx context.Context, dp *v1.Deployment, tortoise *v1beta3.Tortoise) (bool, error) { + originalDP := dp.DeepCopy() + + // Set to Auto because ModifyPodSpecResource doesn't change anything if it's set to Off. + tortoise.Spec.UpdateMode = v1beta3.UpdateModeAuto + s.podService.ModifyPodTemplateResource(&dp.Spec.Template, tortoise, pod.NoScaleDown) + + tortoise.Spec.UpdateMode = v1beta3.UpdateModeOff + // If not updated, early return + if reflect.DeepEqual(originalDP.Spec.Template.Spec.Containers, dp.Spec.Template.Spec.Containers) { + return false, nil + } + + if err := s.c.Update(ctx, dp); err != nil { + return false, fmt.Errorf("failed to update deployment: %w", err) + } + + return true, nil +} + +var errTortoiseAlreadyStopped = errors.New("tortoise is already stopped") + +// stopOne disables the tortoise. +func (s *Stopr) stopOne(ctx context.Context, target types.NamespacedName) (*v1beta3.Tortoise, error) { + t := &v1beta3.Tortoise{} + if err := s.c.Get(ctx, target, t); err != nil { + return nil, fmt.Errorf("failed to get tortoise: %w", err) + } + + if t.Spec.UpdateMode == v1beta3.UpdateModeOff { + return t, errTortoiseAlreadyStopped + } + + t.Spec.UpdateMode = v1beta3.UpdateModeOff + + if err := s.c.Update(ctx, t); err != nil { + return nil, fmt.Errorf("failed to update tortoise: %w", err) + } + + return t, nil +}