package nucleus import ( "encoding/json" "fmt" "os" "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" "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, 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) { //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 } pc, ok := pluginClients[pluginId] if !ok { return nil, fmt.Errorf("plugin not found") } 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() } // 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() { // 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(), }) }