diff --git a/applications/rtdt-manager/clab-config/clab-config.go b/applications/rtdt-manager/clab-config/clab-config.go index 4b58257152d1cf89647f4c9e008b6ad38cf44cb2..e134e21a362b08e0ebf34b9a9c6aeef6f9099030 100644 --- a/applications/rtdt-manager/clab-config/clab-config.go +++ b/applications/rtdt-manager/clab-config/clab-config.go @@ -227,3 +227,13 @@ func ClabDeploy(fullPath string) error { return fmt.Errorf("Deployment interrupted by signal") } } + +func (c *ClabConfig) GetNodeByName(name string) *Node { + for nodename, node := range c.Topology.Nodes { + if nodename == name { + return &node + } + } + fmt.Printf("Couldn't find a node with name %s!\n", name) + return nil +} diff --git a/applications/rtdt-manager/main.go b/applications/rtdt-manager/main.go index 8d7f92846e7e42065bc84c6f1631b389ee1e1840..4d46a3e41c4daccb48316801fac5e19b22e519e5 100644 --- a/applications/rtdt-manager/main.go +++ b/applications/rtdt-manager/main.go @@ -14,6 +14,7 @@ import ( func main() { + var err error // Global stop channel, should be passed to all venvs and App stopChan := make(chan os.Signal, 1) signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM) @@ -56,15 +57,25 @@ func main() { } else { fmt.Println("Successfully deployed physical network") } + err = realnet.DeriveTopologyFromClabData() + if err != nil { + fmt.Printf("Error occured while trying to construct topology in realnet: %v\n", err) + return + } + err = realnet.UploadTopology() + if err != nil { + fmt.Printf("Error occured while trying to upload realnet topology to DB: %v\n", err) + return + } // Register realnet with rtdt-manager rtdtMan := RtdtMan.NewRtdtManager(realnet, &wg, &stopChan) if rtdtMan == nil { fmt.Println("Couldn't initialize rtdt-manager, quitting!") return } - err := rtdtMan.InitEventSystem() + err = rtdtMan.InitEventSystem() if err != nil { - fmt.Printf("Error occured while initializing event system: %v", err) + fmt.Printf("Error occured while initializing event system: %v\n", err) return } // Do performance tests of realnet before starting twin diff --git a/applications/rtdt-manager/rtdt-topology/rtdt-topology.go b/applications/rtdt-manager/rtdt-topology/rtdt-topology.go index 933e58d3bb18ac8017c77158a7444fcee6c45806..59458bae3c1a433bc43dcc9aec6bea1ac26c4156 100644 --- a/applications/rtdt-manager/rtdt-topology/rtdt-topology.go +++ b/applications/rtdt-manager/rtdt-topology/rtdt-topology.go @@ -1,6 +1,7 @@ package rtdt_topology import ( + topoPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology" "code.fbi.h-da.de/danet/gosdn/models/generated/openconfig" "regexp" ) @@ -33,9 +34,9 @@ type Node struct { } type Topology struct { - Links []Link - Ports []Port - Nodes []Node + Links []*Link + Ports []*Port + Nodes []*Node } func NewTopology() *Topology { @@ -45,7 +46,16 @@ func NewTopology() *Topology { func (t *Topology) GetNodeByUUID(UUID string) *Node { for _, node := range t.Nodes { if node.ID == UUID { - return &node + return node + } + } + return nil +} + +func (t *Topology) GetNodeByName(name string) *Node { + for _, node := range t.Nodes { + if node.Name == name { + return node } } return nil @@ -92,3 +102,29 @@ func (n *Node) FillAllFields(containerRegistryURL string) { n.Kind = "ceos" n.Image = containerRegistryURL + n.Kind + ":" + dockerTag } + +// Converter functions to convert between this and topoPb version of topology +func (n *Node) Convert() *topoPb.Node { + return &topoPb.Node{ + Id: n.ID, + Name: n.Name, + } +} +func (p *Port) Convert() *topoPb.Port { + return &topoPb.Port{ + Id: p.ID, + Name: p.Name, + } +} +func (l *Link) Convert() *topoPb.Link { + return &topoPb.Link{ + Id: l.ID, + Name: l.Name, + SourceNode: l.SourceNode.Convert(), + SourcePort: l.SourcePort.Convert(), + TargetNode: l.TargetNode.Convert(), + TargetPort: l.TargetPort.Convert(), + + } +} + diff --git a/applications/rtdt-manager/venv/venv.go b/applications/rtdt-manager/venv/venv.go index d55be3d38e9b8bb58a6febda2a53220ab57bd01b..3a22bdda0a99f6e51a6b8b4326673b8e499bf83a 100644 --- a/applications/rtdt-manager/venv/venv.go +++ b/applications/rtdt-manager/venv/venv.go @@ -36,12 +36,13 @@ type VEnv struct { clabFilename string // This is the name of the yaml file clabData is based on StopChan <-chan struct{} waitGroup *sync.WaitGroup - topology rtdt_topology.Topology + topology *rtdt_topology.Topology containerRegistryURL string savedChanges []*networkelement.ChangeRequest } // Accepts a yaml filename to deploy a container lab environment +// TODO Split up into sub-functions func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { fmt.Printf("[%s] - Creating new virtual environment\n", name) wg.Add(1) // Register the venv and run atleast until it calls wg.Done() @@ -58,7 +59,6 @@ func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { fmt.Printf("[%s] - Failed to load config from yaml file\n", name) return nil } - // get gosdn address inside clab environment var gosdnAddress string for nodename, val := range clabData.Topology.Nodes { @@ -97,22 +97,26 @@ func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { time.Sleep(time.Second * 2) } - // Sleep before adding devices because otherwise it won't work - // Apply the topoYamlFile: we need to register the switches with the controller - var topo rtdt_topology.Topology - var topoNodes []rtdt_topology.Node - for node, val := range clabData.Topology.Nodes { - var topoNode rtdt_topology.Node - topoNode.Name = node - topoNode.MgmtIpv4 = val.MgmtIPv4 - topoNode.Image = val.Image - topoNode.Kind = val.Kind - - if strings.HasPrefix(node, "gnmi-target-") { - fmt.Printf("[%s] - Creating Network Element for node: %s\n", name, node) - ports := strings.Split(val.Ports[0], ":") - port := ports[1] - addr := val.MgmtIPv4 + ":" + port + // load topo into DB via API + return &VEnv{ + auth: gosdnauth, + pnd: gosdn_pnd, + conn: gosdnconn, + clabData: clabData, + clabFilename: topoYamlFile, + waitGroup: wg, + topology: nil, // set this later + containerRegistryURL: "registry.code.fbi.h-da.de/danet/gnmi-target/", // TODO: Could let user choose + } +} + +func (v *VEnv) CreateDevices() error { + for _, node := range v.topology.Nodes { + if strings.HasPrefix(node.Name, "gnmi-target-") { + fmt.Printf("[%s] - Creating Network Element for node: %s\n", v.Name, node.Name) + //ports := strings.Split(node.Ports[0], ":") + //port := ports[1] + addr := node.MgmtIpv4 + ":7030" // gnmi targets will always listen on 7030 opt := &tpb.TransportOption{ Address: addr, Username: "admin", @@ -124,40 +128,29 @@ func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { } // Openconfig pluginid? TODO decide pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically - pndID, _ := uuid.Parse(gosdn_pnd.GetId()) + pndID, _ := uuid.Parse(v.pnd.Id) - fmt.Printf("[%s] - Found target: %s with addr: %s\n", name, node, addr) - fmt.Printf("[%s] - Gosdn controller at %s\n", name, gosdnAddress) - listResponse, err := gosdnutil.AddNetworkElement(gosdnauth, gosdnAddress, node, "", opt, pluginID, pndID, []string{"/"}) + fmt.Printf("[%s] - Found target: %s with addr: %s\n", v.Name, node.Name, addr) + fmt.Printf("[%s] - Gosdn controller at %s\n", v.Name, v.auth.GetAddress()) + listResponse, err := gosdnutil.AddNetworkElement(v.auth, v.auth.GetAddress(), node.Name, node.ID, opt, pluginID, pndID, []string{"/"}) if err != nil { - fmt.Printf("[%s] - Failed to add network elements: %v\n", name, err) + fmt.Printf("[%s] - Failed to add network elements: %v\n", v.Name, err) return nil } else { - fmt.Printf("[%s] - Successfully created network element\n", name) + fmt.Printf("[%s] - Successfully created network element\n", v.Name) } - fmt.Printf("[%s] - Got response from AddNetworkElement: %v\n", name, listResponse) - topoNode.ID = listResponse.GetResponses()[0].GetId() + fmt.Printf("[%s] - Got response from AddNetworkElement: %v\n", v.Name, listResponse) - fmt.Printf("[%s] - Success: registered mne with gosdn controller\n", name) - fmt.Printf("[%s] - Also created gosdn topo node: %v\n", name, topoNode) + fmt.Printf("[%s] - Success: registered mne with gosdn controller\n", v.Name) } - topoNodes = append(topoNodes, topoNode) - } - topo.Nodes = topoNodes - return &VEnv{auth: gosdnauth, - pnd: gosdn_pnd, - conn: gosdnconn, - clabData: clabData, - clabFilename: topoYamlFile, - waitGroup: wg, - topology: topo, - containerRegistryURL: "registry.code.fbi.h-da.de/danet/gnmi-target/", // TODO: Could let user choose } + + return nil } // 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 *[]rtdt_topology.Node) error { +func (v *VEnv) loadNetworkElementModelPathsIntoGosdn(ctx context.Context, conn *grpc.ClientConn, nodes *[]*rtdt_topology.Node) error { networkElementService := networkelement.NewNetworkElementServiceClient(conn) paths := [2]string{"/lldp/config/system-description", "/system/state/"} @@ -177,6 +170,10 @@ func (v *VEnv) loadNetworkElementModelPathsIntoGosdn(ctx context.Context, conn * // What this does: Load yang paths into topo, then iterate over // nodes and fill data in func (v *VEnv) ConstructTopology() error { + if v.clabData == nil { + return fmt.Errorf("Error: Trying to construct topology without containerlab file being loaded first") + } + // Either fill topology from clabData or from database? ctx := v.auth.CreateContextWithAuthorization() conn := v.conn var path = "/" @@ -197,7 +194,6 @@ func (v *VEnv) ConstructTopology() error { networkElementService := networkelement.NewNetworkElementServiceClient(conn) - // This code relies on Nodes already being filled, so what does this achieve?? for iterator, node := range v.topology.Nodes { getNetworkElementResponse, _ := networkElementService.Get(ctx, &networkelement.GetRequest{Mneid: node.ID}) if err != nil { @@ -220,6 +216,71 @@ func (v *VEnv) ConstructTopology() error { return nil } +// Create rtdt_topology.Topology from clabconfig.Topology +func (v *VEnv) DeriveTopologyFromClabData() error { + if v.clabData == nil { + return fmt.Errorf("Can't derive topology without clabData\n") + } + v.topology = &rtdt_topology.Topology{} + var topoNodes []*rtdt_topology.Node + // Get all the nodes from clab structure + for nodeName, node := range v.clabData.Topology.Nodes { + topoNode := rtdt_topology.Node{ + ID: uuid.NewString(), + Name: nodeName, + Kind: node.Kind, + Image: node.Image, + MgmtIpv4: node.MgmtIPv4, + } + topoNodes = append(topoNodes, &topoNode) + } + v.topology.Nodes = topoNodes + for _, link := range v.clabData.Topology.Links { + if len(link.Endpoints) != 2 { + return fmt.Errorf("Couldn't parse clabData into topology\n") + } + var topoLink = rtdt_topology.Link{} + // determine Node name and port + node0Full := strings.Split(link.Endpoints[0], ":") + node0Name := node0Full[0] + node0Port := node0Full[1] + node1Full := strings.Split(link.Endpoints[1], ":") + node1Name := node1Full[0] + node1Port := node1Full[1] + + // find the node that has right name, add links and ports + topoLink.SourceNode = v.topology.GetNodeByName(node0Name) + topoLink.SourcePort = &rtdt_topology.Port{Name: node0Port, ID: uuid.NewString()} + v.topology.Ports = append(v.topology.Ports, topoLink.SourcePort) + topoLink.TargetNode = v.topology.GetNodeByName(node1Name) + topoLink.TargetPort = &rtdt_topology.Port{Name: node1Port, ID: uuid.NewString()} + v.topology.Ports = append(v.topology.Ports, topoLink.TargetPort) + v.topology.Links = append(v.topology.Links, &topoLink) + v.topology.Nodes = topoNodes + } + return nil +} + +func (v *VEnv) UploadTopology() error { + conn := v.auth.GetConn() + topoService := topoPb.NewTopologyServiceClient(conn) + for _, link := range v.topology.Links { + + ctx := v.auth.CreateContextWithAuthorization() + addLinkRequest := &topoPb.AddLinkRequest{ + Timestamp: util.Now(), + Link: link.Convert(), + } + fmt.Println("AddLink is:", addLinkRequest.String()) + topoResponse, err := topoService.AddLink(ctx, addLinkRequest) + if err != nil { + return err + } + fmt.Printf("Successfully uploaded Link to DB: %s\n", topoResponse.String()) + } + return nil +} + func getTypedValue(value string) *gnmi.TypedValue { if boolVal, err := strconv.ParseBool(value); err == nil { return &gnmi.TypedValue{Value: &gnmi.TypedValue_BoolVal{BoolVal: boolVal}} @@ -282,9 +343,9 @@ func (v *VEnv) SetGnmiPath(path, value, mneid string, save bool) error { } else { fmt.Println("Successfully applied changes:", clResponse) } - if save { - v.savedChanges = append(v.savedChanges, changeRequest) - } + if save { + v.savedChanges = append(v.savedChanges, changeRequest) + } return nil } @@ -339,8 +400,8 @@ func (v *VEnv) GetWaitgroup() *sync.WaitGroup { return v.waitGroup } func (v *VEnv) GetTopology() *rtdt_topology.Topology { - return &v.topology + return v.topology } func (v *VEnv) GetSavedChanges() *[]*networkelement.ChangeRequest { - return &v.savedChanges + return &v.savedChanges }