Skip to content
Snippets Groups Projects
venv.go 13.8 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/conflict"
    
    	"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"
    
    	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/sdnconfig"
    
    	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
    
    	gnmitargetygot "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/yang"
    
    	uuid "github.com/google/uuid"
    
    	gnmi "github.com/openconfig/gnmi/proto/gnmi"
    	"github.com/openconfig/ygot/ygot"
    
    	"github.com/openconfig/ygot/ytypes"
    
    	"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
    
    	sdnConfig            *sdnconfig.SdnConfig   // Represents json config file for configuration grpc interface
    
    	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
    
    // This takes FULL path name to clab config
    
    func NewVEnv(name, clabFilename, user, pass string, wg *sync.WaitGroup, sdnConfig *sdnconfig.SdnConfig) *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)
    
    	fmt.Println("Successfully deployed containerlab environment")
    
    
    	// 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: %v\n", name, err)
    
    		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,
    
    		sdnConfig:            sdnConfig,
    		containerRegistryURL: "registry.code.fbi.h-da.de/danet/gnmi-target/debian:interface-enabled-test", // TODO: Could let user choose
    
    // Upload a sdnconfig .json file to the DB
    
    func (v *VEnv) ApplyConfiguration(sdnConfig *sdnconfig.SdnConfig) error {
    	v.sdnConfig = sdnConfig
    	sdnConfigParsed, err := json.Marshal(v.sdnConfig)
    	if err != nil {
    
    		return fmt.Errorf("Parsing Error in ApplyConfiguration(): %w", err)
    
    	}
    	configService := configPb.NewConfigurationManagementServiceClient(v.auth.GetConn())
    	request := &configPb.ImportSDNConfigRequest{
    		Timestamp:     util.Now(),
    		Pid:           v.pnd.Id,
    		SdnConfigData: string(sdnConfigParsed),
    	}
    	response, err := configService.ImportSDNConfig(v.auth.CreateContextWithAuthorization(), request)
    	if err != nil {
    
    		return fmt.Errorf("Response Error in ApplyConfiguration(): %w", err)
    
    	}
    	fmt.Println("Configuration Response:", response.String())
    	return nil
    }
    
    
    // Based on saved sdnconfig, create devices
    
    func (v *VEnv) CreateDevices() error {
    
    	// Alternative (better) approach
    
    	fmt.Println("Creating devices now!")
    	if v.sdnConfig == nil {
    		return fmt.Errorf("Can't create devices without sdnConfig being set")
    	}
    
    	for _, mne := range v.sdnConfig.NetworkElements {
    		fmt.Printf("[%s] - Found mne target: %s with addr: %s\n", v.Name, mne.Name, mne.TransportAddress)
    		opt := &tpb.TransportOption{
    			Address:  mne.TransportAddress,
    			Username: "admin",
    			Password: "admin",
    			Tls:      true,
    			TransportOption: &tpb.TransportOption_GnmiTransportOption{
    				GnmiTransportOption: &tpb.GnmiTransportOption{},
    			},
    
    		pluginID, _ := uuid.Parse("d1c269a2-6482-4010-b0d8-679dff73153b") // TODO Get this dynamically
    		pndID, _ := uuid.Parse(v.pnd.Id)
    		listResponse, err := gosdnutil.AddNetworkElement(v.auth, v.auth.GetAddress(), mne.Name, mne.ID, opt, pluginID, pndID, []string{"/"})
    		if err != nil {
    			return fmt.Errorf("[%s] - Failed to add network element: %w\n", v.Name, err)
    		}
    		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 *[]*sdnconfig.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
    
    func (v *VEnv) ApplyRoutes() error {
    	conn := v.auth.GetConn()
    	hostIP := int64(1)
    
    	for _, link := range v.sdnConfig.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, Not used
    
    func (v *VEnv) UploadTopology() error {
    	conn := v.auth.GetConn()
    	topoService := topoPb.NewTopologyServiceClient(conn)
    
    	for _, link := range v.sdnConfig.Links {
    		req := &topoPb.AddLinkRequest{
    
    			Link: &topoPb.Link{
    				Id:         link.ID,
    				Name:       link.Name,
    				SourceNode: link.SourceNode.Convert(),
    				SourcePort: link.SourcePort.Convert(),
    				TargetNode: link.TargetNode.Convert(),
    				TargetPort: link.TargetPort.Convert(),
    				Metadata:   &conflict.Metadata{},
    			},
    
    		//fmt.Println("AddLink is:", req.String())
    		ctx := v.auth.CreateContextWithAuthorization()
    		topoResponse, err := topoService.AddLink(ctx, req)
    
    		fmt.Printf("Successfully uploaded topo link to DB: %s\n", topoResponse.String())
    
    func getTypedValue(value, ytype string) (*gnmi.TypedValue, error) {
    	switch ytype {
    	case "uint16":
    		{
    			uintVal, err := strconv.ParseUint(value, 10, 64)
    			if err == nil {
    				return &gnmi.TypedValue{
    					Value: &gnmi.TypedValue_UintVal{UintVal: uintVal},
    				}, nil
    			} else {
    				return nil, err
    			}
    		}
    	case "string":
    		{
    			return &gnmi.TypedValue{
    				Value: &gnmi.TypedValue_StringVal{StringVal: value},
    			}, nil
    		}
    	case "int":
    		{
    			intVal, err := strconv.ParseInt(value, 10, 64)
    			if err == nil {
    				return &gnmi.TypedValue{
    					Value: &gnmi.TypedValue_IntVal{IntVal: intVal},
    				}, nil
    			} else {
    				return nil, err
    			}
    		}
    	default:
    		return nil, fmt.Errorf("Unknown type")
    
    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)
    	}
    
    	//ytypes.GetOrCreateNode(gnmitargetygot.Schema(), ,gnmiPath)
    	fmt.Println("GETTING SCHEMA")
    	schema, nil := gnmitargetygot.Schema()
    	fmt.Println("GETTING ROOT SCHEMA")
    	rootSchema := schema.RootSchema()
    	fmt.Println("GET OR CREATE NODE")
    	_, entry, err := ytypes.GetOrCreateNode(rootSchema, &gnmitargetygot.Gnmitarget{}, gnmiPath)
    	if err != nil {
    		return fmt.Errorf("SetGnnmiPath Error: %w", err)
    	}
    
    	fmt.Println("GET TYPED VALUE")
    	yangType := entry.Type
    	gnmiVal, err := getTypedValue(value, yangType.Kind.String())
    	if err != nil {
    		fmt.Println("The given type is not supported yet!")
    	}
    	// fmt.Println("YangType Name is ", yangType.Name)
    	// fmt.Println("YangType Type is ", yangType.Type)
    	// fmt.Println("YangType Kind is ", yangType.Kind.String())
    
    	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) GetSdnConfig() *sdnconfig.SdnConfig {
    	return v.sdnConfig
    }
    
    func (v *VEnv) GetSavedChanges() *[]*networkelement.ChangeRequest {