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"
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
savedChanges []*networkelement.ChangeRequest
}
// 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
}
}
// 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 {
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
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
}
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
func getTypedValue(value, ytype string) (*gnmi.TypedValue, error) {
switch ytype {
case "uint16":
{
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
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)
}
//ytypes.GetOrCreateNode(gnmitargetygot.Schema(), ,gnmiPath)
fmt.Println("GETTING SCHEMA")
schema, nil := gnmitargetygot.Schema()
fmt.Println("GETTING ROOT SCHEMA")
rootSchema := schema.RootSchema()
fmt.Println("GET OR CREATE NODE")
_, entry, err := ytypes.GetOrCreateNode(rootSchema, &gnmitargetygot.Gnmitarget{}, gnmiPath)
if err != nil {
return fmt.Errorf("SetGnnmiPath Error: %w", err)
}
S.H.
committed
//gosdnAddr := v.auth.GetAddress()
fmt.Println("GET TYPED VALUE")
yangType := entry.Type
gnmiVal, err := getTypedValue(value, yangType.Kind.String())
if err != nil {
fmt.Println("The given type is not supported yet!")
}
// fmt.Println("YangType Name is ", yangType.Name)
// fmt.Println("YangType Type is ", yangType.Type)
// fmt.Println("YangType Kind is ", yangType.Kind.String())
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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// 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) GetSdnConfig() *sdnconfig.SdnConfig {
return v.sdnConfig
}
func (v *VEnv) GetSavedChanges() *[]*networkelement.ChangeRequest {
return &v.savedChanges