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 }