google.golang.org/grpc@v1.62.1/xds/internal/httpfilter/fault/fault.go (about)

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