Newer
Older
package venv
import (
S.H.
committed
"context"
"encoding/json"
"strconv"
"strings"
"sync"
"time"
configPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement"
"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/conflict"
S.H.
committed
"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
topoPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
"code.fbi.h-da.de/danet/gosdn/application-framework/event"
"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/benchmark"
clab "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
clabconfig "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/gosdnutil"
rtdt_auth "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth"
"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/sdnconfig"
"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
gnmitargetygot "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/yang"
uuid "github.com/google/uuid"
S.H.
committed
gnmi "github.com/openconfig/gnmi/proto/gnmi"
"github.com/openconfig/ygot/ygot"
"github.com/openconfig/ygot/ytypes"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type VEnv struct {
S.H.
committed
Name string
auth *rtdt_auth.RtdtAuth
conn *grpc.ClientConn // The connection to this specific environment's gosdn
pnd *pnd.PrincipalNetworkDomain
clabData *clabconfig.ClabConfig // Represents yaml file that was used to deploy
sdnConfig *sdnconfig.SdnConfig // Represents json config file for configuration grpc interface
clabFilename string // This is the name of the yaml file clabData is based on
S.H.
committed
StopChan <-chan struct{}
waitGroup *sync.WaitGroup
containerRegistryURL string
SavedEvents []*event.Event
// This is an ugly way of temporarily ignoring events to avoid e.g. infinite sync loops:
SyncBack bool
EventSystemStarted bool
}
// Accepts a yaml filename to deploy a container lab environment
// TODO Split up into sub-functions
// This takes FULL path name to clab config
func NewVEnv(name, clabFilename, user, pass string, wg *sync.WaitGroup, sdnConfig *sdnconfig.SdnConfig) *VEnv {
S.H.
committed
fmt.Printf("[%s] - Creating new virtual environment\n", name)
wg.Add(1) // Register the venv and run atleast until it calls wg.Done()
var err error
//clabFilename := name + "-clab.yaml"
// TODO:
// Use baseClab and sdnConfig to construct a config file for containerlab
if err = clab.ClabDeploy(clabFilename); err != nil {
fmt.Printf("[%s] - Failed to deploy the network\n", name)
return nil
}
fmt.Println("Successfully deployed containerlab environment")
// After having deployed it, load the config into clabData
var clabData *clab.ClabConfig
clabData, err = clab.LoadConfig(clabFilename)
if err != nil {
fmt.Printf("[%s] - Failed to load config from yaml file: %v\n", name, err)
return nil
}
// get gosdn address inside clab environment
var gosdnAddress string
for nodename, val := range clabData.Topology.Nodes {
if strings.HasPrefix(nodename, "gosdn") {
gosdnAddress = val.MgmtIPv4 + ":55055"
S.H.
committed
fmt.Printf("[%s} - Found gosdn ipv4: %s\n", name, gosdnAddress)
fmt.Printf("[%s] - Sleep for 15 seconds to give containers time to settle..\n", name)
time.Sleep(time.Second * 15)
// Now log into gosdn physical network
dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())
S.H.
committed
gosdnconn, err := grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions())
if err != nil {
S.H.
committed
fmt.Printf("[%s] - Failed to create connection to gosdn\n", name)
return nil
} else {
S.H.
committed
fmt.Printf("[%s] - Successfully created connection to gosdn\n", name)
S.H.
committed
gosdnauth := rtdt_auth.NewRtdtAuth(user, gosdnAddress, pass, gosdnconn) // logs in and stores token
if gosdnauth == nil {
fmt.Printf("[%s] - Couldn't log in to gosdn, quitting!\n", name)
return nil
} else {
S.H.
committed
fmt.Printf("[%s] - Successfully logged into gosdn as user: %s, with password: %s, session token: %v\n", name, gosdnauth.GetUsername(), gosdnauth.GetPassword(), gosdnauth.GetSessionToken())
}
// Get PND of gosdn in created venv
S.H.
committed
var gosdn_pnd *pnd.PrincipalNetworkDomain
for {
gosdn_pnd, err = gosdnutil.FetchPnd(gosdnconn, gosdnauth)
if err == nil {
break
}
fmt.Printf("[%s] - Couldn't retrieve PND, retrying in 2 seconds..\n", name)
time.Sleep(time.Second * 2)
// load topo into DB via API
return &VEnv{
auth: gosdnauth,
pnd: gosdn_pnd,
conn: gosdnconn,
clabData: clabData,
clabFilename: clabFilename,
waitGroup: wg,
sdnConfig: sdnConfig,
containerRegistryURL: "registry.code.fbi.h-da.de/danet/gnmi-target/debian:interface-enabled-test", // TODO: Could let user choose
SyncBack: true,
}
}
// Upload a sdnconfig .json file to the DB
func (v *VEnv) ApplyConfiguration(sdnConfig *sdnconfig.SdnConfig) error {
v.sdnConfig = sdnConfig
sdnConfigParsed, err := json.Marshal(v.sdnConfig)
if err != nil {
return fmt.Errorf("Parsing Error in ApplyConfiguration(): %w", err)
}
configService := configPb.NewConfigurationManagementServiceClient(v.auth.GetConn())
request := &configPb.ImportSDNConfigRequest{
Timestamp: util.Now(),
Pid: v.pnd.Id,
SdnConfigData: string(sdnConfigParsed),
}
response, err := configService.ImportSDNConfig(v.auth.CreateContextWithAuthorization(), request)
if err != nil {
return fmt.Errorf("Response Error in ApplyConfiguration(): %w", err)
}
fmt.Println("Configuration Response:", response.String())
return nil
}
// Based on saved sdnconfig, create devices
func (v *VEnv) CreateDevices() error {
// Alternative (better) approach
fmt.Println("Creating devices now!")
if v.sdnConfig == nil {
return fmt.Errorf("Can't create devices without sdnConfig being set")
}
for _, mne := range v.sdnConfig.NetworkElements {
fmt.Printf("[%s] - Found mne target: %s with addr: %s\n", v.Name, mne.Name, mne.TransportAddress)
opt := &tpb.TransportOption{
Address: mne.TransportAddress,
Username: "admin",
Password: "admin",
Tls: true,
TransportOption: &tpb.TransportOption_GnmiTransportOption{
GnmiTransportOption: &tpb.GnmiTransportOption{},
},
S.H.
committed
}
pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically
pndID, _ := uuid.Parse(v.pnd.Id)
listResponse, err := gosdnutil.AddNetworkElement(v.auth, v.auth.GetAddress(), mne.Name, mne.ID, opt, pluginID, pndID, []string{"/"})
if err != nil {
return fmt.Errorf("[%s] - Failed to add network element: %w\n", v.Name, err)
}
fmt.Printf("[%s] - Got response from AddNetworkElement: %v\n", v.Name, listResponse)
fmt.Printf("[%s] - Success: registered mne with gosdn controller\n", v.Name)
S.H.
committed
}
return nil
S.H.
committed
}
// Source: "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/venv-manager/venv-manager.go"
// commit: 0264b698286b6cbb965d743078c681f8af55edf6
func (v *VEnv) loadNetworkElementModelPathsIntoGosdn(ctx context.Context, conn *grpc.ClientConn, nodes *[]*sdnconfig.Node) error {
S.H.
committed
networkElementService := networkelement.NewNetworkElementServiceClient(conn)
paths := [2]string{"/lldp/config/system-description", "/system/state/"}
for _, path := range paths {
for _, node := range *nodes {
_, err := networkElementService.GetPath(ctx, &networkelement.GetPathRequest{Mneid: node.ID, Pid: v.pnd.Id, Path: path})
if err != nil {
return err
}
}
}
S.H.
committed
return nil
}
func (v *VEnv) ApplyRoutes() error {
conn := v.auth.GetConn()
hostIP := int64(1)
for _, link := range v.sdnConfig.Links {
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
req := topoPb.AddRoutingTableRequest{
Timestamp: util.Now(),
RoutingTable: &topoPb.RoutingTable{
Id: uuid.NewString(),
Routes: []*topoPb.Route{
{
Id: uuid.NewString(),
TargetIPRange: "16",
NextHopIP: "192.168.178." + strconv.FormatInt(hostIP, 10),
PortID: link.SourcePort.ID,
},
},
},
}
hostIP++
routeService := topoPb.NewRoutingTableServiceClient(conn)
ctx := v.auth.CreateContextWithAuthorization()
reply, err := routeService.AddRoutingTable(ctx, &req)
if err != nil {
return fmt.Errorf("AddRoutingTable failed: %v\n", err)
}
fmt.Println("Successfully added Link:", reply)
}
return nil
}
// TODO: Fix hardcoded IP addresses, Not used
func (v *VEnv) UploadTopology() error {
conn := v.auth.GetConn()
topoService := topoPb.NewTopologyServiceClient(conn)
for _, link := range v.sdnConfig.Links {
req := &topoPb.AddLinkRequest{
Timestamp: util.Now(),
Link: &topoPb.Link{
Id: link.ID,
Name: link.Name,
SourceNode: link.SourceNode.Convert(),
SourcePort: link.SourcePort.Convert(),
TargetNode: link.TargetNode.Convert(),
TargetPort: link.TargetPort.Convert(),
Metadata: &conflict.Metadata{},
},
//fmt.Println("AddLink is:", req.String())
ctx := v.auth.CreateContextWithAuthorization()
topoResponse, err := topoService.AddLink(ctx, req)
if err != nil {
return err
}
fmt.Printf("Successfully uploaded topo link to DB: %s\n", topoResponse.String())
}
return nil
}
func getTypedValue(value, ytype string) (*gnmi.TypedValue, error) {
// Special case for interface up/down events
if value == "DOWN" {
value = "false"
} else if value == "UP" {
value = "true"
}
fmt.Printf("getTypedValue: value=%s, ytype=%s\n", value, ytype)
switch ytype {
case "bool":
{
boolVal, err := strconv.ParseBool(value)
if err == nil {
return &gnmi.TypedValue{
Value: &gnmi.TypedValue_BoolVal{BoolVal: boolVal},
}, nil
} else {
return nil, err
}
}
case "uint16", "uint32", "uint64":
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
{
uintVal, err := strconv.ParseUint(value, 10, 64)
if err == nil {
return &gnmi.TypedValue{
Value: &gnmi.TypedValue_UintVal{UintVal: uintVal},
}, nil
} else {
return nil, err
}
}
case "string":
{
return &gnmi.TypedValue{
Value: &gnmi.TypedValue_StringVal{StringVal: value},
}, nil
}
case "int":
{
intVal, err := strconv.ParseInt(value, 10, 64)
if err == nil {
return &gnmi.TypedValue{
Value: &gnmi.TypedValue_IntVal{IntVal: intVal},
}, nil
} else {
return nil, err
}
}
default:
return nil, fmt.Errorf("Unknown type")
}
}
func (v *VEnv) SetGnmiPath(path, value, mneid string, save bool) error {
S.H.
committed
fmt.Println("--IN SETGNMIPATH-----------------------")
ctx := v.auth.CreateContextWithAuthorization()
S.H.
committed
mneService := networkelement.NewNetworkElementServiceClient(v.conn)
gnmiPath, err := ygot.StringToStructuredPath(path)
if err != nil {
return fmt.Errorf("Encountered error while trying to parse string path into gnmi path: %w", err)
}
//ytypes.GetOrCreateNode(gnmitargetygot.Schema(), ,gnmiPath)
schema, nil := gnmitargetygot.Schema()
rootSchema := schema.RootSchema()
_, entry, err := ytypes.GetOrCreateNode(rootSchema, &gnmitargetygot.Gnmitarget{}, gnmiPath)
if err != nil {
return fmt.Errorf("SetGnnmiPath Error: %w", err)
}
yangType := entry.Type
gnmiVal, err := getTypedValue(value, yangType.Kind.String())
if err != nil {
fmt.Println("The given type is not supported yet!")
}
fmt.Println("gnmiVal:", gnmiVal)
changeRequest := &networkelement.ChangeRequest{
S.H.
committed
Mneid: mneid,
Path: gnmiPath,
Value: gnmiVal,
S.H.
committed
ApiOp: networkelement.ApiOperation_API_OPERATION_UPDATE,
S.H.
committed
changeRequests := []*networkelement.ChangeRequest{changeRequest}
S.H.
committed
pid := v.pnd.Id
if save {
benchmark.Current.SendChangeRequest = time.Now()
}
setPathListReq := &networkelement.SetPathListRequest{
S.H.
committed
Timestamp: util.Now(),
Pid: pid,
ChangeRequest: changeRequests,
}
setPathResponse, err := mneService.SetPathList(ctx, setPathListReq)
if save {
benchmark.Current.ChangeRequestEnd = time.Now()
}
S.H.
committed
if err != nil {
fmt.Printf("Error: %v\n", err)
return err
S.H.
committed
}
fmt.Println("setPathResponse: ", setPathResponse.String())
// Now try to commit the change
fmt.Println("Now trying to commit change with ID:", setPathResponse.GetResponses()[0].GetId())
setChange := &networkelement.SetChange{
Cuid: setPathResponse.GetResponses()[0].GetId(),
Op: networkelement.Operation_OPERATION_COMMIT,
}
setChangeListRequest := networkelement.SetChangeListRequest{
Change: []*networkelement.SetChange{setChange},
Timestamp: util.Now(),
Pid: pid,
}
clResponse, err := mneService.SetChangeList(ctx, &setChangeListRequest)
if save {
benchmark.Current.CommitEnd = time.Now()
}
if err != nil {
fmt.Println("Error, failed to commit changes:", err)
return err
} else {
fmt.Println("Successfully applied changes:", clResponse)
if save {
benchmark.Current.CommitEnd = time.Now()
}
}
if save {
benchmark.Current.SendChangeRequest = time.Unix(0, setPathListReq.Timestamp) // send req in realnet
benchmark.Current.ReceiveChangeRequest = time.Unix(0, setPathResponse.Timestamp) // rcv in twin
benchmark.Current.SendCommitRequest = time.Unix(0, setChangeListRequest.Timestamp)
benchmark.Current.ReceiveCommitResponse = time.Unix(0, clResponse.Timestamp)
benchmark.Current.PropagationDelay = benchmark.Current.StartTimeRealnet.Sub(benchmark.Current.EndTime)
fmt.Println("---Measurement finished---")
benchmark.Current.Print()
S.H.
committed
return nil
// For later, topology stuff
// func (rMan *RtdtManager) applyTopology() error {
// topoService := topoPb.NewTopologyServiceClient(rMan.conn)
// ctx := rMan.auth.CreateContextWithAuthorization()
// topoService.AddLink(ctx, &topoPb.AddLinkRequest{Timestamp: util.Now()})
//
// return nil
// }
S.H.
committed
// This retrieves the topology from the running gosdn instance
// This is needed to generate the clab file to be used with the virtual net
func (v *VEnv) fetchTopology() error {
topoService := topoPb.NewTopologyServiceClient(v.conn)
ctx := v.auth.CreateContextWithAuthorization()
topoResponse, err := topoService.GetTopology(ctx, &topoPb.GetTopologyRequest{Timestamp: util.Now()})
if err != nil {
return fmt.Errorf("Failed to retrieve Topology: %w", err)
}
fmt.Printf("Successfully read topology, state is: %s\n", topoResponse.String())
fmt.Printf("Length of Links: %d\n", len(topoResponse.Toplogy.Links))
fmt.Printf("String of Topology: %s\n", topoResponse.Toplogy.String())
for i, link := range topoResponse.Toplogy.GetLinks() {
fmt.Printf("index %d, linkID: %s, linkName: %s\n", i, link.GetId(), link.GetName())
}
// TODO: Needs to set topo
return nil
}
func (v *VEnv) FindQueueAddress() (string, error) {
var queueAddress string
for nodename, node := range v.GetClabData().Topology.Nodes {
if nodename == "rabbitmq" {
// This is hardcoded for now, for later this could use credentials passed via CLI
queueAddress = "amqp://guest:guest@" + node.MgmtIPv4 + ":5672/"
return queueAddress, nil
}
}
return "", fmt.Errorf("Couldn't retrieve queue address!")
}
// {G,S}ETTERS
func (v VEnv) GetName() string {
return v.Name
}
func (v VEnv) GetClabData() *clab.ClabConfig {
return v.clabData
}
func (v VEnv) GetClabFilePath() string {
return v.clabFilename
}
func (v *VEnv) GetConn() *grpc.ClientConn {
return v.conn
}
func (v VEnv) GetPnd() *pnd.PrincipalNetworkDomain {
return v.pnd
}
func (v VEnv) GetAuth() *rtdt_auth.RtdtAuth {
return v.auth
}
func (v *VEnv) GetWaitgroup() *sync.WaitGroup {
return v.waitGroup
}
func (v *VEnv) GetSdnConfig() *sdnconfig.SdnConfig {
return v.sdnConfig
}
func (v *VEnv) GetSavedChanges() []*event.Event {
return v.SavedEvents