From c7000780ae9708a63b206581caab85c86bff1b9a Mon Sep 17 00:00:00 2001
From: mikolaj-krzyzanowski-f3 <mikolaj.krzyzanowski@form3.tech>
Date: Tue, 7 Jan 2025 11:14:21 +0000
Subject: [PATCH] WIP

---
 api/v1alpha1/moveazchaos_types.go         | 41 ++++++++++++
 api/v1alpha1/selector.go                  | 21 ++++++
 controllers/chaosimpl/fx.go               |  2 +
 controllers/chaosimpl/moveazchaos/impl.go | 62 ++++++++++++++++++
 pkg/selector/deployment/selector.go       | 78 +++++++++++++++++++++++
 5 files changed, 204 insertions(+)
 create mode 100644 api/v1alpha1/moveazchaos_types.go
 create mode 100644 controllers/chaosimpl/moveazchaos/impl.go
 create mode 100644 pkg/selector/deployment/selector.go

diff --git a/api/v1alpha1/moveazchaos_types.go b/api/v1alpha1/moveazchaos_types.go
new file mode 100644
index 0000000000..bd470cbbe8
--- /dev/null
+++ b/api/v1alpha1/moveazchaos_types.go
@@ -0,0 +1,41 @@
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+//var (
+//	_ InnerObject             = (*MoveAZChaos)(nil)
+//	_ InnerObjectWithSelector = (*MoveAZChaos)(nil)
+//)
+
+// +kubebuilder:object:root=true
+// +chaos-mesh:experiment
+// +chaos-mesh:oneshot=true
+type MoveAZChaos struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   MoveAZChaosSpec   `json:"spec"`
+	Status MoveAZChaosStatus `json:"status"`
+}
+
+type MoveAZChaosSpec struct {
+	DeploymentSelector `json:",inline"`
+	Zone               string `json:"zone"`
+	// Duration represents the duration of the chaos
+	// +optional
+	Duration *string `json:"duration,omitempty"`
+	// RemoteCluster represents the remote cluster where the chaos will be deployed
+	// +optional
+	RemoteCluster string `json:"remoteCluster,omitempty"`
+}
+type MoveAZChaosStatus struct {
+	ChaosStatus `json:",inline"`
+}
+
+func (obj *MoveAZChaos) GetSelectorSpecs() map[string]interface{} {
+	return map[string]interface{}{
+		".": &obj.Spec.Selector,
+	}
+}
diff --git a/api/v1alpha1/selector.go b/api/v1alpha1/selector.go
index 315bde4573..c895c6689d 100644
--- a/api/v1alpha1/selector.go
+++ b/api/v1alpha1/selector.go
@@ -164,3 +164,24 @@ type NodeSelectorSpec struct {
 	// +optional
 	ExpressionSelectors LabelSelectorRequirements `json:"expressionSelectors,omitempty" swaggerignore:"true"`
 }
