Skip to content
Snippets Groups Projects
Commit bb8079c7 authored by Malte Bauch's avatar Malte Bauch
Browse files

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.
parent cc479fd6
Branches
Tags
1 merge request!479Resolve "The model stored in the plugin is increasing in size"
Pipeline #152194 failed
Showing with 150 additions and 43 deletions
...@@ -27,11 +27,9 @@ const ( ...@@ -27,11 +27,9 @@ const (
//CREATED state describes a plugin which has been created but is not yet //CREATED state describes a plugin which has been created but is not yet
//built. //built.
CREATED State = iota CREATED State = iota
// BUILT state describes a plugin which is built and can be loaded into the // INITIALIZED state describes a plugin which is running and has been
// controller. // initialized with the model data of the associated network element.
BUILT INITIALIZED
// LOADED state describes a plugin which is running within the controller.
LOADED
// FAULTY state describes a plugin which couldn't be built or loaded. // FAULTY state describes a plugin which couldn't be built or loaded.
FAULTY FAULTY
) )
......
...@@ -438,17 +438,17 @@ func (n *NetworkElementServer) GetPath(ctx context.Context, request *mnepb.GetPa ...@@ -438,17 +438,17 @@ func (n *NetworkElementServer) GetPath(ctx context.Context, request *mnepb.GetPa
Name: pnd.GetName(), Name: pnd.GetName(),
Description: pnd.GetDescription(), Description: pnd.GetDescription(),
}, },
MneNotification: resp.(*gnmi.GetResponse).Notification, MneNotification: resp.Notification,
}, nil }, 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) res, err := mne.Transport().Get(ctx, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp, ok := res.(proto.Message) protoMessage, ok := res.(proto.Message)
if !ok { if !ok {
return nil, &customerrs.InvalidTypeAssertionError{ return nil, &customerrs.InvalidTypeAssertionError{
Value: res, Value: res,
...@@ -456,12 +456,15 @@ func (n *NetworkElementServer) getPath(ctx context.Context, mne networkelement.N ...@@ -456,12 +456,15 @@ func (n *NetworkElementServer) getPath(ctx context.Context, mne networkelement.N
} }
} }
err = mne.ProcessResponse(resp) getResponse, ok := protoMessage.(*gnmi.GetResponse)
if err != nil { if !ok {
return nil, err 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. // 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 ...@@ -689,7 +692,12 @@ func (n *NetworkElementServer) addMne(ctx context.Context, name string, opt *tpb
} }
if mne.IsTransportValid() { 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 { if err != nil {
return uuid.Nil, err return uuid.Nil, err
} }
......
...@@ -133,7 +133,8 @@ func (s *NetworkElementService) UpdateModel(networkElementID uuid.UUID, modelAsS ...@@ -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) err = exisitingNetworkElement.GetPlugin().SetNode(path, typedValue)
if err != nil { if err != nil {
return err return err
...@@ -235,22 +236,27 @@ func (s *NetworkElementService) createNetworkElementFromStore(loadedNetworkEleme ...@@ -235,22 +236,27 @@ func (s *NetworkElementService) createNetworkElementFromStore(loadedNetworkEleme
return nil, err return nil, err
} }
// Create 'root' path to be able to load the whole model from the store. // if the plugin is not initialized, we need to initialize it with the
path, err := ygot.StringToPath("/", ygot.StructuredPath) // model data from the associated network element.
if err != nil { if mne.GetPlugin().State() != plugin.INITIALIZED {
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
}
typedValue := &gnmi.TypedValue{ typedValue := &gnmi.TypedValue{
Value: &gnmi.TypedValue_JsonIetfVal{ Value: &gnmi.TypedValue_JsonIetfVal{
JsonIetfVal: []byte(loadedNetworkElement.Model), JsonIetfVal: []byte(loadedNetworkElement.Model),
}, },
} }
// 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
err = mne.GetPlugin().SetNode(path, typedValue) // the given ygot.GoStruct.
if err != nil { err = mne.GetPlugin().SetNode(path, typedValue)
return nil, err if err != nil {
return nil, err
}
} }
return mne, nil return mne, nil
......
...@@ -32,6 +32,7 @@ func getNetworkElementTestStores(t *testing.T, mneID uuid.UUID) (networkelement. ...@@ -32,6 +32,7 @@ func getNetworkElementTestStores(t *testing.T, mneID uuid.UUID) (networkelement.
mockPlugin.On("ID").Return(mockPluginID) mockPlugin.On("ID").Return(mockPluginID)
mockPlugin.On("Model", mock.Anything).Return([]byte("hello"), nil) mockPlugin.On("Model", mock.Anything).Return([]byte("hello"), nil)
mockPlugin.On("SetNode", mock.Anything, mock.Anything).Return(nil) mockPlugin.On("SetNode", mock.Anything, mock.Anything).Return(nil)
mockPlugin.On("State").Return(plugin.CREATED)
eventService := eventservice.NewMockEventService() eventService := eventservice.NewMockEventService()
pluginStore := NewMemoryPluginStore() pluginStore := NewMemoryPluginStore()
networkElementStore := NewMemoryNetworkElementStore() networkElementStore := NewMemoryNetworkElementStore()
......
...@@ -151,11 +151,6 @@ func (n *NetworkElementWatcher) handleSubscribeResponseUpdate(resp *gpb.Subscrib ...@@ -151,11 +151,6 @@ func (n *NetworkElementWatcher) handleSubscribeResponseUpdate(resp *gpb.Subscrib
log.Error(err) log.Error(err)
} }
err = mne.Transport().ProcessControlPlaneSubscribeResponse(resp)
if err != nil {
log.Error(err)
}
pathsAndValues := make(map[string]string, len(resp.Update.Update)) pathsAndValues := make(map[string]string, len(resp.Update.Update))
for _, update := range resp.Update.Update { for _, update := range resp.Update.Update {
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
) )
// Plugin is the controllers internal representation of a plugin.
type Plugin struct { type Plugin struct {
UUID uuid.UUID UUID uuid.UUID
state plugin.State state plugin.State
...@@ -24,6 +25,7 @@ type Plugin struct { ...@@ -24,6 +25,7 @@ type Plugin struct {
shared.DeviceModel shared.DeviceModel
} }
// NewPlugin creates a new Plugin.
func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) { func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) {
client := hcplugin.NewClient(&hcplugin.ClientConfig{ client := hcplugin.NewClient(&hcplugin.ClientConfig{
HandshakeConfig: shared.Handshake, HandshakeConfig: shared.Handshake,
...@@ -37,18 +39,24 @@ func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) { ...@@ -37,18 +39,24 @@ func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) {
return nil, err 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() gRPCClient, err := client.Client()
if err != nil { if err != nil {
return nil, err 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") raw, err := gRPCClient.Dispense("deviceModel")
if err != nil { if err != nil {
return nil, err 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) model, ok := raw.(shared.DeviceModel)
if !ok { if !ok {
return nil, customerrs.InvalidTypeAssertionError{ return nil, customerrs.InvalidTypeAssertionError{
...@@ -67,6 +75,7 @@ func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) { ...@@ -67,6 +75,7 @@ func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) {
}, nil }, nil
} }
// NewPluginThroughReattachConfig creates a new Plugin through a reattach config.
func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Plugin, error) { func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Plugin, error) {
client := hcplugin.NewClient(&hcplugin.ClientConfig{ client := hcplugin.NewClient(&hcplugin.ClientConfig{
HandshakeConfig: shared.Handshake, HandshakeConfig: shared.Handshake,
...@@ -75,18 +84,24 @@ func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Pl ...@@ -75,18 +84,24 @@ func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Pl
AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC}, 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() gRPCClient, err := client.Client()
if err != nil { if err != nil {
return nil, err 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") raw, err := gRPCClient.Dispense("deviceModel")
if err != nil { if err != nil {
return nil, err 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) model, ok := raw.(shared.DeviceModel)
if !ok { if !ok {
return nil, customerrs.InvalidTypeAssertionError{ return nil, customerrs.InvalidTypeAssertionError{
...@@ -100,7 +115,7 @@ func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Pl ...@@ -100,7 +115,7 @@ func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Pl
client: client, client: client,
DeviceModel: model, DeviceModel: model,
manifest: &loadedPlugin.Manifest, manifest: &loadedPlugin.Manifest,
state: plugin.CREATED, state: plugin.INITIALIZED,
}, nil }, nil
} }
...@@ -116,13 +131,14 @@ func (p *Plugin) ReattachConfig() *hcplugin.ReattachConfig { ...@@ -116,13 +131,14 @@ func (p *Plugin) ReattachConfig() *hcplugin.ReattachConfig {
// State returns the current state of the plugin. // State returns the current state of the plugin.
// Different states of the plugin can be: // Different states of the plugin can be:
// - built // - created
// - loaded // - initialized
// - faulty // - faulty
func (p *Plugin) State() plugin.State { func (p *Plugin) State() plugin.State {
return p.state return p.state
} }
// ExecPath returns the path to the executable of the plugin.
func (p *Plugin) ExecPath() string { func (p *Plugin) ExecPath() string {
return p.execPath return p.execPath
} }
...@@ -137,10 +153,12 @@ func (p *Plugin) Manifest() *plugin.Manifest { ...@@ -137,10 +153,12 @@ func (p *Plugin) Manifest() *plugin.Manifest {
return p.manifest return p.manifest
} }
// Update updates the plugin to the latest available version.
func (p *Plugin) Update() error { func (p *Plugin) Update() error {
return fmt.Errorf("not implemented yet") return fmt.Errorf("not implemented yet")
} }
// Restart restarts the plugin.
func (p *Plugin) Restart() error { func (p *Plugin) Restart() error {
return fmt.Errorf("not implemented yet") return fmt.Errorf("not implemented yet")
} }
......
...@@ -12,8 +12,16 @@ import ( ...@@ -12,8 +12,16 @@ import (
"github.com/sirupsen/logrus" "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 } 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 { func (m *DeviceModelClient) Unmarshal(json []byte, path *gpb.Path) error {
_, err := m.client.Unmarshal(context.Background(), &pb.UnmarshalRequest{ _, err := m.client.Unmarshal(context.Background(), &pb.UnmarshalRequest{
Json: json, Json: json,
...@@ -22,6 +30,9 @@ func (m *DeviceModelClient) Unmarshal(json []byte, path *gpb.Path) error { ...@@ -22,6 +30,9 @@ func (m *DeviceModelClient) Unmarshal(json []byte, path *gpb.Path) error {
return err 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 { func (m *DeviceModelClient) SetNode(path *gpb.Path, value *gpb.TypedValue) error {
_, err := m.client.SetNode(context.Background(), &pb.SetNodeRequest{ _, err := m.client.SetNode(context.Background(), &pb.SetNodeRequest{
Path: path, Path: path,
...@@ -30,6 +41,10 @@ func (m *DeviceModelClient) SetNode(path *gpb.Path, value *gpb.TypedValue) error ...@@ -30,6 +41,10 @@ func (m *DeviceModelClient) SetNode(path *gpb.Path, value *gpb.TypedValue) error
return err 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) { func (m *DeviceModelClient) GetNode(path *gpb.Path, requestForIntendedState bool) ([]*gpb.Notification, error) {
resp, err := m.client.GetNode(context.Background(), &pb.GetNodeRequest{ resp, err := m.client.GetNode(context.Background(), &pb.GetNodeRequest{
Path: path, Path: path,
...@@ -38,6 +53,9 @@ func (m *DeviceModelClient) GetNode(path *gpb.Path, requestForIntendedState bool ...@@ -38,6 +53,9 @@ func (m *DeviceModelClient) GetNode(path *gpb.Path, requestForIntendedState bool
return resp.GetNodes(), err 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 { func (m *DeviceModelClient) DeleteNode(path *gpb.Path) error {
_, err := m.client.DeleteNode(context.Background(), &pb.DeleteNodeRequest{ _, err := m.client.DeleteNode(context.Background(), &pb.DeleteNodeRequest{
Path: path, Path: path,
...@@ -45,6 +63,10 @@ func (m *DeviceModelClient) DeleteNode(path *gpb.Path) error { ...@@ -45,6 +63,10 @@ func (m *DeviceModelClient) DeleteNode(path *gpb.Path) error {
return err 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) { func (m *DeviceModelClient) Model(filterReadOnly bool) ([]byte, error) {
resp, err := m.client.Model(context.Background(), &pb.ModelRequest{ resp, err := m.client.Model(context.Background(), &pb.ModelRequest{
FilterReadOnly: filterReadOnly, FilterReadOnly: filterReadOnly,
...@@ -52,6 +74,9 @@ func (m *DeviceModelClient) Model(filterReadOnly bool) ([]byte, error) { ...@@ -52,6 +74,9 @@ func (m *DeviceModelClient) Model(filterReadOnly bool) ([]byte, error) {
return resp.Json, err 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) { func (m *DeviceModelClient) Diff(original, modified []byte) (*gpb.Notification, error) {
resp, err := m.client.Diff(context.Background(), &pb.DiffRequest{ resp, err := m.client.Diff(context.Background(), &pb.DiffRequest{
Original: original, Original: original,
...@@ -60,6 +85,10 @@ func (m *DeviceModelClient) Diff(original, modified []byte) (*gpb.Notification, ...@@ -60,6 +85,10 @@ func (m *DeviceModelClient) Diff(original, modified []byte) (*gpb.Notification,
return resp.GetNotification(), err 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) { func (m *DeviceModelClient) ValidateChange(operation mnepb.ApiOperation, path *gpb.Path, value *gpb.TypedValue) ([]byte, error) {
resp, err := m.client.ValidateChange(context.Background(), &pb.ValidateChangeRequest{ resp, err := m.client.ValidateChange(context.Background(), &pb.ValidateChangeRequest{
Operation: operation, Operation: operation,
...@@ -69,6 +98,10 @@ func (m *DeviceModelClient) ValidateChange(operation mnepb.ApiOperation, path *g ...@@ -69,6 +98,10 @@ func (m *DeviceModelClient) ValidateChange(operation mnepb.ApiOperation, path *g
return resp.GetModel(), err 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) { func (m *DeviceModelClient) PruneConfigFalse(value []byte) ([]byte, error) {
resp, err := m.client.PruneConfigFalse(context.Background(), &pb.PruneConfigFalseRequest{ resp, err := m.client.PruneConfigFalse(context.Background(), &pb.PruneConfigFalseRequest{
Value: value, Value: value,
...@@ -76,6 +109,8 @@ func (m *DeviceModelClient) PruneConfigFalse(value []byte) ([]byte, error) { ...@@ -76,6 +109,8 @@ func (m *DeviceModelClient) PruneConfigFalse(value []byte) ([]byte, error) {
return resp.GetModel(), err 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) { func (m *DeviceModelClient) SchemaTreeGzip() ([]byte, error) {
schemaTreeGzipClient, err := m.client.SchemaTreeGzip(context.Background(), &pb.SchemaTreeGzipRequest{}) schemaTreeGzipClient, err := m.client.SchemaTreeGzip(context.Background(), &pb.SchemaTreeGzipRequest{})
if err != nil { if err != nil {
......
...@@ -11,16 +11,19 @@ import ( ...@@ -11,16 +11,19 @@ import (
gpb "github.com/openconfig/gnmi/proto/gnmi" gpb "github.com/openconfig/gnmi/proto/gnmi"
) )
// Hanshake describes the handshake config for the plugin.
var Handshake = plugin.HandshakeConfig{ var Handshake = plugin.HandshakeConfig{
ProtocolVersion: 1, ProtocolVersion: 1,
MagicCookieKey: "GOSDN_PLUGIN_MAGIC_COOKIE", MagicCookieKey: "GOSDN_PLUGIN_MAGIC_COOKIE",
MagicCookieValue: "woux6tn7gbsm53ipb3w4zxb59qd3se43hnqeh5bieynzvfchchktsd32pbjqwuxq", MagicCookieValue: "woux6tn7gbsm53ipb3w4zxb59qd3se43hnqeh5bieynzvfchchktsd32pbjqwuxq",
} }
// PluginMap is the map of plugins that can be used.
var PluginMap = map[string]plugin.Plugin{ var PluginMap = map[string]plugin.Plugin{
"deviceModel": &DeviceModelPlugin{}, "deviceModel": &DeviceModelPlugin{},
} }
// DeviceModel describes the interface that will be accessible through the plugin.
type DeviceModel interface { type DeviceModel interface {
// TODO: It should be possible to pass methods like Unmarshal, SetNode, // TODO: It should be possible to pass methods like Unmarshal, SetNode,
// GetNode, etc. ytypes.Unmarshal-|Set-|GetOptions // GetNode, etc. ytypes.Unmarshal-|Set-|GetOptions
...@@ -35,13 +38,18 @@ type DeviceModel interface { ...@@ -35,13 +38,18 @@ type DeviceModel interface {
PruneConfigFalse(value []byte) ([]byte, error) 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 { type DeviceModelPlugin struct {
plugin.Plugin plugin.Plugin
Impl DeviceModel Impl DeviceModel
} }
func (p *DeviceModelPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { 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}) pb.RegisterPluginServer(s, &DeviceModelServer{Impl: p.Impl})
return nil return nil
} }
......
...@@ -11,11 +11,16 @@ import ( ...@@ -11,11 +11,16 @@ import (
"github.com/sirupsen/logrus" "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 { type DeviceModelServer struct {
Impl DeviceModel Impl DeviceModel
pb.UnimplementedPluginServer 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( func (m *DeviceModelServer) Unmarshal(
ctx context.Context, ctx context.Context,
req *pb.UnmarshalRequest) (*pb.UnmarshalResponse, error) { req *pb.UnmarshalRequest) (*pb.UnmarshalResponse, error) {
...@@ -26,6 +31,9 @@ func (m *DeviceModelServer) Unmarshal( ...@@ -26,6 +31,9 @@ func (m *DeviceModelServer) Unmarshal(
return &pb.UnmarshalResponse{Valid: true}, err 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( func (m *DeviceModelServer) SetNode(
ctx context.Context, ctx context.Context,
req *pb.SetNodeRequest) (*pb.SetNodeResponse, error) { req *pb.SetNodeRequest) (*pb.SetNodeResponse, error) {
...@@ -33,6 +41,9 @@ func (m *DeviceModelServer) SetNode( ...@@ -33,6 +41,9 @@ func (m *DeviceModelServer) SetNode(
return &pb.SetNodeResponse{Valid: true}, err 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( func (m *DeviceModelServer) GetNode(
ctx context.Context, ctx context.Context,
req *pb.GetNodeRequest) (*pb.GetNodeResponse, error) { req *pb.GetNodeRequest) (*pb.GetNodeResponse, error) {
...@@ -40,6 +51,9 @@ func (m *DeviceModelServer) GetNode( ...@@ -40,6 +51,9 @@ func (m *DeviceModelServer) GetNode(
return &pb.GetNodeResponse{Nodes: nodes}, err 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( func (m *DeviceModelServer) DeleteNode(
ctx context.Context, ctx context.Context,
req *pb.DeleteNodeRequest) (*pb.DeleteNodeResponse, error) { req *pb.DeleteNodeRequest) (*pb.DeleteNodeResponse, error) {
...@@ -47,6 +61,8 @@ func (m *DeviceModelServer) DeleteNode( ...@@ -47,6 +61,8 @@ func (m *DeviceModelServer) DeleteNode(
return &pb.DeleteNodeResponse{Valid: true}, err 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( func (m *DeviceModelServer) Model(
ctx context.Context, ctx context.Context,
req *pb.ModelRequest) (*pb.ModelResponse, error) { req *pb.ModelRequest) (*pb.ModelResponse, error) {
...@@ -54,6 +70,9 @@ func (m *DeviceModelServer) Model( ...@@ -54,6 +70,9 @@ func (m *DeviceModelServer) Model(
return &pb.ModelResponse{Json: model}, err 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( func (m *DeviceModelServer) Diff(
ctx context.Context, ctx context.Context,
req *pb.DiffRequest) (*pb.DiffResponse, error) { req *pb.DiffRequest) (*pb.DiffResponse, error) {
...@@ -61,6 +80,9 @@ func (m *DeviceModelServer) Diff( ...@@ -61,6 +80,9 @@ func (m *DeviceModelServer) Diff(
return &pb.DiffResponse{Notification: notification}, err 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( func (m *DeviceModelServer) ValidateChange(
ctx context.Context, ctx context.Context,
req *pb.ValidateChangeRequest) (*pb.ValidateChangeResponse, error) { req *pb.ValidateChangeRequest) (*pb.ValidateChangeResponse, error) {
...@@ -68,6 +90,9 @@ func (m *DeviceModelServer) ValidateChange( ...@@ -68,6 +90,9 @@ func (m *DeviceModelServer) ValidateChange(
return &pb.ValidateChangeResponse{Model: model}, err 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( func (m *DeviceModelServer) PruneConfigFalse(
ctx context.Context, ctx context.Context,
req *pb.PruneConfigFalseRequest) (*pb.PruneConfigFalseResponse, error) { req *pb.PruneConfigFalseRequest) (*pb.PruneConfigFalseResponse, error) {
...@@ -75,6 +100,8 @@ func (m *DeviceModelServer) PruneConfigFalse( ...@@ -75,6 +100,8 @@ func (m *DeviceModelServer) PruneConfigFalse(
return &pb.PruneConfigFalseResponse{Model: model}, err 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( func (m *DeviceModelServer) SchemaTreeGzip(
req *pb.SchemaTreeGzipRequest, req *pb.SchemaTreeGzipRequest,
stream pb.Plugin_SchemaTreeGzipServer) error { stream pb.Plugin_SchemaTreeGzipServer) error {
......
...@@ -26,7 +26,9 @@ services: ...@@ -26,7 +26,9 @@ services:
- 127.0.0.1:15672:15672 - 127.0.0.1:15672:15672
plugin-registry: plugin-registry:
image: plugin-registry:latest build:
context: .
dockerfile: plugin-registry/plugin-registry.Dockerfile
ports: ports:
- 127.0.0.1:55057:55057 - 127.0.0.1:55057:55057
......
...@@ -11,11 +11,13 @@ import ( ...@@ -11,11 +11,13 @@ import (
) )
func main() { func main() {
// Create a new DeviceModel.
deviceModel, err := sdk.NewDeviceModel(generated.Schema, generated.Unmarshal, generated.SchemaTreeGzip) deviceModel, err := sdk.NewDeviceModel(generated.Schema, generated.Unmarshal, generated.SchemaTreeGzip)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
// Serve the DeviceModelPlugin and provide the implemented deviceModel.
plugin.Serve(&plugin.ServeConfig{ plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: shared.Handshake, HandshakeConfig: shared.Handshake,
Plugins: map[string]plugin.Plugin{ Plugins: map[string]plugin.Plugin{
......
...@@ -11,11 +11,13 @@ import ( ...@@ -11,11 +11,13 @@ import (
) )
func main() { func main() {
// Create a new DeviceModel.
deviceModel, err := sdk.NewDeviceModel(generated.Schema, generated.Unmarshal, generated.SchemaTreeGzip) deviceModel, err := sdk.NewDeviceModel(generated.Schema, generated.Unmarshal, generated.SchemaTreeGzip)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
// Serve the DeviceModelPlugin and provide the implemented deviceModel.
plugin.Serve(&plugin.ServeConfig{ plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: shared.Handshake, HandshakeConfig: shared.Handshake,
Plugins: map[string]plugin.Plugin{ Plugins: map[string]plugin.Plugin{
......
...@@ -15,6 +15,8 @@ import ( ...@@ -15,6 +15,8 @@ import (
log "github.com/sirupsen/logrus" 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 { type DeviceModel struct {
mu sync.RWMutex mu sync.RWMutex
model ygot.ValidatedGoStruct model ygot.ValidatedGoStruct
...@@ -23,6 +25,7 @@ type DeviceModel struct { ...@@ -23,6 +25,7 @@ type DeviceModel struct {
genereatedSchemaTreeGzipFn func() []byte 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) { func NewDeviceModel(generatedSchemaFn func() (*ytypes.Schema, error), generatedUnmarshalFn func([]byte, ygot.GoStruct, ...ytypes.UnmarshalOpt) error, genereatedSchemaTreeGzipFn func() []byte) (*DeviceModel, error) {
schema, err := generatedSchemaFn() schema, err := generatedSchemaFn()
if err != nil { if err != nil {
...@@ -228,6 +231,7 @@ func (d *DeviceModel) Diff(original, modified []byte) (*gpb.Notification, error) ...@@ -228,6 +231,7 @@ func (d *DeviceModel) Diff(original, modified []byte) (*gpb.Notification, error)
return ygot.Diff(originalAsValidatedCopy, modifiedAsValidatedCopy, diffOpts...) 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) { func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Path, value *gpb.TypedValue) ([]byte, error) {
d.mu.RLock() d.mu.RLock()
modelCopy, err := createValidatedCopy(d.model) modelCopy, err := createValidatedCopy(d.model)
...@@ -255,6 +259,7 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat ...@@ -255,6 +259,7 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat
return ygot.Marshal7951(modelCopy, getYgotMarshal7951Config(), ygot.JSONIndent("")) 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) { func (d *DeviceModel) PruneConfigFalse(value []byte) ([]byte, error) {
validatedCopy, err := createValidatedCopy(d.schema.Root) validatedCopy, err := createValidatedCopy(d.schema.Root)
if err != nil { if err != nil {
......
...@@ -39,7 +39,7 @@ then ...@@ -39,7 +39,7 @@ then
echo "Need sudo rights." echo "Need sudo rights."
sudo echo "sudo rights granted" sudo echo "sudo rights granted"
# Start databases, etc. # Start databases, etc.
docker compose up -d docker compose up --build -d
sudo containerlab deploy -t $TOPOLOGY sudo containerlab deploy -t $TOPOLOGY
start_gosdn start_gosdn
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment