Skip to content
Snippets Groups Projects
  • Malte Bauch's avatar
    bb8079c7
    Resolve "The model stored in the plugin is increasing in size" · bb8079c7
    Malte Bauch authored
    This commit makes sure that the stored model data is only unmarshalled
    if the plugin is newly created. Otherwise we assume that the plugin
    represents the intended state (this is only temporary and will be
    changed in the future). Requesting configuration data directly from the
    device will now only pass that information through. Only changes
    triggered through the controller itself will be persisted.
    
    Additionally the handling of a subscribe response has been changed so
    the data is not unmarshalled into the plugins model data.
    bb8079c7
    History
    Resolve "The model stored in the plugin is increasing in size"
    Malte Bauch authored
    This commit makes sure that the stored model data is only unmarshalled
    if the plugin is newly created. Otherwise we assume that the plugin
    represents the intended state (this is only temporary and will be
    changed in the future). Requesting configuration data directly from the
    device will now only pass that information through. Only changes
    triggered through the controller itself will be persisted.
    
    Additionally the handling of a subscribe response has been changed so
    the data is not unmarshalled into the plugins model data.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
plugin.go 5.86 KiB
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()
	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
}