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  }