From cf576d411aaea5798775069f532115d8fd8ecf72 Mon Sep 17 00:00:00 2001 From: Karim Radhouani Date: Tue, 22 Jun 2021 22:20:55 +0800 Subject: [PATCH] use the new interface --- clab/ceos.go | 109 --------------- clab/cert.go | 292 --------------------------------------- clab/clab.go | 149 ++------------------ clab/config.go | 172 ++++++++--------------- clab/config_test.go | 16 +-- clab/crpd.go | 72 ---------- clab/file.go | 35 ----- clab/graph.go | 12 +- clab/inventory.go | 2 +- clab/linux.go | 27 ---- clab/mysocketio.go | 153 --------------------- clab/mysocketio_test.go | 295 ---------------------------------------- clab/netlink.go | 26 +--- clab/sonic.go | 21 --- clab/srl.go | 165 ---------------------- clab/vr-csr.go | 33 ----- clab/vr-ros.go | 33 ----- clab/vr-sros.go | 81 ----------- clab/vr-veos.go | 34 ----- clab/vr-vmx.go | 33 ----- clab/vr-xrv.go | 34 ----- clab/vr-xrv9k.go | 36 ----- cmd/deploy.go | 25 ++-- cmd/generate.go | 6 +- cmd/graph.go | 16 +-- cmd/tools_cert.go | 15 +- cmd/tools_veth.go | 3 +- go.sum | 2 + types/types.go | 22 +++ utils/env.go | 9 ++ utils/netlink.go | 20 +++ 31 files changed, 170 insertions(+), 1778 deletions(-) delete mode 100644 clab/ceos.go delete mode 100644 clab/cert.go delete mode 100644 clab/crpd.go delete mode 100644 clab/linux.go delete mode 100644 clab/mysocketio.go delete mode 100644 clab/mysocketio_test.go delete mode 100644 clab/sonic.go delete mode 100644 clab/srl.go delete mode 100644 clab/vr-csr.go delete mode 100644 clab/vr-ros.go delete mode 100644 clab/vr-sros.go delete mode 100644 clab/vr-veos.go delete mode 100644 clab/vr-vmx.go delete mode 100644 clab/vr-xrv.go delete mode 100644 clab/vr-xrv9k.go diff --git a/clab/ceos.go b/clab/ceos.go deleted file mode 100644 index 0beaa4739..000000000 --- a/clab/ceos.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "context" - "fmt" - "net" - "path" - "path/filepath" - "strings" - "time" - - log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func ceosPostDeploy(ctx context.Context, c *CLab, node *types.NodeConfig, lworkers uint) error { - // regenerate ceos config since it is now known which IP address docker assigned to this container - err := node.GenerateConfig(node.ResConfig, defaultConfigTemplates[node.Kind]) - 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.Runtime.StopContainer(ctx, node.ContainerID, &timeout) - 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.Runtime.StartContainer(ctx, node.ContainerID) - if err != nil { - return err - } - node.NSPath, err = c.Runtime.GetNSPath(ctx, node.ContainerID) - if err != nil { - return err - } - err = utils.LinkContainerNS(node.NSPath, node.LongName) - if err != nil { - return err - } - - return err -} - -func (c *CLab) initCeosNode(nodeCfg *types.NodeConfig) error { - var err error - - nodeCfg.Config, err = c.Config.Topology.GetNodeConfig(nodeCfg.ShortName) - if err != nil { - return err - } - if nodeCfg.Config == "" { - nodeCfg.Config = defaultConfigTemplates[nodeCfg.Kind] - } - - // defined env vars for the ceos - kindEnv := map[string]string{ - "CEOS": "1", - "EOS_PLATFORM": "ceoslab", - "container": "docker", - "ETBA": "4", - "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT": "1", - "INTFTYPE": "eth", - "MAPETH0": "1", - "MGMT_INTF": "eth0", - } - nodeCfg.Env = utils.MergeStringMaps(kindEnv, nodeCfg.Env) - - // the node.Cmd should be aligned with the environment. - var envSb strings.Builder - envSb.WriteString("/sbin/init ") - for k, v := range nodeCfg.Env { - envSb.WriteString("systemd.setenv=" + k + "=" + v + " ") - } - nodeCfg.Cmd = envSb.String() - nodeCfg.MacAddress = genMac("00:1c:73") - - // mount config dir - cfgPath := filepath.Join(nodeCfg.LabDir, "flash") - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprintf("%s:/mnt/flash/", cfgPath)) - - return nil -} - -func (c *CLab) createCEOSFiles(node *types.NodeConfig) error { - // generate config directory - utils.CreateDirectory(path.Join(node.LabDir, "flash"), 0777) - cfg := path.Join(node.LabDir, "flash", "startup-config") - node.ResConfig = cfg - - // sysmac is a system mac that is +1 to Ma0 mac - m, err := net.ParseMAC(node.MacAddress) - if err != nil { - return err - } - m[5] = m[5] + 1 - utils.CreateFile(path.Join(node.LabDir, "flash", "system_mac_address"), m.String()) - return nil -} diff --git a/clab/cert.go b/clab/cert.go deleted file mode 100644 index 810324536..000000000 --- a/clab/cert.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "path" - "text/template" - - "github.com/cloudflare/cfssl/api/generator" - "github.com/cloudflare/cfssl/cli/genkey" - "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/csr" - "github.com/cloudflare/cfssl/initca" - "github.com/cloudflare/cfssl/signer" - "github.com/cloudflare/cfssl/signer/universal" - log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -type Certificates struct { - Key []byte - Csr []byte - Cert []byte -} - -// CertInput struct -type CertInput struct { - Hosts []string - CommonName string - Country string - Locality string - Organization string - OrganizationUnit string - Expiry string - - Name string - LongName string - Fqdn string - Prefix string -} - -// CaRootInput struct -type CaRootInput struct { - CommonName string - Country string - Locality string - Organization string - OrganizationUnit string - Expiry string - - Prefix string - Names map[string]string // Not used right now - // prefix for certificate/key file name - NamePrefix string -} - -var rootCACSRTempl string = `{ - "CN": "{{.Prefix}} Root CA", - "key": { - "algo": "rsa", - "size": 2048 - }, - "names": [{ - "C": "BE", - "L": "Antwerp", - "O": "Nokia", - "OU": "Container lab" - }], - "ca": { - "expiry": "262800h" - } -} -` - -var nodeCSRTempl string = `{ - "CN": "{{.Name}}.{{.Prefix}}.io", - "key": { - "algo": "rsa", - "size": 2048 - }, - "names": [{ - "C": "BE", - "L": "Antwerp", - "O": "Nokia", - "OU": "Container lab" - }], - "hosts": [ - "{{.Name}}", - "{{.LongName}}", - "{{.Fqdn}}" - ] -} - - -` - -// GenerateRootCa function -func (c *CLab) GenerateRootCa(csrRootJsonTpl *template.Template, input CaRootInput) (*Certificates, error) { - log.Info("Creating root CA") - // create root CA root directory - utils.CreateDirectory(c.Dir.LabCARoot, 0755) - var err error - csrBuff := new(bytes.Buffer) - err = csrRootJsonTpl.Execute(csrBuff, input) - if err != nil { - return nil, err - } - req := csr.CertificateRequest{ - KeyRequest: csr.NewKeyRequest(), - } - err = json.Unmarshal(csrBuff.Bytes(), &req) - if err != nil { - return nil, err - } - - var key, csrPEM, cert []byte - cert, csrPEM, key, err = initca.New(&req) - if err != nil { - return nil, err - } - certs := &Certificates{ - Key: key, - Csr: csrPEM, - Cert: cert, - } - c.writeCertFiles(certs, path.Join(c.Dir.LabCARoot, input.NamePrefix)) - return certs, nil -} - -// GenerateCert generates and signs a certificate passed as input and saves the certificate and generated private key by path -// CA used to sign the cert is passed as ca and caKey file paths -func (c *CLab) GenerateCert(ca string, caKey string, csrJSONTpl *template.Template, input CertInput, targetPath string) (*Certificates, error) { - c.m.RLock() - defer c.m.RUnlock() - - utils.CreateDirectory(targetPath, 0755) - var err error - csrBuff := new(bytes.Buffer) - err = csrJSONTpl.Execute(csrBuff, input) - if err != nil { - return nil, err - } - - req := &csr.CertificateRequest{ - KeyRequest: csr.NewKeyRequest(), - } - err = json.Unmarshal(csrBuff.Bytes(), req) - if err != nil { - return nil, err - } - - var key, csrBytes []byte - gen := &csr.Generator{Validator: genkey.Validator} - csrBytes, key, err = gen.ProcessRequest(req) - if err != nil { - return nil, err - } - - policy := &config.Signing{ - Profiles: map[string]*config.SigningProfile{}, - Default: config.DefaultConfig(), - } - root := universal.Root{ - Config: map[string]string{ - "cert-file": ca, - "key-file": caKey, - }, - ForceRemote: false, - } - s, err := universal.NewSigner(root, policy) - if err != nil { - return nil, err - } - - var cert []byte - signReq := signer.SignRequest{ - Request: string(csrBytes), - } - cert, err = s.Sign(signReq) - if err != nil { - return nil, err - } - if len(signReq.Hosts) == 0 && len(req.Hosts) == 0 { - log.Warning(generator.CSRNoHostMessage) - } - certs := &Certificates{ - Key: key, - Csr: csrBytes, - Cert: cert, - } - - c.writeCertFiles(certs, path.Join(targetPath, input.Name)) - return certs, nil -} - -// RetrieveNodeCertData reads the node private key and certificate by the well known paths -// if either of those files doesn't exist, an error is returned -func (c *CLab) RetrieveNodeCertData(n *types.NodeConfig) (*Certificates, error) { - var nodeCertFilesDir = path.Join(c.Dir.LabCA, n.ShortName) - var nodeCertFile = path.Join(nodeCertFilesDir, n.ShortName+".pem") - var nodeKeyFile = path.Join(nodeCertFilesDir, n.ShortName+"-key.pem") - - var certs = &Certificates{} - - var err error - stat, err := os.Stat(nodeCertFilesDir) - // the directory for the nodes certificates doesn't exist - if err != nil || !stat.IsDir() { - return nil, err - } - - certs.Cert, err = utils.ReadFileContent(nodeCertFile) - if err != nil { - return nil, err - } - - certs.Key, err = utils.ReadFileContent(nodeKeyFile) - if err != nil { - return nil, err - } - - return certs, nil -} - -func (c *CLab) writeCertFiles(certs *Certificates, filesPrefix string) { - utils.CreateFile(filesPrefix+".pem", string(certs.Cert)) - utils.CreateFile(filesPrefix+"-key.pem", string(certs.Key)) - utils.CreateFile(filesPrefix+".csr", string(certs.Csr)) -} - -//CreateRootCA creates RootCA key/certificate if it is needed by the topology -func (c *CLab) CreateRootCA() error { - rootCANeeded := false - // check if srl kinds defined in topo - // for them we need to create rootCA and certs - for _, n := range c.Nodes { - if n.Kind == "srl" { - rootCANeeded = true - break - } - } - - if !rootCANeeded { - return nil - } - - var rootCaCertPath = path.Join(c.Dir.LabCARoot, "root-ca.pem") - var rootCaKeyPath = path.Join(c.Dir.LabCARoot, "root-ca-key.pem") - - var rootCaCertExists = false - var rootCaKeyExists = false - - _, err := os.Stat(rootCaCertPath) - if err == nil { - rootCaCertExists = true - } - _, err = os.Stat(rootCaKeyPath) - if err == nil { - rootCaKeyExists = true - } - // if both files exist skip root CA creation - if rootCaCertExists && rootCaKeyExists { - rootCANeeded = false - } - if !rootCANeeded { - return nil - } - - tpl, err := template.New("ca-csr").Parse(rootCACSRTempl) - if err != nil { - return fmt.Errorf("failed to parse Root CA CSR Template: %v", err) - } - rootCerts, err := c.GenerateRootCa(tpl, CaRootInput{ - Prefix: c.Config.Name, - NamePrefix: "root-ca", - }) - if err != nil { - return fmt.Errorf("failed to generate rootCa: %v", err) - } - - log.Debugf("root CSR: %s", string(rootCerts.Csr)) - log.Debugf("root Cert: %s", string(rootCerts.Cert)) - log.Debugf("root Key: %s", string(rootCerts.Key)) - return nil -} diff --git a/clab/clab.go b/clab/clab.go index 610116b06..b4ebe6de5 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -6,16 +6,13 @@ package clab import ( "context" - "fmt" "os" - "path" - "strings" "sync" - "text/template" "time" - "github.com/containernetworking/plugins/pkg/ns" log "github.com/sirupsen/logrus" + "github.com/srl-labs/containerlab/nodes" + _ "github.com/srl-labs/containerlab/nodes/all" "github.com/srl-labs/containerlab/runtime" _ "github.com/srl-labs/containerlab/runtime/all" "github.com/srl-labs/containerlab/types" @@ -26,7 +23,7 @@ type CLab struct { Config *Config TopoFile *TopoFile m *sync.RWMutex - Nodes map[string]*types.NodeConfig + Nodes map[string]nodes.Node Links map[int]*types.Link Runtime runtime.ContainerRuntime Dir *Directory @@ -120,7 +117,7 @@ func NewContainerLab(opts ...ClabOption) *CLab { }, TopoFile: new(TopoFile), m: new(sync.RWMutex), - Nodes: make(map[string]*types.NodeConfig), + Nodes: make(map[string]nodes.Node), Links: make(map[int]*types.Link), } @@ -153,68 +150,10 @@ func (c *CLab) initMgmtNetwork() error { return nil } -func (c *CLab) CreateNode(ctx context.Context, node *types.NodeConfig, certs *Certificates) error { - if certs != nil { - c.m.Lock() - node.TLSCert = string(certs.Cert) - node.TLSKey = string(certs.Key) - c.m.Unlock() - } - err := c.CreateNodeDirStructure(node) - if err != nil { - return err - } - return c.Runtime.CreateContainer(ctx, node) -} - -// ExecPostDeployTasks executes tasks that some nodes might require to boot properly after start -func (c *CLab) ExecPostDeployTasks(ctx context.Context, node *types.NodeConfig, lworkers uint) error { - switch node.Kind { - case "ceos": - log.Debugf("Running postdeploy actions for Arista cEOS '%s' node", node.ShortName) - return ceosPostDeploy(ctx, c, node, lworkers) - case "crpd": - _, _, err := c.Runtime.Exec(ctx, node.ContainerID, []string{"service ssh restart"}) - if err != nil { - return err - } - - case "linux": - log.Debugf("Running postdeploy actions for Linux '%s' node", node.ShortName) - return disableTxOffload(node) - - case "sonic-vs": - log.Debugf("Running postdeploy actions for sonic-vs '%s' node", node.ShortName) - // TODO: change this calls to c.ExecNotWait - // exec `supervisord` to start sonic services - _, _, err := c.Runtime.Exec(ctx, node.ContainerID, []string{"supervisord"}) - if err != nil { - return err - } - - _, _, err = c.Runtime.Exec(ctx, node.ContainerID, []string{"/usr/lib/frr/bgpd"}) - if err != nil { - return err - } - - case "mysocketio": - log.Debugf("Running postdeploy actions for mysocketio '%s' node", node.ShortName) - err := disableTxOffload(node) - if err != nil { - return fmt.Errorf("failed to disable tx checksum offload for mysocketio kind: %v", err) - } - - log.Infof("Creating mysocketio tunnels...") - err = createMysocketTunnels(ctx, c, node) - return err - } - return nil -} - func (c *CLab) CreateNodes(ctx context.Context, workers uint) { wg := new(sync.WaitGroup) wg.Add(int(workers)) - nodesChan := make(chan *types.NodeConfig) + nodesChan := make(chan nodes.Node) // start workers for i := uint(0); i < workers; i++ { go func(i uint) { @@ -226,47 +165,18 @@ func (c *CLab) CreateNodes(ctx context.Context, workers uint) { log.Debugf("Worker %d terminating...", i) return } - log.Debugf("Worker %d received node: %+v", i, node) - if node.Kind == "bridge" || node.Kind == "ovs-bridge" { + log.Debugf("Worker %d received node: %+v", i, node.Config()) + // PreDeploy + err := node.PreDeploy(c.Config.Name, c.Dir.LabCA, c.Dir.LabCARoot) + if err != nil { + log.Errorf("failed pre-deploy phase for node %q: %v", node.Config().ShortName, err) continue } - - var nodeCerts *Certificates - var certTpl *template.Template - if node.Kind == "srl" { - var err error - nodeCerts, err = c.RetrieveNodeCertData(node) - // if not available on disk, create cert in next step - if err != nil { - // create CERT - certTpl, err = template.New("node-cert").Parse(nodeCSRTempl) - if err != nil { - log.Errorf("failed to parse Node CSR Template: %v", err) - } - certInput := CertInput{ - Name: node.ShortName, - LongName: node.LongName, - Fqdn: node.Fqdn, - Prefix: c.Config.Name, - } - nodeCerts, err = c.GenerateCert( - path.Join(c.Dir.LabCARoot, "root-ca.pem"), - path.Join(c.Dir.LabCARoot, "root-ca-key.pem"), - certTpl, - certInput, - path.Join(c.Dir.LabCA, certInput.Name), - ) - if err != nil { - log.Errorf("failed to generate certificates for node %s: %v", node.ShortName, err) - } - log.Debugf("%s CSR: %s", node.ShortName, string(nodeCerts.Csr)) - log.Debugf("%s Cert: %s", node.ShortName, string(nodeCerts.Cert)) - log.Debugf("%s Key: %s", node.ShortName, string(nodeCerts.Key)) - } - } - err := c.CreateNode(ctx, node, nodeCerts) + // Deploy + err = node.Deploy(ctx, c.Runtime) if err != nil { - log.Errorf("failed to create node %s: %v", node.ShortName, err) + log.Errorf("failed deploy phase for node %q: %v", node.Config().ShortName, err) + continue } case <-ctx.Done(): return @@ -343,7 +253,6 @@ func (c *CLab) DeleteNodes(ctx context.Context, workers uint, containers []types ctrChan := make(chan *types.GenericContainer) wg.Add(int(workers)) for i := uint(0); i < workers; i++ { - go func(i uint) { defer wg.Done() for { @@ -372,33 +281,3 @@ func (c *CLab) DeleteNodes(ctx context.Context, workers uint, containers []types wg.Wait() } - -func disableTxOffload(n *types.NodeConfig) error { - // skip this if node runs in host mode - if strings.ToLower(n.NetworkMode) == "host" { - return nil - } - // disable tx checksum offload for linux containers on eth0 interfaces - nodeNS, err := ns.GetNS(n.NSPath) - if err != nil { - return err - } - err = nodeNS.Do(func(_ ns.NetNS) error { - // disabling offload on lo0 interface - err := utils.EthtoolTXOff("eth0") - if err != nil { - log.Infof("Failed to disable TX checksum offload for 'eth0' interface for Linux '%s' node: %v", n.ShortName, err) - } - return err - }) - return err -} - -func StringInSlice(slice []string, val string) (int, bool) { - for i, item := range slice { - if item == val { - return i, true - } - } - return -1, false -} diff --git a/clab/config.go b/clab/config.go index 920a58caa..ec6df31de 100644 --- a/clab/config.go +++ b/clab/config.go @@ -18,13 +18,13 @@ import ( "github.com/mitchellh/go-homedir" log "github.com/sirupsen/logrus" + "github.com/srl-labs/containerlab/nodes" "github.com/srl-labs/containerlab/types" "github.com/srl-labs/containerlab/utils" "github.com/vishvananda/netlink" ) const ( - baseConfigDir = "/etc/containerlab/templates/srl/" // prefix is used to distinct containerlab created files/dirs/containers prefix = "clab" // a name of a docker network that nodes management interfaces connect to @@ -33,8 +33,6 @@ const ( dockerNetIPv6Addr = "2001:172:20:20::/64" srlDefaultType = "ixr6" vrsrosDefaultType = "sr-1" - // default connection mode for vrnetlab based containers - vrDefConnMode = "tc" // NSPath value assigned to host interfaces hostNSPath = "__host" // veth link mtu. jacked up to 65k to allow jumbo testing of various sizes @@ -63,13 +61,6 @@ var kinds = []string{ "host", } -var defaultConfigTemplates = map[string]string{ - "srl": "/etc/containerlab/templates/srl/srlconfig.tpl", - "ceos": "/etc/containerlab/templates/arista/ceos.cfg.tpl", - "crpd": "/etc/containerlab/templates/crpd/juniper.conf", - "vr-sros": "", -} - // DefaultCredentials holds default username and password per each kind var DefaultCredentials = map[string][]string{ "vr-sros": {"admin", "admin"}, @@ -77,14 +68,6 @@ var DefaultCredentials = map[string][]string{ "vr-xrv9k": {"clab", "clab@123"}, } -var srlTypes = map[string]string{ - "ixr6": "topology-7250IXR6.yml", - "ixr10": "topology-7250IXR10.yml", - "ixrd1": "topology-7220IXRD1.yml", - "ixrd2": "topology-7220IXRD2.yml", - "ixrd3": "topology-7220IXRD3.yml", -} - // Config defines lab configuration as it is provided in the YAML file type Config struct { Name string `json:"name,omitempty"` @@ -109,7 +92,7 @@ func (c *CLab) ParseTopology() error { c.Dir.LabGraph = c.Dir.Lab + "/" + "graph" // initialize Nodes and Links variable - c.Nodes = make(map[string]*types.NodeConfig) + c.Nodes = make(map[string]nodes.Node) c.Links = make(map[int]*types.Link) // initialize the Node information from the topology map @@ -118,8 +101,10 @@ func (c *CLab) ParseTopology() error { nodeNames = append(nodeNames, nodeName) } sort.Strings(nodeNames) + var err error for idx, nodeName := range nodeNames { - if err := c.NewNode(nodeName, c.Config.Topology.Nodes[nodeName], idx); err != nil { + err = c.NewNode(nodeName, c.Config.Topology.Nodes[nodeName], idx) + if err != nil { return err } } @@ -136,12 +121,28 @@ func (c *CLab) NewNode(nodeName string, nodeDef *types.NodeDefinition, idx int) if err != nil { return err } - - err = c.initializeNodeCfg(nodeCfg) + // Init + nodeInitializer, ok := nodes.Nodes[nodeCfg.Kind] + if !ok { + log.Fatalf("node %q refers to a kind %q which is not supported. Supported kinds are %q", nodeCfg.ShortName, nodeCfg.Kind, kinds) + } + n := nodeInitializer() + // Init + err = n.Init(nodeCfg) if err != nil { - return err + log.Errorf("failed to initialize node %q: %v", nodeCfg.ShortName, err) + return fmt.Errorf("failed to initialize node %q: %v", nodeCfg.ShortName, err) } - c.Nodes[nodeName] = nodeCfg + n.Config().Labels = utils.MergeStringMaps(n.Config().Labels, map[string]string{ + "containerlab": c.Config.Name, + "clab-node-name": n.Config().ShortName, + "clab-node-kind": n.Config().Kind, + "clab-node-type": n.Config().NodeType, + "clab-node-group": n.Config().Group, + "clab-node-lab-dir": n.Config().LabDir, + "clab-topo-file": c.TopoFile.path, + }) + c.Nodes[nodeName] = n return nil } @@ -164,99 +165,34 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx MgmtIPv4Address: nodeDef.GetMgmtIPv4(), MgmtIPv6Address: nodeDef.GetMgmtIPv6(), Publish: c.Config.Topology.GetNodePublish(nodeName), + Sysctls: make(map[string]string), + Endpoints: make([]*types.Endpoint, 0), } + log.Infof("node config: %+v", nodeCfg) + var err error + // intialize config + nodeCfg.Config, err = c.Config.Topology.GetNodeConfig(nodeCfg.ShortName) + if err != nil { + return nil, err + } + // intialize license field + nodeCfg.License, err = c.Config.Topology.GetNodeLicense(nodeCfg.ShortName) + if err != nil { + return nil, err + } // initialize bind mounts binds := c.Config.Topology.GetNodeBinds(nodeName) - err := resolveBindPaths(binds, nodeCfg.LabDir) + err = resolveBindPaths(binds, nodeCfg.LabDir) if err != nil { return nil, err } nodeCfg.Binds = binds nodeCfg.PortSet, nodeCfg.PortBindings, err = c.Config.Topology.GetNodePorts(nodeName) - return nodeCfg, err -} -func (c *CLab) initializeNodeCfg(nodeCfg *types.NodeConfig) error { - var err error - switch nodeCfg.Kind { - case "ceos": - err = c.initCeosNode(nodeCfg) - if err != nil { - return err - } - case "srl": - err = c.initSRLNode(nodeCfg) - if err != nil { - return err - } - case "crpd": - err = c.initCrpdNode(nodeCfg) - if err != nil { - return err - } - case "sonic-vs": - err = c.initSonicNode(nodeCfg) - if err != nil { - return err - } - case "vr-sros": - err = c.initSROSNode(nodeCfg) - if err != nil { - return err - } - case "vr-vmx": - err = c.initVrVMXNode(nodeCfg) - if err != nil { - return err - } - case "vr-xrv": - err = c.initVrXRVNode(nodeCfg) - if err != nil { - return err - } - case "vr-xrv9k": - err = c.initVrXRV9kNode(nodeCfg) - if err != nil { - return err - } - case "vr-veos": - err = c.initVrVeosNode(nodeCfg) - if err != nil { - return err - } - case "vr-csr": - err = c.initVrCSRNode(nodeCfg) - if err != nil { - return err - } - case "vr-ros": - err = c.initVrROSNode(nodeCfg) - if err != nil { - return err - } - case "alpine", "linux", "mysocketio": - err = c.initLinuxNode(nodeCfg) - if err != nil { - return err - } - case "bridge", "ovs-bridge": - default: - return fmt.Errorf("node '%s' refers to a kind '%s' which is not supported. Supported kinds are %q", nodeCfg.ShortName, nodeCfg.Kind, kinds) - } - - // init labels after all node kinds are processed nodeCfg.Labels = c.Config.Topology.GetNodeLabels(nodeCfg.ShortName) - nodeCfg.Labels = utils.MergeStringMaps(nodeCfg.Labels, map[string]string{ - "containerlab": c.Config.Name, - "clab-node-name": nodeCfg.ShortName, - "clab-node-kind": nodeCfg.Kind, - "clab-node-type": nodeCfg.NodeType, - "clab-node-group": nodeCfg.Group, - "clab-node-lab-dir": nodeCfg.LabDir, - "clab-topo-file": c.TopoFile.path, - }) - return nil + + return nodeCfg, err } // NewLink initializes a new link object @@ -290,7 +226,7 @@ func (c *CLab) NewEndpoint(e string) *types.Endpoint { log.Fatalf("interface '%s' name exceeds maximum length of 15 characters", endpoint.EndpointName) } // generate unqiue MAC - endpoint.MAC = genMac(clabOUI) + endpoint.MAC = utils.GenMac(clabOUI) // search the node pointer for a node name referenced in endpoint section switch nName { @@ -312,8 +248,8 @@ func (c *CLab) NewEndpoint(e string) *types.Endpoint { default: c.m.Lock() if n, ok := c.Nodes[nName]; ok { - endpoint.Node = n - n.Endpoints = append(n.Endpoints, endpoint) + endpoint.Node = n.Config() + n.Config().Endpoints = append(n.Config().Endpoints, endpoint) } c.m.Unlock() } @@ -354,7 +290,7 @@ func (c *CLab) CheckTopologyDefinition(ctx context.Context) error { // VerifyBridgeExists verifies if every node of kind=bridge/ovs-bridge exists on the lab host func (c *CLab) verifyBridgesExist() error { for name, node := range c.Nodes { - if node.Kind == "bridge" || node.Kind == "ovs-bridge" { + if node.Config().Kind == "bridge" || node.Config().Kind == "ovs-bridge" { if _, err := netlink.LinkByName(name); err != nil { return fmt.Errorf("bridge %s is referenced in the endpoints section but was not found in the default network namespace", name) } @@ -391,14 +327,14 @@ func (c *CLab) VerifyImages(ctx context.Context) error { images := map[string]struct{}{} for _, node := range c.Nodes { // skip image verification for bridge kinds - if node.Kind == "bridge" || node.Kind == "ovs-bridge" { + if node.Config().Kind == "bridge" || node.Config().Kind == "ovs-bridge" { return nil } - if node.Image == "" { - return fmt.Errorf("missing required image for node %s", node.ShortName) + if node.Config().Image == "" { + return fmt.Errorf("missing required image for node %s", node.Config().ShortName) } - if node.Image != "" { - images[node.Image] = struct{}{} + if node.Config().Image != "" { + images[node.Config().Image] = struct{}{} } } @@ -428,8 +364,8 @@ func (c *CLab) VerifyContainersUniqueness(ctx context.Context) error { dups := []string{} for _, n := range c.Nodes { for _, cnt := range containers { - if "/"+n.LongName == cnt.Names[0] { - dups = append(dups, n.LongName) + if "/"+n.Config().LongName == cnt.Names[0] { + dups = append(dups, n.Config().LongName) } } } @@ -500,7 +436,7 @@ func (c *CLab) verifyRootNetnsInterfaceUniqueness() error { func (c *CLab) verifyVirtSupport() error { virtNeeded := false for _, n := range c.Nodes { - if strings.HasPrefix(n.Kind, "vr-") { + if strings.HasPrefix(n.Config().Kind, "vr-") { virtNeeded = true break } diff --git a/clab/config_test.go b/clab/config_test.go index c5647f727..5a441c47a 100644 --- a/clab/config_test.go +++ b/clab/config_test.go @@ -51,8 +51,8 @@ func TestLicenseInit(t *testing.T) { // fmt.Println(c.Config.Topology.Defaults) // fmt.Println(c.Config.Topology.Kinds) // fmt.Println(c.Config.Topology.Nodes) - if filepath.Base(c.Nodes["node1"].License) != tc.want { - t.Fatalf("wanted '%s' got '%s'", tc.want, c.Nodes["node1"].License) + if filepath.Base(c.Nodes["node1"].Config().License) != tc.want { + t.Fatalf("wanted '%s' got '%s'", tc.want, c.Nodes["node1"].Config().License) } }) } @@ -186,14 +186,8 @@ func TestTypeInit(t *testing.T) { if err := c.ParseTopology(); err != nil { t.Fatal(err) } - - // nodeCfg := c.Config.Topology.Nodes[tc.node] - // node := Node{} - // node.Kind = strings.ToLower(c.kindInitialization(&nodeCfg)) - - // ntype := c.typeInit(&nodeCfg, node.Kind) - if !reflect.DeepEqual(c.Nodes[tc.node].NodeType, tc.want) { - t.Fatalf("wanted %q got %q", tc.want, c.Nodes[tc.node].NodeType) + if !reflect.DeepEqual(c.Nodes[tc.node].Config().NodeType, tc.want) { + t.Fatalf("wanted %q got %q", tc.want, c.Nodes[tc.node].Config().NodeType) } }) } @@ -420,7 +414,7 @@ func TestLabelsInit(t *testing.T) { tc.want["clab-node-lab-dir"], _ = resolvePath(tc.want["clab-node-lab-dir"]) tc.want["clab-topo-file"], _ = resolvePath(tc.want["clab-topo-file"]) - labels := c.Nodes[tc.node].Labels + labels := c.Nodes[tc.node].Config().Labels if !cmp.Equal(labels, tc.want) { t.Errorf("failed at '%s', expected\n%v, got\n%+v", name, tc.want, labels) diff --git a/clab/crpd.go b/clab/crpd.go deleted file mode 100644 index 906cedbbc..000000000 --- a/clab/crpd.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - "path" - - log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initCrpdNode(nodeCfg *types.NodeConfig) error { - var err error - - nodeCfg.Config, err = c.Config.Topology.GetNodeConfig(nodeCfg.ShortName) - if err != nil { - return err - } - if nodeCfg.Config == "" { - nodeCfg.Config = defaultConfigTemplates[nodeCfg.Kind] - } - // initialize license file - nodeCfg.License, err = c.Config.Topology.GetNodeLicense(nodeCfg.ShortName) - if err != nil { - return err - } - - // mount config and log dirs - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(path.Join(nodeCfg.LabDir, "config"), ":/config")) - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(path.Join(nodeCfg.LabDir, "log"), ":/var/log")) - // mount sshd_config - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(path.Join(nodeCfg.LabDir, "config/sshd_config"), ":/etc/ssh/sshd_config")) - return nil -} - -func (c *CLab) createCRPDFiles(nodeCfg *types.NodeConfig) error { - // create config and logs directory that will be bind mounted to crpd - utils.CreateDirectory(path.Join(nodeCfg.LabDir, "config"), 0777) - utils.CreateDirectory(path.Join(nodeCfg.LabDir, "log"), 0777) - - // copy crpd config from default template or user-provided conf file - cfg := path.Join(nodeCfg.LabDir, "/config/juniper.conf") - - err := nodeCfg.GenerateConfig(cfg, defaultConfigTemplates[nodeCfg.Kind]) - if err != nil { - log.Errorf("node=%s, failed to generate config: %v", nodeCfg.ShortName, err) - } - - // copy crpd sshd conf file to crpd node dir - src := "/etc/containerlab/templates/crpd/sshd_config" - dst := path.Join(nodeCfg.LabDir, "/config/sshd_config") - err = utils.CopyFile(src, dst) - if err != nil { - return fmt.Errorf("file copy [src %s -> dst %s] failed %v", src, dst, err) - } - log.Debugf("CopyFile src %s -> dst %s succeeded\n", src, dst) - - if nodeCfg.License != "" { - // copy license file to node specific lab directory - src = nodeCfg.License - dst = path.Join(nodeCfg.LabDir, "/config/license.conf") - if err = utils.CopyFile(src, dst); err != nil { - return fmt.Errorf("file copy [src %s -> dst %s] failed %v", src, dst, err) - } - log.Debugf("CopyFile src %s -> dst %s succeeded", src, dst) - } - return nil -} diff --git a/clab/file.go b/clab/file.go index 137be23dc..fab83d08b 100644 --- a/clab/file.go +++ b/clab/file.go @@ -12,8 +12,6 @@ import ( "strings" log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" "gopkg.in/yaml.v2" ) @@ -51,36 +49,3 @@ func (c *CLab) GetTopology(topo string) error { } return nil } - -// CreateNodeDirStructure create the directory structure and files for the lab nodes -func (c *CLab) CreateNodeDirStructure(node *types.NodeConfig) (err error) { - c.m.RLock() - defer c.m.RUnlock() - - // create node directory in the lab directory - // skip creation of node directory for linux/bridge kinds - // since they don't keep any state normally - if node.Kind != "linux" && node.Kind != "bridge" { - utils.CreateDirectory(node.LabDir, 0777) - } - - switch node.Kind { - case "srl": - if err := c.createSRLFiles(node); err != nil { - return err - } - case "ceos": - if err := c.createCEOSFiles(node); err != nil { - return err - } - case "crpd": - if err := c.createCRPDFiles(node); err != nil { - return err - } - case "vr-sros": - if err := c.createVrSROSFiles(node); err != nil { - return err - } - } - return nil -} diff --git a/clab/graph.go b/clab/graph.go index 9bd573a8d..6a56d34eb 100644 --- a/clab/graph.go +++ b/clab/graph.go @@ -37,20 +37,20 @@ func (c *CLab) GenerateGraph(topo string) error { attr["fillcolor"] = "red" attr["label"] = nodeName - attr["xlabel"] = node.Kind - if len(strings.TrimSpace(node.Group)) != 0 { - attr["group"] = node.Group - if strings.Contains(node.Group, "bb") { + attr["xlabel"] = node.Config().Kind + if len(strings.TrimSpace(node.Config().Group)) != 0 { + attr["group"] = node.Config().Group + if strings.Contains(node.Config().Group, "bb") { attr["fillcolor"] = "blue" attr["color"] = "blue" attr["fontcolor"] = "white" - } else if strings.Contains(node.Kind, "srl") { + } else if strings.Contains(node.Config().Kind, "srl") { attr["fillcolor"] = "green" attr["color"] = "green" attr["fontcolor"] = "black" } } - if err := g.AddNode(c.TopoFile.name, node.ShortName, attr); err != nil { + if err := g.AddNode(c.TopoFile.name, node.Config().ShortName, attr); err != nil { return err } diff --git a/clab/inventory.go b/clab/inventory.go index ae14ac0a9..0bcdc65c0 100644 --- a/clab/inventory.go +++ b/clab/inventory.go @@ -54,7 +54,7 @@ func (c *CLab) generateAnsibleInventory(w io.Writer) error { } for _, n := range c.Nodes { - i.Nodes[n.Kind] = append(i.Nodes[n.Kind], n) + i.Nodes[n.Config().Kind] = append(i.Nodes[n.Config().Kind], n.Config()) } // sort nodes by name as they are not sorted originally diff --git a/clab/linux.go b/clab/linux.go deleted file mode 100644 index 0b8036192..000000000 --- a/clab/linux.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "strings" - - "github.com/srl-labs/containerlab/types" -) - -func (c *CLab) initLinuxNode(nodeCfg *types.NodeConfig) error { - var err error - - nodeCfg.Config, err = c.Config.Topology.GetNodeConfig(nodeCfg.ShortName) - if err != nil { - return err - } - - nodeCfg.Sysctls = make(map[string]string) - if strings.ToLower(nodeCfg.NetworkMode) != "host" { - nodeCfg.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0" - } - - return nil -} diff --git a/clab/mysocketio.go b/clab/mysocketio.go deleted file mode 100644 index b69ef9bf9..000000000 --- a/clab/mysocketio.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "context" - "fmt" - "strconv" - "strings" - - log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/types" -) - -var supportedSockTypes = []string{"tcp", "tls", "http", "https"} - -type mysocket struct { - Stype string - Port int - AllowedDomains []string - AllowedEmails []string -} - -func parseSocketCfg(s string) (mysocket, error) { - var err error - ms := mysocket{} - split := strings.Split(s, "/") - if len(split) > 3 { - return ms, fmt.Errorf("wrong mysocketio publish section %s. should be /[/|,], i.e. tcp/22 or tls/22/gmail.com or http/80/user1@mail.com,gmail.com,user2@clab.com", s) - } - - if err = checkSockType(split[0]); err != nil { - return ms, err - } - ms.Stype = split[0] - p, err := strconv.Atoi(split[1]) // port - if err != nil { - return ms, err - } - if err := checkSockPort(p); err != nil { - return ms, err - } - ms.Port = p - - if len(split) == 3 { - ms.AllowedDomains, ms.AllowedEmails, _ = parseAllowedUsers(split[2]) - - // identity aware sockets for TCP require TLS type. Force the switch to make it easy on users - if (len(ms.AllowedDomains) > 0 || len(ms.AllowedEmails) > 0) && ms.Stype == "tcp" { - ms.Stype = "tls" - } - } - - return ms, err -} - -func parseAllowedUsers(s string) (domains, emails []string, err error) { - - for _, e := range strings.Split(s, ",") { - e = strings.TrimSpace(e) - if e == "" { - continue - } - if strings.Contains(e, "@") { - emails = append(emails, e) - } else { - domains = append(domains, e) - } - } - return domains, emails, err -} - -func checkSockType(t string) error { - if _, ok := StringInSlice(supportedSockTypes, t); !ok { - return fmt.Errorf("mysocketio type %s is not supported. Supported types are tcp/tls/http/https", t) - } - return nil -} - -func checkSockPort(p int) error { - if p < 1 || p > 65535 { - return fmt.Errorf("incorrect port number %v", p) - } - return nil -} - -// createMysocketTunnels creates internet reachable personal tunnels using mysocket.io -func createMysocketTunnels(ctx context.Context, c *CLab, node *types.NodeConfig) error { - // remove the existing sockets - cmd := []string{"/bin/sh", "-c", "mysocketctl socket ls | awk '/clab/ {print $2}' | xargs -n1 mysocketctl socket delete -s"} - log.Debugf("Running postdeploy mysocketio command %q", cmd) - _, _, err := c.Runtime.Exec(ctx, node.ContainerID, cmd) - if err != nil { - return fmt.Errorf("failed to remove existing sockets: %v", err) - } - - for _, n := range c.Nodes { - if len(n.Publish) == 0 { - continue - } - for _, socket := range n.Publish { - ms, err := parseSocketCfg(socket) - if err != nil { - return err - } - - // create socket and get its ID - sockCmd := createSockCmd(ms, n.ShortName) - cmd := []string{"/bin/sh", "-c", fmt.Sprintf("%s | awk 'NR==4 {print $2}'", sockCmd)} - log.Debugf("Running mysocketio command %q", cmd) - stdout, _, err := c.Runtime.Exec(ctx, node.ContainerID, cmd) - if err != nil { - return fmt.Errorf("failed to create mysocketio socket: %v", err) - } - sockID := strings.TrimSpace(string(stdout)) - - // create tunnel and get its ID - cmd = []string{"/bin/sh", "-c", fmt.Sprintf("mysocketctl tunnel create -s %s | awk 'NR==4 {print $4}'", sockID)} - log.Debugf("Running mysocketio command %q", cmd) - stdout, _, err = c.Runtime.Exec(ctx, node.ContainerID, cmd) - if err != nil { - return fmt.Errorf("failed to create mysocketio socket: %v", err) - } - tunID := strings.TrimSpace(string(stdout)) - - // connect tunnel - cmd = []string{"/bin/sh", "-c", fmt.Sprintf("mysocketctl tunnel connect --host %s -p %d -s %s -t %s > socket-%s-%s-%d.log", - n.LongName, ms.Port, sockID, tunID, n.ShortName, ms.Stype, ms.Port)} - log.Debugf("Running mysocketio command %q", cmd) - err = c.Runtime.ExecNotWait(ctx, node.ContainerID, cmd) - if err != nil { - return err - } - } - } - return nil -} - -func createSockCmd(ms mysocket, n string) string { - cmd := fmt.Sprintf("mysocketctl socket create -t %s -n clab-%s-%s-%d", ms.Stype, n, ms.Stype, ms.Port) - if len(ms.AllowedDomains) > 0 || len(ms.AllowedEmails) > 0 { - cmd = fmt.Sprintf("%s -c", cmd) - } - if len(ms.AllowedDomains) > 0 { - cmd = fmt.Sprintf("%s -d '%s'", cmd, strings.Join(ms.AllowedDomains, ",")) - } - if len(ms.AllowedEmails) > 0 { - cmd = fmt.Sprintf("%s -e '%s'", cmd, strings.Join(ms.AllowedEmails, ",")) - } - return cmd -} diff --git a/clab/mysocketio_test.go b/clab/mysocketio_test.go deleted file mode 100644 index b31e96ed4..000000000 --- a/clab/mysocketio_test.go +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" -) - -func TestCheckSockType(t *testing.T) { - tests := map[string]struct { - got string - want error - }{ - "correct-type": { - got: "tcp", - want: nil, - }, - "incorrect-type": { - got: "dns", - want: fmt.Errorf("mysocketio type dns is not supported. Supported types are tcp/tls/http/https"), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - err := checkSockType(tc.got) - - if err != nil && tc.want != nil && (err.Error() != tc.want.Error()) { - t.Fatalf("wanted '%v' got '%v'", tc.want, err) - } - if err != nil && tc.want == nil { - t.Fatalf("wanted '%v' got '%v'", tc.want, err) - } - - }) - } -} - -func TestCheckSockPort(t *testing.T) { - tests := map[string]struct { - got int - want error - }{ - "correct-port": { - got: 22, - want: nil, - }, - "negative-port": { - got: -22, - want: fmt.Errorf("incorrect port number -22"), - }, - "large-port": { - got: 123123123, - want: fmt.Errorf("incorrect port number 123123123"), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - err := checkSockPort(tc.got) - - if err != nil && tc.want != nil && (err.Error() != tc.want.Error()) { - t.Fatalf("wanted '%v' got '%v'", tc.want, err) - } - if err != nil && tc.want == nil { - t.Fatalf("wanted '%v' got '%v'", tc.want, err) - } - - }) - } -} - -func TestParseSocketCfg(t *testing.T) { - tests := map[string]struct { - got string - want mysocket - err error - }{ - "simple-tcp": { - got: "tcp/22", - want: mysocket{ - Stype: "tcp", - Port: 22, - }, - err: nil, - }, - "simple-http": { - got: "http/8080", - want: mysocket{ - Stype: "http", - Port: 8080, - }, - err: nil, - }, - "wrong-type": { - got: "stcp/22", - want: mysocket{}, - err: fmt.Errorf(""), - }, - "with-email-and-domain": { - got: "tls/22/a@b.com,c.com", - want: mysocket{ - Stype: "tls", - Port: 22, - AllowedDomains: []string{"c.com"}, - AllowedEmails: []string{"a@b.com"}, - }, - err: nil, - }, - "wrong-num-of-sections": { - got: "tcp/22/a@b.com/test", - want: mysocket{}, - err: fmt.Errorf(""), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - got, err := parseSocketCfg(tc.got) - - if diff := cmp.Diff(tc.want, got); diff != "" { - t.Errorf("parseSocketCfg() mismatch (-want +got):\n%s", diff) - } - - switch tc.err { - case nil: - if err != nil { - t.Errorf("unexpected error %v", err) - } - case fmt.Errorf(""): - if err != nil { - t.Errorf("expected to have an errorm but got nil instead") - } - - } - - }) - } -} - -func TestParseAllowedUsers(t *testing.T) { - tests := map[string]struct { - got string - want struct { - Domains []string - Emails []string - } - }{ - "single-email": { - got: "a@b.com", - want: struct { - Domains []string - Emails []string - }{ - Domains: nil, - Emails: []string{"a@b.com"}, - }, - }, - "two-emails": { - got: "a@b.com,x@y.com", - want: struct { - Domains []string - Emails []string - }{ - Domains: nil, - Emails: []string{"a@b.com", "x@y.com"}, - }, - }, - "two-emails-with-spaces": { - got: " a@b.com , x@y.com", - want: struct { - Domains []string - Emails []string - }{ - Domains: nil, - Emails: []string{"a@b.com", "x@y.com"}, - }, - }, - "email-and-domain": { - got: "a@b.com,dom.com", - want: struct { - Domains []string - Emails []string - }{ - Domains: []string{"dom.com"}, - Emails: []string{"a@b.com"}, - }, - }, - "many-emails-many-domains": { - got: "a@b.com,dom.com,x@y.com,abc.net", - want: struct { - Domains []string - Emails []string - }{ - Domains: []string{"dom.com", "abc.net"}, - Emails: []string{"a@b.com", "x@y.com"}, - }, - }, - "empty-value": { - got: "a@b.com,,x@y.com,", - want: struct { - Domains []string - Emails []string - }{ - Domains: nil, - Emails: []string{"a@b.com", "x@y.com"}, - }, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - var got struct { - Domains []string - Emails []string - } - got.Domains, got.Emails, _ = parseAllowedUsers(tc.got) - - if diff := cmp.Diff(tc.want, got); diff != "" { - t.Errorf("parseSocketCfg() mismatch (-want +got):\n%s", diff) - } - - }) - } -} - -func TestCreateSockCmd(t *testing.T) { - tests := map[string]struct { - got struct { - MS mysocket - Name string - } - want string - }{ - "single-email": { - got: struct { - MS mysocket - Name string - }{ - MS: mysocket{ - Stype: "tls", - Port: 22, - AllowedEmails: []string{"x@y.com"}, - }, - Name: "test", - }, - want: "mysocketctl socket create -t tls -n clab-test-tls-22 -c -e 'x@y.com'", - }, - "single-domain": { - got: struct { - MS mysocket - Name string - }{ - MS: mysocket{ - Stype: "tls", - Port: 22, - AllowedDomains: []string{"y.com"}, - }, - Name: "test", - }, - want: "mysocketctl socket create -t tls -n clab-test-tls-22 -c -d 'y.com'", - }, - "domains-and-emails": { - got: struct { - MS mysocket - Name string - }{ - MS: mysocket{ - Stype: "tls", - Port: 22, - AllowedDomains: []string{"y.com"}, - AllowedEmails: []string{"x@z.com"}, - }, - Name: "test", - }, - want: "mysocketctl socket create -t tls -n clab-test-tls-22 -c -d 'y.com' -e 'x@z.com'", - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - cmd := createSockCmd(tc.got.MS, tc.got.Name) - - if diff := cmp.Diff(tc.want, cmd); diff != "" { - t.Errorf("parseSocketCfg() mismatch (-want +got):\n%s", diff) - } - - }) - } -} diff --git a/clab/netlink.go b/clab/netlink.go index c8f527604..c4596eb39 100644 --- a/clab/netlink.go +++ b/clab/netlink.go @@ -5,10 +5,8 @@ package clab import ( - "crypto/rand" "fmt" "net" - "os" "strings" "github.com/containernetworking/plugins/pkg/ns" @@ -222,9 +220,9 @@ func (veth *vEthEndpoint) toBridge() error { // DeleteNetnsSymlinks deletes the symlink file created for each container netns func (c *CLab) DeleteNetnsSymlinks() (err error) { for _, node := range c.Nodes { - if node.Kind != "bridge" { - log.Debugf("Deleting %s network namespace", node.LongName) - if err := deleteNetnsSymlink(node.LongName); err != nil { + if node.Config().Kind != "bridge" { + log.Debugf("Deleting %s network namespace", node.Config().LongName) + if err := utils.DeleteNetnsSymlink(node.Config().LongName); err != nil { return err } } @@ -239,17 +237,6 @@ func genIfName() string { return string(s[:8]) } -// deleteNetnsSymlink deletes a network namespace and removes the symlink created by linkContainerNS func -func deleteNetnsSymlink(n string) error { - log.Debug("Deleting netns symlink: ", n) - sl := fmt.Sprintf("/run/netns/%s", n) - err := os.Remove(sl) - if err != nil { - log.Debug("Failed to delete netns symlink by path:", sl) - } - return nil -} - // GetLinksByNamePrefix returns a list of links whose name matches a prefix func GetLinksByNamePrefix(prefix string) ([]netlink.Link, error) { // filtered list of interfaces @@ -272,10 +259,3 @@ func GetLinksByNamePrefix(prefix string) ([]netlink.Link, error) { } return fls, nil } - -// genMac generates a random MAC address for a given OUI -func genMac(oui string) string { - buf := make([]byte, 3) - _, _ = rand.Read(buf) - return fmt.Sprintf("%s:%02x:%02x:%02x", oui, buf[0], buf[1], buf[2]) -} diff --git a/clab/sonic.go b/clab/sonic.go deleted file mode 100644 index 4eb3b7f75..000000000 --- a/clab/sonic.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import "github.com/srl-labs/containerlab/types" - -func (c *CLab) initSonicNode(nodeCfg *types.NodeConfig) error { - var err error - - nodeCfg.Config, err = c.Config.Topology.GetNodeConfig(nodeCfg.ShortName) - if err != nil { - return err - } - - // rewrite entrypoint so sonic won't start supervisord before we attach veth interfaces - nodeCfg.Entrypoint = "/bin/bash" - - return nil -} diff --git a/clab/srl.go b/clab/srl.go deleted file mode 100644 index 9fd0337fd..000000000 --- a/clab/srl.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "crypto/rand" - "fmt" - "os" - "path" - "path/filepath" - "strings" - "text/template" - - log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -type mac struct { - MAC string -} - -func generateSRLTopologyFile(src, labDir string, index int) error { - dst := path.Join(labDir, "topology.yml") - tpl, err := template.ParseFiles(src) - if err != nil { - return err - } - - // generate random bytes to use in the 2-3rd bytes of a base mac - // this ensures that different srl nodes will have different macs for their ports - buf := make([]byte, 2) - _, err = rand.Read(buf) - if err != nil { - return err - } - m := fmt.Sprintf("02:%02x:%02x:00:00:00", buf[0], buf[1]) - - mac := mac{ - MAC: m, - } - log.Debug(mac, dst) - f, err := os.Create(dst) - if err != nil { - return err - } - defer f.Close() - - if err = tpl.Execute(f, mac); err != nil { - return err - } - return nil -} - -func (c *CLab) initSRLNode(nodeCfg *types.NodeConfig) error { - var err error - // initialize the global parameters with defaults, can be overwritten later - nodeCfg.Config, err = c.Config.Topology.GetNodeConfig(nodeCfg.ShortName) - if err != nil { - return err - } - if nodeCfg.Config == "" { - nodeCfg.Config = defaultConfigTemplates[nodeCfg.Kind] - } - nodeCfg.License, err = c.Config.Topology.GetNodeLicense(nodeCfg.ShortName) - if err != nil { - return err - } - if nodeCfg.License == "" { - return fmt.Errorf("no license found for node '%s' of kind '%s'", nodeCfg.ShortName, nodeCfg.Kind) - } - - if nodeCfg.NodeType == "" { - nodeCfg.NodeType = srlDefaultType - } - if filename, found := srlTypes[nodeCfg.NodeType]; found { - nodeCfg.Topology = path.Join(baseConfigDir, filename) - } else { - keys := make([]string, 0, len(srlTypes)) - for key := range srlTypes { - keys = append(keys, key) - } - log.Fatalf("wrong node type. '%s' doesn't exist. should be any of %s", nodeCfg.NodeType, strings.Join(keys, ", ")) - } - - // the addition touch is needed to support non docker runtimes - nodeCfg.Cmd = "sudo bash -c 'touch /.dockerenv && /opt/srlinux/bin/sr_linux'" - - kindEnv := map[string]string{"SRLINUX": "1"} - nodeCfg.Env = utils.MergeStringMaps(kindEnv, nodeCfg.Env) - - // if user was not initialized to a value, use root - if nodeCfg.User == "" { - nodeCfg.User = "0:0" - } - - nodeCfg.Sysctls = make(map[string]string) - nodeCfg.Sysctls["net.ipv4.ip_forward"] = "0" - nodeCfg.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0" - nodeCfg.Sysctls["net.ipv6.conf.all.accept_dad"] = "0" - nodeCfg.Sysctls["net.ipv6.conf.default.accept_dad"] = "0" - nodeCfg.Sysctls["net.ipv6.conf.all.autoconf"] = "0" - nodeCfg.Sysctls["net.ipv6.conf.default.autoconf"] = "0" - - // we mount a fixed path node.Labdir/license.key as the license referenced in topo file will be copied to that path - // in (c *cLab) CreateNodeDirStructure - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(filepath.Join(nodeCfg.LabDir, "license.key"), ":/opt/srlinux/etc/license.key:ro")) - - // mount config directory - cfgPath := filepath.Join(nodeCfg.LabDir, "config") - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(cfgPath, ":/etc/opt/srlinux/:rw")) - - // mount srlinux.conf - srlconfPath := filepath.Join(nodeCfg.LabDir, "srlinux.conf") - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(srlconfPath, ":/home/admin/.srlinux.conf:rw")) - - // mount srlinux topology - topoPath := filepath.Join(nodeCfg.LabDir, "topology.yml") - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(topoPath, ":/tmp/topology.yml:ro")) - - return nil -} - -func (c *CLab) createSRLFiles(node *types.NodeConfig) error { - log.Debugf("Creating directory structure for SRL container: %s", node.ShortName) - var src string - var dst string - - // copy license file to node specific directory in lab - src = node.License - dst = path.Join(node.LabDir, "license.key") - if err := utils.CopyFile(src, dst); err != nil { - return fmt.Errorf("CopyFile src %s -> dst %s failed %v", src, dst, err) - } - log.Debugf("CopyFile src %s -> dst %s succeeded", src, dst) - - // generate SRL topology file - err := generateSRLTopologyFile(node.Topology, node.LabDir, node.Index) - if err != nil { - return err - } - - // generate a config file if the destination does not exist - // if the node has a `config:` statement, the file specified in that section - // will be used as a template in nodeGenerateConfig() - utils.CreateDirectory(path.Join(node.LabDir, "config"), 0777) - dst = path.Join(node.LabDir, "config", "config.json") - err = node.GenerateConfig(dst, defaultConfigTemplates[node.Kind]) - if err != nil { - log.Errorf("node=%s, failed to generate config: %v", node.ShortName, err) - } - - // copy env config to node specific directory in lab - src = "/etc/containerlab/templates/srl/srl_env.conf" - dst = node.LabDir + "/" + "srlinux.conf" - err = utils.CopyFile(src, dst) - if err != nil { - return fmt.Errorf("CopyFile src %s -> dst %s failed %v", src, dst, err) - } - log.Debugf("CopyFile src %s -> dst %s succeeded\n", src, dst) - - return err -} diff --git a/clab/vr-csr.go b/clab/vr-csr.go deleted file mode 100644 index 3c9d059a5..000000000 --- a/clab/vr-csr.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initVrCSRNode(nodeCfg *types.NodeConfig) error { - // env vars are used to set launch.py arguments in vrnetlab container - defEnv := map[string]string{ - "CONNECTION_MODE": vrDefConnMode, - "USERNAME": "admin", - "PASSWORD": "admin", - "DOCKER_NET_V4_ADDR": c.Config.Mgmt.IPv4Subnet, - "DOCKER_NET_V6_ADDR": c.Config.Mgmt.IPv6Subnet, - } - nodeCfg.Env = utils.MergeStringMaps(defEnv, nodeCfg.Env) - - if nodeCfg.Env["CONNECTION_MODE"] == "macvtap" { - // mount dev dir to enable macvtap - nodeCfg.Binds = append(nodeCfg.Binds, "/dev:/dev") - } - - nodeCfg.Cmd = fmt.Sprintf("--username %s --password %s --hostname %s --connection-mode %s --trace", nodeCfg.Env["USERNAME"], nodeCfg.Env["PASSWORD"], nodeCfg.ShortName, nodeCfg.Env["CONNECTION_MODE"]) - - return nil -} diff --git a/clab/vr-ros.go b/clab/vr-ros.go deleted file mode 100644 index 0a9e97811..000000000 --- a/clab/vr-ros.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initVrROSNode(nodeCfg *types.NodeConfig) error { - // env vars are used to set launch.py arguments in vrnetlab container - defEnv := map[string]string{ - "CONNECTION_MODE": vrDefConnMode, - "USERNAME": "admin", - "PASSWORD": "admin", - "DOCKER_NET_V4_ADDR": c.Config.Mgmt.IPv4Subnet, - "DOCKER_NET_V6_ADDR": c.Config.Mgmt.IPv6Subnet, - } - nodeCfg.Env = utils.MergeStringMaps(defEnv, nodeCfg.Env) - - if nodeCfg.Env["CONNECTION_MODE"] == "macvtap" { - // mount dev dir to enable macvtap - nodeCfg.Binds = append(nodeCfg.Binds, "/dev:/dev") - } - - nodeCfg.Cmd = fmt.Sprintf("--username %s --password %s --hostname %s --connection-mode %s --trace", nodeCfg.Env["USERNAME"], nodeCfg.Env["PASSWORD"], nodeCfg.ShortName, nodeCfg.Env["CONNECTION_MODE"]) - - return nil -} diff --git a/clab/vr-sros.go b/clab/vr-sros.go deleted file mode 100644 index 2433fdb7f..000000000 --- a/clab/vr-sros.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - "path" - - log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initSROSNode(nodeCfg *types.NodeConfig) error { - var err error - - nodeCfg.Config, err = c.Config.Topology.GetNodeConfig(nodeCfg.ShortName) - if err != nil { - return err - } - if nodeCfg.Config == "" { - nodeCfg.Config = defaultConfigTemplates[nodeCfg.Kind] - } - // vr-sros type sets the vrnetlab/sros variant (https://github.com/hellt/vrnetlab/sros) - if nodeCfg.NodeType == "" { - nodeCfg.NodeType = vrsrosDefaultType - } - // initialize license file - nodeCfg.License, err = c.Config.Topology.GetNodeLicense(nodeCfg.ShortName) - if err != nil { - return err - } - // env vars are used to set launch.py arguments in vrnetlab container - defEnv := map[string]string{ - "CONNECTION_MODE": vrDefConnMode, - "DOCKER_NET_V4_ADDR": c.Config.Mgmt.IPv4Subnet, - "DOCKER_NET_V6_ADDR": c.Config.Mgmt.IPv6Subnet, - } - nodeCfg.Env = utils.MergeStringMaps(defEnv, nodeCfg.Env) - - // mount tftpboot dir - nodeCfg.Binds = append(nodeCfg.Binds, fmt.Sprint(path.Join(nodeCfg.LabDir, "tftpboot"), ":/tftpboot")) - if nodeCfg.Env["CONNECTION_MODE"] == "macvtap" { - // mount dev dir to enable macvtap - nodeCfg.Binds = append(nodeCfg.Binds, "/dev:/dev") - } - - nodeCfg.Cmd = fmt.Sprintf("--trace --connection-mode %s --hostname %s --variant \"%s\"", nodeCfg.Env["CONNECTION_MODE"], - nodeCfg.ShortName, - nodeCfg.NodeType, - ) - return nil -} - -func (c *CLab) createVrSROSFiles(node *types.NodeConfig) error { - // create config directory that will be bind mounted to vrnetlab container at / path - utils.CreateDirectory(path.Join(node.LabDir, "tftpboot"), 0777) - - if node.License != "" { - // copy license file to node specific lab directory - src := node.License - dst := path.Join(node.LabDir, "/tftpboot/license.txt") - if err := utils.CopyFile(src, dst); err != nil { - return fmt.Errorf("file copy [src %s -> dst %s] failed %v", src, dst, err) - } - log.Debugf("CopyFile src %s -> dst %s succeeded", src, dst) - - cfg := path.Join(node.LabDir, "tftpboot", "config.txt") - if node.Config != "" { - err := node.GenerateConfig(cfg, defaultConfigTemplates[node.Kind]) - 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) - } - } - return nil -} diff --git a/clab/vr-veos.go b/clab/vr-veos.go deleted file mode 100644 index 36509f691..000000000 --- a/clab/vr-veos.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initVrVeosNode(nodeCfg *types.NodeConfig) error { - // env vars are used to set launch.py arguments in vrnetlab container - defEnv := map[string]string{ - "CONNECTION_MODE": vrDefConnMode, - "USERNAME": "admin", - "PASSWORD": "admin", - "DOCKER_NET_V4_ADDR": c.Config.Mgmt.IPv4Subnet, - "DOCKER_NET_V6_ADDR": c.Config.Mgmt.IPv6Subnet, - } - nodeCfg.Env = utils.MergeStringMaps(defEnv, nodeCfg.Env) - - if nodeCfg.Env["CONNECTION_MODE"] == "macvtap" { - // mount dev dir to enable macvtap - nodeCfg.Binds = append(nodeCfg.Binds, "/dev:/dev") - } - - nodeCfg.Cmd = fmt.Sprintf("--username %s --password %s --hostname %s --connection-mode %s --trace", - nodeCfg.Env["USERNAME"], nodeCfg.Env["PASSWORD"], nodeCfg.ShortName, nodeCfg.Env["CONNECTION_MODE"]) - - return nil -} diff --git a/clab/vr-vmx.go b/clab/vr-vmx.go deleted file mode 100644 index 4812fc5e4..000000000 --- a/clab/vr-vmx.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initVrVMXNode(nodeCfg *types.NodeConfig) error { - // env vars are used to set launch.py arguments in vrnetlab container - defEnv := map[string]string{ - "USERNAME": "admin", - "PASSWORD": "admin@123", - "CONNECTION_MODE": vrDefConnMode, - "DOCKER_NET_V4_ADDR": c.Config.Mgmt.IPv4Subnet, - "DOCKER_NET_V6_ADDR": c.Config.Mgmt.IPv6Subnet, - } - nodeCfg.Env = utils.MergeStringMaps(defEnv, nodeCfg.Env) - - if nodeCfg.Env["CONNECTION_MODE"] == "macvtap" { - // mount dev dir to enable macvtap - nodeCfg.Binds = append(nodeCfg.Binds, "/dev:/dev") - } - - nodeCfg.Cmd = fmt.Sprintf("--username %s --password %s --hostname %s --connection-mode %s --trace", nodeCfg.Env["USERNAME"], nodeCfg.Env["PASSWORD"], nodeCfg.ShortName, nodeCfg.Env["CONNECTION_MODE"]) - - return nil -} diff --git a/clab/vr-xrv.go b/clab/vr-xrv.go deleted file mode 100644 index e8e5ed02f..000000000 --- a/clab/vr-xrv.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initVrXRVNode(nodeCfg *types.NodeConfig) error { - // env vars are used to set launch.py arguments in vrnetlab container - defEnv := map[string]string{ - "USERNAME": "clab", - "PASSWORD": "clab@123", - "CONNECTION_MODE": vrDefConnMode, - "DOCKER_NET_V4_ADDR": c.Config.Mgmt.IPv4Subnet, - "DOCKER_NET_V6_ADDR": c.Config.Mgmt.IPv6Subnet, - } - nodeCfg.Env = utils.MergeStringMaps(defEnv, nodeCfg.Env) - - if nodeCfg.Env["CONNECTION_MODE"] == "macvtap" { - // mount dev dir to enable macvtap - nodeCfg.Binds = append(nodeCfg.Binds, "/dev:/dev") - } - - nodeCfg.Cmd = fmt.Sprintf("--username %s --password %s --hostname %s --connection-mode %s --trace", - nodeCfg.Env["USERNAME"], nodeCfg.Env["PASSWORD"], nodeCfg.ShortName, nodeCfg.Env["CONNECTION_MODE"]) - - return nil -} diff --git a/clab/vr-xrv9k.go b/clab/vr-xrv9k.go deleted file mode 100644 index 0d8b3d9b9..000000000 --- a/clab/vr-xrv9k.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -func (c *CLab) initVrXRV9kNode(nodeCfg *types.NodeConfig) error { - // env vars are used to set launch.py arguments in vrnetlab container - defEnv := map[string]string{ - "USERNAME": "clab", - "PASSWORD": "clab@123", - "CONNECTION_MODE": vrDefConnMode, - "VCPU": "2", - "RAM": "12288", - "DOCKER_NET_V4_ADDR": c.Config.Mgmt.IPv4Subnet, - "DOCKER_NET_V6_ADDR": c.Config.Mgmt.IPv6Subnet, - } - nodeCfg.Env = utils.MergeStringMaps(defEnv, nodeCfg.Env) - - if nodeCfg.Env["CONNECTION_MODE"] == "macvtap" { - // mount dev dir to enable macvtap - nodeCfg.Binds = append(nodeCfg.Binds, "/dev:/dev") - } - - nodeCfg.Cmd = fmt.Sprintf("--username %s --password %s --hostname %s --connection-mode %s --vcpu %s --ram %s --trace", - nodeCfg.Env["USERNAME"], nodeCfg.Env["PASSWORD"], nodeCfg.ShortName, nodeCfg.Env["CONNECTION_MODE"], nodeCfg.Env["VCPU"], nodeCfg.Env["RAM"]) - - return nil -} diff --git a/cmd/deploy.go b/cmd/deploy.go index 33bebab4c..4260a3dca 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -16,7 +16,9 @@ import ( cfssllog "github.com/cloudflare/cfssl/log" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/srl-labs/containerlab/cert" "github.com/srl-labs/containerlab/clab" + "github.com/srl-labs/containerlab/nodes" "github.com/srl-labs/containerlab/types" "github.com/srl-labs/containerlab/utils" ) @@ -92,7 +94,7 @@ var deployCmd = &cobra.Command{ if debug { cfssllog.Level = cfssllog.LevelDebug } - if err := c.CreateRootCA(); err != nil { + if err := cert.CreateRootCA(c.Config.Name, c.Dir.LabCARoot, c.Nodes); err != nil { return err } @@ -148,14 +150,13 @@ var deployCmd = &cobra.Command{ var wg sync.WaitGroup wg.Add(len(c.Nodes)) for _, node := range c.Nodes { - go func(node *types.NodeConfig) { + go func(node nodes.Node) { defer wg.Done() - err := c.ExecPostDeployTasks(ctx, node, linksMaxWorkers) + err := node.PostDeploy(ctx, c.Runtime, c.Nodes) if err != nil { - log.Errorf("failed to run postdeploy task for node %s: %v", node.ShortName, err) + log.Errorf("failed to run postdeploy task for node %s: %v", node.Config().ShortName, err) } }(node) - } wg.Wait() @@ -251,24 +252,24 @@ func hostsEntries(containers []types.GenericContainer, bridgeName string) []byte return buff.Bytes() } -func enrichNodes(containers []types.GenericContainer, nodes map[string]*types.NodeConfig, mgmtNet string) { +func enrichNodes(containers []types.GenericContainer, nodes map[string]nodes.Node, mgmtNet string) { for _, c := range containers { name = c.Labels["clab-node-name"] if node, ok := nodes[name]; ok { // add network information // skipping host networking nodes as they don't have separate addresses - if strings.ToLower(node.NetworkMode) == "host" { + if strings.ToLower(node.Config().NetworkMode) == "host" { continue } if c.NetworkSettings.Set { - node.MgmtIPv4Address = c.NetworkSettings.IPv4addr - node.MgmtIPv4PrefixLength = c.NetworkSettings.IPv4pLen - node.MgmtIPv6Address = c.NetworkSettings.IPv6addr - node.MgmtIPv6PrefixLength = c.NetworkSettings.IPv6pLen + node.Config().MgmtIPv4Address = c.NetworkSettings.IPv4addr + node.Config().MgmtIPv4PrefixLength = c.NetworkSettings.IPv4pLen + node.Config().MgmtIPv6Address = c.NetworkSettings.IPv6addr + node.Config().MgmtIPv6PrefixLength = c.NetworkSettings.IPv6pLen } - node.ContainerID = c.ID + node.Config().ContainerID = c.ID } } diff --git a/cmd/generate.go b/cmd/generate.go index e4b584c60..f3fb212ca 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -44,7 +44,7 @@ var errSyntax = errors.New("syntax error") var image []string var kind string -var nodes []string +var nodesFlag []string var license []string var nodePrefix string var groupPrefix string @@ -78,7 +78,7 @@ var generateCmd = &cobra.Command{ } log.Debugf("parsed images: %+v", images) - nodeDefs, err := parseNodesFlag(kind, nodes...) + nodeDefs, err := parseNodesFlag(kind, nodesFlag...) if err != nil { return err } @@ -121,7 +121,7 @@ func init() { generateCmd.Flags().IPNetVarP(&mgmtIPv6Subnet, "ipv6-subnet", "6", net.IPNet{}, "management network IPv6 subnet range") generateCmd.Flags().StringSliceVarP(&image, "image", "", []string{}, "container image name, can be prefixed with the node kind. =") generateCmd.Flags().StringVarP(&kind, "kind", "", "srl", fmt.Sprintf("container kind, one of %v", supportedKinds)) - generateCmd.Flags().StringSliceVarP(&nodes, "nodes", "", []string{}, "comma separated nodes definitions in format ::, each defining a Clos network stage") + generateCmd.Flags().StringSliceVarP(&nodesFlag, "nodes", "", []string{}, "comma separated nodes definitions in format ::, each defining a Clos network stage") generateCmd.Flags().StringSliceVarP(&license, "license", "", []string{}, "path to license file, can be prefix with the node kind. =/path/to/file") generateCmd.Flags().StringVarP(&nodePrefix, "node-prefix", "", defaultNodePrefix, "prefix used in node names") generateCmd.Flags().StringVarP(&groupPrefix, "group-prefix", "", defaultGroupPrefix, "prefix used in group names") diff --git a/cmd/graph.go b/cmd/graph.go index 9b35faf58..2996f920a 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -135,13 +135,13 @@ func buildGraphFromTopo(g *graphTopo, c *clab.CLab) { log.Info("building graph from topology file") for _, node := range c.Nodes { g.Nodes = append(g.Nodes, containerDetails{ - Name: node.ShortName, - Kind: node.Kind, - Image: node.Image, - Group: node.Group, + Name: node.Config().ShortName, + Kind: node.Config().Kind, + Image: node.Config().Image, + Group: node.Config().Group, State: "N/A", - IPv4Address: node.MgmtIPv4Address, - IPv6Address: node.MgmtIPv6Address, + IPv4Address: node.Config().MgmtIPv4Address, + IPv6Address: node.Config().MgmtIPv6Address, }) } @@ -157,9 +157,9 @@ func buildGraphFromDeployedLab(g *graphTopo, c *clab.CLab, containers []types.Ge if node, ok := c.Nodes[name]; ok { g.Nodes = append(g.Nodes, containerDetails{ Name: name, - Kind: node.Kind, + Kind: node.Config().Kind, Image: cont.Image, - Group: node.Group, + Group: node.Config().Group, State: fmt.Sprintf("%s/%s", cont.State, cont.Status), IPv4Address: getContainerIPv4(cont, c.Config.Mgmt.Network), IPv6Address: getContainerIPv6(cont, c.Config.Mgmt.Network), diff --git a/cmd/tools_cert.go b/cmd/tools_cert.go index 9244d118a..65e82ce94 100644 --- a/cmd/tools_cert.go +++ b/cmd/tools_cert.go @@ -12,6 +12,7 @@ import ( cfssllog "github.com/cloudflare/cfssl/log" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/srl-labs/containerlab/cert" "github.com/srl-labs/containerlab/clab" ) @@ -127,7 +128,7 @@ func createCA(cmd *cobra.Command, args []string) error { return err } - _, err = c.GenerateRootCa(csrTpl, clab.CaRootInput{ + _, err = cert.GenerateRootCa(c.Dir.LabCARoot, csrTpl, cert.CaRootInput{ CommonName: commonName, Country: country, Locality: locality, @@ -167,11 +168,11 @@ func signCert(cmd *cobra.Command, args []string) error { ` var err error - opts := []clab.ClabOption{ - clab.WithDebug(debug), - clab.WithTimeout(timeout), - } - c := clab.NewContainerLab(opts...) + // opts := []clab.ClabOption{ + // clab.WithDebug(debug), + // clab.WithTimeout(timeout), + // } + // c := clab.NewContainerLab(opts...) cfssllog.Level = cfssllog.LevelError if debug { @@ -192,7 +193,7 @@ func signCert(cmd *cobra.Command, args []string) error { return err } - _, err = c.GenerateCert(caCertPath, caKeyPath, csrTpl, clab.CertInput{ + _, err = cert.GenerateCert(caCertPath, caKeyPath, csrTpl, cert.CertInput{ Hosts: certHosts, CommonName: commonName, Country: country, diff --git a/cmd/tools_veth.go b/cmd/tools_veth.go index 3a40cb82e..a41fb30a6 100644 --- a/cmd/tools_veth.go +++ b/cmd/tools_veth.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/srl-labs/containerlab/clab" "github.com/srl-labs/containerlab/types" + "github.com/srl-labs/containerlab/utils" ) var AEnd = "" @@ -124,7 +125,7 @@ func parseVethEndpoint(s string) (*vethEndpoint, error) { ve.node = arr[0] ve.iface = arr[1] case 3: - if _, ok := clab.StringInSlice(supportedKinds, arr[0]); !ok { + if _, ok := utils.StringInSlice(supportedKinds, arr[0]); !ok { return nil, fmt.Errorf("node type %s is not supported, supported nodes are %q", arr[0], supportedKinds) } ve.kind = arr[0] diff --git a/go.sum b/go.sum index da3580ec3..6c33ea3a3 100644 --- a/go.sum +++ b/go.sum @@ -673,7 +673,9 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= diff --git a/types/types.go b/types/types.go index 34c77bdd6..c598f7b75 100644 --- a/types/types.go +++ b/types/types.go @@ -12,6 +12,7 @@ import ( "text/template" "github.com/cloudflare/cfssl/log" + "github.com/containernetworking/plugins/pkg/ns" "github.com/docker/go-connections/nat" "github.com/srl-labs/containerlab/utils" ) @@ -114,6 +115,27 @@ func (node *NodeConfig) GenerateConfig(dst, defaultTemplatePath string) error { return err } +func DisableTxOffload(n *NodeConfig) error { + // skip this if node runs in host mode + if strings.ToLower(n.NetworkMode) == "host" { + return nil + } + // disable tx checksum offload for linux containers on eth0 interfaces + nodeNS, err := ns.GetNS(n.NSPath) + if err != nil { + return err + } + err = nodeNS.Do(func(_ ns.NetNS) error { + // disabling offload on lo0 interface + err := utils.EthtoolTXOff("eth0") + if err != nil { + log.Infof("Failed to disable TX checksum offload for 'eth0' interface for Linux '%s' node: %v", n.ShortName, err) + } + return err + }) + return err +} + // Data struct storing generic container data type GenericContainer struct { Names []string diff --git a/utils/env.go b/utils/env.go index 2e7d64375..5cadbb7c3 100644 --- a/utils/env.go +++ b/utils/env.go @@ -33,3 +33,12 @@ func MergeStringMaps(m1, m2 map[string]string) map[string]string { } return m } + +func StringInSlice(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} diff --git a/utils/netlink.go b/utils/netlink.go index a421405c1..02d693168 100644 --- a/utils/netlink.go +++ b/utils/netlink.go @@ -5,9 +5,11 @@ package utils import ( + "crypto/rand" "fmt" "os" + log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" ) @@ -47,3 +49,21 @@ func DefaultNetMTU() (string, error) { } return fmt.Sprint(b.MTU), nil } + +// GenMac generates a random MAC address for a given OUI +func GenMac(oui string) string { + buf := make([]byte, 3) + _, _ = rand.Read(buf) + return fmt.Sprintf("%s:%02x:%02x:%02x", oui, buf[0], buf[1], buf[2]) +} + +// deleteNetnsSymlink deletes a network namespace and removes the symlink created by linkContainerNS func +func DeleteNetnsSymlink(n string) error { + log.Debug("Deleting netns symlink: ", n) + sl := fmt.Sprintf("/run/netns/%s", n) + err := os.Remove(sl) + if err != nil { + log.Debug("Failed to delete netns symlink by path:", sl) + } + return nil +}