+
+type DeploymentSelectorSpec struct {
+	// Map of namespace names to a list of deployments in that namespace.
+	Deployments map[string][]string `json:"deployments,omitempty"`
+}
+
+type DeploymentSelector struct {
+	Selector DeploymentSelectorSpec `json:"selector"`
+
+	// Mode defines the mode to run chaos action.
+	// Supported mode: one / all / fixed / fixed-percent / random-max-percent
+	// +kubebuilder:validation:Enum=one;all;fixed;fixed-percent;random-max-percent
+	Mode SelectorMode `json:"mode"`
+
+	// Value is required when the mode is set to `FixedMode` / `FixedPercentMode` / `RandomMaxPercentMode`.
+	// If `FixedMode`, provide an integer of pods to do chaos action.
+	// If `FixedPercentMode`, provide a number from 0-100 to specify the percent of pods the server can do chaos action.
+	// IF `RandomMaxPercentMode`,  provide a number from 0-100 to specify the max percent of pods to do chaos action
+	// +optional
+	Value string `json:"value,omitempty"`
+}
diff --git a/controllers/chaosimpl/fx.go b/controllers/chaosimpl/fx.go
index 44cc6b9b5e..444fe86585 100644
--- a/controllers/chaosimpl/fx.go
+++ b/controllers/chaosimpl/fx.go
@@ -32,6 +32,7 @@ import (
 	"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/jvmchaos"
 	"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/k8schaos"
 	"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/kernelchaos"
+	"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/moveazchaos"
 	"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/networkchaos"
 	"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/physicalmachinechaos"
 	"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/podchaos"
@@ -66,5 +67,6 @@ var AllImpl = fx.Options(
 	rollingrestartchaos.Module,
 	podpvcchaos.Module,
 	certificatechaos.Module,
+	moveazchaos.Module,
 
 	utils.Module)
diff --git a/controllers/chaosimpl/moveazchaos/impl.go b/controllers/chaosimpl/moveazchaos/impl.go
new file mode 100644
index 0000000000..97fefaf57a
--- /dev/null
+++ b/controllers/chaosimpl/moveazchaos/impl.go
@@ -0,0 +1,62 @@
+package moveazchaos
+
+import (
+	"context"
+	"fmt"
+
+	"go.uber.org/fx"
+	v1 "k8s.io/api/apps/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
+	"github.com/chaos-mesh/chaos-mesh/controllers/utils/controller"
+)
+
+type Impl struct {
+	client.Client
+}
+
+func (i *Impl) Apply(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
+	moveAz, ok := obj.(*v1alpha1.MoveAZChaos)
+
+	if !ok {
+		return v1alpha1.NotInjected, fmt.Errorf("not MoveAZChaos")
+	}
+
+	name, err := controller.ParseNamespacedName(records[index].Id)
+	if err != nil {
+		return v1alpha1.NotInjected, err
+	}
+
+	var deployment *v1.Deployment
+	err = i.Client.Get(ctx, name, deployment)
+	if err != nil {
+		return v1alpha1.NotInjected, err
+	}
+
+	data := []byte(fmt.Sprintf("{\"spec\": {\"template\": {\"spec\": {\"nodeSelector\": {\"topology.kubernetes.io/zone\":\"%s\"}}}}}", moveAz.Spec.Zone))
+	patch := client.RawPatch(types.JSONPatchType, data)
+	err = i.Client.Patch(ctx, deployment, patch)
+	if err != nil {
+		return v1alpha1.NotInjected, err
+	}
+
+	return v1alpha1.Injected, nil
+}
+
+func (i *Impl) Recover(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func NewImpl(c client.Client) Impl {
+	return Impl{c}
+}
+
+var Module = fx.Provide(
+	fx.Annotated{
+		Group:  "impl",
+		Target: NewImpl,
+	},
+)
diff --git a/pkg/selector/deployment/selector.go b/pkg/selector/deployment/selector.go
new file mode 100644
index 0000000000..932a7fdc2f
--- /dev/null
+++ b/pkg/selector/deployment/selector.go
@@ -0,0 +1,78 @@
+package deployment
+
+import (
+	"context"
+
+	v1 "k8s.io/api/apps/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/kubernetes"
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
+)
+
+type Deployment struct {
+	v1.Deployment
+}
+
+func (d *Deployment) Id() string {
+	return (types.NamespacedName{
+		Name:      d.Name,
+		Namespace: d.Namespace,
+	}).String()
+}
+
+type SelectImpl struct{}
+
+func (impl *SelectImpl) Select(ctx context.Context, selector *v1alpha1.DeploymentSelector) ([]*Deployment, error) {
+	if selector == nil {
+		return []*Deployment{}, nil
+	}
+
+	client, err := kubernetesClient()
+	if err != nil {
+		return []*Deployment{}, err
+	}
+
+	var deployments []*Deployment
+	for namespace, names := range selector.Selector.Deployments {
+		deploymentList, err := client.AppsV1().Deployments(namespace).List(ctx, metav1.ListOptions{})
+		if err != nil {
+			return []*Deployment{}, err
+		}
+
+		matched := match(deploymentList, names)
+		deployments = append(deployments, matched...)
+
+	}
+
+	return deployments, nil
+}
+
+func match(list *v1.DeploymentList, names []string) []*Deployment {
+	var deployments []*Deployment
+	for _, selectorName := range names {
+		for _, deployment := range list.Items {
+			if selectorName == deployment.Name {
+				deployments = append(deployments, &Deployment{
+					Deployment: deployment,
+				})
+			}
+		}
+	}
+
+	return deployments
+}
+
+func kubernetesClient() (*kubernetes.Clientset, error) {
+	config, err := ctrl.GetConfig()
+	if err != nil {
+		return nil, err
+	}
+	return kubernetes.NewForConfig(config)
+}
+
+func New() SelectImpl {
+	return SelectImpl{}
+}