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{} +}