Skip to content

Commit

Permalink
Merge branch 'master' into ceos-srl-usecase
Browse files Browse the repository at this point in the history
  • Loading branch information
hellt committed Jan 14, 2021
2 parents d17e14c + c77177d commit 18f9496
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 62 deletions.
59 changes: 58 additions & 1 deletion clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package clab

import (
"context"
"strconv"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -104,7 +105,7 @@ func (c *CLab) CreateNode(ctx context.Context, node *Node, certs *certificates)
}

// ExecPostDeployTasks executes tasks that some nodes might require to boot properly after start
func (c *CLab) ExecPostDeployTasks(ctx context.Context, node *Node) error {
func (c *CLab) ExecPostDeployTasks(ctx context.Context, node *Node, lworkers uint) error {
switch node.Kind {
case "ceos":
log.Debugf("Running postdeploy actions for Arista cEOS '%s' node", node.ShortName)
Expand All @@ -120,10 +121,40 @@ func (c *CLab) ExecPostDeployTasks(ctx context.Context, node *Node) error {
if err != nil {
return err
}
// remove the netns symlink created during original start
// we will re-symlink it later
if err := deleteNetnsSymlink(node.LongName); err != nil {
return err
}
err = c.DockerClient.ContainerStart(ctx, node.ContainerID, types.ContainerStartOptions{})
if err != nil {
return err
}
// since container has been restarted, we need to get its new NSPath and link netns
cont, err := c.DockerClient.ContainerInspect(ctx, node.ContainerID)
if err != nil {
return err
}
log.Debugf("node %s new pid %v", node.LongName, cont.State.Pid)
node.NSPath = "/proc/" + strconv.Itoa(cont.State.Pid) + "/ns/net"
err = linkContainerNS(node.NSPath, node.LongName)
if err != nil {
return err
}
// now its time to create the links which has one end in ceos node
wg := new(sync.WaitGroup)
wg.Add(int(lworkers))
linksChan := make(chan *Link)
c.CreateLinks(ctx, lworkers, linksChan, wg)
for _, link := range c.Links {
if link.A.Node.Kind == "ceos" || link.B.Node.Kind == "ceos" {
linksChan <- link
}
}
// close channel to terminate the workers
close(linksChan)
// wait for all workers to finish
wg.Wait()
case "crpd":
// exec `service ssh restart` to start ssh service and take into account mounted sshd_config
execConfig := types.ExecConfig{Tty: false, AttachStdout: false, AttachStderr: false, Cmd: strings.Fields("service ssh restart")}
Expand Down Expand Up @@ -156,3 +187,29 @@ func (c *CLab) ExecPostDeployTasks(ctx context.Context, node *Node) error {
}
return nil
}

// CreateLinks creates links that are passed to it over linksChan using the number of workers
func (c *CLab) CreateLinks(ctx context.Context, workers uint, linksChan chan *Link, wg *sync.WaitGroup) {
log.Debug("creating links...")
// wire the links between the nodes based on cabling plan
for i := uint(0); i < workers; i++ {
go func(i uint) {
defer wg.Done()
for {
select {
case link := <-linksChan:
if link == nil {
log.Debugf("Worker %d terminating...", i)
return
}
log.Debugf("Worker %d received link: %+v", i, link)
if err := c.CreateVirtualWiring(link); err != nil {
log.Error(err)
}
case <-ctx.Done():
return
}
}
}(i)
}
}
3 changes: 3 additions & 0 deletions clab/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ func (c *CLab) CreateContainer(ctx context.Context, node *Node) (err error) {
if node.Group != "" {
labels["group"] = node.Group
}
if node.LabDir != "" {
labels["clab-node-dir"] = node.LabDir
}

cont, err := c.DockerClient.ContainerCreate(nctx,
&container.Config{
Expand Down
37 changes: 12 additions & 25 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ var deployCmd = &cobra.Command{
return err
}
if reconfigure {
log.Infof("Removing %s directory...", c.Dir.Lab)
err = os.RemoveAll(c.Dir.Lab)
if err != nil {
return err
}
destroyLab(ctx, c)
log.Infof("Removing %s directory...", c.Dir.Lab)
if err := os.RemoveAll(c.Dir.Lab); err != nil {
return err
}

}
if err = c.VerifyBridgesExist(); err != nil {
Expand Down Expand Up @@ -174,29 +176,14 @@ var deployCmd = &cobra.Command{
wg = new(sync.WaitGroup)
wg.Add(int(linksMaxWorkers))
linksChan := make(chan *clab.Link)
log.Debug("creating links...")
// wire the links between the nodes based on cabling plan
for i := uint(0); i < linksMaxWorkers; i++ {
go func(i uint) {
defer wg.Done()
for {
select {
case link := <-linksChan:
if link == nil {
log.Debugf("Worker %d terminating...", i)
return
}
log.Debugf("Worker %d received link: %+v", i, link)
if err := c.CreateVirtualWiring(link); err != nil {
log.Error(err)
}
case <-ctx.Done():
return
}
}
}(i)
}
c.CreateLinks(ctx, linksMaxWorkers, linksChan, wg)
for _, link := range c.Links {
// skip the links of ceos kind is on one end
// ceos containers need to be restarted, thus their data links
// will get recreated after post-deploy stage
if link.A.Node.Kind == "ceos" || link.B.Node.Kind == "ceos" {
continue
}
linksChan <- link
}
// close channel to terminate the workers
Expand Down Expand Up @@ -230,7 +217,7 @@ var deployCmd = &cobra.Command{
for _, node := range c.Nodes {
go func(node *clab.Node) {
defer wg.Done()
err := c.ExecPostDeployTasks(ctx, node)
err := c.ExecPostDeployTasks(ctx, node, linksMaxWorkers)
if err != nil {
log.Errorf("failed to run postdeploy task for node %s: %v", node.ShortName, err)
}
Expand Down
93 changes: 58 additions & 35 deletions cmd/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@ package cmd
import (
"context"
"fmt"
"io/ioutil"
"strings"
"sync"

"github.com/docker/docker/api/types"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/srl-wim/container-lab/clab"
)

var saveCommand = map[string][]string{
"srl": {"sr_cli", "-d", "tools", "system", "configuration", "generate-checkpoint"},
"ceos": {"Cli", "-p", "15", "-c", "copy running flash:conf-saved.conf"},
"crpd": {"cli", "show", "conf"},
}

// saveCmd represents the save command
var saveCmd = &cobra.Command{
Use: "save",
Short: "save containers configuration",
Long: `save performs a configuration save. The exact command that is used to save the config depends on the node kind.
Refer to the https://containerlab.srlinux.dev/cmd/save/ documentation to see the exact command used per node's kind`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
if name == "" && topo == "" {
fmt.Println("provide either lab name (--name) or topology file path (--topo)")
return
return fmt.Errorf("provide topology file path with --topo flag")
}
opts := []clab.ClabOption{
clab.WithDebug(debug),
Expand All @@ -28,48 +36,63 @@ Refer to the https://containerlab.srlinux.dev/cmd/save/ documentation to see the
clab.WithEnvDockerClient(),
}
c := clab.NewContainerLab(opts...)
if name == "" {
name = c.Config.Name
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

containers, err := c.ListContainers(ctx, []string{"containerlab=lab-" + name, "kind=srl"})
containers, err := c.ListContainers(ctx, []string{"containerlab=lab-" + c.Config.Name})
if err != nil {
log.Fatalf("could not list containers: %v", err)
return fmt.Errorf("could not list containers: %v", err)
}
if len(containers) == 0 {
log.Println("no containers found")
return
return fmt.Errorf("no containers found")
}
var saveCmd []string

var wg sync.WaitGroup
wg.Add(len(containers))
for _, cont := range containers {
if cont.State != "running" {
continue
}
log.Debugf("container: %+v", cont)
if k, ok := cont.Labels["kind"]; ok {
switch k {
case "srl":
saveCmd = []string{"sr_cli", "-d", "tools", "system", "configuration", "generate-checkpoint"}
case "ceos":
//TODO
default:
continue
go func(cont types.Container) {
defer wg.Done()
kind := cont.Labels["kind"]
// skip saving process for linux containers
if kind == "linux" {
return
}
stdout, stderr, err := c.Exec(ctx, cont.ID, saveCommand[kind])
if err != nil {
log.Errorf("%s: failed to execute cmd: %v", cont.Names, err)

}
if len(stderr) > 0 {
log.Infof("%s errors: %s", strings.TrimLeft(cont.Names[0], "/"), string(stderr))
}
switch {
// for srl kinds print the full stdout
case kind == "srl":
if len(stdout) > 0 {
confPath := cont.Labels["clab-node-dir"] + "/config/checkpoint/checkpoint-0.json"
log.Infof("saved SR Linux configuration from %s node to %s\noutput:\n%s", strings.TrimLeft(cont.Names[0], "/"), confPath, string(stdout))
}

case kind == "crpd":
// path by which to save a config
confPath := cont.Labels["clab-node-dir"] + "/config/conf-saved.conf"
err := ioutil.WriteFile(confPath, stdout, 0777)
if err != nil {
log.Errorf("failed to write config by %s path from %s container: %v", confPath, strings.TrimLeft(cont.Names[0], "/"), err)
}
log.Infof("saved cRPD configuration from %s node to %s", strings.TrimLeft(cont.Names[0], "/"), confPath)

case kind == "ceos":
// path by which a config was saved
confPath := cont.Labels["clab-node-dir"] + "/flash/conf-saved.conf"
log.Infof("saved cEOS configuration from %s node to %s", strings.TrimLeft(cont.Names[0], "/"), confPath)
}
}
stdout, stderr, err := c.Exec(ctx, cont.ID, saveCmd)
if err != nil {
log.Errorf("%s: failed to execute cmd: %v", cont.Names, err)
continue
}
if len(stdout) > 0 {
log.Infof("%s output: %s", strings.TrimLeft(cont.Names[0], "/"), string(stdout))
}
if len(stderr) > 0 {
log.Infof("%s errors: %s", strings.TrimLeft(cont.Names[0], "/"), string(stderr))
}
}(cont)
}
wg.Wait()

return nil
},
}

Expand Down
2 changes: 1 addition & 1 deletion docs/manual/kinds/crpd.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ topology:
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 `/config/juniper.conf` name and mount that dir to the container. This will result in this config to act as a startup config for the node.

#### Saving configuration
With [`containerlab save`](../../cmd/save.md) command it's possible to save running cEOS configuration into a file. The configuration will be saved by `conf-saved.conf` path in the relevant node directory.
With [`containerlab save`](../../cmd/save.md) command it's possible to save running cRPD configuration into a file. The configuration will be saved by `conf-saved.conf` path in the relevant node directory.

### License
cRPD containers require a license file to have some features to be activated. With a [`license`](../nodes.md#license) directive it's possible to provide a path to a license file that will be copied over to the nodes configuration directory by the `/config/license.conf` path.
Expand Down

0 comments on commit 18f9496

Please sign in to comment.