Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for config generation for ceos #173

Merged
merged 18 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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