Skip to content
Snippets Groups Projects
southbound.go 10.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Manuel Kieweg's avatar
    Manuel Kieweg committed
    package nucleus
    
    
    import (
    
    Neil Schark's avatar
    Neil Schark committed
    	"fmt"
    
    	"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/models/generated/openconfig"
    
    	"go.mongodb.org/mongo-driver/bson"
    
    	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
    
    	"github.com/google/uuid"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	gpb "github.com/openconfig/gnmi/proto/gnmi"
    	"github.com/openconfig/goyang/pkg/yang"
    
    	"github.com/openconfig/ygot/ygot"
    
    	"github.com/openconfig/ygot/ytypes"
    
    	log "github.com/sirupsen/logrus"
    
    // NewSBI creates a SouthboundInterface of a given type.
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    func NewSBI(southbound spb.Type, sbUUID ...uuid.UUID) (southbound.SouthboundInterface, error) {
    
    	var id uuid.UUID
    
    	if len(sbUUID) == 0 {
    		id = uuid.New()
    	} else {
    		id = sbUUID[0]
    	}
    
    
    	switch southbound {
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	case spb.Type_TYPE_OPENCONFIG:
    		return &OpenConfig{id: id}, nil
    
    	case spb.Type_TYPE_CONTAINERISED, spb.Type_TYPE_PLUGIN:
    		p := filepath.Join(viper.GetString("plugin-folder"), id.String())
    		sbip, err := NewSouthboundPlugin(id, p, true)
    		if err != nil {
    			return nil, err
    		}
    		return sbip, nil
    
    	default:
    
    		return nil, customerrs.UnsupportedSbiTypeError{Type: southbound}
    
    	}
    }
    
    // NewSouthboundPlugin that returns a new SouthboundPlugin. The plugin is built
    // within this process and loaded as southbound.SouthboundInterface afterwards.
    func NewSouthboundPlugin(id uuid.UUID, path string, build bool) (*SouthboundPlugin, error) {
    
    	manifest, err := plugin.ReadManifestFromFile(filepath.Join(path, util.ManifestFileName))
    
    	if err != nil {
    		return nil, err
    	}
    	sp := &SouthboundPlugin{
    		state:      plugin.CREATED,
    		BinaryPath: path,
    		manifest:   manifest,
    		sbi:        nil,
    	}
    	if build {
    
    		if err := BuildPlugin(sp.Path(), []string{util.GoStructName, util.GoStructAdditionsName}); err != nil {
    
    			return nil, err
    		}
    	}
    	if err := sp.load(id); err != nil {
    		return nil, err
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // OpenConfig is the implementation of an OpenConfig SBI.
    // The struct holds the YANG schema and a function that
    // returns an SBI specific SetNode function.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    type OpenConfig struct {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	schema *ytypes.Schema
    
    	id     uuid.UUID
    
    	// nolint:unused
    	path string
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // SbiIdentifier returns the string representation of
    // the SBI
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (oc *OpenConfig) SbiIdentifier() string {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	return "openconfig"
    }
    
    
    // Name returns the name of a sbi.
    
    func (oc *OpenConfig) Name() string {
    	return oc.SbiIdentifier()
    }
    
    
    // Schema returns a ygot generated openconfig Schema as ytypes.Schema.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (oc *OpenConfig) Schema() *ytypes.Schema {
    	schema, err := openconfig.Schema()
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		log.Fatal(err)
    	}
    	return schema
    }
    
    // SchemaTreeGzip returns the ygot generated SchemaTree compressed as gzip byte
    
    // slice.
    func (oc *OpenConfig) SchemaTreeGzip() []byte {
    	return openconfig.SchemaTreeGzip()
    }
    
    
    // SetNode injects OpenConfig specific model representation to the transport.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // Needed for type assertion.
    
    func (oc *OpenConfig) SetNode(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error {
    	return ytypes.SetNode(schema, root.(*openconfig.Device), path, val, opts...)
    
    // Unmarshal injects OpenConfig specific model representation to the transport.
    
    // Needed for type assertion.
    
    func (oc *OpenConfig) Unmarshal(bytes []byte, path *gpb.Path, goStruct ygot.GoStruct, opt ...ytypes.UnmarshalOpt) error {
    
    	return unmarshal(oc.Schema(), bytes, path, goStruct, opt...)
    
    Neil Schark's avatar
    Neil Schark committed
    // Generates an empty validated struct from a given schema and path
    func generateEmptyValidatedStructs(schema *ytypes.Schema, path *gpb.Path) (ygot.ValidatedGoStruct, ygot.ValidatedGoStruct, error) {
    
    	// Load SBI definition
    
    	root, err := ygot.DeepCopy(schema.Root)
    
    Neil Schark's avatar
    Neil Schark committed
    		return nil, nil, err
    
    	validatedDeepCopy, ok := root.(ygot.ValidatedGoStruct)
    	if !ok {
    
    Neil Schark's avatar
    Neil Schark committed
    		return nil, nil, &customerrs.InvalidTypeAssertionError{
    
    			Value: root,
    			Type:  (*ygot.ValidatedGoStruct)(nil),
    		}
    
    Neil Schark's avatar
    Neil Schark committed
    	// returns the node we want to fill with the data contained in 'bytes', using the specified 'path'.
    
    	createdNode, _, err := ytypes.GetOrCreateNode(schema.RootSchema(), validatedDeepCopy, path)
    	if err != nil {
    
    Neil Schark's avatar
    Neil Schark committed
    		return nil, nil, err
    
    	}
    	validatedCreatedNode, ok := createdNode.(ygot.ValidatedGoStruct)
    	if !ok {
    
    Neil Schark's avatar
    Neil Schark committed
    		return nil, nil, &customerrs.InvalidTypeAssertionError{
    
    			Value: createdNode,
    			Type:  (*ygot.ValidatedGoStruct)(nil),
    		}
    
    Neil Schark's avatar
    Neil Schark committed
    	return validatedDeepCopy, validatedCreatedNode, nil
    }
    
    
    Neil Schark's avatar
    Neil Schark committed
    func removeReadOnlyFields(validatedGoStruct ygot.ValidatedGoStruct, emptyValidatedGoStruct ygot.ValidatedGoStruct, schema *ytypes.Schema) error {
    	diff, err := ygot.Diff(emptyValidatedGoStruct, validatedGoStruct)
    	if err != nil {
    		return err
    	}
    	filteredUpdates := make([]*gpb.Update, 0)
    	for _, u := range diff.Update {
    		rootSchema := schema.RootSchema()
    		pathString, err := ygot.PathToString(u.GetPath())
    		if err != nil {
    			return err
    		}
    		entry := rootSchema.Find(pathString)
    		if !entry.ReadOnly() {
    			filteredUpdates = append(filteredUpdates, u)
    		}
    	}
    
    	return nil
    }
    
    
    Neil Schark's avatar
    Neil Schark committed
    // unmarshal parses a gNMI response to a go struct.
    func unmarshal(schema *ytypes.Schema, bytes []byte, path *gpb.Path, goStruct ygot.GoStruct, opt ...ytypes.UnmarshalOpt) error {
    	defer func() {
    		if r := recover(); r != nil {
    			log.Error(r.(error))
    		}
    	}()
    
    
    Neil Schark's avatar
    Neil Schark committed
        ygot
    	validatedDeepCopy, validatedCreatedNode, err := generateEmptyValidatedStructs(schema, path)
    
    Neil Schark's avatar
    Neil Schark committed
    	if err != nil {
    		return err
    	}
    
    
    Neil Schark's avatar
    Neil Schark committed
    	validatedDeepCopyForDiff, validatedCreatedNodeForDiff, err := generateEmptyValidatedStructs(schema, path)
    
    Neil Schark's avatar
    Neil Schark committed
    	if err != nil {
    		return err
    	}
    
    
    Neil Schark's avatar
    Neil Schark committed
    	fmt.Println(validatedDeepCopyForDiff)
    	fmt.Println(validatedCreatedNodeForDiff)
    
    	if err := openconfig.Unmarshal(bytes, validatedCreatedNode, opt...); err != nil {
    
    		return err
    	}
    
    Neil Schark's avatar
    Neil Schark committed
        validatedCreatedNode.
    
    	//err = removeReadOnlyFields(validatedCreatedNode, validatedCreatedNodeForDiff, schema)
    	//if err != nil {
    	//	return err
    	//}
    
    
    	opts := []ygot.MergeOpt{&ygot.MergeOverwriteExistingFields{}}
    	return ygot.MergeStructInto(goStruct, validatedDeepCopy, opts...)
    
    // ID returns the ID of the OpenConfig SBI.
    
    func (oc *OpenConfig) ID() uuid.UUID {
    
    // SetID sets the ID of the OpenConfig SBI.
    
    func (oc *OpenConfig) SetID(id uuid.UUID) {
    	oc.id = id
    }
    
    // Type returns the Southbound's type.
    
    func (oc *OpenConfig) Type() spb.Type {
    	return spb.Type_TYPE_OPENCONFIG
    
    // SouthboundPlugin is the implementation of a southbound goSDN plugin.
    type SouthboundPlugin struct {
    	sbi        southbound.SouthboundInterface
    	state      plugin.State
    	BinaryPath string
    	manifest   *plugin.Manifest
    
    // Name returns the name of a sbi.
    
    func (p *SouthboundPlugin) Name() string {
    	return "plugin"
    }
    
    
    // SetNode injects SBI specific model representation to the transport.
    
    // Needed for type assertion.
    
    func (p *SouthboundPlugin) SetNode(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error {
    	return p.sbi.SetNode(schema, root, path, val, opts...)
    }
    
    
    // Schema returns a ygot generated Schema as ytypes.Schema.
    
    func (p *SouthboundPlugin) Schema() *ytypes.Schema {
    	return p.sbi.Schema()
    }
    
    
    // SchemaTreeGzip returns the ygot generated SchemaTree compressed as gzip byte
    // slice.
    func (p *SouthboundPlugin) SchemaTreeGzip() []byte {
    	return p.sbi.SchemaTreeGzip()
    }
    
    
    // ID returns the ID of the plugin.
    func (p *SouthboundPlugin) ID() uuid.UUID {
    	return p.sbi.ID()
    }
    
    
    // SetID sets the ID of the SouthboundPlugin's SBI.
    
    func (p *SouthboundPlugin) SetID(id uuid.UUID) {
    	p.sbi.SetID(id)
    
    // Type returns the Southbound's type of the SouthboundPlugin.
    
    func (p *SouthboundPlugin) Type() spb.Type {
    	return p.sbi.Type()
    }
    
    // Unmarshal injects SBI specific model representation to the transport.
    
    // Needed for type assertion.
    
    func (p *SouthboundPlugin) Unmarshal(data []byte, path *gpb.Path, root ygot.GoStruct, opts ...ytypes.UnmarshalOpt) error {
    
    	return p.sbi.Unmarshal(data, path, root, opts...)
    
    // State returns the current state of the plugin.
    func (p *SouthboundPlugin) State() plugin.State {
    	return p.state
    }
    
    // Path returns the path of the plugins binary.
    func (p *SouthboundPlugin) Path() string {
    	return p.BinaryPath
    }
    
    // Manifest returns the Manifest of the plugin.
    func (p *SouthboundPlugin) Manifest() *plugin.Manifest {
    	return p.manifest
    }
    
    // load is a helper function that loads a plugin and casts it to the type of
    // southbound.SouthboundInterface. Therefore a SouthboundPlugin has to be provided
    // so it can be loaded by using its BinaryPath. The loaded plugin is typecasted to
    // southbound.SouthboundInterface and is set as the plugin's southbound interface.
    func (p *SouthboundPlugin) load(id uuid.UUID) error {
    	// load the SouthboundPlugin
    	symbol, err := LoadPlugin(p.BinaryPath)
    
    	if err != nil {
    
    	// Typecast the go plugins symbol to southbound.SouthboundInterface
    	sbi, ok := symbol.(southbound.SouthboundInterface)
    	if !ok {
    		p.state = plugin.FAULTY
    
    			Value: symbol,
    			Type:  (*southbound.SouthboundInterface)(nil),
    
    		}
    	}
    	// Note(mbauch): We could consider moving this into plugin creation.
    	// set the ID of the southbound interface to the plugins ID
    	sbi.SetID(id)
    	// Update the state of the plugin to LOADED
    	p.state = plugin.LOADED
    	// Update the plugins sbi to the loaded go plugin
    	p.sbi = sbi
    
    	log.WithFields(log.Fields{
    		"id":   sbi.ID(),
    		"type": sbi.Type(),
    	}).Trace("plugin information")
    
    // Update updates the SouthboundPlugin's SBI.
    func (p *SouthboundPlugin) Update() error {
    	updated, err := UpdatePlugin(p)
    	if err != nil {
    		return err
    	}
    	if updated {
    		err = p.load(p.ID())
    		if err != nil {
    			return err
    		}
    	}
    	return nil
    
    // MarshalJSON implements the MarshalJSON interface to store a sbi as JSON.
    
    func (p *SouthboundPlugin) MarshalJSON() ([]byte, error) {
    	return json.Marshal(&struct {
    		ID   string   `json:"_id"`
    		Type spb.Type `json:"type"`
    	}{
    		ID:   p.sbi.ID().String(),
    		Type: p.Type(),
    	})
    }
    
    
    // MarshalJSON implements the MarshalJSON interface to store a sbi as JSON.
    
    func (oc *OpenConfig) MarshalJSON() ([]byte, error) {
    	return json.Marshal(&struct {
    		ID   string   `json:"_id"`
    		Type spb.Type `json:"type"`
    	}{
    		ID:   oc.id.String(),
    		Type: oc.Type(),
    	})
    }
    
    
    // MarshalBSON implements the MarshalBSON interface to store a sbi as BSON.
    
    Andre Sterba's avatar
    Andre Sterba committed
    func (p *SouthboundPlugin) MarshalBSON() ([]byte, error) {
    	return bson.Marshal(&struct {
    		ID   string   `bson:"_id"`
    		Type spb.Type `bson:"type"`
    	}{
    		ID:   p.sbi.ID().String(),
    		Type: p.Type(),
    	})
    }
    
    
    // MarshalBSON implements the MarshalBSON interface to store a sbi as BSON.
    
    func (oc *OpenConfig) MarshalBSON() ([]byte, error) {
    	return bson.Marshal(&struct {
    		ID   string   `bson:"_id"`
    		Type spb.Type `bson:"type"`
    	}{
    		ID:   oc.id.String(),
    		Type: oc.Type(),
    	})
    }