go.ligato.io/vpp-agent/v3@v3.5.0/pkg/models/registry.go (about) 1 // Copyright (c) 2019 Cisco and/or its affiliates. 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 models 16 17 import ( 18 "fmt" 19 "os" 20 "reflect" 21 "strings" 22 23 "go.ligato.io/cn-infra/v2/logging" 24 "google.golang.org/protobuf/proto" 25 "google.golang.org/protobuf/reflect/protoreflect" 26 "google.golang.org/protobuf/reflect/protoregistry" 27 "google.golang.org/protobuf/types/dynamicpb" 28 ) 29 30 var ( 31 // DefaultRegistry represents a global registry for local models (models known in compile time) 32 DefaultRegistry Registry = NewRegistry() 33 34 debugRegister = strings.Contains(os.Getenv("DEBUG_MODELS"), "register") 35 ) 36 37 // LocalRegistry defines model registry for managing registered local models. Local models are locally compiled into 38 // the program binary and hence some additional information in compare to remote models, i.e. go type. 39 type LocalRegistry struct { 40 registeredModelsByGoType map[reflect.Type]*LocallyKnownModel 41 registeredModelsByProtoName map[string]*LocallyKnownModel 42 modelNames map[string]*LocallyKnownModel 43 ordered []reflect.Type 44 proxied *RemoteRegistry 45 } 46 47 // NewRegistry returns initialized Registry. 48 func NewRegistry() *LocalRegistry { 49 return &LocalRegistry{ 50 registeredModelsByGoType: make(map[reflect.Type]*LocallyKnownModel), 51 registeredModelsByProtoName: make(map[string]*LocallyKnownModel), 52 modelNames: make(map[string]*LocallyKnownModel), 53 proxied: NewRemoteRegistry(), 54 } 55 } 56 57 // GetModel returns registered model for the given model name 58 // or error if model is not found. 59 func (r *LocalRegistry) GetModel(name string) (KnownModel, error) { 60 model, found := r.modelNames[name] 61 if !found { 62 if model, err := r.proxied.GetModel(name); err == nil { 63 return model, nil 64 } 65 return &LocallyKnownModel{}, fmt.Errorf("no model registered for name %v", name) 66 } 67 return model, nil 68 } 69 70 // GetModelFor returns registered model for the given proto message. 71 func (r *LocalRegistry) GetModelFor(x interface{}) (KnownModel, error) { 72 // find model by Go type 73 t := reflect.TypeOf(x) 74 model, found := r.registeredModelsByGoType[t] 75 if !found { 76 // check remotely retrieved models registered in local registry 77 if proxModel, err := r.proxied.GetModelFor(x); err == nil { 78 return proxModel, nil 79 } 80 81 // find model by Proto name 82 // (useful when using dynamically generated config instead of configurator.Config => go type of proto 83 // messages is in such case always dynamicpb.Message and never the go type of registered (generated) 84 // proto message) 85 if len(r.registeredModelsByProtoName) == 0 && len(r.registeredModelsByGoType) > 0 { 86 r.lazyInitRegisteredTypesByProtoName() 87 } 88 var protoName string 89 if pb, ok := x.(protoreflect.ProtoMessage); ok { 90 protoName = string(pb.ProtoReflect().Descriptor().FullName()) 91 } else if v1, ok := x.(proto.Message); ok { 92 protoName = string(v1.ProtoReflect().Descriptor().FullName()) 93 } 94 if protoName != "" { 95 if model, found = r.registeredModelsByProtoName[protoName]; found { 96 return model, nil 97 } 98 } 99 100 // find model by checking proto options 101 if model = r.checkProtoOptions(x); model == nil { 102 return &LocallyKnownModel{}, fmt.Errorf("no model registered for type %v", t) 103 } 104 } 105 return model, nil 106 } 107 108 // lazyInitRegisteredTypesByProtoName performs lazy initialization of registeredModelsByProtoName. The reason 109 // why initialization can't happen while registration (call of func Register(...)) is that some proto reflect 110 // functionality is not available during this time. The registration happens as variable initialization, but 111 // the reflection is initialized in init() func and that happens after variable initialization. 112 // 113 // Alternative solution would be to change when the models are registered (VPP-Agent have it like described 114 // above and 3rd party model are probably copying the same behaviour). So to not break anything, the lazy 115 // initialization seems like the best solution for now. 116 func (r *LocalRegistry) lazyInitRegisteredTypesByProtoName() { 117 for _, model := range r.registeredModelsByGoType { 118 r.registeredModelsByProtoName[model.ProtoName()] = model // ProtoName() == ProtoReflect().Descriptor().FullName() 119 } 120 } 121 122 // GetModelForKey returns registered model for the given key or error. 123 func (r *LocalRegistry) GetModelForKey(key string) (KnownModel, error) { 124 for _, model := range r.registeredModelsByGoType { 125 if model.IsKeyValid(key) { 126 return model, nil 127 } 128 } 129 if model, err := r.proxied.GetModelForKey(key); err == nil { 130 return model, nil 131 } 132 return &LocallyKnownModel{}, fmt.Errorf("no registered model matches for key %v", key) 133 } 134 135 // RegisteredModels returns all registered modules. 136 func (r *LocalRegistry) RegisteredModels() []KnownModel { 137 var models []KnownModel 138 for _, typ := range r.ordered { 139 models = append(models, r.registeredModelsByGoType[typ]) 140 } 141 models = append(models, r.proxied.RegisteredModels()...) 142 return models 143 } 144 145 // MessageTypeRegistry creates new message type registry from registered proto messages 146 func (r *LocalRegistry) MessageTypeRegistry() *protoregistry.Types { 147 typeRegistry := new(protoregistry.Types) 148 for _, model := range r.modelNames { 149 err := typeRegistry.RegisterMessage(dynamicpb.NewMessageType(model.proto.ProtoReflect().Descriptor())) 150 if err != nil { 151 logging.Warn("registering message %v for local registry failed: %v", model, err) 152 } 153 } 154 proxiedTypes := r.proxied.MessageTypeRegistry() 155 proxiedTypes.RangeMessages(func(mt protoreflect.MessageType) bool { 156 err := typeRegistry.RegisterMessage(mt) 157 if err != nil { 158 logging.Warn("registering proxied message %v for local registry failed: %v", mt, err) 159 } 160 return true 161 }) 162 return typeRegistry 163 } 164 165 // Register registers either a protobuf message known at compile-time together with the given model specification, 166 // or a remote model represented by an instance of ModelInfo obtained via KnownModels RPC from MetaService. 167 // While the former case is prevalent, the latter option is useful for scenarios with multiple agents and configuration 168 // requests being proxied from one to another (remote model registered into LocalRegistry may act as a proxy for the 169 // agent from which it was learned). 170 // If spec.Class is unset then it defaults to 'config'. 171 func (r *LocalRegistry) Register(x interface{}, spec Spec, opts ...ModelOption) (KnownModel, error) { 172 // check if the model was learned remotely 173 if modelInfo, isProxied := x.(*ModelInfo); isProxied { 174 // check for collision with local models 175 mName := ToSpec(modelInfo.Spec).ModelName() 176 if _, duplicate := r.modelNames[mName]; duplicate { 177 return nil, fmt.Errorf("model %v is already known locally and cannot be proxied", mName) 178 } 179 return r.proxied.Register(x, spec, opts...) 180 } 181 182 goType := reflect.TypeOf(x) 183 // Check go type duplicate registration 184 if m, ok := r.registeredModelsByGoType[goType]; ok { 185 return nil, fmt.Errorf("go type %v already registered for model %v", goType, m.Name()) 186 } 187 188 // Check model spec 189 if spec.Class == "" { 190 // spec with undefined class fallbacks to config 191 spec.Class = "config" 192 } 193 if spec.Version == "" { 194 spec.Version = "v0" 195 } 196 197 if err := spec.Validate(); err != nil { 198 return nil, fmt.Errorf("spec validation for %s failed: %v", goType, err) 199 } 200 201 // Check model name collisions 202 if pn, ok := r.modelNames[spec.ModelName()]; ok { 203 return nil, fmt.Errorf("model name %q already used by %s", spec.ModelName(), pn.goType) 204 } 205 if _, err := r.proxied.GetModel(spec.ModelName()); err == nil { 206 return nil, fmt.Errorf("model name %q is already proxied", spec.ModelName()) 207 } 208 209 model := &LocallyKnownModel{ 210 spec: spec, 211 goType: goType, 212 } 213 214 if pb, ok := x.(protoreflect.ProtoMessage); ok { 215 model.proto = pb 216 } else if v1, ok := x.(proto.Message); ok { 217 model.proto = v1 218 } 219 220 // Use GetName as fallback for generating name 221 if _, ok := x.(named); ok { 222 model.nameFunc = func(obj interface{}) (s string, e error) { 223 // handling dynamic messages (they don't implement named interface) 224 if dynMessage, ok := obj.(*dynamicpb.Message); ok { 225 obj, e = DynamicLocallyKnownMessageToGeneratedMessage(dynMessage) 226 if e != nil { 227 return "", e 228 } 229 } 230 // handling other proto message 231 return obj.(named).GetName(), nil 232 } 233 model.nameTemplate = namedTemplate 234 } 235 236 // Apply custom options 237 for _, opt := range opts { 238 opt(&model.modelOptions) 239 } 240 241 r.registeredModelsByGoType[goType] = model 242 r.modelNames[model.Name()] = model 243 r.ordered = append(r.ordered, goType) 244 245 if debugRegister { 246 fmt.Printf("- model %s registered: %+v\n", model.Name(), model) 247 } 248 return model, nil 249 }