Code owners
Assign users and groups as approvers for specific file changes. Learn more.
venv.go 16.75 KiB
package venv
import (
"context"
"fmt"
"strconv"
"strings"
"sync"
"time"
"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"
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"
rtdt_topology "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-topology"
"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
yangparser "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/yang-parser"
"code.fbi.h-da.de/danet/gosdn/models/generated/openconfig"
uuid "github.com/google/uuid"
gnmi "github.com/openconfig/gnmi/proto/gnmi"
"github.com/openconfig/ygot/ygot"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type VEnv struct {
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
clabFilename string // This is the name of the yaml file clabData is based on
StopChan <-chan struct{}
waitGroup *sync.WaitGroup
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()
var err error
if err = clab.ClabDeploy(topoYamlFile); err != nil {
fmt.Printf("[%s] - Failed to deploy the physical network\n", name)
return nil
}
// After having deployed it, load the config into clabData
var clabData *clab.ClabConfig
clabData, err = clab.LoadConfig(topoYamlFile)
if err != nil {
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 {
if strings.HasPrefix(nodename, "gosdn") {
gosdnAddress = val.MgmtIPv4 + ":55055"
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())
gosdnconn, err := grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions())
if err != nil {
fmt.Printf("[%s] - Failed to create connection to gosdn\n", name)
return nil
} else {
fmt.Printf("[%s] - Successfully created connection to gosdn\n", name)
}
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 {
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
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..", name)
time.Sleep(time.Second * 2)
}
// 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",
Password: "admin",
Tls: true,
TransportOption: &tpb.TransportOption_GnmiTransportOption{
GnmiTransportOption: &tpb.GnmiTransportOption{},
},
}
// Openconfig pluginid? TODO decide
pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically
pndID, _ := uuid.Parse(v.pnd.Id)
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", v.Name, err)
return nil
} else {
fmt.Printf("[%s] - Successfully created network element\n", v.Name)
}
fmt.Printf("[%s] - Got response from AddNetworkElement: %v\n", v.Name, listResponse)
fmt.Printf("[%s] - Success: registered mne with gosdn controller\n", v.Name)
}
}
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 {
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
}
}
}
return nil
}
// Based on getAndAddMoreData() in: "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/venv-manager/venv-manager.go"
// commit: 0264b698286b6cbb965d743078c681f8af55edf6
// 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 = "/"
var ygotPath *gnmi.Path
// Create 'root' path to be able to load the whole model from the store.
var err error
ygotPath, err = ygot.StringToPath(path, ygot.StructuredPath)
if err != nil {
return err
}
// just to load model data into goSDN to guaranteed have new data available for get request
err = v.loadNetworkElementModelPathsIntoGosdn(ctx, conn, &v.topology.Nodes)
if err != nil {
return err
}
networkElementService := networkelement.NewNetworkElementServiceClient(conn)
for iterator, node := range v.topology.Nodes {
getNetworkElementResponse, _ := networkElementService.Get(ctx, &networkelement.GetRequest{Mneid: node.ID})
if err != nil {
return err
}
var marshalledYangData openconfig.Device
err = yangparser.Unmarshal([]byte(getNetworkElementResponse.Mne.Model), ygotPath, &marshalledYangData)
if err != nil {
return err
}
mgmntAddress := strings.Split(getNetworkElementResponse.Mne.TransportAddress, ":")
v.topology.Nodes[iterator].MgmtIpv4 = mgmntAddress[0]
v.topology.Nodes[iterator].YangData = marshalledYangData
v.topology.Nodes[iterator].FillAllFields(v.containerRegistryURL)
}
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) ApplyRoutes() error {
conn := v.auth.GetConn()
hostIP := int64(1)
for _, link := range v.topology.Links {
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
}
func (v *VEnv) UploadTopology() error {
conn := v.auth.GetConn()
topoService := topoPb.NewTopologyServiceClient(conn)
for _, link := range v.topology.Links {
ctx := v.auth.CreateContextWithAuthorization()
l := link.Convert()
l.Name = l.SourceNode.Name + ":" + l.TargetNode.Name
l.SourcePort.Configuration = &topoPb.Configuration{Ip: "192.168.178.2", PrefixLength: 24}
l.TargetPort.Configuration = &topoPb.Configuration{Ip: "192.168.178.3", PrefixLength: 24}
addLinkRequest := &topoPb.AddLinkRequest{
Timestamp: util.Now(),
Link: l,
}
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 (v *VEnv) RetrieveTopology() error {
conn := v.auth.GetConn()
ctx := v.auth.CreateContextWithAuthorization()
topoService := topoPb.NewTopologyServiceClient(conn)
topoResponse, err := topoService.GetTopology(ctx, &topoPb.GetTopologyRequest{Timestamp: util.Now()})
if err != nil {
return fmt.Errorf("Couldn't retrieve topology from DB: %v\n", err)
}
sourceTopo := topoResponse.GetToplogy()
targetTopo := rtdt_topology.NewTopology()
for _, link := range sourceTopo.Links {
var n0 rtdt_topology.Node
var n1 rtdt_topology.Node
if targetTopo.GetNodeByUUID(link.SourceNode.Id) == nil {
snode := link.SourceNode
n1 = rtdt_topology.Node{
ID: snode.Id,
Name: snode.Name,
Kind: "todo",
Image: "todo", // How to do this?
MgmtIpv4: "todo",
}
v.topology.Nodes = append(v.topology.Nodes, &n1)
}
if targetTopo.GetNodeByUUID(link.TargetNode.Id) == nil {
tnode := link.TargetNode
n1 = rtdt_topology.Node{
ID: tnode.Id,
Name: tnode.Name,
Kind: "todo",
Image: "todo", // How to do this?
MgmtIpv4: "todo",
}
v.topology.Nodes = append(v.topology.Nodes, &n1)
}
if targetTopo.GetLinkByUUID(link.Id) == nil {
var newLink = rtdt_topology.Link{
ID: link.Id,
Name: link.Name,
SourceNode: &n0,
TargetNode: &n1,
}
v.topology.Links = append(v.topology.Links, &newLink)
}
}
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}}
} else if uintVal, err := strconv.ParseUint(value, 10, 64); err == nil {
return &gnmi.TypedValue{Value: &gnmi.TypedValue_UintVal{UintVal: uintVal}}
} else if intVal, err := strconv.ParseInt(value, 10, 64); err == nil {
return &gnmi.TypedValue{Value: &gnmi.TypedValue_IntVal{IntVal: intVal}}
} else {
return &gnmi.TypedValue{Value: &gnmi.TypedValue_StringVal{StringVal: value}}
}
}
func (v *VEnv) SetGnmiPath(path, value, mneid string, save bool) error {
ctx := v.auth.CreateContextWithAuthorization()
fmt.Println("--IN SETGNMIPATH-----------------------")
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)
}
//gosdnAddr := v.auth.GetAddress()
gnmiVal := getTypedValue(value)
changeRequest := &networkelement.ChangeRequest{
Mneid: mneid,
Path: gnmiPath,
Value: gnmiVal,
ApiOp: networkelement.ApiOperation_API_OPERATION_UPDATE,
}
changeRequests := []*networkelement.ChangeRequest{changeRequest}
pid := v.pnd.Id
setPathResponse, err := mneService.SetPathList(ctx, &networkelement.SetPathListRequest{
Timestamp: util.Now(),
Pid: pid,
ChangeRequest: changeRequests,
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return err
}
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 err != nil {
fmt.Println("Error, failed to commit changes:", err)
return err
} else {
fmt.Println("Successfully applied changes:", clResponse)
}
if save {
v.savedChanges = append(v.savedChanges, changeRequest)
}
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
// }
// 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
}
// {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) GetTopology() *rtdt_topology.Topology {
return v.topology
}
func (v *VEnv) GetSavedChanges() *[]*networkelement.ChangeRequest {
return &v.savedChanges
}