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

import (
	"encoding/json"

	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
	"code.fbi.h-da.de/danet/gosdn/controller/conflict"
	"code.fbi.h-da.de/danet/gosdn/controller/customerrs"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/transport"
	"github.com/docker/docker/pkg/namesgenerator"
	"github.com/google/uuid"
	"github.com/openconfig/ygot/ygot"
	"go.mongodb.org/mongo-driver/bson"
	"google.golang.org/protobuf/proto"
)

// NewNetworkElement creates a network element.
func NewNetworkElement(
	name string,
	uuidInput uuid.UUID,
	opt *tpb.TransportOption,
	sbi southbound.SouthboundInterface,
	pndID uuid.UUID,
	metadata conflict.Metadata,
) (networkelement.NetworkElement, error) {
	t, err := NewTransport(opt, sbi)
	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)
	}

	// We want a representation of the MNE's config (the SBI-schema's root created through ygot),
	// but do not want to work on the sbi.Schema() directly.
	// So the root of sbi.Schema() is never changed when a set or get on a network element will be called.
	root, err := ygot.DeepCopy(sbi.Schema().Root)
	if err != nil {
		return nil, err
	}
	ygotDeepCopy, ok := root.(ygot.GoStruct)
	if !ok {
		return nil, &customerrs.InvalidTypeAssertionError{
			Value: root,
			Type:  (*ygot.ValidatedGoStruct)(nil),
		}
	}

	if opt.Type == spb.Type_TYPE_CONTAINERISED {
		return &CsbiNetworkElement{
			CommonNetworkElement: CommonNetworkElement{
				UUID:             uuidInput,
				Model:            ygotDeepCopy,
				sbi:              sbi,
				transport:        t,
				name:             name,
				transportOptions: opt,
				Metadata:         metadata,
			},
		}, nil
	}

	return &CommonNetworkElement{
		UUID:             uuidInput,
		Model:            ygotDeepCopy,
		sbi:              sbi,
		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

	// Network Element embeds a ygot.GoStruct containing the network element details
	Model ygot.GoStruct

	// SBI is the network element's southbound interface implementation
	sbi southbound.SouthboundInterface

	// Transport is the network element's Transport implementation
	transport 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() ygot.GoStruct {
	return n.Model
}

// CreateModelCopy returns a copy of the ygot representation of the Network Element.
func (n *CommonNetworkElement) CreateModelCopy() (ygot.ValidatedGoStruct, error) {
	return createValidatedCopy(n)
}

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

// 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
}

// SBI returns the sbi of the Network Element.
func (n *CommonNetworkElement) SBI() southbound.SouthboundInterface {
	return n.sbi
}

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

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

// SetSBI sets the Network Element's SBI.
func (n *CommonNetworkElement) SetSBI(sbi southbound.SouthboundInterface) {
	n.sbi = sbi
}

// ProcessResponse processes a response for the Network Element.
func (n *CommonNetworkElement) ProcessResponse(resp proto.Message) error {
	return n.transport.ProcessResponse(resp, n.Model, n.sbi.Schema())
}

// 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
}

// 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 (n *CsbiNetworkElement) GetModel() ygot.GoStruct {
	return n.Model
}

// CreateModelCopy returns a copy of the ygot representation of the Network Element.
func (n *CsbiNetworkElement) CreateModelCopy() (ygot.ValidatedGoStruct, error) {
	return createValidatedCopy(n)
}

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

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

// SBI returns the sbi of the Network Element.
func (n *CsbiNetworkElement) SBI() southbound.SouthboundInterface {
	return n.sbi
}

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

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

func createValidatedCopy(n networkelement.NetworkElement) (ygot.ValidatedGoStruct, error) {
	cpy, err := ygot.DeepCopy(n.GetModel())
	ygot.BuildEmptyTree(cpy)
	if err != nil {
		return nil, err
	}

	validatedCpy, ok := cpy.(ygot.ValidatedGoStruct)
	if !ok {
		return nil, customerrs.InvalidTypeAssertionError{
			Value: validatedCpy,
			Type:  (*ygot.ValidatedGoStruct)(nil),
		}
	}

	return validatedCpy, nil
}

// 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 transportOptionType spb.Type

	// 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"
		transportOptionType = spb.Type_TYPE_OPENCONFIG
	} else {
		transportType = n.transport.Type()
		transportAddress = n.transportOptions.Address
		transportUsername = n.transportOptions.Username
		transportPassword = n.transportOptions.Password
		transportOptionType = n.transportOptions.Type
	}

	var sbiUUID uuid.UUID

	if n.sbi == nil {
		sbiUUID = uuid.UUID{}
	} else {
		sbiUUID = n.sbi.ID()
	}

	var pndUUID uuid.UUID

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

	modelAsString, err := ygot.EmitJSON(n.Model, n.getYgotEmitJSONConfig())
	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"`
		TransportOptionType spb.Type  `json:"transport_option"`
		SBI                 uuid.UUID `json:"sbi,omitempty"`
		Model               string    `bson:"model,omitempty"`
		PndID               uuid.UUID `json:"pnd_id,omitempty"`
	}{
		ID:                  n.ID(),
		Name:                n.Name(),
		TransportType:       transportType,
		TransportAddress:    transportAddress,
		TransportUsername:   transportUsername,
		TransportPassword:   transportPassword,
		TransportOptionType: transportOptionType,
		SBI:                 sbiUUID,
		Model:               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 transportOptionType spb.Type

	// 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"
		transportOptionType = spb.Type_TYPE_OPENCONFIG
	} else {
		transportType = n.transport.Type()
		transportAddress = n.transportOptions.Address
		transportUsername = n.transportOptions.Username
		transportPassword = n.transportOptions.Password
		transportOptionType = n.transportOptions.Type
	}

	modelAsString, err := ygot.EmitJSON(n.Model, n.getYgotEmitJSONConfig())
	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"`
		TransportOptionType spb.Type `bson:"transport_option"`
		SBI                 string   `bson:"sbi,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,
		TransportOptionType: transportOptionType,
		SBI:                 n.sbi.ID().String(),
		Model:               modelAsString,
		PndID:               n.PndID.String(),
	})
}

// GetModelAsString returns the YANG model of a network element as string.
func (n *CommonNetworkElement) GetModelAsString() (string, error) {
	modelAsString, err := ygot.EmitJSON(n.Model, n.getYgotEmitJSONConfig())
	if err != nil {
		return "", err
	}

	return modelAsString, nil
}

func (n *CommonNetworkElement) getYgotEmitJSONConfig() *ygot.EmitJSONConfig {
	return &ygot.EmitJSONConfig{
		Format:         ygot.RFC7951,
		Indent:         "",
		SkipValidation: true,
		RFC7951Config: &ygot.RFC7951JSONConfig{
			AppendModuleName: true,
		}}
}