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"
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"
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"
S.H.
committed
rtdt_topology "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-topology"
"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/sdnconfig"
"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
S.H.
committed
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"
S.H.
committed
gnmi "github.com/openconfig/gnmi/proto/gnmi"
"github.com/openconfig/ygot/ygot"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"gopkg.in/yaml.v3"
)
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
topology *rtdt_topology.Topology
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
savedChanges []*networkelement.ChangeRequest
}
// Accepts a yaml filename to deploy a container lab environment
// TODO Split up into sub-functions
func NewVEnv(name, clabFilename, user, pass string, wg *sync.WaitGroup) *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
}
// After having deployed it, load the config into clabData
var clabData *clab.ClabConfig
clabData, err = clab.LoadConfig(clabFilename)
if err != nil {
S.H.
committed
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"
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,
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)
S.H.
committed
}
fmt.Printf("[%s] - Got response from AddNetworkElement: %v\n", v.Name, listResponse)
S.H.
committed
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 *[]*rtdt_topology.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
}
// 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?
S.H.
committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
228
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
}
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// 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
}
273
274
275
276
277
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
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
}
// TODO: Fix hardcoded IP addresses
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(),
}
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
}
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
// What this does: Upload the nodes that make up the gosdn environment
// via the gosdn configuration service to the db (applies to db, message broker, etc.)
// This is probably not the intended use for the configuration interface?
func (v *VEnv) UploadClabConfig() error {
if v.clabData == nil {
return fmt.Errorf("Can't upload clabconfig when it hasn't been loaded yet (is nil pointer)")
}
var clabToUpload = *v.clabData
for nodename, node := range v.clabData.Topology.Nodes {
if !strings.Contains(nodename, "gosdn") &&
!strings.Contains(nodename, "mongodb") &&
!strings.Contains(nodename, "rabbitmq") &&
!strings.Contains(nodename, "plugin-registry") {
fmt.Println("Removing node:", node)
delete(clabToUpload.Topology.Nodes, nodename)
}
}
clabToUpload.Topology.Links = nil
clabConfig, err := yaml.Marshal(clabToUpload)
if err != nil {
return fmt.Errorf("Encountered error marshalling clabConfig: %w", err)
}
var jsonData interface{}
if err := yaml.Unmarshal(clabConfig, &jsonData); err != nil {
return fmt.Errorf("failed to unmarshal YAML to JSON: %w", err)
}
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
return fmt.Errorf("Failed to marshal JSON: %w", err)
}
req := &configPb.ImportSDNConfigRequest{
Timestamp: util.Now(),
Pid: v.pnd.GetId(), // TODO: check if this can be nil
SdnConfigData: string(jsonBytes),
}
conn := v.auth.GetConn()
configService := configPb.NewConfigurationManagementServiceClient(conn)
ctx := v.auth.CreateContextWithAuthorization()
configResponse, err := configService.ImportSDNConfig(ctx, req)
if err != nil {
return err
}
fmt.Println("Successfully uploaded clab config:", configResponse.String())
return nil
}
// TODO This doesn't really work this way..
func (v *VEnv) RetrieveClabConfig() error {
ctx := v.auth.CreateContextWithAuthorization()
conn := v.auth.GetConn()
configService := configPb.NewConfigurationManagementServiceClient(conn)
req := &configPb.ExportSDNConfigRequest{
Timestamp: util.Now(),
Pid: v.pnd.GetId(),
}
configResponse, err := configService.ExportSDNConfig(ctx, req)
if err != nil {
return fmt.Errorf("Failed retrieving config clab data: %w", err)
}
var jsonData map[string]interface{}
err = json.Unmarshal([]byte(configResponse.SdnConfigData), &jsonData)
if err != nil {
return fmt.Errorf("Failed to unmarshal SDN config data: %w", err)
}
// Pretty-print the JSON data
formattedJSON, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
return fmt.Errorf("failed to format JSON data: %w", err)
}
fmt.Println("Retrieved SDN Config Data:")
fmt.Println(string(formattedJSON)) // Print the formatted JSON
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 {
S.H.
committed
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{
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
S.H.
committed
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
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 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)
}
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
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
// 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
}
S.H.
committed
func (v *VEnv) GetTopology() *rtdt_topology.Topology {
return v.topology
S.H.
committed
}
func (v *VEnv) GetSavedChanges() *[]*networkelement.ChangeRequest {
return &v.savedChanges