Select Git revision
venv-manager.go
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())
}