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 }