Skip to content

Commit

Permalink
added support for config generation for ceos (#173)
Browse files Browse the repository at this point in the history
* adjusted the method comment

* move node dir creation out of srl scope

* removed alpine case

* added a map of def cfg template locations

* fixed unnecessary config rewrite in ceos case #141

* added config generation for ceos

* removed comments

* added config file mount for ceos

* added creation of node dir for nos kinds only

* added mgmt iface to ceos

* mount whole /flash dir to ceos

* save containerJSON struct within the node

* used new err to elimintae race condition

* added ceos config

* removed CJSON from node struct

* added postdeploy actions
  • Loading branch information
hellt committed Dec 1, 2020
1 parent 840ecaa commit db56f6c
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 42 deletions.
23 changes: 23 additions & 0 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"sync"
"time"

"github.com/docker/docker/api/types"
docker "github.com/docker/docker/client"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -99,3 +100,25 @@ func (c *cLab) CreateNode(ctx context.Context, node *Node, certs *certificates)
}
return c.CreateContainer(ctx, node)
}

// ExecPostDeployTasks executes tasks that some nodes might require to boot properly after start
func (c *cLab) ExecPostDeployTasks(ctx context.Context, node *Node) error {
switch node.Kind {
case "ceos":
log.Infof("Running postdeploy actions for '%s' node", node.ShortName)
// regenerate ceos config since it is now known which IP address docker assigned to this container
err := node.generateConfig(node.ResConfig)
if err != nil {
return err
}
log.Infof("Restarting '%s' node", node.ShortName)
// force stopping and start is faster than ContainerRestart
var timeout time.Duration = 1
err = c.DockerClient.ContainerStop(ctx, node.ContainerID, &timeout)
if err != nil {
return err
}
err = c.DockerClient.ContainerStart(ctx, node.ContainerID, types.ContainerStartOptions{})
}
return nil
}
80 changes: 46 additions & 34 deletions clab/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ const (
dockerNetName = "clab"
dockerNetIPv4Addr = "172.20.20.0/24"
dockerNetIPv6Addr = "2001:172:20:20::/80"

defaultConfigTemplate = "/etc/containerlab/templates/srl/srlconfig.tpl"
)

// supported kinds
var kinds = []string{"srl", "ceos", "linux", "alpine", "bridge"}

var defaultConfigTemplates = map[string]string{
"srl": "/etc/containerlab/templates/srl/srlconfig.tpl",
"ceos": "/etc/containerlab/templates/arista/ceos.cfg.tpl",
}

