Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • danet/gosdn
1 result
Select Git revision
Show changes
Commits on Source (2)
  • Malte Bauch's avatar
    Resolve "After failing to add a network element to the storage, it is not... · 646ca304
    Malte Bauch authored
    Resolve "After failing to add a network element to the storage, it is not possible to redo this because of an issue with a file lock in the plugin regitry"
    
    This change fixes the `text file is busy` error if multiple network elements are registered using the same plugin. With this change it is ensured that the `RequestPlugin` method within the controller checks if the requested plugin has already been requested before and is therefore already present. If this is the case the plugin is reused and not downloaded again. Plugin has been extended with the path to the executable that is used.
    
    See merge request !450
    
    Co-authored-by: default avatarFabian Seidl <fabian.seidl@h-da.de>
    646ca304
  • Malte Bauch's avatar
    Correctly init the goStruct within the plugins ValidateChange method · 39c5e73a
    Malte Bauch authored
    The nodes along a path of the copied model (which is a copy of the
    underlying goStruct of the plugin) within the ValidateChange method of a
    plugin were not properly initialized.
    
    This means if a JSON string was provided that contained a node that was
    not initialized an error was thrown. But since the
    `ytypes.IgnoreExtraFields` option was set, the node was just ignored and
    no error was thrown.
    
    This change correctly initializes the node at the given path and removes
    the `ytypes.IgnoreExtraFields` option.
    39c5e73a
