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

Resolve "After failing to add a network element to the storage, it is not...

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>
parent 949595cb
Branches
Tags
1 merge request!450Resolve "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"
Pipeline #138929 passed
...@@ -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"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment