dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/httpfilter/fault/fault.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  /*
    19   *
    20   * Copyright 2021 gRPC authors.
    21   *
    22   */
    23  
    24  // Package fault implements the Envoy Fault Injection HTTP filter.
    25  package fault
    26  
    27  import (
    28  	"context"
    29  	"errors"
    30  	"fmt"
    31  	"io"
    32  	"strconv"
    33  	"sync/atomic"
    34  	"time"
    35  )
    36  
    37  import (
    38  	cpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/common/fault/v3"
    39  	fpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3"
    40  	tpb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
    41  
    42  	"github.com/golang/protobuf/proto"
    43  	"github.com/golang/protobuf/ptypes"
    44  
    45  	"google.golang.org/grpc/codes"
    46  
    47  	"google.golang.org/grpc/metadata"
    48  
    49  	"google.golang.org/grpc/status"
    50  
    51  	"google.golang.org/protobuf/types/known/anypb"
    52  )
    53  
    54  import (
    55  	"dubbo.apache.org/dubbo-go/v3/xds/httpfilter"
    56  	"dubbo.apache.org/dubbo-go/v3/xds/utils/grpcrand"
    57  	iresolver "dubbo.apache.org/dubbo-go/v3/xds/utils/resolver"
    58  )
    59  
    60  const headerAbortHTTPStatus = "x-envoy-fault-abort-request"
    61  const headerAbortGRPCStatus = "x-envoy-fault-abort-grpc-request"
    62  const headerAbortPercentage = "x-envoy-fault-abort-request-percentage"
    63  
    64  const headerDelayPercentage = "x-envoy-fault-delay-request-percentage"
    65  const headerDelayDuration = "x-envoy-fault-delay-request"
    66  
    67  var statusMap = map[int]codes.Code{
    68  	400: codes.Internal,
    69  	401: codes.Unauthenticated,
    70  	403: codes.PermissionDenied,
    71  	404: codes.Unimplemented,
    72  	429: codes.Unavailable,
    73  	502: codes.Unavailable,
    74  	503: codes.Unavailable,
    75  	504: codes.Unavailable,
    76  }
    77  
    78  func init() {
    79  	httpfilter.Register(builder{})
    80  }
    81  
    82  type builder struct {
    83  }
    84  
    85  type config struct {
    86  	httpfilter.FilterConfig
    87  	config *fpb.HTTPFault
    88  }
    89  
    90  func (builder) TypeURLs() []string {
    91  	return []string{"type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault"}
    92  }
    93  
    94  // Parsing is the same for the base config and the override config.
    95  func parseConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
    96  	if cfg == nil {
    97  		return nil, fmt.Errorf("fault: nil configuration message provided")
    98  	}
    99  	any, ok := cfg.(*anypb.Any)
   100  	if !ok {
   101  		return nil, fmt.Errorf("fault: error parsing config %v: unknown type %T", cfg, cfg)
   102  	}
   103  	msg := new(fpb.HTTPFault)
   104  	if err := ptypes.UnmarshalAny(any, msg); err != nil {
   105  		return nil, fmt.Errorf("fault: error parsing config %v: %v", cfg, err)
   106  	}
   107  	return config{config: msg}, nil
   108  }
   109  
   110  func (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
   111  	return parseConfig(cfg)
   112  }
   113  
   114  func (builder) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
   115  	return parseConfig(override)
   116  }
   117  
   118  func (builder) IsTerminal() bool {
   119  	return false
   120  }
   121  
   122  var _ httpfilter.ClientInterceptorBuilder = builder{}
   123  
   124  func (builder) BuildClientInterceptor(cfg, override httpfilter.FilterConfig) (iresolver.ClientInterceptor, error) {
   125  	if cfg == nil {
   126  		return nil, fmt.Errorf("fault: nil config provided")
   127  	}
   128  
   129  	c, ok := cfg.(config)
   130  	if !ok {
   131  		return nil, fmt.Errorf("fault: incorrect config type provided (%T): %v", cfg, cfg)
   132  	}
   133  
   134  	if override != nil {
   135  		// override completely replaces the listener configuration; but we
   136  		// still validate the listener config type.
   137  		c, ok = override.(config)
   138  		if !ok {
   139  			return nil, fmt.Errorf("fault: incorrect override config type provided (%T): %v", override, override)
   140  		}
   141  	}
   142  
   143  	icfg := c.config
   144  	if (icfg.GetMaxActiveFaults() != nil && icfg.GetMaxActiveFaults().GetValue() == 0) ||
   145  		(icfg.GetDelay() == nil && icfg.GetAbort() == nil) {
   146  		return nil, nil
   147  	}
   148  	return &interceptor{config: icfg}, nil
   149  }
   150  
   151  type interceptor struct {
   152  	config *fpb.HTTPFault
   153  }
   154  
   155  var activeFaults uint32 // global active faults; accessed atomically
   156  
   157  func (i *interceptor) NewStream(ctx context.Context, ri iresolver.RPCInfo, done func(), newStream func(ctx context.Context, done func()) (iresolver.ClientStream, error)) (iresolver.ClientStream, error) {
   158  	if maxAF := i.config.GetMaxActiveFaults(); maxAF != nil {
   159  		defer atomic.AddUint32(&activeFaults, ^uint32(0)) // decrement counter
   160  		if af := atomic.AddUint32(&activeFaults, 1); af > maxAF.GetValue() {
   161  			// Would exceed maximum active fault limit.
   162  			return newStream(ctx, done)
   163  		}
   164  	}
   165  
   166  	if err := injectDelay(ctx, i.config.GetDelay()); err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	if err := injectAbort(ctx, i.config.GetAbort()); err != nil {
   171  		if err == errOKStream {
   172  			return &okStream{ctx: ctx}, nil
   173  		}
   174  		return nil, err
   175  	}
   176  	return newStream(ctx, done)
   177  }
   178  
   179  // For overriding in tests
   180  var randIntn = grpcrand.Intn
   181  var newTimer = time.NewTimer
   182  
   183  func injectDelay(ctx context.Context, delayCfg *cpb.FaultDelay) error {
   184  	numerator, denominator := splitPct(delayCfg.GetPercentage())
   185  	var delay time.Duration
   186  	switch delayType := delayCfg.GetFaultDelaySecifier().(type) {
   187  	case *cpb.FaultDelay_FixedDelay:
   188  		delay = delayType.FixedDelay.AsDuration()
   189  	case *cpb.FaultDelay_HeaderDelay_:
   190  		md, _ := metadata.FromOutgoingContext(ctx)
   191  		v := md[headerDelayDuration]
   192  		if v == nil {
   193  			// No delay configured for this RPC.
   194  			return nil
   195  		}
   196  		ms, ok := parseIntFromMD(v)
   197  		if !ok {
   198  			// Malformed header; no delay.
   199  			return nil
   200  		}
   201  		delay = time.Duration(ms) * time.Millisecond
   202  		if v := md[headerDelayPercentage]; v != nil {
   203  			if num, ok := parseIntFromMD(v); ok && num < numerator {
   204  				numerator = num
   205  			}
   206  		}
   207  	}
   208  	if delay == 0 || randIntn(denominator) >= numerator {
   209  		return nil
   210  	}
   211  	t := newTimer(delay)
   212  	select {
   213  	case <-t.C:
   214  	case <-ctx.Done():
   215  		t.Stop()
   216  		return ctx.Err()
   217  	}
   218  	return nil
   219  }
   220  
   221  func injectAbort(ctx context.Context, abortCfg *fpb.FaultAbort) error {
   222  	numerator, denominator := splitPct(abortCfg.GetPercentage())
   223  	code := codes.OK
   224  	okCode := false
   225  	switch errType := abortCfg.GetErrorType().(type) {
   226  	case *fpb.FaultAbort_HttpStatus:
   227  		code, okCode = grpcFromHTTP(int(errType.HttpStatus))
   228  	case *fpb.FaultAbort_GrpcStatus:
   229  		code, okCode = sanitizeGRPCCode(codes.Code(errType.GrpcStatus)), true
   230  	case *fpb.FaultAbort_HeaderAbort_:
   231  		md, _ := metadata.FromOutgoingContext(ctx)
   232  		if v := md[headerAbortHTTPStatus]; v != nil {
   233  			// HTTP status has priority over gRPC status.
   234  			if httpStatus, ok := parseIntFromMD(v); ok {
   235  				code, okCode = grpcFromHTTP(httpStatus)
   236  			}
   237  		} else if v := md[headerAbortGRPCStatus]; v != nil {
   238  			if grpcStatus, ok := parseIntFromMD(v); ok {
   239  				code, okCode = sanitizeGRPCCode(codes.Code(grpcStatus)), true
   240  			}
   241  		}
   242  		if v := md[headerAbortPercentage]; v != nil {
   243  			if num, ok := parseIntFromMD(v); ok && num < numerator {
   244  				numerator = num
   245  			}
   246  		}
   247  	}
   248  	if !okCode || randIntn(denominator) >= numerator {
   249  		return nil
   250  	}
   251  	if code == codes.OK {
   252  		return errOKStream
   253  	}
   254  	return status.Errorf(code, "RPC terminated due to fault injection")
   255  }
   256  
   257  var errOKStream = errors.New("stream terminated early with OK status")
   258  
   259  // parseIntFromMD returns the integer in the last header or nil if parsing
   260  // failed.
   261  func parseIntFromMD(header []string) (int, bool) {
   262  	if len(header) == 0 {
   263  		return 0, false
   264  	}
   265  	v, err := strconv.Atoi(header[len(header)-1])
   266  	return v, err == nil
   267  }
   268  
   269  func splitPct(fp *tpb.FractionalPercent) (num int, den int) {
   270  	if fp == nil {
   271  		return 0, 100
   272  	}
   273  	num = int(fp.GetNumerator())
   274  	switch fp.GetDenominator() {
   275  	case tpb.FractionalPercent_HUNDRED:
   276  		return num, 100
   277  	case tpb.FractionalPercent_TEN_THOUSAND:
   278  		return num, 10 * 1000
   279  	case tpb.FractionalPercent_MILLION:
   280  		return num, 1000 * 1000
   281  	}
   282  	return num, 100
   283  }
   284  
   285  func grpcFromHTTP(httpStatus int) (codes.Code, bool) {
   286  	if httpStatus < 200 || httpStatus >= 600 {
   287  		// Malformed; ignore this fault type.
   288  		return codes.OK, false
   289  	}
   290  	if c := statusMap[httpStatus]; c != codes.OK {
   291  		// OK = 0/the default for the map.
   292  		return c, true
   293  	}
   294  	// All undefined HTTP status codes convert to Unknown. HTTP status of 200
   295  	// is "success", but gRPC converts to Unknown due to missing grpc status.
   296  	return codes.Unknown, true
   297  }
   298  
   299  func sanitizeGRPCCode(c codes.Code) codes.Code {
   300  	if c > codes.Code(16) {
   301  		return codes.Unknown
   302  	}
   303  	return c
   304  }
   305  
   306  type okStream struct {
   307  	ctx context.Context
   308  }
   309  
   310  func (*okStream) Header() (metadata.MD, error) { return nil, nil }
   311  func (*okStream) Trailer() metadata.MD         { return nil }
   312  func (*okStream) CloseSend() error             { return nil }
   313  func (o *okStream) Context() context.Context   { return o.ctx }
   314  func (*okStream) SendMsg(m interface{}) error  { return io.EOF }
   315  func (*okStream) RecvMsg(m interface{}) error  { return io.EOF }