package plugin import ( "encoding/json" "fmt" "net" "os" "path/filepath" "regexp" "code.fbi.h-da.de/danet/gosdn/controller/customerrs" "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" "gopkg.in/yaml.v3" ) // State represents the current state of a plugin within the controller. Since // the plugins used within the controller are basic go plugins, they can be // CREATED, BUILT, LOADED or FAULTY. A plugin can not be unloaded (this is a // limitation of go plugins in general). type State int64 const ( //CREATED state describes a plugin which has been created but is not yet //built. CREATED State = iota // INITIALIZED state describes a plugin which is running and has been // initialized with the model data of the associated network element. INITIALIZED // FAULTY state describes a plugin which couldn't be built or loaded. FAULTY ) // 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 Close() Remove() error shared.DeviceModel } // Manifest represents the manifest of a plugin. type Manifest struct { // Name of the plugin Name string `yaml:"name" json:"name" bson:"name"` // Name of the plugin Firmware string `yaml:"firmware" json:"firmware" bson:"firmware"` // Author of the plugin Author string `yaml:"author" json:"author" bson:"author"` // Version of the plugin Version string `yaml:"version" json:"version" bson:"version"` } // Validate is a method to check if the manifest is valid and is compliant with // the requirements. func (m *Manifest) Validate() error { errs := []error{} if m.Name == "" { errs = append(errs, fmt.Errorf("Name is required")) } if m.Firmware == "" { errs = append(errs, fmt.Errorf("Firmware is required")) } if m.Author == "" { errs = append(errs, fmt.Errorf("Author is required")) } if m.Version == "" { errs = append(errs, fmt.Errorf("Version is required")) } // regex from: https://stackoverflow.com/a/68921827 validVersion, err := regexp.MatchString(`^([1-9]\d*|0)(\.(([1-9]\d*)|0)){2}$`, m.Version) if err != nil { errs = append(errs, err) } if !validVersion { errs = append(errs, fmt.Errorf("Version has to be of form: X.X.X")) } if len(errs) != 0 { return customerrs.CombinedErrListError{Errors: errs} } return nil } // ReadManifestFromFile reads a manifest file and returns a pointer to a newly // created Manifest. func ReadManifestFromFile(path string) (*Manifest, error) { manifest := &Manifest{} manifestFile, err := os.ReadFile(filepath.Join(path, util.ManifestFileName)) if err != nil { return nil, err } err = yaml.Unmarshal(manifestFile, manifest) if err != nil { return nil, err } // validate the loaded manifest if err := manifest.Validate(); err != nil { return nil, err } return manifest, nil } type LoadedPlugin struct { // ID represents the UUID of the LoadedPlugin. ID string `json:"id" bson:"_id"` // 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"` // 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"` } func (lp *LoadedPlugin) UnmarshalBSON(data []byte) error { loadedPluginHelper := new(LoadedPluginHelper) if err := bson.Unmarshal(data, loadedPluginHelper); err != nil { return err } 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, Addr: &net.UnixAddr{ Name: loadedPluginHelper.ReattachConfig.Addr.Name, Net: loadedPluginHelper.ReattachConfig.Addr.Net, }, Pid: loadedPluginHelper.ReattachConfig.Pid, Test: loadedPluginHelper.ReattachConfig.Test, } return nil } func (lp *LoadedPlugin) UnmarshalJSON(data []byte) error { loadedPluginHelper := new(LoadedPluginHelper) if err := json.Unmarshal(data, loadedPluginHelper); err != nil { return err } 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, Addr: &net.UnixAddr{ Name: loadedPluginHelper.ReattachConfig.Addr.Name, Net: loadedPluginHelper.ReattachConfig.Addr.Net, }, Pid: loadedPluginHelper.ReattachConfig.Pid, Test: loadedPluginHelper.ReattachConfig.Test, } return nil } 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"` } type LoadedReattachConfig struct { Protocol string ProtocolVersion int Addr LoadedAddress Pid int Test bool } type LoadedAddress struct { Name string Net string }