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
No related branches found
No related tags found
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 (
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"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment