Skip to content
Snippets Groups Projects
venv.go 18.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	configPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement"
    
    	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
    
    	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
    	topoPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
    	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
    	clab "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
    	clabconfig "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
    	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/gosdnutil"
    	rtdt_auth "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-auth"
    
    	rtdt_topology "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/rtdt-topology"
    
    	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/sdnconfig"
    
    	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
    
    	yangparser "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/yang-parser"
    	"code.fbi.h-da.de/danet/gosdn/models/generated/openconfig"
    
    	uuid "github.com/google/uuid"
    
    	gnmi "github.com/openconfig/gnmi/proto/gnmi"
    	"github.com/openconfig/ygot/ygot"
    
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    
    	Name                 string
    	auth                 *rtdt_auth.RtdtAuth
    	conn                 *grpc.ClientConn // The connection to this specific environment's gosdn
    	pnd                  *pnd.PrincipalNetworkDomain
    	clabData             *clabconfig.ClabConfig // Represents yaml file that was used to deploy
    
    	sdnConfig            *sdnconfig.SdnConfig   // Represents json config file for configuration grpc interface
    	topology             *rtdt_topology.Topology
    	clabFilename         string // This is the name of the yaml file clabData is based on
    
    	StopChan             <-chan struct{}
    	waitGroup            *sync.WaitGroup
    	containerRegistryURL string
    
    	savedChanges         []*networkelement.ChangeRequest
    
    }
    
    // Accepts a yaml filename to deploy a container lab environment
    
    // TODO Split up into sub-functions
    
    func NewVEnv(name, clabFilename, user, pass string, wg *sync.WaitGroup) *VEnv {
    
    	fmt.Printf("[%s] - Creating new virtual environment\n", name)
    
    	wg.Add(1) // Register the venv and run atleast until it calls wg.Done()
    	var err error
    
    	//clabFilename := name + "-clab.yaml"
    
    	// TODO:
    	// Use baseClab and sdnConfig to construct a config file for containerlab
    
    	if err = clab.ClabDeploy(clabFilename); err != nil {
    		fmt.Printf("[%s] - Failed to deploy the network\n", name)
    
    		return nil
    	}
    
    	// After having deployed it, load the config into clabData
    	var clabData *clab.ClabConfig
    
    	clabData, err = clab.LoadConfig(clabFilename)
    
    		fmt.Printf("[%s] - Failed to load config from yaml file\n", name)
    
    		return nil
    	}
    	// get gosdn address inside clab environment
    	var gosdnAddress string
    	for nodename, val := range clabData.Topology.Nodes {
    		if strings.HasPrefix(nodename, "gosdn") {
    			gosdnAddress = val.MgmtIPv4 + ":55055"
    
    			fmt.Printf("[%s} - Found gosdn ipv4: %s\n", name, gosdnAddress)
    
    	fmt.Printf("[%s] - Sleep for 15 seconds to give containers time to settle..\n", name)
    	time.Sleep(time.Second * 15)
    
    	// Now log into gosdn physical network
    	dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())
    
    	gosdnconn, err := grpc.NewClient(gosdnAddress, dialOption, grpc.WithDefaultCallOptions())
    
    		fmt.Printf("[%s] - Failed to create connection to gosdn\n", name)
    
    		fmt.Printf("[%s] - Successfully created connection to gosdn\n", name)
    
    	gosdnauth := rtdt_auth.NewRtdtAuth(user, gosdnAddress, pass, gosdnconn) // logs in and stores token
    	if gosdnauth == nil {
    		fmt.Printf("[%s] - Couldn't log in to gosdn, quitting!\n", name)
    
    		fmt.Printf("[%s] - Successfully logged into gosdn as user: %s, with password: %s, session token: %v\n", name, gosdnauth.GetUsername(), gosdnauth.GetPassword(), gosdnauth.GetSessionToken())
    
    	}
    	// Get PND of gosdn in created venv
    
    	var gosdn_pnd *pnd.PrincipalNetworkDomain
    	for {
    		gosdn_pnd, err = gosdnutil.FetchPnd(gosdnconn, gosdnauth)
    		if err == nil {
    			break
    		}
    
    		fmt.Printf("[%s] - Couldn't retrieve PND, retrying in 2 seconds..\n", name)
    
    	// load topo into DB via API
    	return &VEnv{
    		auth:                 gosdnauth,
    		pnd:                  gosdn_pnd,
    		conn:                 gosdnconn,
    		clabData:             clabData,
    
    		waitGroup:            wg,
    		topology:             nil,                                            // set this later
    		containerRegistryURL: "registry.code.fbi.h-da.de/danet/gnmi-target/", // TODO: Could let user choose
    	}
    }
    
    func (v *VEnv) CreateDevices() error {
    	for _, node := range v.topology.Nodes {
    		if strings.HasPrefix(node.Name, "gnmi-target-") {
    			fmt.Printf("[%s] - Creating Network Element for node: %s\n", v.Name, node.Name)
    			//ports := strings.Split(node.Ports[0], ":")
    			//port := ports[1]
    			addr := node.MgmtIpv4 + ":7030" // gnmi targets will always listen on 7030
    
    			opt := &tpb.TransportOption{
    				Address:  addr,
    				Username: "admin",
    				Password: "admin",
    				Tls:      true,
    				TransportOption: &tpb.TransportOption_GnmiTransportOption{
    					GnmiTransportOption: &tpb.GnmiTransportOption{},
    				},
    			}
    
    			// Openconfig pluginid? TODO decide
    
    			pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically
    
    			pndID, _ := uuid.Parse(v.pnd.Id)
    
    			fmt.Printf("[%s] - Found target: %s with addr: %s\n", v.Name, node.Name, addr)
    			fmt.Printf("[%s] - Gosdn controller at %s\n", v.Name, v.auth.GetAddress())
    			listResponse, err := gosdnutil.AddNetworkElement(v.auth, v.auth.GetAddress(), node.Name, node.ID, opt, pluginID, pndID, []string{"/"})
    
    				fmt.Printf("[%s] - Failed to add network elements: %v\n", v.Name, err)
    
    				fmt.Printf("[%s] - Successfully created network element\n", v.Name)
    
    			fmt.Printf("[%s] - Got response from AddNetworkElement: %v\n", v.Name, listResponse)
    
    			fmt.Printf("[%s] - Success: registered mne with gosdn controller\n", v.Name)
    
    }
    
    // Source: "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/venv-manager/venv-manager.go"
    // commit: 0264b698286b6cbb965d743078c681f8af55edf6
    
    func (v *VEnv) loadNetworkElementModelPathsIntoGosdn(ctx context.Context, conn *grpc.ClientConn, nodes *[]*rtdt_topology.Node) error {
    
    	networkElementService := networkelement.NewNetworkElementServiceClient(conn)
    
    	paths := [2]string{"/lldp/config/system-description", "/system/state/"}
    	for _, path := range paths {
    		for _, node := range *nodes {
    			_, err := networkElementService.GetPath(ctx, &networkelement.GetPathRequest{Mneid: node.ID, Pid: v.pnd.Id, Path: path})
    			if err != nil {
    				return err
    
    	return nil
    }
    
    // Based on getAndAddMoreData() in: "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/venv-manager/venv-manager.go"
    // commit: 0264b698286b6cbb965d743078c681f8af55edf6
    // What this does: Load yang paths into topo, then iterate over
    // nodes and fill data in
    func (v *VEnv) ConstructTopology() error {
    
    	if v.clabData == nil {
    		return fmt.Errorf("Error: Trying to construct topology without containerlab file being loaded first")
    	}
    	// Either fill topology from clabData or from database?
    
    	ctx := v.auth.CreateContextWithAuthorization()
    	conn := v.conn
    	var path = "/"
    	var ygotPath *gnmi.Path
    
    	// Create 'root' path to be able to load the whole model from the store.
    	var err error
    	ygotPath, err = ygot.StringToPath(path, ygot.StructuredPath)
    	if err != nil {
    		return err
    	}
    
    	// just to load model data into goSDN to guaranteed have new data available for get request
    	err = v.loadNetworkElementModelPathsIntoGosdn(ctx, conn, &v.topology.Nodes)
    	if err != nil {
    		return err
    	}
    
    	networkElementService := networkelement.NewNetworkElementServiceClient(conn)
    
    	for iterator, node := range v.topology.Nodes {
    		getNetworkElementResponse, _ := networkElementService.Get(ctx, &networkelement.GetRequest{Mneid: node.ID})
    		if err != nil {
    			return err
    		}
    
    		var marshalledYangData openconfig.Device
    
    		err = yangparser.Unmarshal([]byte(getNetworkElementResponse.Mne.Model), ygotPath, &marshalledYangData)
    		if err != nil {
    			return err
    		}
    
    		mgmntAddress := strings.Split(getNetworkElementResponse.Mne.TransportAddress, ":")
    		v.topology.Nodes[iterator].MgmtIpv4 = mgmntAddress[0]
    		v.topology.Nodes[iterator].YangData = marshalledYangData
    		v.topology.Nodes[iterator].FillAllFields(v.containerRegistryURL)
    	}
    
    	return nil
    }
    
    
    // Create rtdt_topology.Topology from clabconfig.Topology
    func (v *VEnv) DeriveTopologyFromClabData() error {
    	if v.clabData == nil {
    		return fmt.Errorf("Can't derive topology without clabData\n")
    	}
    	v.topology = &rtdt_topology.Topology{}
    	var topoNodes []*rtdt_topology.Node
    	// Get all the nodes from clab structure
    	for nodeName, node := range v.clabData.Topology.Nodes {
    		topoNode := rtdt_topology.Node{
    			ID:       uuid.NewString(),
    			Name:     nodeName,
    			Kind:     node.Kind,
    			Image:    node.Image,
    			MgmtIpv4: node.MgmtIPv4,
    		}
    		topoNodes = append(topoNodes, &topoNode)
    	}
    	v.topology.Nodes = topoNodes
    	for _, link := range v.clabData.Topology.Links {
    		if len(link.Endpoints) != 2 {
    			return fmt.Errorf("Couldn't parse clabData into topology\n")
    		}
    		var topoLink = rtdt_topology.Link{}
    		// determine Node name and port
    		node0Full := strings.Split(link.Endpoints[0], ":")
    		node0Name := node0Full[0]
    		node0Port := node0Full[1]
    		node1Full := strings.Split(link.Endpoints[1], ":")
    		node1Name := node1Full[0]
    		node1Port := node1Full[1]
    
    		// find the node that has right name, add links and ports
    		topoLink.SourceNode = v.topology.GetNodeByName(node0Name)
    		topoLink.SourcePort = &rtdt_topology.Port{Name: node0Port, ID: uuid.NewString()}
    		v.topology.Ports = append(v.topology.Ports, topoLink.SourcePort)
    		topoLink.TargetNode = v.topology.GetNodeByName(node1Name)
    		topoLink.TargetPort = &rtdt_topology.Port{Name: node1Port, ID: uuid.NewString()}
    		v.topology.Ports = append(v.topology.Ports, topoLink.TargetPort)
    		v.topology.Links = append(v.topology.Links, &topoLink)
    		v.topology.Nodes = topoNodes
    	}
    	return nil
    }
    
    
    func (v *VEnv) ApplyRoutes() error {
    	conn := v.auth.GetConn()
    	hostIP := int64(1)
    	for _, link := range v.topology.Links {
    		req := topoPb.AddRoutingTableRequest{
    			Timestamp: util.Now(),
    			RoutingTable: &topoPb.RoutingTable{
    				Id: uuid.NewString(),
    				Routes: []*topoPb.Route{
    					{
    						Id:            uuid.NewString(),
    						TargetIPRange: "16",
    						NextHopIP:     "192.168.178." + strconv.FormatInt(hostIP, 10),
    						PortID:        link.SourcePort.ID,
    					},
    				},
    			},
    		}
    		hostIP++
    		routeService := topoPb.NewRoutingTableServiceClient(conn)
    
    		ctx := v.auth.CreateContextWithAuthorization()
    		reply, err := routeService.AddRoutingTable(ctx, &req)
    		if err != nil {
    			return fmt.Errorf("AddRoutingTable failed: %v\n", err)
    		}
    		fmt.Println("Successfully added Link:", reply)
    	}
    	return nil
    }
    
    
    // TODO: Fix hardcoded IP addresses
    
    func (v *VEnv) UploadTopology() error {
    	conn := v.auth.GetConn()
    	topoService := topoPb.NewTopologyServiceClient(conn)
    	for _, link := range v.topology.Links {
    		ctx := v.auth.CreateContextWithAuthorization()
    
    
    		l := link.Convert()
    		l.Name = l.SourceNode.Name + ":" + l.TargetNode.Name
    		l.SourcePort.Configuration = &topoPb.Configuration{Ip: "192.168.178.2", PrefixLength: 24}
    		l.TargetPort.Configuration = &topoPb.Configuration{Ip: "192.168.178.3", PrefixLength: 24}
    
    
    		addLinkRequest := &topoPb.AddLinkRequest{
    			Timestamp: util.Now(),
    
    			Link:      l,
    
    		}
    		fmt.Println("AddLink is:", addLinkRequest.String())
    		topoResponse, err := topoService.AddLink(ctx, addLinkRequest)
    		if err != nil {
    			return err
    		}
    		fmt.Printf("Successfully uploaded Link to DB: %s\n", topoResponse.String())
    	}
    	return nil
    }
    
    
    // What this does: Upload the nodes that make up the gosdn environment
    // via the gosdn configuration service to the db (applies to db, message broker, etc.)
    // This is probably not the intended use for the configuration interface?
    func (v *VEnv) UploadClabConfig() error {
    	if v.clabData == nil {
    		return fmt.Errorf("Can't upload clabconfig when it hasn't been loaded yet (is nil pointer)")
    	}
    	var clabToUpload = *v.clabData
    	for nodename, node := range v.clabData.Topology.Nodes {
    		if !strings.Contains(nodename, "gosdn") &&
    			!strings.Contains(nodename, "mongodb") &&
    			!strings.Contains(nodename, "rabbitmq") &&
    			!strings.Contains(nodename, "plugin-registry") {
    			fmt.Println("Removing node:", node)
    			delete(clabToUpload.Topology.Nodes, nodename)
    		}
    	}
    	clabToUpload.Topology.Links = nil
    
    	clabConfig, err := yaml.Marshal(clabToUpload)
    	if err != nil {
    		return fmt.Errorf("Encountered error marshalling clabConfig: %w", err)
    	}
    	var jsonData interface{}
    	if err := yaml.Unmarshal(clabConfig, &jsonData); err != nil {
    		return fmt.Errorf("failed to unmarshal YAML to JSON: %w", err)
    	}
    	jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
    	if err != nil {
    		return fmt.Errorf("Failed to marshal JSON: %w", err)
    	}
    
    	req := &configPb.ImportSDNConfigRequest{
    		Timestamp:     util.Now(),
    		Pid:           v.pnd.GetId(), // TODO: check if this can be nil
    		SdnConfigData: string(jsonBytes),
    	}
    	conn := v.auth.GetConn()
    	configService := configPb.NewConfigurationManagementServiceClient(conn)
    	ctx := v.auth.CreateContextWithAuthorization()
    	configResponse, err := configService.ImportSDNConfig(ctx, req)
    	if err != nil {
    		return err
    	}
    	fmt.Println("Successfully uploaded clab config:", configResponse.String())
    	return nil
    }
    
    
    // TODO This doesn't really work this way..
    
    func (v *VEnv) RetrieveClabConfig() error {
    	ctx := v.auth.CreateContextWithAuthorization()
    	conn := v.auth.GetConn()
    	configService := configPb.NewConfigurationManagementServiceClient(conn)
    
    	req := &configPb.ExportSDNConfigRequest{
    		Timestamp: util.Now(),
    		Pid:       v.pnd.GetId(),
    	}
    
    	configResponse, err := configService.ExportSDNConfig(ctx, req)
    	if err != nil {
    		return fmt.Errorf("Failed retrieving config clab data: %w", err)
    	}
    	var jsonData map[string]interface{}
    	err = json.Unmarshal([]byte(configResponse.SdnConfigData), &jsonData)
    	if err != nil {
    		return fmt.Errorf("Failed to unmarshal SDN config data: %w", err)
    	}
    
    	// Pretty-print the JSON data
    
    	formattedJSON, err := json.MarshalIndent(jsonData, "", "  ")
    	if err != nil {
    		return fmt.Errorf("failed to format JSON data: %w", err)
    	}
    
    	fmt.Println("Retrieved SDN Config Data:")
    	fmt.Println(string(formattedJSON)) // Print the formatted JSON
    
    	return nil
    
    }
    
    
    func getTypedValue(value string) *gnmi.TypedValue {
    	if boolVal, err := strconv.ParseBool(value); err == nil {
    		return &gnmi.TypedValue{Value: &gnmi.TypedValue_BoolVal{BoolVal: boolVal}}
    
    	} else if uintVal, err := strconv.ParseUint(value, 10, 64); err == nil {
    		return &gnmi.TypedValue{Value: &gnmi.TypedValue_UintVal{UintVal: uintVal}}
    
    	} else if intVal, err := strconv.ParseInt(value, 10, 64); err == nil {
    		return &gnmi.TypedValue{Value: &gnmi.TypedValue_IntVal{IntVal: intVal}}
    	} else {
    		return &gnmi.TypedValue{Value: &gnmi.TypedValue_StringVal{StringVal: value}}
    	}
    }
    
    
    func (v *VEnv) SetGnmiPath(path, value, mneid string, save bool) error {
    
    	ctx := v.auth.CreateContextWithAuthorization()
    	fmt.Println("--IN SETGNMIPATH-----------------------")
    	mneService := networkelement.NewNetworkElementServiceClient(v.conn)
    	gnmiPath, err := ygot.StringToStructuredPath(path)
    	if err != nil {
    		return fmt.Errorf("Encountered error while trying to parse string path into gnmi path: %w", err)
    	}
    	//gosdnAddr := v.auth.GetAddress()
    
    	changeRequest := &networkelement.ChangeRequest{
    
    		ApiOp: networkelement.ApiOperation_API_OPERATION_UPDATE,
    
    	changeRequests := []*networkelement.ChangeRequest{changeRequest}
    
    	setPathResponse, err := mneService.SetPathList(ctx, &networkelement.SetPathListRequest{
    		Timestamp:     util.Now(),
    		Pid:           pid,
    		ChangeRequest: changeRequests,
    	})
    	if err != nil {
    
    		fmt.Printf("Error: %v\n", err)
    		return err
    
    	}
    	fmt.Println("setPathResponse: ", setPathResponse.String())
    
    
    	// Now try to commit the change
    	fmt.Println("Now trying to commit change with ID:", setPathResponse.GetResponses()[0].GetId())
    	setChange := &networkelement.SetChange{
    		Cuid: setPathResponse.GetResponses()[0].GetId(),
    		Op:   networkelement.Operation_OPERATION_COMMIT,
    	}
    	setChangeListRequest := networkelement.SetChangeListRequest{
    		Change:    []*networkelement.SetChange{setChange},
    		Timestamp: util.Now(),
    		Pid:       pid,
    	}
    	clResponse, err := mneService.SetChangeList(ctx, &setChangeListRequest)
    	if err != nil {
    		fmt.Println("Error, failed to commit changes:", err)
    		return err
    	} else {
    		fmt.Println("Successfully applied changes:", clResponse)
    	}
    
    	if save {
    		v.savedChanges = append(v.savedChanges, changeRequest)
    	}
    
    // 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 gosdn instance
    
    // This is needed to generate the clab file to be used with the virtual net
    func (v *VEnv) fetchTopology() error {
    	topoService := topoPb.NewTopologyServiceClient(v.conn)
    	ctx := v.auth.CreateContextWithAuthorization()
    	topoResponse, err := topoService.GetTopology(ctx, &topoPb.GetTopologyRequest{Timestamp: util.Now()})
    	if err != nil {
    		return fmt.Errorf("Failed to retrieve Topology: %w", err)
    	}
    	fmt.Printf("Successfully read topology, state is: %s\n", topoResponse.String())
    	fmt.Printf("Length of Links: %d\n", len(topoResponse.Toplogy.Links))
    	fmt.Printf("String of Topology: %s\n", topoResponse.Toplogy.String())
    	for i, link := range topoResponse.Toplogy.GetLinks() {
    		fmt.Printf("index %d, linkID: %s, linkName: %s\n", i, link.GetId(), link.GetName())
    	}
    	// TODO: Needs to set topo
    	return nil
    }
    
    // {G,S}ETTERS
    func (v VEnv) GetName() string {
    	return v.Name
    }
    func (v VEnv) GetClabData() *clab.ClabConfig {
    	return v.clabData
    }
    func (v VEnv) GetClabFilePath() string {
    	return v.clabFilename
    }
    func (v *VEnv) GetConn() *grpc.ClientConn {
    	return v.conn
    }
    func (v VEnv) GetPnd() *pnd.PrincipalNetworkDomain {
    	return v.pnd
    }
    func (v VEnv) GetAuth() *rtdt_auth.RtdtAuth {
    	return v.auth
    }
    
    func (v *VEnv) GetWaitgroup() *sync.WaitGroup {
    
    func (v *VEnv) GetTopology() *rtdt_topology.Topology {
    
    func (v *VEnv) GetSavedChanges() *[]*networkelement.ChangeRequest {