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 }