Skip to content

Commit

Permalink
Merge branch 'master' into jvb-add-startup-delay
Browse files Browse the repository at this point in the history
  • Loading branch information
hellt committed Aug 24, 2021
2 parents eb09c50 + 9c63bd6 commit c415e69
Show file tree
Hide file tree
Showing 17 changed files with 340 additions and 136 deletions.
51 changes: 32 additions & 19 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,31 @@ func (c *CLab) GlobalRuntime() runtime.ContainerRuntime {
}

func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, serialNodes map[string]struct{}) {
staticIPNodes := make(map[string]nodes.Node)
dynIPNodes := make(map[string]nodes.Node)

wg := new(sync.WaitGroup)
for name, n := range c.Nodes {
if n.Config().MgmtIPv4Address != "" || n.Config().MgmtIPv6Address != "" {
staticIPNodes[name] = n
continue
}
dynIPNodes[name] = n
}
if len(staticIPNodes) > 0 {
log.Debug("scheduling nodes with static IPs...")
c.createNodes(ctx, int(maxWorkers), serialNodes, staticIPNodes)
}
if len(dynIPNodes) > 0 {
log.Debug("scheduling nodes with dynamic IPs...")
c.createNodes(ctx, int(maxWorkers), serialNodes, dynIPNodes)
}
}

