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, 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. schema, err := sbi.Schema() if err != nil { return nil, err } root, err := ygot.DeepCopy(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, 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 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 } // GetModelAsFilteredCopy returns the ygot representation of the Network // Element, but as copy with read-only fields removed. func (n *CommonNetworkElement) GetModelAsFilteredCopy() (ygot.GoStruct, error) { filteredModelCopy, err := createFilteredCopy(n) if err != nil { return nil, err } return filteredModelCopy, nil } // SetModel sets the ygot representation of the Network Element. func (n *CommonNetworkElement) SetModel(model ygot.GoStruct) { n.Model = 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 { schema, err := n.sbi.Schema() if err != nil { return err } return n.transport.ProcessResponse(resp, n.Model, 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 } // GetModelAsFilteredCopy returns the ygot representation of the Network // Element, but as copy with read-only fields removed. func (n *CsbiNetworkElement) GetModelAsFilteredCopy() (ygot.GoStruct, error) { filteredModelCopy, err := createFilteredCopy(n) if err != nil { return nil, err } return filteredModelCopy, nil } // SetModel returns the ygot representation of the Network Element. func (n *CsbiNetworkElement) SetModel(model ygot.GoStruct) { n.Model = 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 schema, err := n.SBI().Schema() if err != nil { return err } return n.transport.ProcessResponse(resp, n.Model, 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 } func createFilteredCopy(n networkelement.NetworkElement) (ygot.GoStruct, error) { modelCopy, err := n.CreateModelCopy() if err != nil { return nil, err } schema, err := n.SBI().Schema() if err != nil { return nil, err } //remove read-only fields err = ygot.PruneConfigFalse(schema.RootSchema(), modelCopy) if err != nil { return nil, err } return modelCopy, 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() } 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"` }{ ID: n.ID(), Name: n.Name(), TransportType: transportType, TransportAddress: transportAddress, TransportUsername: transportUsername, TransportPassword: transportPassword, TransportOptionType: transportOptionType, SBI: sbiUUID, Model: modelAsString, }) } // 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"` }{ ID: n.ID().String(), Name: n.Name(), TransportType: transportType, TransportAddress: transportAddress, TransportUsername: transportUsername, TransportPassword: transportPassword, TransportOptionType: transportOptionType, SBI: n.sbi.ID().String(), Model: modelAsString, }) } // 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, }} }