basepnduuid = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d" basepnduuid = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
basesouthboundtype = 1
basesouthbounduuid = "ca29311a-3b17-4385-96f8-515b602a97ac"
csbi-orchestrator = "clab-gosdn_csbi_arista_base-csbi-orchestrator:55056" csbi-orchestrator = "clab-gosdn_csbi_arista_base-csbi-orchestrator:55056"
plugin-registry = "clab-gosdn_csbi_arista_base-plugin-registry:55057" plugin-registry = "clab-gosdn_csbi_arista_base-plugin-registry:55057"
help = false help = false
......
...@@ -4,8 +4,6 @@ amqpport = '5672' ...@@ -4,8 +4,6 @@ amqpport = '5672'
amqpprefix = 'amqp://' amqpprefix = 'amqp://'
amqpuser = 'guest' amqpuser = 'guest'
basepnduuid = '5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d' basepnduuid = '5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d'
basesouthboundtype = 1
basesouthbounduuid = 'ca29311a-3b17-4385-96f8-515b602a97ac'
cli_pnd = '0455b241-5863-4660-ad15-dfde7617738e' cli_pnd = '0455b241-5863-4660-ad15-dfde7617738e'
cli_sbi = 'a249f2d2-f7da-481d-8a99-b7f11471e0af' cli_sbi = 'a249f2d2-f7da-481d-8a99-b7f11471e0af'
config = '/home/neil/code/gosdn/controller/configs/development-gosdn.toml' config = '/home/neil/code/gosdn/controller/configs/development-gosdn.toml'
......
...@@ -36,14 +36,14 @@ const ( ...@@ -36,14 +36,14 @@ const (
FAULTY FAULTY
) )
// Plugin describes an interface for a plugin within the controller - a plugin // Plugin describes an interface for a plugin within the controller. A plugin
// in the context of the controller is a basic go plugin. A plugin satisfies // is based on hashicorp's `go plugin`.
// the Storable interface and can be stored.
type Plugin interface { type Plugin interface {
ID() uuid.UUID ID() uuid.UUID
GetClient() *hcplugin.Client GetClient() *hcplugin.Client
State() State State() State
Manifest() *Manifest Manifest() *Manifest
ExecPath() string
Update() error Update() error
Ping() error Ping() error
Restart() error Restart() error
...@@ -123,7 +123,11 @@ type LoadedPlugin struct { ...@@ -123,7 +123,11 @@ type LoadedPlugin struct {
// Manifest represents the manifest of the LoadedPlugin. // Manifest represents the manifest of the LoadedPlugin.
Manifest Manifest `json:"manifest" bson:"manifest"` Manifest Manifest `json:"manifest" bson:"manifest"`
// State represents the state of the LoadedPlugin. // State represents the state of the LoadedPlugin.
State State `json:"state,omitempty" bson:"state"` State State `json:"state,omitempty" bson:"state"`
// ExecPath represents the path to the executable of the plugin.
ExecPath string `json:"exec_path,omitempty" bson:"exec_path"`
// ReattachConfig represents the configuration to reattach to a already
// running plugin.
ReattachConfig hcplugin.ReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"` ReattachConfig hcplugin.ReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"`
} }
...@@ -136,6 +140,7 @@ func (lp *LoadedPlugin) UnmarshalBSON(data []byte) error { ...@@ -136,6 +140,7 @@ func (lp *LoadedPlugin) UnmarshalBSON(data []byte) error {
lp.ID = loadedPluginHelper.ID lp.ID = loadedPluginHelper.ID
lp.Manifest = loadedPluginHelper.Manifest lp.Manifest = loadedPluginHelper.Manifest
lp.State = loadedPluginHelper.State lp.State = loadedPluginHelper.State
lp.ExecPath = loadedPluginHelper.ExecPath
lp.ReattachConfig = hcplugin.ReattachConfig{ lp.ReattachConfig = hcplugin.ReattachConfig{
Protocol: hcplugin.Protocol(loadedPluginHelper.ReattachConfig.Protocol), Protocol: hcplugin.Protocol(loadedPluginHelper.ReattachConfig.Protocol),
ProtocolVersion: loadedPluginHelper.ReattachConfig.ProtocolVersion, ProtocolVersion: loadedPluginHelper.ReattachConfig.ProtocolVersion,
...@@ -159,6 +164,7 @@ func (lp *LoadedPlugin) UnmarshalJSON(data []byte) error { ...@@ -159,6 +164,7 @@ func (lp *LoadedPlugin) UnmarshalJSON(data []byte) error {
lp.ID = loadedPluginHelper.ID lp.ID = loadedPluginHelper.ID
lp.Manifest = loadedPluginHelper.Manifest lp.Manifest = loadedPluginHelper.Manifest
lp.State = loadedPluginHelper.State lp.State = loadedPluginHelper.State
lp.ExecPath = loadedPluginHelper.ExecPath
lp.ReattachConfig = hcplugin.ReattachConfig{ lp.ReattachConfig = hcplugin.ReattachConfig{
Protocol: hcplugin.Protocol(loadedPluginHelper.ReattachConfig.Protocol), Protocol: hcplugin.Protocol(loadedPluginHelper.ReattachConfig.Protocol),
ProtocolVersion: loadedPluginHelper.ReattachConfig.ProtocolVersion, ProtocolVersion: loadedPluginHelper.ReattachConfig.ProtocolVersion,
...@@ -177,6 +183,7 @@ type LoadedPluginHelper struct { ...@@ -177,6 +183,7 @@ type LoadedPluginHelper struct {
ID string `json:"id" bson:"_id"` ID string `json:"id" bson:"_id"`
Manifest Manifest `json:"manifest" bson:"manifest"` Manifest Manifest `json:"manifest" bson:"manifest"`
State State `json:"state,omitempty" bson:"state"` State State `json:"state,omitempty" bson:"state"`
ExecPath string `json:"exec_path,omitempty" bson:"exec_path"`
ReattachConfig LoadedReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"` ReattachConfig LoadedReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"`
} }
......
...@@ -65,6 +65,20 @@ func (_m *Plugin) Diff(original []byte, modified []byte) (*gnmi.Notification, er ...@@ -65,6 +65,20 @@ func (_m *Plugin) Diff(original []byte, modified []byte) (*gnmi.Notification, er
return r0, r1 return r0, r1
} }
// ExecPath provides a mock function with given fields:
func (_m *Plugin) ExecPath() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// GetClient provides a mock function with given fields: // GetClient provides a mock function with given fields:
func (_m *Plugin) GetClient() *go_plugin.Client { func (_m *Plugin) GetClient() *go_plugin.Client {
ret := _m.Called() ret := _m.Called()
......
...@@ -4,9 +4,11 @@ import ( ...@@ -4,9 +4,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os/exec" "os/exec"
"path/filepath"
"code.fbi.h-da.de/danet/gosdn/controller/customerrs" "code.fbi.h-da.de/danet/gosdn/controller/customerrs"
"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin" "code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
"code.fbi.h-da.de/danet/gosdn/controller/nucleus/util"
"code.fbi.h-da.de/danet/gosdn/controller/plugin/shared" "code.fbi.h-da.de/danet/gosdn/controller/plugin/shared"
"github.com/google/uuid" "github.com/google/uuid"
hcplugin "github.com/hashicorp/go-plugin" hcplugin "github.com/hashicorp/go-plugin"
...@@ -16,25 +18,21 @@ import ( ...@@ -16,25 +18,21 @@ import (
type Plugin struct { type Plugin struct {
UUID uuid.UUID UUID uuid.UUID
state plugin.State state plugin.State
path string execPath string
manifest *plugin.Manifest manifest *plugin.Manifest
client *hcplugin.Client client *hcplugin.Client
shared.DeviceModel shared.DeviceModel
} }
func NewPlugin(id uuid.UUID) (*Plugin, error) { func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) {
if id == uuid.Nil {
id = uuid.New()
}
client := hcplugin.NewClient(&hcplugin.ClientConfig{ client := hcplugin.NewClient(&hcplugin.ClientConfig{
HandshakeConfig: shared.Handshake, HandshakeConfig: shared.Handshake,
Plugins: shared.PluginMap, Plugins: shared.PluginMap,
Cmd: exec.Command("sh", "-c", fmt.Sprintf("plugins/%s/plugin", id.String())), Cmd: exec.Command("sh", "-c", filepath.Join(execPath, util.PluginExecutableName)),
AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC}, AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
}) })
manifest, err := plugin.ReadManifestFromFile(fmt.Sprintf("plugins/%s", id.String())) manifest, err := plugin.ReadManifestFromFile(execPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -62,6 +60,7 @@ func NewPlugin(id uuid.UUID) (*Plugin, error) { ...@@ -62,6 +60,7 @@ func NewPlugin(id uuid.UUID) (*Plugin, error) {
return &Plugin{ return &Plugin{
UUID: id, UUID: id,
client: client, client: client,
execPath: execPath,
DeviceModel: model, DeviceModel: model,
manifest: manifest, manifest: manifest,
state: plugin.CREATED, state: plugin.CREATED,
...@@ -124,8 +123,8 @@ func (p *Plugin) State() plugin.State { ...@@ -124,8 +123,8 @@ func (p *Plugin) State() plugin.State {
return p.state return p.state
} }
func (p *Plugin) Path() string { func (p *Plugin) ExecPath() string {
return p.path return p.execPath
} }
// GetClient returns the client of the plugin. // GetClient returns the client of the plugin.
...@@ -173,11 +172,13 @@ func (p *Plugin) MarshalJSON() ([]byte, error) { ...@@ -173,11 +172,13 @@ func (p *Plugin) MarshalJSON() ([]byte, error) {
ID uuid.UUID `json:"id,omitempty"` ID uuid.UUID `json:"id,omitempty"`
Manifest *plugin.Manifest `json:"manifest" bson:"manifest"` Manifest *plugin.Manifest `json:"manifest" bson:"manifest"`
State plugin.State `json:"state,omitempty" bson:"state"` State plugin.State `json:"state,omitempty" bson:"state"`
ExecPath string `json:"exec_path,omitempty" bson:"exec_path"`
ReattachConfig *hcplugin.ReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"` ReattachConfig *hcplugin.ReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"`
}{ }{
ID: p.ID(), ID: p.ID(),
Manifest: p.Manifest(), Manifest: p.Manifest(),
State: p.State(), State: p.State(),
ExecPath: p.ExecPath(),
ReattachConfig: p.ReattachConfig(), ReattachConfig: p.ReattachConfig(),
}) })
} }
...@@ -187,11 +188,13 @@ func (p *Plugin) MarshalBSON() ([]byte, error) { ...@@ -187,11 +188,13 @@ func (p *Plugin) MarshalBSON() ([]byte, error) {
ID string `bson:"_id,omitempty"` ID string `bson:"_id,omitempty"`
Manifest *plugin.Manifest `json:"manifest" bson:"manifest"` Manifest *plugin.Manifest `json:"manifest" bson:"manifest"`
State plugin.State `json:"state,omitempty" bson:"state"` State plugin.State `json:"state,omitempty" bson:"state"`
ExecPath string `json:"exec_path,omitempty" bson:"exec_path"`
ReattachConfig *hcplugin.ReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"` ReattachConfig *hcplugin.ReattachConfig `json:"reattatch_config,omitempty" bson:"reattatch_config"`
}{ }{
ID: p.ID().String(), ID: p.ID().String(),
Manifest: p.Manifest(), Manifest: p.Manifest(),
State: p.State(), State: p.State(),
ExecPath: p.ExecPath(),
ReattachConfig: p.ReattachConfig(), ReattachConfig: p.ReattachConfig(),
}) })
} }
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
...@@ -118,7 +119,7 @@ func (s *PluginService) createPluginFromStore(loadedPlugin plugin.LoadedPlugin) ...@@ -118,7 +119,7 @@ func (s *PluginService) createPluginFromStore(loadedPlugin plugin.LoadedPlugin)
plugin, err := s.createPluginFromStoreFn(loadedPlugin) plugin, err := s.createPluginFromStoreFn(loadedPlugin)
if err != nil { if err != nil {
if errors.Is(err, hcplugin.ErrProcessNotFound) { if errors.Is(err, hcplugin.ErrProcessNotFound) {
plugin, err = NewPlugin(uuid.MustParse(loadedPlugin.ID)) plugin, err = NewPlugin(uuid.MustParse(loadedPlugin.ID), loadedPlugin.ExecPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -144,20 +145,24 @@ func (s *PluginService) RequestPlugin(requestID uuid.UUID) (plugin.Plugin, error ...@@ -144,20 +145,24 @@ func (s *PluginService) RequestPlugin(requestID uuid.UUID) (plugin.Plugin, error
Id: requestID.String(), Id: requestID.String(),
} }
dClient, err := s.pluginRegistryClient.Download(ctx, pluginDownloadRequest) folderName := viper.GetString("plugin-folder")
if err != nil { path := filepath.Join(folderName, requestID.String())
return nil, err if _, err := os.Stat(filepath.Join(path, util.PluginExecutableName)); errors.Is(err, fs.ErrNotExist) {
} dClient, err := s.pluginRegistryClient.Download(ctx, pluginDownloadRequest)
if err != nil {
return nil, err
}
if err := saveStreamToFile(dClient, util.BundledPluginName, requestID); err != nil { if err := saveStreamToFile(dClient, util.BundledPluginName, requestID); err != nil {
return nil, err return nil, err
} }
if err := util.UnzipPlugin(requestID); err != nil { if err := util.UnzipPlugin(requestID); err != nil {
return nil, err return nil, err
}
} }
plugin, err := NewPlugin(requestID) plugin, err := NewPlugin(uuid.New(), path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -12,6 +12,8 @@ import ( ...@@ -12,6 +12,8 @@ import (
// TODO: can be private in the future. // TODO: can be private in the future.
const ( const (
// PluginExecutableName references the unzippped name of a plugin.
PluginExecutableName string = "plugin"
// ManifestFileName references the name of a manifest file that has been // ManifestFileName references the name of a manifest file that has been
// requested while creating a new device of type plugin/csbi. // requested while creating a new device of type plugin/csbi.
ManifestFileName string = "plugin.yaml" ManifestFileName string = "plugin.yaml"
......
...@@ -197,14 +197,28 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat ...@@ -197,14 +197,28 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat
switch operation { switch operation {
case mnepb.ApiOperation_API_OPERATION_UPDATE, mnepb.ApiOperation_API_OPERATION_REPLACE: case mnepb.ApiOperation_API_OPERATION_UPDATE, mnepb.ApiOperation_API_OPERATION_REPLACE:
_, entry, err := ytypes.GetOrCreateNode(d.schema.RootSchema(), modelCopy, path) createdNode, entry, err := ytypes.GetOrCreateNode(d.schema.RootSchema(), modelCopy, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
validatedCreatedNode, ok := createdNode.(ygot.ValidatedGoStruct)
if !ok {
return nil, &customerrs.InvalidTypeAssertionError{
Value: createdNode,
Type: (*ygot.ValidatedGoStruct)(nil),
}
}
if entry.IsDir() { if entry.IsDir() {
opts := []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}} opts := []ytypes.UnmarshalOpt{
if err := d.generatedUnmarshalFn(value, modelCopy, opts...); err != nil { // NOTE: I think we should not ignore extra fields if we want
// to validate a specific change. The input for a valid change
// should be correct to be valid.
//
//&ytypes.IgnoreExtraFields{}
}
if err := d.generatedUnmarshalFn(value, validatedCreatedNode, opts...); err != nil {
return nil, err return nil, err
} }
} else if entry.IsLeaf() { } else if entry.IsLeaf() {
...@@ -213,7 +227,7 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat ...@@ -213,7 +227,7 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat
return nil, err return nil, err
} }
opts := []ytypes.SetNodeOpt{&ytypes.InitMissingElements{}, &ytypes.TolerateJSONInconsistencies{}} opts := []ytypes.SetNodeOpt{&ytypes.InitMissingElements{}, &ytypes.TolerateJSONInconsistencies{}}
if err := ytypes.SetNode(d.schema.RootSchema(), modelCopy, path, typedValue, opts...); err != nil { if err := ytypes.SetNode(d.schema.RootSchema(), validatedCreatedNode, path, typedValue, opts...); err != nil {
return nil, err return nil, err
} }
} }
......