diff --git a/applications/rtdt-manager/gosdnutil/gosdnutil.go b/applications/rtdt-manager/gosdnutil/gosdnutil.go index 95386c30d535705a57893a5f077737d958e23ce1..571eefefcc7834f13ad6d0bee947ee66d0c6f860 100644 --- a/applications/rtdt-manager/gosdnutil/gosdnutil.go +++ b/applications/rtdt-manager/gosdnutil/gosdnutil.go @@ -1,15 +1,17 @@ package gosdnutil import ( + "path/filepath" "time" + "fmt" + mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement" pnd "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd" spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound" tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport" 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" uuid "github.com/google/uuid" "google.golang.org/grpc" ) @@ -33,7 +35,7 @@ func FetchPnd(conn *grpc.ClientConn, auth *rtdt_auth.RtdtAuth) (*pnd.PrincipalNe // This function is necessary because AddNetworkElement from gosdn api uses a shared connection for all created services func AddNetworkElement(rtdtAuth *rtdt_auth.RtdtAuth, addr, mneName, mneUUID string, opt *tpb.TransportOption, pluginID, pid uuid.UUID, gNMISubscribePaths []string) (*mnepb.AddListResponse, error) { - // Here wee use the pb generated code directly instead of using nbi + // Here wee use the pb generated code directly instead of using nbi mneClient := mnepb.NewNetworkElementServiceClient(rtdtAuth.GetConn()) request := &mnepb.AddListRequest{ @@ -56,8 +58,25 @@ func AddNetworkElement(rtdtAuth *rtdt_auth.RtdtAuth, addr, mneName, mneUUID stri request.Mne[0].TransportOption.Type = t default: } - fmt.Println("DEBUG: in AddNetworkElement, request is:", request) + fmt.Println("DEBUG: in AddNetworkElement, request is:", request) listResponse, err := mneClient.AddList(rtdtAuth.CreateContextWithAuthorization(), request) // Return AddListResponse return listResponse, err } + +func ConfigPath() (string, error) { + gosdnPath, err := util.GenerateGosdnPath() + if err != nil { + return "", fmt.Errorf("Error: Couldn't get Gosdn Path: %w\n", err) + } + return filepath.Join(gosdnPath, "/applications/rtdt-manager/data"), nil +} + +func ConfigTmpPath() (string, error) { + gosdnPath, err := util.GenerateGosdnPath() + if err != nil { + return "", fmt.Errorf("Error: Couldn't get Gosdn Path: %w\n", err) + } + return filepath.Join(gosdnPath, "/applications/rtdt-manager/data/tmp"), nil +} + diff --git a/applications/rtdt-manager/main.go b/applications/rtdt-manager/main.go index 4bee9cc7ba850f2ef999391952d02920f680fc10..1c2f7ecf156435fa3ade8dffec27d542984ecaec 100644 --- a/applications/rtdt-manager/main.go +++ b/applications/rtdt-manager/main.go @@ -88,13 +88,14 @@ func main() { } // Do performance tests of realnet before starting twin if benchmark { - fmt.Println("Now doing performance measurements of gosdn's propagation delay ") + fmt.Println("Now doing performance measurements of gosdn's propagation delay") rtdtMan.RunBenchmark0() } if withTwin { rtdtMan.LaunchTwin("172.101.0.0/16", "2001:db9::/64", "test-twin") } + // Runs the main loop if err := rtdtMan.Run(); err != nil { fmt.Println("Program exited with errors: %w", err) } diff --git a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go index ae5d2f77bdc269a678b7754030b68ad770f799a1..c394da00807412a54f1d731bdfee606628ff3563 100644 --- a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go +++ b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go @@ -10,10 +10,13 @@ import ( "sync" "time" + // "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement" + // submanagement "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/subscriptionmanagement" "code.fbi.h-da.de/danet/gosdn/application-framework/event" "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/sdnconfig" + // "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util" "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/venv" ) @@ -166,8 +169,6 @@ func (r *RtdtManager) LaunchTwin(twinSubnetIPv4, twinSubnetIPv6, twinName string } // Transpose the retrieved SdnConfig structs TwinSdnConfig.Transpose(twinName, twinSubnetIPv4, twinSubnetIPv6, prefixLength) - fmt.Println("TwinSdnConfig:") - fmt.Println(TwinSdnConfig) // produce the twin clab configuration based on the transposed sdnconfig and derived clab baseClabConfig, err := clabconfig.DeriveConfig(r.baseClabConfig.Copy(), twinSubnetIPv4, twinSubnetIPv6, twinName) twinClabConfig, err := r.ProduceClabConfig(twinName, &TwinSdnConfig, baseClabConfig) @@ -188,6 +189,13 @@ func (r *RtdtManager) LaunchTwin(twinSubnetIPv4, twinSubnetIPv6, twinName string twin := venv.NewVEnv(twinName, twinClabFName, "admin", "TestPassword", &r.waitGroup, nil) r.rtdt_twins = append(r.rtdt_twins, twin) + + TwinSdnConfig.Plugins = nil + TwinSdnConfig.WriteSdnConfig(twinName + ".json") + err = twin.ApplyConfiguration(&TwinSdnConfig) + if err != nil { + fmt.Printf("Failed to apply configuration: %v\n", err) + } return nil } @@ -277,6 +285,43 @@ func (r *RtdtManager) Run() error { // Receive events from realnet VEnv func (r *RtdtManager) InitEventSystem() error { + // THIS DOESN'T WORK YET + //Make sure that all network elements are subscribed to + //Get all networkelements present in realnet + // getAllReq := &networkelement.GetAllRequest{ + // Timestamp: util.Now(), + // Pid: r.realnet.GetPnd().Id, + // } + // mneServiceClient := networkelement.NewNetworkElementServiceClient(r.realnet.GetAuth().GetConn()) + // response, err := mneServiceClient.GetAll(r.realnet.GetAuth().CreateContextWithAuthorization(), getAllReq) + // if err != nil { + // fmt.Printf("Bad thing happened. Bad thing: %v\n", err) + // return err + // } + // rootpath := &submanagement.Path{ + // Elem: []string{"/"}, + // } + // subManager := submanagement.NewSubscriptionManagementServiceClient(r.realnet.GetAuth().GetConn()) + // for _, mne := range response.GetMne() { + // sub := submanagement.Subscription{ + // Pid: mne.GetAssociatedPnd(), + // Mneid: mne.Id, + // MneName: mne.Name, + // Paths: []*submanagement.Path{rootpath}, // Assign rootpath to Paths + // } + // subAddRequest := &submanagement.AddRequest{ + // Timestamp: util.Now(), + // Mneid: mne.Id, + // Subscription: &sub, + // } + // addResponse, err := subManager.Add(r.realnet.GetAuth().CreateContextWithAuthorization(), subAddRequest) + // if err != nil { + // fmt.Printf("Error while subscribing mnes to events: %v\n", err) + // return err + // } + // fmt.Println("Added subscription:", addResponse) + // } + // fmt.Println("Starting Event System for realnet!") realnet_auth := r.realnet.GetAuth() ctx := realnet_auth.CreateContextWithAuthorization() @@ -309,7 +354,6 @@ func (r *RtdtManager) InitEventSystem() error { {Type: event.Type(event.Update), Callback: r.userEventCallback}, {Type: event.Type(event.Add), Callback: r.userEventCallback}, {Type: event.Type(event.Delete), Callback: r.userEventCallback}, - {Type: event.Type(event.Subscribe), Callback: r.userEventCallback}, }) // Now iterate over all topics of service and create goRoutines // that consumes queue @@ -346,10 +390,16 @@ func (r *RtdtManager) updateMNECallback(event *event.Event) { // 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 { + //gnmiPath, err := ygot.StringToStructuredPath(path) + // if err != nil { + // fmt.Printf("Error: %v\n", err) + // } + // Based on EntityID, get the gnmi target from twin's ClabConfig // First get hostname of realnet node realnetNode := r.realnet.GetTopology().GetNodeByUUID(event.EntityID.String()) + // What's the point of this? var twinEntityID string for _, node := range twin.GetTopology().Nodes { if strings.HasPrefix(node.Name, realnetNode.Name) { diff --git a/applications/rtdt-manager/sdnconfig/sdnconfig.go b/applications/rtdt-manager/sdnconfig/sdnconfig.go index 89032c9ec2e3d5c54ced8e885f5e13963e0bac01..ab29aaf2bf21f83ea547b0abb9c67c28fb9709e5 100644 --- a/applications/rtdt-manager/sdnconfig/sdnconfig.go +++ b/applications/rtdt-manager/sdnconfig/sdnconfig.go @@ -11,12 +11,14 @@ import ( configPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement" "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/conflict" topoPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology" + "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" "github.com/google/uuid" hcplugin "github.com/hashicorp/go-plugin" ) +// These structs mirror roughly the struct definitions from gosdn/controller/northbound/server/configurationmanagement.go type SdnConfig struct { PndID string `json:"pndID"` Nodes []Node `json:"nodes"` @@ -41,6 +43,7 @@ type NetworkElement struct { Plugin string `json:"plugin"` Model string `json:"model"` PndID string `json:"pnd_id"` + GnmiSubscriptionPaths [][]string `json:"gnmi_transcription_paths,omitempty" bson:"gnmi_transcription_paths,omitempty"` } type Plugin struct { @@ -132,16 +135,35 @@ func (s *SdnConfig) LoadSdnConfig(configFilename string) error { return nil } +// This just take the name of the file, it is then placed into the tmp folder of data +func (s *SdnConfig) WriteSdnConfig(configFilename string) error { + confPath, err := gosdnutil.ConfigTmpPath() + if err != nil { + return fmt.Errorf("Couldn't get gosdn path: %w", err) + } + filename := filepath.Join(confPath, configFilename) + var data []byte + data, err = json.MarshalIndent(s, "", " ") + if err != nil { + return fmt.Errorf("Couldn't marshal json struct: %w", err) + } + os.WriteFile(filename, data, 0600) + + return nil +} + // Transpose an sdnconfig struct networkElements // takes name of network, address range in ipv4 and ipv6 (e.g. 172.101.0.0) and ipv4 prefix for network // Limitations: // - No differing prefix lengths between realnet, twin, // - No prefix lengths other than 8, 16, 24 (cidr) +// For now: Also delete plugins and set mne plugins statically func (s *SdnConfig) Transpose(name, addressIpv4, addressIpv6 string, ipv4PrefixLength int) { ipv4Parts := strings.Split(addressIpv4, ".") for i := range s.Nodes { s.Nodes[i].Name += fmt.Sprintf("-%s", name) } + s.Plugins = nil for i, mne := range s.NetworkElements { HostIpv4Parts := strings.Split(s.NetworkElements[i].TransportAddress, ".") var NewIpv4 string @@ -154,12 +176,16 @@ func (s *SdnConfig) Transpose(name, addressIpv4, addressIpv6 string, ipv4PrefixL NewIpv4 = fmt.Sprintf("%s.%s.%s.%s", ipv4Parts[0], ipv4Parts[1], ipv4Parts[2], HostIpv4Parts[3]) default: fmt.Printf("Error: unsupported ipv4 prefix length\n") - + continue } + // TODO This is a workaround for a bug with plugins not working + s.NetworkElements[i].Plugin = "d1c269a2-6482-4010-b0d8-679dff73153b" s.NetworkElements[i].Name += fmt.Sprintf("-%s", name) // TransportAddress has form 172.100.0.5:7030 s.NetworkElements[i].TransportAddress = NewIpv4 - s.NetworkElements[i].Model = s.transposeModelIpv4(mne.Model, mne.TransportAddress, addressIpv6, ipv4PrefixLength) + fmt.Println("Transport Address:", s.NetworkElements[i].TransportAddress) + + s.NetworkElements[i].Model = s.transposeModel(mne.Model, addressIpv4, mne.TransportAddress, ipv4PrefixLength, addressIpv6) } for i := range s.Links { s.Links[i].SourceNode.Name += fmt.Sprintf("-%s", name) @@ -167,13 +193,23 @@ func (s *SdnConfig) Transpose(name, addressIpv4, addressIpv6 string, ipv4PrefixL } } -func (s *SdnConfig) transposeModelIpv4(model, ipv4, oldIpv4 string, ipv4PrefixLength int) string { +func (s *SdnConfig) transposeModel(model, ipv4, oldIpv4 string, ipv4PrefixLength int, ipv6 string) string { ipv4Parts := strings.Split(ipv4, ".") OldIpv4Parts := strings.Split(oldIpv4, ".") networkPortionLength := ipv4PrefixLength / 8 - oldNetworkPortion := strings.Join(OldIpv4Parts[:networkPortionLength-1], ".") - newNetworkPortion := strings.Join(ipv4Parts[:networkPortionLength-1], ".") - strings.ReplaceAll(model, oldNetworkPortion, newNetworkPortion) + oldNetworkPortion := strings.Join(OldIpv4Parts[:networkPortionLength], ".") + fmt.Println("transposeModel(): oldNetworkPortion =", oldNetworkPortion) + newNetworkPortion := strings.Join(ipv4Parts[:networkPortionLength], ".") + fmt.Println("transposeModel(): newNetworkPortion =", newNetworkPortion) + model = strings.ReplaceAll(model, oldNetworkPortion, newNetworkPortion) + + // Hacky ipv6 replacement, doesn't work yet.. + // ipv6Regex := regexp.MustCompile(`([0-9a-fA-F:]+:+[0-9a-fA-F]*)`) + // model = ipv6Regex.ReplaceAllString(model, ipv6) + + fmt.Println("---------------- NEW MODEL --------------------") + fmt.Println(model) + fmt.Println("---------------- NEW MODEL --------------------") return model } @@ -220,16 +256,6 @@ func (s *SdnConfig) UploadSdnConfig(pndId string, auth rtdt_auth.RtdtAuth) error return nil } -// Applying an SdnConfig means parsing it and then: -// - Get plugins -> Create them? -// - Get network elements -> Create them -// - Create topology from it -> Create that too -// - Only if the data matches will the sdnconfig apply -func (s *SdnConfig) ApplySdnConfig(auth *rtdt_auth.RtdtAuth) error { - - return nil -} - func (s *SdnConfig) GetMneByID(ID string) *NetworkElement { for _, mne := range s.NetworkElements { if mne.ID == ID { diff --git a/applications/rtdt-manager/test-config/downloaded-config.json b/applications/rtdt-manager/test/downloaded-config.json similarity index 98% rename from applications/rtdt-manager/test-config/downloaded-config.json rename to applications/rtdt-manager/test/downloaded-config.json index ccfe2ec8bca9c87c0aa08163d058b957bb34dc46..593387a8e505dc00be44d1843cde32e91d9d8fbe 100644 --- a/applications/rtdt-manager/test-config/downloaded-config.json +++ b/applications/rtdt-manager/test/downloaded-config.json @@ -281,7 +281,10 @@ "transport_tls": true, "plugin": "d1c269a2-6482-4010-b0d8-679dff73153b", "model": "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"config\":{\"enabled\":true,\"mtu\":1500,\"name\":\"eth0\"},\"name\":\"eth0\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1025,\"loopback-mode\":false,\"oper-status\":\"UP\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv4\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"172.100.0.11\",\"prefix-length\":16},\"ip\":\"172.100.0.11\"}]}},\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2001:db8::a\",\"prefix-length\":64},\"ip\":\"2001:db8::a\"}]}}},{\"config\":{\"index\":1},\"index\":1,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"fe80::42:acff:fe64:b\",\"prefix-length\":64},\"ip\":\"fe80::42:acff:fe64:b\"}]}}}]}},{\"config\":{\"enabled\":true,\"mtu\":9500,\"name\":\"eth1\"},\"name\":\"eth1\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1027,\"loopback-mode\":false,\"oper-status\":\"UP\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"fe80::a8c1:abff:fe42:2347\",\"prefix-length\":64},\"ip\":\"fe80::a8c1:abff:fe42:2347\"}]}}}]}},{\"config\":{\"enabled\":true,\"mtu\":9500,\"name\":\"eth2\"},\"name\":\"eth2\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1021,\"loopback-mode\":false,\"oper-status\":\"UP\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"fe80::a8c1:abff:fe80:e8d8\",\"prefix-length\":64},\"ip\":\"fe80::a8c1:abff:fe80:e8d8\"}]}}}]}},{\"config\":{\"enabled\":false,\"mtu\":0,\"name\":\"lo\"},\"name\":\"lo\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1,\"loopback-mode\":true,\"oper-status\":\"UNKNOWN\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv4\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"127.0.0.1\",\"prefix-length\":8},\"ip\":\"127.0.0.1\"}]}},\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"::1\",\"prefix-length\":128},\"ip\":\"::1\"}]}}}]}}]},\"openconfig-network-instance:network-instances\":{\"network-instance\":[{\"config\":{\"name\":\"DEFAULT\"},\"name\":\"DEFAULT\",\"protocols\":{\"protocol\":[{\"config\":{\"identifier\":\"openconfig-policy-types:STATIC\",\"name\":\"STATIC\"},\"identifier\":\"openconfig-policy-types:STATIC\",\"name\":\"STATIC\",\"static-routes\":{\"static\":[{\"config\":{\"prefix\":\"0.0.0.0/0\"},\"next-hops\":{\"next-hop\":[{\"config\":{\"index\":\"AUTO_172.100.0.1\",\"next-hop\":\"172.100.0.1\"},\"index\":\"AUTO_172.100.0.1\",\"interface-ref\":{\"config\":{\"interface\":\"eth0\"}}}]},\"prefix\":\"0.0.0.0/0\"},{\"config\":{\"prefix\":\"::/0\"},\"next-hops\":{\"next-hop\":[{\"config\":{\"index\":\"AUTO_2001:db8::1\",\"next-hop\":\"2001:db8::1\"},\"index\":\"AUTO_2001:db8::1\",\"interface-ref\":{\"config\":{\"interface\":\"eth0\"}}}]},\"prefix\":\"::/0\"}]}}]}}]},\"openconfig-system:system\":{\"clock\":{\"config\":{\"timezone-name\":\"UTC\"}},\"config\":{\"domain-name\":\"Not.implemented.yet\",\"hostname\":\"gnmi-target-switch0\",\"motd-banner\":\"\\nThe programs included with the Debian GNU/Linux system are free software;\\nthe exact distribution terms for each program are described in the\\nindividual files in /usr/share/doc/*/copyright.\\n\\nDebian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent\\npermitted by applicable law.\\n\"},\"memory\":{\"state\":{\"free\":\"6126836\",\"physical\":\"32767196\",\"used\":\"26640360\"}},\"state\":{\"boot-time\":\"1740559631\",\"current-datetime\":\"2025-02-26T19:18:35Z\",\"software-version\":\"debian:12\"}}}", - "pnd_id": "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d" + "pnd_id": "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d", + "gnmi_transcription_paths": [ + ["/interfaces"] + ] }, { "id": "f14ccc06-0143-4196-9aba-7812fae01d11", @@ -293,7 +296,10 @@ "transport_tls": true, "plugin": "d1c269a2-6482-4010-b0d8-679dff73153b", "model": "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"config\":{\"enabled\":true,\"mtu\":1500,\"name\":\"eth0\"},\"name\":\"eth0\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1023,\"loopback-mode\":false,\"oper-status\":\"UP\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv4\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"172.100.0.12\",\"prefix-length\":16},\"ip\":\"172.100.0.12\"}]}},\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"2001:db8::9\",\"prefix-length\":64},\"ip\":\"2001:db8::9\"}]}}},{\"config\":{\"index\":1},\"index\":1,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"fe80::42:acff:fe64:c\",\"prefix-length\":64},\"ip\":\"fe80::42:acff:fe64:c\"}]}}}]}},{\"config\":{\"enabled\":true,\"mtu\":9500,\"name\":\"eth1\"},\"name\":\"eth1\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1028,\"loopback-mode\":false,\"oper-status\":\"UP\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"fe80::a8c1:abff:fec8:de01\",\"prefix-length\":64},\"ip\":\"fe80::a8c1:abff:fec8:de01\"}]}}}]}},{\"config\":{\"enabled\":true,\"mtu\":9500,\"name\":\"eth2\"},\"name\":\"eth2\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1019,\"loopback-mode\":false,\"oper-status\":\"UP\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"fe80::a8c1:abff:fe5f:5950\",\"prefix-length\":64},\"ip\":\"fe80::a8c1:abff:fe5f:5950\"}]}}}]}},{\"config\":{\"enabled\":false,\"mtu\":0,\"name\":\"lo\"},\"name\":\"lo\",\"state\":{\"admin-status\":\"UP\",\"ifindex\":1,\"loopback-mode\":true,\"oper-status\":\"UNKNOWN\"},\"subinterfaces\":{\"subinterface\":[{\"config\":{\"index\":0},\"index\":0,\"openconfig-if-ip:ipv4\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"127.0.0.1\",\"prefix-length\":8},\"ip\":\"127.0.0.1\"}]}},\"openconfig-if-ip:ipv6\":{\"addresses\":{\"address\":[{\"config\":{\"ip\":\"::1\",\"prefix-length\":128},\"ip\":\"::1\"}]}}}]}}]},\"openconfig-network-instance:network-instances\":{\"network-instance\":[{\"config\":{\"name\":\"DEFAULT\"},\"name\":\"DEFAULT\",\"protocols\":{\"protocol\":[{\"config\":{\"identifier\":\"openconfig-policy-types:STATIC\",\"name\":\"STATIC\"},\"identifier\":\"openconfig-policy-types:STATIC\",\"name\":\"STATIC\",\"static-routes\":{\"static\":[{\"config\":{\"prefix\":\"0.0.0.0/0\"},\"next-hops\":{\"next-hop\":[{\"config\":{\"index\":\"AUTO_172.100.0.1\",\"next-hop\":\"172.100.0.1\"},\"index\":\"AUTO_172.100.0.1\",\"interface-ref\":{\"config\":{\"interface\":\"eth0\"}}}]},\"prefix\":\"0.0.0.0/0\"},{\"config\":{\"prefix\":\"::/0\"},\"next-hops\":{\"next-hop\":[{\"config\":{\"index\":\"AUTO_2001:db8::1\",\"next-hop\":\"2001:db8::1\"},\"index\":\"AUTO_2001:db8::1\",\"interface-ref\":{\"config\":{\"interface\":\"eth0\"}}}]},\"prefix\":\"::/0\"}]}}]}}]},\"openconfig-system:system\":{\"clock\":{\"config\":{\"timezone-name\":\"UTC\"}},\"config\":{\"domain-name\":\"Not.implemented.yet\",\"hostname\":\"gnmi-target-switch1\",\"motd-banner\":\"\\nThe programs included with the Debian GNU/Linux system are free software;\\nthe exact distribution terms for each program are described in the\\nindividual files in /usr/share/doc/*/copyright.\\n\\nDebian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent\\npermitted by applicable law.\\n\"},\"memory\":{\"state\":{\"free\":\"6126836\",\"physical\":\"32767196\",\"used\":\"26640360\"}},\"state\":{\"boot-time\":\"1740559631\",\"current-datetime\":\"2025-02-26T19:18:35Z\",\"software-version\":\"debian:12\"}}}", - "pnd_id": "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d" + "pnd_id": "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d", + "gnmi_transcription_paths": [ + ["system", "config", "hostname"] + ] } ] } diff --git a/applications/rtdt-manager/test-config/main.go b/applications/rtdt-manager/test/main.go similarity index 59% rename from applications/rtdt-manager/test-config/main.go rename to applications/rtdt-manager/test/main.go index 9783d44a074bdfc1c94f723406859f5802361b39..1ae90414c4a7ddc718e945ce34b592f0a88ae8aa 100644 --- a/applications/rtdt-manager/test-config/main.go +++ b/applications/rtdt-manager/test/main.go @@ -9,10 +9,13 @@ import ( "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement" "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd" + spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound" + tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport" "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" + "github.com/google/uuid" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -61,6 +64,74 @@ func GetMne(name string) { } } +func CreateMnes() { + gosdn_pnd, testauth := SetupGosdnConn() + if gosdn_pnd == nil || testauth == nil { + fmt.Println("ERROR: couldn't establish connection to gosdn") + } + pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically + + mneClient := networkelement.NewNetworkElementServiceClient(testauth.GetConn()) + + gNMISubscribePaths := []string{"/"} + opt0 := &tpb.TransportOption{ + Address: "172.100.0.11:7030", + Username: "admin", + Password: "admin", + Tls: true, + TransportOption: &tpb.TransportOption_GnmiTransportOption{ + GnmiTransportOption: &tpb.GnmiTransportOption{}, + }, + } + opt1 := &tpb.TransportOption{ + Address: "172.100.0.12:7030", + Username: "admin", + Password: "admin", + Tls: true, + TransportOption: &tpb.TransportOption_GnmiTransportOption{ + GnmiTransportOption: &tpb.GnmiTransportOption{}, + }, + } + request := &networkelement.AddListRequest{ + Timestamp: time.Now().UnixNano(), + Mne: []*networkelement.SetMne{ + { + Address: opt0.GetAddress(), + MneName: "gnmi-target-switch0", + PluginId: pluginID.String(), + Pid: gosdn_pnd.String(), + TransportOption: opt0, + GnmiSubscribePaths: gNMISubscribePaths, + //MneId: , + }, + { + Address: opt1.GetAddress(), + MneName: "gnmi-target-switch0", + PluginId: pluginID.String(), + Pid: gosdn_pnd.String(), + TransportOption: opt1, + GnmiSubscribePaths: gNMISubscribePaths, + //MneId: , + }, + }, + Pid: pluginID.String(), + } + switch t := opt0.Type; t { + case spb.Type_TYPE_CONTAINERISED, spb.Type_TYPE_PLUGIN: + request.Mne[0].TransportOption.Type = t + default: + } + + if listResponse, err := mneClient.AddList(testauth.CreateContextWithAuthorization(), request); err != nil { + fmt.Printf("Error when adding network elements:%v", err) + return + } else { + fmt.Printf("Got response from AddNetworkElement: %v\n", listResponse) + fmt.Printf("Success: registered mne with gosdn controller\n") + } + +} + func Download() { gosdn_pnd, testauth := SetupGosdnConn() if gosdn_pnd == nil || testauth == nil { @@ -107,16 +178,19 @@ func main() { flag.Parse() switch mode { + case "create_mne": + fmt.Println("Mode: create_mne") + CreateMnes() case "download": - fmt.Println("Mode: Download") + fmt.Println("Mode: download") Download() return case "upload": - fmt.Println("Mode: Upload") + fmt.Println("Mode: upload") Upload(sdnConfigPath) return case "get": - fmt.Println("Mode: Get") + fmt.Println("Mode: get") GetMne("gnmi-target-switch0") default: fmt.Println("Unknown Mode!") diff --git a/controller/configs/gNMISubscriptions.txt.example b/controller/configs/gNMISubscriptions.txt.example index 56361a17f54806f87bdc86d8f2587d241785f2c2..2dac2553e3068ef390e301277410dda646f2bf8a 100644 --- a/controller/configs/gNMISubscriptions.txt.example +++ b/controller/configs/gNMISubscriptions.txt.example @@ -1 +1,3 @@ system/config/domain-name +/ +system/config/hostname diff --git a/controller/northbound/server/configurationmanagement.go b/controller/northbound/server/configurationmanagement.go index 71dd2e2df3316de6c5b166c7cfbc12c2c9c42570..dc16ef0d69e32d545dafd526a7967a4240021afe 100644 --- a/controller/northbound/server/configurationmanagement.go +++ b/controller/northbound/server/configurationmanagement.go @@ -9,6 +9,7 @@ import ( cmpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement" spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound" tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport" + "code.fbi.h-da.de/danet/gosdn/controller/config" "code.fbi.h-da.de/danet/gosdn/controller/conflict" "code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain" "code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement" @@ -29,13 +30,14 @@ import ( // ConfigurationManagementServer represents ConfigurationManagementServer... type ConfigurationManagementServer struct { cmpb.UnimplementedConfigurationManagementServiceServer - pndService networkdomain.Service - mneService networkelement.Service - topologyService topology.Service - nodeService nodes.Service - portService ports.Service - pluginService plugin.Service - protoValidator *protovalidate.Validator + pndService networkdomain.Service + mneService networkelement.Service + topologyService topology.Service + nodeService nodes.Service + portService ports.Service + pluginService plugin.Service + protoValidator *protovalidate.Validator + networkElementWatcher *nucleus.NetworkElementWatcher } // NewConfigurationManagementServer creates the ConfigurationManagementServer.. @@ -47,15 +49,17 @@ func NewConfigurationManagementServer( portService ports.Service, pluginService plugin.Service, protoValidator *protovalidate.Validator, + networkElementWatcher *nucleus.NetworkElementWatcher, ) *ConfigurationManagementServer { return &ConfigurationManagementServer{ - pndService: pndService, - mneService: mneService, - topologyService: topologyService, - nodeService: nodeService, - portService: portService, - pluginService: pluginService, - protoValidator: protoValidator, + pndService: pndService, + mneService: mneService, + topologyService: topologyService, + nodeService: nodeService, + portService: portService, + pluginService: pluginService, + protoValidator: protoValidator, + networkElementWatcher: networkElementWatcher, } } @@ -333,14 +337,15 @@ func (c ConfigurationManagementServer) createNetworkElements(sdnConfig *loadedSD // TODO: change TransportOption - type is not needed; this should // be removed as soon as we remove the csbi device type Type: spb.Type_TYPE_OPENCONFIG, - Tls: inputNetworkElement.TransportTLS, + Tls: inputNetworkElement.TransportTLS, } plugin, err := c.pluginService.RequestPlugin(uuid.MustParse(inputNetworkElement.Plugin)) if err != nil { return err } - fmt.Println(inputNetworkElement.Name, "DEBUG CHECK 0") + fmt.Println("In createNetworkElements() ----") + fmt.Println("GnmiSubscriptionPaths:", inputNetworkElement.GnmiSubscriptionPaths) createdNetworkElement, err := nucleus.NewNetworkElement( inputNetworkElement.Name, @@ -355,7 +360,6 @@ func (c ConfigurationManagementServer) createNetworkElements(sdnConfig *loadedSD return err } - fmt.Println(inputNetworkElement.Name, "DEBUG CHECK 1") if err := c.mneService.Add(createdNetworkElement); err != nil { return err } @@ -364,7 +368,6 @@ func (c ConfigurationManagementServer) createNetworkElements(sdnConfig *loadedSD return err } - fmt.Println(inputNetworkElement.Name, "DEBUG CHECK 2") err = c.mneService.UpdateModel(createdNetworkElement.ID(), inputNetworkElement.Model) if err != nil { return err @@ -375,15 +378,15 @@ func (c ConfigurationManagementServer) createNetworkElements(sdnConfig *loadedSD return err } - networkElementAsString, err := networkElement.GetModelAsString() - if err != nil { - return err - } - fmt.Println(inputNetworkElement.Name, "DEBUG CHECK 3, networkElement:", networkElementAsString) if err := networkelement.EnsureIntendedConfigurationIsAppliedOnNetworkElement(networkElement); err != nil { return err } - fmt.Println(inputNetworkElement.Name, "DEBUG CHECK 4") + if createdNetworkElement.GetGnmiSubscriptionPaths() != nil || config.GetGnmiSubscriptionPaths() != nil { + fmt.Println("--- Creating GNMI Subscription for mne:", createdNetworkElement.Name(), "----") + fmt.Println("- GNMI Subscribe Paths:", createdNetworkElement.GetGnmiSubscriptionPaths(), "-") + fmt.Println("config.GetGnmiSubscriptionPaths():", config.GetGnmiSubscriptionPaths()) + c.networkElementWatcher.SubscribeToNetworkElement(createdNetworkElement, nil) + } } return nil } diff --git a/controller/northbound/server/nbi.go b/controller/northbound/server/nbi.go index 5657e3e5ad43a7137827dfeb5f2f1f734045394b..92a3c7ba7ba6c7dfd893655836f295714f2ad8b1 100644 --- a/controller/northbound/server/nbi.go +++ b/controller/northbound/server/nbi.go @@ -78,7 +78,7 @@ func NewNBI( App: NewAppServer(apps, protoValidator), NetworkElement: NewNetworkElementServer(mneService, pndService, pluginService, changeStore, protoValidator, networkElementWatchter), Routes: NewRoutingTableServiceServer(routeService, nodeService, portService, protoValidator), - ConfigurationManagement: NewConfigurationManagementServer(pndService, mneService, topologyService, nodeService, portService, pluginService, protoValidator), + ConfigurationManagement: NewConfigurationManagementServer(pndService, mneService, topologyService, nodeService, portService, pluginService, protoValidator, networkElementWatchter), SubManagement: NewSubManagementServer(networkElementWatchter), } } diff --git a/controller/northbound/server/networkElement.go b/controller/northbound/server/networkElement.go index 4a3db1c4facc3b772a12d5f7ac9ebcb1710c8698..6ea85a4895a51a448c829984fbf064474addea89 100644 --- a/controller/northbound/server/networkElement.go +++ b/controller/northbound/server/networkElement.go @@ -643,6 +643,7 @@ func (n *NetworkElementServer) addMne(ctx context.Context, } }() + fmt.Println("-------- addMne() called --------") networkElementID := uuid.Nil if len(optionalNetworkElementID) > 0 { networkElementID = optionalNetworkElementID[0] @@ -661,6 +662,9 @@ func (n *NetworkElementServer) addMne(ctx context.Context, if err != nil { return uuid.Nil, err } + if mne != nil { + fmt.Println("--- addMne() gnmi subscribe paths:", mne.GetGnmiSubscriptionPaths()) + } if mne.IsTransportValid() { err = n.initialNetworkElementRootPathRequest(ctx, mne) diff --git a/controller/nucleus/networkElement.go b/controller/nucleus/networkElement.go index 7eb5fa6e865bcbc0677f5813e80c122dfead0c06..e8f16e5cc7b30cd9adbe6acac68534b469b2731e 100644 --- a/controller/nucleus/networkElement.go +++ b/controller/nucleus/networkElement.go @@ -2,6 +2,7 @@ package nucleus import ( "encoding/json" + "fmt" spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound" tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport" @@ -38,6 +39,7 @@ func NewNetworkElement( if name == "" { name = namesgenerator.GetRandomName(0) } + fmt.Println("In \"NewNetworkElement():\"", gnmiSubscriptionPaths) if opt.Type == spb.Type_TYPE_CONTAINERISED { return &CsbiNetworkElement{