Skip to content

Commit

Permalink
Implement tortoisectl stop command
Browse files Browse the repository at this point in the history
  • Loading branch information
sanposhiho committed May 7, 2024
1 parent 097a5c9 commit 61271a1
Show file tree
Hide file tree
Showing 57 changed files with 5,203 additions and 36 deletions.
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ 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-update
test-tortoisectl-update: envtest
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -timeout 30s -run Test_TortoiseCtlStop ./cmd/tortoisectl/test/... --update

.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

Expand All @@ -82,6 +90,10 @@ $(GINKGO): $(LOCALBIN)
build: generate fmt vet ## Build manager binary.
go build -o bin/manager main.go

.PHONY: build-tortoisectl
build-tortoisectl:
go build -o bin/tortoisectl cmd/tortoisectl/main.go

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./main.go
Expand Down
2 changes: 1 addition & 1 deletion api/core/v1/pod_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions cmd/tortoisectl/commands/root.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
110 changes: 110 additions & 0 deletions cmd/tortoisectl/commands/stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package commands

import (
"fmt"
"os"
"path/filepath"

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"
"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"
)

var stopCmd = &cobra.Command{
Use: "stop tortoise1 tortoise2...",
Short: "stop tortoise(s) safely",
Long: `stop is the command to turn off tortoise(s) safely.`,
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.`)
}
7 changes: 7 additions & 0 deletions cmd/tortoisectl/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/mercari/tortoise/cmd/tortoisectl/commands"

func main() {
commands.Execute()
}
Original file line number Diff line number Diff line change
@@ -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: {}
Original file line number Diff line number Diff line change
@@ -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: {}
Original file line number Diff line number Diff line change
@@ -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: {}
Original file line number Diff line number Diff line change
@@ -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: {}
Loading

0 comments on commit 61271a1

Please sign in to comment.