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  }