Newer
Older
package nucleus
import (
"encoding/json"
"fmt"
Malte Bauch
committed
"os"
"os/exec"
"path/filepath"
Fabian Seidl
committed
"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"
"go.mongodb.org/mongo-driver/bson"
)
type pluginConnection struct {
client *hcplugin.Client
model shared.DeviceModel
}
var pluginClients = make(map[uuid.UUID]pluginConnection, 0)
// Plugin is the controllers internal representation of a plugin.
type Plugin struct {
UUID uuid.UUID
state plugin.State
execPath string
manifest *plugin.Manifest
client *hcplugin.Client
shared.DeviceModel
}
// NewPlugin creates a new Plugin.
func NewPlugin(id uuid.UUID, execPath string) (*Plugin, error) {
client := hcplugin.NewClient(&hcplugin.ClientConfig{
HandshakeConfig: shared.Handshake,
Plugins: shared.PluginMap,
Malte Bauch
committed
Cmd: exec.Command(filepath.Join(execPath, util.PluginExecutableName)),
AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
})
manifest, err := plugin.ReadManifestFromFile(execPath)
if err != nil {
return nil, err
}
// create a client that is within the AllowedProtocols. In this case this
// returns a gRPCClient. Allows to connect through gRPC.
gRPCClient, err := client.Client()
if err != nil {
return nil, err
}
// Request the plugin. This returns the gRPC client from the
// DeviceModelPlugin. This can then be casted to the interface that we are
// exposing through the plugin (in this case "DeviceModel").
raw, err := gRPCClient.Dispense("deviceModel")
if err != nil {
return nil, err
}
// cast the raw plugin to the DeviceModel interface. This allows to call
// methods on the plugin as if it were a normal DeviceModel instance but
// actually they are executed on the plugin sent through gRPC.
model, ok := raw.(shared.DeviceModel)
if !ok {
return nil, customerrs.InvalidTypeAssertionError{
Value: raw,
Type: (*shared.DeviceModel)(nil),
}
}
pluginClients[id] = pluginConnection{
client: client,
model: model,
}
return &Plugin{
UUID: id,
client: client,
execPath: execPath,
DeviceModel: model,
manifest: manifest,
state: plugin.CREATED,
}, nil
}
// NewPluginThroughReattachConfig creates a new Plugin through a reattach config.
func NewPluginThroughReattachConfig(loadedPlugin plugin.LoadedPlugin) (plugin.Plugin, error) {
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//client := hcplugin.NewClient(&hcplugin.ClientConfig{
// HandshakeConfig: shared.Handshake,
// Plugins: shared.PluginMap,
// Reattach: &loadedPlugin.ReattachConfig,
// AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
//})
//// create a client that is within the AllowedProtocols. In this case this
//// returns a gRPCClient. Allows to connect through gRPC.
//gRPCClient, err := client.Client()
//if err != nil {
// return nil, err
//}
//// Request the plugin. This returns the gRPC client from the
//// DeviceModelPlugin. This can then be casted to the interface that we are
//// exposing through the plugin (in this case "DeviceModel").
//raw, err := gRPCClient.Dispense("deviceModel")
//if err != nil {
// return nil, err
//}
//// cast the raw plugin to the DeviceModel interface. This allows to call
//// methods on the plugin as if it were a normal DeviceModel instance but
//// actually they are executed on the plugin sent through gRPC.
//model, ok := raw.(shared.DeviceModel)
//if !ok {
// return nil, customerrs.InvalidTypeAssertionError{
// Value: model,
// Type: (*shared.DeviceModel)(nil),
// }
//}
pluginId, err := uuid.Parse(loadedPlugin.ID)
if err != nil {
return nil, err
}
if !ok {
}
return &Plugin{
UUID: uuid.MustParse(loadedPlugin.ID),
client: pc.client,
DeviceModel: pc.model,
manifest: &loadedPlugin.Manifest,
state: plugin.INITIALIZED,
}, nil
}
// ID returns the ID of the plugin.
func (p *Plugin) ID() uuid.UUID {
return p.UUID
}
// ID returns the ID of the plugin.
func (p *Plugin) ReattachConfig() *hcplugin.ReattachConfig {
return p.client.ReattachConfig()
}
Malte Bauch
committed
// Remove ensures that the Plugin is killed and the corresponding files are
// removed.
func (p *Plugin) Remove() error {
// stop the running plugins process
p.Close()
// remove the plugins folder
return os.RemoveAll(p.ExecPath())
}
// State returns the current state of the plugin.
// Different states of the plugin can be:
// - created
// - initialized
// - faulty
func (p *Plugin) State() plugin.State {
return p.state
}
// ExecPath returns the path to the executable of the plugin.
func (p *Plugin) ExecPath() string {
return p.execPath
}
// GetClient returns the client of the plugin.
func (p *Plugin) GetClient() *hcplugin.Client {
return p.client
}
// Manifest returns the manifest of the plugin.
func (p *Plugin) Manifest() *plugin.Manifest {
return p.manifest
}
// Update updates the plugin to the latest available version.
func (p *Plugin) Update() error {
return fmt.Errorf("not implemented yet")
}
// Restart restarts the plugin.
func (p *Plugin) Restart() error {
return fmt.Errorf("not implemented yet")
}
// Close ends the execution of the plugin.
func (p *Plugin) Close() {
Malte Bauch
committed
// end the plugin process
p.client.Kill()
}
// Ping checks if the client connection is healthy.
func (p *Plugin) Ping() error {
protocolClient, err := p.client.Client()
if err != nil {
return err
}
return protocolClient.Ping()
}
// TODO: update for the new way of handling plugins
// UpdatePlugin updates a given Plugin. Therefore the version of the
// `plugin.yml` manifest file is compared to the version in use. If a new
// version is within the plugin folder, the new version of the plugin is built.
func UpdatePlugin(p plugin.Plugin) (updated bool, err error) {
return false, fmt.Errorf("not implemented yet")
}
func (p *Plugin) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
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(),
})
}
func (p *Plugin) MarshalBSON() ([]byte, error) {
return bson.Marshal(&struct {
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(),
})