From bb8079c7cb816f2d735fc7d296529cf77ebb7ff2 Mon Sep 17 00:00:00 2001 From: Malte Bauch <malte.bauch@stud.h-da.de> Date: Fri, 16 Jun 2023 09:55:21 +0000 Subject: [PATCH] Resolve "The model stored in the plugin is increasing in size" This commit makes sure that the stored model data is only unmarshalled if the plugin is newly created. Otherwise we assume that the plugin represents the intended state (this is only temporary and will be changed in the future). Requesting configuration data directly from the device will now only pass that information through. Only changes triggered through the controller itself will be persisted. Additionally the handling of a subscribe response has been changed so the data is not unmarshalled into the plugins model data. --- controller/interfaces/plugin/plugin.go | 8 ++--- .../northbound/server/networkElement.go | 24 ++++++++----- controller/nucleus/networkElementService.go | 36 +++++++++++-------- .../nucleus/networkElementService_test.go | 1 + controller/nucleus/networkElementWatcher.go | 5 --- controller/nucleus/plugin.go | 32 +++++++++++++---- controller/plugin/shared/client.go | 35 ++++++++++++++++++ controller/plugin/shared/interface.go | 10 +++++- controller/plugin/shared/server.go | 27 ++++++++++++++ docker-compose.yml | 4 ++- plugins/examples/arista/cmd/main.go | 2 ++ plugins/examples/openconfig/cmd/main.go | 2 ++ plugins/sdk/deviceModel.go | 5 +++ scripts/simple-dev-setup.sh | 2 +- 14 files changed, 150 insertions(+), 43 deletions(-) diff --git a/controller/interfaces/plugin/plugin.go b/controller/interfaces/plugin/plugin.go index dc01a0ee8..f61bda8a8 100644 --- a/controller/interfaces/plugin/plugin.go +++ b/controller/interfaces/plugin/plugin.go @@ -27,11 +27,9 @@ const ( //CREATED state describes a plugin which has been created but is not yet //built. CREATED State = iota - // BUILT state describes a plugin which is built and can be loaded into the - // controller. - BUILT - // LOADED state describes a plugin which is running within the controller. - LOADED + // INITIALIZED state describes a plugin which is running and has been + // initialized with the model data of the associated network element. + INITIALIZED // FAULTY state describes a plugin which couldn't be built or loaded. FAULTY ) diff --git a/controller/northbound/server/networkElement.go b/controller/northbound/server/networkElement.go index 2041d576d..a7e0dda02 100644 --- a/controller/northbound/server/networkElement.go +++ b/controller/northbound/server/networkElement.go @@ -438,17 +438,17 @@ func (n *NetworkElementServer) GetPath(ctx context.Context, request *mnepb.GetPa Name: pnd.GetName(), Description: pnd.GetDescription(), }, - MneNotification: resp.(*gnmi.GetResponse).Notification, + MneNotification: resp.Notification, }, nil } -func (n *NetworkElementServer) getPath(ctx context.Context, mne networkelement.NetworkElement, path string) (proto.Message, error) { +func (n *NetworkElementServer) getPath(ctx context.Context, mne networkelement.NetworkElement, path string) (*gnmi.GetResponse, error) { res, err := mne.Transport().Get(ctx, path) if err != nil { return nil, err } - resp, ok := res.(proto.Message) + protoMessage, ok := res.(proto.Message) if !ok { return nil, &customerrs.InvalidTypeAssertionError{ Value: res, @@ -456,12 +456,15 @@ func (n *NetworkElementServer) getPath(ctx context.Context, mne networkelement.N } } - err = mne.ProcessResponse(resp) - if err != nil { - return nil, err + getResponse, ok := protoMessage.(*gnmi.GetResponse) + if !ok { + return nil, &customerrs.InvalidTypeAssertionError{ + Value: res, + Type: (*gnmi.GetResponse)(nil), + } } - return resp, nil + return getResponse, nil } // GetIntendedPath gets a path as the intended state stored in the storage. @@ -689,7 +692,12 @@ func (n *NetworkElementServer) addMne(ctx context.Context, name string, opt *tpb } if mne.IsTransportValid() { - _, err = n.getPath(ctx, mne, "/") + resp, err := n.getPath(ctx, mne, "/") + if err != nil { + return uuid.Nil, err + } + + err = mne.ProcessResponse(resp) if err != nil { return uuid.Nil, err } diff --git a/controller/nucleus/networkElementService.go b/controller/nucleus/networkElementService.go index ccd7d5473..3a8111abf 100644 --- a/controller/nucleus/networkElementService.go +++ b/controller/nucleus/networkElementService.go @@ -133,7 +133,8 @@ func (s *NetworkElementService) UpdateModel(networkElementID uuid.UUID, modelAsS }, } - // Use unmarshall from the network elements SBI to unmarshall ygot json in go struct. + // Use SetNode within the related plugin to map the path/value pair to the + // given ygot.GoStruct. err = exisitingNetworkElement.GetPlugin().SetNode(path, typedValue) if err != nil { return err @@ -235,22 +236,27 @@ func (s *NetworkElementService) createNetworkElementFromStore(loadedNetworkEleme return nil, err } - // Create 'root' path to be able to load the whole model from the store. - path, err := ygot.StringToPath("/", ygot.StructuredPath) - if err != nil { - return nil, err - } + // if the plugin is not initialized, we need to initialize it with the + // model data from the associated network element. + if mne.GetPlugin().State() != plugin.INITIALIZED { + // Create 'root' path to be able to load the whole model from the store. + path, err := ygot.StringToPath("/", ygot.StructuredPath) + if err != nil { + return nil, err + } - typedValue := &gnmi.TypedValue{ - Value: &gnmi.TypedValue_JsonIetfVal{ - JsonIetfVal: []byte(loadedNetworkElement.Model), - }, - } + typedValue := &gnmi.TypedValue{ + Value: &gnmi.TypedValue_JsonIetfVal{ + JsonIetfVal: []byte(loadedNetworkElement.Model), + }, + } - // Use unmarshall from the network elements SBI to unmarshall ygot json in go struct. - err = mne.GetPlugin().SetNode(path, typedValue) - if err != nil { - return nil, err + // Use SetNode within the related plugin to map the path/value pair to + // the given ygot.GoStruct. + err = mne.GetPlugin().SetNode(path, typedValue) + if err != nil { + return nil, err + } } return mne, nil diff --git a/controller/nucleus/networkElementService_test.go b/controller/nucleus/networkElementService_test.go index 7a02b94d8..1df0d17dc 100644 --- a/controller/nucleus/networkElementService_test.go +++ b/controller/nucleus/networkElementService_test.go @@ -32,6 +32,7 @@ func getNetworkElementTestStores(t *testing.T, mneID uuid.UUID) (networkelement. mockPlugin.On("ID").Return(mockPluginID) mockPlugin.On("Model", mock.Anything).Return([]byte("hello"), nil) mockPlugin.On("SetNode", mock.Anything, mock.Anything).Return(nil) + mockPlugin.On("State").Return(plugin.CREATED) eventService := eventservice.NewMockEventService() pluginStore := NewMemoryPluginStore() networkElementStore := NewMemoryNetworkElementStore() diff --git a/controller/nucleus/networkElementWatcher.go b/controller/nucleus/networkElementWatcher.go index 9ff8ee31e..2ead5fcdb 100644 --- a/controller/nucleus/networkElementWatcher.go +++ b/controller/nucleus/networkElementWatcher.go @@ -151,11 +151,6 @@ func (n *NetworkElementWatcher) handleSubscribeResponseUpdate(resp *gpb.Subscrib log.Error(err) } - err = mne.Transport().ProcessControlPlaneSubscribeResponse(resp) - if err != nil { - log.Error(err) - } - pathsAndValues := make(map[string]string, len(resp.Update.Update)) for _, update := range resp.Update.Update { diff --git a/controller/nucleus/plugin.go b/controller/nucleus/plugin.go index adc0312b3..032c30ed4 100644 --- a/controller/nucleus/plugin.go +++ b/controller/nucleus/plugin.go @@ -15,6 +15,7 @@ import ( "go.mongodb.org/mongo-driver/bson" ) +// Plugin is the controllers internal representation of a plugin. type Plugin struct { UUID uuid.UUID state plugin.State @@ -24,6 +25,7 @@ type Plugin struct { shared.DeviceModel } +// NewPlugin creates a new Plugin. func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) { client := hcplugin.NewClient(&hcplugin.ClientConfig{ HandshakeConfig: shared.Handshake, @@ -37,18 +39,24 @@ func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) { return nil, err } - // connect through grpc + // create a client that is within the AllowedProtocols. In this case this + // returns a gRPCClient. Allows to connect through gRPC. gRPCClient, err := client.Client() if err != nil { return nil, err } - // Request the plugin + // Request the plugin. This returns the gRPC client from the + // DeviceModelPlugin. This can then be casted to the interface that we are + // exposing through the plugin (in this case "DeviceModel"). raw, err := gRPCClient.Dispense("deviceModel") if err != nil { return nil, err } + // cast the raw plugin to the DeviceModel interface. This allows to call + // methods on the plugin as if it were a normal DeviceModel instance but + // actually they are executed on the plugin sent through gRPC. model, ok := raw.(shared.DeviceModel) if !ok { return nil, customerrs.InvalidTypeAssertionError{ @@ -67,6 +75,7 @@ func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) { }, nil } +// NewPluginThroughReattachConfig creates a new Plugin through a reattach config. func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Plugin, error) { client := hcplugin.NewClient(&hcplugin.ClientConfig{ HandshakeConfig: shared.Handshake, @@ -75,18 +84,24 @@ func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Pl AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC}, }) - // connect through grpc + // create a client that is within the AllowedProtocols. In this case this + // returns a gRPCClient. Allows to connect through gRPC. gRPCClient, err := client.Client() if err != nil { return nil, err } - // Request the plugin + // Request the plugin. This returns the gRPC client from the + // DeviceModelPlugin. This can then be casted to the interface that we are + // exposing through the plugin (in this case "DeviceModel"). raw, err := gRPCClient.Dispense("deviceModel") if err != nil { return nil, err } + // cast the raw plugin to the DeviceModel interface. This allows to call + // methods on the plugin as if it were a normal DeviceModel instance but + // actually they are executed on the plugin sent through gRPC. model, ok := raw.(shared.DeviceModel) if !ok { return nil, customerrs.InvalidTypeAssertionError{ @@ -100,7 +115,7 @@ func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Pl client: client, DeviceModel: model, manifest: &loadedPlugin.Manifest, - state: plugin.CREATED, + state: plugin.INITIALIZED, }, nil } @@ -116,13 +131,14 @@ func (p *Plugin) ReattachConfig() *hcplugin.ReattachConfig { // State returns the current state of the plugin. // Different states of the plugin can be: -// - built -// - loaded +// - created +// - initialized // - faulty func (p *Plugin) State() plugin.State { return p.state } +// ExecPath returns the path to the executable of the plugin. func (p *Plugin) ExecPath() string { return p.execPath } @@ -137,10 +153,12 @@ func (p *Plugin) Manifest() *plugin.Manifest { return p.manifest } +// Update updates the plugin to the latest available version. func (p *Plugin) Update() error { return fmt.Errorf("not implemented yet") } +// Restart restarts the plugin. func (p *Plugin) Restart() error { return fmt.Errorf("not implemented yet") } diff --git a/controller/plugin/shared/client.go b/controller/plugin/shared/client.go index 2fc692326..4bdfdb839 100644 --- a/controller/plugin/shared/client.go +++ b/controller/plugin/shared/client.go @@ -12,8 +12,16 @@ import ( "github.com/sirupsen/logrus" ) +// DeviceModelClient is the implementation of a DeviceModel that can +// communicate via gRPC. The main purpose it to act as the gRPC client for the +// DeviceModelPlugin. A hashicorp client can consume and use it. The +// DeviceModelClient has to satisfy the DeviceModel interface and therefore +// implements all the needed methods. type DeviceModelClient struct{ client pb.PluginClient } +// Unmarshal calls the Unmarshal method of the DeviceModelClients client. A +// request is created with the given json and path. If the operation failed an +// error is returned. func (m *DeviceModelClient) Unmarshal(json []byte, path *gpb.Path) error { _, err := m.client.Unmarshal(context.Background(), &pb.UnmarshalRequest{ Json: json, @@ -22,6 +30,9 @@ func (m *DeviceModelClient) Unmarshal(json []byte, path *gpb.Path) error { return err } +// SetNode calls the SetNode method of the DeviceModelClients client. A request +// is created with the given path and value. If the operation failed an error is +// returned. func (m *DeviceModelClient) SetNode(path *gpb.Path, value *gpb.TypedValue) error { _, err := m.client.SetNode(context.Background(), &pb.SetNodeRequest{ Path: path, @@ -30,6 +41,10 @@ func (m *DeviceModelClient) SetNode(path *gpb.Path, value *gpb.TypedValue) error return err } +// GetNode calls the GetNode method of the DeviceModelClients client. A request +// is created with the given path and the provided boolean that decides if only +// the intended state should be requested. The response is returned as a slice +// of notifications. If the operation failed an error is returned. func (m *DeviceModelClient) GetNode(path *gpb.Path, requestForIntendedState bool) ([]*gpb.Notification, error) { resp, err := m.client.GetNode(context.Background(), &pb.GetNodeRequest{ Path: path, @@ -38,6 +53,9 @@ func (m *DeviceModelClient) GetNode(path *gpb.Path, requestForIntendedState bool return resp.GetNodes(), err } +// DeleteNode calls the DeleteNode method of the DeviceModelClients client. A +// request is created with the given path. If the operation failed an error is +// returned. func (m *DeviceModelClient) DeleteNode(path *gpb.Path) error { _, err := m.client.DeleteNode(context.Background(), &pb.DeleteNodeRequest{ Path: path, @@ -45,6 +63,10 @@ func (m *DeviceModelClient) DeleteNode(path *gpb.Path) error { return err } +// Model calls the Model method of the DeviceModelClients client. A request is +// created with the given boolean that decides if read-only nodes should be +// filtered. The response returns the model (filtered or not) as json. If the +// operation failed an error is returned. func (m *DeviceModelClient) Model(filterReadOnly bool) ([]byte, error) { resp, err := m.client.Model(context.Background(), &pb.ModelRequest{ FilterReadOnly: filterReadOnly, @@ -52,6 +74,9 @@ func (m *DeviceModelClient) Model(filterReadOnly bool) ([]byte, error) { return resp.Json, err } +// Diff calls the Diff method of the DeviceModelClients client. A request is +// created with the given original and modified model. The response returns the +// diff as a gnmi notifications. If the operation failed an error is returned. func (m *DeviceModelClient) Diff(original, modified []byte) (*gpb.Notification, error) { resp, err := m.client.Diff(context.Background(), &pb.DiffRequest{ Original: original, @@ -60,6 +85,10 @@ func (m *DeviceModelClient) Diff(original, modified []byte) (*gpb.Notification, return resp.GetNotification(), err } +// ValidateChange calls the ValidateChange method of the DeviceModelClients +// client. A request is created with the given operation, path and value (as +// gnmi.TypedValue). The response returns the model as json. If the operation +// failed an error is returned. func (m *DeviceModelClient) ValidateChange(operation mnepb.ApiOperation, path *gpb.Path, value *gpb.TypedValue) ([]byte, error) { resp, err := m.client.ValidateChange(context.Background(), &pb.ValidateChangeRequest{ Operation: operation, @@ -69,6 +98,10 @@ func (m *DeviceModelClient) ValidateChange(operation mnepb.ApiOperation, path *g return resp.GetModel(), err } +// PruneConfigFalse calls the PruneConfigFalse method of the DeviceModelClients +// client. A request is created with the given model value (json as byte[]). +// The response returns the model with the pruned config false nodes. If the +// operation failed an error is returned. func (m *DeviceModelClient) PruneConfigFalse(value []byte) ([]byte, error) { resp, err := m.client.PruneConfigFalse(context.Background(), &pb.PruneConfigFalseRequest{ Value: value, @@ -76,6 +109,8 @@ func (m *DeviceModelClient) PruneConfigFalse(value []byte) ([]byte, error) { return resp.GetModel(), err } +// SchemaTree calls the SchemaTree method of the DeviceModelClients client. The +// response stream is packed into a slice of bytes and returned. func (m *DeviceModelClient) SchemaTreeGzip() ([]byte, error) { schemaTreeGzipClient, err := m.client.SchemaTreeGzip(context.Background(), &pb.SchemaTreeGzipRequest{}) if err != nil { diff --git a/controller/plugin/shared/interface.go b/controller/plugin/shared/interface.go index 8f438f94d..e690481b3 100644 --- a/controller/plugin/shared/interface.go +++ b/controller/plugin/shared/interface.go @@ -11,16 +11,19 @@ import ( gpb "github.com/openconfig/gnmi/proto/gnmi" ) +// Hanshake describes the handshake config for the plugin. var Handshake = plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "GOSDN_PLUGIN_MAGIC_COOKIE", MagicCookieValue: "woux6tn7gbsm53ipb3w4zxb59qd3se43hnqeh5bieynzvfchchktsd32pbjqwuxq", } +// PluginMap is the map of plugins that can be used. var PluginMap = map[string]plugin.Plugin{ "deviceModel": &DeviceModelPlugin{}, } +// DeviceModel describes the interface that will be accessible through the plugin. type DeviceModel interface { // TODO: It should be possible to pass methods like Unmarshal, SetNode, // GetNode, etc. ytypes.Unmarshal-|Set-|GetOptions @@ -35,13 +38,18 @@ type DeviceModel interface { PruneConfigFalse(value []byte) ([]byte, error) } -// DeviceModelPlugin implements a hashicorp gRPC plugin. +// DeviceModelPlugin is the implementation of a plugin.GRPCPlugin. It embeds +// the Plugin interface from hashicorp/go-plugin as well a the DeviceModel +// interface. type DeviceModelPlugin struct { plugin.Plugin Impl DeviceModel } func (p *DeviceModelPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + // Register a PluginServer defined through the proto definitions to the + // provided server - a DeviceModelServer implements the PluginServer + // interface. pb.RegisterPluginServer(s, &DeviceModelServer{Impl: p.Impl}) return nil } diff --git a/controller/plugin/shared/server.go b/controller/plugin/shared/server.go index 56d0270b3..b49c0558b 100644 --- a/controller/plugin/shared/server.go +++ b/controller/plugin/shared/server.go @@ -11,11 +11,16 @@ import ( "github.com/sirupsen/logrus" ) +// DeviceModelServer is the gRPC server a DeviceModelClient can communicate +// with. A implementation of a DeviceModel should be provided. type DeviceModelServer struct { Impl DeviceModel pb.UnimplementedPluginServer } +// Unmarshal calls the Unmarshal method of the DeviceModel implementation. It +// returns a response with a boolean indicating if the operation was +// successful and an error if the operation failed. func (m *DeviceModelServer) Unmarshal( ctx context.Context, req *pb.UnmarshalRequest) (*pb.UnmarshalResponse, error) { @@ -26,6 +31,9 @@ func (m *DeviceModelServer) Unmarshal( return &pb.UnmarshalResponse{Valid: true}, err } +// SetNode calls the SetNode method of the DeviceModel implementation. It +// returns a response with a boolean indicating if the operation was +// successful and an error if the operation failed. func (m *DeviceModelServer) SetNode( ctx context.Context, req *pb.SetNodeRequest) (*pb.SetNodeResponse, error) { @@ -33,6 +41,9 @@ func (m *DeviceModelServer) SetNode( return &pb.SetNodeResponse{Valid: true}, err } +// GetNode calls the GetNode method of the DeviceModel implementation. It +// returns a response with a slice of notifications and an error if the +// operation failed. func (m *DeviceModelServer) GetNode( ctx context.Context, req *pb.GetNodeRequest) (*pb.GetNodeResponse, error) { @@ -40,6 +51,9 @@ func (m *DeviceModelServer) GetNode( return &pb.GetNodeResponse{Nodes: nodes}, err } +// DeleteNode calls the DeleteNode method of the DeviceModel implementation. It +// returns a response with a boolean indicating if the operation was +// successful and an error if the operation failed. func (m *DeviceModelServer) DeleteNode( ctx context.Context, req *pb.DeleteNodeRequest) (*pb.DeleteNodeResponse, error) { @@ -47,6 +61,8 @@ func (m *DeviceModelServer) DeleteNode( return &pb.DeleteNodeResponse{Valid: true}, err } +// Model calls the Model method of the DeviceModel implementation. It returns a +// response with the model as json and an error if the operation failed. func (m *DeviceModelServer) Model( ctx context.Context, req *pb.ModelRequest) (*pb.ModelResponse, error) { @@ -54,6 +70,9 @@ func (m *DeviceModelServer) Model( return &pb.ModelResponse{Json: model}, err } +// Diff calls the Diff method of the DeviceModel implementation. It returns a +// response with gnmi notifications containing the found Diffs and an error if +// the operation failed. func (m *DeviceModelServer) Diff( ctx context.Context, req *pb.DiffRequest) (*pb.DiffResponse, error) { @@ -61,6 +80,9 @@ func (m *DeviceModelServer) Diff( return &pb.DiffResponse{Notification: notification}, err } +// ValidateChange calls the ValidateChange method of the DeviceModel. It +// returns a response with the validated model as json and an error if the +// operation failed. func (m *DeviceModelServer) ValidateChange( ctx context.Context, req *pb.ValidateChangeRequest) (*pb.ValidateChangeResponse, error) { @@ -68,6 +90,9 @@ func (m *DeviceModelServer) ValidateChange( return &pb.ValidateChangeResponse{Model: model}, err } +// PruneConfigFalse calls the PruneConfigFalse method of the DeviceModel. It +// returns a response with the pruned model as json and an error if the +// operation failed. func (m *DeviceModelServer) PruneConfigFalse( ctx context.Context, req *pb.PruneConfigFalseRequest) (*pb.PruneConfigFalseResponse, error) { @@ -75,6 +100,8 @@ func (m *DeviceModelServer) PruneConfigFalse( return &pb.PruneConfigFalseResponse{Model: model}, err } +// SchemaTree calls the SchemaTree method of the DeviceModel. +// The SchemaTree is a byte array that will be sent through a stream. func (m *DeviceModelServer) SchemaTreeGzip( req *pb.SchemaTreeGzipRequest, stream pb.Plugin_SchemaTreeGzipServer) error { diff --git a/docker-compose.yml b/docker-compose.yml index f71ce23af..82bb6b750 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,9 @@ services: - 127.0.0.1:15672:15672 plugin-registry: - image: plugin-registry:latest + build: + context: . + dockerfile: plugin-registry/plugin-registry.Dockerfile ports: - 127.0.0.1:55057:55057 diff --git a/plugins/examples/arista/cmd/main.go b/plugins/examples/arista/cmd/main.go index 6db446f5b..58046ad7d 100644 --- a/plugins/examples/arista/cmd/main.go +++ b/plugins/examples/arista/cmd/main.go @@ -11,11 +11,13 @@ import ( ) func main() { + // Create a new DeviceModel. deviceModel, err := sdk.NewDeviceModel(generated.Schema, generated.Unmarshal, generated.SchemaTreeGzip) if err != nil { log.Println(err) os.Exit(1) } + // Serve the DeviceModelPlugin and provide the implemented deviceModel. plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: shared.Handshake, Plugins: map[string]plugin.Plugin{ diff --git a/plugins/examples/openconfig/cmd/main.go b/plugins/examples/openconfig/cmd/main.go index fcdd8b0bc..a7c3a92fe 100644 --- a/plugins/examples/openconfig/cmd/main.go +++ b/plugins/examples/openconfig/cmd/main.go @@ -11,11 +11,13 @@ import ( ) func main() { + // Create a new DeviceModel. deviceModel, err := sdk.NewDeviceModel(generated.Schema, generated.Unmarshal, generated.SchemaTreeGzip) if err != nil { log.Println(err) os.Exit(1) } + // Serve the DeviceModelPlugin and provide the implemented deviceModel. plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: shared.Handshake, Plugins: map[string]plugin.Plugin{ diff --git a/plugins/sdk/deviceModel.go b/plugins/sdk/deviceModel.go index 3016c4a7a..8aec7a59d 100644 --- a/plugins/sdk/deviceModel.go +++ b/plugins/sdk/deviceModel.go @@ -15,6 +15,8 @@ import ( log "github.com/sirupsen/logrus" ) +// Device model satisfies the DeviceModel interface of a plugin defined in +// "code.fbi.h-da.de/danet/gosdn/controller/plugin/shared". type DeviceModel struct { mu sync.RWMutex model ygot.ValidatedGoStruct @@ -23,6 +25,7 @@ type DeviceModel struct { genereatedSchemaTreeGzipFn func() []byte } +// NewDeviceModel creates a new DeviceModel. func NewDeviceModel(generatedSchemaFn func() (*ytypes.Schema, error), generatedUnmarshalFn func([]byte, ygot.GoStruct, ...ytypes.UnmarshalOpt) error, genereatedSchemaTreeGzipFn func() []byte) (*DeviceModel, error) { schema, err := generatedSchemaFn() if err != nil { @@ -228,6 +231,7 @@ func (d *DeviceModel) Diff(original, modified []byte) (*gpb.Notification, error) return ygot.Diff(originalAsValidatedCopy, modifiedAsValidatedCopy, diffOpts...) } +// ValidateChange validates that the given value can be set at the given path. func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Path, value *gpb.TypedValue) ([]byte, error) { d.mu.RLock() modelCopy, err := createValidatedCopy(d.model) @@ -255,6 +259,7 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat return ygot.Marshal7951(modelCopy, getYgotMarshal7951Config(), ygot.JSONIndent("")) } +// PruneConfigFalse removes all config false elements from the given model. func (d *DeviceModel) PruneConfigFalse(value []byte) ([]byte, error) { validatedCopy, err := createValidatedCopy(d.schema.Root) if err != nil { diff --git a/scripts/simple-dev-setup.sh b/scripts/simple-dev-setup.sh index a77615317..8f8abeaa2 100755 --- a/scripts/simple-dev-setup.sh +++ b/scripts/simple-dev-setup.sh @@ -39,7 +39,7 @@ then echo "Need sudo rights." sudo echo "sudo rights granted" # Start databases, etc. - docker compose up -d + docker compose up --build -d sudo containerlab deploy -t $TOPOLOGY start_gosdn -- GitLab