func (c *CLab) createNodes(ctx context.Context, maxWorkers int, serialNodes map[string]struct{}, scheduledNodes map[string]nodes.Node) {
concurrentChan := make(chan nodes.Node)
serialChan := make(chan nodes.Node)

workerFunc := func(i uint, input chan nodes.Node, wg *sync.WaitGroup) {
workerFunc := func(i int, input chan nodes.Node, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
Expand Down Expand Up @@ -198,11 +216,17 @@ func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, serialNodes map
}
}

numScheduledNodes := len(scheduledNodes)
if numScheduledNodes < maxWorkers {
maxWorkers = numScheduledNodes
}
wg := new(sync.WaitGroup)

// start concurrent workers
wg.Add(int(maxWorkers))
// it's safe to not check if all nodes are serial because in that case
// maxWorkers will be 0
for i := uint(0); i < maxWorkers; i++ {
for i := 0; i < maxWorkers; i++ {
go workerFunc(i, concurrentChan, wg)
}

Expand All @@ -213,8 +237,11 @@ func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, serialNodes map
}

// send nodes to workers
for _, n := range c.Nodes {
for _, n := range scheduledNodes {
if _, ok := serialNodes[n.Config().LongName]; ok {
// delete the entry to avoid starting a serial worker in the
// case of dynamic IP nodes scheduling
delete(serialNodes, n.Config().LongName)
serialChan <- n
continue
}
Expand Down Expand Up @@ -261,21 +288,7 @@ func (c *CLab) CreateLinks(ctx context.Context, workers uint, postdeploy bool) {
}

for _, link := range c.Links {
// skip the links of ceos kind
// ceos containers need to be restarted in the postdeploy stage, thus their data links
// will get recreated after post-deploy stage
if !postdeploy {
if link.A.Node.Kind == "ceos" || link.B.Node.Kind == "ceos" {
continue
}
linksChan <- link
} else {
// postdeploy stage
// create ceos links that were skipped during original links creation
if link.A.Node.Kind == "ceos" || link.B.Node.Kind == "ceos" {
linksChan <- link
}
}
linksChan <- link
}
// close channel to terminate the workers
close(linksChan)
Expand Down
7 changes: 3 additions & 4 deletions clab/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx
Runtime: c.Config.Topology.GetNodeRuntime(nodeName),
CPU: c.Config.Topology.GetNodeCPU(nodeName),
RAM: c.Config.Topology.GetNodeRAM(nodeName),
StartupDelay: c.Config.Topology.GetNodeStartupDelay(nodeName),
}

log.Debugf("node config: %+v", nodeCfg)
Expand All @@ -240,10 +241,8 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx
if err != nil {
return nil, err
}
// Get optional startup delay
// JvB note this code could be refactored to lookup the node once, instead of
// every GetNodeXXX method
nodeCfg.StartupDelay = c.Config.Topology.GetNodeStartupDelay(nodeCfg.ShortName)

nodeCfg.EnforceStartupConfig = c.Config.Topology.GetNodeEnforceStartupConfig(nodeCfg.ShortName)

// initialize license field
nodeCfg.License, err = c.Config.Topology.GetNodeLicense(nodeCfg.ShortName)
Expand Down
26 changes: 23 additions & 3 deletions clab/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ func TestTypeInit(t *testing.T) {

func TestEnvInit(t *testing.T) {
tests := map[string]struct {
got string
node string
want map[string]string
got string
node string
envvar map[string]string
want map[string]string
}{
"env_defined_at_node_level": {
got: "test_data/topo1.yml",
Expand Down Expand Up @@ -234,10 +235,29 @@ func TestEnvInit(t *testing.T) {
"env5": "node",
},
},
"expand_env_variables": {
got: "test_data/topo9.yml",
node: "node1",
envvar: map[string]string{
"CONTAINERLAB_TEST_ENV5": "node",
},
want: map[string]string{
"env1": "node",
"env2": "kind",
"env3": "global",
"env4": "kind",
"env5": "node",
},
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
for k, v := range tc.envvar {
os.Setenv(k, v)
defer os.Unsetenv(k)
}

opts := []ClabOption{
WithTopoFile(tc.got),
}
Expand Down
4 changes: 4 additions & 0 deletions clab/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package clab
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
Expand All @@ -31,11 +32,14 @@ func (c *CLab) GetTopology(topo string) error {
}
log.Debug(fmt.Sprintf("Topology file contents:\n%s\n", yamlFile))

yamlFile = []byte(os.ExpandEnv(string(yamlFile)))
err = yaml.UnmarshalStrict(yamlFile, c.Config)
if err != nil {
return err
}

c.Config.Topology.ImportEnvs()

topoAbsPath, err := filepath.Abs(topo)
if err != nil {
return err
Expand Down
31 changes: 31 additions & 0 deletions clab/test_data/topo9.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: topo4
topology:
defaults:
license: test_data/default.lic
binds:
- test_data/default.lic:/dst
env:
env1: global
env2: global
env3: global
user: customglobal
kinds:
srl:
license: test_data/kind.lic
binds:
- test_data/kind.lic:/dst
env:
env2: kind
env4: kind
user: customkind
nodes:
node1:
kind: srl
type: ixr6
license: test_data/node1.lic
binds:
- test_data/node1.lic:/dst
env:
env1: node
env5: ${CONTAINERLAB_TEST_ENV5}
user: customnode
5 changes: 0 additions & 5 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ var deployCmd = &cobra.Command{
for _, n := range c.Nodes {
if n.GetRuntime().GetName() == runtime.IgniteRuntime {
serialNodes[n.Config().LongName] = struct{}{}
// decreasing the num of nodeworkers as they are used for concurrent nodes
nodeWorkers = nodeWorkers - 1
}
}

Expand Down Expand Up @@ -181,9 +179,6 @@ var deployCmd = &cobra.Command{
}
}

// run links postdeploy creation (ceos links creation)
c.CreateLinks(ctx, linkWorkers, true)

log.Info("Adding containerlab host entries to /etc/hosts file")
err = clab.AppendHostsFileEntries(containers, c.Config.Name)
if err != nil {
Expand Down
31 changes: 7 additions & 24 deletions docs/manual/kinds/ceos.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,10 @@ topology:
## Features and options
### Node configuration
cEOS nodes have a dedicated [`config`](../conf-artifacts.md#identifying-a-lab-directory) directory that is used to persist the configuration of the node. It is possible to launch nodes of `ceos` kind with a basic config or to provide a custom config file that will be
used as a startup config instead.
cEOS nodes have a dedicated [`config`](../conf-artifacts.md#identifying-a-lab-directory) directory that is used to persist the configuration of the node. It is possible to launch nodes of `ceos` kind with a basic config or to provide a custom config file that will be used as a startup config instead.

#### Default node configuration
When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/nodes/ceos/ceos.cfg) and copy it to the config directory of the node.
When a node is defined without `startup-config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-labs/containerlab/blob/master/nodes/ceos/ceos.cfg) and copy it to the config directory of the node.

```yaml
# example of a topo file that does not define a custom config
Expand Down Expand Up @@ -138,13 +137,13 @@ topology:
startup-config: myconfig.conf
```

When a config file is passed via `startup-config` parameter, it will override any configuration that may have left upon lab destroy.
When a config file is passed via `startup-config` parameter it will be used during an initial lab deployment. However, a config file that might be in the lab directory of a node takes precedence over the startup-config[^3].

With such topology file containerlab is instructed to take a file `myconfig.conf` from the current working directory, copy it to the lab directory for that specific node under the `/flash/startup-config` name and mount that dir to the container. This will result in this config to act as a startup config for the node.

It is possible to change the default config which every ceos node will start with with the following steps:

1. Save the [default configuration template](https://github.com/srl-labs/containerlab/blob/master/nodes/ceos/ceos.cfg) under some local file name[^2] and add the necessary changes to it
1. Craft a valid startup configuration file[^2].
2. Use this file as a startup-config for ceos kind:
```
name: ceos
Expand All @@ -167,25 +166,8 @@ It is possible to change the default config which every ceos node will start wit
- endpoints: ["ceos1:eth1", "ceos2:eth1"]
```

#### Configuration persistency

It is important to understand how configuration persistency behaves when a single lab is going through rounds of `deploy->destroy` actions.

When the lab with cEOS nodes gets deployed for the first time the configuration file is generated with the IPv4/6 address assigned to `Ma0` management interface. These management interface addresses match the IP addresses that docker has assigned to cEOS containers. This makes it possible to have the cEOS nodes to start up with Management interface already correctly addressed.

When a user later configures the nodes during the lab exercise and saves it with `wr mem` or similar, the changes will be written to `startup-config` file of cEOS.

User then may destroy the lab and the config changes will persist on disk, this is done with `destroy` command. During this operation the containers will be destroyed, but their configuration files will still be kept in the lab directory by the path `clab-$labName`.

If a user then desires to start this lab once again it may lead to a problem. Since docker may assign new IP addresses to the cEOS nodes of the lab, the configuration saved on disk may not match those new docker-assigned addresses, and that will result in an incorrect management interface configuration.

To avoid this, and be able to start the nodes with the previously saved configuration, users may do the following:

1. Address the nodes explicitly via [user defined addresses](../network.md#user-defined-addresses). This will instruct docker to use the addresses as specified by a user in a clab file.
2. Leverage [user defined config](#user-defined-config), if all you need is to have a startup config.

#### Saving configuration
In addition to cli commands such as `write memory` user can take advantage of the [`containerlab save`](../../cmd/save.md) command. It saves running cEOS configuration into a file by `conf-saved.conf` path in the relevant node directory.
In addition to cli commands such as `write memory` user can take advantage of the [`containerlab save`](../../cmd/save.md) command. It saves running cEOS configuration into a startup config file effectively calling the `write` CLI command.

## Container configuration
To start an Arista cEOS node containerlab uses the configuration instructions described in Arista Forums[^1]. The exact parameters are outlined below.
Expand Down Expand Up @@ -246,4 +228,5 @@ As of this writing (22-June, 2021), ceos-lab image requires a cgroups v1 environ
Consult your distribution's documentation for details regarding configuring cgroups v1 in case you see similar startup issues as indicated in [#467](https://github.com/srl-labs/containerlab/issues/467).
[^1]: https://eos.arista.com/ceos-lab-topo/
[^2]: do not remove the template variables from the `Management0` interface, otherwise the nodes will not apply the IP address from docker IPAM service.
[^2]: feel free to omit the IP addressing for Management interface, as it will be configured by containerlab when ceos node boots.
[^3]: if startup config needs to be enforced, either deploy a lab with `--reconfigure` flag, or use [`enforce-startup-config`](../nodes.md#enforce-startup-config) setting.
13 changes: 12 additions & 1 deletion docs/manual/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ Some containerized NOSes require a license to operate or can leverage a license
### startup-config
For some kinds it's possible to pass a path to a config file that a node will use on start instead of a bare config. Check documentation for a specific kind to see if `startup-config` element is supported.

Note, that if a config file exists in the lab directory for a given node, then it will take preference over the startup config passed with this setting. If it is desired to discard the previously saved config and use the startup config instead, use the `enforce-startup-config` setting or deploy a lab with the [`reconfigure`](../cmd/deploy.md#reconfigure) flag.

### enforce-startup-config
By default, containerlab will use the config file that is available in the lab directory for a given node even if the `startup config` parameter points to another file. To make a node to boot with the config set with `startup-config` parameter no matter what, set the `enforce-startup-config` to `true`.

### binds
In order to expose host files to the containerized nodes a user can leverage the bind mount capability.

Expand Down Expand Up @@ -169,8 +174,14 @@ topology:
node1:
env:
ENV1: 1 # ENV1=1 will be set for node1
# env vars expansion is available, for example
# ENV2 variable will be set to the value of the environment variable SOME_ENV
# that is defined for the shell you run containerlab with
ENV2: ${SOME_ENV}
```

You can also specify a magic ENV VAR - `__IMPORT_ENVS: true` - which will import all environment variables defined in your shell to the relevant topology level.

### user
To set a user which will be used to run a containerized process use the `user` configuration option. Can be defined at `node`, `kind` and `global` levels.

Expand Down Expand Up @@ -304,4 +315,4 @@ The default runtime can also be influenced via the `CLAB_RUNTIME` environment va
my-node:
image: alpine:3
runtime: containerd
```
```
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,4 @@ To get a broader view on the containerlab features and components, refer to the
Do not forget to check out the [Lab examples](lab-examples/lab-examples.md) section where we provide complete and ready-to-run topology definition files. This is a great starting point to explore containerlab by doing.

[^1]: For other installation options such as package managers, manual binary downloads or instructions to get containerlab for non-RHEL/Debian distros, refer to the [installation guide](install.md).
[^2]: Containerlab would try to pull the images upon the lab deployment, but if the images are not available publicly or you do not have the private repositories configured, then you need to import the images upfront.
[^2]: Containerlab would try to pull the images upon the lab deployment, but if the images are not available publicly or you do not have the private repositories configured, then you need to import the images upfront.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ require (
github.com/olekukonko/tablewriter v0.0.5-0.20201029120751-42e21c7531a3
github.com/opencontainers/runtime-spec v1.0.3-0.20210303205135-43e4633e40c1
github.com/pkg/errors v0.9.1
github.com/scrapli/scrapligo v0.0.0-20210704164516-6c3b4e74cfad
github.com/scrapli/scrapligo v0.0.0-20210822185345-c949ba367b79
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.0.0
github.com/srl-labs/srlinux-scrapli v0.2.0
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
github.com/weaveworks/ignite v0.9.1-0.20210705155449-2dbcdd663727
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee h1:BnPxIde0gjtTnc9Er7cxvBk8DHLWhEux0SxayC8dP6I=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/carlmontanari/difflibgo v0.0.0-20210718170140-424f52054f94/go.mod h1:+3MuSIeC3qmdSesR12cTLeb47R/Vvo+bHdB6hC5HShk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
Expand Down Expand Up @@ -919,8 +920,9 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/scrapli/scrapligo v0.0.0-20210704164516-6c3b4e74cfad h1:eb+6BSk5gTJ3rsQ1CfZGfMsoxvo7ZYwKlCTszU1CtJs=
github.com/scrapli/scrapligo v0.0.0-20210704164516-6c3b4e74cfad/go.mod h1:+csimZHh80jQXjdDdHmAIKCwiXPZvXQ7ZgKEQWmFpK8=
github.com/scrapli/scrapligo v0.0.0-20210814224131-df0e66d7cd23/go.mod h1:0tHMgiCiTuWOvSceFU7klaYThXvRZNvc7k+fmQrtH54=
github.com/scrapli/scrapligo v0.0.0-20210822185345-c949ba367b79 h1:fFnWvBZu5CLbZ5lKP7HJzOygxDQFWoDC6pVs1Yc44RQ=
github.com/scrapli/scrapligo v0.0.0-20210822185345-c949ba367b79/go.mod h1:0tHMgiCiTuWOvSceFU7klaYThXvRZNvc7k+fmQrtH54=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
Expand Down Expand Up @@ -967,6 +969,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/srl-labs/srlinux-scrapli v0.2.0 h1:uBvD7E326ucH1AKfe0ufo9063MAa+rTeIJSeBBZXy6o=
github.com/srl-labs/srlinux-scrapli v0.2.0/go.mod h1:j4SjAR3WX5OdjTSTaU8IJn4V7Hv6ateazBhI36AyKCk=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
Loading

0 comments on commit c415e69

Please sign in to comment.