github.com/yandex/pandora@v0.5.32/components/guns/grpc/scenario/core.go (about) 1 package scenario 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "math/rand" 8 "time" 9 10 "github.com/jhump/protoreflect/dynamic" 11 grpcgun "github.com/yandex/pandora/components/guns/grpc" 12 "github.com/yandex/pandora/core" 13 "github.com/yandex/pandora/core/aggregator/netsample" 14 "github.com/yandex/pandora/core/warmup" 15 "github.com/yandex/pandora/lib/answlog" 16 "go.uber.org/zap" 17 "golang.org/x/exp/maps" 18 "google.golang.org/grpc/metadata" 19 ) 20 21 const defaultTimeout = time.Second * 15 22 23 type GunConfig struct { 24 Target string `validate:"required"` 25 ReflectPort int64 `config:"reflect_port"` 26 ReflectMetadata map[string]string `config:"reflect_metadata"` 27 Timeout time.Duration `config:"timeout"` // grpc request timeout 28 TLS bool `config:"tls"` 29 DialOptions GrpcDialOptions `config:"dial_options"` 30 AnswLog AnswLogConfig `config:"answlog"` 31 } 32 33 type GrpcDialOptions struct { 34 Authority string `config:"authority"` 35 Timeout time.Duration `config:"timeout"` 36 } 37 38 type AnswLogConfig struct { 39 Enabled bool `config:"enabled"` 40 Path string `config:"path"` 41 Filter string `config:"filter" valid:"oneof=all warning error"` 42 } 43 44 func DefaultGunConfig() GunConfig { 45 return GunConfig{ 46 Target: "default target", 47 AnswLog: AnswLogConfig{ 48 Enabled: false, 49 Path: "answ.log", 50 Filter: "all", 51 }, 52 } 53 } 54 55 func NewGun(conf GunConfig) *Gun { 56 answLog := answlog.Init(conf.AnswLog.Path, conf.AnswLog.Enabled) 57 r := rand.New(rand.NewSource(0)) //TODO: use real random 58 return &Gun{ 59 templ: NewTextTemplater(), 60 gun: &grpcgun.Gun{Conf: grpcgun.GunConfig{ 61 Target: conf.Target, 62 ReflectPort: conf.ReflectPort, 63 ReflectMetadata: conf.ReflectMetadata, 64 Timeout: conf.Timeout, 65 TLS: conf.TLS, 66 DialOptions: grpcgun.GrpcDialOptions{ 67 Authority: conf.DialOptions.Authority, 68 Timeout: conf.DialOptions.Timeout, 69 }, 70 AnswLog: grpcgun.AnswLogConfig{ 71 Enabled: conf.AnswLog.Enabled, 72 Path: conf.AnswLog.Path, 73 Filter: conf.AnswLog.Filter, 74 }, 75 }, 76 AnswLog: answLog}, 77 rand: r, 78 } 79 } 80 81 type Gun struct { 82 gun *grpcgun.Gun 83 rand *rand.Rand 84 templ Templater 85 } 86 87 func (g *Gun) WarmUp(opts *warmup.Options) (interface{}, error) { 88 return g.gun.WarmUp(opts) 89 } 90 91 func (g *Gun) Bind(aggr core.Aggregator, deps core.GunDeps) error { 92 return g.gun.Bind(aggr, deps) 93 } 94 95 func (g *Gun) Shoot(am core.Ammo) { 96 scen := am.(*Scenario) 97 98 templateVars := map[string]any{} 99 if scen.VariableStorage != nil { 100 templateVars["source"] = scen.VariableStorage.Variables() 101 } else { 102 templateVars["source"] = map[string]any{} 103 } 104 105 err := g.shoot(scen, templateVars) 106 if err != nil { 107 g.gun.Log.Warn("Invalid ammo", zap.Uint64("request", scen.id), zap.Error(err)) 108 return 109 } else { 110 g.gun.Log.Debug("Valid ammo", zap.Uint64("request", scen.id)) 111 } 112 } 113 114 func (g *Gun) shoot(ammo *Scenario, templateVars map[string]any) error { 115 if templateVars == nil { 116 templateVars = map[string]any{} 117 } 118 119 requestVars := map[string]any{} 120 templateVars["request"] = requestVars 121 if g.gun.DebugLog { 122 g.gun.GunDeps.Log.Debug("Source variables", zap.Any("variables", templateVars)) 123 } 124 125 startAt := time.Now() 126 for _, call := range ammo.Calls { 127 tag := ammo.Name + "." + call.Tag 128 sample := netsample.Acquire(tag) 129 130 err := g.shootStep(&call, sample, ammo.Name, templateVars, requestVars) 131 if err != nil { 132 return err 133 } 134 } 135 spent := time.Since(startAt) 136 if ammo.MinWaitingTime > spent { 137 time.Sleep(ammo.MinWaitingTime - spent) 138 } 139 return nil 140 } 141 142 func (g *Gun) shootStep(step *Call, sample *netsample.Sample, ammoName string, templateVars map[string]any, requestVars map[string]any) error { 143 const op = "base_gun.shootStep" 144 code := 0 145 defer func() { 146 sample.SetProtoCode(code) 147 g.gun.Aggr.Report(sample) 148 }() 149 150 stepVars := map[string]any{} 151 requestVars[step.Name] = stepVars 152 153 // Preprocessor 154 preprocVars := map[string]any{} 155 for _, preProcessor := range step.Preprocessors { 156 pp, err := preProcessor.Process(step, templateVars) 157 if err != nil { 158 return fmt.Errorf("%s preProcessor %w", op, err) 159 } 160 preprocVars = mergeMaps(preprocVars, pp) 161 if g.gun.DebugLog { 162 g.gun.GunDeps.Log.Debug("PreparePreprocessor variables", zap.Any(fmt.Sprintf(".request.%s.preprocessor", step.Name), pp)) 163 } 164 } 165 stepVars["preprocessor"] = preprocVars 166 167 // Template 168 payloadJSON, err := g.templ.Apply(step.Payload, step.Metadata, templateVars, ammoName, step.Name) 169 if err != nil { 170 return fmt.Errorf("%s templater.Apply %w", op, err) 171 } 172 173 // Method 174 method, ok := g.gun.Services[step.Call] 175 if !ok { 176 g.gun.GunDeps.Log.Error("invalid step.Call", zap.String("method", step.Call), 177 zap.Strings("allowed_methods", maps.Keys(g.gun.Services))) 178 return fmt.Errorf("%s invalid step.Call", op) 179 } 180 181 md := method.GetInputType() 182 message := dynamic.NewMessage(md) 183 err = message.UnmarshalJSON(payloadJSON) 184 if err != nil { 185 code = 400 186 g.gun.GunDeps.Log.Error("invalid payload. Cant unmarshal gRPC", zap.Error(err)) 187 return fmt.Errorf("%s invalid payload. Cant unmarshal gRPC", op) 188 } 189 190 timeout := defaultTimeout 191 if g.gun.Conf.Timeout != 0 { 192 timeout = g.gun.Conf.Timeout 193 } 194 195 ctx, cancel := context.WithTimeout(context.Background(), timeout) 196 defer cancel() 197 ctx = metadata.NewOutgoingContext(ctx, metadata.New(step.Metadata)) 198 out, grpcErr := g.gun.Stub.InvokeRpc(ctx, &method, message) 199 code = grpcgun.ConvertGrpcStatus(grpcErr) 200 sample.SetProtoCode(code) // for setRTT inside 201 202 if grpcErr != nil { 203 g.gun.GunDeps.Log.Error("response error", zap.Error(err)) 204 } 205 206 g.gun.Answ(&method, message, step.Metadata, out, grpcErr, code) 207 208 for _, postProcessor := range step.Postprocessors { 209 pp, err := postProcessor.Process(out, code) 210 if err != nil { 211 return fmt.Errorf("%s postProcessor %w", op, err) 212 } 213 stepVars = mergeMaps(stepVars, pp) 214 if g.gun.DebugLog { 215 g.gun.GunDeps.Log.Debug("Postprocessor variables", zap.Any(fmt.Sprintf(".request.%s.postprocessor", step.Name), pp)) 216 } 217 } 218 if out != nil { 219 // Postprocessor 220 // if it is nessesary 221 md = method.GetOutputType() 222 message = dynamic.NewMessage(md) 223 err = message.ConvertFrom(out) 224 if err != nil { 225 // unexpected result 226 return fmt.Errorf("%s message.ConvertFrom `%s`; err: %w", op, out.String(), err) 227 228 } 229 b, err := message.MarshalJSON() 230 if err != nil { 231 // unexpected result 232 return fmt.Errorf("%s message.MarshalJSON %w", op, err) 233 } 234 var outMap map[string]any 235 err = json.Unmarshal(b, &outMap) 236 if err != nil { 237 // unexpected result 238 return fmt.Errorf("%s json.Unmarshal %w", op, err) 239 } 240 stepVars["postprocessor"] = outMap 241 242 if g.gun.DebugLog { 243 g.gun.GunDeps.Log.Debug("Postprocessor variables", zap.String(fmt.Sprintf(".resuest.%s.postprocessor", step.Name), out.String())) 244 } 245 } 246 247 if step.Sleep > 0 { 248 time.Sleep(step.Sleep) 249 } 250 251 return nil 252 } 253 254 // mergeMaps merges newvars into previous 255 // if key exists in previous, it will be skipped 256 func mergeMaps(previous map[string]any, newvars map[string]any) map[string]any { 257 for k, v := range newvars { 258 if _, ok := previous[k]; !ok { 259 previous[k] = v 260 } 261 } 262 return previous 263 } 264 265 func (g *Gun) reportErr(sample *netsample.Sample, err error) { 266 if err == nil { 267 return 268 } 269 sample.AddTag("__EMPTY__") 270 sample.SetProtoCode(0) 271 sample.SetErr(err) 272 g.gun.Aggr.Report(sample) 273 } 274 275 var _ warmup.WarmedUp = (*Gun)(nil)