Skip to content
Snippets Groups Projects
Select Git revision
  • 35dfd8adb0296b14ffd10dda4dfce86faceb90b3
  • master default protected
  • prompt-none
  • connector-ldap-fix-tests
  • approval
  • also-push-image-with-commit-sha-tag
  • hdacloud
  • dependabot/go_modules/api/v2/google.golang.org/grpc-1.69.4
  • dependabot/go_modules/examples/google.golang.org/grpc-1.69.4
  • dependabot/docker/distroless/static-debian12-6ec5aa9
  • dependabot/docker/golang-2314d93
  • dependabot/go_modules/golang.org/x/net-0.34.0
  • dependabot/go_modules/google.golang.org/api-0.216.0
  • dependabot/github_actions/actions/cache-4.2.0
  • dependabot/github_actions/docker/setup-buildx-action-3.8.0
  • dependabot/github_actions/helm/kind-action-1.12.0
  • dependabot/github_actions/docker/setup-qemu-action-3.3.0
  • dependabot/github_actions/anchore/sbom-action-0.17.9
  • update-go
  • dependabot/go_modules/examples/go_modules-232a611e2d
  • dependabot/go_modules/go_modules-232a611e2d
  • api/v2.2.0
  • v2.41.1
  • v2.41.0
  • v2.40.0
  • v2.39.1
  • v2.39.0
  • v2.38.0
  • v2.37.0
  • v2.36.0
  • v2.35.3
  • v2.35.2
  • v2.35.1
  • v2.35.0
  • v2.32.1
  • v2.34.0
  • v2.33.1
  • v2.33.0
  • v2.32.0
  • v2.31.2
  • v2.31.1
41 results

