Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
networkElement.go 12.95 KiB
package model

import (
	"context"
	"encoding/json"

	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
	"code.fbi.h-da.de/danet/gosdn/controller/conflict"
	"github.com/docker/docker/pkg/namesgenerator"
	"github.com/google/uuid"
	"github.com/openconfig/ygot/ygot"
	log "github.com/sirupsen/logrus"
	"go.mongodb.org/mongo-driver/bson"
	"google.golang.org/protobuf/proto"

	gpb "github.com/openconfig/gnmi/proto/gnmi"
)

// NetworkElement represents an Managed Network Element (MNE) which is managed by
// nucleus.
type NetworkElement interface {
	ID() uuid.UUID
	GetModel() ([]byte, error)
	GetPlugin() PluginIface
	GetModelAsFilteredCopy() ([]byte, error)
	Transport() Transport
	Name() string
	ProcessResponse(proto.Message) error
	IsTransportValid() bool
	GetModelAsString() (string, error)
	TransportAddress() string
	GetMetadata() conflict.Metadata
	PndID() uuid.UUID
}

type LoadedNetworkElement struct {
	// ID represents the UUID of the LoadedNetworkElement.
	ID string `json:"id" bson:"_id"`
	// Name represents the name of the LoadedNetworkElement.
	Name string `json:"name,omitempty"`
	// TransportType represent the type of the transport in use of the LoadedNetworkElement.
	TransportType string `json:"transport_type,omitempty" bson:"transport_type,omitempty"`
	// TransportAddress represents the address from which the network element can be reached via the transport method.
	TransportAddress string `json:"transport_address,omitempty" bson:"transport_address,omitempty"`
	// TransportUsername is used for authentication via the transport method in use.
	TransportUsername string `json:"transport_username,omitempty" bson:"transport_username,omitempty"`
	// TransportPassword is used for authentication via the transport method in use.
	TransportPassword string `json:"transport_password,omitempty" bson:"transport_password,omitempty"`
	// Note: deprecated, should be removed.
	TransportOptionCsbi bool `json:"transport_option_csbi,omitempty" bson:"transport_option_csbi,omitempty"`
	// TransportTLS uses TLS from the client side when if true.
	TransportTLS bool `json:"transport_tls,omitempty" bson:"transport_tls,omitempty"`

	// SBI indicates the southbound interface, which is used by this network element as UUID.
	Plugin string `json:"plugin"`
	Model  string `json:"model,omitempty" bson:"model,omitempty"`

	Metadata conflict.Metadata `json:"metadata" bson:"metadata"`

	PndID string `json:"pnd_id" bson:"pnd_id"`
}

// NewNetworkElement creates a network element.
func NewNetworkElement(
	name string,
	uuidInput uuid.UUID,
	opt *tpb.TransportOption,
	pndID uuid.UUID,
	plugin PluginIface,
	metadata conflict.Metadata) (NetworkElement, error) {
	t, err := NewTransport(opt, plugin)
	if err != nil {
		return nil, err
	}

	// TODO: this needs to check the case that the uuidInput is set, as the
	// same uuid may be already stored.
	if uuidInput == uuid.Nil {
		uuidInput = uuid.New()
	}

	if name == "" {
		name = namesgenerator.GetRandomName(0)
	}

	// if opt.Type == spb.Type_TYPE_CONTAINERISED {
	// 	return &CsbiNetworkElement{
	// 		CommonNetworkElement: CommonNetworkElement{
	// 			UUID:             uuidInput,
	// 			Plugin:           plugin,
	// 			transport:        t,
	// 			name:             name,
	// 			transportOptions: opt,
	// 			Metadata:         metadata,
	// 			pndID:            pndID,
	// 		},
	// 	}, nil
	// }

	return &CommonNetworkElement{
		UUID:             uuidInput,
		Plugin:           plugin,
		transport:        t,
		name:             name,
		transportOptions: opt,
		pndID:            pndID,
		Metadata:         metadata,
	}, nil
}

// CommonNetworkElement represents an MNE.
type CommonNetworkElement struct {
	// UUID represents the Network Elements UUID
	UUID uuid.UUID

	// Plugin embeds a gosdn ygot plugin. Allows to work on a devices config
	// based its supported YANG models. The code for this is generated through
	// ygot.
	Plugin PluginIface

	// Transport is the network element's Transport implementation
	transport Transport

	// Name is the network element's human readable name
	name string

	transportOptions *tpb.TransportOption

	Metadata conflict.Metadata

	// ID of the PND this network element is associated with.
	pndID uuid.UUID
}

// ID returns the UUID of the Network Element.
func (n *CommonNetworkElement) ID() uuid.UUID {
	return n.UUID
}

