From 028dcefcf067ae0b4794319baeb6420627ec0bd9 Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 22 Feb 2024 08:46:47 +0100 Subject: [PATCH 01/18] init --- clab/clab.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/clab/clab.go b/clab/clab.go index 438d09cc9..4cb5c2565 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -922,3 +922,38 @@ func (c *CLab) ExtractDNSServers(filesys fs.FS) error { return nil } + +// Deploy the given topology +func (c *CLab) Deploy() error { + return nil +} + +// Destroy the given topology +func (c *CLab) Destroy() error { + return nil +} + +// Inspect the given topology +func (c *CLab) Inspect() error { + return nil +} + +// Save configuration of the given topology nodes +func (c *CLab) Save() error { + return nil +} + +// Graph the given topology +func (c *CLab) Graph() error { + return nil +} + +// Exec execute commands on running topology nodes +func (c *CLab) Exec() error { + return nil +} + +// Configure topology nodes +func (c *CLab) Configure() error { + return nil +} From ed1f74a02a2fae05484af25c077605b40f7d5d1f Mon Sep 17 00:00:00 2001 From: steiler Date: Tue, 27 Feb 2024 12:57:27 +0100 Subject: [PATCH 02/18] update --- clab/clab.go | 293 ++++++++++++++++++++++++++++++++++++----- clab/clab_test.go | 4 +- clab/config.go | 43 ++---- clab/config_test.go | 4 +- clab/deploy_options.go | 79 +++++++++++ clab/file.go | 4 +- clab/network.go | 4 +- cmd/deploy.go | 278 +++----------------------------------- cmd/inspect.go | 2 +- cmd/tools_veth.go | 4 +- nodes/default_node.go | 10 ++ 11 files changed, 386 insertions(+), 339 deletions(-) create mode 100644 clab/deploy_options.go diff --git a/clab/clab.go b/clab/clab.go index 4cb5c2565..b9ef28d0d 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -50,7 +50,7 @@ type CLab struct { dependencyManager depMgr.DependencyManager m *sync.RWMutex timeout time.Duration - globalRuntime string + globalRuntimeName string // nodeFilter is a list of node names to be deployed, // names are provided exactly as they are listed in the topology file. nodeFilter []string @@ -68,6 +68,42 @@ func WithTimeout(dur time.Duration) ClabOption { } } +// WithLabName sets the name of the lab +// to the provided string +func WithLabName(n string) ClabOption { + return func(c *CLab) error { + c.Config.Name = n + return nil + } +} + +// WithManagementNetworkName sets the name of the +// management network that is to be used +func WithManagementNetworkName(n string) ClabOption { + return func(c *CLab) error { + c.Config.Mgmt.Network = n + return nil + } +} + +// WithManagementIpv4Subnet defined the IPv4 subnet +// that will be used for the mgmt network +func WithManagementIpv4Subnet(s string) ClabOption { + return func(c *CLab) error { + c.Config.Mgmt.IPv4Subnet = s + return nil + } +} + +// WithManagementIpv6Subnet defined the IPv6 subnet +// that will be used for the mgmt network +func WithManagementIpv6Subnet(s string) ClabOption { + return func(c *CLab) error { + c.Config.Mgmt.IPv6Subnet = s + return nil + } +} + // WithDependencyManager adds Dependency Manager. func WithDependencyManager(dm depMgr.DependencyManager) ClabOption { return func(c *CLab) error { @@ -92,7 +128,7 @@ func WithRuntime(name string, rtconfig *runtime.RuntimeConfig) ClabOption { return err } - c.globalRuntime = name + c.globalRuntimeName = name r := rInit() log.Debugf("Running runtime.Init with params %+v and %+v", rtconfig, c.Config.Mgmt) @@ -131,7 +167,7 @@ func RuntimeInitializer(name string) (string, runtime.Initializer, error) { func WithKeepMgmtNet() ClabOption { return func(c *CLab) error { - c.GlobalRuntime().WithKeepMgmtNet() + c.globalRuntime().WithKeepMgmtNet() return nil } } @@ -142,7 +178,7 @@ func WithTopoPath(path, varsFile string) ClabOption { if err != nil { return err } - if err := c.GetTopology(file, varsFile); err != nil { + if err := c.LoadTopology(file, varsFile); err != nil { return fmt.Errorf("failed to read topology file: %v", err) } @@ -351,8 +387,8 @@ func (c *CLab) initMgmtNetwork() error { return nil } -func (c *CLab) GlobalRuntime() runtime.ContainerRuntime { - return c.Runtimes[c.globalRuntime] +func (c *CLab) globalRuntime() runtime.ContainerRuntime { + return c.Runtimes[c.globalRuntimeName] } // CreateNodes schedules nodes creation and returns a waitgroup for all nodes @@ -703,7 +739,7 @@ func (c *CLab) WaitForExternalNodeDependencies(ctx context.Context, nodeName str return } - runtime.WaitForContainerRunning(ctx, c.Runtimes[c.globalRuntime], contName, nodeName) + runtime.WaitForContainerRunning(ctx, c.Runtimes[c.globalRuntimeName], contName, nodeName) } func (c *CLab) DeleteNodes(ctx context.Context, workers uint, serialNodes map[string]struct{}) { @@ -757,7 +793,7 @@ func (c *CLab) DeleteNodes(ctx context.Context, workers uint, serialNodes map[st close(serialChan) // also call delete on the special nodes - for _, n := range c.GetSpecialLinkNodes() { + for _, n := range c.getSpecialLinkNodes() { err := n.Delete(ctx) if err != nil { log.Warn(err) @@ -782,7 +818,7 @@ func (c *CLab) ListContainers(ctx context.Context, filter []*types.GenericFilter } // ListNodesContainers lists all containers based on the nodes stored in clab instance. -func (c *CLab) ListNodesContainers(ctx context.Context) ([]runtime.GenericContainer, error) { +func (c *CLab) listNodesContainers(ctx context.Context) ([]runtime.GenericContainer, error) { var containers []runtime.GenericContainer for _, n := range c.Nodes { @@ -812,22 +848,9 @@ func (c *CLab) ListNodesContainersIgnoreNotFound(ctx context.Context) ([]runtime return containers, nil } -func (c *CLab) GetNodeRuntime(contName string) (runtime.ContainerRuntime, error) { - shortName, err := getShortName(c.Config.Name, c.Config.Prefix, contName) - if err != nil { - return nil, err - } - - if node, ok := c.Nodes[shortName]; ok { - return node.GetRuntime(), nil - } - - return nil, fmt.Errorf("could not find a container matching name %q", contName) -} - // GetLinkNodes returns all CLab.Nodes nodes as links.Nodes enriched with the special nodes - host and mgmt-net. // The CLab nodes are copied to a new map and thus clab.Node interface is converted to link.Node. -func (c *CLab) GetLinkNodes() map[string]links.Node { +func (c *CLab) getLinkNodes() map[string]links.Node { // resolveNodes is a map of all nodes in the topology // that is artificially created to combat circular dependencies. // If no circ deps were in place we could've used c.Nodes map instead. @@ -838,7 +861,7 @@ func (c *CLab) GetLinkNodes() map[string]links.Node { } // add the virtual host and mgmt-bridge nodes to the resolve nodes - specialNodes := c.GetSpecialLinkNodes() + specialNodes := c.getSpecialLinkNodes() for _, n := range specialNodes { resolveNodes[n.GetShortName()] = n } @@ -849,7 +872,7 @@ func (c *CLab) GetLinkNodes() map[string]links.Node { // GetSpecialLinkNodes returns a map of special nodes that are used to resolve links. // Special nodes are host and mgmt-bridge nodes that are not typically present in the topology file // but are required to resolve links. -func (c *CLab) GetSpecialLinkNodes() map[string]links.Node { +func (c *CLab) getSpecialLinkNodes() map[string]links.Node { // add the virtual host and mgmt-bridge nodes to the resolve nodes specialNodes := map[string]links.Node{ "host": links.GetHostLinkNode(), @@ -862,7 +885,7 @@ func (c *CLab) GetSpecialLinkNodes() map[string]links.Node { // ResolveLinks resolves raw links to the actual link types and stores them in the CLab.Links map. func (c *CLab) ResolveLinks() error { resolveParams := &links.ResolveParams{ - Nodes: c.GetLinkNodes(), + Nodes: c.getLinkNodes(), MgmtBridgeName: c.Config.Mgmt.Bridge, NodesFilter: c.nodeFilter, } @@ -924,36 +947,238 @@ func (c *CLab) ExtractDNSServers(filesys fs.FS) error { } // Deploy the given topology -func (c *CLab) Deploy() error { - return nil +func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.GenericContainer, error) { + var err error + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + err = c.ResolveLinks() + if err != nil { + return nil, err + } + + log.Debugf("lab Conf: %+v", c.Config) + + if options.reconfigure { + _ = c.Destroy(ctx) + log.Infof("Removing %s directory...", c.TopoPaths.TopologyLabDir()) + if err := os.RemoveAll(c.TopoPaths.TopologyLabDir()); err != nil { + return nil, err + } + } + + // create management network or use existing one + if err = c.CreateNetwork(ctx); err != nil { + return nil, err + } + + err = links.SetMgmtNetUnderlayingBridge(c.Config.Mgmt.Bridge) + if err != nil { + return nil, err + } + + if err = c.checkTopologyDefinition(ctx); err != nil { + return nil, err + } + + if err = c.loadKernelModules(); err != nil { + return nil, err + } + + log.Info("Creating lab directory: ", c.TopoPaths.TopologyLabDir()) + utils.CreateDirectory(c.TopoPaths.TopologyLabDir(), 0755) + // adjust ACL for Labdir such that SUDO_UID Users will + // also have access to lab directory files + err = utils.AdjustFileACLs(c.TopoPaths.TopologyLabDir()) + if err != nil { + log.Infof("unable to adjust Labdir file ACLs: %v", err) + } + + // create an empty ansible inventory file that will get populated later + // we create it here first, so that bind mounts of ansible-inventory.yml file could work + ansibleInvFPath := c.TopoPaths.AnsibleInventoryFileAbsPath() + _, err = os.Create(ansibleInvFPath) + if err != nil { + return nil, err + } + + // in an similar fashion, create an empty topology data file + topoDataFPath := c.TopoPaths.TopoExportFile() + topoDataF, err := os.Create(topoDataFPath) + if err != nil { + return nil, err + } + + if err := c.certificateAuthoritySetup(); err != nil { + return nil, err + } + + c.SSHPubKeys, err = c.RetrieveSSHPubKeys() + if err != nil { + log.Warn(err) + } + + if err := c.CreateAuthzKeysFile(); err != nil { + return nil, err + } + + // extraHosts holds host entries for nodes with static IPv4/6 addresses + // these entries will be used by container runtime to populate /etc/hosts file + extraHosts := make([]string, 0, len(c.Nodes)) + + for _, n := range c.Nodes { + if n.Config().MgmtIPv4Address != "" { + log.Debugf("Adding static ipv4 /etc/hosts entry for %s:%s", + n.Config().ShortName, n.Config().MgmtIPv4Address) + extraHosts = append(extraHosts, n.Config().ShortName+":"+n.Config().MgmtIPv4Address) + } + + if n.Config().MgmtIPv6Address != "" { + log.Debugf("Adding static ipv6 /etc/hosts entry for %s:%s", + n.Config().ShortName, n.Config().MgmtIPv6Address) + extraHosts = append(extraHosts, n.Config().ShortName+":"+n.Config().MgmtIPv6Address) + } + } + + for _, n := range c.Nodes { + n.Config().ExtraHosts = extraHosts + } + + nodesWg, execCollection, err := c.CreateNodes(ctx, options.maxWorkers, options.skipPostDeploy) + if err != nil { + return nil, err + } + + if nodesWg != nil { + nodesWg.Wait() + } + + // write to log + execCollection.Log() + + if err := c.GenerateInventories(); err != nil { + return nil, err + } + + if err := c.GenerateExports(ctx, topoDataF, options.exportTemplate); err != nil { + return nil, err + } + + // generate graph of the lab topology + if options.graph { + if err = c.GenerateDotGraph(); err != nil { + log.Error(err) + } + } + + containers, err := c.listNodesContainers(ctx) + if err != nil { + return nil, err + } + + log.Info("Adding containerlab host entries to /etc/hosts file") + err = AppendHostsFileEntries(containers, c.Config.Name) + if err != nil { + log.Errorf("failed to create hosts file: %v", err) + } + + log.Info("Adding ssh config for containerlab nodes") + err = c.AddSSHConfig() + if err != nil { + log.Errorf("failed to create ssh config file: %v", err) + } + + return containers, nil +} + +// certificateAuthoritySetup sets up the certificate authority parameters. +func (c *CLab) certificateAuthoritySetup() error { + // init the Cert storage and CA + c.Cert.CertStorage = cert.NewLocalDirCertStorage(c.TopoPaths) + c.Cert.CA = cert.NewCA() + + s := c.Config.Settings + + // Set defaults for the CA parameters + keySize := 2048 + validityDuration := time.Until(time.Now().AddDate(1, 0, 0)) // 1 year as default + + // check that Settings.CertificateAuthority exists. + if s != nil && s.CertificateAuthority != nil { + // if ValidityDuration is set use the value + if s.CertificateAuthority.ValidityDuration != 0 { + validityDuration = s.CertificateAuthority.ValidityDuration + } + + // if KeyLength is set use the value + if s.CertificateAuthority.KeySize != 0 { + keySize = s.CertificateAuthority.KeySize + } + + // if external CA cert and and key are set, propagate to topopaths + extCACert := s.CertificateAuthority.Cert + extCAKey := s.CertificateAuthority.Key + + // override external ca and key from env vars + if v := os.Getenv("CLAB_CA_KEY_FILE"); v != "" { + extCAKey = v + } + + if v := os.Getenv("CLAB_CA_CERT_FILE"); v != "" { + extCACert = v + } + + if extCACert != "" && extCAKey != "" { + err := c.TopoPaths.SetExternalCaFiles(extCACert, extCAKey) + if err != nil { + return err + } + } + } + + // define the attributes used to generate the CA Cert + caCertInput := &cert.CACSRInput{ + CommonName: c.Config.Name + " lab CA", + Country: "US", + Expiry: validityDuration, + Organization: "containerlab", + KeySize: keySize, + } + + return c.LoadOrGenerateCA(caCertInput) } // Destroy the given topology -func (c *CLab) Destroy() error { +func (c *CLab) Destroy(ctx context.Context) error { + return nil +} + +func (c *CLab) Version(ctx context.Context) error { return nil } // Inspect the given topology -func (c *CLab) Inspect() error { +func (c *CLab) Inspect(ctx context.Context) error { return nil } // Save configuration of the given topology nodes -func (c *CLab) Save() error { +func (c *CLab) Save(ctx context.Context) error { return nil } // Graph the given topology -func (c *CLab) Graph() error { +func (c *CLab) Graph(ctx context.Context) error { return nil } // Exec execute commands on running topology nodes -func (c *CLab) Exec() error { +func (c *CLab) Exec(ctx context.Context) error { return nil } // Configure topology nodes -func (c *CLab) Configure() error { +func (c *CLab) Configure(ctx context.Context) error { return nil } diff --git a/clab/clab_test.go b/clab/clab_test.go index 362c613b3..8059fd4f7 100644 --- a/clab/clab_test.go +++ b/clab/clab_test.go @@ -192,8 +192,8 @@ func Test_WaitForExternalNodeDependencies_OK(t *testing.T) { // create a barebone CLab struct c := CLab{ - Nodes: getNodeMap(mockCtrl), - globalRuntime: "mock", + Nodes: getNodeMap(mockCtrl), + globalRuntimeName: "mock", Runtimes: map[string]runtime.ContainerRuntime{ "mock": crMock, }, diff --git a/clab/config.go b/clab/config.go index d5456f21a..069239058 100644 --- a/clab/config.go +++ b/clab/config.go @@ -91,7 +91,7 @@ func (c *CLab) parseTopology() error { } // saving the global default runtime - nodeRuntimes[nodeName] = c.globalRuntime + nodeRuntimes[nodeName] = c.globalRuntimeName } // initialize any extra runtimes @@ -104,7 +104,7 @@ func (c *CLab) parseTopology() error { if rInit, ok := clabRuntimes.ContainerRuntimes[r]; ok { newRuntime := rInit() - defaultConfig := c.Runtimes[c.globalRuntime].Config() + defaultConfig := c.Runtimes[c.globalRuntimeName].Config() err := newRuntime.Init( clabRuntimes.WithConfig(&defaultConfig), ) @@ -310,9 +310,9 @@ func (c *CLab) processStartupConfig(nodeCfg *types.NodeConfig) error { return nil } -// CheckTopologyDefinition runs topology checks and returns any errors found. +// checkTopologyDefinition runs topology checks and returns any errors found. // This function runs after topology file is parsed and all nodes/links are initialized. -func (c *CLab) CheckTopologyDefinition(ctx context.Context) error { +func (c *CLab) checkTopologyDefinition(ctx context.Context) error { var err error if err = c.verifyLinks(); err != nil { return err @@ -329,7 +329,7 @@ func (c *CLab) CheckTopologyDefinition(ctx context.Context) error { if err = c.verifyDuplicateAddresses(); err != nil { return err } - if err = c.VerifyContainersUniqueness(ctx); err != nil { + if err = c.verifyContainersUniqueness(ctx); err != nil { return err } return nil @@ -376,7 +376,7 @@ func (c *CLab) verifyLinks() error { var err error verificationErrors := []error{} for _, e := range c.Endpoints { - err = e.Verify(c.GlobalRuntime().Config().VerifyLinkParams) + err = e.Verify(c.globalRuntime().Config().VerifyLinkParams) if err != nil { verificationErrors = append(verificationErrors, err) } @@ -388,7 +388,7 @@ func (c *CLab) verifyLinks() error { } // LoadKernelModules loads containerlab-required kernel modules. -func (c *CLab) LoadKernelModules() error { +func (c *CLab) loadKernelModules() error { modules := []string{"ip_tables", "ip6_tables"} for _, m := range modules { @@ -449,9 +449,9 @@ func (c *CLab) verifyDuplicateAddresses() error { return nil } -// VerifyContainersUniqueness ensures that nodes defined in the topology do not have names of the existing containers +// verifyContainersUniqueness ensures that nodes defined in the topology do not have names of the existing containers // additionally it checks that the lab name is unique and no containers are currently running with the same lab name label. -func (c *CLab) VerifyContainersUniqueness(ctx context.Context) error { +func (c *CLab) verifyContainersUniqueness(ctx context.Context) error { nctx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() @@ -527,31 +527,6 @@ func (c *CLab) resolveBindPaths(binds []string, nodedir string) error { return nil } -// SetClabIntfsEnvVar sets CLAB_INTFS env var for each node -// which holds the number of interfaces a node expects to have (without mgmt interfaces). -func (c *CLab) SetClabIntfsEnvVar() { - for _, n := range c.Nodes { - // Injecting the env var with expected number of links - numIntfs := map[string]string{ - types.CLAB_ENV_INTFS: fmt.Sprintf("%d", len(n.GetEndpoints())), - } - n.Config().Env = utils.MergeStringMaps(n.Config().Env, numIntfs) - } -} - -// returns nodeCfg.ShortName based on the provided containerName and labName. -func getShortName(labName string, labPrefix *string, containerName string) (string, error) { - if *labPrefix == "" { - return containerName, nil - } - - result := strings.Split(containerName, "-"+labName+"-") - if len(result) != 2 { - return "", fmt.Errorf("failed to parse container name %q", containerName) - } - return result[1], nil -} - // HasKind returns true if kind k is found in the list of nodes. func (c *CLab) HasKind(k string) bool { for _, n := range c.Nodes { diff --git a/clab/config_test.go b/clab/config_test.go index b03cbb33d..8821e0c10 100644 --- a/clab/config_test.go +++ b/clab/config_test.go @@ -615,13 +615,13 @@ func TestVerifyContainersUniqueness(t *testing.T) { // set mockRuntime parameters mockRuntime := mockruntime.NewMockContainerRuntime(ctrl) c.Runtimes[rtName] = mockRuntime - c.globalRuntime = rtName + c.globalRuntimeName = rtName // prepare runtime result mockRuntime.EXPECT().ListContainers(gomock.Any(), gomock.Any()).AnyTimes().Return(tc.mockResult.c, tc.mockResult.e) ctx := context.Background() - err = c.VerifyContainersUniqueness(ctx) + err = c.verifyContainersUniqueness(ctx) if tc.wantError { assert.Error(t, err) } else { diff --git a/clab/deploy_options.go b/clab/deploy_options.go new file mode 100644 index 000000000..11dd4ed22 --- /dev/null +++ b/clab/deploy_options.go @@ -0,0 +1,79 @@ +package clab + +import ( + "math" + + log "github.com/sirupsen/logrus" + "github.com/tklauser/numcpus" +) + +type DeployOptions struct { + reconfigure bool + skipPostDeploy bool + graph bool + maxWorkers uint + exportTemplate string +} + +func NewDeployOptions(maxWorkers uint) (*DeployOptions, error) { + d := &DeployOptions{ + maxWorkers: math.MaxUint, + } + err := d.initWorkerCount(maxWorkers) + return d, err +} + +func (d *DeployOptions) SetReconfigure(b bool) *DeployOptions { + d.reconfigure = b + return d +} + +func (d *DeployOptions) SetSkipPostDeploy(b bool) *DeployOptions { + d.skipPostDeploy = b + return d +} + +func (d *DeployOptions) SetGraph(b bool) *DeployOptions { + d.graph = b + return d +} + +func (d *DeployOptions) SetMaxWorkers(i uint) *DeployOptions { + d.maxWorkers = i + return d +} + +func (d *DeployOptions) SetExportTemplate(templatePath string) *DeployOptions { + d.exportTemplate = templatePath + return d +} + +// countWorkers calculates the number workers used for the creation of nodes. +// If a user provided --max-workers this takes precedence. +// If maxWorkers is not set then the workers are limited by the number of available CPUs when +// number of nodes exceeds the number of available CPUs. +func (d *DeployOptions) initWorkerCount(maxWorkers uint) error { + // init number of Workers to the number of nodes + nodeWorkers := uint(0) + + switch { + // if maxworkers is provided, use that value + case maxWorkers > 0: + nodeWorkers = maxWorkers + + // if maxWorkers is not set, limit workers number by number of available CPUs + case maxWorkers <= 0: + // retrieve vCPU count + vCpus, err := numcpus.GetOnline() + if err != nil { + return err + } + nodeWorkers = uint(vCpus) + } + + // finally set the value + d.maxWorkers = nodeWorkers + log.Debugf("Number of Node workers: %d", nodeWorkers) + + return nil +} diff --git a/clab/file.go b/clab/file.go index 68ae95ab3..e27c716ea 100644 --- a/clab/file.go +++ b/clab/file.go @@ -27,9 +27,9 @@ const ( varFileSuffix = "_vars" ) -// GetTopology parses the topology file into c.Conf structure +// LoadTopology parses the topology file into c.Conf structure // as well as populates the TopoFile structure with the topology file related information. -func (c *CLab) GetTopology(topo, varsFile string) error { +func (c *CLab) LoadTopology(topo, varsFile string) error { var err error c.TopoPaths, err = types.NewTopoPaths(topo) diff --git a/clab/network.go b/clab/network.go index 86f3973c6..4223d8d78 100644 --- a/clab/network.go +++ b/clab/network.go @@ -8,13 +8,13 @@ import ( func (c *CLab) CreateNetwork(ctx context.Context) error { // create docker network or use existing one - if err := c.GlobalRuntime().CreateNet(ctx); err != nil { + if err := c.globalRuntime().CreateNet(ctx); err != nil { return err } // save mgmt bridge name as a label for _, n := range c.Nodes { - n.Config().Labels[labels.NodeMgmtNetBr] = c.GlobalRuntime().Mgmt().Bridge + n.Config().Labels[labels.NodeMgmtNetBr] = c.globalRuntime().Mgmt().Bridge } return nil diff --git a/cmd/deploy.go b/cmd/deploy.go index e1f809e35..24c8d20ab 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -10,17 +10,12 @@ import ( "os" "os/signal" "syscall" - "time" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/srl-labs/containerlab/cert" "github.com/srl-labs/containerlab/clab" "github.com/srl-labs/containerlab/clab/dependency_manager" - "github.com/srl-labs/containerlab/links" "github.com/srl-labs/containerlab/runtime" - "github.com/srl-labs/containerlab/utils" - "github.com/tklauser/numcpus" ) const ( @@ -107,170 +102,43 @@ func deployFn(_ *cobra.Command, _ []string) error { clab.WithDebug(debug), } - c, err := clab.NewContainerLab(opts...) - if err != nil { - return err - } - - err = c.ResolveLinks() - if err != nil { - return err - } - - c.SetClabIntfsEnvVar() - - setFlags(c.Config) - log.Debugf("lab Conf: %+v", c.Config) - - // dispatch a version check that will run in background - vCh := getLatestClabVersion(ctx) - - if reconfigure { - _ = destroyLab(ctx, c) - log.Infof("Removing %s directory...", c.TopoPaths.TopologyLabDir()) - if err := os.RemoveAll(c.TopoPaths.TopologyLabDir()); err != nil { - return err - } - } - - // create management network or use existing one - if err = c.CreateNetwork(ctx); err != nil { - return err - } - - err = links.SetMgmtNetUnderlayingBridge(c.Config.Mgmt.Bridge) - if err != nil { - return err - } - - if err = c.CheckTopologyDefinition(ctx); err != nil { - return err - } - - if err = c.LoadKernelModules(); err != nil { - return err - } - - log.Info("Creating lab directory: ", c.TopoPaths.TopologyLabDir()) - utils.CreateDirectory(c.TopoPaths.TopologyLabDir(), 0755) - // adjust ACL for Labdir such that SUDO_UID Users will - // also have access to lab directory files - err = utils.AdjustFileACLs(c.TopoPaths.TopologyLabDir()) - if err != nil { - log.Infof("unable to adjust Labdir file ACLs: %v", err) - } - - // create an empty ansible inventory file that will get populated later - // we create it here first, so that bind mounts of ansible-inventory.yml file could work - ansibleInvFPath := c.TopoPaths.AnsibleInventoryFileAbsPath() - _, err = os.Create(ansibleInvFPath) - if err != nil { - return err - } - - // in an similar fashion, create an empty topology data file - topoDataFPath := c.TopoPaths.TopoExportFile() - topoDataF, err := os.Create(topoDataFPath) - if err != nil { - return err + // process optional settings + if name != "" { + opts = append(opts, clab.WithLabName(name)) } - - if err := certificateAuthoritySetup(c); err != nil { - return err + if mgmtNetName != "" { + opts = append(opts, clab.WithManagementNetworkName(mgmtNetName)) } - - c.SSHPubKeys, err = c.RetrieveSSHPubKeys() - if err != nil { - log.Warn(err) + if v4 := mgmtIPv4Subnet.String(); v4 != "" { + opts = append(opts, clab.WithManagementIpv4Subnet(v4)) } - - if err := c.CreateAuthzKeysFile(); err != nil { - return err + if v6 := mgmtIPv6Subnet.String(); v6 != "" { + opts = append(opts, clab.WithManagementIpv6Subnet(v6)) } - // determine the number of node and link worker - nodeWorkers, _, err := countWorkers(uint(len(c.Nodes)), uint(len(c.Links)), maxWorkers) + c, err := clab.NewContainerLab(opts...) if err != nil { return err } - // extraHosts holds host entries for nodes with static IPv4/6 addresses - // these entries will be used by container runtime to populate /etc/hosts file - extraHosts := make([]string, 0, len(c.Nodes)) - - for _, n := range c.Nodes { - if n.Config().MgmtIPv4Address != "" { - log.Debugf("Adding static ipv4 /etc/hosts entry for %s:%s", - n.Config().ShortName, n.Config().MgmtIPv4Address) - extraHosts = append(extraHosts, n.Config().ShortName+":"+n.Config().MgmtIPv4Address) - } - - if n.Config().MgmtIPv6Address != "" { - log.Debugf("Adding static ipv6 /etc/hosts entry for %s:%s", - n.Config().ShortName, n.Config().MgmtIPv6Address) - extraHosts = append(extraHosts, n.Config().ShortName+":"+n.Config().MgmtIPv6Address) - } - } - - for _, n := range c.Nodes { - n.Config().ExtraHosts = extraHosts - } + // dispatch a version check that will run in background + vCh := getLatestClabVersion(ctx) - nodesWg, execCollection, err := c.CreateNodes(ctx, nodeWorkers, skipPostDeploy) + deploymentOptions, err := clab.NewDeployOptions(maxWorkers) if err != nil { return err } - // also call deploy on the special nodes endpoints (only host is required for the - // vxlan stitched endpoints) - eps := c.GetSpecialLinkNodes()["host"].GetEndpoints() - for _, ep := range eps { - err = ep.Deploy(ctx) - if err != nil { - log.Warnf("failed deploying endpoint %s", ep) - } - } - - // wait for all the containers to get created - if nodesWg != nil { - nodesWg.Wait() - } - - // write to log - execCollection.Log() - - if err := c.GenerateInventories(); err != nil { - return err - } - - if err := c.GenerateExports(ctx, topoDataF, exportTemplate); err != nil { - return err - } + deploymentOptions.SetExportTemplate(exportTemplate). + SetGraph(graph). + SetMaxWorkers(maxWorkers). + SetSkipPostDeploy(skipPostDeploy) - // generate graph of the lab topology - if graph { - if err = c.GenerateDotGraph(); err != nil { - log.Error(err) - } - } - - containers, err := c.ListNodesContainers(ctx) + containers, err := c.Deploy(ctx, deploymentOptions) if err != nil { return err } - log.Info("Adding containerlab host entries to /etc/hosts file") - err = clab.AppendHostsFileEntries(containers, c.Config.Name) - if err != nil { - log.Errorf("failed to create hosts file: %v", err) - } - - log.Info("Adding ssh config for containerlab nodes") - err = c.AddSSHConfig() - if err != nil { - log.Errorf("failed to create ssh config file: %v", err) - } - // log new version availability info if ready newVerNotification(vCh) @@ -278,63 +146,6 @@ func deployFn(_ *cobra.Command, _ []string) error { return printContainerInspect(containers, deployFormat) } -// certificateAuthoritySetup sets up the certificate authority parameters. -func certificateAuthoritySetup(c *clab.CLab) error { - // init the Cert storage and CA - c.Cert.CertStorage = cert.NewLocalDirCertStorage(c.TopoPaths) - c.Cert.CA = cert.NewCA() - - s := c.Config.Settings - - // Set defaults for the CA parameters - keySize := 2048 - validityDuration := time.Until(time.Now().AddDate(1, 0, 0)) // 1 year as default - - // check that Settings.CertificateAuthority exists. - if s != nil && s.CertificateAuthority != nil { - // if ValidityDuration is set use the value - if s.CertificateAuthority.ValidityDuration != 0 { - validityDuration = s.CertificateAuthority.ValidityDuration - } - - // if KeyLength is set use the value - if s.CertificateAuthority.KeySize != 0 { - keySize = s.CertificateAuthority.KeySize - } - - // if external CA cert and and key are set, propagate to topopaths - extCACert := s.CertificateAuthority.Cert - extCAKey := s.CertificateAuthority.Key - - // override external ca and key from env vars - if v := os.Getenv("CLAB_CA_KEY_FILE"); v != "" { - extCAKey = v - } - - if v := os.Getenv("CLAB_CA_CERT_FILE"); v != "" { - extCACert = v - } - - if extCACert != "" && extCAKey != "" { - err := c.TopoPaths.SetExternalCaFiles(extCACert, extCAKey) - if err != nil { - return err - } - } - } - - // define the attributes used to generate the CA Cert - caCertInput := &cert.CACSRInput{ - CommonName: c.Config.Name + " lab CA", - Country: "US", - Expiry: validityDuration, - Organization: "containerlab", - KeySize: keySize, - } - - return c.LoadOrGenerateCA(caCertInput) -} - // setupCTRLCHandler sets-up the handler for CTRL-C // The deployment will be stopped and a destroy action is // performed when interrupt signal is received. @@ -356,56 +167,3 @@ func setupCTRLCHandler(cancel context.CancelFunc) { os.Exit(1) // skipcq: RVV-A0003 }() } - -func setFlags(conf *clab.Config) { - if name != "" { - conf.Name = name - } - if mgmtNetName != "" { - conf.Mgmt.Network = mgmtNetName - } - if v4 := mgmtIPv4Subnet.String(); v4 != "" { - conf.Mgmt.IPv4Subnet = v4 - } - if v6 := mgmtIPv6Subnet.String(); v6 != "" { - conf.Mgmt.IPv6Subnet = v6 - } -} - -// countWorkers calculates the number workers used for the creation of links and nodes. -// If a user provided --max-workers value it is used for both links and nodes workers. -// If maxWorkers is not set then the workers are limited by the number of available CPUs when -// number of nodes/links exceeds the number of available CPUs. -func countWorkers(nodeCount, linkCount, maxWorkers uint) (nodeWorkers, linkWorkers uint, err error) { - // init number of Workers to the number of links and nodes - nodeWorkers = nodeCount - linkWorkers = linkCount - - switch { - // if maxWorkers is not set, limit workers number by number of available CPUs - case maxWorkers <= 0: - // retrieve vCPU count - vCpus, err := numcpus.GetOnline() - if err != nil { - return 0, 0, err - } - - numCPUs := uint(vCpus) - - // limit node/link workers only if there is more node/links thans CPU cores available - if nodeCount > numCPUs { - nodeWorkers = numCPUs - } - - if linkCount > numCPUs { - linkWorkers = numCPUs - } - case maxWorkers > 0: - nodeWorkers = maxWorkers - linkWorkers = maxWorkers - } - - log.Debugf("Number of Node workers: %d, Number of Link workers: %d", nodeWorkers, linkWorkers) - - return nodeWorkers, linkWorkers, nil -} diff --git a/cmd/inspect.go b/cmd/inspect.go index 3d5bfb591..cc84df094 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -85,7 +85,7 @@ func inspectFn(_ *cobra.Command, _ []string) error { // if the topo file is available, use it if topo != "" { - containers, err = c.ListNodesContainers(ctx) + containers, err = c.listNodesContainers(ctx) if err != nil { return fmt.Errorf("failed to list containers: %s", err) } diff --git a/cmd/tools_veth.go b/cmd/tools_veth.go index d74d1fd62..a2e5cba03 100644 --- a/cmd/tools_veth.go +++ b/cmd/tools_veth.go @@ -147,7 +147,7 @@ func createNodes(ctx context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) e // its namespace path. // techinically we don't care which node this is, as long as it uses // standard veth interface attachment process. - nspath, err := c.GlobalRuntime().GetNSPath(ctx, epDefinition.Node) + nspath, err := c.globalRuntime().GetNSPath(ctx, epDefinition.Node) if err != nil { return err } @@ -227,7 +227,7 @@ func createFakeNode(c *clab.CLab, kind string, nodeCfg *types.NodeConfig) error } // Init - err = n.Init(nodeCfg, nodes.WithRuntime(c.GlobalRuntime())) + err = n.Init(nodeCfg, nodes.WithRuntime(c.globalRuntime())) if err != nil { return fmt.Errorf("failed to initialize node %s: %v", name, err) } diff --git a/nodes/default_node.go b/nodes/default_node.go index aeb8195b8..3a0fed240 100644 --- a/nodes/default_node.go +++ b/nodes/default_node.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "fmt" + "internal/itoa" "os" "path" "path/filepath" @@ -129,15 +130,24 @@ func (d *DefaultNode) VerifyHostRequirements() error { } func (d *DefaultNode) Deploy(ctx context.Context, _ *DeployParams) error { + // Set the "CLAB_INTFS" variable to the number of interfaces + // Which is required by vrnetlab to determine if all configured interfaces are present + // such that the internal VM can be started with these interfaces assigned. + d.Config().Env[types.CLAB_ENV_INTFS] = itoa.Itoa(len(d.GetEndpoints())) + + // create the container cID, err := d.Runtime.CreateContainer(ctx, d.Cfg) if err != nil { return err } + + // start the container _, err = d.Runtime.StartContainer(ctx, cID, d) if err != nil { return err } + // Update the nodes state d.SetState(state.Deployed) return nil From fe8a56fd8bcf5c0a41c7e6ea1b0ab9f607e34444 Mon Sep 17 00:00:00 2001 From: steiler Date: Tue, 27 Feb 2024 14:54:06 +0100 Subject: [PATCH 03/18] update --- clab/clab.go | 54 ++++++++++++++++++++++++++++++++++------ clab/exec_options.go | 17 +++++++++++++ cmd/exec.go | 59 ++++++-------------------------------------- cmd/inspect.go | 17 +++++++------ 4 files changed, 81 insertions(+), 66 deletions(-) create mode 100644 clab/exec_options.go diff --git a/clab/clab.go b/clab/clab.go index b9ef28d0d..0eeb921c2 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -1158,11 +1158,6 @@ func (c *CLab) Version(ctx context.Context) error { return nil } -// Inspect the given topology -func (c *CLab) Inspect(ctx context.Context) error { - return nil -} - // Save configuration of the given topology nodes func (c *CLab) Save(ctx context.Context) error { return nil @@ -1174,8 +1169,53 @@ func (c *CLab) Graph(ctx context.Context) error { } // Exec execute commands on running topology nodes -func (c *CLab) Exec(ctx context.Context) error { - return nil +func (c *CLab) Exec(ctx context.Context, cmds []string, options *ExecOptions) (*exec.ExecCollection, error) { + + err := links.SetMgmtNetUnderlayingBridge(c.Config.Mgmt.Bridge) + if err != nil { + return nil, err + } + + cnts, err := c.ListContainers(ctx, options.filters) + if err != nil { + return nil, err + } + + // make sure filter returned containers + if len(cnts) == 0 { + return nil, fmt.Errorf("filter did not match any containers") + } + + // prepare the exec collection and the exec command + resultCollection := exec.NewExecCollection() + + // build execs from the string input + var execCmds []*exec.ExecCmd + for _, execCmdStr := range cmds { + execCmd, err := exec.NewExecCmdFromString(execCmdStr) + if err != nil { + return nil, err + } + execCmds = append(execCmds, execCmd) + } + + // run the exec commands on all the containers matching the filter + for _, cnt := range cnts { + // iterate over the commands + for _, execCmd := range execCmds { + // execute the commands + execResult, err := cnt.RunExec(ctx, execCmd) + if err != nil { + // skip nodes that do not support exec + if err == exec.ErrRunExecNotSupported { + continue + } + } + resultCollection.Add(cnt.Names[0], execResult) + } + } + + return resultCollection, nil } // Configure topology nodes diff --git a/clab/exec_options.go b/clab/exec_options.go new file mode 100644 index 000000000..e6c099469 --- /dev/null +++ b/clab/exec_options.go @@ -0,0 +1,17 @@ +package clab + +import "github.com/srl-labs/containerlab/types" + +type ExecOptions struct { + filters []*types.GenericFilter +} + +func NewExecOptions(filters []*types.GenericFilter) *ExecOptions { + return &ExecOptions{ + filters: []*types.GenericFilter{}, + } +} + +func (e *ExecOptions) AddFilters(f ...*types.GenericFilter) { + e.filters = append(e.filters, f...) +} diff --git a/cmd/exec.go b/cmd/exec.go index 046dbe5aa..c35112550 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -13,7 +13,6 @@ import ( "github.com/srl-labs/containerlab/clab" "github.com/srl-labs/containerlab/clab/exec" "github.com/srl-labs/containerlab/labels" - "github.com/srl-labs/containerlab/links" "github.com/srl-labs/containerlab/runtime" "github.com/srl-labs/containerlab/types" ) @@ -33,6 +32,10 @@ var execCmd = &cobra.Command{ } func execFn(_ *cobra.Command, _ []string) error { + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if len(execCommands) == 0 { return errors.New("provide command to execute") } @@ -63,23 +66,15 @@ func execFn(_ *cobra.Command, _ []string) error { clab.WithDebug(debug), ) - c, err := clab.NewContainerLab(opts...) - if err != nil { - return err + if name != "" { + opts = append(opts, clab.WithLabName(name)) } - err = links.SetMgmtNetUnderlayingBridge(c.Config.Mgmt.Bridge) + c, err := clab.NewContainerLab(opts...) if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - if name == "" { - name = c.Config.Name - } - var filters []*types.GenericFilter if len(labelsFilter) != 0 { @@ -91,45 +86,7 @@ func execFn(_ *cobra.Command, _ []string) error { filters = append(filters, types.FilterFromLabelStrings(labFilter)...) } - // list all containers using global runtime using provided filters - cnts, err := c.GlobalRuntime().ListContainers(ctx, filters) - if err != nil { - return err - } - - // make sure filter returned containers - if len(cnts) == 0 { - return fmt.Errorf("filter did not match any containers") - } - - // prepare the exec collection and the exec command - resultCollection := exec.NewExecCollection() - - // build execs from the string input - var execCmds []*exec.ExecCmd - for _, execCmdStr := range execCommands { - execCmd, err := exec.NewExecCmdFromString(execCmdStr) - if err != nil { - return err - } - execCmds = append(execCmds, execCmd) - } - - // run the exec commands on all the containers matching the filter - for _, cnt := range cnts { - // iterate over the commands - for _, execCmd := range execCmds { - // execute the commands - execResult, err := cnt.RunExec(ctx, execCmd) - if err != nil { - // skip nodes that do not support exec - if err == exec.ErrRunExecNotSupported { - continue - } - } - resultCollection.Add(cnt.Names[0], execResult) - } - } + resultCollection, err := c.Exec(ctx, execCommands, clab.NewExecOptions(filters)) switch outputFormat { case exec.ExecFormatPlain: diff --git a/cmd/inspect.go b/cmd/inspect.go index cc84df094..37607a6c5 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -50,6 +50,10 @@ func inspectFn(_ *cobra.Command, _ []string) error { fmt.Println("provide either a lab name (--name) or a topology file path (--topo) or the --all flag") return nil } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + opts := []clab.ClabOption{ clab.WithTimeout(timeout), clab.WithRuntime(rt, @@ -69,23 +73,20 @@ func inspectFn(_ *cobra.Command, _ []string) error { ) } + if name != "" { + opts = append(opts, clab.WithLabName(name)) + } + c, err := clab.NewContainerLab(opts...) if err != nil { return fmt.Errorf("could not parse the topology file: %v", err) } - if name == "" { - name = c.Config.Name - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - var containers []runtime.GenericContainer // if the topo file is available, use it if topo != "" { - containers, err = c.listNodesContainers(ctx) + containers, err = c.ListContainers(ctx, nil) if err != nil { return fmt.Errorf("failed to list containers: %s", err) } From b516a8563efb2c6dbc5ab9e7345b3e367824ae42 Mon Sep 17 00:00:00 2001 From: steiler Date: Wed, 13 Mar 2024 16:35:38 +0100 Subject: [PATCH 04/18] update --- clab/clab.go | 81 ++++++++++++++++++++++++++++++++++---- clab/config.go | 6 +-- clab/config_test.go | 4 +- clab/hostsfile.go | 24 +++++++---- cmd/deploy.go | 2 +- cmd/destroy.go | 74 +--------------------------------- cmd/disableTxOffload.go | 25 +++++------- cmd/tools_veth.go | 4 +- links/endpoint.go | 16 ++++---- links/endpoint_bridge.go | 10 ++--- links/endpoint_host.go | 4 +- links/endpoint_macvlan.go | 4 +- links/endpoint_veth.go | 2 +- links/endpoint_vxlan.go | 2 +- links/generic_link_node.go | 2 +- links/link.go | 2 +- links/link_macvlan.go | 4 +- links/link_veth.go | 4 +- links/link_veth_test.go | 2 +- links/link_vxlan.go | 4 +- mocks/mocknodes/node.go | 8 ++-- nodes/default_node.go | 34 +++++++++++++--- nodes/node.go | 2 +- 23 files changed, 170 insertions(+), 150 deletions(-) diff --git a/clab/clab.go b/clab/clab.go index 0eeb921c2..70983b90d 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -32,6 +32,8 @@ import ( "golang.org/x/exp/slices" ) +var ErrNodeNotFound = errors.New("node not found") + type CLab struct { Config *Config `json:"config,omitempty"` TopoPaths *types.TopoPaths @@ -391,6 +393,14 @@ func (c *CLab) globalRuntime() runtime.ContainerRuntime { return c.Runtimes[c.globalRuntimeName] } +// GetNode retrieve a node from the clab instance +func (c *CLab) GetNode(name string) (nodes.Node, error) { + if node, exists := c.Nodes[name]; exists { + return node, nil + } + return nil, fmt.Errorf("%w: %s", ErrNodeNotFound, name) +} + // CreateNodes schedules nodes creation and returns a waitgroup for all nodes // with the exec collection created from the exec config of each node. // The exec collection is returned to the caller to ensure that the execution log @@ -950,18 +960,14 @@ func (c *CLab) ExtractDNSServers(filesys fs.FS) error { func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.GenericContainer, error) { var err error - ctx, cancel := context.WithCancel(ctx) - defer cancel() - err = c.ResolveLinks() if err != nil { return nil, err } log.Debugf("lab Conf: %+v", c.Config) - if options.reconfigure { - _ = c.Destroy(ctx) + _ = c.Destroy(ctx, uint(len(c.Nodes)), true) log.Infof("Removing %s directory...", c.TopoPaths.TopologyLabDir()) if err := os.RemoveAll(c.TopoPaths.TopologyLabDir()); err != nil { return nil, err @@ -1078,7 +1084,7 @@ func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.Ge } log.Info("Adding containerlab host entries to /etc/hosts file") - err = AppendHostsFileEntries(containers, c.Config.Name) + err = c.AppendHostsFileEntries(ctx) if err != nil { log.Errorf("failed to create hosts file: %v", err) } @@ -1150,7 +1156,68 @@ func (c *CLab) certificateAuthoritySetup() error { } // Destroy the given topology -func (c *CLab) Destroy(ctx context.Context) error { +func (c *CLab) Destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) error { + containers, err := c.ListNodesContainersIgnoreNotFound(ctx) + if err != nil { + return err + } + + if len(containers) == 0 { + return nil + } + + if maxWorkers == 0 { + maxWorkers = uint(len(c.Nodes)) + } + + // a set of workers that do not support concurrency + serialNodes := make(map[string]struct{}) + for _, n := range c.Nodes { + if n.GetRuntime().GetName() == ignite.RuntimeName { + serialNodes[n.Config().LongName] = struct{}{} + // decreasing the num of maxWorkers as they are used for concurrent nodes + maxWorkers = maxWorkers - 1 + } + } + + // Serializing ignite workers due to busy device error + if _, ok := c.Runtimes[ignite.RuntimeName]; ok { + maxWorkers = 1 + } + + log.Infof("Destroying lab: %s", c.Config.Name) + c.DeleteNodes(ctx, maxWorkers, serialNodes) + + log.Info("Removing containerlab host entries from /etc/hosts file") + err = c.DeleteEntriesFromHostsFile() + if err != nil { + return fmt.Errorf("error while trying to clean up the hosts file: %w", err) + } + + log.Info("Removing ssh config for containerlab nodes") + err = c.RemoveSSHConfig(c.TopoPaths) + if err != nil { + log.Errorf("failed to remove ssh config file: %v", err) + } + + // delete container network namespaces symlinks + for _, node := range c.Nodes { + err = node.DeleteNetnsSymlink() + if err != nil { + return fmt.Errorf("error while deleting netns symlinks: %w", err) + } + } + + // delete lab management network + if c.Config.Mgmt.Network != "bridge" && !keepMgmtNet { + log.Debugf("Calling DeleteNet method. *CLab.Config.Mgmt value is: %+v", c.Config.Mgmt) + if err = c.globalRuntime().DeleteNet(ctx); err != nil { + // do not log error message if deletion error simply says that such network doesn't exist + if err.Error() != fmt.Sprintf("Error: No such network: %s", c.Config.Mgmt.Network) { + log.Error(err) + } + } + } return nil } diff --git a/clab/config.go b/clab/config.go index 069239058..b6264a5f2 100644 --- a/clab/config.go +++ b/clab/config.go @@ -314,7 +314,7 @@ func (c *CLab) processStartupConfig(nodeCfg *types.NodeConfig) error { // This function runs after topology file is parsed and all nodes/links are initialized. func (c *CLab) checkTopologyDefinition(ctx context.Context) error { var err error - if err = c.verifyLinks(); err != nil { + if err = c.verifyLinks(ctx); err != nil { return err } if err = c.verifyRootNetNSLinks(); err != nil { @@ -372,11 +372,11 @@ func (c *CLab) verifyRootNetNSLinks() error { // verifyLinks checks if all the endpoints in the links section of the topology file // appear only once. -func (c *CLab) verifyLinks() error { +func (c *CLab) verifyLinks(ctx context.Context) error { var err error verificationErrors := []error{} for _, e := range c.Endpoints { - err = e.Verify(c.globalRuntime().Config().VerifyLinkParams) + err = e.Verify(ctx, c.globalRuntime().Config().VerifyLinkParams) if err != nil { verificationErrors = append(verificationErrors, err) } diff --git a/clab/config_test.go b/clab/config_test.go index 8821e0c10..1e262d2e2 100644 --- a/clab/config_test.go +++ b/clab/config_test.go @@ -349,6 +349,8 @@ func TestVerifyLinks(t *testing.T) { teardownTestCase := setupTestCase(t) defer teardownTestCase(t) + ctx := context.Background() + for name, tc := range tests { t.Run(name, func(t *testing.T) { opts := []ClabOption{ @@ -368,7 +370,7 @@ func TestVerifyLinks(t *testing.T) { if err != nil { t.Fatal(err) } - err = c.verifyLinks() + err = c.verifyLinks(ctx) if err != nil && err.Error() != tc.want { t.Fatalf("wanted %q got %q", tc.want, err.Error()) } diff --git a/clab/hostsfile.go b/clab/hostsfile.go index afa925d1e..381760350 100644 --- a/clab/hostsfile.go +++ b/clab/hostsfile.go @@ -3,6 +3,7 @@ package clab import ( "bufio" "bytes" + "context" "errors" "fmt" "io" @@ -19,9 +20,9 @@ const ( clabHostsFilename = "/etc/hosts" ) -func AppendHostsFileEntries(containers []runtime.GenericContainer, labname string) error { +func (c *CLab) AppendHostsFileEntries(ctx context.Context) error { filename := clabHostsFilename - if labname == "" { + if c.Config.Name == "" { return fmt.Errorf("missing lab name") } if !utils.FileExists(filename) { @@ -31,11 +32,17 @@ func AppendHostsFileEntries(containers []runtime.GenericContainer, labname strin } } // lets make sure to remove the entries of a non-properly destroyed lab in the hosts file - err := DeleteEntriesFromHostsFile(labname) + err := c.DeleteEntriesFromHostsFile() if err != nil { return err } - data := generateHostsEntries(containers, labname) + + containers, err := c.listNodesContainers(ctx) + if err != nil { + return err + } + + data := generateHostsEntries(containers, c.Config.Name) if len(data) == 0 { return nil } @@ -80,8 +87,9 @@ func generateHostsEntries(containers []runtime.GenericContainer, labname string) return entries.Bytes() } -func DeleteEntriesFromHostsFile(labname string) error { - if labname == "" { +func (c *CLab) DeleteEntriesFromHostsFile() error { + + if c.Config.Name == "" { return errors.New("missing containerlab name") } f, err := os.OpenFile(clabHostsFilename, os.O_RDWR, 0644) // skipcq: GSC-G302 @@ -92,8 +100,8 @@ func DeleteEntriesFromHostsFile(labname string) error { reader := bufio.NewReader(f) skiplines := false output := bytes.Buffer{} - prefix := fmt.Sprintf(clabHostEntryPrefix, labname) - postfix := fmt.Sprintf(clabHostEntryPostfix, labname) + prefix := fmt.Sprintf(clabHostEntryPrefix, c.Config.Name) + postfix := fmt.Sprintf(clabHostEntryPostfix, c.Config.Name) for { line, err := reader.ReadString(byte('\n')) if err == io.EOF { diff --git a/cmd/deploy.go b/cmd/deploy.go index 24c8d20ab..d54892a1c 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -130,8 +130,8 @@ func deployFn(_ *cobra.Command, _ []string) error { } deploymentOptions.SetExportTemplate(exportTemplate). + SetReconfigure(reconfigure). SetGraph(graph). - SetMaxWorkers(maxWorkers). SetSkipPostDeploy(skipPostDeploy) containers, err := c.Deploy(ctx, deploymentOptions) diff --git a/cmd/destroy.go b/cmd/destroy.go index 4554a9c7d..ce3ed3618 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -16,7 +16,6 @@ import ( "github.com/srl-labs/containerlab/labels" "github.com/srl-labs/containerlab/links" "github.com/srl-labs/containerlab/runtime" - "github.com/srl-labs/containerlab/runtime/ignite" "github.com/srl-labs/containerlab/types" "gopkg.in/yaml.v2" ) @@ -169,78 +168,7 @@ func destroyFn(_ *cobra.Command, _ []string) error { } func destroyLab(ctx context.Context, c *clab.CLab) (err error) { - containers, err := c.ListNodesContainersIgnoreNotFound(ctx) - if err != nil { - return err - } - - if len(containers) == 0 { - return nil - } - - if maxWorkers == 0 { - maxWorkers = uint(len(c.Nodes)) - } - - // a set of workers that do not support concurrency - serialNodes := make(map[string]struct{}) - for _, n := range c.Nodes { - if n.GetRuntime().GetName() == ignite.RuntimeName { - serialNodes[n.Config().LongName] = struct{}{} - // decreasing the num of maxWorkers as they are used for concurrent nodes - maxWorkers = maxWorkers - 1 - } - } - - // Serializing ignite workers due to busy device error - if _, ok := c.Runtimes[ignite.RuntimeName]; ok { - maxWorkers = 1 - } - - // populating the nspath for the nodes - for _, n := range c.Nodes { - nsp, err := n.GetRuntime().GetNSPath(ctx, n.Config().LongName) - if err != nil { - continue - } - n.Config().NSPath = nsp - } - - log.Infof("Destroying lab: %s", c.Config.Name) - c.DeleteNodes(ctx, maxWorkers, serialNodes) - - log.Info("Removing containerlab host entries from /etc/hosts file") - err = clab.DeleteEntriesFromHostsFile(c.Config.Name) - if err != nil { - return fmt.Errorf("error while trying to clean up the hosts file: %w", err) - } - - log.Info("Removing ssh config for containerlab nodes") - err = c.RemoveSSHConfig(c.TopoPaths) - if err != nil { - log.Errorf("failed to remove ssh config file: %v", err) - } - - // delete lab management network - if c.Config.Mgmt.Network != "bridge" && !keepMgmtNet { - log.Debugf("Calling DeleteNet method. *CLab.Config.Mgmt value is: %+v", c.Config.Mgmt) - if err = c.GlobalRuntime().DeleteNet(ctx); err != nil { - // do not log error message if deletion error simply says that such network doesn't exist - if err.Error() != fmt.Sprintf("Error: No such network: %s", c.Config.Mgmt.Network) { - log.Error(err) - } - } - } - - // delete container network namespaces symlinks - for _, node := range c.Nodes { - err = node.DeleteNetnsSymlink() - if err != nil { - return fmt.Errorf("error while deleting netns symlinks: %w", err) - } - } - - return err + return c.Destroy(ctx, maxWorkers, keepMgmtNet) } // listContainers lists containers belonging to a certain topo if topo file path is specified diff --git a/cmd/disableTxOffload.go b/cmd/disableTxOffload.go index 9e4239af0..d12d1be0d 100644 --- a/cmd/disableTxOffload.go +++ b/cmd/disableTxOffload.go @@ -23,6 +23,9 @@ var disableTxOffloadCmd = &cobra.Command{ Short: "disables tx checksum offload on eth0 interface of a container", RunE: func(cmd *cobra.Command, args []string) error { + + ctx := context.Background() + opts := []clab.ClabOption{ clab.WithTimeout(timeout), clab.WithRuntime(rt, @@ -38,36 +41,26 @@ var disableTxOffloadCmd = &cobra.Command{ if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - log.Infof("getting container '%s' information", cntName) - - nodeRuntime := c.GlobalRuntime() - if err != nil { - return err - } - NSPath, err := nodeRuntime.GetNSPath(ctx, cntName) + node, err := c.GetNode("cntName") if err != nil { return err } - nodeNS, err := ns.GetNS(NSPath) - if err != nil { - return err - } - err = nodeNS.Do(func(_ ns.NetNS) error { + disableTXOffload := func(_ ns.NetNS) error { // disabling offload on lo0 interface err = utils.EthtoolTXOff("eth0") if err != nil { log.Infof("Failed to disable TX checksum offload for 'eth0' interface for '%s' container", cntName) } return nil - }) + } + + err = node.ExecFunction(ctx, disableTXOffload) if err != nil { return err } + log.Infof("Tx checksum offload disabled for eth0 interface of %s container", cntName) return nil }, diff --git a/cmd/tools_veth.go b/cmd/tools_veth.go index a2e5cba03..e1f6a2acf 100644 --- a/cmd/tools_veth.go +++ b/cmd/tools_veth.go @@ -147,7 +147,7 @@ func createNodes(ctx context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) e // its namespace path. // techinically we don't care which node this is, as long as it uses // standard veth interface attachment process. - nspath, err := c.globalRuntime().GetNSPath(ctx, epDefinition.Node) + nspath, err := c.Runtimes[rt].GetNSPath(ctx, epDefinition.Node) if err != nil { return err } @@ -227,7 +227,7 @@ func createFakeNode(c *clab.CLab, kind string, nodeCfg *types.NodeConfig) error } // Init - err = n.Init(nodeCfg, nodes.WithRuntime(c.globalRuntime())) + err = n.Init(nodeCfg, nodes.WithRuntime(c.Runtimes[rt])) if err != nil { return fmt.Errorf("failed to initialize node %s: %v", name, err) } diff --git a/links/endpoint.go b/links/endpoint.go index 5df6542ed..cc1eeb933 100644 --- a/links/endpoint.go +++ b/links/endpoint.go @@ -26,11 +26,11 @@ type Endpoint interface { // GetLink retrieves the link that the endpoint is assigned to GetLink() Link // Verify verifies that the endpoint is valid and can be deployed - Verify(*VerifyLinkParams) error + Verify(context.Context, *VerifyLinkParams) error // HasSameNodeAndInterface returns true if an endpoint that implements this interface // has the same node and interface name as the given endpoint. HasSameNodeAndInterface(ept Endpoint) bool - Remove() error + Remove(context.Context) error // Deploy deploys the endpoint by calling the Deploy method of the link it is assigned to // and passing the endpoint as an argument so that the link that consists of A and B endpoints // can deploy them independently. @@ -83,8 +83,8 @@ func (e *EndpointGeneric) GetNode() Node { return e.Node } -func (e *EndpointGeneric) Remove() error { - return e.GetNode().ExecFunction(func(n ns.NetNS) error { +func (e *EndpointGeneric) Remove(ctx context.Context) error { + return e.GetNode().ExecFunction(ctx, func(n ns.NetNS) error { brSideEp, err := netlink.LinkByName(e.GetIfaceName()) _, notfound := err.(netlink.LinkNotFoundError) @@ -130,8 +130,8 @@ func CheckEndpointUniqueness(e Endpoint) error { // CheckEndpointExists checks that a certain // interface exists in the network namespace of the given node. -func CheckEndpointExists(e Endpoint) error { - err := CheckEndpointDoesNotExistYet(e) +func CheckEndpointExists(ctx context.Context, e Endpoint) error { + err := CheckEndpointDoesNotExistYet(ctx, e) if err == nil { return fmt.Errorf("interface %q does not exist", e.String()) } @@ -140,8 +140,8 @@ func CheckEndpointExists(e Endpoint) error { // CheckEndpointDoesNotExistYet verifies that the interface referenced in the // provided endpoint does not yet exist in the referenced node. -func CheckEndpointDoesNotExistYet(e Endpoint) error { - return e.GetNode().ExecFunction(func(_ ns.NetNS) error { +func CheckEndpointDoesNotExistYet(ctx context.Context, e Endpoint) error { + return e.GetNode().ExecFunction(ctx, func(_ ns.NetNS) error { // we expect a netlink.LinkNotFoundError when querying for // the interface with the given endpoints name var err error diff --git a/links/endpoint_bridge.go b/links/endpoint_bridge.go index 5201af276..19e2c1103 100644 --- a/links/endpoint_bridge.go +++ b/links/endpoint_bridge.go @@ -21,19 +21,19 @@ func NewEndpointBridge(eg *EndpointGeneric, isMgmtBridgeEndpoint bool) *Endpoint } } -func (e *EndpointBridge) Verify(p *VerifyLinkParams) error { +func (e *EndpointBridge) Verify(ctx context.Context, p *VerifyLinkParams) error { var errs []error err := CheckEndpointUniqueness(e) if err != nil { errs = append(errs, err) } if p.RunBridgeExistsCheck { - err = CheckBridgeExists(e.GetNode()) + err = CheckBridgeExists(ctx, e.GetNode()) if err != nil { errs = append(errs, err) } } - err = CheckEndpointDoesNotExistYet(e) + err = CheckEndpointDoesNotExistYet(ctx, e) if err != nil { errs = append(errs, err) } @@ -55,8 +55,8 @@ func (e *EndpointBridge) IsNodeless() bool { // CheckBridgeExists verifies that the given bridge is present in the // network namespace referenced via the provided nspath handle. -func CheckBridgeExists(n Node) error { - return n.ExecFunction(func(_ ns.NetNS) error { +func CheckBridgeExists(ctx context.Context, n Node) error { + return n.ExecFunction(ctx, func(_ ns.NetNS) error { br, err := netlink.LinkByName(n.GetShortName()) _, notfound := err.(netlink.LinkNotFoundError) switch { diff --git a/links/endpoint_host.go b/links/endpoint_host.go index 93b8b158e..39b8a0f5b 100644 --- a/links/endpoint_host.go +++ b/links/endpoint_host.go @@ -19,13 +19,13 @@ func (e *EndpointHost) Deploy(ctx context.Context) error { return e.GetLink().Deploy(ctx, e) } -func (e *EndpointHost) Verify(_ *VerifyLinkParams) error { +func (e *EndpointHost) Verify(ctx context.Context, _ *VerifyLinkParams) error { var errs []error err := CheckEndpointUniqueness(e) if err != nil { errs = append(errs, err) } - err = CheckEndpointDoesNotExistYet(e) + err = CheckEndpointDoesNotExistYet(ctx, e) if err != nil { errs = append(errs, err) } diff --git a/links/endpoint_macvlan.go b/links/endpoint_macvlan.go index 2811e0da0..ec44f1944 100644 --- a/links/endpoint_macvlan.go +++ b/links/endpoint_macvlan.go @@ -17,8 +17,8 @@ func (e *EndpointMacVlan) Deploy(ctx context.Context) error { } // Verify runs verification to check if the endpoint can be deployed. -func (e *EndpointMacVlan) Verify(_ *VerifyLinkParams) error { - return CheckEndpointExists(e) +func (e *EndpointMacVlan) Verify(ctx context.Context, _ *VerifyLinkParams) error { + return CheckEndpointExists(ctx, e) } func (e *EndpointMacVlan) IsNodeless() bool { diff --git a/links/endpoint_veth.go b/links/endpoint_veth.go index 51c0e7bef..e9c137cec 100644 --- a/links/endpoint_veth.go +++ b/links/endpoint_veth.go @@ -13,7 +13,7 @@ func NewEndpointVeth(eg *EndpointGeneric) *EndpointVeth { } // Verify verifies the veth based deployment pre-conditions. -func (e *EndpointVeth) Verify(_ *VerifyLinkParams) error { +func (e *EndpointVeth) Verify(_ context.Context, _ *VerifyLinkParams) error { return CheckEndpointUniqueness(e) } diff --git a/links/endpoint_vxlan.go b/links/endpoint_vxlan.go index 9d0db9b32..91e4eee5a 100644 --- a/links/endpoint_vxlan.go +++ b/links/endpoint_vxlan.go @@ -30,7 +30,7 @@ func (e *EndpointVxlan) String() string { } // Verify verifies that the endpoint is valid and can be deployed. -func (e *EndpointVxlan) Verify(*VerifyLinkParams) error { +func (e *EndpointVxlan) Verify(_ context.Context, _ *VerifyLinkParams) error { return CheckEndpointUniqueness(e) } diff --git a/links/generic_link_node.go b/links/generic_link_node.go index 6a00ebf76..11a97ad3c 100644 --- a/links/generic_link_node.go +++ b/links/generic_link_node.go @@ -28,7 +28,7 @@ func (g *GenericLinkNode) AddLinkToContainer(_ context.Context, link netlink.Lin return netns.Do(f) } -func (g *GenericLinkNode) ExecFunction(f func(ns.NetNS) error) error { +func (g *GenericLinkNode) ExecFunction(_ context.Context, f func(ns.NetNS) error) error { // retrieve the namespace handle netns, err := ns.GetNS(g.nspath) if err != nil { diff --git a/links/link.go b/links/link.go index 881522197..6646d43af 100644 --- a/links/link.go +++ b/links/link.go @@ -361,7 +361,7 @@ type Node interface { GetLinkEndpointType() LinkEndpointType GetShortName() string GetEndpoints() []Endpoint - ExecFunction(func(ns.NetNS) error) error + ExecFunction(context.Context, func(ns.NetNS) error) error GetState() state.NodeState Delete(ctx context.Context) error } diff --git a/links/link_macvlan.go b/links/link_macvlan.go index 103f0843f..bf81b4f3f 100644 --- a/links/link_macvlan.go +++ b/links/link_macvlan.go @@ -159,14 +159,14 @@ func (l *LinkMacVlan) Deploy(ctx context.Context, _ Endpoint) error { return err } -func (l *LinkMacVlan) Remove(_ context.Context) error { +func (l *LinkMacVlan) Remove(ctx context.Context) error { // check Deployment state, if the Link was already // removed via e.g. the peer node if l.DeploymentState == LinkDeploymentStateRemoved { return nil } // trigger link removal via the NodeEndpoint - err := l.NodeEndpoint.Remove() + err := l.NodeEndpoint.Remove(ctx) if err != nil { log.Debug(err) } diff --git a/links/link_veth.go b/links/link_veth.go index 7ca915420..692c78ccf 100644 --- a/links/link_veth.go +++ b/links/link_veth.go @@ -243,14 +243,14 @@ func (l *LinkVEth) Deploy(ctx context.Context, ep Endpoint) error { } } -func (l *LinkVEth) Remove(_ context.Context) error { +func (l *LinkVEth) Remove(ctx context.Context) error { l.deployMutex.Lock() defer l.deployMutex.Unlock() if l.DeploymentState == LinkDeploymentStateRemoved { return nil } for _, ep := range l.GetEndpoints() { - err := ep.Remove() + err := ep.Remove(ctx) if err != nil { log.Debug(err) } diff --git a/links/link_veth_test.go b/links/link_veth_test.go index e469a7707..7db684fc9 100644 --- a/links/link_veth_test.go +++ b/links/link_veth_test.go @@ -235,7 +235,7 @@ func (f *fakeNode) GetEndpoints() []Endpoint { return f.Endpoints } -func (*fakeNode) ExecFunction(_ func(ns.NetNS) error) error { +func (*fakeNode) ExecFunction(ctx context.Context, _ func(ns.NetNS) error) error { panic("not implemented") } diff --git a/links/link_vxlan.go b/links/link_vxlan.go index db20ea1f9..764f303ec 100644 --- a/links/link_vxlan.go +++ b/links/link_vxlan.go @@ -271,11 +271,11 @@ func (l *LinkVxlan) deployVxlanInterface() error { return nil } -func (l *LinkVxlan) Remove(_ context.Context) error { +func (l *LinkVxlan) Remove(ctx context.Context) error { if l.DeploymentState == LinkDeploymentStateRemoved { return nil } - err := l.localEndpoint.Remove() + err := l.localEndpoint.Remove(ctx) if err != nil { log.Debug(err) } diff --git a/mocks/mocknodes/node.go b/mocks/mocknodes/node.go index ae25b5c12..ed48a7be1 100644 --- a/mocks/mocknodes/node.go +++ b/mocks/mocknodes/node.go @@ -172,17 +172,17 @@ func (mr *MockNodeMockRecorder) DeployEndpoints(ctx any) *gomock.Call { } // ExecFunction mocks base method. -func (m *MockNode) ExecFunction(arg0 func(ns.NetNS) error) error { +func (m *MockNode) ExecFunction(arg0 context.Context, arg1 func(ns.NetNS) error) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExecFunction", arg0) + ret := m.ctrl.Call(m, "ExecFunction", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // ExecFunction indicates an expected call of ExecFunction. -func (mr *MockNodeMockRecorder) ExecFunction(arg0 any) *gomock.Call { +func (mr *MockNodeMockRecorder) ExecFunction(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecFunction", reflect.TypeOf((*MockNode)(nil).ExecFunction), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecFunction", reflect.TypeOf((*MockNode)(nil).ExecFunction), arg0, arg1) } // GenerateConfig mocks base method. diff --git a/nodes/default_node.go b/nodes/default_node.go index 3a0fed240..e97d54e13 100644 --- a/nodes/default_node.go +++ b/nodes/default_node.go @@ -8,10 +8,10 @@ import ( "bytes" "context" "fmt" - "internal/itoa" "os" "path" "path/filepath" + "strconv" "sync" "text/template" @@ -133,7 +133,7 @@ func (d *DefaultNode) Deploy(ctx context.Context, _ *DeployParams) error { // Set the "CLAB_INTFS" variable to the number of interfaces // Which is required by vrnetlab to determine if all configured interfaces are present // such that the internal VM can be started with these interfaces assigned. - d.Config().Env[types.CLAB_ENV_INTFS] = itoa.Itoa(len(d.GetEndpoints())) + d.Config().Env[types.CLAB_ENV_INTFS] = strconv.Itoa(len(d.GetEndpoints())) // create the container cID, err := d.Runtime.CreateContainer(ctx, d.Cfg) @@ -153,7 +153,19 @@ func (d *DefaultNode) Deploy(ctx context.Context, _ *DeployParams) error { return nil } +// getNsPath retrieve the nodes nspath +func (d *DefaultNode) getNsPath(ctx context.Context) (string, error) { + nsp, err := d.Runtime.GetNSPath(ctx, d.Cfg.LongName) + if err != nil { + log.Errorf("Unable to determine NetNS Path for node %s: %v", d.Cfg.ShortName, err) + } + d.Config().NSPath = nsp + + return nsp, err +} + func (d *DefaultNode) Delete(ctx context.Context) error { + for _, e := range d.Endpoints { err := e.GetLink().Remove(ctx) if err != nil { @@ -464,9 +476,14 @@ func (d *DefaultNode) LoadOrGenerateCertificate(certInfra *cert.Cert, topoName s return nodeCert, nil } -func (d *DefaultNode) AddLinkToContainer(_ context.Context, link netlink.Link, f func(ns.NetNS) error) error { +func (d *DefaultNode) AddLinkToContainer(ctx context.Context, link netlink.Link, f func(ns.NetNS) error) error { + // retrieve nodes nspath + nsp, err := d.getNsPath(ctx) + if err != nil { + return err + } // retrieve the namespace handle - netns, err := ns.GetNS(d.Cfg.NSPath) + netns, err := ns.GetNS(nsp) if err != nil { return err } @@ -483,8 +500,13 @@ func (d *DefaultNode) AddLinkToContainer(_ context.Context, link netlink.Link, f } // ExecFunction executes the given function in the nodes network namespace. -func (d *DefaultNode) ExecFunction(f func(ns.NetNS) error) error { - nspath := d.Cfg.NSPath +func (d *DefaultNode) ExecFunction(ctx context.Context, f func(ns.NetNS) error) error { + + // retrieve nodes nspath + nspath, err := d.getNsPath(ctx) + if err != nil { + return err + } if d.Cfg.IsRootNamespaceBased { nshandle, err := ns.GetCurrentNS() diff --git a/nodes/node.go b/nodes/node.go index 825efe5dc..4312db712 100644 --- a/nodes/node.go +++ b/nodes/node.go @@ -103,7 +103,7 @@ type Node interface { // DeployEndpoints deploys the links for the node. DeployEndpoints(ctx context.Context) error // ExecFunction executes the given function within the nodes network namespace - ExecFunction(func(ns.NetNS) error) error + ExecFunction(context.Context, func(ns.NetNS) error) error GetState() state.NodeState SetState(state.NodeState) GetSSHConfig() *types.SSHConfig From 88173e74b095a066cca2ae50e479d9dfb34517d1 Mon Sep 17 00:00:00 2001 From: steiler Date: Wed, 13 Mar 2024 17:07:53 +0100 Subject: [PATCH 05/18] fix exec --- clab/clab.go | 1 + clab/exec_options.go | 2 +- cmd/exec.go | 3 +++ tests/01-smoke/01-basic-flow.robot | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/clab/clab.go b/clab/clab.go index 70983b90d..55e2302db 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -1278,6 +1278,7 @@ func (c *CLab) Exec(ctx context.Context, cmds []string, options *ExecOptions) (* continue } } + log.Warnf("error while execing on node %s: %v", cnt.Names[0], err) resultCollection.Add(cnt.Names[0], execResult) } } diff --git a/clab/exec_options.go b/clab/exec_options.go index e6c099469..da31ddef5 100644 --- a/clab/exec_options.go +++ b/clab/exec_options.go @@ -8,7 +8,7 @@ type ExecOptions struct { func NewExecOptions(filters []*types.GenericFilter) *ExecOptions { return &ExecOptions{ - filters: []*types.GenericFilter{}, + filters: filters, } } diff --git a/cmd/exec.go b/cmd/exec.go index c35112550..256a387a9 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -87,6 +87,9 @@ func execFn(_ *cobra.Command, _ []string) error { } resultCollection, err := c.Exec(ctx, execCommands, clab.NewExecOptions(filters)) + if err != nil { + return err + } switch outputFormat { case exec.ExecFormatPlain: diff --git a/tests/01-smoke/01-basic-flow.robot b/tests/01-smoke/01-basic-flow.robot index 8a8317cce..ed57210f5 100644 --- a/tests/01-smoke/01-basic-flow.robot +++ b/tests/01-smoke/01-basic-flow.robot @@ -86,7 +86,7 @@ Ensure CLAB_INTFS env var is set ... This test ensures that the CLAB_INTFS environment variable is set in the container ... and that it contains the correct number of interfaces. ${output} = Process.Run Process - ... sudo -E ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 --cmd 'ash -c "echo $CLAB_INTFS"' + ... sudo -E ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 --cmd 'ash -c "echo $$CLAB_INTFS"' ... shell=True Log ${output.stdout} Log ${output.stderr} From c0b461ea3cdefddff6c73971d65cce154d6fdd24 Mon Sep 17 00:00:00 2001 From: steiler Date: Wed, 13 Mar 2024 17:56:25 +0100 Subject: [PATCH 06/18] fix nspath for bridge --- nodes/default_node.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/nodes/default_node.go b/nodes/default_node.go index e97d54e13..5af6813f9 100644 --- a/nodes/default_node.go +++ b/nodes/default_node.go @@ -155,9 +155,22 @@ func (d *DefaultNode) Deploy(ctx context.Context, _ *DeployParams) error { // getNsPath retrieve the nodes nspath func (d *DefaultNode) getNsPath(ctx context.Context) (string, error) { - nsp, err := d.Runtime.GetNSPath(ctx, d.Cfg.LongName) - if err != nil { - log.Errorf("Unable to determine NetNS Path for node %s: %v", d.Cfg.ShortName, err) + var err error + nsp := "" + + if d.Cfg.IsRootNamespaceBased { + netns, err := ns.GetCurrentNS() + if err != nil { + return "", err + } + nsp = netns.Path() + } + if nsp == "" { + nsp, err = d.Runtime.GetNSPath(ctx, d.Cfg.LongName) + if err != nil { + log.Errorf("Unable to determine NetNS Path for node %s: %v", d.Cfg.ShortName, err) + return "", err + } } d.Config().NSPath = nsp From 376e95e36c06a7e4b718901609fd6300f32814b7 Mon Sep 17 00:00:00 2001 From: steiler Date: Wed, 13 Mar 2024 19:18:05 +0100 Subject: [PATCH 07/18] update --- cmd/disableTxOffload.go | 12 +----------- cmd/tools_veth.go | 10 ++-------- nodes/border0/border0.go | 5 +++-- nodes/default_node.go | 1 - nodes/ext_container/ext_container.go | 10 ---------- nodes/linux/linux.go | 9 ++++++--- runtime/docker/docker.go | 5 ++--- runtime/ignite/ignite.go | 5 ++--- runtime/podman/util.go | 10 +++++----- types/types.go | 25 +------------------------ utils/ethtool.go | 15 +++++++++++++++ 11 files changed, 37 insertions(+), 70 deletions(-) diff --git a/cmd/disableTxOffload.go b/cmd/disableTxOffload.go index d12d1be0d..f7e6f1d3b 100644 --- a/cmd/disableTxOffload.go +++ b/cmd/disableTxOffload.go @@ -7,7 +7,6 @@ package cmd import ( "context" - "github.com/containernetworking/plugins/pkg/ns" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/srl-labs/containerlab/clab" @@ -47,16 +46,7 @@ var disableTxOffloadCmd = &cobra.Command{ return err } - disableTXOffload := func(_ ns.NetNS) error { - // disabling offload on lo0 interface - err = utils.EthtoolTXOff("eth0") - if err != nil { - log.Infof("Failed to disable TX checksum offload for 'eth0' interface for '%s' container", cntName) - } - return nil - } - - err = node.ExecFunction(ctx, disableTXOffload) + err = node.ExecFunction(ctx, utils.NSEthtoolTXOff(cntName, "eth0")) if err != nil { return err } diff --git a/cmd/tools_veth.go b/cmd/tools_veth.go index e1f6a2acf..e1f99e116 100644 --- a/cmd/tools_veth.go +++ b/cmd/tools_veth.go @@ -123,7 +123,7 @@ var vethCreateCmd = &cobra.Command{ // createNodes creates fake nodes in c.Nodes map to make link resolve work. // It checks which endpoint type is set by a user and creates a node that matches the type. -func createNodes(ctx context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) error { +func createNodes(_ context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) error { for _, epDefinition := range []parsedEndpoint{AEnd, BEnd} { switch epDefinition.Kind { case links.LinkEndpointTypeHost: @@ -147,14 +147,8 @@ func createNodes(ctx context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) e // its namespace path. // techinically we don't care which node this is, as long as it uses // standard veth interface attachment process. - nspath, err := c.Runtimes[rt].GetNSPath(ctx, epDefinition.Node) - if err != nil { - return err - } - - err = createFakeNode(c, "linux", &types.NodeConfig{ + err := createFakeNode(c, "linux", &types.NodeConfig{ ShortName: epDefinition.Node, - NSPath: nspath, }) if err != nil { return err diff --git a/nodes/border0/border0.go b/nodes/border0/border0.go index 40f887830..16737df49 100644 --- a/nodes/border0/border0.go +++ b/nodes/border0/border0.go @@ -87,9 +87,10 @@ func (b *border0) PreDeploy(_ context.Context, params *nodes.PreDeployParams) er func (b *border0) PostDeploy(ctx context.Context, params *nodes.PostDeployParams) error { // disable tx offloading log.Debugf("Running postdeploy actions for border0.com node %q", b.Cfg.ShortName) - err := types.DisableTxOffload(b.Cfg) + + err := b.ExecFunction(ctx, utils.NSEthtoolTXOff(b.GetShortName(), "eth0")) if err != nil { - return fmt.Errorf("failed to disable tx checksum offload for border0 kind: %v", err) + log.Error(err) } log.Infof("Creating border0.com tunnels...") diff --git a/nodes/default_node.go b/nodes/default_node.go index 5af6813f9..e292372d9 100644 --- a/nodes/default_node.go +++ b/nodes/default_node.go @@ -172,7 +172,6 @@ func (d *DefaultNode) getNsPath(ctx context.Context) (string, error) { return "", err } } - d.Config().NSPath = nsp return nsp, err } diff --git a/nodes/ext_container/ext_container.go b/nodes/ext_container/ext_container.go index 69c3ca0f7..4aa31831a 100644 --- a/nodes/ext_container/ext_container.go +++ b/nodes/ext_container/ext_container.go @@ -7,8 +7,6 @@ package ext_container import ( "context" - "github.com/pkg/errors" - "github.com/srl-labs/containerlab/labels" "github.com/srl-labs/containerlab/nodes" "github.com/srl-labs/containerlab/nodes/state" @@ -48,14 +46,6 @@ func (e *extcont) Deploy(ctx context.Context, _ *nodes.DeployParams) error { return err } - // request nspath from runtime - nspath, err := e.Runtime.GetNSPath(ctx, e.Cfg.ShortName) - if err != nil { - return errors.Wrap(err, "reading external container namespace path") - } - // set nspath in node config - e.Cfg.NSPath = nspath - e.SetState(state.Deployed) return nil diff --git a/nodes/linux/linux.go b/nodes/linux/linux.go index 043699102..cde187dcc 100644 --- a/nodes/linux/linux.go +++ b/nodes/linux/linux.go @@ -14,6 +14,7 @@ import ( "github.com/srl-labs/containerlab/nodes/state" "github.com/srl-labs/containerlab/runtime/ignite" "github.com/srl-labs/containerlab/types" + "github.com/srl-labs/containerlab/utils" "github.com/weaveworks/ignite/pkg/operations" ) @@ -64,10 +65,12 @@ func (n *linux) Deploy(ctx context.Context, _ *nodes.DeployParams) error { return err } -func (n *linux) PostDeploy(_ context.Context, _ *nodes.PostDeployParams) error { +func (n *linux) PostDeploy(ctx context.Context, _ *nodes.PostDeployParams) error { log.Debugf("Running postdeploy actions for Linux '%s' node", n.Cfg.ShortName) - if err := types.DisableTxOffload(n.Cfg); err != nil { - return err + + err := n.ExecFunction(ctx, utils.NSEthtoolTXOff(n.GetShortName(), "eth0")) + if err != nil { + log.Error(err) } // when ignite runtime is in use diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go index f07ebb2fb..5a7fdc612 100644 --- a/runtime/docker/docker.go +++ b/runtime/docker/docker.go @@ -594,12 +594,11 @@ func (d *DockerRuntime) StartContainer(ctx context.Context, cID string, node run // postStartActions performs misc. tasks that are needed after the container starts. func (d *DockerRuntime) postStartActions(ctx context.Context, cID string, node *types.NodeConfig) error { - var err error - node.NSPath, err = d.GetNSPath(ctx, cID) + nspath, err := d.GetNSPath(ctx, cID) if err != nil { return err } - err = utils.LinkContainerNS(node.NSPath, node.LongName) + err = utils.LinkContainerNS(nspath, node.LongName) return err } diff --git a/runtime/ignite/ignite.go b/runtime/ignite/ignite.go index f30dec516..a76492bbe 100644 --- a/runtime/ignite/ignite.go +++ b/runtime/ignite/ignite.go @@ -264,12 +264,11 @@ func (c *IgniteRuntime) StartContainer(ctx context.Context, _ string, node runti return nil, err } - nodecfg.NSPath, err = c.GetNSPath(ctx, vm.PrefixedID()) + nspath, err := c.GetNSPath(ctx, vm.PrefixedID()) if err != nil { return nil, err } - - return vmChans, utils.LinkContainerNS(nodecfg.NSPath, nodecfg.LongName) + return vmChans, utils.LinkContainerNS(nspath, nodecfg.LongName) } func (*IgniteRuntime) CreateContainer(_ context.Context, node *types.NodeConfig) (string, error) { diff --git a/runtime/podman/util.go b/runtime/podman/util.go index 4ce51bb8b..3fdf79f27 100644 --- a/runtime/podman/util.go +++ b/runtime/podman/util.go @@ -501,14 +501,14 @@ func (r *PodmanRuntime) postStartActions(ctx context.Context, cID string, cfg *t return nil } var err error - // Add NSpath to the node config struct - cfg.NSPath, err = r.GetNSPath(ctx, cID) + + // And setup netns alias. Not really needed with podman + // But currently (Oct 2021) clab depends on the specific naming scheme of veth aliases. + nspath, err := r.GetNSPath(ctx, cID) if err != nil { return err } - // And setup netns alias. Not really needed with podman - // But currently (Oct 2021) clab depends on the specific naming scheme of veth aliases. - err = utils.LinkContainerNS(cfg.NSPath, cfg.LongName) + err = utils.LinkContainerNS(nspath, cfg.LongName) if err != nil { return err } diff --git a/types/types.go b/types/types.go index 3edda1c36..70c6176c0 100644 --- a/types/types.go +++ b/types/types.go @@ -9,10 +9,8 @@ import ( "strings" "time" - "github.com/containernetworking/plugins/pkg/ns" "github.com/docker/go-connections/nat" log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/utils" "gopkg.in/yaml.v2" ) @@ -164,7 +162,7 @@ type NodeConfig struct { Certificate *CertificateConfig // Healthcheck configuration parameters Healthcheck *HealthcheckConfig - NSPath string `json:"nspath,omitempty"` // network namespace path for this node + // NSPath string `json:"nspath,omitempty"` // network namespace path for this node // list of ports to publish with mysocketctl Publish []string `json:"publish,omitempty"` // Extra /etc/hosts entries for all nodes. @@ -196,27 +194,6 @@ type NodeConfig struct { SkipUniquenessCheck bool } -func DisableTxOffload(n *NodeConfig) error { - // skip this if node runs in host mode - if strings.ToLower(n.NetworkMode) == "host" || strings.ToLower(n.NetworkMode) == "none" { - return nil - } - // disable tx checksum offload for linux containers on eth0 interfaces - nodeNS, err := ns.GetNS(n.NSPath) - if err != nil { - return err - } - err = nodeNS.Do(func(_ ns.NetNS) error { - // disabling offload on eth0 interface - err := utils.EthtoolTXOff("eth0") - if err != nil { - log.Infof("Failed to disable TX checksum offload for 'eth0' interface for Linux '%s' node: %v", n.ShortName, err) - } - return err - }) - return err -} - type GenericFilter struct { // defined by now "label" / "name" [then only Match is required] FilterType string diff --git a/utils/ethtool.go b/utils/ethtool.go index 50ba78621..8d8742339 100644 --- a/utils/ethtool.go +++ b/utils/ethtool.go @@ -8,6 +8,9 @@ import ( "fmt" "syscall" "unsafe" + + "github.com/containernetworking/plugins/pkg/ns" + log "github.com/sirupsen/logrus" ) const ( @@ -37,6 +40,18 @@ func ioctlEthtool(fd int, argp uintptr) error { return nil } +// NSEthtoolTXOff EthtoolTXOff wrapper that can be handed straight to Node.ExecFunc() +func NSEthtoolTXOff(cntName, ifaceName string) func(ns.NetNS) error { + return func(ns.NetNS) error { + // disabling offload on given interface + err := EthtoolTXOff(ifaceName) + if err != nil { + log.Infof("failed to disable TX checksum offload for %s interface for %s container", ifaceName, cntName) + } + return nil + } +} + // EthtoolTXOff disables TX checksum offload on specified interface. func EthtoolTXOff(name string) error { if len(name)+1 > IFNAMSIZ { From 55d11b279cfe43348bd0aa8cf9ded3501d44768b Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 09:17:03 +0100 Subject: [PATCH 08/18] fix tool create veth command --- cmd/tools_veth.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/tools_veth.go b/cmd/tools_veth.go index e1f99e116..1c5e9fe44 100644 --- a/cmd/tools_veth.go +++ b/cmd/tools_veth.go @@ -77,8 +77,13 @@ var vethCreateCmd = &cobra.Command{ ctx, cancel := context.WithCancel(context.Background()) defer cancel() + rtName, _, err := clab.RuntimeInitializer(name) + if err != nil { + return err + } + // create fake nodes to make links resolve work - err = createNodes(ctx, c, parsedAEnd, parsedBEnd) + err = createNodes(ctx, c, parsedAEnd, parsedBEnd, rtName) if err != nil { return err } @@ -123,12 +128,14 @@ var vethCreateCmd = &cobra.Command{ // createNodes creates fake nodes in c.Nodes map to make link resolve work. // It checks which endpoint type is set by a user and creates a node that matches the type. -func createNodes(_ context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) error { +func createNodes(_ context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint, rt string) error { for _, epDefinition := range []parsedEndpoint{AEnd, BEnd} { switch epDefinition.Kind { case links.LinkEndpointTypeHost: err := createFakeNode(c, "host", &types.NodeConfig{ ShortName: epDefinition.Node, + LongName: epDefinition.Node, + Runtime: rt, }) if err != nil { return err @@ -137,6 +144,8 @@ func createNodes(_ context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) err case links.LinkEndpointTypeBridge: err := createFakeNode(c, "bridge", &types.NodeConfig{ ShortName: epDefinition.Node, + LongName: epDefinition.Node, + Runtime: rt, }) if err != nil { return err @@ -149,6 +158,8 @@ func createNodes(_ context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) err // standard veth interface attachment process. err := createFakeNode(c, "linux", &types.NodeConfig{ ShortName: epDefinition.Node, + LongName: epDefinition.Node, + Runtime: rt, }) if err != nil { return err @@ -221,7 +232,7 @@ func createFakeNode(c *clab.CLab, kind string, nodeCfg *types.NodeConfig) error } // Init - err = n.Init(nodeCfg, nodes.WithRuntime(c.Runtimes[rt])) + err = n.Init(nodeCfg, nodes.WithRuntime(c.Runtimes[nodeCfg.Runtime])) if err != nil { return fmt.Errorf("failed to initialize node %s: %v", name, err) } From 6aab0c0b6761210c7b2d56dd7bdfd1897fb4301c Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 10:11:29 +0100 Subject: [PATCH 09/18] fix ext-container --- nodes/ext_container/ext_container.go | 1 + tests/01-smoke/01-basic-flow.robot | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nodes/ext_container/ext_container.go b/nodes/ext_container/ext_container.go index 4aa31831a..bc77b3278 100644 --- a/nodes/ext_container/ext_container.go +++ b/nodes/ext_container/ext_container.go @@ -36,6 +36,7 @@ func (s *extcont) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error { // Indicate that the pre-deployment UniquenessCheck is to be skipped. // Since we would stop deployment on pre-existing containers. s.Cfg.SkipUniquenessCheck = true + s.Cfg.LongName = s.Cfg.ShortName return nil } diff --git a/tests/01-smoke/01-basic-flow.robot b/tests/01-smoke/01-basic-flow.robot index ed57210f5..8a8317cce 100644 --- a/tests/01-smoke/01-basic-flow.robot +++ b/tests/01-smoke/01-basic-flow.robot @@ -86,7 +86,7 @@ Ensure CLAB_INTFS env var is set ... This test ensures that the CLAB_INTFS environment variable is set in the container ... and that it contains the correct number of interfaces. ${output} = Process.Run Process - ... sudo -E ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 --cmd 'ash -c "echo $$CLAB_INTFS"' + ... sudo -E ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 --cmd 'ash -c "echo $CLAB_INTFS"' ... shell=True Log ${output.stdout} Log ${output.stderr} From 2e8b324bc5a56c3f3acd02d055208a3c0dc0bcc4 Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 10:12:28 +0100 Subject: [PATCH 10/18] fix CLAB_INTFS on linux node --- nodes/linux/linux.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nodes/linux/linux.go b/nodes/linux/linux.go index cde187dcc..7bc1c5503 100644 --- a/nodes/linux/linux.go +++ b/nodes/linux/linux.go @@ -7,6 +7,7 @@ package linux import ( "context" "fmt" + "strconv" "strings" log "github.com/sirupsen/logrus" @@ -50,6 +51,11 @@ func (n *linux) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error { } func (n *linux) Deploy(ctx context.Context, _ *nodes.DeployParams) error { + // Set the "CLAB_INTFS" variable to the number of interfaces + // Which is required by vrnetlab to determine if all configured interfaces are present + // such that the internal VM can be started with these interfaces assigned. + n.Config().Env[types.CLAB_ENV_INTFS] = strconv.Itoa(len(n.GetEndpoints())) + cID, err := n.Runtime.CreateContainer(ctx, n.Cfg) if err != nil { return err From 4b393084b4117769bb7c96e72b9e80c1cacd0fec Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 12:05:09 +0100 Subject: [PATCH 11/18] add host endpoints --- clab/clab.go | 10 ++++++++++ tests/08-vxlan/02-vxlan-stitch.clab.yml | 1 + 2 files changed, 11 insertions(+) diff --git a/clab/clab.go b/clab/clab.go index 55e2302db..524bee49c 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -1056,6 +1056,16 @@ func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.Ge return nil, err } + // also call deploy on the special nodes endpoints (only host is required for the + // vxlan stitched endpoints) + eps := c.getSpecialLinkNodes()["host"].GetEndpoints() + for _, ep := range eps { + err = ep.Deploy(ctx) + if err != nil { + log.Warnf("failed deploying endpoint %s", ep) + } + } + if nodesWg != nil { nodesWg.Wait() } diff --git a/tests/08-vxlan/02-vxlan-stitch.clab.yml b/tests/08-vxlan/02-vxlan-stitch.clab.yml index 65393ed7e..52f83a430 100644 --- a/tests/08-vxlan/02-vxlan-stitch.clab.yml +++ b/tests/08-vxlan/02-vxlan-stitch.clab.yml @@ -3,6 +3,7 @@ name: vxlan-stitch mgmt: network: clab-vxlan bridge: clab-vxlan-br + mtu: 9100 ipv4-subnet: 172.20.25.0/24 topology: From 6010b296f678afef13d8c0f95835defaff35392f Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 12:58:27 +0100 Subject: [PATCH 12/18] fix runtime detection for tools veth create --- cmd/tools_veth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tools_veth.go b/cmd/tools_veth.go index 1c5e9fe44..9086b5019 100644 --- a/cmd/tools_veth.go +++ b/cmd/tools_veth.go @@ -77,7 +77,7 @@ var vethCreateCmd = &cobra.Command{ ctx, cancel := context.WithCancel(context.Background()) defer cancel() - rtName, _, err := clab.RuntimeInitializer(name) + rtName, _, err := clab.RuntimeInitializer(rt) if err != nil { return err } From fd483a5cac32a34d7bcaa9b3afbb95894ba30317 Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 13:27:42 +0100 Subject: [PATCH 13/18] limit internal functions visibility --- clab/authz_keys.go | 4 ++-- clab/clab.go | 47 ++++++++++++++-------------------------------- clab/clab_test.go | 6 +++--- clab/hostsfile.go | 2 +- clab/sshconfig.go | 4 ++-- 5 files changed, 22 insertions(+), 41 deletions(-) diff --git a/clab/authz_keys.go b/clab/authz_keys.go index 9be42f854..e1e3a4a69 100644 --- a/clab/authz_keys.go +++ b/clab/authz_keys.go @@ -25,9 +25,9 @@ const ( authzKeysFPath = "~/.ssh/authorized_keys" ) -// CreateAuthzKeysFile creates the authorized_keys file in the lab directory +// createAuthzKeysFile creates the authorized_keys file in the lab directory // using the public ssh keys retrieved from agent and local files. -func (c *CLab) CreateAuthzKeysFile() error { +func (c *CLab) createAuthzKeysFile() error { b := new(bytes.Buffer) for _, k := range c.SSHPubKeys { diff --git a/clab/clab.go b/clab/clab.go index 524bee49c..f8ed83ca9 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -359,7 +359,7 @@ func NewContainerLab(opts ...ClabOption) (*CLab, error) { // Extract the host systems DNS servers and populate the // Nodes DNS Config with these if not specifically provided fileSystem := os.DirFS("/") - if err := c.ExtractDNSServers(fileSystem); err != nil { + if err := c.extractDNSServers(fileSystem); err != nil { return nil, err } @@ -401,12 +401,12 @@ func (c *CLab) GetNode(name string) (nodes.Node, error) { return nil, fmt.Errorf("%w: %s", ErrNodeNotFound, name) } -// CreateNodes schedules nodes creation and returns a waitgroup for all nodes +// createNodes schedules nodes creation and returns a waitgroup for all nodes // with the exec collection created from the exec config of each node. // The exec collection is returned to the caller to ensure that the execution log // is printed after the nodes are created. // Nodes interdependencies are created in this function. -func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, skipPostDeploy bool) (*sync.WaitGroup, *exec.ExecCollection, error) { +func (c *CLab) createNodes(ctx context.Context, maxWorkers uint, skipPostDeploy bool) (*sync.WaitGroup, *exec.ExecCollection, error) { for _, node := range c.Nodes { c.dependencyManager.AddNode(node) } @@ -709,7 +709,7 @@ func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int, skipPostDeploy node.EnterStage(ctx, types.WaitForCreate) // wait for possible external dependencies - c.WaitForExternalNodeDependencies(ctx, node.Config().ShortName) + c.waitForExternalNodeDependencies(ctx, node.Config().ShortName) // when all nodes that this node depends on are created, push it into the channel workerChan <- node // indicate we are done, such that only when all of these functions are done, the workerChan is being closed @@ -726,10 +726,10 @@ func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int, skipPostDeploy return wg, execCollection } -// WaitForExternalNodeDependencies makes nodes that have a reference to an external container network-namespace (network-mode: container:) +// waitForExternalNodeDependencies makes nodes that have a reference to an external container network-namespace (network-mode: container:) // to wait until the referenced container is in started status. // The wait time is 15 minutes by default. -func (c *CLab) WaitForExternalNodeDependencies(ctx context.Context, nodeName string) { +func (c *CLab) waitForExternalNodeDependencies(ctx context.Context, nodeName string) { if _, exists := c.Nodes[nodeName]; !exists { log.Errorf("unable to find referenced node %q", nodeName) return @@ -752,7 +752,7 @@ func (c *CLab) WaitForExternalNodeDependencies(ctx context.Context, nodeName str runtime.WaitForContainerRunning(ctx, c.Runtimes[c.globalRuntimeName], contName, nodeName) } -func (c *CLab) DeleteNodes(ctx context.Context, workers uint, serialNodes map[string]struct{}) { +func (c *CLab) deleteNodes(ctx context.Context, workers uint, serialNodes map[string]struct{}) { wg := new(sync.WaitGroup) concurrentChan := make(chan nodes.Node) @@ -918,9 +918,9 @@ func (c *CLab) ResolveLinks() error { return nil } -// ExtractDNSServers extracts DNS servers from the resolv.conf files +// extractDNSServers extracts DNS servers from the resolv.conf files // and populates the Nodes DNS Config with these if not specifically provided. -func (c *CLab) ExtractDNSServers(filesys fs.FS) error { +func (c *CLab) extractDNSServers(filesys fs.FS) error { // extract DNS servers from the relevant resolv.conf files DNSServers, err := utils.ExtractDNSServersFromResolvConf(filesys, []string{"etc/resolv.conf", "run/systemd/resolve/resolv.conf"}) @@ -1025,7 +1025,7 @@ func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.Ge log.Warn(err) } - if err := c.CreateAuthzKeysFile(); err != nil { + if err := c.createAuthzKeysFile(); err != nil { return nil, err } @@ -1051,7 +1051,7 @@ func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.Ge n.Config().ExtraHosts = extraHosts } - nodesWg, execCollection, err := c.CreateNodes(ctx, options.maxWorkers, options.skipPostDeploy) + nodesWg, execCollection, err := c.createNodes(ctx, options.maxWorkers, options.skipPostDeploy) if err != nil { return nil, err } @@ -1094,13 +1094,13 @@ func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.Ge } log.Info("Adding containerlab host entries to /etc/hosts file") - err = c.AppendHostsFileEntries(ctx) + err = c.appendHostsFileEntries(ctx) if err != nil { log.Errorf("failed to create hosts file: %v", err) } log.Info("Adding ssh config for containerlab nodes") - err = c.AddSSHConfig() + err = c.addSSHConfig() if err != nil { log.Errorf("failed to create ssh config file: %v", err) } @@ -1196,7 +1196,7 @@ func (c *CLab) Destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) e } log.Infof("Destroying lab: %s", c.Config.Name) - c.DeleteNodes(ctx, maxWorkers, serialNodes) + c.deleteNodes(ctx, maxWorkers, serialNodes) log.Info("Removing containerlab host entries from /etc/hosts file") err = c.DeleteEntriesFromHostsFile() @@ -1231,20 +1231,6 @@ func (c *CLab) Destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) e return nil } -func (c *CLab) Version(ctx context.Context) error { - return nil -} - -// Save configuration of the given topology nodes -func (c *CLab) Save(ctx context.Context) error { - return nil -} - -// Graph the given topology -func (c *CLab) Graph(ctx context.Context) error { - return nil -} - // Exec execute commands on running topology nodes func (c *CLab) Exec(ctx context.Context, cmds []string, options *ExecOptions) (*exec.ExecCollection, error) { @@ -1295,8 +1281,3 @@ func (c *CLab) Exec(ctx context.Context, cmds []string, options *ExecOptions) (* return resultCollection, nil } - -// Configure topology nodes -func (c *CLab) Configure(ctx context.Context) error { - return nil -} diff --git a/clab/clab_test.go b/clab/clab_test.go index 8059fd4f7..76c2441a3 100644 --- a/clab/clab_test.go +++ b/clab/clab_test.go @@ -200,7 +200,7 @@ func Test_WaitForExternalNodeDependencies_OK(t *testing.T) { } // run the check - c.WaitForExternalNodeDependencies(ctx, "node4") + c.waitForExternalNodeDependencies(ctx, "node4") // check that the function was called "counterMax" times if counter != counterMax { @@ -218,7 +218,7 @@ func Test_WaitForExternalNodeDependencies_NoContainerNetworkMode(t *testing.T) { } // run the check with a node that has no "network-mode: container:" - c.WaitForExternalNodeDependencies(context.TODO(), "node5") + c.waitForExternalNodeDependencies(context.TODO(), "node5") // should simply and quickly return } @@ -232,7 +232,7 @@ func Test_WaitForExternalNodeDependencies_NodeNonExisting(t *testing.T) { } // run the check with a node that has no "network-mode: container:" - c.WaitForExternalNodeDependencies(context.TODO(), "NonExistingNode") + c.waitForExternalNodeDependencies(context.TODO(), "NonExistingNode") // should simply and quickly return } diff --git a/clab/hostsfile.go b/clab/hostsfile.go index 381760350..695bf2ae3 100644 --- a/clab/hostsfile.go +++ b/clab/hostsfile.go @@ -20,7 +20,7 @@ const ( clabHostsFilename = "/etc/hosts" ) -func (c *CLab) AppendHostsFileEntries(ctx context.Context) error { +func (c *CLab) appendHostsFileEntries(ctx context.Context) error { filename := clabHostsFilename if c.Config.Name == "" { return fmt.Errorf("missing lab name") diff --git a/clab/sshconfig.go b/clab/sshconfig.go index 27d3621e7..8002cd8a9 100644 --- a/clab/sshconfig.go +++ b/clab/sshconfig.go @@ -42,8 +42,8 @@ func (c *CLab) RemoveSSHConfig(topoPaths *types.TopoPaths) error { return nil } -// AddSSHConfig adds the lab specific ssh config file. -func (c *CLab) AddSSHConfig() error { +// addSSHConfig adds the lab specific ssh config file. +func (c *CLab) addSSHConfig() error { sshConfigDir := path.Dir(c.TopoPaths.SSHConfigPath()) if !utils.FileOrDirExists(sshConfigDir) { log.Debugf("ssh config directory %s does not exist, skipping ssh config generation", sshConfigDir) From eaa9f848f77eae82982b73118e981c27770ca5ec Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 16:35:25 +0100 Subject: [PATCH 14/18] comments @hellt --- Makefile | 2 +- clab/clab.go | 2 +- clab/deploy_options.go | 6 +----- clab/file.go | 2 +- cmd/disableTxOffload.go | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index a814e18c3..5162d10f1 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ endif ifndef suite override suite = . endif -robot-test: build-debug +robot-test: build-with-podman-debug CLAB_BIN=$(BINARY) $$PWD/tests/rf-run.sh $(runtime) $$PWD/tests/$(suite) MOCKDIR = ./mocks diff --git a/clab/clab.go b/clab/clab.go index f8ed83ca9..e25a99507 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -180,7 +180,7 @@ func WithTopoPath(path, varsFile string) ClabOption { if err != nil { return err } - if err := c.LoadTopology(file, varsFile); err != nil { + if err := c.LoadTopologyFromFile(file, varsFile); err != nil { return fmt.Errorf("failed to read topology file: %v", err) } diff --git a/clab/deploy_options.go b/clab/deploy_options.go index 11dd4ed22..7bdfd0f21 100644 --- a/clab/deploy_options.go +++ b/clab/deploy_options.go @@ -1,8 +1,6 @@ package clab import ( - "math" - log "github.com/sirupsen/logrus" "github.com/tklauser/numcpus" ) @@ -16,9 +14,7 @@ type DeployOptions struct { } func NewDeployOptions(maxWorkers uint) (*DeployOptions, error) { - d := &DeployOptions{ - maxWorkers: math.MaxUint, - } + d := &DeployOptions{} err := d.initWorkerCount(maxWorkers) return d, err } diff --git a/clab/file.go b/clab/file.go index e27c716ea..c538bbe65 100644 --- a/clab/file.go +++ b/clab/file.go @@ -29,7 +29,7 @@ const ( // LoadTopology parses the topology file into c.Conf structure // as well as populates the TopoFile structure with the topology file related information. -func (c *CLab) LoadTopology(topo, varsFile string) error { +func (c *CLab) LoadTopologyFromFile(topo, varsFile string) error { var err error c.TopoPaths, err = types.NewTopoPaths(topo) diff --git a/cmd/disableTxOffload.go b/cmd/disableTxOffload.go index f7e6f1d3b..a3b1b2f17 100644 --- a/cmd/disableTxOffload.go +++ b/cmd/disableTxOffload.go @@ -41,7 +41,7 @@ var disableTxOffloadCmd = &cobra.Command{ return err } - node, err := c.GetNode("cntName") + node, err := c.GetNode(cntName) if err != nil { return err } From 4ac07676777da80a05870cbe5861d56ffd5c263f Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 14 Mar 2024 16:37:28 +0100 Subject: [PATCH 15/18] please deepsource --- links/link_veth_test.go | 2 +- types/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/links/link_veth_test.go b/links/link_veth_test.go index 7db684fc9..80726cfaf 100644 --- a/links/link_veth_test.go +++ b/links/link_veth_test.go @@ -235,7 +235,7 @@ func (f *fakeNode) GetEndpoints() []Endpoint { return f.Endpoints } -func (*fakeNode) ExecFunction(ctx context.Context, _ func(ns.NetNS) error) error { +func (*fakeNode) ExecFunction(_ context.Context, _ func(ns.NetNS) error) error { panic("not implemented") } diff --git a/types/types.go b/types/types.go index 70c6176c0..2eec49269 100644 --- a/types/types.go +++ b/types/types.go @@ -262,7 +262,7 @@ type K8sKindExtras struct { Deploy *K8sKindDeployExtras `yaml:"deploy,omitempty"` } -// KindDeployOptions represents the options used for the kind cluster creation. +// K8sKindDeployExtras represents the options used for the kind cluster creation. // It is aligned with the `kind create cluster` command options, but exposes // only the ones that are relevant for containerlab. type K8sKindDeployExtras struct { From 2bd2c6dcc482d356366bbee2d0d887b32d86115c Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 15 Mar 2024 09:59:18 +0200 Subject: [PATCH 16/18] added comments and getters --- clab/deploy_options.go | 52 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/clab/deploy_options.go b/clab/deploy_options.go index 7bdfd0f21..8e166be07 100644 --- a/clab/deploy_options.go +++ b/clab/deploy_options.go @@ -5,55 +5,87 @@ import ( "github.com/tklauser/numcpus" ) +// DeployOptions represents the options for deploying a lab. type DeployOptions struct { - reconfigure bool - skipPostDeploy bool - graph bool - maxWorkers uint - exportTemplate string + reconfigure bool // reconfigure indicates whether to reconfigure the lab. + skipPostDeploy bool // skipPostDeploy indicates whether to skip post-deployment steps. + graph bool // graph indicates whether to generate a graph of the lab. + maxWorkers uint // maxWorkers is the maximum number of workers for node creation. + exportTemplate string // exportTemplate is the path to the export template. } +// NewDeployOptions creates a new DeployOptions instance with the specified maxWorkers value. func NewDeployOptions(maxWorkers uint) (*DeployOptions, error) { d := &DeployOptions{} err := d.initWorkerCount(maxWorkers) return d, err } +// SetReconfigure sets the reconfigure option and returns the updated DeployOptions instance. func (d *DeployOptions) SetReconfigure(b bool) *DeployOptions { d.reconfigure = b return d } +// Reconfigure returns the reconfigure option value. +func (d *DeployOptions) Reconfigure() bool { + return d.reconfigure +} + +// SetSkipPostDeploy sets the skipPostDeploy option and returns the updated DeployOptions instance. func (d *DeployOptions) SetSkipPostDeploy(b bool) *DeployOptions { d.skipPostDeploy = b return d } +// SkipPostDeploy returns the skipPostDeploy option value. +func (d *DeployOptions) SkipPostDeploy() bool { + return d.skipPostDeploy +} + +// SetGraph sets the graph option and returns the updated DeployOptions instance. func (d *DeployOptions) SetGraph(b bool) *DeployOptions { d.graph = b return d } +// Graph returns the graph option value. +func (d *DeployOptions) Graph() bool { + return d.graph +} + +// SetMaxWorkers sets the maxWorkers option and returns the updated DeployOptions instance. func (d *DeployOptions) SetMaxWorkers(i uint) *DeployOptions { d.maxWorkers = i return d } +// MaxWorkers returns the maxWorkers option value. +func (d *DeployOptions) MaxWorkers() uint { + return d.maxWorkers +} + +// SetExportTemplate sets the exportTemplate option and returns the updated DeployOptions instance. func (d *DeployOptions) SetExportTemplate(templatePath string) *DeployOptions { d.exportTemplate = templatePath return d } -// countWorkers calculates the number workers used for the creation of nodes. -// If a user provided --max-workers this takes precedence. -// If maxWorkers is not set then the workers are limited by the number of available CPUs when -// number of nodes exceeds the number of available CPUs. +// ExportTemplate returns the exportTemplate option value. +func (d *DeployOptions) ExportTemplate() string { + return d.exportTemplate +} + +// initWorkerCount calculates the number of workers used for node creation. +// If maxWorkers is provided, it takes precedence. +// If maxWorkers is not set, the number of workers is limited by the number of available CPUs +// when the number of nodes exceeds the number of available CPUs. func (d *DeployOptions) initWorkerCount(maxWorkers uint) error { // init number of Workers to the number of nodes nodeWorkers := uint(0) switch { - // if maxworkers is provided, use that value + // if maxWorkers is provided, use that value case maxWorkers > 0: nodeWorkers = maxWorkers From bca1525cdb6dff86d67787c8f37b946b11a98d9d Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 15 Mar 2024 09:59:34 +0200 Subject: [PATCH 17/18] run `make format` --- clab/clab.go | 17 ++++++++--------- clab/hostsfile.go | 1 - cmd/disableTxOffload.go | 1 - cmd/exec.go | 1 - nodes/default_node.go | 4 +--- nodes/fortinet_fortigate/fortigate.go | 2 +- utils/ethtool.go | 2 +- 7 files changed, 11 insertions(+), 17 deletions(-) diff --git a/clab/clab.go b/clab/clab.go index e25a99507..a1485253e 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -71,7 +71,7 @@ func WithTimeout(dur time.Duration) ClabOption { } // WithLabName sets the name of the lab -// to the provided string +// to the provided string. func WithLabName(n string) ClabOption { return func(c *CLab) error { c.Config.Name = n @@ -80,7 +80,7 @@ func WithLabName(n string) ClabOption { } // WithManagementNetworkName sets the name of the -// management network that is to be used +// management network that is to be used. func WithManagementNetworkName(n string) ClabOption { return func(c *CLab) error { c.Config.Mgmt.Network = n @@ -89,7 +89,7 @@ func WithManagementNetworkName(n string) ClabOption { } // WithManagementIpv4Subnet defined the IPv4 subnet -// that will be used for the mgmt network +// that will be used for the mgmt network. func WithManagementIpv4Subnet(s string) ClabOption { return func(c *CLab) error { c.Config.Mgmt.IPv4Subnet = s @@ -98,7 +98,7 @@ func WithManagementIpv4Subnet(s string) ClabOption { } // WithManagementIpv6Subnet defined the IPv6 subnet -// that will be used for the mgmt network +// that will be used for the mgmt network. func WithManagementIpv6Subnet(s string) ClabOption { return func(c *CLab) error { c.Config.Mgmt.IPv6Subnet = s @@ -393,7 +393,7 @@ func (c *CLab) globalRuntime() runtime.ContainerRuntime { return c.Runtimes[c.globalRuntimeName] } -// GetNode retrieve a node from the clab instance +// GetNode retrieve a node from the clab instance. func (c *CLab) GetNode(name string) (nodes.Node, error) { if node, exists := c.Nodes[name]; exists { return node, nil @@ -956,7 +956,7 @@ func (c *CLab) extractDNSServers(filesys fs.FS) error { return nil } -// Deploy the given topology +// Deploy the given topology. func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.GenericContainer, error) { var err error @@ -1165,7 +1165,7 @@ func (c *CLab) certificateAuthoritySetup() error { return c.LoadOrGenerateCA(caCertInput) } -// Destroy the given topology +// Destroy the given topology. func (c *CLab) Destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) error { containers, err := c.ListNodesContainersIgnoreNotFound(ctx) if err != nil { @@ -1231,9 +1231,8 @@ func (c *CLab) Destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) e return nil } -// Exec execute commands on running topology nodes +// Exec execute commands on running topology nodes. func (c *CLab) Exec(ctx context.Context, cmds []string, options *ExecOptions) (*exec.ExecCollection, error) { - err := links.SetMgmtNetUnderlayingBridge(c.Config.Mgmt.Bridge) if err != nil { return nil, err diff --git a/clab/hostsfile.go b/clab/hostsfile.go index 695bf2ae3..156f36a7f 100644 --- a/clab/hostsfile.go +++ b/clab/hostsfile.go @@ -88,7 +88,6 @@ func generateHostsEntries(containers []runtime.GenericContainer, labname string) } func (c *CLab) DeleteEntriesFromHostsFile() error { - if c.Config.Name == "" { return errors.New("missing containerlab name") } diff --git a/cmd/disableTxOffload.go b/cmd/disableTxOffload.go index a3b1b2f17..4613c848a 100644 --- a/cmd/disableTxOffload.go +++ b/cmd/disableTxOffload.go @@ -22,7 +22,6 @@ var disableTxOffloadCmd = &cobra.Command{ Short: "disables tx checksum offload on eth0 interface of a container", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.Background() opts := []clab.ClabOption{ diff --git a/cmd/exec.go b/cmd/exec.go index 256a387a9..62098dece 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -32,7 +32,6 @@ var execCmd = &cobra.Command{ } func execFn(_ *cobra.Command, _ []string) error { - ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/nodes/default_node.go b/nodes/default_node.go index e292372d9..1a30e32e8 100644 --- a/nodes/default_node.go +++ b/nodes/default_node.go @@ -153,7 +153,7 @@ func (d *DefaultNode) Deploy(ctx context.Context, _ *DeployParams) error { return nil } -// getNsPath retrieve the nodes nspath +// getNsPath retrieve the nodes nspath. func (d *DefaultNode) getNsPath(ctx context.Context) (string, error) { var err error nsp := "" @@ -177,7 +177,6 @@ func (d *DefaultNode) getNsPath(ctx context.Context) (string, error) { } func (d *DefaultNode) Delete(ctx context.Context) error { - for _, e := range d.Endpoints { err := e.GetLink().Remove(ctx) if err != nil { @@ -513,7 +512,6 @@ func (d *DefaultNode) AddLinkToContainer(ctx context.Context, link netlink.Link, // ExecFunction executes the given function in the nodes network namespace. func (d *DefaultNode) ExecFunction(ctx context.Context, f func(ns.NetNS) error) error { - // retrieve nodes nspath nspath, err := d.getNsPath(ctx) if err != nil { diff --git a/nodes/fortinet_fortigate/fortigate.go b/nodes/fortinet_fortigate/fortigate.go index 9cc514ec2..92f149e10 100644 --- a/nodes/fortinet_fortigate/fortigate.go +++ b/nodes/fortinet_fortigate/fortigate.go @@ -21,7 +21,7 @@ var ( const ( // scrapligo doesn't have a driver for fortigate, can be copied from scrapli community - // scrapliPlatformName = "fortinet_fortigate" + // scrapliPlatformName = "fortinet_fortigate". configDirName = "config" startupCfgFName = "startup-config.cfg" ) diff --git a/utils/ethtool.go b/utils/ethtool.go index 8d8742339..c8141cf21 100644 --- a/utils/ethtool.go +++ b/utils/ethtool.go @@ -40,7 +40,7 @@ func ioctlEthtool(fd int, argp uintptr) error { return nil } -// NSEthtoolTXOff EthtoolTXOff wrapper that can be handed straight to Node.ExecFunc() +// NSEthtoolTXOff EthtoolTXOff wrapper that can be handed straight to Node.ExecFunc(). func NSEthtoolTXOff(cntName, ifaceName string) func(ns.NetNS) error { return func(ns.NetNS) error { // disabling offload on given interface From d6425592ad19849ddbf91d79508c8c8534f69ed0 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 15 Mar 2024 10:16:36 +0200 Subject: [PATCH 18/18] augment func comment --- clab/file.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clab/file.go b/clab/file.go index c538bbe65..784550694 100644 --- a/clab/file.go +++ b/clab/file.go @@ -27,7 +27,8 @@ const ( varFileSuffix = "_vars" ) -// LoadTopology parses the topology file into c.Conf structure +// LoadTopologyFromFile loads a topology by the topo file path +// parses the topology file into c.Conf structure // as well as populates the TopoFile structure with the topology file related information. func (c *CLab) LoadTopologyFromFile(topo, varsFile string) error { var err error