go.ligato.io/vpp-agent/v3@v3.5.0/plugins/restapi/handlers.go (about) 1 // Copyright (c) 2018 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 //go:generate go-bindata-assetfs -pkg restapi -o bindata.go ./templates/... 16 17 package restapi 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "io" 25 "net/http" 26 "net/url" 27 "runtime" 28 "strings" 29 30 yaml2 "github.com/ghodss/yaml" 31 "github.com/goccy/go-yaml" 32 "github.com/unrolled/render" 33 "go.ligato.io/cn-infra/v2/logging" 34 "go.ligato.io/cn-infra/v2/logging/logrus" 35 "google.golang.org/protobuf/encoding/protojson" 36 "google.golang.org/protobuf/encoding/prototext" 37 "google.golang.org/protobuf/proto" 38 "google.golang.org/protobuf/reflect/protodesc" 39 "google.golang.org/protobuf/reflect/protoreflect" 40 "google.golang.org/protobuf/types/descriptorpb" 41 "google.golang.org/protobuf/types/dynamicpb" 42 "google.golang.org/protobuf/types/pluginpb" 43 44 "go.ligato.io/vpp-agent/v3/client" 45 "go.ligato.io/vpp-agent/v3/cmd/agentctl/api/types" 46 "go.ligato.io/vpp-agent/v3/pkg/models" 47 "go.ligato.io/vpp-agent/v3/pkg/version" 48 "go.ligato.io/vpp-agent/v3/plugins/configurator" 49 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 50 kvscheduler "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 51 "go.ligato.io/vpp-agent/v3/plugins/orchestrator" 52 "go.ligato.io/vpp-agent/v3/plugins/orchestrator/contextdecorator" 53 "go.ligato.io/vpp-agent/v3/plugins/restapi/jsonschema/converter" 54 "go.ligato.io/vpp-agent/v3/plugins/restapi/resturl" 55 interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 56 ) 57 58 const ( 59 // URLFieldNamingParamName is URL parameter name for JSON schema http handler's setting 60 // to output field names using proto/json/both names for fields 61 URLFieldNamingParamName = "fieldnames" 62 // OnlyProtoFieldNames is URL parameter value for JSON schema http handler to use only proto names as field names 63 OnlyProtoFieldNames = "onlyproto" 64 // OnlyJSONFieldNames is URL parameter value for JSON schema http handler to use only JSON names as field names 65 OnlyJSONFieldNames = "onlyjson" 66 67 // URLReplaceParamName is URL parameter name for modifying NB configuration PUT behaviour to act as whole 68 // configuration replacer instead of config updater (fullresync vs update). It has the same effect as replace 69 // parameter for agentctl config update. 70 // Examples how to use full resync: 71 // <VPP-Agent IP address>:9191/configuration?replace 72 // <VPP-Agent IP address>:9191/configuration?replace=true 73 URLReplaceParamName = "replace" 74 75 // YamlContentType is http header content type for YAML content 76 YamlContentType = "application/yaml" 77 78 internalErrorLogPrefix = "500 Internal server error: " 79 ) 80 81 var ( 82 // ErrHandlerUnavailable represents error returned when particular 83 // handler is not available 84 ErrHandlerUnavailable = errors.New("handler is not available") 85 ) 86 87 func (p *Plugin) registerInfoHandlers() { 88 p.HTTPHandlers.RegisterHTTPHandler(resturl.Version, p.versionHandler, GET) 89 p.HTTPHandlers.RegisterHTTPHandler(resturl.JSONSchema, p.jsonSchemaHandler, GET) 90 } 91 92 func (p *Plugin) registerNBConfigurationHandlers() { 93 p.HTTPHandlers.RegisterHTTPHandler(resturl.Validate, p.validationHandler, POST) 94 p.HTTPHandlers.RegisterHTTPHandler(resturl.Configuration, p.configurationGetHandler, GET) 95 p.HTTPHandlers.RegisterHTTPHandler(resturl.Configuration, p.configurationUpdateHandler, PUT) 96 } 97 98 // Registers ABF REST handler 99 func (p *Plugin) registerABFHandler() { 100 p.registerHTTPHandler(resturl.ABF, GET, func() (interface{}, error) { 101 if p.abfHandler == nil { 102 return nil, ErrHandlerUnavailable 103 } 104 return p.abfHandler.DumpABFPolicy() 105 }) 106 } 107 108 // Registers access list REST handlers 109 func (p *Plugin) registerACLHandlers() { 110 // GET IP ACLs 111 p.registerHTTPHandler(resturl.ACLIP, GET, func() (interface{}, error) { 112 if p.aclHandler == nil { 113 return nil, ErrHandlerUnavailable 114 } 115 return p.aclHandler.DumpACL() 116 }) 117 // GET MACIP ACLs 118 p.registerHTTPHandler(resturl.ACLMACIP, GET, func() (interface{}, error) { 119 if p.aclHandler == nil { 120 return nil, ErrHandlerUnavailable 121 } 122 return p.aclHandler.DumpMACIPACL() 123 }) 124 } 125 126 // Registers interface REST handlers 127 func (p *Plugin) registerInterfaceHandlers() { 128 // GET all interfaces 129 p.registerHTTPHandler(resturl.Interface, GET, func() (interface{}, error) { 130 return p.ifHandler.DumpInterfaces(context.TODO()) 131 }) 132 // GET loopback interfaces 133 p.registerHTTPHandler(resturl.Loopback, GET, func() (interface{}, error) { 134 return p.ifHandler.DumpInterfacesByType(context.TODO(), interfaces.Interface_SOFTWARE_LOOPBACK) 135 }) 136 // GET ethernet interfaces 137 p.registerHTTPHandler(resturl.Ethernet, GET, func() (interface{}, error) { 138 return p.ifHandler.DumpInterfacesByType(context.TODO(), interfaces.Interface_DPDK) 139 }) 140 // GET memif interfaces 141 p.registerHTTPHandler(resturl.Memif, GET, func() (interface{}, error) { 142 return p.ifHandler.DumpInterfacesByType(context.TODO(), interfaces.Interface_MEMIF) 143 }) 144 // GET tap interfaces 145 p.registerHTTPHandler(resturl.Tap, GET, func() (interface{}, error) { 146 return p.ifHandler.DumpInterfacesByType(context.TODO(), interfaces.Interface_TAP) 147 }) 148 // GET af-packet interfaces 149 p.registerHTTPHandler(resturl.AfPacket, GET, func() (interface{}, error) { 150 return p.ifHandler.DumpInterfacesByType(context.TODO(), interfaces.Interface_AF_PACKET) 151 }) 152 // GET VxLAN interfaces 153 p.registerHTTPHandler(resturl.VxLan, GET, func() (interface{}, error) { 154 return p.ifHandler.DumpInterfacesByType(context.TODO(), interfaces.Interface_VXLAN_TUNNEL) 155 }) 156 } 157 158 // Registers NAT REST handlers 159 func (p *Plugin) registerNATHandlers() { 160 // GET NAT global config 161 p.registerHTTPHandler(resturl.NatGlobal, GET, func() (interface{}, error) { 162 if p.natHandler == nil { 163 return nil, ErrHandlerUnavailable 164 } 165 return p.natHandler.Nat44GlobalConfigDump(false) 166 }) 167 // GET DNAT config 168 p.registerHTTPHandler(resturl.NatDNat, GET, func() (interface{}, error) { 169 if p.natHandler == nil { 170 return nil, ErrHandlerUnavailable 171 } 172 return p.natHandler.DNat44Dump() 173 }) 174 // GET NAT interfaces 175 p.registerHTTPHandler(resturl.NatInterfaces, GET, func() (interface{}, error) { 176 if p.natHandler == nil { 177 return nil, ErrHandlerUnavailable 178 } 179 return p.natHandler.Nat44InterfacesDump() 180 }) 181 // GET NAT address pools 182 p.registerHTTPHandler(resturl.NatAddressPools, GET, func() (interface{}, error) { 183 if p.natHandler == nil { 184 return nil, ErrHandlerUnavailable 185 } 186 return p.natHandler.Nat44AddressPoolsDump() 187 }) 188 } 189 190 // Registers L2 plugin REST handlers 191 func (p *Plugin) registerL2Handlers() { 192 // GET bridge domains 193 p.registerHTTPHandler(resturl.Bd, GET, func() (interface{}, error) { 194 if p.l2Handler == nil { 195 return nil, ErrHandlerUnavailable 196 } 197 return p.l2Handler.DumpBridgeDomains() 198 }) 199 // GET FIB entries 200 p.registerHTTPHandler(resturl.Fib, GET, func() (interface{}, error) { 201 if p.l2Handler == nil { 202 return nil, ErrHandlerUnavailable 203 } 204 return p.l2Handler.DumpL2FIBs() 205 }) 206 // GET cross connects 207 p.registerHTTPHandler(resturl.Xc, GET, func() (interface{}, error) { 208 if p.l2Handler == nil { 209 return nil, ErrHandlerUnavailable 210 } 211 return p.l2Handler.DumpXConnectPairs() 212 }) 213 } 214 215 // Registers L3 plugin REST handlers 216 func (p *Plugin) registerL3Handlers() { 217 // GET ARP entries 218 p.registerHTTPHandler(resturl.Arps, GET, func() (interface{}, error) { 219 if p.l3Handler == nil { 220 return nil, ErrHandlerUnavailable 221 } 222 return p.l3Handler.DumpArpEntries() 223 }) 224 // GET proxy ARP interfaces 225 p.registerHTTPHandler(resturl.PArpIfs, GET, func() (interface{}, error) { 226 if p.l3Handler == nil { 227 return nil, ErrHandlerUnavailable 228 } 229 return p.l3Handler.DumpProxyArpInterfaces() 230 }) 231 // GET proxy ARP ranges 232 p.registerHTTPHandler(resturl.PArpRngs, GET, func() (interface{}, error) { 233 if p.l3Handler == nil { 234 return nil, ErrHandlerUnavailable 235 } 236 return p.l3Handler.DumpProxyArpRanges() 237 }) 238 // GET static routes 239 p.registerHTTPHandler(resturl.Routes, GET, func() (interface{}, error) { 240 if p.l3Handler == nil { 241 return nil, ErrHandlerUnavailable 242 } 243 return p.l3Handler.DumpRoutes() 244 }) 245 // GET scan ip neighbor setup 246 p.registerHTTPHandler(resturl.IPScanNeigh, GET, func() (interface{}, error) { 247 if p.l3Handler == nil { 248 return nil, ErrHandlerUnavailable 249 } 250 return p.l3Handler.GetIPScanNeighbor() 251 }) 252 // GET vrrp entries 253 p.registerHTTPHandler(resturl.Vrrps, GET, func() (interface{}, error) { 254 if p.l3Handler == nil { 255 return nil, ErrHandlerUnavailable 256 } 257 return p.l3Handler.DumpVrrpEntries() 258 }) 259 } 260 261 // Registers IPSec plugin REST handlers 262 func (p *Plugin) registerIPSecHandlers() { 263 // GET IPSec SPD entries 264 p.registerHTTPHandler(resturl.SPDs, GET, func() (interface{}, error) { 265 if p.ipSecHandler == nil { 266 return nil, ErrHandlerUnavailable 267 } 268 return p.ipSecHandler.DumpIPSecSPD() 269 }) 270 // GET IPSec SP entries 271 p.registerHTTPHandler(resturl.SPs, GET, func() (interface{}, error) { 272 if p.ipSecHandler == nil { 273 return nil, ErrHandlerUnavailable 274 } 275 return p.ipSecHandler.DumpIPSecSP() 276 }) 277 // GET IPSec SA entries 278 p.registerHTTPHandler(resturl.SAs, GET, func() (interface{}, error) { 279 if p.ipSecHandler == nil { 280 return nil, ErrHandlerUnavailable 281 } 282 return p.ipSecHandler.DumpIPSecSA() 283 }) 284 } 285 286 // Registers punt plugin REST handlers 287 func (p *Plugin) registerPuntHandlers() { 288 // GET punt registered socket entries 289 p.registerHTTPHandler(resturl.PuntSocket, GET, func() (interface{}, error) { 290 if p.puntHandler == nil { 291 return nil, ErrHandlerUnavailable 292 } 293 return p.puntHandler.DumpRegisteredPuntSockets() 294 }) 295 } 296 297 // Registers linux interface plugin REST handlers 298 func (p *Plugin) registerLinuxInterfaceHandlers() { 299 // GET linux interfaces 300 p.registerHTTPHandler(resturl.LinuxInterface, GET, func() (interface{}, error) { 301 return p.linuxIfHandler.DumpInterfaces() 302 }) 303 // GET linux interface stats 304 p.registerHTTPHandler(resturl.LinuxInterfaceStats, GET, func() (interface{}, error) { 305 return p.linuxIfHandler.DumpInterfaceStats() 306 }) 307 } 308 309 // Registers linux L3 plugin REST handlers 310 func (p *Plugin) registerLinuxL3Handlers() { 311 // GET linux routes 312 p.registerHTTPHandler(resturl.LinuxRoutes, GET, func() (interface{}, error) { 313 return p.linuxL3Handler.DumpRoutes() 314 }) 315 // GET linux ARPs 316 p.registerHTTPHandler(resturl.LinuxArps, GET, func() (interface{}, error) { 317 return p.linuxL3Handler.DumpARPEntries() 318 }) 319 } 320 321 // Registers Telemetry handler 322 func (p *Plugin) registerTelemetryHandlers() { 323 p.HTTPHandlers.RegisterHTTPHandler(resturl.Telemetry, p.telemetryHandler, GET) 324 p.HTTPHandlers.RegisterHTTPHandler(resturl.TMemory, p.telemetryMemoryHandler, GET) 325 p.HTTPHandlers.RegisterHTTPHandler(resturl.TRuntime, p.telemetryRuntimeHandler, GET) 326 p.HTTPHandlers.RegisterHTTPHandler(resturl.TNodeCount, p.telemetryNodeCountHandler, GET) 327 } 328 329 func (p *Plugin) registerStatsHandler() { 330 p.HTTPHandlers.RegisterHTTPHandler(resturl.ConfiguratorStats, p.configuratorStatsHandler, GET) 331 } 332 333 // Registers index page 334 func (p *Plugin) registerIndexHandlers() { 335 r := render.New(render.Options{ 336 Directory: "templates", 337 Asset: Asset, 338 AssetNames: AssetNames, 339 }) 340 handlerFunc := func(formatter *render.Render) http.HandlerFunc { 341 return func(w http.ResponseWriter, req *http.Request) { 342 343 p.Log.Debugf("%v - %s %q", req.RemoteAddr, req.Method, req.URL) 344 p.logError(r.HTML(w, http.StatusOK, "index", p.index)) 345 } 346 } 347 p.HTTPHandlers.RegisterHTTPHandler("/", handlerFunc, GET) 348 } 349 350 // registerHTTPHandler is common register method for all handlers 351 func (p *Plugin) registerHTTPHandler(key, method string, f func() (interface{}, error)) { 352 handlerFunc := func(formatter *render.Render) http.HandlerFunc { 353 return func(w http.ResponseWriter, req *http.Request) { 354 p.govppmux.Lock() 355 defer p.govppmux.Unlock() 356 357 res, err := f() 358 if err != nil { 359 errMsg := fmt.Sprintf("500 Internal server error: request failed: %v\n", err) 360 p.Log.Error(errMsg) 361 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 362 return 363 } 364 p.Deps.Log.Debugf("Rest uri: %s, data: %v", key, res) 365 p.logError(formatter.JSON(w, http.StatusOK, res)) 366 } 367 } 368 p.HTTPHandlers.RegisterHTTPHandler(key, handlerFunc, method) 369 } 370 371 // jsonSchemaHandler returns JSON schema of VPP-Agent configuration. 372 // This handler also accepts URL query parameters changing the exported field names of proto messages. By default, 373 // proto message fields are exported twice in JSON scheme. Once with proto name and once with JSON name. This should 374 // allow to use any of the 2 forms in JSON/YAML configuration when used JSON schema for validation. However, 375 // this behaviour can be modified by URLFieldNamingParamName URL query parameter, that force to export only 376 // proto named fields (OnlyProtoFieldNames URL query parameter value) or JSON named fields (OnlyJSONFieldNames 377 // URL query parameter value). 378 func (p *Plugin) jsonSchemaHandler(formatter *render.Render) http.HandlerFunc { 379 return func(w http.ResponseWriter, req *http.Request) { 380 res, err := buildJsonSchema(req.URL.Query()) 381 if err != nil { 382 if res != nil { 383 errMsg := fmt.Sprintf("failed generate JSON schema: %v (%v)\n", res.Error, err) 384 p.Log.Error(internalErrorLogPrefix + errMsg) 385 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 386 return 387 } 388 p.internalError("", err, w, formatter) 389 return 390 } 391 392 // extract json schema 393 // (protoc_plugin.CodeGeneratorResponse could have cut the file content into multiple pieces 394 // for performance optimization (due to godoc), but we know that all pieces are only one file 395 // due to requesting one file -> join all content together) 396 var sb strings.Builder 397 for _, file := range res.File { 398 sb.WriteString(file.GetContent()) 399 } 400 401 // writing response 402 // (jsonschema is in raw form (string) and non of the available format renders supports raw data output 403 // with customizable content type setting in header -> custom handling) 404 w.Header().Set(render.ContentType, render.ContentJSON+"; charset=UTF-8") 405 _, err = w.Write([]byte(sb.String())) // will also call WriteHeader(http.StatusOK) automatically 406 if err != nil { 407 p.internalError("failed to write to HTTP response", err, w, formatter) 408 return 409 } 410 } 411 } 412 413 func buildJsonSchema(query url.Values) (*pluginpb.CodeGeneratorResponse, error) { 414 logging.Debugf("=======================================================") 415 logging.Debugf(" BUILDING JSON SCHEMA ") 416 logging.Debugf("=======================================================") 417 418 // create FileDescriptorProto for dynamic Config holding all VPP-Agent configuration 419 knownModels, err := client.LocalClient.KnownModels("config") // locally registered models 420 if err != nil { 421 return nil, fmt.Errorf("can't get registered models: %w", err) 422 } 423 config, err := client.NewDynamicConfig(knownModels) 424 if err != nil { 425 return nil, fmt.Errorf("can't create dynamic config: %w", err) 426 } 427 dynConfigFileDescProto := protodesc.ToFileDescriptorProto(config.ProtoReflect().Descriptor().ParentFile()) 428 429 // create list of all FileDescriptorProtos (imports should be before converted proto file -> dynConfig is last) 430 fileDescriptorProtos := allFileDescriptorProtos(knownModels) 431 fileDescriptorProtos = append(fileDescriptorProtos, dynConfigFileDescProto) 432 433 // creating input for protoc's plugin (code extracted in plugins/restapi/jsonschema) that can convert 434 // FileDescriptorProtos to JSONSchema 435 params := []string{ 436 "messages=[Dynamic_config]", // targeting only the main config message (proto file has also other messages) 437 "disallow_additional_properties", // additional unknown json fields makes configuration applying fail 438 } 439 fieldNamesConverterParam := "proto_and_json_fieldnames" // create proto and json named fields by default 440 if fieldNames, found := query[URLFieldNamingParamName]; found && len(fieldNames) > 0 { 441 // converting REST API request params to 3rd party tool params 442 switch fieldNames[0] { 443 case OnlyProtoFieldNames: 444 fieldNamesConverterParam = "" 445 case OnlyJSONFieldNames: 446 fieldNamesConverterParam = "json_fieldnames" 447 } 448 } 449 if fieldNamesConverterParam != "" { 450 params = append(params, fieldNamesConverterParam) 451 } 452 paramsStr := strings.Join(params, ",") 453 cgReq := &pluginpb.CodeGeneratorRequest{ 454 ProtoFile: fileDescriptorProtos, 455 FileToGenerate: []string{dynConfigFileDescProto.GetName()}, 456 Parameter: ¶msStr, 457 CompilerVersion: nil, // compiler version is not need in this protoc plugin 458 } 459 cgReqMarshalled, err := proto.Marshal(cgReq) 460 if err != nil { 461 return nil, fmt.Errorf("can't proto marshal CodeGeneratorRequest: %w", err) 462 } 463 464 logging.Debugf("-------------------------------------------------------") 465 logging.Debugf(" CONVERTING SCHEMA ") 466 logging.Debugf("-------------------------------------------------------") 467 468 // use JSON schema converter and handle error cases 469 logging.Debug("Processing code generator request") 470 protoConverter := converter.New(logrus.DefaultLogger().Logger) 471 res, err := protoConverter.ConvertFrom(bytes.NewReader(cgReqMarshalled)) 472 if err != nil { 473 if res == nil { 474 // p.internalError("failed to read registered model configuration input", err, w, formatter) 475 return nil, fmt.Errorf("failed to read registered model configuration input: %w", err) 476 } 477 return res, err 478 } 479 480 return res, nil 481 } 482 483 // allImports retrieves all imports from given FileDescriptor including transitive imports (import 484 // duplication can occur) 485 func allImports(fileDesc protoreflect.FileDescriptor) []protoreflect.FileDescriptor { 486 result := make([]protoreflect.FileDescriptor, 0) 487 imports := fileDesc.Imports() 488 for i := 0; i < imports.Len(); i++ { 489 currentImport := imports.Get(i).FileDescriptor 490 result = append(result, currentImport) 491 result = append(result, allImports(currentImport)...) 492 } 493 return result 494 } 495 496 // allFileDescriptorProtos retrieves all FileDescriptorProtos related to given models (including 497 // all imported proto files) 498 func allFileDescriptorProtos(knownModels []*client.ModelInfo) []*descriptorpb.FileDescriptorProto { 499 // extract all FileDescriptors for given known models (including direct and transitive file imports) 500 fileDescriptors := make(map[string]protoreflect.FileDescriptor) // using map for deduplication 501 for _, knownModel := range knownModels { 502 protoFile := knownModel.MessageDescriptor.ParentFile() 503 fileDescriptors[protoFile.Path()] = protoFile 504 for _, importProtoFile := range allImports(protoFile) { 505 fileDescriptors[importProtoFile.Path()] = importProtoFile 506 } 507 } 508 509 // convert retrieved FileDescriptors to FileDescriptorProtos 510 fileDescriptorProtos := make([]*descriptorpb.FileDescriptorProto, 0, len(knownModels)) 511 for _, fileDescriptor := range fileDescriptors { 512 fileDescriptorProtos = append(fileDescriptorProtos, protodesc.ToFileDescriptorProto(fileDescriptor)) 513 } 514 return fileDescriptorProtos 515 } 516 517 // versionHandler returns version of Agent. 518 func (p *Plugin) versionHandler(formatter *render.Render) http.HandlerFunc { 519 return func(w http.ResponseWriter, req *http.Request) { 520 ver := types.Version{ 521 App: version.App(), 522 Version: version.Version(), 523 GitCommit: version.GitCommit(), 524 GitBranch: version.GitBranch(), 525 BuildUser: version.BuildUser(), 526 BuildHost: version.BuildHost(), 527 BuildTime: version.BuildTime(), 528 GoVersion: runtime.Version(), 529 OS: runtime.GOOS, 530 Arch: runtime.GOARCH, 531 } 532 p.logError(formatter.JSON(w, http.StatusOK, ver)) 533 } 534 } 535 536 // validationHandler validates yaml configuration for VPP-Agent. This is the same configuration as used 537 // in agentctl configuration get/update. 538 func (p *Plugin) validationHandler(formatter *render.Render) http.HandlerFunc { 539 return func(w http.ResponseWriter, req *http.Request) { 540 // reading input data (yaml-formatted dynamic config containing all VPP-Agent configuration) 541 yamlBytes, err := io.ReadAll(req.Body) 542 if err != nil { 543 p.internalError("can't read request body", err, w, formatter) 544 return 545 } 546 547 // get empty dynamic Config able to hold all VPP-Agent configuration 548 knownModels, err := client.LocalClient.KnownModels("config") // locally registered models 549 if err != nil { 550 p.internalError("can't get registered models", err, w, formatter) 551 return 552 } 553 config, err := client.NewDynamicConfig(knownModels) 554 if err != nil { 555 p.internalError("can't create dynamic config", err, w, formatter) 556 return 557 } 558 559 // filling dynamically created config with data from request body 560 // (=syntax check of data + prepare for further processing) 561 bj, err := yaml2.YAMLToJSON(yamlBytes) 562 if err != nil { 563 p.internalError("can't convert yaml configuration "+ 564 "from request body to JSON", err, w, formatter) 565 return 566 } 567 err = protojson.Unmarshal(bj, config) 568 if err != nil { 569 p.internalError("can't unmarshall string input data "+ 570 "into dynamically created config", err, w, formatter) 571 return 572 } 573 574 // extracting proto messages from dynamically created config structure 575 configMessages, err := client.DynamicConfigExport(config) 576 if err != nil { 577 p.internalError("can't extract single proto message "+ 578 "from one dynamic config to validate them per proto message", err, w, formatter) 579 return 580 } 581 582 // run Descriptor validators on config messages 583 err = p.KVScheduler.ValidateSemantically(configMessages) 584 if err != nil { 585 if validationErrors, ok := err.(*kvscheduler.InvalidMessagesError); ok { 586 convertedValidationErrors := p.ConvertValidationErrorOutput(validationErrors, knownModels, config) 587 p.logError(formatter.JSON(w, http.StatusBadRequest, convertedValidationErrors)) 588 return 589 } 590 p.internalError("can't validate data", err, w, formatter) 591 return 592 } 593 p.logError(formatter.JSON(w, http.StatusOK, struct{}{})) 594 } 595 } 596 597 // ConvertValidationErrorOutput converts kvscheduler.ValidateSemantically(...) output to REST API output 598 func (p *Plugin) ConvertValidationErrorOutput(validationErrors *kvscheduler.InvalidMessagesError, knownModels []*models.ModelInfo, config *dynamicpb.Message) []interface{} { 599 // create helper mapping 600 nameToModel := make(map[protoreflect.FullName]*models.ModelInfo) 601 for _, knownModel := range knownModels { 602 nameToModel[knownModel.MessageDescriptor.FullName()] = knownModel 603 } 604 605 // define types for REST API output (could use map, but struct hold field ordering within each validation error) 606 type singleConfig struct { 607 Path string `json:"path"` 608 Error string `json:"error"` 609 } 610 type repeatedConfig struct { 611 Path string `json:"path"` 612 Error string `json:"error"` 613 ErrorConfigPart string `json:"error_config_part"` 614 } 615 type singleConfigDerivedValue struct { 616 Path string `json:"path"` 617 Error string `json:"error"` 618 ErrorDerivedConfigPart string `json:"error_derived_config_part"` 619 } 620 type repeatedConfigDerivedValue struct { 621 Path string `json:"path"` 622 Error string `json:"error"` 623 ErrorDerivedConfigPart string `json:"error_derived_config_part"` 624 ErrorConfigPart string `json:"error_config_part"` 625 } 626 627 // convert each validation error to REST API output (data filled structs defined above) 628 convertedValidationErrors := make([]interface{}, 0, len(validationErrors.MessageErrors())) 629 for _, messageError := range validationErrors.MessageErrors() { 630 // get yaml names of messages/fields on path to configuration with error 631 nonDerivedMessage := messageError.Message() 632 if messageError.ParentMessage() != nil { 633 nonDerivedMessage = messageError.ParentMessage() 634 } 635 messageModel := nameToModel[nonDerivedMessage.ProtoReflect().Descriptor().FullName()] 636 groupFieldName := client.DynamicConfigGroupFieldNaming(messageModel) 637 modelFieldProtoName, modelFieldName := client.DynamicConfigKnownModelFieldNaming(messageModel) 638 invalidMessageFields := messageError.InvalidFields() 639 invalidMessageFieldsStr := invalidMessageFields[0] 640 if invalidMessageFieldsStr == "" { 641 invalidMessageFieldsStr = "<unknown field>" 642 } 643 if len(invalidMessageFields) > 1 { 644 invalidMessageFieldsStr = fmt.Sprintf("[%s]", strings.Join(invalidMessageFields, ",")) 645 } 646 647 // attempt to guess yaml field by name from KVDescriptor.Validate (there is no enforcing of correct field name) 648 if len(invalidMessageFields) == 1 { // guessing only for single field references 649 // disassemble field reference (can refer to inner message field), guess the yaml name for each 650 // segment and assemble the path again 651 fieldPath := strings.Split(invalidMessageFieldsStr, ".") 652 messageDesc := messageError.Message().ProtoReflect().Descriptor() 653 for i := range fieldPath { 654 // find current field path segment in proto message fields 655 fieldDesc := messageDesc.Fields().ByName(protoreflect.Name(fieldPath[i])) 656 if fieldDesc == nil { 657 fieldDesc = messageDesc.Fields().ByJSONName(fieldPath[i]) 658 } 659 if fieldDesc == nil { 660 break // name guessing failed -> can't continue and replace other field path segments 661 } 662 663 // replacing messageError name with name used in yaml 664 fieldPath[i] = fieldDesc.JSONName() 665 666 // updating message descriptor as we move through field path 667 messageDesc = fieldDesc.Message() 668 } 669 invalidMessageFieldsStr = strings.Join(fieldPath, ".") 670 } 671 672 // compute cardinality of field (in configGroup) referring to configuration with error 673 cardinality := protoreflect.Optional 674 if configGroupField := config.ProtoReflect().Descriptor().Fields(). 675 ByName(protoreflect.Name(groupFieldName)); configGroupField != nil { 676 modelField := configGroupField.Message().Fields().ByName(protoreflect.Name(modelFieldProtoName)) 677 if modelField != nil { 678 cardinality = modelField.Cardinality() 679 } 680 } 681 682 // compute string representation of derived value configuration (yaml is preferred even when there is 683 // no direct yaml configuration for derived value) 684 var parentConfigPart string 685 if messageError.ParentMessage() != nil { 686 parentConfigPart = prototext.Format(messageError.ParentMessage()) 687 json, err := protojson.Marshal(messageError.ParentMessage()) 688 if err == nil { 689 parentConfigPart = string(json) 690 b, err := yaml2.JSONToYAML(json) 691 if err == nil { 692 parentConfigPart = string(b) 693 } 694 } 695 } 696 697 // compute again the string representation of error configuration (yaml is preferred) 698 // (no original reference to REST API string is remembered -> computing it from proto message) 699 configPart := prototext.Format(messageError.Message()) 700 json, err := protojson.Marshal(messageError.Message()) 701 if err == nil { 702 configPart = string(json) 703 b, err := yaml2.JSONToYAML(json) 704 if err == nil { 705 configPart = string(b) 706 } 707 } 708 709 // fill correct struct for REST API output 710 var convertedValidationError interface{} 711 if cardinality == protoreflect.Repeated { 712 if parentConfigPart == "" { 713 convertedValidationError = repeatedConfig{ 714 Path: fmt.Sprintf("%s.%s*.%s", 715 groupFieldName, modelFieldName, invalidMessageFieldsStr), 716 Error: messageError.ValidationError().Error(), 717 ErrorConfigPart: configPart, 718 } 719 } else { // problem in derived values 720 convertedValidationError = repeatedConfigDerivedValue{ 721 Path: fmt.Sprintf("%s.%s*.[derivedConfiguration].%s", 722 groupFieldName, modelFieldName, invalidMessageFieldsStr), 723 Error: messageError.ValidationError().Error(), 724 ErrorConfigPart: parentConfigPart, 725 ErrorDerivedConfigPart: configPart, 726 } 727 } 728 } else { 729 if parentConfigPart == "" { 730 convertedValidationError = singleConfig{ 731 Path: fmt.Sprintf("%s.%s.%s", groupFieldName, modelFieldName, invalidMessageFieldsStr), 732 Error: messageError.ValidationError().Error(), 733 } 734 } else { // problem in derived values 735 convertedValidationError = singleConfigDerivedValue{ 736 Path: fmt.Sprintf("%s.%s.[derivedConfiguration].%s", 737 groupFieldName, modelFieldName, invalidMessageFieldsStr), 738 Error: messageError.ValidationError().Error(), 739 ErrorDerivedConfigPart: configPart, 740 } 741 } 742 } 743 744 convertedValidationErrors = append(convertedValidationErrors, convertedValidationError) 745 } 746 return convertedValidationErrors 747 } 748 749 // configurationGetHandler returns NB configuration of VPP-Agent in yaml format as used by agentctl. 750 func (p *Plugin) configurationGetHandler(formatter *render.Render) http.HandlerFunc { 751 return func(w http.ResponseWriter, req *http.Request) { 752 // create dynamically config that can hold all locally known models (to use only configurator.Config is 753 // not enough as VPP-Agent could be used as library and additional model could be registered and 754 // these models are unknown for configurator.Config) 755 knownModels, err := client.LocalClient.KnownModels("config") 756 if err != nil { 757 p.internalError("failed to get registered models", err, w, formatter) 758 return 759 } 760 config, err := client.NewDynamicConfig(knownModels) 761 if err != nil { 762 p.internalError("failed to create empty "+ 763 "all-config proto message dynamically", err, w, formatter) 764 return 765 } 766 767 // retrieve data into config 768 if err := client.LocalClient.GetConfig(config); err != nil { 769 p.internalError("failed to retrieve all configuration "+ 770 "into dynamic all-config proto message", err, w, formatter) 771 return 772 } 773 774 // convert data-filled config into yaml 775 jsonBytes, err := protojson.Marshal(config) 776 if err != nil { 777 p.internalError("failed to convert retrieved configuration "+ 778 "to intermediate json output", err, w, formatter) 779 return 780 } 781 var yamlObj interface{} 782 if err := yaml.UnmarshalWithOptions(jsonBytes, &yamlObj, yaml.UseOrderedMap()); err != nil { 783 p.internalError("failed to unmarshall intermediate json formatted "+ 784 "retrieved configuration to yaml object", err, w, formatter) 785 return 786 } 787 yamlBytes, err := yaml.Marshal(yamlObj) 788 if err != nil { 789 p.internalError("failed to marshal retrieved configuration to yaml output", err, w, formatter) 790 return 791 } 792 793 // writing response (no YAML support in formatters -> custom handling) 794 w.Header().Set(render.ContentType, YamlContentType+"; charset=UTF-8") 795 _, err = w.Write(yamlBytes) // will also call WriteHeader(http.StatusOK) automatically 796 if err != nil { 797 p.internalError("failed to write to HTTP response", err, w, formatter) 798 return 799 } 800 } 801 } 802 803 func (p *Plugin) internalError(additionalErrorMsgPrefix string, err error, w http.ResponseWriter, 804 formatter *render.Render) { 805 errMsg := fmt.Sprintf("%s: %v\n", additionalErrorMsgPrefix, err) 806 p.Log.Error(internalErrorLogPrefix + errMsg) 807 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 808 } 809 810 // configurationUpdateHandler creates/updates NB configuration of VPP-Agent. The input configuration should be 811 // in yaml format as used by agentctl. 812 func (p *Plugin) configurationUpdateHandler(formatter *render.Render) http.HandlerFunc { 813 return func(w http.ResponseWriter, req *http.Request) { 814 // create dynamically config that can hold input yaml configuration 815 knownModels, err := client.LocalClient.KnownModels("config") 816 if err != nil { 817 p.internalError("failed to get registered models", err, w, formatter) 818 return 819 } 820 config, err := client.NewDynamicConfig(knownModels) 821 if err != nil { 822 p.internalError("can't create all-config proto message dynamically", err, w, formatter) 823 return 824 } 825 826 // reading input data (yaml-formatted dynamic config containing all VPP-Agent configuration) 827 yamlBytes, err := io.ReadAll(req.Body) 828 if err != nil { 829 p.internalError("can't read request body", err, w, formatter) 830 return 831 } 832 833 // filling dynamically created config with data 834 bj, err := yaml2.YAMLToJSON(yamlBytes) 835 if err != nil { 836 p.internalError("converting yaml input to json failed", err, w, formatter) 837 return 838 } 839 err = protojson.Unmarshal(bj, config) 840 if err != nil { 841 p.internalError("can't unmarshall input yaml data "+ 842 "into dynamically created config", err, w, formatter) 843 return 844 } 845 846 // extracting proto messages from dynamically created config structure 847 // (further processing needs single proto messages and not one big hierarchical config) 848 configMessages, err := client.DynamicConfigExport(config) 849 if err != nil { 850 p.internalError("can't extract single configuration proto messages "+ 851 "from one big configuration proto message", err, w, formatter) 852 return 853 } 854 855 // convert config messages to input for p.Dispatcher.PushData(...) 856 var configKVPairs []orchestrator.KeyVal 857 for _, configMessage := range configMessages { 858 // convert config message from dynamic to statically-generated proto message (if possible) 859 // (this is needed for later processing of message - generated KVDescriptor adapters cast 860 // to statically-generated proto message and fail with dynamicpb.Message proto messages) 861 dynamicMessage, ok := configMessage.(*dynamicpb.Message) 862 if !ok { // should not happen, but checking anyway 863 errMsg := fmt.Sprintf("proto message is expected to be "+ 864 "dynamicpb.Message (message=%s)\n", configMessage) 865 p.Log.Error(internalErrorLogPrefix + errMsg) 866 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 867 return 868 } 869 model, err := models.GetModelFor(dynamicMessage) 870 if err != nil { 871 errMsg := fmt.Sprintf("can't get model for dynamic message "+ 872 "due to: %v (message=%v)", err, dynamicMessage) 873 p.Log.Error(internalErrorLogPrefix + errMsg) 874 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 875 return 876 } 877 var message proto.Message 878 if _, isRemoteModel := model.(*models.RemotelyKnownModel); isRemoteModel { 879 // message is retrieved from localclient but it has remotely known model => it is the proxy 880 // models in local model registry => can't convert it to generated message due to unknown 881 // generated message go type (to use reflection to create it), however the processing of proxy 882 // models is different so it might no need type casting fix at all -> using the only thing 883 // available, the dynamic message 884 message = dynamicMessage 885 } else { // message has locally known model -> using generated proto message 886 message, err = models.DynamicLocallyKnownMessageToGeneratedMessage(dynamicMessage) 887 if err != nil { 888 errMsg := fmt.Sprintf("can't convert dynamic message to statically generated message "+ 889 "due to: %v (dynamic message=%v)", err, dynamicMessage) 890 p.Log.Error(internalErrorLogPrefix + errMsg) 891 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 892 return 893 } 894 } 895 896 // extract model key 897 key, err := models.GetKey(message) 898 if err != nil { 899 errMsg := fmt.Sprintf("can't get model key for dynamic message "+ 900 "due to: %v (dynamic message=%v)", err, dynamicMessage) 901 p.Log.Error(internalErrorLogPrefix + errMsg) 902 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 903 } 904 905 // build key-value pair structure 906 configKVPairs = append(configKVPairs, orchestrator.KeyVal{ 907 Key: key, 908 Val: message, 909 }) 910 } 911 912 // create context for data push 913 ctx := context.Background() 914 // // FullResync 915 if _, found := req.URL.Query()[URLReplaceParamName]; found { 916 ctx = kvs.WithResync(ctx, kvs.FullResync, true) 917 } 918 // // Note: using "grpc" data source so that 'agentctl update --replace' can also work with this data 919 // // ('agentctl update' can change data also from non-grpc data sources, but 920 // // 'agentctl update --replace' (=resync) can't) 921 ctx = contextdecorator.DataSrcContext(ctx, "grpc") 922 923 // config data pushed into VPP-Agent 924 _, err = p.Dispatcher.PushData(ctx, configKVPairs, nil) 925 if err != nil { 926 p.internalError("can't push data into vpp-agent", err, w, formatter) 927 return 928 } 929 930 p.logError(formatter.JSON(w, http.StatusOK, struct{}{})) 931 } 932 } 933 934 // telemetryHandler - returns various telemetry data 935 func (p *Plugin) telemetryHandler(formatter *render.Render) http.HandlerFunc { 936 return func(w http.ResponseWriter, req *http.Request) { 937 type cmdOut struct { 938 Command string 939 Output interface{} 940 } 941 var cmdOuts []cmdOut 942 943 var runCmd = func(command string) { 944 out, err := p.vpeHandler.RunCli(context.TODO(), command) 945 if err != nil { 946 errMsg := fmt.Sprintf("500 Internal server error: sending command failed: %v\n", err) 947 p.Log.Error(errMsg) 948 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 949 return 950 } 951 cmdOuts = append(cmdOuts, cmdOut{ 952 Command: command, 953 Output: out, 954 }) 955 } 956 957 runCmd("show node counters") 958 runCmd("show runtime") 959 runCmd("show buffers") 960 runCmd("show memory") 961 runCmd("show ip fib") 962 runCmd("show ip6 fib") 963 964 p.logError(formatter.JSON(w, http.StatusOK, cmdOuts)) 965 } 966 } 967 968 // telemetryMemoryHandler - returns various telemetry data 969 func (p *Plugin) telemetryMemoryHandler(formatter *render.Render) http.HandlerFunc { 970 return func(w http.ResponseWriter, req *http.Request) { 971 info, err := p.teleHandler.GetMemory(context.TODO()) 972 if err != nil { 973 errMsg := fmt.Sprintf("500 Internal server error: sending command failed: %v\n", err) 974 p.Log.Error(errMsg) 975 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 976 return 977 } 978 979 p.logError(formatter.JSON(w, http.StatusOK, info)) 980 } 981 } 982 983 // telemetryHandler - returns various telemetry data 984 func (p *Plugin) telemetryRuntimeHandler(formatter *render.Render) http.HandlerFunc { 985 return func(w http.ResponseWriter, req *http.Request) { 986 runtimeInfo, err := p.teleHandler.GetRuntimeInfo(context.TODO()) 987 if err != nil { 988 errMsg := fmt.Sprintf("500 Internal server error: sending command failed: %v\n", err) 989 p.Log.Error(errMsg) 990 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 991 return 992 } 993 994 p.logError(formatter.JSON(w, http.StatusOK, runtimeInfo)) 995 } 996 } 997 998 // telemetryHandler - returns various telemetry data 999 func (p *Plugin) telemetryNodeCountHandler(formatter *render.Render) http.HandlerFunc { 1000 return func(w http.ResponseWriter, req *http.Request) { 1001 nodeCounters, err := p.teleHandler.GetNodeCounters(context.TODO()) 1002 if err != nil { 1003 errMsg := fmt.Sprintf("500 Internal server error: sending command failed: %v\n", err) 1004 p.Log.Error(errMsg) 1005 p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg)) 1006 return 1007 } 1008 1009 p.logError(formatter.JSON(w, http.StatusOK, nodeCounters)) 1010 } 1011 } 1012 1013 // configuratorStatsHandler - returns stats for Configurator 1014 func (p *Plugin) configuratorStatsHandler(formatter *render.Render) http.HandlerFunc { 1015 return func(w http.ResponseWriter, req *http.Request) { 1016 stats := configurator.GetStats() 1017 if stats == nil { 1018 p.logError(formatter.JSON(w, http.StatusOK, "Configurator stats not available")) 1019 return 1020 } 1021 1022 p.logError(formatter.JSON(w, http.StatusOK, stats)) 1023 } 1024 } 1025 1026 // logError logs non-nil errors from JSON formatter 1027 func (p *Plugin) logError(err error) { 1028 if err != nil { 1029 p.Log.Error(err) 1030 } 1031 }