dev-guide.md

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    venv-manager.go 8.49 KiB
    package venvmanager
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"os"
    	"strings"
    	"time"
    
    	corePb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/core"
    	devicePb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/device"
    	pndPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
    	topologyPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
    	"code.fbi.h-da.de/danet/gosdn/applications/venv-manager/containerlab"
    	link "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/links"
    	"code.fbi.h-da.de/danet/gosdn/applications/venv-manager/node"
    	port "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/port"
    	topology "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/topology"
    	yangparser "code.fbi.h-da.de/danet/gosdn/applications/venv-manager/yang-parser"
    	"code.fbi.h-da.de/danet/gosdn/models/generated/openconfig"
    
    	"github.com/openconfig/ygot/ygot"
    	"google.golang.org/grpc"
    	"gopkg.in/yaml.v3"
    )
    
    // VenvManager is the object containing the core logic of the application.
    type VenvManager struct {
    	dialConnectionURL    string
    	dialOption           grpc.DialOption
    	yamlFilepath         string
    	containerRegistryURL string
    	pnd                  string
    }
    
    // NewVenvManager creates a new VenvManager to use.
    func NewVenvManager(dialConnectionURL string, dialOption grpc.DialOption, yamlFilepath string, containerRegistryURL string) *VenvManager {
    	v := new(VenvManager)
    	v.dialConnectionURL = dialConnectionURL
    	v.dialOption = dialOption
    	v.yamlFilepath = yamlFilepath
    	v.containerRegistryURL = containerRegistryURL
    	return v
    }
    
    func (v *VenvManager) createConnection() (*grpc.ClientConn, error) {
    	conn, err := grpc.Dial(v.dialConnectionURL, v.dialOption)
    
    	if err != nil {
    		return nil, err
    	}
    
    	return conn, nil
    }
    
    func (v *VenvManager) closeConnection(conn *grpc.ClientConn) {
    	err := conn.Close()
    
    	if err != nil {
    		fmt.Println(err)
    	}
    }
    
    // TestConnection checks if it can reach the controller via the api.
    func (v *VenvManager) TestConnection() error {
    	conn, err := v.createConnection()
    	if err != nil {
    		return err
    	}
    	defer v.closeConnection(conn)
    
    	ctx := context.Background()
    
    	//Replace with health check as soon as available
    	coreService := corePb.NewCoreServiceClient(conn)
    	_, err = coreService.GetPndList(ctx, &corePb.GetPndListRequest{})
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    
    // CreateTopologyFile creates the topology file.
    func (v *VenvManager) CreateTopologyFile() error {
    	topologyData, err := v.getTopologyData()
    	if err != nil {
    		return err
    	}
    
    	parsedTopology, err := v.parseTopologyDataIntoStructs(topologyData)
    	if err != nil {
    		return err
    	}
    
    	enhancedTopology, err := v.getAndAddMoreData(parsedTopology)
    	if err != nil {
    		return err
    	}
    
    	containerlabTopology, err := v.parseIntoContainerlabTopology(enhancedTopology)
    	if err != nil {
    		return err
    	}
    
    	err = v.writeTopologyToYamlFile(containerlabTopology)
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    
    func (v *VenvManager) getTopologyData() (*topologyPb.GetTopologyResponse, error) {
    	conn, err := v.createConnection()
    	if err != nil {
    		return nil, err
    	}
    	defer v.closeConnection(conn)
    
    	ctx := context.Background()
    
    	//get PND, might remove later because we won't support PND in the future
    	coreService := corePb.NewCoreServiceClient(conn)
    	pndRes, err := coreService.GetPndList(ctx, &corePb.GetPndListRequest{Timestamp: getTimestamp()})
    	if err != nil {
    		return nil, err
    	}
    	v.pnd = pndRes.Pnd[0].Id
    
    	toplogyService := topologyPb.NewTopologyServiceClient(conn)
    	topologyResponse, err := toplogyService.GetTopology(ctx, &topologyPb.GetTopologyRequest{Timestamp: getTimestamp()})
    	if err != nil {
    		return nil, err
    	}
    
    	return topologyResponse, nil
    }
    
    func (v *VenvManager) parseTopologyDataIntoStructs(topologyData *topologyPb.GetTopologyResponse) (*topology.GoSdnTopology, error) {
    	topology := topology.GoSdnTopology{}
    
    	links := []link.Link{}
    	nodes := []node.Node{}
    	ports := []port.Port{}
    
    	for _, unparsedLink := range topologyData.Toplogy.Links {
    		sourceNode := node.Node{ID: unparsedLink.SourceNode.Id, Name: unparsedLink.SourceNode.Name, Kind: "", Image: ""}
    		targetNode := node.Node{ID: unparsedLink.TargetNode.Id, Name: unparsedLink.TargetNode.Name, Kind: "", Image: ""}
    		sourcePort := port.Port{ID: unparsedLink.SourcePort.Id, Name: unparsedLink.SourcePort.Name}
    		targetPort := port.Port{ID: unparsedLink.TargetPort.Id, Name: unparsedLink.TargetPort.Name}
    
    		index, err := getIndexOfElement(nodes, sourceNode.ID)
    		if err != nil {
    			nodes = append(nodes, sourceNode)
    		} else {
    			sourceNode = nodes[index]
    		}
    
    		index, err = getIndexOfElement(nodes, targetNode.ID)
    		if err != nil {
    			nodes = append(nodes, targetNode)
    		} else {
    			targetNode = nodes[index]
    		}
    
    		index, err = getIndexOfElement(ports, sourcePort.ID)
    		if err != nil {
    			ports = append(ports, sourcePort)
    		} else {
    			sourcePort = ports[index]
    		}
    
    		index, err = getIndexOfElement(ports, targetPort.ID)
    		if err != nil {
    			ports = append(ports, targetPort)
    		} else {
    			targetPort = ports[index]
    		}
    
    		newLink := link.Link{ID: unparsedLink.Id, Name: unparsedLink.Name, SourceNode: &sourceNode, TargetNode: &targetNode, SourcePort: &sourcePort, TargetPort: &targetPort}
    		links = append(links, newLink)
    	}
    
    	topology.Nodes = nodes
    	topology.Ports = ports
    	topology.Links = links
    
    	return &topology, nil
    }
    
    func (v *VenvManager) loadDeviceModelPathsIntoGosdn(ctx context.Context, conn *grpc.ClientConn, nodes *[]node.Node) error {
    	pndService := pndPb.NewPndServiceClient(conn)
    
    	for _, node := range *nodes {
    		_, err := pndService.GetPath(ctx, &pndPb.GetPathRequest{Did: node.ID, Pid: v.pnd, Path: "/"})
    		if err != nil {
    			return err
    		}
    	}
    
    	return nil
    }
    
    func (v *VenvManager) getAndAddMoreData(topologyData *topology.GoSdnTopology) (*topology.GoSdnTopology, error) {
    	conn, err := v.createConnection()
    	if err != nil {
    		return nil, err
    	}
    	defer v.closeConnection(conn)
    
    	ctx := context.Background()
    
    	// Create 'root' path to be able to load the whole model from the store.
    	path, err := ygot.StringToPath("/", ygot.StructuredPath)
    	if err != nil {
    		return nil, err
    	}
    
    	// just to load model data into goSDN to have newest data available for get request
    	err = v.loadDeviceModelPathsIntoGosdn(ctx, conn, &topologyData.Nodes)
    	if err != nil {
    		return nil, err
    	}
    
    	deviceService := devicePb.NewDeviceServiceClient(conn)
    
    	for iterator, node := range topologyData.Nodes {
    		getDeviceResponse, _ := deviceService.Get(ctx, &devicePb.GetDeviceRequest{DeviceID: node.ID})
    		if err != nil {
    			return nil, err
    		}
    
    		var marshalledYangData openconfig.Device
    
    		err = yangparser.Unmarshal([]byte(getDeviceResponse.Device.Model), path, &marshalledYangData)
    		if err != nil {
    			return nil, err
    		}
    
    		mgmntAddress := strings.Split(getDeviceResponse.Device.TransportAddress, ":")
    		topologyData.Nodes[iterator].MgmtIpv4 = mgmntAddress[0]
    		topologyData.Nodes[iterator].YangData = marshalledYangData
    		topologyData.Nodes[iterator].FillAllFields(v.containerRegistryURL)
    	}
    
    	return topologyData, nil
    }
    
    func (v *VenvManager) parseIntoContainerlabTopology(topologyData *topology.GoSdnTopology) (*containerlab.YamlStruct, error) {
    	containerlabTopology := containerlab.YamlStruct{}
    
    	containerlabTopology.Name = v.pnd + topologyData.Name
    
    	// find a better way than to do this
    	var managementNet string
    	if len(topologyData.Nodes) > 0 {
    		managementNet = topologyData.Nodes[0].MgmtIpv4 + "/16"
    	}
    
    	containerlabTopology.Mgmt = containerlab.Management{Network: containerlabTopology.Name + "-network", Ipv4Subnet: managementNet}
    
    	containerlabTopology.Topology = containerlab.Topology{Nodes: make(map[string]containerlab.Node), Links: make([]containerlab.Link, 0)}
    	for _, node := range topologyData.Nodes {
    		containerlabTopology.Topology.Nodes[node.Name] = containerlab.Node{Kind: node.Kind, Image: node.Image, MgmtIpv4: node.MgmtIpv4}
    	}
    	for _, link := range topologyData.Links {
    		newLink := containerlab.Link{
    			Endpoints: link.GetLinkAsSliceOfStrings(),
    		}
    		containerlabTopology.Topology.Links = append(containerlabTopology.Topology.Links, newLink)
    	}
    
    	return &containerlabTopology, nil
    }
    
    func (v *VenvManager) writeTopologyToYamlFile(containerlabStruct *containerlab.YamlStruct) error {
    	yaml, err := yaml.Marshal(containerlabStruct)
    	if err != nil {
    		return err
    	}
    
    	err = os.WriteFile(v.yamlFilepath, yaml, 0644)
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    
    func getIndexOfElement[T hasGetID](items []T, id string) (int, error) {
    	for index, arrayItem := range items {
    		if arrayItem.GetID() == id {
    			return index, nil
    		}
    	}
    
    	return -1, errors.New("not found")
    }
    
    type hasGetID interface {
    	GetID() string
    }
    
    func getTimestamp() int64 {
    	return int64(time.Now().Nanosecond())
    }