// GetModel returns the ygot representation of the Network Element.
func (n *CommonNetworkElement) GetModel() ([]byte, error) {
	return n.Plugin.Model(false)
}

// GetModelAsFilteredCopy returns the ygot representation of the Network
// Element, but as copy with read-only fields removed.
func (n *CommonNetworkElement) GetModelAsFilteredCopy() ([]byte, error) {
	return n.Plugin.Model(true)
}

// Transport returns the Transport of the network element.
func (n *CommonNetworkElement) Transport() Transport {
	return n.transport
}

// Transport returns the Transport of the network element.
func (d *CommonNetworkElement) GetPlugin() PluginIface {
	return d.Plugin
}

// TransportAddress returns the TransportAddress of the network element.
func (n *CommonNetworkElement) TransportAddress() string {
	return n.transportOptions.Address
}

// Name returns the name of the network element.
func (n *CommonNetworkElement) Name() string {
	return n.name
}

// SetTransport sets the Network Element's Transport.
func (n *CommonNetworkElement) SetTransport(t Transport) {
	n.transport = t
}

// SetName sets the Network Element's name.
func (n *CommonNetworkElement) SetName(name string) {
	n.name = name
}

// ProcessResponse processes a response for the Network Element.
func (d *CommonNetworkElement) ProcessResponse(resp proto.Message) error {
	return d.transport.ProcessResponse(resp)
}

// IsTransportValid returns a boolean if the transport of a network element is valid.
func (n *CommonNetworkElement) IsTransportValid() bool {
	if n.transportOptions != nil && n.transportOptions.Address != "" {
		return true
	}

	return false
}

// GetMetadata returns the metadate of a network element.
func (n *CommonNetworkElement) GetMetadata() conflict.Metadata {
	return n.Metadata
}

// PndID returns the ID of the associated PND.
func (n *CommonNetworkElement) PndID() uuid.UUID {
	return n.pndID
}

// SetPnd sets the Network Element's PndId.
func (n *CommonNetworkElement) SetPnd(id uuid.UUID) {
	n.pndID = id
}

// CsbiNetworkElement is used for the cSBI functionality.
type CsbiNetworkElement struct {
	CommonNetworkElement
}

// ID returns the UUID of the Network Element.
func (n *CsbiNetworkElement) ID() uuid.UUID {
	return n.UUID
}

// GetModel returns the ygot representation of the Network Element.
func (d *CsbiNetworkElement) GetModel() ([]byte, error) {
	return d.Plugin.Model(false)
}

// GetModelAsFilteredCopy returns the ygot representation of the Network
// Element, but as copy with read-only fields removed.
func (n *CsbiNetworkElement) GetModelAsFilteredCopy() ([]byte, error) {
	return n.Plugin.Model(true)
}

// Transport returns the Transport of the network element.
func (n *CsbiNetworkElement) Transport() Transport {
	return n.transport
}

// Transport returns the Transport of the device.
func (d *CsbiNetworkElement) GetPlugin() PluginIface {
	return d.Plugin
}

// Name returns the name of the network element.
func (n *CsbiNetworkElement) Name() string {
	return n.name
}

// GetMetadata returns the metadate of a network element.
func (n *CsbiNetworkElement) GetMetadata() conflict.Metadata {
	return n.Metadata
}

// PndID returns the ID of the associated PND.
func (n *CsbiNetworkElement) PndID() uuid.UUID {
	return n.pndID
}

// ProcessResponse processes a response for the Network Element.
func (d *CsbiNetworkElement) ProcessResponse(resp proto.Message) error {
	// TODO: callback to send response to caller
	return d.transport.ProcessResponse(resp)
}

// IsTransportValid returns a boolean if the transport of a network element is valid.
func (n *CsbiNetworkElement) IsTransportValid() bool {
	if n.transportOptions != nil && n.transportOptions.Address != "" {
		return true
	}

	return false
}

