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"
basesouthboundtype = 1
basesouthbounduuid = "ca29311a-3b17-4385-96f8-515b602a97ac"
csbi-orchestrator = "clab-gosdn_csbi_arista_base-csbi-orchestrator:55056"
plugin-registry = "clab-gosdn_csbi_arista_base-plugin-registry:55057"
help = false
......
......@@ -4,8 +4,6 @@ amqpport = '5672'
amqpprefix = 'amqp://'
amqpuser = 'guest'
basepnduuid = '5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d'
basesouthboundtype = 1
basesouthbounduuid = 'ca29311a-3b17-4385-96f8-515b602a97ac'
cli_pnd = '0455b241-5863-4660-ad15-dfde7617738e'
cli_sbi = 'a249f2d2-f7da-481d-8a99-b7f11471e0af'
config = '/home/neil/code/gosdn/controller/configs/development-gosdn.toml'
......
......@@ -36,14 +36,14 @@ const (
FAULTY
)
// 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
// the Storable interface and can be stored.
// Plugin describes an interface for a plugin within the controller. A plugin
// is based on hashicorp's `go plugin`.
type Plugin interface {
ID() uuid.UUID
GetClient() *hcplugin.Client
State() State
Manifest() *Manifest
ExecPath() string
Update() error
Ping() error
Restart() error
......@@ -123,7 +123,11 @@ type LoadedPlugin struct {
// Manifest represents the manifest of the LoadedPlugin.
Manifest Manifest `json:"manifest" bson:"manifest"`
// 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"`
}
......@@ -136,6 +140,7 @@ func (lp *LoadedPlugin) UnmarshalBSON(data []byte) error {
lp.ID = loadedPluginHelper.ID
lp.Manifest = loadedPluginHelper.Manifest
lp.State = loadedPluginHelper.State
lp.ExecPath = loadedPluginHelper.ExecPath
lp.ReattachConfig = hcplugin.ReattachConfig{
Protocol: hcplugin.Protocol(loadedPluginHelper.ReattachConfig.Protocol),
ProtocolVersion: loadedPluginHelper.ReattachConfig.ProtocolVersion,
......@@ -159,6 +164,7 @@ func (lp *LoadedPlugin) UnmarshalJSON(data []byte) error {
lp.ID = loadedPluginHelper.ID
lp.Manifest = loadedPluginHelper.Manifest
lp.State = loadedPluginHelper.State
lp.ExecPath = loadedPluginHelper.ExecPath
lp.ReattachConfig = hcplugin.ReattachConfig{
Protocol: hcplugin.Protocol(loadedPluginHelper.ReattachConfig.Protocol),
ProtocolVersion: loadedPluginHelper.ReattachConfig.ProtocolVersion,
......@@ -177,6 +183,7 @@ type LoadedPluginHelper struct {
ID string `json:"id" bson:"_id"`
Manifest Manifest `json:"manifest" bson:"manifest"`
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"`
}
......
......@@ -65,6 +65,20 @@ func (_m *Plugin) Diff(original []byte, modified []byte) (*gnmi.Notification, er
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:
func (_m *Plugin) GetClient() *go_plugin.Client {
ret := _m.Called()
......
......@@ -4,9 +4,11 @@ import (
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"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/nucleus/util"
"code.fbi.h-da.de/danet/gosdn/controller/plugin/shared"
"github.com/google/uuid"
hcplugin "github.com/hashicorp/go-plugin"
......@@ -16,25 +18,21 @@ import (
type Plugin struct {
UUID uuid.UUID
state plugin.State
path string
execPath string
manifest *plugin.Manifest
client *hcplugin.Client
shared.DeviceModel
}
func NewPlugin(id uuid.UUID) (*Plugin, error) {
if id == uuid.Nil {
id = uuid.New()
}
func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) {
client := hcplugin.NewClient(&hcplugin.ClientConfig{
HandshakeConfig: shared.Handshake,
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},
})
manifest, err := plugin.ReadManifestFromFile(fmt.Sprintf("plugins/%s", id.String()))
manifest, err := plugin.ReadManifestFromFile(execPath)
if err != nil {
return nil, err
}
......@@ -62,6 +60,7 @@ func NewPlugin(id uuid.UUID) (*Plugin, error) {
return &Plugin{
UUID: id,
client: client,
execPath: execPath,
DeviceModel: model,
manifest: manifest,
state: plugin.CREATED,
......@@ -124,8 +123,8 @@ func (p *Plugin) State() plugin.State {
return p.state
}
func (p *Plugin) Path() string {
return p.path
func (p *Plugin) ExecPath() string {
return p.execPath
}
// GetClient returns the client of the plugin.
......@@ -173,11 +172,13 @@ func (p *Plugin) MarshalJSON() ([]byte, error) {
ID uuid.UUID `json:"id,omitempty"`
Manifest *plugin.Manifest `json:"manifest" bson:"manifest"`
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"`
}{
ID: p.ID(),
Manifest: p.Manifest(),
State: p.State(),
ExecPath: p.ExecPath(),
ReattachConfig: p.ReattachConfig(),
})
}
......@@ -187,11 +188,13 @@ func (p *Plugin) MarshalBSON() ([]byte, error) {
ID string `bson:"_id,omitempty"`
Manifest *plugin.Manifest `json:"manifest" bson:"manifest"`
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"`
}{
ID: p.ID().String(),
Manifest: p.Manifest(),
State: p.State(),
ExecPath: p.ExecPath(),
ReattachConfig: p.ReattachConfig(),
})
}
......@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
......@@ -118,7 +119,7 @@ func (s *PluginService) createPluginFromStore(loadedPlugin plugin.LoadedPlugin)
plugin, err := s.createPluginFromStoreFn(loadedPlugin)
if err != nil {
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 {
return nil, err
}
......@@ -144,20 +145,24 @@ func (s *PluginService) RequestPlugin(requestID uuid.UUID) (plugin.Plugin, error
Id: requestID.String(),
}
dClient, err := s.pluginRegistryClient.Download(ctx, pluginDownloadRequest)
if err != nil {
return nil, err
}
folderName := viper.GetString("plugin-folder")
path := filepath.Join(folderName, requestID.String())
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 {
return nil, err
}
if err := saveStreamToFile(dClient, util.BundledPluginName, requestID); err != nil {
return nil, err
}
if err := util.UnzipPlugin(requestID); err != nil {
return nil, err
if err := util.UnzipPlugin(requestID); err != nil {
return nil, err
}
}
plugin, err := NewPlugin(requestID)
plugin, err := NewPlugin(uuid.New(), path)
if err != nil {
return nil, err
}
......
......@@ -12,6 +12,8 @@ import (
// TODO: can be private in the future.
const (
// PluginExecutableName references the unzippped name of a plugin.
PluginExecutableName string = "plugin"
// ManifestFileName references the name of a manifest file that has been
// requested while creating a new device of type plugin/csbi.
ManifestFileName string = "plugin.yaml"
......
......@@ -197,14 +197,28 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat
switch operation {
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 {
return nil, err
}
validatedCreatedNode, ok := createdNode.(ygot.ValidatedGoStruct)
if !ok {
return nil, &customerrs.InvalidTypeAssertionError{
Value: createdNode,
Type: (*ygot.ValidatedGoStruct)(nil),
}
}
if entry.IsDir() {
opts := []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}}
if err := d.generatedUnmarshalFn(value, modelCopy, opts...); err != nil {
opts := []ytypes.UnmarshalOpt{
// 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
}
} else if entry.IsLeaf() {
......@@ -213,7 +227,7 @@ func (d *DeviceModel) ValidateChange(operation mnepb.ApiOperation, path *gpb.Pat
return nil, err
}
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
}
}
......