github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/fault/fault.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package fault 21 22 import ( 23 "context" 24 "fmt" 25 "regexp" 26 "strconv" 27 "strings" 28 29 "github.com/spf13/cobra" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/cli-runtime/pkg/genericiooptions" 33 "k8s.io/client-go/kubernetes" 34 "k8s.io/klog/v2" 35 cmdutil "k8s.io/kubectl/pkg/cmd/util" 36 37 "github.com/1aal/kubeblocks/pkg/cli/create" 38 "github.com/1aal/kubeblocks/pkg/cli/printer" 39 "github.com/1aal/kubeblocks/pkg/cli/util" 40 ) 41 42 type Selector struct { 43 PodNameSelectors map[string][]string `json:"pods"` 44 45 NamespaceSelectors []string `json:"namespaces"` 46 47 LabelSelectors map[string]string `json:"labelSelectors"` 48 49 PodPhaseSelectors []string `json:"podPhaseSelectors"` 50 51 NodeLabelSelectors map[string]string `json:"nodeSelectors"` 52 53 AnnotationSelectors map[string]string `json:"annotationSelectors"` 54 55 NodeNameSelectors []string `json:"nodes"` 56 } 57 58 type FaultBaseOptions struct { 59 Action string `json:"action"` 60 61 Mode string `json:"mode"` 62 63 Value string `json:"value"` 64 65 Duration string `json:"duration"` 66 67 Selector `json:"selector"` 68 69 create.CreateOptions `json:"-"` 70 } 71 72 func NewFaultCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 73 cmd := &cobra.Command{ 74 Use: "fault", 75 Short: "Inject faults to pod.", 76 } 77 cmd.AddCommand( 78 NewPodChaosCmd(f, streams), 79 NewNetworkChaosCmd(f, streams), 80 NewTimeChaosCmd(f, streams), 81 NewIOChaosCmd(f, streams), 82 NewStressChaosCmd(f, streams), 83 NewNodeChaosCmd(f, streams), 84 NewListCmd(f, streams), 85 NewDeleteCmd(f, streams), 86 ) 87 return cmd 88 } 89 90 func registerFlagCompletionFunc(cmd *cobra.Command, f cmdutil.Factory) { 91 var formatsWithDesc = map[string]string{ 92 "JSON": "Output result in JSON format", 93 "YAML": "Output result in YAML format", 94 } 95 util.CheckErr(cmd.RegisterFlagCompletionFunc("output", 96 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 97 var names []string 98 for format, desc := range formatsWithDesc { 99 if strings.HasPrefix(format, toComplete) { 100 names = append(names, fmt.Sprintf("%s\t%s", format, desc)) 101 } 102 } 103 return names, cobra.ShellCompDirectiveNoFileComp 104 })) 105 } 106 107 func (o *FaultBaseOptions) AddCommonFlag(cmd *cobra.Command) { 108 cmd.Flags().StringVar(&o.Mode, "mode", "all", `You can select "one", "all", "fixed", "fixed-percent", "random-max-percent", Specify the experimental mode, that is, which Pods to experiment with.`) 109 cmd.Flags().StringVar(&o.Value, "value", "", `If you choose mode=fixed or fixed-percent or random-max-percent, you can enter a value to specify the number or percentage of pods you want to inject.`) 110 cmd.Flags().StringVar(&o.Duration, "duration", "10s", "Supported formats of the duration are: ms / s / m / h.") 111 cmd.Flags().StringToStringVar(&o.LabelSelectors, "label", map[string]string{}, `label for pod, such as '"app.kubernetes.io/component=mysql, statefulset.kubernetes.io/pod-name=mycluster-mysql-0.`) 112 cmd.Flags().StringArrayVar(&o.NamespaceSelectors, "ns-fault", []string{"default"}, `Specifies the namespace into which you want to inject faults.`) 113 cmd.Flags().StringArrayVar(&o.PodPhaseSelectors, "phase", []string{}, `Specify the pod that injects the fault by the state of the pod.`) 114 cmd.Flags().StringToStringVar(&o.NodeLabelSelectors, "node-label", map[string]string{}, `label for node, such as '"kubernetes.io/arch=arm64,kubernetes.io/hostname=minikube-m03,kubernetes.io/os=linux.`) 115 cmd.Flags().StringArrayVar(&o.NodeNameSelectors, "node", []string{}, `Inject faults into pods in the specified node.`) 116 cmd.Flags().StringToStringVar(&o.AnnotationSelectors, "annotation", map[string]string{}, `Select the pod to inject the fault according to Annotation.`) 117 cmd.Flags().StringVar(&o.DryRun, "dry-run", "none", `Must be "client", or "server". If with client strategy, only print the object that would be sent, and no data is actually sent. If with server strategy, submit the server-side request, but no data is persistent.`) 118 cmd.Flags().Lookup("dry-run").NoOptDefVal = Unchanged 119 120 printer.AddOutputFlagForCreate(cmd, &o.Format, false) 121 } 122 123 func (o *FaultBaseOptions) BaseValidate() error { 124 if o.DryRun == "none" { 125 enable, err := o.checkChaosMeshEnable() 126 if err != nil { 127 return err 128 } 129 if !enable { 130 return fmt.Errorf("chaos-mesh is not enabled, use `kbcli addon enable fault-chaos-mesh` to enable chaos-mesh first") 131 } 132 } 133 134 if ok, err := IsRegularMatch(o.Duration); !ok { 135 return err 136 } 137 138 if o.Value == "" && (o.Mode == "fixed" || o.Mode == "fixed-percent" || o.Mode == "random-max-percent") { 139 return fmt.Errorf("you must use --value to specify an integer") 140 } 141 142 if ok, err := IsInteger(o.Value); !ok { 143 return err 144 } 145 146 return nil 147 } 148 149 func (o *FaultBaseOptions) BaseComplete() error { 150 if len(o.Args) > 0 { 151 o.PodNameSelectors = make(map[string][]string, len(o.NamespaceSelectors)) 152 for _, ns := range o.NamespaceSelectors { 153 o.PodNameSelectors[ns] = o.Args 154 } 155 } 156 return nil 157 } 158 159 func IsRegularMatch(str string) (bool, error) { 160 pattern := regexp.MustCompile(`^\d+(ms|s|m|h)$`) 161 if str != "" && !pattern.MatchString(str) { 162 return false, fmt.Errorf("invalid duration:%s; input format must be in the form of number + time unit, like 10s, 10m", str) 163 } else { 164 return true, nil 165 } 166 } 167 168 func IsInteger(str string) (bool, error) { 169 if _, err := strconv.Atoi(str); str != "" && err != nil { 170 return false, fmt.Errorf("invalid value:%s; must be an integer", str) 171 } else { 172 return true, nil 173 } 174 } 175 176 func GetGVR(group, version, resourceName string) schema.GroupVersionResource { 177 return schema.GroupVersionResource{Group: group, Version: version, Resource: resourceName} 178 } 179 180 func (o *FaultBaseOptions) checkChaosMeshEnable() (bool, error) { 181 config, err := o.Factory.ToRESTConfig() 182 if err != nil { 183 return false, err 184 } 185 186 clientSet, err := kubernetes.NewForConfig(config) 187 if err != nil { 188 return false, err 189 } 190 podList, err := clientSet.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{ 191 LabelSelector: "app.kubernetes.io/part-of=chaos-mesh", 192 }) 193 if err != nil { 194 klog.V(1).Info(err) 195 return false, err 196 } 197 198 if len(podList.Items) > 0 { 199 return true, nil 200 } 201 return false, nil 202 }