1// Copyright 2018 Istio Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package config is designed to listen to the config changes through the store and create a fully-resolved configuration 16// state that can be used by the rest of the runtime code. 17// 18// The main purpose of this library is to create an object-model that simplifies queries and correctness checks that 19// the client code needs to deal with. This is accomplished by making sure the config state is fully resolved, and 20// incorporating otherwise complex queries within this package. 21package config 22 23import ( 24 "bytes" 25 "context" 26 "encoding/json" 27 "fmt" 28 "sync" 29 30 "github.com/gogo/protobuf/jsonpb" 31 "github.com/gogo/protobuf/proto" 32 "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 33 "github.com/gogo/protobuf/types" 34 multierror "github.com/hashicorp/go-multierror" 35 "go.opencensus.io/stats" 36 37 "istio.io/api/mixer/adapter/model/v1beta1" 38 config "istio.io/api/policy/v1beta1" 39 "istio.io/istio/mixer/pkg/adapter" 40 "istio.io/istio/mixer/pkg/config/store" 41 "istio.io/istio/mixer/pkg/config/storetest" 42 "istio.io/istio/mixer/pkg/lang/checker" 43 "istio.io/istio/mixer/pkg/protobuf/yaml" 44 "istio.io/istio/mixer/pkg/protobuf/yaml/dynamic" 45 "istio.io/istio/mixer/pkg/runtime/config/constant" 46 "istio.io/istio/mixer/pkg/runtime/lang" 47 "istio.io/istio/mixer/pkg/runtime/monitoring" 48 "istio.io/istio/mixer/pkg/template" 49 "istio.io/pkg/attribute" 50 "istio.io/pkg/log" 51) 52 53// Ephemeral configuration state that gets updated by incoming config change events. By itself, the data contained 54// is not meaningful. BuildSnapshot must be called to create a new snapshot instance, which contains fully resolved 55// config. 56// The Ephemeral is thread safe, which mean the state can be incrementally built asynchronously, before calling 57// BuildSnapshot, using ApplyEvent. 58type Ephemeral struct { 59 // Static information 60 adapters map[string]*adapter.Info 61 templates map[string]*template.Info 62 63 // next snapshot id 64 nextID int64 65 66 // type checkers 67 attributes attribute.AttributeDescriptorFinder 68 tcs map[lang.LanguageRuntime]lang.TypeChecker 69 70 // The ephemeral object is used inside a webhooks validators which run as multiple nodes. 71 // Which means every ephemeral instance (associated with every isolated webhook node) needs to keep itself in sync with the 72 // store's state to do validation of incoming config stanza. The user of the ephemeral must therefore attach the 73 // ApplyEvent function to a background store store.WatchChanges callback. Therefore, we need to lock protect the entries 74 // because it can get updated either when webhook is invoked for validation or in the background via 75 // store.WatchChanges callbacks. 76 lock sync.RWMutex // protects resources below 77 78 // entries that are currently known. 79 entries map[store.Key]*store.Resource 80} 81 82// NewEphemeral returns a new Ephemeral instance. 83// 84// NOTE: initial state is computed even if there are errors in the config. Configuration that has errors 85// is reported in the returned error object, and is ignored in the snapshot creation. 86func NewEphemeral( 87 templates map[string]*template.Info, 88 adapters map[string]*adapter.Info) *Ephemeral { 89 90 e := &Ephemeral{ 91 templates: templates, 92 adapters: adapters, 93 94 nextID: 0, 95 tcs: make(map[lang.LanguageRuntime]checker.TypeChecker), 96 97 entries: make(map[store.Key]*store.Resource), 98 } 99 100 return e 101} 102 103func (e *Ephemeral) checker(mode lang.LanguageRuntime) checker.TypeChecker { 104 if out, ok := e.tcs[mode]; ok { 105 return out 106 } 107 108 out := lang.NewTypeChecker(e.attributes, mode) 109 e.tcs[mode] = out 110 return out 111} 112 113// SetState with the supplied state map. All existing ephemeral state is overwritten. 114func (e *Ephemeral) SetState(state map[store.Key]*store.Resource) { 115 e.lock.Lock() 116 defer e.lock.Unlock() 117 e.entries = state 118} 119 120// GetEntry returns the value stored for the key in the ephemeral. 121func (e *Ephemeral) GetEntry(event *store.Event) (*store.Resource, bool) { 122 e.lock.RLock() 123 defer e.lock.RUnlock() 124 v, ok := e.entries[event.Key] 125 return v, ok 126} 127 128// ApplyEvent to the internal ephemeral state. This gets called by an external event listener to relay store change 129// events to this ephemeral config object. 130func (e *Ephemeral) ApplyEvent(events []*store.Event) { 131 e.lock.Lock() 132 defer e.lock.Unlock() 133 for _, event := range events { 134 switch event.Type { 135 case store.Update: 136 e.entries[event.Key] = event.Value 137 case store.Delete: 138 delete(e.entries, event.Key) 139 } 140 } 141} 142 143// BuildSnapshot builds a stable, fully-resolved snapshot view of the configuration. 144func (e *Ephemeral) BuildSnapshot() (*Snapshot, error) { 145 errs := &multierror.Error{} 146 id := e.nextID 147 e.nextID++ 148 149 log.Debugf("Building new config.Snapshot: id='%d'", id) 150 151 // Allocate new monitoring context to use with the new snapshot. 152 monitoringCtx := context.Background() 153 154 e.lock.RLock() 155 156 attributes := e.processAttributeManifests(monitoringCtx) 157 158 shandlers := e.processStaticAdapterHandlerConfigs() 159 160 af := attribute.NewFinder(attributes) 161 e.attributes = af 162 instances, instErrs := e.processInstanceConfigs(errs) 163 164 // New dynamic configurations 165 dTemplates := e.processDynamicTemplateConfigs(monitoringCtx, errs) 166 dAdapters := e.processDynamicAdapterConfigs(monitoringCtx, dTemplates, errs) 167 dhandlers := e.processDynamicHandlerConfigs(monitoringCtx, dAdapters, errs) 168 dInstances, dInstErrs := e.processDynamicInstanceConfigs(dTemplates, errs) 169 170 rules := e.processRuleConfigs(monitoringCtx, shandlers, instances, dhandlers, dInstances, errs) 171 172 stats.Record(monitoringCtx, 173 monitoring.HandlersTotal.M(int64(len(shandlers)+len(dhandlers))), 174 monitoring.InstancesTotal.M(int64(len(instances)+len(dInstances))), 175 monitoring.RulesTotal.M(int64(len(rules))), 176 monitoring.AdapterInfosTotal.M(int64(len(dAdapters))), 177 monitoring.TemplatesTotal.M(int64(len(dTemplates))), 178 monitoring.InstanceErrs.M(instErrs+dInstErrs), 179 ) 180 181 s := &Snapshot{ 182 ID: id, 183 Templates: e.templates, 184 Adapters: e.adapters, 185 TemplateMetadatas: dTemplates, 186 AdapterMetadatas: dAdapters, 187 Attributes: af, 188 HandlersStatic: shandlers, 189 InstancesStatic: instances, 190 Rules: rules, 191 192 HandlersDynamic: dhandlers, 193 InstancesDynamic: dInstances, 194 195 MonitoringContext: monitoringCtx, 196 } 197 e.lock.RUnlock() 198 199 log.Debugf("config.Snapshot creation error=%v, contents:\n%s", errs.ErrorOrNil(), s) 200 return s, errs.ErrorOrNil() 201} 202 203func (e *Ephemeral) processAttributeManifests(ctx context.Context) map[string]*config.AttributeManifest_AttributeInfo { 204 attrs := make(map[string]*config.AttributeManifest_AttributeInfo) 205 for k, obj := range e.entries { 206 if k.Kind != constant.AttributeManifestKind { 207 continue 208 } 209 210 log.Debug("Start processing attributes from changed manifest...") 211 212 cfg := obj.Spec 213 for an, at := range cfg.(*config.AttributeManifest).Attributes { 214 attrs[an] = at 215 log.Debugf("Attribute '%s': '%s'.", an, at.ValueType) 216 } 217 } 218 219 // append all the well known attribute vocabulary from the static templates. 220 // 221 // ATTRIBUTE_GENERATOR variety templates allow operators to write Attributes 222 // using the $out.<field Name> convention, where $out refers to the output object from the attribute generating adapter. 223 // The list of valid names for a given Template is available in the Template.Info.AttributeManifests object. 224 for _, info := range e.templates { 225 if info.Variety != v1beta1.TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR { 226 continue 227 } 228 229 log.Debugf("Processing attributes from template: '%s'", info.Name) 230 231 for _, v := range info.AttributeManifests { 232 for an, at := range v.Attributes { 233 attrs[an] = at 234 log.Debugf("Attribute '%s': '%s'", an, at.ValueType) 235 } 236 } 237 } 238 239 log.Debug("Completed processing attributes.") 240 stats.Record(ctx, monitoring.AttributesTotal.M(int64(len(attrs)))) 241 return attrs 242} 243 244// convert converts unstructured spec into the target proto. 245func convert(spec map[string]interface{}, target proto.Message) error { 246 jsonData, err := json.Marshal(spec) 247 if err != nil { 248 return err 249 } 250 if err = jsonpb.Unmarshal(bytes.NewReader(jsonData), target); err != nil { 251 log.Warnf("unable to unmarshal: %s, %s", err.Error(), string(jsonData)) 252 } 253 return err 254} 255 256func (e *Ephemeral) processStaticAdapterHandlerConfigs() map[string]*HandlerStatic { 257 handlers := make(map[string]*HandlerStatic, len(e.adapters)) 258 259 for key, resource := range e.entries { 260 var info *adapter.Info 261 var found bool 262 263 if key.Kind == constant.HandlerKind { 264 log.Debugf("Static Handler: %#v (name: %s)", key, key.Name) 265 handlerProto := resource.Spec.(*config.Handler) 266 a, ok := e.adapters[handlerProto.CompiledAdapter] 267 if !ok { 268 continue 269 } 270 staticConfig := &HandlerStatic{ 271 // example: stdio.istio-system 272 Name: key.Name + "." + key.Namespace, 273 Adapter: a, 274 Params: a.DefaultConfig, 275 } 276 if handlerProto.Params != nil { 277 c := proto.Clone(staticConfig.Adapter.DefaultConfig) 278 if handlerProto.GetParams() != nil { 279 dict, err := toDictionary(handlerProto.Params) 280 if err != nil { 281 log.Warnf("could not convert handler params; using default config: %v", err) 282 } else if err := convert(dict, c); err != nil { 283 log.Warnf("could not convert handler params; using default config: %v", err) 284 } 285 } 286 staticConfig.Params = c 287 } 288 handlers[key.String()] = staticConfig 289 continue 290 } 291 292 if info, found = e.adapters[key.Kind]; !found { 293 // This config resource is not for an adapter (or at least not for one that Mixer is currently aware of). 294 continue 295 } 296 297 adapterName := key.String() 298 299 log.Debugf("Processing incoming handler config: name='%s'\n%s", adapterName, resource.Spec) 300 301 cfg := &HandlerStatic{ 302 Name: adapterName, 303 Adapter: info, 304 Params: resource.Spec, 305 } 306 307 handlers[cfg.Name] = cfg 308 } 309 310 return handlers 311} 312 313func getCanonicalRef(n, kind, ns string, lookup func(string) interface{}) (interface{}, string) { 314 name, altName := canonicalize(n, kind, ns) 315 v := lookup(name) 316 if v != nil { 317 return v, name 318 } 319 320 return lookup(altName), altName 321} 322 323func (e *Ephemeral) processDynamicHandlerConfigs(ctx context.Context, adapters map[string]*Adapter, errs *multierror.Error) map[string]*HandlerDynamic { 324 handlers := make(map[string]*HandlerDynamic, len(e.adapters)) 325 var validationErrs int64 326 327 for key, resource := range e.entries { 328 if key.Kind != constant.HandlerKind { 329 continue 330 } 331 332 handlerName := key.String() 333 log.Debugf("Processing incoming handler config: name='%s'\n%s", handlerName, resource.Spec) 334 335 hdl := resource.Spec.(*config.Handler) 336 if len(hdl.CompiledAdapter) > 0 { 337 continue // this will have already been added in processStaticHandlerConfigs 338 } 339 adpt, _ := getCanonicalRef(hdl.Adapter, constant.AdapterKind, key.Namespace, func(n string) interface{} { 340 if a, ok := adapters[n]; ok { 341 return a 342 } 343 return nil 344 }) 345 346 if adpt == nil { 347 validationErrs++ 348 appendErr(errs, fmt.Sprintf("handler='%s'.adapter", handlerName), "adapter '%s' not found", hdl.Adapter) 349 continue 350 } 351 adapter := adpt.(*Adapter) 352 353 var adapterCfg *types.Any 354 if len(adapter.ConfigDescSet.File) != 0 { 355 // validate if the param is valid 356 bytes, err := validateEncodeBytes(hdl.Params, adapter.ConfigDescSet, getParamsMsgFullName(adapter.PackageName)) 357 if err != nil { 358 validationErrs++ 359 appendErr(errs, fmt.Sprintf("handler='%s'.params", handlerName), err.Error()) 360 continue 361 } 362 typeFQN := adapter.PackageName + ".Params" 363 adapterCfg = asAny(typeFQN, bytes) 364 } 365 366 cfg := &HandlerDynamic{ 367 Name: handlerName, 368 Adapter: adapter, 369 Connection: hdl.Connection, 370 AdapterConfig: adapterCfg, 371 } 372 373 handlers[cfg.Name] = cfg 374 } 375 376 stats.Record(ctx, monitoring.HandlerValidationErrors.M(validationErrs)) 377 378 return handlers 379} 380 381const googleApis = "type.googleapis.com/" 382 383func asAny(msgFQN string, bytes []byte) *types.Any { 384 return &types.Any{ 385 TypeUrl: googleApis + msgFQN, 386 Value: bytes, 387 } 388} 389 390func (e *Ephemeral) processDynamicInstanceConfigs(templates map[string]*Template, errs *multierror.Error) (map[string]*InstanceDynamic, int64) { 391 instances := make(map[string]*InstanceDynamic, len(e.templates)) 392 var instErrs int64 393 394 for key, resource := range e.entries { 395 if key.Kind != constant.InstanceKind { 396 continue 397 } 398 399 inst := resource.Spec.(*config.Instance) 400 401 // should be processed as static instance 402 if inst.CompiledTemplate != "" { 403 continue 404 } 405 406 instanceName := key.String() 407 log.Debugf("Processing incoming instance config: name='%s'\n%s", instanceName, resource.Spec) 408 409 tmpl, _ := getCanonicalRef(inst.Template, constant.TemplateKind, key.Namespace, func(n string) interface{} { 410 if a, ok := templates[n]; ok { 411 return a 412 } 413 return nil 414 }) 415 416 if tmpl == nil { 417 instErrs++ 418 appendErr(errs, fmt.Sprintf("instance='%s'.template", instanceName), "template '%s' not found", inst.Template) 419 continue 420 } 421 422 template := tmpl.(*Template) 423 // validate if the param is valid 424 mode := lang.GetLanguageRuntime(resource.Metadata.Annotations) 425 compiler := lang.NewBuilder(e.attributes, mode) 426 resolver := yaml.NewResolver(template.FileDescSet) 427 b := dynamic.NewEncoderBuilder( 428 resolver, 429 compiler, 430 false) 431 var enc dynamic.Encoder 432 var params map[string]interface{} 433 var err error 434 if inst.Params != nil { 435 if params, err = toDictionary(inst.Params); err != nil { 436 instErrs++ 437 appendErr(errs, fmt.Sprintf("instance='%s'.params", instanceName), "invalid params block.") 438 continue 439 } 440 441 // name field is not provided by instance config author, instead it is added by Mixer into the request 442 // object that is passed to the adapter. 443 params["name"] = fmt.Sprintf("\"%s\"", instanceName) 444 enc, err = b.Build(getTemplatesMsgFullName(template.PackageName), params) 445 if err != nil { 446 instErrs++ 447 appendErr(errs, fmt.Sprintf("instance='%s'.params", instanceName), 448 "config does not conform to schema of template '%s': %v", inst.Template, err.Error()) 449 continue 450 } 451 } 452 453 cfg := &InstanceDynamic{ 454 Name: instanceName, 455 Template: template, 456 Encoder: enc, 457 Params: params, 458 AttributeBindings: inst.AttributeBindings, 459 Language: mode, 460 } 461 462 instances[cfg.Name] = cfg 463 } 464 465 return instances, instErrs 466} 467 468func getTemplatesMsgFullName(pkgName string) string { 469 return "." + pkgName + ".InstanceMsg" 470} 471 472func getParamsMsgFullName(pkgName string) string { 473 return "." + pkgName + ".Params" 474} 475 476func validateEncodeBytes(params *types.Struct, fds *descriptor.FileDescriptorSet, msgName string) ([]byte, error) { 477 if params == nil { 478 return []byte{}, nil 479 } 480 d, err := toDictionary(params) 481 if err != nil { 482 return nil, fmt.Errorf("error converting parameters to dictionary: %v", err) 483 } 484 return yaml.NewEncoder(fds).EncodeBytes(d, msgName, false) 485} 486 487func (e *Ephemeral) processInstanceConfigs(errs *multierror.Error) (map[string]*InstanceStatic, int64) { 488 instances := make(map[string]*InstanceStatic, len(e.templates)) 489 var instErrs int64 490 491 for key, resource := range e.entries { 492 var info *template.Info 493 var found bool 494 495 var params proto.Message 496 instanceName := key.String() 497 498 if key.Kind == constant.InstanceKind { 499 inst := resource.Spec.(*config.Instance) 500 if inst.CompiledTemplate == "" { 501 continue 502 } 503 info, found = e.templates[inst.CompiledTemplate] 504 if !found { 505 instErrs++ 506 appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), "missing compiled template") 507 continue 508 } 509 510 // cast from struct to template specific proto 511 params = proto.Clone(info.CtrCfg) 512 buf := &bytes.Buffer{} 513 514 if inst.Params == nil { 515 inst.Params = &types.Struct{Fields: make(map[string]*types.Value)} 516 } 517 // populate attribute bindings 518 if len(inst.AttributeBindings) > 0 { 519 bindings := &types.Struct{Fields: make(map[string]*types.Value)} 520 for k, v := range inst.AttributeBindings { 521 bindings.Fields[k] = &types.Value{Kind: &types.Value_StringValue{StringValue: v}} 522 } 523 inst.Params.Fields["attribute_bindings"] = &types.Value{ 524 Kind: &types.Value_StructValue{StructValue: bindings}, 525 } 526 } 527 if err := (&jsonpb.Marshaler{}).Marshal(buf, inst.Params); err != nil { 528 instErrs++ 529 appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error()) 530 continue 531 } 532 if err := (&jsonpb.Unmarshaler{AllowUnknownFields: false}).Unmarshal(buf, params); err != nil { 533 instErrs++ 534 appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error()) 535 continue 536 } 537 } else { 538 info, found = e.templates[key.Kind] 539 if !found { 540 // This config resource is not for an instance (or at least not for one that Mixer is currently aware of). 541 continue 542 } 543 params = resource.Spec 544 } 545 546 mode := lang.GetLanguageRuntime(resource.Metadata.Annotations) 547 548 log.Debugf("Processing incoming instance config: name='%s'\n%s", instanceName, params) 549 inferredType, err := info.InferType(params, func(s string) (config.ValueType, error) { 550 return e.checker(mode).EvalType(s) 551 }) 552 553 if err != nil { 554 instErrs++ 555 appendErr(errs, fmt.Sprintf("instance='%s'", instanceName), err.Error()) 556 continue 557 } 558 cfg := &InstanceStatic{ 559 Name: instanceName, 560 Template: info, 561 Params: params, 562 InferredType: inferredType, 563 Language: mode, 564 } 565 566 instances[cfg.Name] = cfg 567 } 568 569 return instances, instErrs 570} 571 572func (e *Ephemeral) processDynamicAdapterConfigs(ctx context.Context, availableTmpls map[string]*Template, errs *multierror.Error) map[string]*Adapter { 573 result := map[string]*Adapter{} 574 log.Debug("Begin processing adapter info configurations.") 575 var adapterErrs int64 576 for adapterInfoKey, resource := range e.entries { 577 if adapterInfoKey.Kind != constant.AdapterKind { 578 continue 579 } 580 581 adapterName := adapterInfoKey.String() 582 583 cfg := resource.Spec.(*v1beta1.Info) 584 585 log.Debugf("Processing incoming adapter info: name='%s'\n%v", adapterName, cfg) 586 587 fds, desc, err := GetAdapterCfgDescriptor(cfg.Config) 588 if err != nil { 589 adapterErrs++ 590 appendErr(errs, fmt.Sprintf("adapter='%s'", adapterName), "unable to parse adapter configuration: %v", err) 591 continue 592 } 593 supportedTmpls := make([]*Template, 0, len(cfg.Templates)) 594 for _, tmplN := range cfg.Templates { 595 596 template, tmplFullName := getCanonicalRef(tmplN, constant.TemplateKind, adapterInfoKey.Namespace, func(n string) interface{} { 597 if a, ok := availableTmpls[n]; ok { 598 return a 599 } 600 return nil 601 }) 602 if template == nil { 603 adapterErrs++ 604 appendErr(errs, fmt.Sprintf("adapter='%s'", adapterName), "unable to find template '%s'", tmplN) 605 continue 606 } 607 supportedTmpls = append(supportedTmpls, availableTmpls[tmplFullName]) 608 } 609 if len(cfg.Templates) == len(supportedTmpls) { 610 // only record adapter if all templates are valid 611 result[adapterName] = &Adapter{ 612 Name: adapterName, 613 ConfigDescSet: fds, 614 PackageName: desc.GetPackage(), 615 SupportedTemplates: supportedTmpls, 616 SessionBased: cfg.SessionBased, 617 Description: cfg.Description, 618 } 619 } 620 } 621 622 stats.Record(ctx, monitoring.AdapterErrs.M(adapterErrs)) 623 return result 624} 625 626func assertType(tc lang.TypeChecker, expression string, expectedType config.ValueType) error { 627 if t, err := tc.EvalType(expression); err != nil { 628 return err 629 } else if t != expectedType { 630 return fmt.Errorf("expression '%s' evaluated to type %v, expected type %v", expression, t, expectedType) 631 } 632 return nil 633} 634 635func (e *Ephemeral) processRuleConfigs( 636 ctx context.Context, 637 sHandlers map[string]*HandlerStatic, 638 sInstances map[string]*InstanceStatic, 639 dHandlers map[string]*HandlerDynamic, 640 dInstances map[string]*InstanceDynamic, 641 errs *multierror.Error) []*Rule { 642 643 log.Debug("Begin processing rule configurations.") 644 var ruleErrs int64 645 var rules []*Rule 646 647 for ruleKey, resource := range e.entries { 648 if ruleKey.Kind != constant.RulesKind { 649 continue 650 } 651 652 ruleName := ruleKey.String() 653 654 cfg := resource.Spec.(*config.Rule) 655 mode := lang.GetLanguageRuntime(resource.Metadata.Annotations) 656 657 log.Debugf("Processing incoming rule: name='%s'\n%s", ruleName, cfg) 658 659 if cfg.Match != "" { 660 if err := assertType(e.checker(mode), cfg.Match, config.BOOL); err != nil { 661 ruleErrs++ 662 appendErr(errs, fmt.Sprintf("rule='%s'.Match", ruleName), err.Error()) 663 } 664 } 665 666 // extract the set of actions from the rule, and the handlers they reference. 667 // A rule can have both static and dynamic actions. 668 669 actionsStat := make([]*ActionStatic, 0, len(cfg.Actions)) 670 actionsDynamic := make([]*ActionDynamic, 0, len(cfg.Actions)) 671 for i, a := range cfg.Actions { 672 log.Debugf("Processing action: %s[%d]", ruleName, i) 673 var processStaticHandler bool 674 var processDynamicHandler bool 675 var sahandler *HandlerStatic 676 var dahandler *HandlerDynamic 677 hdl, handlerName := getCanonicalRef(a.Handler, constant.HandlerKind, ruleKey.Namespace, func(n string) interface{} { 678 if a, ok := sHandlers[n]; ok { 679 return a 680 } 681 return nil 682 }) 683 if hdl != nil { 684 sahandler = hdl.(*HandlerStatic) 685 processStaticHandler = true 686 } else { 687 hdl, handlerName = getCanonicalRef(a.Handler, constant.HandlerKind, ruleKey.Namespace, func(n string) interface{} { 688 if a, ok := dHandlers[n]; ok { 689 return a 690 } 691 return nil 692 }) 693 694 if hdl != nil { 695 dahandler = hdl.(*HandlerDynamic) 696 processDynamicHandler = true 697 } 698 } 699 700 if !processStaticHandler && !processDynamicHandler { 701 ruleErrs++ 702 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "Handler not found: handler='%s'", a.Handler) 703 continue 704 } 705 706 if processStaticHandler { 707 // Keep track of unique instances, to avoid using the same instance multiple times within the same 708 // action 709 uniqueInstances := make(map[string]bool, len(a.Instances)) 710 711 actionInstances := make([]*InstanceStatic, 0, len(a.Instances)) 712 for _, instanceNameRef := range a.Instances { 713 inst, instName := getCanonicalRef(instanceNameRef, constant.InstanceKind, ruleKey.Namespace, func(n string) interface{} { 714 if a, ok := sInstances[n]; ok { 715 return a 716 } 717 return nil 718 }) 719 720 if inst == nil { 721 ruleErrs++ 722 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "Instance not found: instance='%s'", instanceNameRef) 723 continue 724 } 725 726 instance := inst.(*InstanceStatic) 727 728 if _, ok := uniqueInstances[instName]; ok { 729 ruleErrs++ 730 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), 731 "action specified the same instance multiple times: instance='%s',", instName) 732 continue 733 } 734 uniqueInstances[instName] = true 735 736 if !contains(sahandler.Adapter.SupportedTemplates, instance.Template.Name) { 737 ruleErrs++ 738 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), 739 "instance '%s' is of template '%s' which is not supported by handler '%s'", 740 instName, instance.Template.Name, handlerName) 741 continue 742 } 743 744 actionInstances = append(actionInstances, instance) 745 } 746 747 // If there are no valid instances found for this action, then elide the action. 748 if len(actionInstances) == 0 { 749 ruleErrs++ 750 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "No valid instances found") 751 continue 752 } 753 754 action := &ActionStatic{ 755 Handler: sahandler, 756 Instances: actionInstances, 757 Name: a.Name, 758 } 759 760 actionsStat = append(actionsStat, action) 761 } else { 762 // Keep track of unique instances, to avoid using the same instance multiple times within the same 763 // action 764 uniqueInstances := make(map[string]bool, len(a.Instances)) 765 766 actionInstances := make([]*InstanceDynamic, 0, len(a.Instances)) 767 for _, instanceNameRef := range a.Instances { 768 inst, instName := getCanonicalRef(instanceNameRef, constant.InstanceKind, ruleKey.Namespace, func(n string) interface{} { 769 if a, ok := dInstances[n]; ok { 770 return a 771 } 772 return nil 773 }) 774 775 if inst == nil { 776 ruleErrs++ 777 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "Instance not found: instance='%s'", instanceNameRef) 778 continue 779 } 780 781 instance := inst.(*InstanceDynamic) 782 if _, ok := uniqueInstances[instName]; ok { 783 ruleErrs++ 784 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), 785 "action specified the same instance multiple times: instance='%s',", instName) 786 continue 787 } 788 uniqueInstances[instName] = true 789 790 found := false 791 for _, supTmpl := range dahandler.Adapter.SupportedTemplates { 792 if supTmpl.Name == instance.Template.Name { 793 found = true 794 break 795 } 796 } 797 798 if !found { 799 ruleErrs++ 800 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), 801 "instance '%s' is of template '%s' which is not supported by handler '%s'", 802 instName, instance.Template.Name, handlerName) 803 continue 804 } 805 806 actionInstances = append(actionInstances, instance) 807 } 808 809 // If there are no valid instances found for this action, then elide the action. 810 if len(actionInstances) == 0 { 811 ruleErrs++ 812 appendErr(errs, fmt.Sprintf("action='%s[%d]'", ruleName, i), "No valid instances found") 813 continue 814 } 815 816 action := &ActionDynamic{ 817 Handler: dahandler, 818 Instances: actionInstances, 819 Name: a.Name, 820 } 821 822 actionsDynamic = append(actionsDynamic, action) 823 } 824 } 825 826 // If there are no valid actions found for this rule, then elide the rule. 827 if len(actionsStat) == 0 && len(actionsDynamic) == 0 && 828 len(cfg.RequestHeaderOperations) == 0 && len(cfg.ResponseHeaderOperations) == 0 { 829 ruleErrs++ 830 appendErr(errs, fmt.Sprintf("rule=%s", ruleName), "No valid actions found in rule") 831 continue 832 } 833 834 rule := &Rule{ 835 Name: ruleName, 836 Namespace: ruleKey.Namespace, 837 ActionsStatic: actionsStat, 838 ActionsDynamic: actionsDynamic, 839 Match: cfg.Match, 840 RequestHeaderOperations: cfg.RequestHeaderOperations, 841 ResponseHeaderOperations: cfg.ResponseHeaderOperations, 842 Language: mode, 843 } 844 845 rules = append(rules, rule) 846 } 847 848 stats.Record(ctx, monitoring.RuleErrs.M(ruleErrs)) 849 850 return rules 851} 852 853func contains(strs []string, w string) bool { 854 for _, v := range strs { 855 if v == w { 856 return true 857 } 858 } 859 return false 860} 861 862func (e *Ephemeral) processDynamicTemplateConfigs(ctx context.Context, errs *multierror.Error) map[string]*Template { 863 result := map[string]*Template{} 864 log.Debug("Begin processing templates.") 865 var templateErrs int64 866 for templateKey, resource := range e.entries { 867 if templateKey.Kind != constant.TemplateKind { 868 continue 869 } 870 871 templateName := templateKey.String() 872 cfg := resource.Spec.(*v1beta1.Template) 873 log.Debugf("Processing incoming template: name='%s'\n%v", templateName, cfg) 874 875 fds, desc, name, variety, err := GetTmplDescriptor(cfg.Descriptor_) 876 if err != nil { 877 templateErrs++ 878 appendErr(errs, fmt.Sprintf("template='%s'", templateName), "unable to parse descriptor: %v", err) 879 continue 880 } 881 882 result[templateName] = &Template{ 883 Name: templateName, 884 InternalPackageDerivedName: name, 885 FileDescSet: fds, 886 PackageName: desc.GetPackage(), 887 Variety: variety, 888 } 889 890 if variety == v1beta1.TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR || variety == v1beta1.TEMPLATE_VARIETY_CHECK_WITH_OUTPUT { 891 resolver := yaml.NewResolver(fds) 892 msgName := "." + desc.GetPackage() + ".OutputMsg" 893 // OutputMsg is a fixed generated name for the output template 894 desc := resolver.ResolveMessage(msgName) 895 if desc == nil { 896 continue 897 } 898 899 // We only support a single level of nesting in output templates. 900 attributes := make(map[string]*config.AttributeManifest_AttributeInfo) 901 for _, field := range desc.GetField() { 902 attributes["output."+field.GetName()] = &config.AttributeManifest_AttributeInfo{ 903 ValueType: yaml.DecodeType(resolver, field), 904 } 905 } 906 907 result[templateName].AttributeManifest = attributes 908 } 909 } 910 stats.Record(ctx, monitoring.TemplateErrs.M(templateErrs)) 911 return result 912} 913 914func appendErr(errs *multierror.Error, field string, format string, a ...interface{}) { 915 err := fmt.Errorf(format, a...) 916 log.Debug(err.Error()) 917 _ = multierror.Append(errs, adapter.ConfigError{Field: field, Underlying: err}) 918} 919 920// GetSnapshotForTest creates a config.Snapshot for testing purposes, based on the supplied configuration. 921func GetSnapshotForTest(templates map[string]*template.Info, adapters map[string]*adapter.Info, serviceConfig string, globalConfig string) (*Snapshot, error) { 922 store, _ := storetest.SetupStoreForTest(serviceConfig, globalConfig) 923 924 _ = store.Init(KindMap(adapters, templates)) 925 926 data := store.List() 927 928 // NewEphemeral tries to build a snapshot with empty entries therefore it never fails; Ignoring the error. 929 e := NewEphemeral(templates, adapters) 930 931 e.SetState(data) 932 933 store.Stop() 934 935 return e.BuildSnapshot() 936} 937