var srlTypes = map[string]string{
"ixr6": "topology-7250IXR6.yml",
"ixr10": "topology-7250IXR10.yml",
Expand Down Expand Up @@ -79,31 +82,37 @@ type Config struct {

// Node is a struct that contains the information of a container element
type Node struct {
ShortName string
LongName string
Fqdn string
LabDir string
Index int
Group string
Kind string
Config string
NodeType string
Position string
License string
Image string
Topology string
EnvConf string
Sysctls map[string]string
User string
Cmd string
Env []string
Binds []string // Bind mounts strings (src:dest:options)
PortBindings nat.PortMap // PortBindings define the bindings between the container ports and host ports
PortSet nat.PortSet // PortSet define the ports that should be exposed on a container

TLSCert string
TLSKey string
TLSAnchor string
ShortName string
LongName string
Fqdn string
LabDir string
Index int
Group string
Kind string
Config string // path to config template file that is used for config generation
ResConfig string // path to config file that is actually mounted to the container and is a result of templation
NodeType string
Position string
License string
Image string
Topology string
EnvConf string
Sysctls map[string]string
User string
Cmd string
Env []string
Binds []string // Bind mounts strings (src:dest:options)
PortBindings nat.PortMap // PortBindings define the bindings between the container ports and host ports
PortSet nat.PortSet // PortSet define the ports that should be exposed on a container
MgmtNet string // name of the docker network this node is connected to with its first interface
MgmtIPv4Address string
MgmtIPv4PrefixLength int
MgmtIPv6Address string
MgmtIPv6PrefixLength int
ContainerID string
TLSCert string
TLSKey string
TLSAnchor string
}

// Link is a struct that contains the information of a link between 2 containers
Expand Down Expand Up @@ -230,7 +239,7 @@ func (c *cLab) configInitialization(nodeCfg *NodeConfig, kind string) string {
if c.Config.Topology.Defaults.Config != "" {
return c.Config.Topology.Defaults.Config
}
return defaultConfigTemplate
return defaultConfigTemplates[kind]
}

func (c *cLab) imageInitialization(nodeCfg *NodeConfig, kind string) string {
Expand Down Expand Up @@ -314,25 +323,24 @@ func (c *cLab) NewNode(nodeName string, nodeCfg NodeConfig, idx int) error {
case "ceos":
// initialize the global parameters with defaults, can be overwritten later
node.Config = c.configInitialization(&nodeCfg, node.Kind)
//node.License = t.SRLLicense
node.Image = c.imageInitialization(&nodeCfg, node.Kind)
//node.NodeType = "ixr6"
node.Position = c.positionInitialization(&nodeCfg, node.Kind)

// initialize specifc container information
node.Cmd = "/sbin/init systemd.setenv=INTFTYPE=eth systemd.setenv=ETBA=1 systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1 systemd.setenv=CEOS=1 systemd.setenv=EOS_PLATFORM=ceoslab systemd.setenv=container=docker"
//node.Cmd = "/sbin/init"
node.Cmd = "/sbin/init systemd.setenv=INTFTYPE=eth systemd.setenv=ETBA=4 systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1 systemd.setenv=CEOS=1 systemd.setenv=EOS_PLATFORM=ceoslab systemd.setenv=container=docker systemd.setenv=MAPETH0=1 systemd.setenv=MGMT_INTF=eth0"

node.Env = []string{
"CEOS=1",
"EOS_PLATFORM=ceoslab",
"container=docker",
"ETBA=1",
"SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1",
"INTFTYPE=eth"}
"INTFTYPE=eth",
"MAPETH0=1",
"MGMT_INTF=eth0"}
node.User = "root"
node.Group = c.groupInitialization(&nodeCfg, node.Kind)
node.NodeType = nodeCfg.Type
node.Config = nodeCfg.Config

node.Sysctls = make(map[string]string)
node.Sysctls["net.ipv4.ip_forward"] = "0"
Expand All @@ -342,6 +350,10 @@ func (c *cLab) NewNode(nodeName string, nodeCfg NodeConfig, idx int) error {
node.Sysctls["net.ipv6.conf.all.autoconf"] = "0"
node.Sysctls["net.ipv6.conf.default.autoconf"] = "0"

// mount config dir
cfgPath := filepath.Join(node.LabDir, "flash")
node.Binds = append(node.Binds, fmt.Sprint(cfgPath, ":/mnt/flash/"))

case "srl":
// initialize the global parameters with defaults, can be overwritten later
node.Config = c.configInitialization(&nodeCfg, node.Kind)
Expand Down
5 changes: 3 additions & 2 deletions clab/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,14 @@ func (c *cLab) CreateContainer(ctx context.Context, node *Node) (err error) {
if err != nil {
return err
}
log.Debugf("Container started: %s", node.LongName)
nctx, cancelFn := context.WithTimeout(ctx, c.timeout)
defer cancelFn()
cJson, err := c.DockerClient.ContainerInspect(nctx, cont.ID)
cJSON, err := c.DockerClient.ContainerInspect(nctx, cont.ID)
if err != nil {
return err
}
return linkContainerNS(cJson.State.Pid, node.LongName)
return linkContainerNS(cJSON.State.Pid, node.LongName)
}

func (c *cLab) PullImageIfRequired(ctx context.Context, imageName string) error {
Expand Down
25 changes: 20 additions & 5 deletions clab/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,22 @@ func CreateDirectory(path string, perm os.FileMode) {
}
}

// CreateNodeDirStructure create the directory structure and files for the clab
// CreateNodeDirStructure create the directory structure and files for the lab nodes
func (c *cLab) CreateNodeDirStructure(node *Node) (err error) {
c.m.RLock()
defer c.m.RUnlock()

// create node directory in the lab directory
if node.Kind != "linux" && node.Kind != "bridge" {
CreateDirectory(node.LabDir, 0777)
}

switch node.Kind {
case "srl":
log.Infof("Create directory structure for SRL container: %s", node.ShortName)
var src string
var dst string

// create node directory in lab
CreateDirectory(node.LabDir, 0777)

// copy license file to node specific directory in lab
src = node.License
dst = path.Join(node.LabDir, "license.key")
Expand Down Expand Up @@ -180,9 +183,20 @@ func (c *cLab) CreateNodeDirStructure(node *Node) (err error) {
}
log.Debugf("CopyFile src %s -> dst %s succeeded\n", src, dst)

case "alpine":
case "linux":
case "ceos":
// generate config directory
CreateDirectory(path.Join(node.LabDir, "flash"), 0777)
cfg := path.Join(node.LabDir, "flash", "startup-config")
node.ResConfig = cfg
if !fileExists(cfg) {
err = node.generateConfig(cfg)
if err != nil {
log.Errorf("node=%s, failed to generate config: %v", node.ShortName, err)
}
} else {
log.Debugf("Config file exists for node %s", node.ShortName)
}
case "bridge":
default:
}
Expand All @@ -192,6 +206,7 @@ func (c *cLab) CreateNodeDirStructure(node *Node) (err error) {

// GenerateConfig generates configuration for the nodes
func (node *Node) generateConfig(dst string) error {
log.Debugf("generating config for node %s from file %s", node.ShortName, node.Config)
tpl, err := template.New(filepath.Base(node.Config)).ParseFiles(node.Config)
if err != nil {
return err
Expand Down
35 changes: 35 additions & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,24 @@ var deployCmd = &cobra.Command{
if len(containers) == 0 {
return fmt.Errorf("no containers found")
}

log.Debug("enriching nodes with IP information...")
enrichNodes(containers, c.Nodes, c.Config.Mgmt.Network)

wg = new(sync.WaitGroup)
wg.Add(len(c.Nodes))
for _, node := range c.Nodes {
go func(node *clab.Node) {
defer wg.Done()
err := c.ExecPostDeployTasks(ctx, node)
if err != nil {
log.Errorf("failed to run postdeploy task for node %s: %v", node.ShortName, err)
}
}(node)

}
wg.Wait()

log.Info("Writing /etc/hosts file")
err = createHostsFile(containers, c.Config.Mgmt.Network)
if err != nil {
Expand Down Expand Up @@ -298,3 +316,20 @@ func hostsEntries(containers []types.Container, bridgeName string) []byte {
}
return buff.Bytes()
}

func enrichNodes(containers []types.Container, nodes map[string]*clab.Node, mgmtNet string) {
for _, c := range containers {
name = strings.Split(c.Names[0], "-")[2]
if node, ok := nodes[name]; ok {
// add network information
node.MgmtNet = mgmtNet
node.MgmtIPv4Address = c.NetworkSettings.Networks[mgmtNet].IPAddress
node.MgmtIPv4PrefixLength = c.NetworkSettings.Networks[mgmtNet].IPPrefixLen
node.MgmtIPv6Address = c.NetworkSettings.Networks[mgmtNet].GlobalIPv6Address
node.MgmtIPv6PrefixLength = c.NetworkSettings.Networks[mgmtNet].GlobalIPv6PrefixLen

node.ContainerID = c.ID
}

}
}
2 changes: 1 addition & 1 deletion cmd/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ var destroyCmd = &cobra.Command{
name = strings.TrimLeft(cont.Names[0], "/")
}
log.Infof("Stopping container: %s", name)
err = c.DeleteContainer(ctx, name)
err := c.DeleteContainer(ctx, name)
if err != nil {
log.Errorf("could not remove container '%s': %v", name, err)
}
Expand Down
17 changes: 17 additions & 0 deletions templates/arista/ceos.cfg.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
hostname {{ .ShortName }}
username admin privilege 15 secret admin
!
interface Management0
{{ if .MgmtIPv4Address }}ip address {{ .MgmtIPv4Address }}/{{.MgmtIPv4PrefixLength}}{{end}}
{{ if .MgmtIPv6Address }}ipv6 address {{ .MgmtIPv6Address }}/{{.MgmtIPv6PrefixLength}}{{end}}
!
management api gnmi
transport grpc default
!
management api netconf
transport ssh default
!
management api http-commands
no shutdown
!
end

0 comments on commit db56f6c

Please sign in to comment.