github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/fault/fault_http.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  	"fmt"
    24  
    25  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    26  	"github.com/spf13/cobra"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/json"
    30  	"k8s.io/cli-runtime/pkg/genericiooptions"
    31  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    32  	"k8s.io/kubectl/pkg/util/templates"
    33  
    34  	"github.com/1aal/kubeblocks/pkg/cli/create"
    35  )
    36  
    37  var faultHTTPExample = templates.Examples(`
    38  	# By default, the method of GET from port 80 is blocked.
    39  	kbcli fault network http abort --duration=1m
    40  	
    41  	# Block the method of GET from port 4399.
    42  	kbcli fault network http abort --port=4399 --duration=1m
    43  
    44  	# Block the method of POST from port 4399.
    45  	kbcli fault network http abort --port=4399 --method=POST --duration=1m
    46  
    47  	# Delays post requests from port 4399.
    48  	kbcli fault network http delay --port=4399 --method=POST --delay=15s
    49  	
    50  	# Replace the GET method sent from port 80 with the PUT method.
    51  	kbcli fault network http replace --replace-method=PUT --duration=1m
    52  
    53  	# Replace the GET method sent from port 80 with the PUT method, and replace the request body.
    54  	kbcli fault network http replace --body="you are good luck" --replace-method=PUT --duration=2m
    55  
    56  	# Replace the response content "you" from port 80.
    57  	kbcli fault network http replace --target=Response --body=you --duration=30s
    58  	
    59  	# Append content to the body of the post request sent from port 4399, in JSON format.
    60  	kbcli fault network http patch --method=POST --port=4399 --body="you are good luck" --type=JSON --duration=30s
    61  `)
    62  
    63  type HTTPReplace struct {
    64  	ReplaceBody      []byte `json:"body,omitempty"`
    65  	InputReplaceBody string `json:"-"`
    66  	ReplacePath      string `json:"path,omitempty"`
    67  	ReplaceMethod    string `json:"method,omitempty"`
    68  }
    69  
    70  type HTTPPatch struct {
    71  	HTTPPatchBody `json:"body,omitempty"`
    72  }
    73  
    74  type HTTPPatchBody struct {
    75  	PatchBodyValue string `json:"value,omitempty"`
    76  	PatchBodyType  string `json:"type,omitempty"`
    77  }
    78  
    79  type HTTPChaosOptions struct {
    80  	Target string `json:"target"`
    81  	Port   int32  `json:"port"`
    82  	Path   string `json:"path"`
    83  	Method string `json:"method"`
    84  	Code   int32  `json:"code,omitempty"`
    85  
    86  	// abort command
    87  	Abort bool `json:"abort,omitempty"`
    88  	// delay command
    89  	Delay string `json:"delay,omitempty"`
    90  	// replace command
    91  	HTTPReplace `json:"replace,omitempty"`
    92  	// patch command
    93  	HTTPPatch `json:"patch,omitempty"`
    94  
    95  	FaultBaseOptions
    96  }
    97  
    98  func NewHTTPChaosOptions(f cmdutil.Factory, streams genericiooptions.IOStreams, action string) *HTTPChaosOptions {
    99  	o := &HTTPChaosOptions{
   100  		FaultBaseOptions: FaultBaseOptions{
   101  			CreateOptions: create.CreateOptions{
   102  				Factory:         f,
   103  				IOStreams:       streams,
   104  				CueTemplateName: CueTemplateHTTPChaos,
   105  				GVR:             GetGVR(Group, Version, ResourceHTTPChaos),
   106  			},
   107  			Action: action,
   108  		},
   109  	}
   110  	o.CreateOptions.PreCreate = o.PreCreate
   111  	o.CreateOptions.Options = o
   112  	return o
   113  }
   114  
   115  func NewHTTPChaosCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   116  	cmd := &cobra.Command{
   117  		Use:   "http",
   118  		Short: "Intercept HTTP requests and responses.",
   119  	}
   120  	cmd.AddCommand(
   121  		NewAbortCmd(f, streams),
   122  		NewHTTPDelayCmd(f, streams),
   123  		NewReplaceCmd(f, streams),
   124  		NewPatchCmd(f, streams),
   125  	)
   126  	return cmd
   127  }
   128  
   129  func NewAbortCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   130  	o := NewHTTPChaosOptions(f, streams, "")
   131  	cmd := o.NewCobraCommand(Abort, AbortShort)
   132  
   133  	o.AddCommonFlag(cmd)
   134  	cmd.Flags().BoolVar(&o.Abort, "abort", true, `Indicates whether to inject the fault that interrupts the connection.`)
   135  
   136  	return cmd
   137  }
   138  
   139  func NewHTTPDelayCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   140  	o := NewHTTPChaosOptions(f, streams, "")
   141  	cmd := o.NewCobraCommand(HTTPDelay, HTTPDelayShort)
   142  
   143  	o.AddCommonFlag(cmd)
   144  	cmd.Flags().StringVar(&o.Delay, "delay", "10s", `The time for delay.`)
   145  
   146  	return cmd
   147  }
   148  
   149  func NewReplaceCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   150  	o := NewHTTPChaosOptions(f, streams, "")
   151  	cmd := o.NewCobraCommand(Replace, ReplaceShort)
   152  
   153  	o.AddCommonFlag(cmd)
   154  	cmd.Flags().StringVar(&o.InputReplaceBody, "body", "", `The content of the request body or response body to replace the failure.`)
   155  	cmd.Flags().StringVar(&o.ReplacePath, "replace-path", "", `The URI path used to replace content.`)
   156  	cmd.Flags().StringVar(&o.ReplaceMethod, "replace-method", "", `The replaced content of the HTTP request method.`)
   157  
   158  	return cmd
   159  }
   160  
   161  func NewPatchCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   162  	o := NewHTTPChaosOptions(f, streams, "")
   163  	cmd := o.NewCobraCommand(Patch, PatchShort)
   164  
   165  	o.AddCommonFlag(cmd)
   166  	cmd.Flags().StringVar(&o.PatchBodyValue, "body", "", `The fault of the request body or response body with patch faults.`)
   167  	cmd.Flags().StringVar(&o.PatchBodyType, "type", "", `The type of patch faults of the request body or response body. Currently, it only supports JSON.`)
   168  
   169  	return cmd
   170  }
   171  
   172  func (o *HTTPChaosOptions) NewCobraCommand(use, short string) *cobra.Command {
   173  	return &cobra.Command{
   174  		Use:     use,
   175  		Short:   short,
   176  		Example: faultHTTPExample,
   177  		Run: func(cmd *cobra.Command, args []string) {
   178  			o.Args = args
   179  			cmdutil.CheckErr(o.CreateOptions.Complete())
   180  			cmdutil.CheckErr(o.Validate())
   181  			cmdutil.CheckErr(o.Complete())
   182  			cmdutil.CheckErr(o.Run())
   183  		},
   184  	}
   185  }
   186  
   187  func (o *HTTPChaosOptions) AddCommonFlag(cmd *cobra.Command) {
   188  	o.FaultBaseOptions.AddCommonFlag(cmd)
   189  
   190  	cmd.Flags().StringVar(&o.Target, "target", "Request", `Specifies whether the target of fault injection is Request or Response. The target-related fields should be configured at the same time.`)
   191  	cmd.Flags().Int32Var(&o.Port, "port", 80, `The TCP port that the target service listens on.`)
   192  	cmd.Flags().StringVar(&o.Path, "path", "*", `The URI path of the target request. Supports Matching wildcards.`)
   193  	cmd.Flags().StringVar(&o.Method, "method", "GET", `The HTTP method of the target request method. For example: GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH.`)
   194  	cmd.Flags().Int32Var(&o.Code, "code", 0, `The status code responded by target.`)
   195  
   196  	// register flag completion func
   197  	registerFlagCompletionFunc(cmd, o.Factory)
   198  }
   199  
   200  func (o *HTTPChaosOptions) Validate() error {
   201  	if o.PatchBodyType != "" && o.PatchBodyType != "JSON" {
   202  		return fmt.Errorf("--type only supports JSON")
   203  	}
   204  	if o.PatchBodyValue != "" && o.PatchBodyType == "" {
   205  		return fmt.Errorf("--type is required when --body is specified")
   206  	}
   207  	if o.PatchBodyType != "" && o.PatchBodyValue == "" {
   208  		return fmt.Errorf("--body is required when --type is specified")
   209  	}
   210  
   211  	var msg interface{}
   212  	if o.PatchBodyValue != "" && json.Unmarshal([]byte(o.PatchBodyValue), &msg) != nil {
   213  		return fmt.Errorf("--body is not a valid JSON")
   214  	}
   215  
   216  	if o.Target == "Request" && o.Code != 0 {
   217  		return fmt.Errorf("--code is only supported when --target=Response")
   218  	}
   219  
   220  	if ok, err := IsRegularMatch(o.Delay); !ok {
   221  		return err
   222  	}
   223  	return o.BaseValidate()
   224  }
   225  
   226  func (o *HTTPChaosOptions) Complete() error {
   227  	o.ReplaceBody = []byte(o.InputReplaceBody)
   228  	return o.BaseComplete()
   229  }
   230  
   231  func (o *HTTPChaosOptions) PreCreate(obj *unstructured.Unstructured) error {
   232  	c := &v1alpha1.HTTPChaos{}
   233  	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, c); err != nil {
   234  		return err
   235  	}
   236  
   237  	data, e := runtime.DefaultUnstructuredConverter.ToUnstructured(c)
   238  	if e != nil {
   239  		return e
   240  	}
   241  	obj.SetUnstructuredContent(data)
   242  	return nil
   243  }