go.ligato.io/vpp-agent/v3@v3.5.0/client/remoteclient/grpc_client.go (about)

     1  package remoteclient
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"google.golang.org/grpc"
     9  	"google.golang.org/protobuf/proto"
    10  	"google.golang.org/protobuf/reflect/protodesc"
    11  	"google.golang.org/protobuf/reflect/protoreflect"
    12  	"google.golang.org/protobuf/reflect/protoregistry"
    13  	"google.golang.org/protobuf/types/descriptorpb"
    14  
    15  	"go.ligato.io/vpp-agent/v3/client"
    16  	"go.ligato.io/vpp-agent/v3/pkg/models"
    17  	"go.ligato.io/vpp-agent/v3/pkg/util"
    18  	"go.ligato.io/vpp-agent/v3/proto/ligato/generic"
    19  )
    20  
    21  type grpcClient struct {
    22  	manager       generic.ManagerServiceClient
    23  	meta          generic.MetaServiceClient
    24  	modelRegistry models.Registry
    25  }
    26  
    27  type NewClientOption = func(client.GenericClient) error
    28  
    29  // NewClientGRPC returns new instance that uses given service client for requests.
    30  func NewClientGRPC(conn grpc.ClientConnInterface, options ...NewClientOption) (client.ConfigClient, error) {
    31  	manager := generic.NewManagerServiceClient(conn)
    32  	meta := generic.NewMetaServiceClient(conn)
    33  	c := &grpcClient{
    34  		manager:       manager,
    35  		meta:          meta,
    36  		modelRegistry: models.DefaultRegistry,
    37  	}
    38  	for _, option := range options {
    39  		if err := option(c); err != nil {
    40  			return nil, fmt.Errorf("cannot apply option to newly created GRPC client due to: %w", err)
    41  		}
    42  	}
    43  	return c, nil
    44  }
    45  
    46  func (c *grpcClient) KnownModels(class string) ([]*client.ModelInfo, error) {
    47  	ctx := context.Background()
    48  
    49  	// get known models from meta service
    50  	resp, err := c.meta.KnownModels(ctx, &generic.KnownModelsRequest{
    51  		Class: class,
    52  	})
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	knownModels := resp.KnownModels
    57  
    58  	// extract proto files for meta service known models
    59  	protoFilePaths := make(map[string]struct{}) // using map as set for deduplication
    60  	for _, modelDetail := range knownModels {
    61  		protoFilePath, err := client.ModelOptionFor("protoFile", modelDetail.Options)
    62  		if err != nil {
    63  			return nil, fmt.Errorf("cannot get protoFile from model options of "+
    64  				"known model %v due to: %w", modelDetail.ProtoName, err)
    65  		}
    66  		protoFilePaths[protoFilePath] = struct{}{}
    67  	}
    68  
    69  	// query meta service for extracted proto files to get their file descriptor protos
    70  	fileDescProtos := make(map[string]*descriptorpb.FileDescriptorProto) // deduplicaton + data container
    71  	for protoFilePath := range protoFilePaths {
    72  		ctx := context.Background()
    73  		resp, err := c.meta.ProtoFileDescriptor(ctx, &generic.ProtoFileDescriptorRequest{
    74  			FullProtoFileName: protoFilePath,
    75  		})
    76  		if err != nil {
    77  			return nil, fmt.Errorf("cannot retrieve ProtoFileDescriptor "+
    78  				"for proto file %v due to: %w", protoFilePath, err)
    79  		}
    80  		if resp.FileDescriptor == nil {
    81  			return nil, fmt.Errorf("returned file descriptor proto "+
    82  				"for proto file %v from meta service can't be nil", protoFilePath)
    83  		}
    84  		if resp.FileImportDescriptors == nil {
    85  			return nil, fmt.Errorf("returned import file descriptors proto "+
    86  				"for proto file %v from meta service can't be nil", protoFilePath)
    87  		}
    88  
    89  		fileDescProtos[*resp.FileDescriptor.Name] = resp.FileDescriptor
    90  		for _, fid := range resp.FileImportDescriptors.File {
    91  			if fid != nil {
    92  				fileDescProtos[*fid.Name] = fid
    93  			}
    94  		}
    95  	}
    96  
    97  	// convert file descriptor protos to file descriptors
    98  	fileDescProtosSlice := make([]*descriptorpb.FileDescriptorProto, 0) // conversion set to slice
    99  	for _, fdp := range fileDescProtos {
   100  		fileDescProtosSlice = append(fileDescProtosSlice, fdp)
   101  	}
   102  	fileDescriptors, err := toFileDescriptors(fileDescProtosSlice)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("cannot convert file descriptor protos to file descriptors "+
   105  			"(for dependency registry creation) due to: %w", err)
   106  	}
   107  
   108  	// extract all messages from file descriptors
   109  	messageDescriptors := make(map[string]protoreflect.MessageDescriptor)
   110  	for _, fd := range fileDescriptors {
   111  		for i := 0; i < fd.Messages().Len(); i++ {
   112  			messageDescriptors[string(fd.Messages().Get(i).FullName())] = fd.Messages().Get(i)
   113  		}
   114  	}
   115  
   116  	// pack all gathered information into correct output format
   117  	var result []*models.ModelInfo
   118  	for _, info := range knownModels {
   119  		result = append(result, &models.ModelInfo{
   120  			ModelDetail:       info,
   121  			MessageDescriptor: messageDescriptors[info.ProtoName],
   122  		})
   123  	}
   124  
   125  	return result, nil
   126  }
   127  
   128  func (c *grpcClient) ChangeRequest() client.ChangeRequest {
   129  	return &setConfigRequest{
   130  		client:        c.manager,
   131  		modelRegistry: c.modelRegistry,
   132  		req:           &generic.SetConfigRequest{},
   133  	}
   134  }
   135  
   136  func (c *grpcClient) ResyncConfig(items ...proto.Message) error {
   137  	req := &generic.SetConfigRequest{
   138  		OverwriteAll: true,
   139  	}
   140  
   141  	for _, protoModel := range items {
   142  		item, err := models.MarshalItemUsingModelRegistry(protoModel, c.modelRegistry)
   143  		if err != nil {
   144  			return err
   145  		}
   146  		req.Updates = append(req.Updates, &generic.UpdateItem{
   147  			Item: item,
   148  		})
   149  	}
   150  
   151  	_, err := c.manager.SetConfig(context.Background(), req)
   152  	return err
   153  }
   154  
   155  func (c *grpcClient) GetFilteredConfig(filter client.Filter, dsts ...interface{}) error {
   156  	ctx := context.Background()
   157  
   158  	resp, err := c.manager.GetConfig(ctx, &generic.GetConfigRequest{Ids: filter.Ids, Labels: filter.Labels})
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	protos := map[string]proto.Message{}
   164  	for _, item := range resp.Items {
   165  		var key string
   166  		val, err := models.UnmarshalItemUsingModelRegistry(item.Item, c.modelRegistry)
   167  		if err != nil {
   168  			return err
   169  		}
   170  		if data := item.Item.GetData(); data != nil {
   171  			key, err = models.GetKeyUsingModelRegistry(val, c.modelRegistry)
   172  		} else {
   173  			key, err = models.GetKeyForItemUsingModelRegistry(item.Item, c.modelRegistry)
   174  		}
   175  		if err != nil {
   176  			return err
   177  		}
   178  		protos[key] = val
   179  	}
   180  
   181  	protoDsts := extractProtoMessages(dsts)
   182  	if len(dsts) == len(protoDsts) { // all dsts are proto messages
   183  		// TODO the clearIgnoreLayerCount function argument should be a option of generic.Client
   184  		//  (the value 1 generates from dynamic config the same json/yaml output as the hardcoded
   185  		//  configurator.Config and therefore serves for backward compatibility)
   186  		util.PlaceProtosIntoProtos(protoMapToList(protos), 1, protoDsts...)
   187  	} else {
   188  		util.PlaceProtos(protos, dsts...)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func (c *grpcClient) GetConfig(dsts ...interface{}) error {
   195  	return c.GetFilteredConfig(client.Filter{}, dsts...)
   196  }
   197  
   198  func (c *grpcClient) GetItems(ctx context.Context) ([]*client.ConfigItem, error) {
   199  	resp, err := c.manager.GetConfig(ctx, &generic.GetConfigRequest{})
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	return resp.GetItems(), err
   204  }
   205  
   206  func (c *grpcClient) UpdateItems(ctx context.Context, items []client.UpdateItem, resync bool) ([]*client.UpdateResult, error) {
   207  	req := &generic.SetConfigRequest{
   208  		OverwriteAll: resync,
   209  	}
   210  	for _, ui := range items {
   211  		var item *generic.Item
   212  		item, err := models.MarshalItemUsingModelRegistry(ui.Message, c.modelRegistry)
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  		req.Updates = append(req.Updates, &generic.UpdateItem{
   217  			Item:   item,
   218  			Labels: ui.Labels,
   219  		})
   220  	}
   221  	res, err := c.manager.SetConfig(ctx, req)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	var updateResults []*client.UpdateResult
   226  	for _, r := range res.Results {
   227  		updateResults = append(updateResults, &client.UpdateResult{
   228  			Key:    r.Key,
   229  			Status: r.Status,
   230  		})
   231  	}
   232  	return updateResults, nil
   233  }
   234  
   235  func (c *grpcClient) DeleteItems(ctx context.Context, items []client.UpdateItem) ([]*client.UpdateResult, error) {
   236  	req := &generic.SetConfigRequest{}
   237  	for _, ui := range items {
   238  		var item *generic.Item
   239  		item, err := models.MarshalItemUsingModelRegistry(ui.Message, c.modelRegistry)
   240  		if err != nil {
   241  			return nil, err
   242  		}
   243  		item.Data = nil // delete
   244  		req.Updates = append(req.Updates, &generic.UpdateItem{
   245  			Item: item,
   246  		})
   247  	}
   248  	res, err := c.manager.SetConfig(ctx, req)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	var updateResults []*client.UpdateResult
   253  	for _, r := range res.Results {
   254  		updateResults = append(updateResults, &client.UpdateResult{
   255  			Key:    r.Key,
   256  			Status: r.Status,
   257  		})
   258  	}
   259  	return updateResults, nil
   260  }
   261  
   262  func (c *grpcClient) DumpState() ([]*client.StateItem, error) {
   263  	ctx := context.Background()
   264  
   265  	resp, err := c.manager.DumpState(ctx, &generic.DumpStateRequest{})
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	return resp.GetItems(), nil
   271  }
   272  
   273  type setConfigRequest struct {
   274  	client        generic.ManagerServiceClient
   275  	modelRegistry models.Registry
   276  	req           *generic.SetConfigRequest
   277  	err           error
   278  }
   279  
   280  func (r *setConfigRequest) Update(items ...proto.Message) client.ChangeRequest {
   281  	if r.err != nil {
   282  		return r
   283  	}
   284  	for _, protoModel := range items {
   285  		var item *generic.Item
   286  		item, r.err = models.MarshalItemUsingModelRegistry(protoModel, r.modelRegistry)
   287  		if r.err != nil {
   288  			return r
   289  		}
   290  		r.req.Updates = append(r.req.Updates, &generic.UpdateItem{
   291  			Item: item,
   292  		})
   293  	}
   294  	return r
   295  }
   296  
   297  func (r *setConfigRequest) Delete(items ...proto.Message) client.ChangeRequest {
   298  	if r.err != nil {
   299  		return r
   300  	}
   301  	for _, protoModel := range items {
   302  		item, err := models.MarshalItemUsingModelRegistry(protoModel, r.modelRegistry)
   303  		if err != nil {
   304  			r.err = err
   305  			return r
   306  		}
   307  		item.Data = nil // delete
   308  		r.req.Updates = append(r.req.Updates, &generic.UpdateItem{
   309  			Item: item,
   310  		})
   311  	}
   312  	return r
   313  }
   314  
   315  func (r *setConfigRequest) Send(ctx context.Context) (err error) {
   316  	if r.err != nil {
   317  		return r.err
   318  	}
   319  	_, err = r.client.SetConfig(ctx, r.req)
   320  	return err
   321  }
   322  
   323  // UseRemoteRegistry modifies remote client to use remote model registry instead of local model registry. The
   324  // remote model registry is filled with remote known models for given class (modelClass).
   325  func UseRemoteRegistry(modelClass string) NewClientOption {
   326  	return func(c client.GenericClient) error {
   327  		if grpcClient, ok := c.(*grpcClient); ok {
   328  			// get all remote models
   329  			knownModels, err := grpcClient.KnownModels(modelClass)
   330  			if err != nil {
   331  				return fmt.Errorf("cannot retrieve remote models (in UseRemoteRegistry) due to: %w", err)
   332  			}
   333  
   334  			// fill them into new remote registry and use that registry instead of default local model registry
   335  			grpcClient.modelRegistry = models.NewRemoteRegistry()
   336  			for _, knowModel := range knownModels {
   337  				if _, err := grpcClient.modelRegistry.Register(knowModel, models.ToSpec(knowModel.Spec)); err != nil {
   338  					return fmt.Errorf("cannot register remote known model "+
   339  						"for remote generic client usage due to: %w", err)
   340  				}
   341  			}
   342  		}
   343  		return nil
   344  	}
   345  }
   346  
   347  // toFileDescriptors convert file descriptor protos to file descriptors. This conversion handles correctly
   348  // possible transitive dependencies, but all dependencies (direct or transitive) must be included in input
   349  // file descriptor protos.
   350  func toFileDescriptors(fileDescProtos []*descriptorpb.FileDescriptorProto) ([]protoreflect.FileDescriptor, error) {
   351  	// NOTE this could be done more efficiently by creating dependency tree and
   352  	// traversing it and all, but it seems more complicated to implement
   353  	// => going over unresolved FileDescriptorProto over and over while resolving that FileDescriptorProto that
   354  	// could be resolved, in the end(first round with nothing new to resolve) there is either everything resolved
   355  	// (everything went ok) or there exist something that is not resolved and can't be resolved with given
   356  	// input to this function (this result is considered error expecting not adding to function input
   357  	// additional useless file descriptor protos)
   358  	unresolvedFDProtos := make(map[string]*descriptorpb.FileDescriptorProto)
   359  	for _, fdp := range fileDescProtos {
   360  		unresolvedFDProtos[*fdp.Name] = fdp
   361  	}
   362  	resolved := make(map[string]protoreflect.FileDescriptor)
   363  
   364  	newResolvedInLastRound := true
   365  	for len(unresolvedFDProtos) > 0 && newResolvedInLastRound {
   366  		newResolvedInLastRound = false
   367  		for fdpName, fdp := range unresolvedFDProtos {
   368  			allDepsFound := true
   369  			reg := &protoregistry.Files{}
   370  			for _, dependencyName := range fdp.Dependency {
   371  				resolvedDep, found := resolved[dependencyName]
   372  				if !found {
   373  					allDepsFound = false
   374  					break
   375  				}
   376  				if err := reg.RegisterFile(resolvedDep); err != nil {
   377  					return nil, fmt.Errorf("cannot put resolved dependency %v "+
   378  						"into descriptor registry due to: %v", resolvedDep.Name(), err)
   379  				}
   380  			}
   381  			if allDepsFound {
   382  				fd, err := protodesc.NewFile(fdp, reg)
   383  				if err != nil {
   384  					return nil, fmt.Errorf("cannot create file descriptor "+
   385  						"(from file descriptor proto named %v) due to: %v", *fdp.Name, err)
   386  				}
   387  				resolved[fdpName] = fd
   388  				delete(unresolvedFDProtos, fdpName)
   389  				newResolvedInLastRound = true
   390  			}
   391  		}
   392  	}
   393  	if len(unresolvedFDProtos) > 0 {
   394  		return nil, fmt.Errorf("cannot resolve some FileDescriptorProtos due to missing of "+
   395  			"some protos of their imports (FileDescriptorProtos with unresolvable imports: %v)",
   396  			fileDescriptorProtoMapToString(unresolvedFDProtos))
   397  	}
   398  
   399  	result := make([]protoreflect.FileDescriptor, 0, len(resolved))
   400  	for _, fd := range resolved {
   401  		result = append(result, fd)
   402  	}
   403  	return result, nil
   404  }
   405  
   406  func fileDescriptorProtoMapToString(fdps map[string]*descriptorpb.FileDescriptorProto) string {
   407  	keys := make([]string, 0, len(fdps))
   408  	for key := range fdps {
   409  		keys = append(keys, key)
   410  	}
   411  	return strings.Join(keys, ",")
   412  }
   413  
   414  func extractProtoMessages(dsts []interface{}) []proto.Message {
   415  	protoDsts := make([]proto.Message, 0)
   416  	for _, dst := range dsts {
   417  		msg, ok := dst.(proto.Message)
   418  		if ok {
   419  			protoDsts = append(protoDsts, msg)
   420  		} else {
   421  			break
   422  		}
   423  	}
   424  	return protoDsts
   425  }
   426  
   427  func protoMapToList(protoMap map[string]proto.Message) []proto.Message {
   428  	result := make([]proto.Message, 0, len(protoMap))
   429  	for _, msg := range protoMap {
   430  		result = append(result, msg)
   431  	}
   432  	return result
   433  }