Skip to content
Snippets Groups Projects
clab-config.go 7.74 KiB
Newer Older
  • Learn to ignore specific revisions
  • package clabconfig
    
    import (
    	"fmt"
    	"os"
    	"os/exec"
    
    	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
    
    	util "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
    
    )
    
    // 1st level (Root entry)
    type ClabConfig struct {
    	Name     string   `yaml:"name"`
    	Mgmt     Mgmt     `yaml:"mgmt"`
    	Topology Topology `yaml:"topology"`
    }
    
    // 2nd level
    type Mgmt struct {
    	Network    string `yaml:"network"`
    	IPv4Subnet string `yaml:"ipv4-subnet"`
    	IPv6Subnet string `yaml:"ipv6-subnet"`
    	MTU        int    `yaml:"mtu"`
    }
    
    // 2nd level
    type Topology struct {
    	Nodes map[string]Node `yaml:"nodes"`
    	Links []Link          `yaml:"links"`
    }
    
    // topology.nodes
    type Node struct {
    	Kind         string            `yaml:"kind"`
    	Image        string            `yaml:"image"`
    	Ports        []string          `yaml:"ports,omitempty"`
    	Cmd          string            `yaml:"cmd,omitempty"`
    	MgmtIPv4     string            `yaml:"mgmt-ipv4"`
    	Env          map[string]string `yaml:"env,omitempty"`
    	Binds        []string          `yaml:"binds,omitempty"`
    	StartupDelay int               `yaml:"startup-delay,omitempty"`
    	Group        string            `yaml:"group,omitempty"`
    }
    
    // topology.links
    type Link struct {
    	Endpoints []string `yaml:"endpoints"`
    }
    
    
    // return absolute clab config path based on gosdn root
    // should return /home/user/path/to/gosdn/dev_env_data/clab
    func ClabConfigPath() (string, error) {
    
    	gosdnPath, err := util.GenerateGosdnPath()
    	if err != nil {
    		return "", fmt.Errorf("Couldn't get Gosdn Path: %w\n", err)
    	}
    
    	return filepath.Join(gosdnPath, "/applications/rtdt-manager/data"), nil
    
    // Read file and parse into ClabConfig struct
    
    // Only load gosdn environment: rabbitmq, mongodb,
    
    func LoadConfig(filename string) (*ClabConfig, error) {
    	absFilepath, err := filepath.Abs(filename)
    	if err != nil {
    		return nil, fmt.Errorf("Failed to get absolute path: %w", err)
    	}
    	data, err := os.ReadFile(absFilepath)
    	if err != nil {
    		return nil, fmt.Errorf("Failed to read file: %w", err)
    	}
    
    	var clabconfig ClabConfig
    	err = yaml.Unmarshal(data, &clabconfig)
    	if err != nil {
    		return nil, fmt.Errorf("Failed to unmarshal YAML: %w", err)
    	}
    
    	fmt.Println("--- Successfully loaded file:", absFilepath, "----")
    
    	return &clabconfig, nil
    }
    
    // incrementPort takes a port as a string, adds an offset, and returns the new port as a string.
    func incrementPort(port string, offset int) (string, error) {
    	portNum, err := strconv.Atoi(port)
    	if err != nil {
    		return "", fmt.Errorf("invalid port number: %s", port)
    	}
    	return strconv.Itoa(portNum + offset), nil
    }
    
    
    // TODO
    func (c *ClabConfig) InsertMNE(mnes []*networkelement.ManagedNetworkElement) {
    	for _, mne := range mnes {
    		c.Topology.Nodes[mne.Name] = Node{
    			Kind:  "TBD",
    			Image: "TBD",
    		}
    	}
    }
    
    // Take a clab yaml config file and derive another clab config from it
    
    func DeriveConfig(clabconfig *ClabConfig, newIPv4Subnet, newIPv6Subnet string, postfix string) (*ClabConfig, error) {
    	// Create deep copy
    	derivedConfig := *clabconfig
    	derivedConfig.Topology.Nodes = make(map[string]Node)
    	derivedConfig.Topology.Links = append([]Link{}, clabconfig.Topology.Links...) // Copy links
    
    	portOffset := 5                                                               // TODO set dynamically in some way
    
    	derivedConfig.Name = fmt.Sprintf("%s-%s", clabconfig.Name, postfix)
    
    	subnetParts := strings.Split(newIPv4Subnet, ".")
    	derivedConfig.Mgmt.IPv4Subnet = newIPv4Subnet
    	derivedConfig.Mgmt.IPv6Subnet = newIPv6Subnet
    	derivedConfig.Mgmt.Network = fmt.Sprintf("%s-%s", clabconfig.Name, postfix)
    
    	// Adjust all nodes
    	for name, node := range clabconfig.Topology.Nodes {
    		splitIPv4 := strings.Split(node.MgmtIPv4, ".")
    		splitIPv4[0], splitIPv4[1], splitIPv4[2] = subnetParts[0], subnetParts[1], subnetParts[2]
    		node.MgmtIPv4 = strings.Join(splitIPv4, ".")
    
    
    		if strings.HasPrefix(name, "gosdn") {
    			node.Cmd = "--config /app/configs/containerlab-gosdn-twin.toml"
    
    			//node.Binds = []string{"../../../applications/rtdt-manager/data/ssl/gosdn:/app/ssl"}
    
    		//
    		// //use separate ssl folders, testing
    		// if strings.HasPrefix(name, "gnmi-target") {
    		// 	node.Binds = []string{"../../../applications/rtdt-manager/data/ssl/gnmi-target:/etc/gnmi-target/ssl"}
    		// }
    
    		// Ports: host side needs to be incremented or there will be conflicts
    		// for now just use 5 as increment
    		for i, portBinding := range node.Ports {
    			parts := strings.Split(portBinding, ":")
    			if len(parts) == 3 {
    				// Format: <host-ip>:<host-port>:<container-port>
    				hostPort := parts[1]
    				newHostPort, err := incrementPort(hostPort, portOffset)
    				if err != nil {
    					return nil, fmt.Errorf("invalid port binding: %s", portBinding)
    				}
    				parts[1] = newHostPort
    			} else if len(parts) == 2 {
    				// Format: <host-port>:<container-port>
    				hostPort := parts[0]
    				newHostPort, err := incrementPort(hostPort, portOffset)
    				if err != nil {
    					return nil, fmt.Errorf("invalid port binding: %s", portBinding)
    				}
    				parts[0] = newHostPort
    			} else {
    				return nil, fmt.Errorf("invalid port binding format: %s", portBinding)
    			}
    			node.Ports[i] = strings.Join(parts, ":")
    		}
    
    	}
    
    	// Update Links so they reference the correct nodes
    
    	// for i, link := range clabconfig.Topology.Links {
    	// 	for j, endpoint := range link.Endpoints {
    	// 		parts := strings.Split(endpoint, ":")
    	// 		if len(parts) != 2 {
    	// 			return nil, fmt.Errorf("invalid link endpoint: %s", endpoint)
    	// 		}
    	// 		clabconfig.Topology.Links[i].Endpoints[j] = strings.Join(parts, ":")
    	// 	}
    	// }
    
    
    	return &derivedConfig, nil
    }
    
    // WriteConfig writes the Config struct to a YAML file
    
    // Takes config file as absolute path
    func WriteConfig(filename string, config *ClabConfig) error {
    
    	data, err := yaml.Marshal(config)
    	if err != nil {
    		return fmt.Errorf("failed to marshal YAML: %w", err)
    	}
    
    
    	err = os.WriteFile(filename, data, 0644)
    
    	if err != nil {
    		return fmt.Errorf("failed to write file: %w", err)
    	}
    
    	return nil
    }
    
    
    func ClabDestroy(fullPath string) error {
    
    	fmt.Println("Trying to destroy venv: ", fullPath)
    
    	cmd := exec.Command("sudo", "containerlab", "destroy", "-t", fullPath)
    
    	cmd.Stdout = os.Stdout
    	cmd.Stderr = os.Stderr
    	err := cmd.Run()
    
    		return fmt.Errorf("Error(s) occured while destroying clab environment: %w", err)
    
    	}
    	return err
    }
    
    // Launch a containerlab environment, pass in absolute path of clab yaml
    func ClabDeploy(fullPath string) error {
    
    	fmt.Println("Deploying file: ", fullPath)
    
    	cmd := exec.Command("sudo", "containerlab", "deploy", "-t", fullPath, "--reconfigure")
    
    	cmd.Stdout = os.Stdout
    	cmd.Stderr = os.Stderr
    
    	// Run the command in a Goroutine
    	done := make(chan error, 1)
    
    	stopdeploy := make(chan os.Signal, 1)
    	signal.Notify(stopdeploy, os.Interrupt, syscall.SIGTERM)
    
    		err := cmd.Run() // Use CombinedOutput to capture stdout and stderr
    
    		if err != nil {
    			fmt.Printf("Error during deployment: %s\n", err)
    		}
    		done <- err
    		close(done) // Ensure the channel is closed after sending the result
    	}()
    
    	// Wait for the deployment to finish or a signal to stop
    	select {
    	case err := <-done: // Command finished
    
    		fmt.Println("Successfully deployed containerlab environment")
    
    	case <-stopdeploy: // Signal received to interrupt
    
    		if err := cmd.Process.Kill(); err != nil {
    			fmt.Printf("Failed to kill process: %v\n", err)
    		}
    
    		err := ClabDestroy(fullPath)
    		if err != nil {
    			return fmt.Errorf("Deploying containerlab environment was interrupted and couldn't be cleaned up: %v", err)
    		}
    
    		return fmt.Errorf("Deployment interrupted by signal")
    
    
    func (c *ClabConfig) GetNodeByName(name string) *Node {
    	for nodename, node := range c.Topology.Nodes {
    		if nodename == name {
    			return &node
    		}
    	}
    
    	fmt.Printf("Couldn't find a node with name %s!\n", name)