diff --git a/controller/interfaces/plugin/plugin.go b/controller/interfaces/plugin/plugin.go index 6835b7e738b764149bac026232a0fd4d35d43208..dc01a0ee8145f5638cd4ca92d0bfa5b3017b68fe 100644 --- a/controller/interfaces/plugin/plugin.go +++ b/controller/interfaces/plugin/plugin.go @@ -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"` } diff --git a/controller/mocks/Plugin.go b/controller/mocks/Plugin.go index a9fd4e3508a08437f9ddea704aaed12f6e83aa8b..aa33c39de6ae2a0f7a92f63a5374bc6dcf4af401 100644 --- a/controller/mocks/Plugin.go +++ b/controller/mocks/Plugin.go @@ -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() diff --git a/controller/nucleus/plugin.go b/controller/nucleus/plugin.go index 8851e16a9aa8248cde209faa9921ef2cc2001609..adc0312b3d41c5ba03d695dcead5232d51e9563a 100644 --- a/controller/nucleus/plugin.go +++ b/controller/nucleus/plugin.go @@ -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(), }) } diff --git a/controller/nucleus/pluginService.go b/controller/nucleus/pluginService.go index 2a8c28c80da6f63c412de938133f56001607f31f..ed7a7ab689cd65e1c7f8ae408de107812a1f454b 100644 --- a/controller/nucleus/pluginService.go +++ b/controller/nucleus/pluginService.go @@ -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 } diff --git a/controller/nucleus/util/plugin.go b/controller/nucleus/util/plugin.go index a0e90b2d37ddf8edeb744c8a722b76a1546c575d..9d78e96740961b39256cea28d269e6fcc2ddbad0 100644 --- a/controller/nucleus/util/plugin.go +++ b/controller/nucleus/util/plugin.go @@ -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"