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:       &paramsStr,
   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  }