diff --git a/applications/rtdt-manager/clab-config/clab-config.go b/applications/rtdt-manager/clab-config/clab-config.go index d051eb9fd0fb33c4ee7bc4c43736ab1da083e3b3..47bb2c914e9edd61c201f4776c4d4fe6a2253dfe 100644 --- a/applications/rtdt-manager/clab-config/clab-config.go +++ b/applications/rtdt-manager/clab-config/clab-config.go @@ -81,7 +81,7 @@ func LoadConfig(filename string) (*ClabConfig, error) { if err != nil { return nil, fmt.Errorf("Failed to unmarshal YAML: %w", err) } - + fmt.Println("--- Successfully loaded file:", absFilepath, "----") return &clabconfig, nil } @@ -98,9 +98,9 @@ func DeriveConfig(clabconfig *ClabConfig, newIPv4Subnet, newIPv6Subnet string, p derivedConfig := *clabconfig derivedConfig.Topology.Nodes = make(map[string]Node) derivedConfig.Topology.Links = append([]Link{}, clabconfig.Topology.Links...) // Copy links - portOffset := 5 + portOffset := 5 // TODO set dynamically in some way - derivedConfig.Name = fmt.Sprintf("%s_%s", clabconfig.Name, postfix) + derivedConfig.Name = fmt.Sprintf("%s-%s", clabconfig.Name, postfix) subnetParts := strings.Split(newIPv4Subnet, ".") derivedConfig.Mgmt.IPv4Subnet = newIPv4Subnet derivedConfig.Mgmt.IPv6Subnet = newIPv6Subnet @@ -112,6 +112,15 @@ func DeriveConfig(clabconfig *ClabConfig, newIPv4Subnet, newIPv6Subnet string, p splitIPv4[0], splitIPv4[1], splitIPv4[2] = subnetParts[0], subnetParts[1], subnetParts[2] node.MgmtIPv4 = strings.Join(splitIPv4, ".") + if strings.HasPrefix(name, "gosdn") { + node.Cmd = "--config /app/configs/containerlab-gosdn-twin.toml" + node.Binds = []string{"../../../applications/rtdt-manager/data/ssl/gosdn:/app/ssl"} + } + + //use separate ssl folders, testing + if strings.HasPrefix(name, "gnmi-target") { + node.Binds = []string{"../../../applications/rtdt-manager/data/ssl/gnmi-target:/etc/gnmi-target/ssl"} + } // Ports: host side needs to be incremented or there will be conflicts // for now just use 5 as increment for i, portBinding := range node.Ports { @@ -137,21 +146,19 @@ func DeriveConfig(clabconfig *ClabConfig, newIPv4Subnet, newIPv6Subnet string, p } node.Ports[i] = strings.Join(parts, ":") } - newName := fmt.Sprintf("%s-%s", name, postfix) - derivedConfig.Topology.Nodes[newName] = node + derivedConfig.Topology.Nodes[name] = node } // Update Links so they reference the correct nodes - for i, link := range clabconfig.Topology.Links { - for j, endpoint := range link.Endpoints { - parts := strings.Split(endpoint, ":") - if len(parts) != 2 { - return nil, fmt.Errorf("invalid link endpoint: %s", endpoint) - } - parts[0] = fmt.Sprintf("%s-%s", parts[0], postfix) // Update the node name - clabconfig.Topology.Links[i].Endpoints[j] = strings.Join(parts, ":") - } - } + // for i, link := range clabconfig.Topology.Links { + // for j, endpoint := range link.Endpoints { + // parts := strings.Split(endpoint, ":") + // if len(parts) != 2 { + // return nil, fmt.Errorf("invalid link endpoint: %s", endpoint) + // } + // clabconfig.Topology.Links[i].Endpoints[j] = strings.Join(parts, ":") + // } + // } return &derivedConfig, nil } diff --git a/applications/rtdt-manager/data/containerlab-gosdn-twin.toml.example b/applications/rtdt-manager/data/containerlab-gosdn-twin.toml.example new file mode 100644 index 0000000000000000000000000000000000000000..8c3fdb5a4ce5d32dd2cca7603a54911497f42acc --- /dev/null +++ b/applications/rtdt-manager/data/containerlab-gosdn-twin.toml.example @@ -0,0 +1,19 @@ +basepnduuid = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49e" +plugin-registry = "clab-gosdn_csbi_arista_base-TEST-TWIN-plugin-registry:55057" +help = false +plugin-folder = "plugins" +log-level = "debug" +socket = ":55055" +databaseConnection = "mongodb://root:example@clab-gosdn_csbi_arista_base-TEST-TWIN-mongodb:27017" +filesystemPathToStores = "stores" +gNMISubscriptionsPath = "configs/gNMISubscriptions.txt" +tlscertfile = '/ssl/certs/gosdn-selfsigned.crt' +tlskeyfile = '/ssl/private/gosdn-selfsigned.key' +tlscacertfile = '/ssl/ca.crt' +maxTokensPerUser = 100 + +amqpPrefix = "amqp://" +amqpUser = "guest" +amqpPassword = "guest" +amqpHost = "rabbitmq" +amqpPort = "5672" diff --git a/applications/rtdt-manager/gosdnutil/gosdnutil.go b/applications/rtdt-manager/gosdnutil/gosdnutil.go index 80cc1a36f89d985346fed182aa62dd4bfd2a35e2..ecc8449c51c8ba08d36d6c12412f600e5ad6d9ce 100644 --- a/applications/rtdt-manager/gosdnutil/gosdnutil.go +++ b/applications/rtdt-manager/gosdnutil/gosdnutil.go @@ -15,7 +15,7 @@ func FetchPnd(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth) (*pnd.PrincipalNe ctx := auth.CreateContextWithAuthorization() pndResponse, err := pndService.GetPndList(ctx, &pnd.GetPndListRequest{Timestamp: util.Now()}) if err != nil { - return nil, fmt.Errorf("Failed to retrieve PND information: %w", err) + return nil, fmt.Errorf("Failed to retrieve PND information:\npndresponse: %v\nerror: %w\n", pndResponse, err) } if pndList := pndResponse.GetPnd(); pndList != nil { fmt.Println("pndUUID is now: ", pndList[0].Id) diff --git a/applications/rtdt-manager/main.go b/applications/rtdt-manager/main.go index a18a6a54dde1b09dfd37082346620b9ff3801aea..5926b54c321ad27c93ef3223914862cc1dae29ba 100644 --- a/applications/rtdt-manager/main.go +++ b/applications/rtdt-manager/main.go @@ -23,61 +23,48 @@ func main() { var pass string var user string var topology_file string - var generate bool var withTwin bool - var testMode bool flag.StringVar(&address, "address", "172.100.0.5:55055", "Address of the gosdn controller") flag.StringVar(&address, "a", "172.100.0.5:55055", "Address of the gosdn controller (shorthand)") flag.StringVar(&pass, "password", "TestPassword", "Password for admin user") flag.StringVar(&pass, "p", "TestPassword", "Password for admin user (shorthand)") flag.StringVar(&user, "user", "admin", "Username") flag.StringVar(&user, "u", "admin", "Username (shorthand)") - flag.BoolVar(&generate, "generate", false, "Whether to start the realnet containerlab environment ourselves") - flag.BoolVar(&generate, "g", false, "Whether to start the realnet containerlab environment ourselves (shorthand)") flag.BoolVar(&withTwin, "with-twin", false, "Whether to start a twin") - flag.StringVar(&topology_file, "topology", "data/clab.yaml", "Containerlab file on the basis of which to create topo") flag.StringVar(&topology_file, "t", "data/clab.yaml", "Containerlab file on the basis of which to create topo (shorthand)") - flag.BoolVar(&testMode, "test", false, "Whether to execute the test") - flag.Usage = func() { fmt.Println("--address, -a: Address of the gosdn controller (realnet)") fmt.Println("--user, -u: User to log into realnet as") fmt.Println("--password, -p: Password for the user to log into realnet as") fmt.Println("--topology, -t: Topology .yaml file to use to generate realnet and twins") - fmt.Println("--generate, -g: Whether to start the realnet containerlab environment ourselves") - fmt.Println("--with-twin: Whether to start the realnet containerlab environment for the twin") - fmt.Println("--test: Whether to execute a test of the event system") + fmt.Println("--with-twin: Whether to start the containerlab virtual environment for the twin") } flag.Parse() fmt.Println("Topology file path: ", topology_file) + // Start virtual environment for realnet var realnet *venv.VEnv - if generate { - fmt.Println("Generate flag is set, starting clab environment for realnet") - realnet = venv.NewVEnv("REALNET", topology_file, user, pass, &wg) - if realnet == nil { - fmt.Println("ERROR: Couldn't deploy the physical network") - } else { - fmt.Println("Successfully deployed physical network") - } + realnet = venv.NewVEnv("REALNET", topology_file, user, pass, &wg) + if realnet == nil { + fmt.Println("ERROR: Couldn't deploy the physical network") + return } else { - fmt.Println("Generate flag not set!") + fmt.Println("Successfully deployed physical network") } - // 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() - if err != nil { - fmt.Printf("Error occured while initializing event system: %v", err) - return - } + // err := rtdtMan.InitEventSystem() + // if err != nil { + // fmt.Printf("Error occured while initializing event system: %v", err) + // return + // } if withTwin { - rtdtMan.LaunchTwin("172.101.0.0/16", "2001:db9::/64", "test-twin") + rtdtMan.LaunchTwin("172.101.0.0/16", "2001:db9::/64", "TEST-TWIN") } if err := rtdtMan.Run(); err != nil { fmt.Println("Program exited with errors: %w", err) diff --git a/applications/rtdt-manager/mneadd/main.go b/applications/rtdt-manager/mneadd/main.go index 9a5fc663cba73b58fe59de96840e59a429697e9a..679ecf182e2ebcbc9750b4850e6606664cc580f4 100644 --- a/applications/rtdt-manager/mneadd/main.go +++ b/applications/rtdt-manager/mneadd/main.go @@ -15,44 +15,47 @@ import ( ) func main() { - gosdn_addr := "172.100.0.5:55055" - addr := "172.100.0.11:7030" - var sessionToken string + gosdn_addr1 := "172.100.0.5:55055" + gosdn_addr2 := "172.101.0.5:55055" + mneAddr1 := "172.100.0.11:7030" + mneAddr2 := "172.101.0.11:7030" + var sessionToken1 string + var sessionToken2 string // Create Connection dialOption := grpc.WithTransportCredentials(insecure.NewCredentials()) - conn, err := grpc.NewClient(gosdn_addr, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) + conn1, err := grpc.NewClient(gosdn_addr1, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) // Log in to controller // Register - loginResponse, err := api.Login(context.Background(), gosdn_addr, "admin", "TestPassword") + loginResponse1, err := api.Login(context.Background(), gosdn_addr1, "admin", "TestPassword") if err != nil { fmt.Println("Couldn't log in to gosdn controller:", err) return } else { - fmt.Println("Successully logged in to gosdn controller") + fmt.Println("Successully logged in to gosdn controller at", gosdn_addr1) time.Sleep(time.Second) } - sessionToken = loginResponse.GetToken() + sessionToken1 = loginResponse1.GetToken() // Create Context with Authorization - md := metadata.Pairs("authorize", sessionToken) - ctx := metadata.NewOutgoingContext(context.Background(), md) + md := metadata.Pairs("authorize", sessionToken1) + ctx1 := metadata.NewOutgoingContext(context.Background(), md) // PND - var p *pnd.PrincipalNetworkDomain - pndService := pnd.NewPndServiceClient(conn) - pndResponse, err := pndService.GetPndList(ctx, &pnd.GetPndListRequest{Timestamp: int64(time.Now().Nanosecond())}) + var p1 *pnd.PrincipalNetworkDomain + pndService := pnd.NewPndServiceClient(conn1) + pndResponse, err := pndService.GetPndList(ctx1, &pnd.GetPndListRequest{Timestamp: int64(time.Now().UnixNano())}) if err != nil { fmt.Println("Failed to fetch PND") return } else { - fmt.Println("Successfully got PND") + fmt.Println("Successfully got PND:", pndResponse.GetPnd()) time.Sleep(time.Second) } // Add MNE - p = pndResponse.GetPnd()[0] - pndID, _ := uuid.Parse(p.GetId()) + p1 = pndResponse.GetPnd()[0] + pndID1, _ := uuid.Parse(p1.GetId()) //fmt.Println("pndID: ", pndID) //pluginUUID, _ := uuid.Parse("823aad29-69be-42f0-b279-90f2c1b6a94d") //pluginID0, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically @@ -61,7 +64,7 @@ func main() { //fmt.Println("pluginID: ", pluginID) opt := &tpb.TransportOption{ - Address: addr, + Address: mneAddr1, Username: "admin", Password: "admin", Tls: true, @@ -69,7 +72,7 @@ func main() { GnmiTransportOption: &tpb.GnmiTransportOption{}, }, } - listResponse, err := api.AddNetworkElement(ctx, gosdn_addr, "1234", "", opt, pluginID, pndID, nil) + listResponse, err := api.AddNetworkElement(ctx1, gosdn_addr1, "elem1", "", opt, pluginID, pndID1, nil) if err != nil { fmt.Println("Failed to add network element: ", err) return @@ -77,4 +80,70 @@ func main() { fmt.Println("Successfylly added network element!") } _ = listResponse + + conn1.Close() + // More! ------------------------------------------------------------------------------------------- + + // Create Connection 2 + dialOption2 := grpc.WithTransportCredentials(insecure.NewCredentials()) + conn2, err := grpc.NewClient(gosdn_addr2, dialOption2, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) + conn2.Connect() + // Log in to controller + // Register + loginResponse2, err := api.Login(context.Background(), gosdn_addr2, "admin", "TestPassword") + if err != nil { + fmt.Println("Couldn't log in to gosdn controller:", err) + return + } else { + fmt.Println("Successully logged in to gosdn controller at", gosdn_addr2) + time.Sleep(time.Second) + } + sessionToken2 = loginResponse2.GetToken() + + // Create Context with Authorization + md2 := metadata.Pairs("authorize", sessionToken2) + ctx2 := metadata.NewOutgoingContext(context.Background(), md2) + + // PND + var p2 *pnd.PrincipalNetworkDomain + pndService2 := pnd.NewPndServiceClient(conn2) + pndResponse2, err := pndService2.GetPndList(ctx2, &pnd.GetPndListRequest{Timestamp: int64(time.Now().UnixNano())}) + if err != nil { + fmt.Println("Failed to fetch PND2, err: ", err) + } else { + fmt.Println("Successfully got PND2:", pndResponse2.GetPnd()) + time.Sleep(time.Second) + } + + // Add MNE + if len(pndResponse2.GetPnd()) > 0 { + p2 = pndResponse2.GetPnd()[0] + } + pndID2, _ := uuid.Parse(p2.GetId()) + //fmt.Println("pndID: ", pndID) + //pluginUUID, _ := uuid.Parse("823aad29-69be-42f0-b279-90f2c1b6a94d") + //pluginID0, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically + + pluginID2, _ := uuid.Parse("e2c358b3-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically + //fmt.Println("pluginID: ", pluginID) + + opt2 := &tpb.TransportOption{ + Address: mneAddr2, + Username: "admin", + Password: "admin", + Tls: true, + TransportOption: &tpb.TransportOption_GnmiTransportOption{ + GnmiTransportOption: &tpb.GnmiTransportOption{}, + }, + } + fmt.Println("Using pnd:", pndID2) + listResponse2, err := api.AddNetworkElement(ctx2, gosdn_addr2, "elem2", "", opt2, pluginID2, pndID2, nil) + if err != nil { + fmt.Println("Failed to add network element: ", err) + return + } else { + fmt.Println("Successfylly added network element!") + } + _ = listResponse2 + conn2.Close() } diff --git a/applications/rtdt-manager/rtdt-auth/rtdt-auth.go b/applications/rtdt-manager/rtdt-auth/rtdt-auth.go index ae15f67b5d3ce7934ed7c978faf46f8fc5cade0d..0b576517b492cd50c241726ec9f3ac646229a8f5 100644 --- a/applications/rtdt-manager/rtdt-auth/rtdt-auth.go +++ b/applications/rtdt-manager/rtdt-auth/rtdt-auth.go @@ -23,8 +23,6 @@ func NewRtdtAuth(userName, url, password string, conn *grpc.ClientConn) *RtdtAut if err != nil { fmt.Println("Encountered error while trying to log in: ", err) return nil - } else { - fmt.Println("Successfully logged in to gosdn controller at ", url, " as user ", userName) } return &RtdtAuth{ userName: userName, @@ -36,6 +34,7 @@ func NewRtdtAuth(userName, url, password string, conn *grpc.ClientConn) *RtdtAut // createContextWithAuthorization creates a context with the token received after login. func (r *RtdtAuth) CreateContextWithAuthorization() context.Context { + fmt.Println("Creating Context with session token: ", r.sessionTok) md := metadata.Pairs("authorize", r.sessionTok) return metadata.NewOutgoingContext(context.Background(), md) } @@ -48,8 +47,8 @@ func (r RtdtAuth) GetAddress() string { return r.address } func (r RtdtAuth) GetUsername() string { - return r.userName + return r.userName } func (r RtdtAuth) GetPassword() string { - return r.password + return r.password } diff --git a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go index e2c185c34384944257f8b500f95e2846fe3be1fa..e3ea4bb1f79ef77ed309b873b1354ba592498926 100644 --- a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go +++ b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go @@ -1,22 +1,15 @@ package rtdtmanager import ( + "code.fbi.h-da.de/danet/gosdn/application-framework/event" "fmt" "os" "path/filepath" - "regexp" "strings" "sync" - "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement" - "code.fbi.h-da.de/danet/gosdn/application-framework/event" - - //"code.fbi.h-da.de/danet/gosdn/csbi/cmd" - "code.fbi.h-da.de/danet/gosdn/application-framework/registration" clabconfig "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config" - "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/gosdnutil" - "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util" "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/venv" ) @@ -26,8 +19,7 @@ type RtdtManager struct { rtdt_twins []*venv.VEnv eventService event.ServiceInterface // Receive events from realnet gosdn (not used yet) waitGroup *sync.WaitGroup - stopChan *chan os.Signal // Global stop channel TODO Can I use that like this? - // TODO auth and conn for virtual net? + stopChan *chan os.Signal } // needs to be passed a running realnet VEnv @@ -41,14 +33,11 @@ func NewRtdtManager(realnet *venv.VEnv, wg *sync.WaitGroup, stopChan *chan os.Si return &rMan } -// - // To launch a new twin, this runs through the following steps: // - Load the clab config for current realnet gosdn (passed on cli) // - Derive the config we need for twin // - Write the config to disk // - Use that config to call "containerlab deploy" -// Takes // Launch a second gosdn instance which will manage the virtual network func (r *RtdtManager) LaunchTwin(twinSubnetIPv4, twinSubnetIPv6, twinName string) error { var derivedConfig *clabconfig.ClabConfig @@ -67,17 +56,15 @@ func (r *RtdtManager) LaunchTwin(twinSubnetIPv4, twinSubnetIPv6, twinName string } // Now run deploy with new config file - clabconfig.ClabDeploy(clabConfigFullPath) - user := r.realnet.GetAuth().GetUsername() - password := r.realnet.GetAuth().GetPassword() - twin := venv.NewVEnv(twinName, clabConfigFullPath, user, password, r.waitGroup) + // user := r.realnet.GetAuth().GetUsername() + // password := r.realnet.GetAuth().GetPassword() + twin := venv.NewVEnv(twinName, clabConfigFullPath, "admin", "TestPassword", r.waitGroup) r.rtdt_twins = append(r.rtdt_twins, twin) return nil } -// Start the eventsystem for each venv, manage clean up +// Run realnet and twins (if present) until signal received, manage clean up func (r *RtdtManager) Run() error { - fmt.Println("Starting twin..") if r.realnet == nil { fmt.Println("You did not start the physical network, please do so before executing App.Run()") return nil @@ -118,8 +105,8 @@ func (r *RtdtManager) Run() error { return nil } +// Receive events from realnet VEnv func (r *RtdtManager) InitEventSystem() error { - realnet_auth := r.realnet.GetAuth() ctx := realnet_auth.CreateContextWithAuthorization() queueCredentials, err := registration.Register(ctx, realnet_auth.GetAddress(), "basic-interface-monitoring", "SecurePresharedToken") @@ -168,42 +155,42 @@ func (r *RtdtManager) updateMNECallback(event *event.Event) { fmt.Println("EventID: ", event.ID.ID()) fmt.Println("Event Type: ", event.Type) fmt.Println("PathsAndValuesMap: ", event.PathsAndValuesMap) + fmt.Println("EntityID", event.EntityID) prefix := "/interfaces/interface[name=" - //suffix := "]/state/oper-status" - re := regexp.MustCompile(`/interfaces/interface\[name=([^]]+)]/state/oper-status`) - + prefixHostname := "/system/config/hostname" + //realnetClabData := r.realnet.GetClabData() + // for i, node := range realnetClabData.Topology.Nodes { + // } + suffix := "]/config/mtu" + //re := regexp.MustCompile(`/interfaces/interface\[name=([^]]+)]/state/oper-status`) + + // Apply event to a twin + // No twins to apply changes to: + if len(r.rtdt_twins) == 0 { + fmt.Println("--- NO TWINS TO APPLY CHANGE TO ---") + return + } + // Test if the path is supported first, only set Gnmi Paths I actually want to be able to set for now for path, value := range event.PathsAndValuesMap { - if strings.HasPrefix(path, prefix) { - matches := re.FindStringSubmatch(path) - if len(matches) > 1 { - fmt.Printf("matches: %v", matches) - interfaceName := matches[1] // Extracted interface name (eth0, eth1, etc.) - state := value // UP or DOWN - _ = state - mneService := networkelement.NewNetworkElementServiceClient(r.realnet.GetConn()) - _ = mneService - - // Construct the request to twin? - request := &networkelement.GetRequest{ - Timestamp: util.Now(), - Mneid: event.EntityID.String(), + if strings.HasPrefix(path, prefix) && strings.HasSuffix(path, suffix) { + fmt.Println("--- CHANGE MTU TRIGGERED ---") + fmt.Println("Value of new MTU: ", value) + for _, twin := range r.rtdt_twins { + twin.SetGnmiPath(path, value, event.EntityID.String()) + } + } + if strings.HasPrefix(path, prefixHostname) { + fmt.Println("--- CHANGE HOSTNAME TRIGGERED ---") + for _, twin := range r.rtdt_twins { + if twin == nil { + fmt.Println("Encountered nil twin, skipping") + continue } - _ = request - - fmt.Printf("Interface: %s, State: %s\n", interfaceName, state) + fmt.Println("ENTERING SETGNMIPATH, value: ", value, "path:", path) + twin.SetGnmiPath(path, value, event.EntityID.String()) } - } } - ctx := r.realnet.GetAuth().CreateContextWithAuthorization() - gosdn_pnd, err := gosdnutil.FetchPnd(r.realnet.GetConn(), r.realnet.GetAuth()) - if err != nil { - fmt.Printf("Error encountered in callback: %v", err) - } else { - fmt.Println("Retrieved pnd in callback: ", gosdn_pnd) - } - //api.UpdateNetworkElement(ctx, "172.100.0.5:55055") - _ = ctx } func (r *RtdtManager) userEventCallback(event *event.Event) { fmt.Println("--------------------------------") diff --git a/applications/rtdt-manager/rtdt-topology/rtdt-topology.go b/applications/rtdt-manager/rtdt-topology/rtdt-topology.go index e7a0b3d94f0d8e41f1e0bed27db8d04accb7dd54..171ab72e87923ddafac69fcc99a744229c015fc5 100644 --- a/applications/rtdt-manager/rtdt-topology/rtdt-topology.go +++ b/applications/rtdt-manager/rtdt-topology/rtdt-topology.go @@ -1,78 +1,86 @@ package rtdt_topology import ( - "fmt" - "os" - "path/filepath" - - "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/containerlab" - "gopkg.in/yaml.v3" + "regexp" + "code.fbi.h-da.de/danet/gosdn/models/generated/openconfig" ) -// Probably don't need a package, just use api inside rtdt-manager.go -// import ( -// "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology" -// "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth" -// "google.golang.org/grpc" -// ) -// -// type Link struct { -// Id string -// name string -// } -// type Port struct { -// } -// -// type Node struct { -// } -// -// type Topology struct { -// Links *[]Link -// Ports *[]Port -// Nodes *[]Node -// } -// -// func NewTopology() { -// -// return &Topology{} -// } -// -// func ApplyTopology(conn grpc.ClientConnInterface, auth rtdt_auth.RtdtAuth) { -// ctx := auth.CreateContextWithAuthorization() -// topoService := topology.NewTopologyServiceClient(conn) -// addLink := topology.AddLinkRequest{} -// topoService.AddLink(ctx, addLink) -// } -// -// func LoadTopologyFromFile() +// Struct definitions were taken from: +// "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/topology/port.go" +// "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/topology/link.go" +// "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/topology/node.go" +// commit: 0264b698286b6cbb965d743078c681f8af55edf6 +type Link struct { + ID string + Name string + SourceNode *Node + TargetNode *Node + SourcePort *Port + TargetPort *Port +} +type Port struct { + ID string + Name string +} -func ParseTopology(filename string) (*containerlab.YamlStruct, error) { - fmt.Println("Parsing file: ", filename) - var absFilepath string - var err error - if absFilepath, err = filepath.Abs(filename); err != nil { - return nil, fmt.Errorf("Failed to convert filename %v to absolute path", filename) - } - fmt.Printf("absolute filepath: %v\n", absFilepath) +type Node struct { + ID string + Name string + Kind string + Image string + MgmtIpv4 string + YangData openconfig.Device +} - file, err := os.Open(absFilepath) - if err != nil { - return nil, fmt.Errorf("Encountered error while trying to parse clab file into topology: %v", err) - } - decoder := yaml.NewDecoder(file) - var topoData containerlab.YamlStruct - if err := decoder.Decode(&topoData); err != nil { - return nil, fmt.Errorf("Failed to decode YAML: %v", err) - } +type Topology struct { + Links []Link + Ports []Port + Nodes []Node +} + +func NewTopology() *Topology { - fmt.Println("Successfully parsed given clab file into topology, nice!") + return &Topology{} +} - return &topoData, nil +// Source for function: "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/link.go" +// commit: 0264b698286b6cbb965d743078c681f8af55edf6 +// GetLinkAsSliceOfStrings returns the link as a slice of strings for yaml representation. +func (l *Link) GetLinkAsSliceOfStrings() [2]string { + return [2]string{l.SourceNode.Name + ":" + l.SourcePort.Name, l.TargetNode.Name + ":" + l.TargetPort.Name} } +// Source for function: "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/node.go" +// commit: 0264b698286b6cbb965d743078c681f8af55edf6 +// FillAllFields fills all remaining fields of object with data from YangData. +func (n *Node) FillAllFields(containerRegistryURL string) { + // Works if linux and our gnmi target is used. + softwareVersion := n.YangData.System.State.SoftwareVersion + if softwareVersion != nil { + // Checks if software version is in compatible format. + result, _ := regexp.MatchString(`^([A-Za-z0-9\.\/])*:([A-Za-z0-9\.])*`, *softwareVersion) + if result { + n.Kind = "linux" + n.Image = containerRegistryURL + *softwareVersion + return + } -// Needs to be in rtdt-manager -func ApplyTopology(topoYaml *containerlab.YamlStruct) error { + n.Kind = "couldn't detect kind" + n.Image = "couldn't detect image" + return + } + + // Specific to arista + regex := regexp.MustCompile(`[0-9]+\.[0-9]+\.[0-9][A-Z]`) + dockerTag := string(regex.FindAll([]byte(*n.YangData.Lldp.Config.SystemDescription), 1)[0]) + + // If it's not linux with our gnmi target and not arista, we don't support it. + if len(dockerTag) == 0 { + n.Kind = "couldn't detect kind" + n.Image = "couldn't detect image" + return + } - return nil + n.Kind = "ceos" + n.Image = containerRegistryURL + n.Kind + ":" + dockerTag } diff --git a/applications/rtdt-manager/venv/venv.go b/applications/rtdt-manager/venv/venv.go index 9ff2a99f3d13740df501cf1a2d7ef8804a2a076c..8fcb26b0375dbcbdd669be68e58997f92bec31d6 100644 --- a/applications/rtdt-manager/venv/venv.go +++ b/applications/rtdt-manager/venv/venv.go @@ -1,11 +1,13 @@ package venv import ( + "context" "fmt" "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" @@ -13,31 +15,38 @@ import ( 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/controller/api" + "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 + 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 } // Accepts a yaml filename to deploy a container lab environment func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { - fmt.Println("Creating new virtual environment with name: ", name) + 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.Println("Failed to deploy the physical network") + fmt.Printf("[%s] - Failed to deploy the physical network\n", name) return nil } @@ -45,7 +54,7 @@ func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { var clabData *clab.ClabConfig clabData, err = clab.LoadConfig(topoYamlFile) if err != nil { - fmt.Println("Failed to load config from yaml file") + fmt.Printf("[%s] - Failed to load config from yaml file\n", name) return nil } @@ -53,46 +62,68 @@ func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { var gosdnAddress string for nodename, val := range clabData.Topology.Nodes { if strings.HasPrefix(nodename, "gosdn") { - fmt.Println("Found gosdn ipv4: ", val.MgmtIPv4) gosdnAddress = val.MgmtIPv4 + ":55055" + fmt.Printf("[%s} - Found gosdn ipv4: %s\n", name, gosdnAddress) } } - fmt.Println("Sleep for 10 seconds to give things time to settle..") - time.Sleep(time.Second * 10) + fmt.Printf("[%s] - Sleep for 5 seconds to give containers time to settle..\n", name) + time.Sleep(time.Second * 5) // Now log into gosdn physical network dialOption := grpc.WithTransportCredentials(insecure.NewCredentials()) - conn, err := grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) + gosdnconn, err := grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions()) if err != nil { - fmt.Println("Failed to create connection to network") + fmt.Printf("[%s] - Failed to create connection to gosdn\n", name) return nil } else { - fmt.Println("Successfully created connection to gosdn") + fmt.Printf("[%s] - Successfully created connection to gosdn\n", name) + fmt.Printf("[%s] - State of conn: \n%v\n\n", name, gosdnconn) } - auth := rtdt_auth.NewRtdtAuth(user, gosdnAddress, pass, conn) // logs in and stores token - if auth == nil { - fmt.Println("Couldn't log in to gosdn physical network, quitting!") + time.Sleep(time.Second * 2) + 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.Println("Successfully logged into gosdn") + 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 - gosdn_pnd, err := gosdnutil.FetchPnd(conn, auth) - if err != nil { - fmt.Println("Failed to fetch pnd from venv") - return nil + var gosdn_pnd *pnd.PrincipalNetworkDomain + for { + gosdn_pnd, err = gosdnutil.FetchPnd(gosdnconn, gosdnauth) + if err == nil { + break + } + gosdnconn.Close() + gosdnconn, err = grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions()) + if err != nil { + fmt.Println("All is over") + return nil + } + gosdnauth = rtdt_auth.NewRtdtAuth("admin", "172.101.0.5:55055", "TestPassword", gosdnconn) + + fmt.Printf("[%s] - Failed to fetch pnd from venv: %v, retrying\n", name, err) + time.Sleep(time.Second * 150) } // Sleep before adding devices because otherwise it won't work // Apply the topoYamlFile: we need to register the switches with the controller - fmt.Println("Sleep for 6 seconds to give things time to settle before creating mne..") - time.Sleep(time.Second * 6) + fmt.Printf("[%s] - Sleep for 7 more seconds to give containers time to settle before creating mne..\n", name) + time.Sleep(time.Second * 7) + 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 - opt := &tpb.TransportOption{ Address: addr, Username: "admin", @@ -106,29 +137,133 @@ func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically pndID, _ := uuid.Parse(gosdn_pnd.GetId()) - fmt.Println("Found target: ", node, " with addr: ", addr) - fmt.Println("Gosdn controller at ", gosdnAddress) - ctx := auth.CreateContextWithAuthorization() + fmt.Printf("[%s] - Found target: %s with addr: %s\n", name, node, addr) + fmt.Printf("[%s] - Gosdn controller at %s\n", name, gosdnAddress) + ctx := gosdnauth.CreateContextWithAuthorization() //listResponse, err := api.AddNetworkElement(ctx, val.MgmtIPv4+":7030", node, "", opt, mneUUID, pid, []string{"/"}) - listResponse, err := api.AddNetworkElement(ctx, gosdnAddress, "TEST", "", opt, pluginID, pndID, []string{"/"}) + listResponse, err := api.AddNetworkElement(ctx, gosdnAddress, node, "", opt, pluginID, pndID, []string{"/interfaces"}) if err != nil { - fmt.Println("Failed to add network elements: ", err) + fmt.Printf("[%s] - Failed to add network elements: %v\n", name, err) return nil } else { - fmt.Println("Successfully created network element!!") + fmt.Printf("[%s] - Successfully created network element\n", name) + } + fmt.Printf("[%s] - Got response from AddNetworkElement: %v\n", name, listResponse) + topoNode.ID = listResponse.GetResponses()[0].GetId() + + fmt.Printf("[%s] - Success: registered mne with gosdn controller\n", name) + fmt.Printf("[%s] - Also created gosdn topo node: %v\n", name, topoNode) + } + 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 + } +} + +// 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 } - fmt.Printf("Got response from AddNetworkElement: %v\n", listResponse) - _ = listResponse // TODO: Might need the id's (are they the UUIDs?) - fmt.Println("Success: registered mne with gosdn controller") } } - return &VEnv{auth: auth, - pnd: gosdn_pnd, - conn: conn, - clabData: clabData, - clabFilename: topoYamlFile, - waitGroup: wg, + 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 { + 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) + + // 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 { + 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 +} + +func (v *VEnv) SetGnmiPath(path, value, mneid string) 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() + fmt.Println("Got to setting pid from v.pnd.Id") + pid := v.pnd.Id + gnmiVal := gnmi.TypedValue{} + req := &networkelement.ChangeRequest{ + Mneid: mneid, + Path: gnmiPath, + Value: &gnmiVal, + ApiOp: networkelement.ApiOperation_API_OPERATION_UPDATE, } + + changeRequests := []*networkelement.ChangeRequest{req} + + setPathResponse, err := mneService.SetPathList(ctx, &networkelement.SetPathListRequest{ + Timestamp: util.Now(), + Pid: pid, + ChangeRequest: changeRequests, + }) + if err != nil { + panic(err) + } + fmt.Println("setPathResponse: ", setPathResponse.String()) + return nil } // For later, topology stuff @@ -140,9 +275,8 @@ func NewVEnv(name, topoYamlFile, user, pass string, wg *sync.WaitGroup) *VEnv { // return nil // } -// This retrieves the topology from the running realnet gosdn instance +// This retrieves the topology from the running gosdn instance // This is needed to generate the clab file to be used with the virtual net -// TODO Solve this not returning anything func (v *VEnv) fetchTopology() error { topoService := topoPb.NewTopologyServiceClient(v.conn) ctx := v.auth.CreateContextWithAuthorization() @@ -182,3 +316,6 @@ func (v VEnv) GetAuth() *rtdt_auth.RtdtAuth { func (v *VEnv) GetWaitgroup() *sync.WaitGroup { return v.waitGroup } +func (v *VEnv) GetTopology() *rtdt_topology.Topology { + return &v.topology +} diff --git a/controller/controller.Dockerfile b/controller/controller.Dockerfile index 88d55fcfb9db250ba4aa1e7f8b86c85a8957a492..84b6101d544afb7deb0ff08e345e32260763de7b 100644 --- a/controller/controller.Dockerfile +++ b/controller/controller.Dockerfile @@ -19,6 +19,7 @@ COPY --from=builder /gosdn/controller/configs/development-gosdn.toml.example ./c COPY --from=builder /gosdn/controller/configs/integration-test-gosdn.toml ./configs/integration-test-gosdn.toml COPY --from=builder /gosdn/controller/configs/containerlab-gosdn.toml.example ./configs/containerlab-gosdn.toml COPY --from=builder /gosdn/controller/configs/gNMISubscriptions.txt.example ./configs/gNMISubscriptions.txt +COPY --from=builder /gosdn/applications/rtdt-manager/data/containerlab-gosdn-twin.toml.example ./configs/containerlab-gosdn-twin.toml EXPOSE 55055 8080 40000 ENTRYPOINT ["./gosdn"] diff --git a/scripts/simple-dev-setup.sh b/scripts/simple-dev-setup.sh index 8f8abeaa23d1016f826bb157a40309e5c8022215..b5df1b9495068f0a819a35557f8bd0348c74e96f 100755 --- a/scripts/simple-dev-setup.sh +++ b/scripts/simple-dev-setup.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash POSITIONAL_ARGS=() KEEPDB='false'