Skip to content

Commit

Permalink
Merge branch 'master' into populate-etc-hosts-inside-containers
Browse files Browse the repository at this point in the history
  • Loading branch information
hellt committed Aug 29, 2021
2 parents 28355b3 + 75230b2 commit def3659
Show file tree
Hide file tree
Showing 38 changed files with 601 additions and 189 deletions.
47 changes: 32 additions & 15 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ jobs:
- "README.md"
- '.github/workflows/cicd.yml'
build-containerlab:
runs-on: ubuntu-20.04
needs: file-changes
if: needs.file-changes.outputs.code == 'true' || startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GOVER }}
- name: Build containerlab
run: go build
# store clab binary as artifact
- uses: actions/upload-artifact@v2
with:
name: containerlab
path: containerlab

unit-test:
runs-on: ubuntu-20.04
needs: file-changes
Expand All @@ -64,17 +81,17 @@ jobs:
runtime: ["docker", "containerd"]
needs:
- unit-test
- build-containerlab
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
- uses: actions/download-artifact@v2
with:
go-version: ${{ env.GOVER }}
- name: Build containerlab
run: go build && sudo mv ./containerlab /usr/bin/containerlab
name: containerlab
- name: Move containerlab to usr/bin
run: sudo mv ./containerlab /usr/bin/containerlab && sudo chmod a+x /usr/bin/containerlab
- uses: actions/setup-python@v2
with:
python-version: "3.8"
Expand All @@ -101,17 +118,17 @@ jobs:
# - "containerd"
needs:
- unit-test
- build-containerlab
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
- uses: actions/download-artifact@v2
with:
go-version: ${{ env.GOVER }}
- name: Build containerlab
run: go build && sudo mv ./containerlab /usr/bin/containerlab
name: containerlab
- name: Move containerlab to usr/bin
run: sudo mv ./containerlab /usr/bin/containerlab && sudo chmod a+x /usr/bin/containerlab
- uses: actions/setup-python@v2
with:
python-version: "3.8"
Expand Down Expand Up @@ -146,17 +163,17 @@ jobs:
# - "containerd"
needs:
- unit-test
- build-containerlab
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
- uses: actions/download-artifact@v2
with:
go-version: ${{ env.GOVER }}
- name: Build containerlab
run: go build && sudo mv ./containerlab /usr/bin/containerlab
name: containerlab
- name: Move containerlab to usr/bin
run: sudo mv ./containerlab /usr/bin/containerlab && sudo chmod a+x /usr/bin/containerlab
- uses: actions/setup-python@v2
with:
python-version: "3.8"
Expand Down
93 changes: 72 additions & 21 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,40 @@ func (c *CLab) GlobalRuntime() runtime.ContainerRuntime {
return c.Runtimes[c.globalRuntime]
}

func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, serialNodes map[string]struct{}) {

wg := new(sync.WaitGroup)
// CreateNodes will schedule nodes creation
// returns waitgroups for nodes with static and dynamic IPs,
// since static nodes are scheduled first
func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint,
serialNodes map[string]struct{}) (*sync.WaitGroup, *sync.WaitGroup) {
staticIPNodes := make(map[string]nodes.Node)
dynIPNodes := make(map[string]nodes.Node)

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

func (c *CLab) createNodes(ctx context.Context, maxWorkers int,
serialNodes map[string]struct{}, scheduledNodes map[string]nodes.Node) *sync.WaitGroup {
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 All @@ -171,6 +197,14 @@ func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, serialNodes map
return
}
log.Debugf("Worker %d received node: %+v", i, node.Config())

// Apply any startup delay
delay := node.Config().StartupDelay
if delay > 0 {
log.Infof("node %q is being delayed for %d seconds", node.Config().ShortName, delay)
time.Sleep(time.Duration(delay) * time.Second)
}

// PreDeploy
err := node.PreDeploy(c.Config.Name, c.Dir.LabCA, c.Dir.LabCARoot)
if err != nil {
Expand All @@ -183,17 +217,29 @@ func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, serialNodes map
log.Errorf("failed deploy phase for node %q: %v", node.Config().ShortName, err)
continue
}

// set deployment status of a node to created to indicate that it finished creating
// this status is checked during link creation to only schedule link creation if both nodes are ready
c.m.Lock()
node.Config().DeploymentStatus = "created"
c.m.Unlock()
case <-ctx.Done():
return
}
}
}

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 @@ -204,8 +250,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 All @@ -216,8 +265,7 @@ func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, serialNodes map
close(concurrentChan)
close(serialChan)

// wait for all workers to finish
wg.Wait()
return wg
}

// CreateLinks creates links using the specified number of workers
Expand Down Expand Up @@ -251,23 +299,26 @@ func (c *CLab) CreateLinks(ctx context.Context, workers uint, postdeploy bool) {
}(i)
}

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" {
// create a copy of links map to loop over
// so that we can wait till all the nodes are ready before scheduling a link
linksCopy := map[int]*types.Link{}
for k, v := range c.Links {
linksCopy[k] = v
}
for {
if len(linksCopy) == 0 {
break
}
for k, link := range linksCopy {
c.m.Lock()
if link.A.Node.DeploymentStatus == "created" && link.B.Node.DeploymentStatus == "created" {
linksChan <- link
delete(linksCopy, k)
}
c.m.Unlock()
}
}

// close channel to terminate the workers
close(linksChan)
// wait for all workers to finish
Expand Down
16 changes: 11 additions & 5 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,6 +241,9 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx
if err != nil {
return nil, err
}

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

// initialize license field
nodeCfg.License, err = c.Config.Topology.GetNodeLicense(nodeCfg.ShortName)
if err != nil {
Expand Down Expand Up @@ -304,16 +308,18 @@ func (c *CLab) NewEndpoint(e string) *types.Endpoint {
// for which we create an special Node with kind "host"
case "host":
endpoint.Node = &types.NodeConfig{
Kind: "host",
ShortName: "host",
NSPath: hostNSPath,
Kind: "host",
ShortName: "host",
NSPath: hostNSPath,
DeploymentStatus: "created",
}
// mgmt-net is a special reference to a bridge of the docker network
// that is used as the management network
case "mgmt-net":
endpoint.Node = &types.NodeConfig{
Kind: "bridge",
ShortName: "mgmt-net",
Kind: "bridge",
ShortName: "mgmt-net",
DeploymentStatus: "created",
}
default:
c.m.Lock()
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
2 changes: 1 addition & 1 deletion cmd/config_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var configTemplateCmd = &cobra.Command{
}

for _, n := range configFilter {
allConfig[n].Print(true, false)
allConfig[n].Print(false, true)
}

return nil
Expand Down
Loading

0 comments on commit def3659

Please sign in to comment.