Newer
Older
"path/filepath"
Fabian Seidl
committed
"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"
gpb "github.com/openconfig/gnmi/proto/gnmi"
"github.com/openconfig/goyang/pkg/yang"
"github.com/openconfig/ygot/ygot"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
// NewSBI creates a SouthboundInterface of a given type.
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]
}
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
Fabian Seidl
committed
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
return sp, nil
// OpenConfig is the implementation of an OpenConfig SBI.
// The struct holds the YANG schema and a function that
// returns an SBI specific SetNode function.
// nolint:unused
path string
// SbiIdentifier returns the string representation of
// the SBI
// deprecated.
// 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.
func (oc *OpenConfig) Schema() *ytypes.Schema {
schema, err := openconfig.Schema()
Manuel Kieweg
committed
if err != nil {
Manuel Kieweg
committed
// 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.
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 {
Malte Bauch
committed
return unmarshal(oc.Schema(), bytes, path, goStruct, opt...)
// Generates an empty validated struct from a given schema and path
func generateEmptyValidatedStructs(schema *ytypes.Schema, path *gpb.Path) (ygot.ValidatedGoStruct, ygot.ValidatedGoStruct, error) {
validatedDeepCopy, ok := root.(ygot.ValidatedGoStruct)
if !ok {
Value: root,
Type: (*ygot.ValidatedGoStruct)(nil),
}
// returns the node we want to fill with the data contained in 'bytes', using the specified 'path'.
Malte Bauch
committed
createdNode, _, err := ytypes.GetOrCreateNode(schema.RootSchema(), validatedDeepCopy, path)
if err != nil {
Malte Bauch
committed
}
validatedCreatedNode, ok := createdNode.(ygot.ValidatedGoStruct)
if !ok {
Value: createdNode,
Type: (*ygot.ValidatedGoStruct)(nil),
}
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
}
// 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))
}
}()
ygot
validatedDeepCopy, validatedCreatedNode, err := generateEmptyValidatedStructs(schema, path)
validatedDeepCopyForDiff, validatedCreatedNodeForDiff, err := generateEmptyValidatedStructs(schema, path)
fmt.Println(validatedDeepCopyForDiff)
fmt.Println(validatedCreatedNodeForDiff)
if err := openconfig.Unmarshal(bytes, validatedCreatedNode, opt...); err != nil {
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.
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.
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)
p.state = plugin.FAULTY
return err
// Typecast the go plugins symbol to southbound.SouthboundInterface
sbi, ok := symbol.(southbound.SouthboundInterface)
if !ok {
p.state = plugin.FAULTY
Fabian Seidl
committed
return &customerrs.InvalidTypeAssertionError{
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")
return nil
// 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.
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(),
})
}