From 9d801680752af0f77e484e28fcca0c05a43cf73b Mon Sep 17 00:00:00 2001
From: "S.H." <sebastian.heiss94@proton.me>
Date: Tue, 11 Mar 2025 11:31:19 +0100
Subject: [PATCH] Work on subscription with configurationmanagement

---
 .../rtdt-manager/gosdnutil/gosdnutil.go       | 25 +++++-
 applications/rtdt-manager/main.go             |  3 +-
 .../rtdt-manager/rtdt-manager/rtdt-manager.go | 56 ++++++++++++-
 .../rtdt-manager/sdnconfig/sdnconfig.go       | 58 ++++++++++----
 .../downloaded-config.json                    | 10 ++-
 .../{test-config => test}/main.go             | 80 ++++++++++++++++++-
 .../configs/gNMISubscriptions.txt.example     |  2 +
 .../server/configurationmanagement.go         | 51 ++++++------
 controller/northbound/server/nbi.go           |  2 +-
 .../northbound/server/networkElement.go       |  4 +
 controller/nucleus/networkElement.go          |  2 +
 11 files changed, 240 insertions(+), 53 deletions(-)
 rename applications/rtdt-manager/{test-config => test}/downloaded-config.json (98%)
 rename applications/rtdt-manager/{test-config => test}/main.go (59%)

diff --git a/applications/rtdt-manager/gosdnutil/gosdnutil.go b/applications/rtdt-manager/gosdnutil/gosdnutil.go
index 95386c30d..571eefefc 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 4bee9cc7b..1c2f7ecf1 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 ae5d2f77b..c394da008 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 89032c9ec..ab29aaf2b 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 ccfe2ec8b..593387a8e 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 9783d44a0..1ae90414c 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 56361a17f..2dac2553e 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 71dd2e2df..dc16ef0d6 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 5657e3e5a..92a3c7ba7 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 4a3db1c4f..6ea85a489 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 7eb5fa6e8..e8f16e5cc 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{
-- 
GitLab