// MarshalJSON implements the MarshalJSON interface to store a network element as JSON.
func (n *CommonNetworkElement) MarshalJSON() ([]byte, error) {
	var transportType string
	var transportAddress string
	var transportUsername string
	var transportPassword string
	var transportTLS bool

	// Handling of these cases is necessary as we use partial network elements for testing.
	// eg. in most tests no transport or sbi is defined.
	// The marshaller will crash if we want to access a nil field.
	if n.transport == nil || n.transportOptions == nil {
		transportType = "testing"
		transportAddress = "testing"
		transportUsername = "testing"
		transportPassword = "testing"
		transportTLS = false
	} else {
		transportType = n.transport.Type()
		transportAddress = n.transportOptions.Address
		transportUsername = n.transportOptions.Username
		transportPassword = n.transportOptions.Password
		transportTLS = n.transportOptions.Tls
	}

	pluginUUID := n.Plugin.ID()

	var pndUUID uuid.UUID

	if n.pndID == uuid.Nil {
		pndUUID = uuid.UUID{}
	} else {
		pndUUID = n.pndID
	}

	modelAsString, err := n.Plugin.Model(false)
	if err != nil {
		return []byte{}, err
	}

	return json.Marshal(&struct {
		ID                uuid.UUID `json:"id,omitempty"`
		Name              string    `json:"name,omitempty"`
		TransportType     string    `json:"transport_type,omitempty"`
		TransportAddress  string    `json:"transport_address,omitempty"`
		TransportUsername string    `json:"transport_username,omitempty"`
		TransportPassword string    `json:"transport_password,omitempty"`
		TransportTLS      bool      `json:"transport_tls"`
		Plugin            uuid.UUID `json:"plugin,omitempty"`
		Model             string    `json:"model,omitempty"`
		PndID             uuid.UUID `json:"pnd_id,omitempty"`
	}{
		ID:                n.ID(),
		Name:              n.Name(),
		TransportType:     transportType,
		TransportAddress:  transportAddress,
		TransportUsername: transportUsername,
		TransportPassword: transportPassword,
		TransportTLS:      transportTLS,
		Plugin:            pluginUUID,
		Model:             string(modelAsString),
		PndID:             pndUUID,
	})
}

// MarshalBSON implements the MarshalBSON interface to store a network element as BSON.
func (n *CommonNetworkElement) MarshalBSON() ([]byte, error) {
	var transportType string
	var transportAddress string
	var transportUsername string
	var transportPassword string
	var transportTLS bool

	// Handling of these cases is necessary as we use partial network elements for testing.
	// eg. in most tests no transport or sbi is defined.
	// The marshaller will crash if we want to access a nil field.
	if n.transport == nil || n.transportOptions == nil {
		transportType = "testing"
		transportAddress = "testing"
		transportUsername = "testing"
		transportPassword = "testing"
		transportTLS = false
	} else {
		transportType = n.transport.Type()
		transportAddress = n.transportOptions.Address
		transportUsername = n.transportOptions.Username
		transportPassword = n.transportOptions.Password
		transportTLS = n.transportOptions.Tls
	}

	pluginUUID := n.Plugin.ID()

	modelAsString, err := n.GetModelAsString()
	if err != nil {
		return []byte{}, err
	}

	return bson.Marshal(&struct {
		ID                string `bson:"_id,omitempty"`
		Name              string `bson:"name,omitempty"`
		TransportType     string `bson:"transport_type,omitempty"`
		TransportAddress  string `bson:"transport_address,omitempty"`
		TransportUsername string `bson:"transport_username,omitempty"`
		TransportPassword string `bson:"transport_password,omitempty"`
		TransportTLS      bool   `bson:"transport_tls"`
		Plugin            string `bson:"plugin,omitempty"`
		Model             string `bson:"model,omitempty"`
		PndID             string `bson:"pnd_id,omitempty"`
	}{
		ID:                n.ID().String(),
		Name:              n.Name(),
		TransportType:     transportType,
		TransportAddress:  transportAddress,
		TransportUsername: transportUsername,
		TransportPassword: transportPassword,
		Plugin:            pluginUUID.String(),
		Model:             modelAsString,
		PndID:             n.pndID.String(),
		TransportTLS:      transportTLS,
	})
}

// GetModelAsString returns the YANG model of a network element as string.
func (d *CommonNetworkElement) GetModelAsString() (string, error) {
	byteModel, err := d.Plugin.Model(false)
	return string(byteModel), err
}

// EnsureIntendedConfigurationIsAppliedOnNetworkElement pushes the stored
// configuration to a network element.
// TODO: find a better place for this function.
func EnsureIntendedConfigurationIsAppliedOnNetworkElement(mne NetworkElement) error {
	model, err := mne.GetModelAsFilteredCopy()
	if err != nil {
		return err
	}

	req := &gpb.SetRequest{}
	path, err := ygot.StringToStructuredPath("/")
	if err != nil {
		return err
	}

	req.Update = []*gpb.Update{{
		Path: path,
		Val: &gpb.TypedValue{
			Value: &gpb.TypedValue_JsonIetfVal{JsonIetfVal: []byte(model)},
		},
	}}
	response, err := mne.Transport().CustomSet(context.Background(), req)
	if err != nil {
		log.Errorf("Failed to apply model of network element err=%+v, response=%+v", err, response)
		return err
	}

	return nil
}