diff --git a/applications/rtdt-manager/app/app.go b/applications/rtdt-manager/app/app.go index 43572373a2d1ee83559c9ce2a6212fc61b7bbe1f..e0d03fd72380fa578559a85a766e2fd0c0d3e3d3 100644 --- a/applications/rtdt-manager/app/app.go +++ b/applications/rtdt-manager/app/app.go @@ -3,52 +3,30 @@ package app import ( "fmt" "os" - "os/signal" - "syscall" "code.fbi.h-da.de/danet/gosdn/application-framework/event" "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config" - "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth" "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-manager" - "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util" - "google.golang.org/grpc" ) -// An App manages one realnet instance of gosdn and one (or potentially more) twin +// An App wraps an RtdtManager instance and makes sure the program stays open type App struct { - gosdnPath string // Absolute root of gosdn repo on system - conn *grpc.ClientConn // connection to realnet gosdn - stopChan chan os.Signal - managers []*rtdtmanager.RtdtManager // future consideration: launch multiple siblings + manager *rtdtmanager.RtdtManager + stopChan <-chan struct{} } -func NewApp(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth) *App { - gosdnPath, err := util.GenerateGosdnPath() - if err != nil { - return nil - } +func NewApp(manager *rtdtmanager.RtdtManager, stopChan chan os.Signal) *App { return &App{ - gosdnPath: gosdnPath, - conn: conn, - stopChan: make(chan os.Signal), // TODO: Should this be shared globally? + manager: manager, } } -func (app *App) AddManager(rMan *rtdtmanager.RtdtManager) { - app.managers = append(app.managers, rMan) -} - -func (app App) GetGosdnPath() string { - return app.gosdnPath -} - // Run the app with manager, catch events and react to them func (app *App) Run() error { - if len(app.managers) == 0 { - fmt.Println("You did not register any managers, please do so before executing App.Run()") + if app.manager == nil { + fmt.Println("You did not register a manager, please do so before executing App.Run()") return nil } - signal.Notify(app.stopChan, os.Interrupt, syscall.SIGSTOP) // Run until SIGINT, SIGTERM is received // Based on "code.fbi.h-da.de/danet/gosdn/applications/hostname-checker/app.go" diff --git a/applications/rtdt-manager/clab-config/clab-config.go b/applications/rtdt-manager/clab-config/clab-config.go index 04b694c576b46b3657a47a7a3045c10e00746147..2640398dd4ed478c6699d0e053ade4d7f45fb1cb 100644 --- a/applications/rtdt-manager/clab-config/clab-config.go +++ b/applications/rtdt-manager/clab-config/clab-config.go @@ -183,23 +183,22 @@ func ClabDestroy(fullPath string) error { fmt.Println("Output of containerlab: ", string(output)) time.Sleep(20 * time.Second) return err - } // Launch a containerlab environment, pass in absolute path of clab yaml func ClabDeploy(fullPath string) error { + fmt.Println("Deploying file: ", fullPath) cmd := exec.Command("sudo", "containerlab", "deploy", "-t", fullPath, "--reconfigure") - + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr // Run the command in a Goroutine done := make(chan error, 1) stopdeploy := make(chan os.Signal, 1) signal.Notify(stopdeploy, os.Interrupt, syscall.SIGTERM) go func() { - output, err := cmd.CombinedOutput() // Use CombinedOutput to capture stdout and stderr + err := cmd.Run() // Use CombinedOutput to capture stdout and stderr if err != nil { fmt.Printf("Error during deployment: %s\n", err) - } else { - fmt.Println(string(output)) } done <- err close(done) // Ensure the channel is closed after sending the result @@ -208,7 +207,7 @@ func ClabDeploy(fullPath string) error { // Wait for the deployment to finish or a signal to stop select { case err := <-done: // Command finished - fmt.Println("Successfully deployed the twin") + fmt.Println("Successfully deployed containerlab environment") return err case <-stopdeploy: // Signal received to interrupt if err := cmd.Process.Kill(); err != nil { diff --git a/applications/rtdt-manager/gosdnutil/gosdnutil.go b/applications/rtdt-manager/gosdnutil/gosdnutil.go new file mode 100644 index 0000000000000000000000000000000000000000..80cc1a36f89d985346fed182aa62dd4bfd2a35e2 --- /dev/null +++ b/applications/rtdt-manager/gosdnutil/gosdnutil.go @@ -0,0 +1,27 @@ +package gosdnutil + +import ( + pnd "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd" + rtdt_auth "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth" + "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util" + + "fmt" + "google.golang.org/grpc" +) + +func FetchPnd(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth) (*pnd.PrincipalNetworkDomain, error) { + var p *pnd.PrincipalNetworkDomain + pndService := pnd.NewPndServiceClient(conn) + 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) + } + if pndList := pndResponse.GetPnd(); pndList != nil { + fmt.Println("pndUUID is now: ", pndList[0].Id) + p = pndList[0] // TODO: Only supports one pnd for now + return p, nil + } else { + return nil, fmt.Errorf("Failed to retrieve PND from Pnd List: %w", err) + } +} diff --git a/applications/rtdt-manager/main.go b/applications/rtdt-manager/main.go index f78258b0b22c16404d9f4a9362a9cd6dc93b20b1..aa3d10578d1ff23a97b963a261f3331364f94946 100644 --- a/applications/rtdt-manager/main.go +++ b/applications/rtdt-manager/main.go @@ -3,58 +3,69 @@ package main import ( "flag" "fmt" + "os" + "os/signal" + "sync" + "syscall" - app "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/app" - rtdt_auth "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth" RtdtMan "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-manager" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + venv "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/venv" ) func main() { - var url string + // Global stop channel, should be passed to all venvs and App + stopChan := make(chan os.Signal, 1) + signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM) + var wg sync.WaitGroup + + var address string var pass string var user string var topology_file string - flag.StringVar(&url, "url", "172.100.0.5:55055", "Address of the gosdn controller") - flag.StringVar(&pass, "p", "TestPassword", "Password for admin user") - flag.StringVar(&user, "u", "admin", "Username") + var generate 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.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.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") + } flag.Parse() - fmt.Println("Trying to connect to gosdn controller at ", url) fmt.Println("Topology file path: ", topology_file) - - dialOption := grpc.WithTransportCredentials(insecure.NewCredentials()) - conn, err := grpc.NewClient(url, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) - if err != nil { - fmt.Println("Failed to create connection") - return + 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") + return + } + } else { + fmt.Println("Generate flag not set!") } - fmt.Println("Created connection successfully") - defer conn.Close() - auth := rtdt_auth.NewRtdtAuth(user, url, pass, conn) // logs in and stores token - if auth == nil { - fmt.Println("Couldn't log in to gosdn, quitting!") - return - } - rtdt_app := app.NewApp(conn, auth) - rtdtMan := RtdtMan.NewRtdtManager(conn, auth, topology_file) + // Register realnet with rtdt-manager + rtdtMan := RtdtMan.NewRtdtManager(realnet, &wg) if rtdtMan == nil { fmt.Println("Couldn't initialize rtdt-manager, quitting!") return } - // Register manager with app and run - - rtdt_app.AddManager(rtdtMan) - // If twin should be launched, uncomment: - //rtdtMan.LaunchTwin("172.101.0.0/16", "2001:db9::/64", "twin") - - err = rtdt_app.Run() - if err != nil { - fmt.Println("Encountered error in app.Run(): ", err) - } else { - fmt.Println("Ran without errors") + 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 new file mode 100644 index 0000000000000000000000000000000000000000..9a5fc663cba73b58fe59de96840e59a429697e9a --- /dev/null +++ b/applications/rtdt-manager/mneadd/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "fmt" + "time" + + pnd "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd" + tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport" + "code.fbi.h-da.de/danet/gosdn/controller/api" + "github.com/google/uuid" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" +) + +func main() { + gosdn_addr := "172.100.0.5:55055" + addr := "172.100.0.11:7030" + var sessionToken string + + // Create Connection + dialOption := grpc.WithTransportCredentials(insecure.NewCredentials()) + conn, err := grpc.NewClient(gosdn_addr, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) + // Log in to controller + // Register + loginResponse, err := api.Login(context.Background(), gosdn_addr, "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") + time.Sleep(time.Second) + } + sessionToken = loginResponse.GetToken() + + // Create Context with Authorization + md := metadata.Pairs("authorize", sessionToken) + ctx := 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())}) + if err != nil { + fmt.Println("Failed to fetch PND") + return + } else { + fmt.Println("Successfully got PND") + time.Sleep(time.Second) + } + + // Add MNE + p = pndResponse.GetPnd()[0] + pndID, _ := uuid.Parse(p.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 + + pluginID, _ := uuid.Parse("e2c358b3-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically + //fmt.Println("pluginID: ", pluginID) + + opt := &tpb.TransportOption{ + Address: addr, + Username: "admin", + Password: "admin", + Tls: true, + TransportOption: &tpb.TransportOption_GnmiTransportOption{ + GnmiTransportOption: &tpb.GnmiTransportOption{}, + }, + } + listResponse, err := api.AddNetworkElement(ctx, gosdn_addr, "1234", "", opt, pluginID, pndID, nil) + if err != nil { + fmt.Println("Failed to add network element: ", err) + return + } else { + fmt.Println("Successfylly added network element!") + } + _ = listResponse +} diff --git a/applications/rtdt-manager/rtdt-auth/rtdt-auth.go b/applications/rtdt-manager/rtdt-auth/rtdt-auth.go index e024051d835702305d97583959c34947998eb654..ae15f67b5d3ce7934ed7c978faf46f8fc5cade0d 100644 --- a/applications/rtdt-manager/rtdt-auth/rtdt-auth.go +++ b/applications/rtdt-manager/rtdt-auth/rtdt-auth.go @@ -12,7 +12,7 @@ import ( type RtdtAuth struct { userName string password string - url string + address string sessionTok string } @@ -29,7 +29,7 @@ func NewRtdtAuth(userName, url, password string, conn *grpc.ClientConn) *RtdtAut return &RtdtAuth{ userName: userName, password: password, - url: url, + address: url, sessionTok: loginResponse.GetToken(), } } @@ -41,9 +41,15 @@ func (r *RtdtAuth) CreateContextWithAuthorization() context.Context { } // Silly {g,s}etters -func (r *RtdtAuth) GetSessionToken() string { +func (r RtdtAuth) GetSessionToken() string { return r.sessionTok } -func (r *RtdtAuth) GetURL() string { - return r.url +func (r RtdtAuth) GetAddress() string { + return r.address +} +func (r RtdtAuth) GetUsername() string { + return r.userName +} +func (r RtdtAuth) GetPassword() string { + return r.password } diff --git a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go index fd94b4fe7152412b1a3d4b09f061375e83cec027..78b267a6432f1ee049ab29ff8321c3cb58956e7c 100644 --- a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go +++ b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go @@ -3,154 +3,39 @@ package rtdtmanager import ( "fmt" "os" - "os/signal" "path/filepath" - "strings" - "syscall" - "time" + "sync" "code.fbi.h-da.de/danet/gosdn/application-framework/event" - "code.fbi.h-da.de/danet/gosdn/application-framework/registration" - "code.fbi.h-da.de/danet/gosdn/controller/api" - uuid "github.com/google/uuid" //"code.fbi.h-da.de/danet/gosdn/csbi/cmd" - confManPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement" - pnd "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" clabconfig "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config" - "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth" - util "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util" - "google.golang.org/grpc" + "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/venv" ) +// Manages an environment containing one physical network (realnet) and a digital twin type RtdtManager struct { - auth *rtdt_auth.RtdtAuth // auth struct for realnet gosdn - conn *grpc.ClientConn // connection to twin's gosdn instance - Pnd *pnd.PrincipalNetworkDomain // PND for realnet gosdn - topo *topoPb.Topology // Topology with which to create clab.yaml - sdnConfig string // TODO - clabFilename string // Clab file that exemplifies the topology of realnet - clabTwinFilename string - clabData *clabconfig.ClabConfig // Represents the yaml config file as data structure - eventService event.ServiceInterface // Receive events from realnet gosdn (not used yet) - stopChan chan os.Signal // Global stop channel TODO Can I use that like this? + realnet *venv.VEnv + 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? } -func NewRtdtManager(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth, clabFilename string) *RtdtManager { +// needs to be passed a running realnet VEnv +func NewRtdtManager(realnet *venv.VEnv, wg *sync.WaitGroup) *RtdtManager { rMan := RtdtManager{ - conn: conn, - auth: auth, - clabFilename: clabFilename, - stopChan: make(chan os.Signal, 1), - } - signal.Notify(rMan.stopChan, os.Interrupt, syscall.SIGTERM) - - if err := rMan.fetchPndUUID(); err != nil { - fmt.Println(err) - return nil - } - // Event system for realnet gosdn - if err := rMan.initEventSystem(); err != nil { - fmt.Println("Failed to initialize event system: ", err) - return nil - } - - // Set up the twin here - // The topology of the currently running realnet needs to be parsed. We get this - // from the clab.yaml file that was used to create it, that means that --topolog needs to point - // to it - // TODO Implement - var err error - if rMan.clabData, err = clabconfig.LoadConfig(rMan.clabFilename); err != nil { - fmt.Println(err) - return nil + realnet: realnet, + waitGroup: wg, + stopChan: make(chan os.Signal, 1), } fmt.Println("Success: RtdtManager created") return &rMan } -func (rMan *RtdtManager) fetchPndUUID() error { - pndService := pnd.NewPndServiceClient(rMan.conn) - ctx := rMan.auth.CreateContextWithAuthorization() - pndResponse, err := pndService.GetPndList(ctx, &pnd.GetPndListRequest{Timestamp: util.Now()}) - if err != nil { - return fmt.Errorf("Failed to retrieve PND information: %w", err) - } - if pndList := pndResponse.GetPnd(); pndList != nil { - fmt.Println("pndUUID is now: ", pndList[0].Id) // TODO If more than 1, let user choose - rMan.Pnd = pndList[0] - return nil - } else { - return fmt.Errorf("Failed to retrieve PND from Pnd List: %w", err) - } -} - -func (rMan *RtdtManager) applyTopology() error { - topoService := topoPb.NewTopologyServiceClient(rMan.conn) - ctx := rMan.auth.CreateContextWithAuthorization() - topoService.AddLink(ctx, &topoPb.AddLinkRequest{Timestamp: util.Now()}) - - return nil -} - -// \cite venv-manager -// Keep for later -// Write a clab.yaml file to launch a different -// func (rMan *RtdtManager) writeModifiedTopologyToFile(clabStruct *containerlab.YamlStruct) error { -// rMan.topoData.Mgmt.Network = "gosdn-csbi-arist-twin-net" -// -// splitMainNetwork := strings.Split(rMan.topoData.Mgmt.Ipv4Subnet, ".") -// splitMainNetwork[1] = "101" -// rMan.topoData.Mgmt.Ipv4Subnet = strings.Join(splitMainNetwork, ".") -// -// splitMainNetworkIPv6 := strings.Split(rMan.topoData.Mgmt.Ipv6Subnet, ":") -// splitMainNetworkIPv6[1] = "db9" -// rMan.topoData.Mgmt.Ipv6Subnet = strings.Join(splitMainNetworkIPv6, ":") -// -// // Different network for our twin -// for i, node := range rMan.topoData.Topology.Nodes { -// splitIPv4 := strings.Split(node.MgmtIpv4, ".") -// splitIPv4[1] = "101" -// node.MgmtIpv4 = strings.Join(splitIPv4, ".") -// rMan.topoData.Topology.Nodes[i] = node -// } -// yaml, err := yaml.Marshal(clabStruct) -// if err != nil { -// return err -// } -// -// fname := "./topo.clab.tmp.yaml" -// err = os.WriteFile(fname, yaml, 0600) -// if err != nil { -// return err -// } // -// return nil -// } - -// This retrieves the topology from the running realnet gosdn instance -// This is needed to generate the clab file to be used with the virtual net -// TODO Solve this not returning anything -func (rMan *RtdtManager) fetchTopology() error { - topoService := topoPb.NewTopologyServiceClient(rMan.conn) - ctx := rMan.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()) - } - rMan.topo = topoResponse.Toplogy - return nil -} // To launch a new twin, this runs through the following steps: // - Load the clab config for current realnet gosdn (passed on cli) @@ -163,129 +48,89 @@ func (r *RtdtManager) LaunchTwin(twinSubnetIPv4, twinSubnetIPv6, twinName string var derivedConfig *clabconfig.ClabConfig var clabConfigPath string var err error + baseConfig := r.realnet.GetClabData() - if r.clabData == nil { - err = fmt.Errorf("Can't launch a twin without loading a clab yaml file for physical network first") - return err - } - if derivedConfig, err = clabconfig.DeriveConfig(r.clabData, twinSubnetIPv4, twinSubnetIPv6, twinName); err != nil { + if derivedConfig, err = clabconfig.DeriveConfig(baseConfig, twinSubnetIPv4, twinSubnetIPv6, twinName); err != nil { return fmt.Errorf("Failed to derive config for twin: %w", err) } + // Construct filepath for derived clab yaml clabConfigPath, err = clabconfig.ClabConfigPath() clabConfigFullPath := filepath.Join(clabConfigPath, "twin-clab.yaml") if err = clabconfig.WriteConfig(clabConfigFullPath, derivedConfig); err != nil { return fmt.Errorf("Failed to write modified twin clab config to disk: %w", err) } + // Now run deploy with new config file clabconfig.ClabDeploy(clabConfigFullPath) - - // TODO For each switch in the topology, run mne create - for node, val := range r.clabData.Topology.Nodes { - if strings.HasPrefix(node, "gnmi-target-") { - fmt.Println("Found target: ", node) - opt := &tpb.TransportOption{ - Address: val.MgmtIPv4, - Username: "admin", - Password: "admin", - TransportOption: &tpb.TransportOption_GnmiTransportOption{ - GnmiTransportOption: &tpb.GnmiTransportOption{}, - }, - } - ctx := r.auth.CreateContextWithAuthorization() - mneUUID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") - pid, _ := uuid.Parse(r.Pnd.GetId()) - // addListResponse, err := api.AddNetworkElement(ctx, val.MgmtIPv4, node, nil, uuid, opt, r.Pnd.GetId(), []string{"/"}) - api.AddNetworkElement(ctx, val.MgmtIPv4, node, "", opt, mneUUID, pid, []string{"/"}) - } - } + user := r.realnet.GetAuth().GetUsername() + password := r.realnet.GetAuth().GetPassword() + twin := venv.NewVEnv(twinName, clabConfigFullPath, user, password, r.waitGroup) + r.rtdt_twins = append(r.rtdt_twins, twin) return nil } -// TODO research what is actually returned from the api call -func (rMan *RtdtManager) manageConfig(op string) error { - switch op { - // Import the configuration into rtdt-manager (by exporting from gosdn) - case "import": - fmt.Println("Now importing sdnConfig into rtdt-manager") - confManService := confManPb.NewConfigurationManagementServiceClient(rMan.conn) - ctx := rMan.auth.CreateContextWithAuthorization() - exportSdnConfigResponse, err := confManService.ExportSDNConfig(ctx, &confManPb.ExportSDNConfigRequest{Timestamp: int64(time.Now().Nanosecond())}) - if err != nil { - return fmt.Errorf("Failed to retrieve config file of running goSDN instance: %w", err) - } - rMan.sdnConfig = exportSdnConfigResponse.SdnConfigData +// Start the eventsystem for each venv, manage clean up +func (r *RtdtManager) Run() error { + if r.realnet == nil { + fmt.Println("You did not start the physical network, please do so before executing App.Run()") return nil - // Export from rtdt-manager into gosdn controller - case "export": - fmt.Println("Now exporting sdnConfig into gosdn controller") - //TODO Implement me some day - return nil - default: - return fmt.Errorf("Unknown config operation") - } -} - -// Each Manager must have its own event system -// It receives events based on topics -func (rMan *RtdtManager) initEventSystem() error { - ctx := rMan.auth.CreateContextWithAuthorization() - queueCredentials, err := registration.Register(ctx, rMan.auth.GetURL(), "basic-interface-monitoring", "SecurePresharedToken") - fmt.Println("Acquired queueCredentials: ", queueCredentials) - // TODO: Find out how I can receive the ip address here (it returns rabbitmq) - queueCredentials = "amqp://guest:guest@172.100.0.15:5672/" // fix by setting manually - if err != nil { - return fmt.Errorf("Encountered error while trying to register event system: %w", err) - } - fmt.Println("Trying to register with amqp with following queueCredentials: ", queueCredentials) - // You have to have one event service for a topic - eventServiceMNE, err := event.NewEventService(queueCredentials, []event.Topic{event.ManagedNetworkElement}) - if err != nil { - return fmt.Errorf("Failed to attach to event system: %w", err) } - eventServiceUser, err := event.NewEventService(queueCredentials, []event.Topic{event.User}) - if err != nil { - return fmt.Errorf("Failed to attach to event system: %w", err) - } - - // Can have different callback per type per topic (e.g. adding mne or updating mne) - eventServiceMNE.SubscribeToEventType([]event.TypeToCallbackTuple{ - {Type: event.Type(event.Update), Callback: rMan.updateMNECallback}, - {Type: event.Type(event.Add), Callback: rMan.updateMNECallback}, - {Type: event.Type(event.Delete), Callback: rMan.updateMNECallback}, - {Type: event.Type(event.Subscribe), Callback: rMan.updateMNECallback}, - }) - eventServiceUser.SubscribeToEventType([]event.TypeToCallbackTuple{ - {Type: event.Type(event.Update), Callback: rMan.userEventCallback}, - {Type: event.Type(event.Add), Callback: rMan.userEventCallback}, - {Type: event.Type(event.Delete), Callback: rMan.userEventCallback}, - {Type: event.Type(event.Subscribe), Callback: rMan.userEventCallback}, - }) - // Now iterate over all topics of service and create goRoutines - // that consumes queue - // This function is supposed to be removed in the future? - eventServiceMNE.SetupEventReciever(make(chan os.Signal, 1)) // doesn't seem to use stop channel internally.. - eventServiceUser.SetupEventReciever(make(chan os.Signal, 1)) // doesn't seem to use stop channel internally.. - fmt.Println("Subscribed to events user and mne") + if len(r.rtdt_twins) < 1 { + fmt.Println("You did not start any twins, do so first") + } + + // Run until SIGINT, SIGTERM is received + // Based on "code.fbi.h-da.de/danet/gosdn/applications/hostname-checker/app.go" + go func() { + for { + select { + // idea: make channels for different events? + case stop := <-r.stopChan: + fmt.Print("Received SIGINT/SIGSTOP, quitting..\n") + // this takes full path now + for _, twin := range r.rtdt_twins { + twin_path := twin.GetClabFilePath() + err := clabconfig.ClabDestroy(twin_path) + if err != nil { + fmt.Printf("Error occured while cleaning up twin with name %s: %v\n", twin.GetName(), err) + } + twin.GetWaitgroup().Done() // indicate twin is done + } + err := clabconfig.ClabDestroy(r.realnet.GetClabFilePath()) + if err != nil { + fmt.Printf("Couldn't clean up physical network: %v\n", err) + } + r.realnet.GetWaitgroup().Done() + _ = stop + return + } + } + }() + // This waits for as long as not all venvs have called waitGroup.Done() + r.waitGroup.Wait() return nil } -func (rMan *RtdtManager) updateMNECallback(event *event.Event) { - fmt.Println("MNE Event has happened (type: update), hurray!") - fmt.Println("EventID: ", event.ID.ID()) - fmt.Println("Event Type: ", event.Type) -} - -func (rMan *RtdtManager) userEventCallback(event *event.Event) { - fmt.Println("User Event has happened (type: update), hurray!") - fmt.Println("EventID: ", event.ID.ID()) - fmt.Println("Event Type: ", event.Type) -} - -// {Get,Set}ers -func (r RtdtManager) GetClabFilename() string { - return r.clabFilename -} - -func (r RtdtManager) GetClabTwinFilename() string { - return r.clabTwinFilename -} +// TODO research what is actually returned from the api call +// func (rMan *RtdtManager) manageConfig(op string) error { +// switch op { +// // Import the configuration into rtdt-manager (by exporting from gosdn) +// case "import": +// fmt.Println("Now importing sdnConfig into rtdt-manager") +// confManService := confManPb.NewConfigurationManagementServiceClient(rMan.conn) +// ctx := rMan.auth.CreateContextWithAuthorization() +// exportSdnConfigResponse, err := confManService.ExportSDNConfig(ctx, &confManPb.ExportSDNConfigRequest{Timestamp: int64(time.Now().Nanosecond())}) +// if err != nil { +// return fmt.Errorf("Failed to retrieve config file of running goSDN instance: %w", err) +// } +// rMan.sdnConfig = exportSdnConfigResponse.SdnConfigData +// return nil +// // Export from rtdt-manager into gosdn controller +// case "export": +// fmt.Println("Now exporting sdnConfig into gosdn controller") +// //TODO Implement me some day +// return nil +// default: +// return fmt.Errorf("Unknown config operation") +// } +// } diff --git a/applications/rtdt-manager/venv/venv.go b/applications/rtdt-manager/venv/venv.go new file mode 100644 index 0000000000000000000000000000000000000000..24d1ed2b6f5f69da193e1dcf78f297f2150b6b6c --- /dev/null +++ b/applications/rtdt-manager/venv/venv.go @@ -0,0 +1,239 @@ +package venv + +import ( + "fmt" + "os" + "strings" + "sync" + "time" + + "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" + "code.fbi.h-da.de/danet/gosdn/application-framework/event" + "code.fbi.h-da.de/danet/gosdn/application-framework/registration" + 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/util" + "code.fbi.h-da.de/danet/gosdn/controller/api" + uuid "github.com/google/uuid" + "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 +} + +// 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) + 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") + return nil + } + + // After having deployed it, load the config into clabData + var clabData *clab.ClabConfig + clabData, err = clab.LoadConfig(topoYamlFile) + if err != nil { + fmt.Println("Failed to load config from yaml file") + return nil + } + + // get gosdn address inside clab environment + 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" + } + } + + // Now log into gosdn physical network + dialOption := grpc.WithTransportCredentials(insecure.NewCredentials()) + conn, err := grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) + if err != nil { + fmt.Println("Failed to create connection to network") + return nil + } else { + fmt.Println("Successfully created connection to gosdn") + } + 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!") + return nil + } else { + fmt.Println("Successfully logged into gosdn") + } + // 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 + } + + // Sleep before adding devices because otherwise it won't work + // Apply the topoYamlFile: we need to register the switches with the controller + for node, val := range clabData.Topology.Nodes { + time.Sleep(time.Second * 6) + if strings.HasPrefix(node, "gnmi-target-") { + //ports := strings.Split(val.Ports[0], ":") + //port := ports[0] + addr := val.MgmtIPv4 + ":" + "7030" + + opt := &tpb.TransportOption{ + Address: addr, + Username: "admin", + Password: "admin", + Tls: true, + TransportOption: &tpb.TransportOption_GnmiTransportOption{ + GnmiTransportOption: &tpb.GnmiTransportOption{}, + }, + } + // Openconfig pluginid + 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() + //listResponse, err := api.AddNetworkElement(ctx, val.MgmtIPv4+":7030", node, "", opt, mneUUID, pid, []string{"/"}) + listResponse, err := api.AddNetworkElement(ctx, gosdnAddress, "TEST", "", opt, pluginID, pndID, nil) + if err != nil { + fmt.Println("Failed to add network elements: ", err) + return nil + } else { + fmt.Println("Successfully created network element!!") + } + 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, + } +} +func (v *VEnv) initEventSystem() error { + + realnet_auth := v.auth + ctx := realnet_auth.CreateContextWithAuthorization() + queueCredentials, err := registration.Register(ctx, realnet_auth.GetAddress(), "basic-interface-monitoring", "SecurePresharedToken") + fmt.Println("Acquired queueCredentials: ", queueCredentials) + // TODO: Find out how I can receive the ip address here (it returns rabbitmq) + queueCredentials = "amqp://guest:guest@172.100.0.15:5672/" // TODO: retrieve this from clab struct + if err != nil { + return fmt.Errorf("Encountered error while trying to register event system: %w", err) + } + fmt.Println("Trying to register with amqp with following queueCredentials: ", queueCredentials) + // You have to have one event service for a topic + eventServiceMNE, err := event.NewEventService(queueCredentials, []event.Topic{event.ManagedNetworkElement}) + if err != nil { + return fmt.Errorf("Failed to attach to event system: %w", err) + } + eventServiceUser, err := event.NewEventService(queueCredentials, []event.Topic{event.User}) + if err != nil { + return fmt.Errorf("Failed to attach to event system: %w", err) + } + + // Can have different callback per type per topic (e.g. adding mne or updating mne) + eventServiceMNE.SubscribeToEventType([]event.TypeToCallbackTuple{ + {Type: event.Type(event.Update), Callback: v.updateMNECallback}, + {Type: event.Type(event.Add), Callback: v.updateMNECallback}, + {Type: event.Type(event.Delete), Callback: v.updateMNECallback}, + {Type: event.Type(event.Subscribe), Callback: v.updateMNECallback}, + }) + eventServiceUser.SubscribeToEventType([]event.TypeToCallbackTuple{ + {Type: event.Type(event.Update), Callback: v.userEventCallback}, + {Type: event.Type(event.Add), Callback: v.userEventCallback}, + {Type: event.Type(event.Delete), Callback: v.userEventCallback}, + {Type: event.Type(event.Subscribe), Callback: v.userEventCallback}, + }) + // Now iterate over all topics of service and create goRoutines + // that consumes queue + // This function is supposed to be removed in the future? + eventServiceMNE.SetupEventReciever(make(chan os.Signal, 1)) // doesn't seem to use stop channel internally.. + eventServiceUser.SetupEventReciever(make(chan os.Signal, 1)) // doesn't seem to use stop channel internally.. + fmt.Println("Subscribed to events user and mne") + return nil +} + +func (v *VEnv) updateMNECallback(event *event.Event) { + fmt.Println("MNE Event has happened (type: update), hurray!") + fmt.Println("EventID: ", event.ID.ID()) + fmt.Println("Event Type: ", event.Type) +} + +func (v *VEnv) userEventCallback(event *event.Event) { + fmt.Println("User Event has happened (type: update), hurray!") + fmt.Println("EventID: ", event.ID.ID()) + fmt.Println("Event Type: ", event.Type) +} + +// 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 realnet 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() + 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 +}