diff --git a/.github/workflows/docs-test.yml b/.github/workflows/docs-test.yml new file mode 100644 index 000000000..ad486b63e --- /dev/null +++ b/.github/workflows/docs-test.yml @@ -0,0 +1,16 @@ +--- +name: Test +on: + push: + branches: + - "*" + - "!releases/**" + paths: + - "docs/**" + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: docker run -v $(pwd):/docs --entrypoint mkdocs squidfunk/mkdocs-material:6.1.5 build --clean --strict --site-dir /tmp/mkdocs-test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 461752b4b..fe576af07 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: docs on: push: branches: - - "docs-*" + - "docs-publish" tags: - "v*" @@ -11,4 +11,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: docker run -v $(pwd):/docs --entrypoint mkdocs squidfunk/mkdocs-material:6.1.0 gh-deploy --force --strict + - run: docker run -v $(pwd):/docs --entrypoint mkdocs squidfunk/mkdocs-material:6.1.5 gh-deploy --force --strict diff --git a/.github/workflows/manual-checks.yml b/.github/workflows/manual-checks.yml new file mode 100644 index 000000000..f0b663e4e --- /dev/null +++ b/.github/workflows/manual-checks.yml @@ -0,0 +1,18 @@ +name: manual code checks +on: + workflow_dispatch: + +jobs: + run-checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: 1.14 + - name: Staticcheck + run: | + go get -u honnef.co/go/tools/cmd/staticcheck + staticcheck ./... + env: + CGO_ENABLED: 0 diff --git a/.github/workflows/manual-pubdocs.yml b/.github/workflows/manual-pubdocs.yml new file mode 100644 index 000000000..ea016b808 --- /dev/null +++ b/.github/workflows/manual-pubdocs.yml @@ -0,0 +1,10 @@ +name: manual docs publish +on: + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: docker run -v $(pwd):/docs --entrypoint mkdocs squidfunk/mkdocs-material:6.1.5 gh-deploy --force --strict \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 977c721d1..8555f50e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,11 @@ on: branches: - "*" - "!releases/**" + paths-ignore: + - "docs/**" + - "lab-examples/**" + - "mkdocs.yml" + - "README.md" jobs: test: @@ -17,9 +22,3 @@ jobs: - run: go test -cover ./... env: CGO_ENABLED: 0 - -# enable staticcheck when we reach maturity -# - name: Staticcheck -# run: | -# go get -u honnef.co/go/tools/cmd/staticcheck -# staticcheck ./... diff --git a/.gitignore b/.gitignore index 816eb4024..f0220039e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,6 @@ dist # ignore the following files PRIVATE.md -graph graph/* containerlab* containerlab*/ diff --git a/.goreleaser.yml b/.goreleaser.yml index af524b00c..b8377a3c7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -25,6 +25,7 @@ nfpms: file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" package_name: containerlab maintainer: Wim Henderickx , Karim Radhouani , Roman Dodin + homepage: https://containerlab.srlinux.dev description: | containerlab written in go vendor: Nokia @@ -32,7 +33,9 @@ nfpms: formats: - rpm - deb - bindir: /usr/local/bin + bindir: /usr/bin files: ./lab-examples/**/*: /etc/containerlab/lab-examples ./templates/**/*: /etc/containerlab/templates + symlinks: + /usr/bin/clab: /usr/bin/containerlab diff --git a/README.md b/README.md index 237ab518f..0f7f8c8da 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,37 @@ ## Description -Containerlab provides a framework for setting up and destroying labs for networking containers. It builds a virtual wiring using veth pairs between the containers to provide virtual topologies. +Containerlab provides a framework for setting up networking labs with containers. It starts the containers and builds a virtual wiring between them to create lab topologies of users choice. + +![pic](https://gitlab.com/rdodin/pics/-/wikis/uploads/8244ceb188abd3831e3715c42d4fa38f/image.png) +Containerlab focuses on containerized Network Operating Systems which are typically used to test network features and designs, such as: + +* [Nokia SR-Linux](https://www.nokia.com/networks/products/service-router-linux-NOS/) +* [Arista cEOS](https://www.arista.com/en/products/software-controlled-container-networking) +* [SONiC](https://azure.github.io/SONiC/) +* [Juniper cRPD](https://www.juniper.net/documentation/en_US/crpd/topics/concept/understanding-crpd.html) + +But, of course, containerlab is perfectly capable of wiring up arbitrary containers which can host your network applications, virtual router or simply be a test client. + +

+ +

+ +## Features +* **IaaC approach** + Declarative way of defining the labs by means of the [topology definition files](https://containerlab.srlinux.dev/manual/topo-def-file/). +* **Network Operating Systems centric** + Focus on containerized Network Operating Systems. The sophisticated startup requirements of various NOS containers are abstracted with [kinds](https://containerlab.srlinux.dev/manual/kinds/) which allows the user to focus on the use cases, rather than infrastructure. +* **Simplicity and convenience are keys** + One-click [installation](https://containerlab.srlinux.dev/install/) and upgrade capabilities. +* **Fast** + Blazing fast way to create container based labs on any Debian or RHEL system. +* **Automated TLS certificates provisioning** + The nodes which require TLS certs will get them automatically on start. +* **Documentation is a first-class citizen** + We do not let our users guess by making a complete, concise and clean [documentation](https://containerlab.srlinux.dev). +* **Lab catalog** + The "most-wanted" lab topologies are [documented and included](https://containerlab.srlinux.dev/lab-examples/lab-examples/) with containerlab installation. Based on this cherry-picked selection you can start crafting the labs answering your needs. -The labs could also be wired to an external bridge to connect to external environment or to build hierarchical labs. - -A CA can be provided per lab and when enabled, containerlab generates certificates per device that can be used for various use cases, like GNMI, JSON RPC, etc. - -Lastly, containerlab also allows for a graphical output to validate the lab in a visual format using [graphviz](https://graphviz.org) - -Containerlab supports the following containers: - -* standard linux/alpine containers, typically used as test clients -* networking containers: - * Nokia SR-Linux - * Arista cEOS. Containerlab documentation is provided at https://containerlab.srlinux.dev. diff --git a/clab/clab.go b/clab/clab.go index 75943bae1..5af5e5801 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -5,7 +5,9 @@ import ( "sync" "time" + "github.com/docker/docker/api/types" docker "github.com/docker/docker/client" + log "github.com/sirupsen/logrus" ) // var debug bool @@ -19,8 +21,9 @@ type cLab struct { DockerClient *docker.Client Dir *cLabDirectory - debug bool - timeout time.Duration + debug bool + timeout time.Duration + gracefulShutdown bool } type cLabDirectory struct { @@ -30,23 +33,60 @@ type cLabDirectory struct { LabGraph string } +type ClabOption func(c *cLab) + +func WithDebug(d bool) ClabOption { + return func(c *cLab) { + c.debug = d + } +} + +func WithTimeout(dur time.Duration) ClabOption { + return func(c *cLab) { + c.timeout = dur + } +} + +func WithEnvDockerClient() ClabOption { + return func(c *cLab) { + var err error + c.DockerClient, err = docker.NewEnvClient() + if err != nil { + log.Fatalf("failed to create docker client: %v", err) + } + } +} + +func WithTopoFile(file string) ClabOption { + return func(c *cLab) { + if file == "" { + return + } + if err := c.GetTopology(file); err != nil { + log.Fatalf("failed to read topology file: %v", err) + } + } +} + +func WithGracefulShutdown(gracefulShutdown bool) ClabOption { + return func(c *cLab) { + c.gracefulShutdown = gracefulShutdown + } +} + // NewContainerLab function defines a new container lab -func NewContainerLab(d bool) *cLab { - return &cLab{ +func NewContainerLab(opts ...ClabOption) *cLab { + c := &cLab{ Config: new(Config), TopoFile: new(TopoFile), m: new(sync.RWMutex), Nodes: make(map[string]*Node), Links: make(map[int]*Link), - debug: d, } -} - -// Init creates a docker client and adds it to containerlab structure -func (c *cLab) Init(timeout time.Duration) (err error) { - c.DockerClient, err = docker.NewEnvClient() - c.timeout = timeout - return + for _, o := range opts { + o(c) + } + return c } func (c *cLab) CreateNode(ctx context.Context, node *Node, certs *certificates) error { @@ -60,3 +100,28 @@ 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{}) + if err != nil { + return err + } + } + return nil +} diff --git a/clab/config.go b/clab/config.go index b0c865257..ed974ae4b 100644 --- a/clab/config.go +++ b/clab/config.go @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" + "github.com/docker/go-connections/nat" + "github.com/mitchellh/go-homedir" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" ) @@ -23,6 +25,11 @@ const ( // 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", @@ -34,77 +41,79 @@ var srlTypes = map[string]string{ // mgmtNet struct defines the management network options // it is provided via docker network object type mgmtNet struct { - Network string // docker network name - Ipv4Subnet string `yaml:"ipv4_subnet"` - Ipv6Subnet string `yaml:"ipv6_subnet"` + Network string `yaml:"network,omitempty"` // docker network name + IPv4Subnet string `yaml:"ipv4_subnet,omitempty"` + IPv6Subnet string `yaml:"ipv6_subnet,omitempty"` } // NodeConfig represents a configuration a given node can have in the lab definition file type NodeConfig struct { - Kind string - Group string - Type string - Config string - Image string - License string - Position string - Cmd string + Kind string `yaml:"kind,omitempty"` + Group string `yaml:"group,omitempty"` + Type string `yaml:"type,omitempty"` + Config string `yaml:"config,omitempty"` + Image string `yaml:"image,omitempty"` + License string `yaml:"license,omitempty"` + Position string `yaml:"position,omitempty"` + Cmd string `yaml:"cmd,omitempty"` + Binds []string `yaml:"binds,omitempty"` // list of bind mount compatible strings + Ports []string `yaml:"ports,omitempty"` // list of port bindings } // Topology represents a lab topology type Topology struct { - Defaults NodeConfig - Kinds map[string]NodeConfig - Nodes map[string]NodeConfig - Links []link + Defaults NodeConfig `yaml:"defaults,omitempty"` + Kinds map[string]NodeConfig `yaml:"kinds,omitempty"` + Nodes map[string]NodeConfig `yaml:"nodes,omitempty"` + Links []LinkConfig `yaml:"links,omitempty"` } -type link struct { +type LinkConfig struct { Endpoints []string Labels map[string]string `yaml:"labels,omitempty"` } // Config defines lab configuration as it is provided in the YAML file type Config struct { - Name string - Mgmt mgmtNet - Topology Topology - ConfigPath string `yaml:"config_path"` -} - -type volume struct { - Source string - Destination string - ReadOnly bool + Name string `json:"name,omitempty"` + Mgmt mgmtNet `json:"mgmt,omitempty"` + Topology Topology `json:"topology,omitempty"` + ConfigPath string `yaml:"config_path,omitempty"` } // 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 - Mounts map[string]volume - Volumes map[string]struct{} - Binds []string - - 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 @@ -123,15 +132,16 @@ type Endpoint struct { // ParseIPInfo parses IP information func (c *cLab) parseIPInfo() error { - // DockerInfo = t.DockerInfo if c.Config.Mgmt.Network == "" { c.Config.Mgmt.Network = dockerNetName } - if c.Config.Mgmt.Ipv4Subnet == "" { - c.Config.Mgmt.Ipv4Subnet = dockerNetIPv4Addr - } - if c.Config.Mgmt.Ipv6Subnet == "" { - c.Config.Mgmt.Ipv6Subnet = dockerNetIPv6Addr + if c.Config.Mgmt.IPv4Subnet == "" && c.Config.Mgmt.IPv6Subnet == "" { + if c.Config.Mgmt.IPv4Subnet == "" { + c.Config.Mgmt.IPv4Subnet = dockerNetIPv4Addr + } + if c.Config.Mgmt.IPv6Subnet == "" { + c.Config.Mgmt.IPv6Subnet = dockerNetIPv6Addr + } } return nil } @@ -139,7 +149,7 @@ func (c *cLab) parseIPInfo() error { // ParseTopology parses the lab topology func (c *cLab) ParseTopology() error { log.Info("Parsing topology information ...") - log.Debugf("Prefix: %s", c.Config.Name) + log.Debugf("Lab name: %s", c.Config.Name) // initialize DockerInfo err := c.parseIPInfo() if err != nil { @@ -183,6 +193,30 @@ func (c *cLab) kindInitialization(nodeCfg *NodeConfig) string { return c.Config.Topology.Defaults.Kind } +func (c *cLab) bindsInit(nodeCfg *NodeConfig) []string { + switch { + case len(nodeCfg.Binds) != 0: + return nodeCfg.Binds + case len(c.Config.Topology.Kinds[nodeCfg.Kind].Binds) != 0: + return c.Config.Topology.Kinds[nodeCfg.Kind].Binds + case len(c.Config.Topology.Defaults.Binds) != 0: + return c.Config.Topology.Defaults.Binds + } + return nil +} + +// portsInit produces the nat.PortMap out of the slice of string representation of port bindings +func (c *cLab) portsInit(nodeCfg *NodeConfig) (nat.PortSet, nat.PortMap, error) { + if len(nodeCfg.Ports) != 0 { + ps, pb, err := nat.ParsePortSpecs(nodeCfg.Ports) + if err != nil { + return nil, nil, err + } + return ps, pb, nil + } + return nil, nil, nil +} + func (c *cLab) groupInitialization(nodeCfg *NodeConfig, kind string) string { if nodeCfg.Group != "" { return nodeCfg.Group @@ -203,31 +237,52 @@ func (c *cLab) configInitialization(nodeCfg *NodeConfig, kind string) string { if nodeCfg.Config != "" { return nodeCfg.Config } - return c.Config.Topology.Kinds[kind].Config + if kindConfig, ok := c.Config.Topology.Kinds[kind]; ok { + if kindConfig.Config != "" { + return kindConfig.Config + } + } + if c.Config.Topology.Defaults.Config != "" { + return c.Config.Topology.Defaults.Config + } + return defaultConfigTemplates[kind] } func (c *cLab) imageInitialization(nodeCfg *NodeConfig, kind string) string { if nodeCfg.Image != "" { return nodeCfg.Image } - return c.Config.Topology.Kinds[kind].Image + if c.Config.Topology.Kinds[kind].Image != "" { + return c.Config.Topology.Kinds[kind].Image + } + return c.Config.Topology.Defaults.Image } -func (c *cLab) licenseInitialization(nodeCfg *NodeConfig, kind string) string { - if nodeCfg.License != "" { - return nodeCfg.License +func (c *cLab) licenseInit(nodeCfg *NodeConfig, node *Node) (string, error) { + switch { + case nodeCfg.License != "": + return nodeCfg.License, nil + case c.Config.Topology.Kinds[node.Kind].License != "": + return c.Config.Topology.Kinds[node.Kind].License, nil + case c.Config.Topology.Defaults.License != "": + return c.Config.Topology.Defaults.License, nil + default: + return "", fmt.Errorf("no license found for node '%s' of kind '%s'", node.ShortName, node.Kind) } - return c.Config.Topology.Kinds[kind].License } -func (c *cLab) cmdInitialization(nodeCfg *NodeConfig, kind string, defCmd string) string { - if nodeCfg.Cmd != "" { +func (c *cLab) cmdInit(nodeCfg *NodeConfig, kind string) string { + switch { + case nodeCfg.Cmd != "": return nodeCfg.Cmd - } - if c.Config.Topology.Kinds[kind].Cmd != "" { + + case c.Config.Topology.Kinds[kind].Cmd != "": return c.Config.Topology.Kinds[kind].Cmd + + case c.Config.Topology.Defaults.Cmd != "": + return c.Config.Topology.Defaults.Cmd } - return defCmd + return "" } func (c *cLab) positionInitialization(nodeCfg *NodeConfig, kind string) string { @@ -253,29 +308,44 @@ func (c *cLab) NewNode(nodeName string, nodeCfg NodeConfig, idx int) error { // Kind initialization is either coming from `topology.nodes` section or from `topology.defaults` // normalize the data to lower case to compare node.Kind = strings.ToLower(c.kindInitialization(&nodeCfg)) + + // initialize bind mounts + binds := c.bindsInit(&nodeCfg) + err := resolveBindPaths(binds) + if err != nil { + return err + } + node.Binds = binds + + ps, pb, err := c.portsInit(&nodeCfg) + if err != nil { + return err + } + node.PortBindings = pb + node.PortSet = ps + switch node.Kind { 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" @@ -285,10 +355,25 @@ 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) - node.License = c.licenseInitialization(&nodeCfg, node.Kind) + + lp, err := c.licenseInit(&nodeCfg, node) + if err != nil { + return err + } + lp, err = resolvePath(lp) + if err != nil { + return err + } + + node.License = lp + node.Image = c.imageInitialization(&nodeCfg, node.Kind) node.Group = c.groupInitialization(&nodeCfg, node.Kind) node.NodeType = c.typeInitialization(&nodeCfg, node.Kind) @@ -317,49 +402,21 @@ 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" - node.Mounts = make(map[string]volume) - var v volume - v.Source = c.Dir.Lab + "/" + "license.key" - v.Destination = "/opt/srlinux/etc/license.key" - v.ReadOnly = true - log.Debug("License key: ", v.Source) - node.Mounts["license"] = v - - v.Source = node.LabDir + "/" + "config/" - v.Destination = "/etc/opt/srlinux/" - v.ReadOnly = false - log.Debug("Config: ", v.Source) - node.Mounts["config"] = v - - v.Source = node.LabDir + "/" + "srlinux.conf" - v.Destination = "/home/admin/.srlinux.conf" - v.ReadOnly = false - log.Debug("Env Config: ", v.Source) - node.Mounts["envConf"] = v - - v.Source = node.LabDir + "/" + "topology.yml" - v.Destination = "/tmp/topology.yml" - v.ReadOnly = true - log.Debug("Topology File: ", v.Source) - node.Mounts["topology"] = v - - node.Volumes = make(map[string]struct{}) - node.Volumes = map[string]struct{}{ - node.Mounts["license"].Destination: {}, - node.Mounts["config"].Destination: {}, - node.Mounts["envConf"].Destination: {}, - node.Mounts["topology"].Destination: {}, - } + // 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 + node.Binds = append(node.Binds, fmt.Sprint(filepath.Join(node.LabDir, "license.key"), ":/opt/srlinux/etc/license.key:ro")) - bindLicense := node.Mounts["license"].Source + ":" + node.Mounts["license"].Destination + ":" + "ro" - bindConfig := node.Mounts["config"].Source + ":" + node.Mounts["config"].Destination + ":" + "rw" - bindEnvConf := node.Mounts["envConf"].Source + ":" + node.Mounts["envConf"].Destination + ":" + "rw" - bindTopology := node.Mounts["topology"].Source + ":" + node.Mounts["topology"].Destination + ":" + "ro" + // mount config directory + cfgPath := filepath.Join(node.LabDir, "config") + node.Binds = append(node.Binds, fmt.Sprint(cfgPath, ":/etc/opt/srlinux/:rw")) - node.Binds = append(node.Binds, bindLicense) - node.Binds = append(node.Binds, bindConfig) - node.Binds = append(node.Binds, bindEnvConf) - node.Binds = append(node.Binds, bindTopology) + // mount srlinux.conf + srlconfPath := filepath.Join(node.LabDir, "srlinux.conf") + node.Binds = append(node.Binds, fmt.Sprint(srlconfPath, ":/home/admin/.srlinux.conf:rw")) + + // mount srlinux topology + topoPath := filepath.Join(node.LabDir, "topology.yml") + node.Binds = append(node.Binds, fmt.Sprint(topoPath, ":/tmp/topology.yml:ro")) case "alpine", "linux": node.Config = c.configInitialization(&nodeCfg, node.Kind) @@ -368,7 +425,7 @@ func (c *cLab) NewNode(nodeName string, nodeCfg NodeConfig, idx int) error { node.Group = c.groupInitialization(&nodeCfg, node.Kind) node.NodeType = c.typeInitialization(&nodeCfg, node.Kind) node.Position = c.positionInitialization(&nodeCfg, node.Kind) - node.Cmd = c.cmdInitialization(&nodeCfg, node.Kind, "/bin/sh") + node.Cmd = c.cmdInit(&nodeCfg, node.Kind) node.Sysctls = make(map[string]string) node.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0" @@ -385,7 +442,7 @@ func (c *cLab) NewNode(nodeName string, nodeCfg NodeConfig, idx int) error { } // NewLink initializes a new link object -func (c *cLab) NewLink(l link) *Link { +func (c *cLab) NewLink(l LinkConfig) *Link { // initialize a new link link := new(Link) link.Labels = l.Labels @@ -442,9 +499,44 @@ func (c *cLab) VerifyBridgesExist() error { for name, node := range c.Nodes { if node.Kind == "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) + return fmt.Errorf("bridge %s is referenced in the endpoints section but was not found in the default network namespace", name) } } } return nil } + +//resolvePath resolves a string path by expanding `~` to home dir or getting Abs path for the given path +func resolvePath(p string) (string, error) { + var err error + switch { + // resolve ~/ path + case p[0] == '~': + p, err = homedir.Expand(p) + if err != nil { + return "", err + } + default: + p, err = filepath.Abs(p) + if err != nil { + return "", err + } + } + return p, nil +} + +// resolveBindPaths resolves the host paths in a bind string, such as /hostpath:/remotepath(:options) string +// it allows host path to have `~` and returns absolute path for a relative path +func resolveBindPaths(binds []string) error { + for i := range binds { + // host path is a first element in a /hostpath:/remotepath(:options) string + elems := strings.Split(binds[i], ":") + hp, err := resolvePath(elems[0]) + if err != nil { + return err + } + elems[0] = hp + binds[i] = strings.Join(elems, ":") + } + return nil +} diff --git a/clab/config_test.go b/clab/config_test.go new file mode 100644 index 000000000..3727354f0 --- /dev/null +++ b/clab/config_test.go @@ -0,0 +1,104 @@ +package clab + +import ( + "reflect" + "strings" + "testing" +) + +func TestLicenseInit(t *testing.T) { + tests := map[string]struct { + got string + want string + }{ + "node_license": { + got: "test_data/topo1.yml", + want: "node1.lic", + }, + "kind_license": { + got: "test_data/topo2.yml", + want: "kind.lic", + }, + "default_license": { + got: "test_data/topo3.yml", + want: "default.lic", + }, + "kind_overwrite": { + got: "test_data/topo4.yml", + want: "node1.lic", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + opts := []ClabOption{ + WithTopoFile(tc.got), + } + c := NewContainerLab(opts...) + if err := c.ParseTopology(); err != nil { + t.Fatal(err) + } + + nodeCfg := c.Config.Topology.Nodes["node1"] + node := Node{} + node.Kind = strings.ToLower(c.kindInitialization(&nodeCfg)) + + lic, err := c.licenseInit(&nodeCfg, &node) + if err != nil { + t.Fatal(err) + } + if lic != tc.want { + t.Fatalf("wanted '%s' got '%s'", tc.want, lic) + } + }) + } +} + +func TestBindsInit(t *testing.T) { + tests := map[string]struct { + got string + want []string + }{ + "node_sing_bind": { + got: "test_data/topo1.yml", + want: []string{"/node/src:/dst"}, + }, + "node_many_binds": { + got: "test_data/topo2.yml", + want: []string{"/node/src1:/dst1", "/node/src2:/dst2"}, + }, + "kind_binds": { + got: "test_data/topo5.yml", + want: []string{"/kind/src:/dst"}, + }, + "default_binds": { + got: "test_data/topo3.yml", + want: []string{"/default/src:/dst"}, + }, + "node_binds_override": { + got: "test_data/topo4.yml", + want: []string{"/node/src:/dst"}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + opts := []ClabOption{ + WithTopoFile(tc.got), + } + c := NewContainerLab(opts...) + if err := c.ParseTopology(); err != nil { + t.Fatal(err) + } + + nodeCfg := c.Config.Topology.Nodes["node1"] + node := Node{} + node.Kind = strings.ToLower(c.kindInitialization(&nodeCfg)) + + binds := c.bindsInit(&nodeCfg) + if !reflect.DeepEqual(binds, tc.want) { + t.Fatalf("wanted %q got %q", tc.want, binds) + } + }) + } +} diff --git a/clab/docker.go b/clab/docker.go index 1187b5102..67917bfdb 100644 --- a/clab/docker.go +++ b/clab/docker.go @@ -10,7 +10,6 @@ import ( "path" "strconv" "strings" - "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -25,16 +24,21 @@ const sysctlBase = "/proc/sys" // CreateBridge creates a docker bridge func (c *cLab) CreateBridge(ctx context.Context) (err error) { log.Info("Creating docker bridge") + log.Debugf("Creating docker bridge: Name: %s, IPv4Subnet: '%s', IPv6Subnet: '%s", c.Config.Mgmt.Network, c.Config.Mgmt.IPv4Subnet, c.Config.Mgmt.IPv6Subnet) - ipamIPv4Config := network.IPAMConfig{ - Subnet: c.Config.Mgmt.Ipv4Subnet, + enableIPv6 := false + var ipamConfig []network.IPAMConfig + if c.Config.Mgmt.IPv4Subnet != "" { + ipamConfig = append(ipamConfig, network.IPAMConfig{ + Subnet: c.Config.Mgmt.IPv4Subnet, + }) } - ipamIPv6Config := network.IPAMConfig{ - Subnet: c.Config.Mgmt.Ipv6Subnet, + if c.Config.Mgmt.IPv6Subnet != "" { + ipamConfig = append(ipamConfig, network.IPAMConfig{ + Subnet: c.Config.Mgmt.IPv6Subnet, + }) + enableIPv6 = true } - var ipamConfig []network.IPAMConfig - ipamConfig = append(ipamConfig, ipamIPv4Config) - ipamConfig = append(ipamConfig, ipamIPv6Config) ipam := &network.IPAM{ Driver: "default", @@ -45,12 +49,15 @@ func (c *cLab) CreateBridge(ctx context.Context) (err error) { CheckDuplicate: true, Driver: "bridge", //Scope: "local", - EnableIPv6: true, + EnableIPv6: enableIPv6, IPAM: ipam, Internal: false, Attachable: false, //Ingress: false, //ConfigOnly: false, + Labels: map[string]string{ + "containerlab": "", + }, } var bridgeName string @@ -105,7 +112,7 @@ func (c *cLab) CreateBridge(ctx context.Context) (err error) { log.Debugf("Disabling TX checksum offloading for the %s bridge interface...", bridgeName) err = EthtoolTXOff(bridgeName) if err != nil { - return fmt.Errorf("Failed to disable TX checksum offloading for the %s bridge interface: %v", bridgeName, err) + return fmt.Errorf("failed to disable TX checksum offloading for the %s bridge interface: %v", bridgeName, err) } return nil } @@ -139,6 +146,11 @@ func (c *cLab) DeleteBridge(ctx context.Context) (err error) { func (c *cLab) CreateContainer(ctx context.Context, node *Node) (err error) { log.Infof("Create container: %s", node.ShortName) + err = c.PullImageIfRequired(ctx, node.Image) + if err != nil { + return err + } + nctx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() labels := map[string]string{ @@ -155,11 +167,6 @@ func (c *cLab) CreateContainer(ctx context.Context, node *Node) (err error) { labels["group"] = node.Group } - err = c.PullImageIfRequired(nctx, node.Image) - if err != nil { - return err - } - cont, err := c.DockerClient.ContainerCreate(nctx, &container.Config{ Image: node.Image, @@ -168,15 +175,16 @@ func (c *cLab) CreateContainer(ctx context.Context, node *Node) (err error) { AttachStdout: true, AttachStderr: true, Hostname: node.ShortName, - Volumes: node.Volumes, Tty: true, User: node.User, Labels: labels, + ExposedPorts: node.PortSet, }, &container.HostConfig{ - Binds: node.Binds, - Sysctls: node.Sysctls, - Privileged: true, - NetworkMode: container.NetworkMode(c.Config.Mgmt.Network), + Binds: node.Binds, + PortBindings: node.PortBindings, + Sysctls: node.Sysctls, + Privileged: true, + NetworkMode: container.NetworkMode(c.Config.Mgmt.Network), }, nil, node.LongName) if err != nil { return err @@ -188,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 { @@ -268,6 +277,7 @@ func (c *cLab) ListContainers(ctx context.Context, labels []string) ([]types.Con filter.Add("label", l) } return c.DockerClient.ContainerList(ctx, types.ContainerListOptions{ + All: true, Filters: filter, }) } @@ -322,12 +332,15 @@ func (c *cLab) Exec(ctx context.Context, id string, cmd []string) ([]byte, []byt } // DeleteContainer tries to stop a container then remove it -func (c *cLab) DeleteContainer(ctx context.Context, name string, timeout time.Duration) error { - force := false - err := c.DockerClient.ContainerStop(ctx, name, &timeout) - if err != nil { - log.Errorf("could not stop container '%s': %v", name, err) - force = true +func (c *cLab) DeleteContainer(ctx context.Context, name string) error { + var err error + force := !c.gracefulShutdown + if c.gracefulShutdown { + err = c.DockerClient.ContainerStop(ctx, name, &c.timeout) + if err != nil { + log.Errorf("could not stop container '%s': %v", name, err) + force = true + } } log.Infof("Removing container: %s", name) err = c.DockerClient.ContainerRemove(ctx, name, types.ContainerRemoveOptions{Force: force}) diff --git a/clab/file.go b/clab/file.go index a47d5f6a1..6876794b1 100644 --- a/clab/file.go +++ b/clab/file.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "strings" "text/template" @@ -56,7 +57,7 @@ func fileExists(filename string) bool { } // CopyFile copies a file from src to dst. If src and dst files exist, and are -// the same, then return success. Otherise, copy the file contents from src to dst. +// the same, then return success. Otherwise, copy the file contents from src to dst. func copyFile(src, dst string) (err error) { sfi, err := os.Stat(src) if err != nil { @@ -131,25 +132,30 @@ 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 + // copy license file to node specific directory in lab src = node.License - dst = path.Join(c.Dir.Lab, "license.key") + dst = path.Join(node.LabDir, "license.key") if err = 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) - // create node directory in lab - CreateDirectory(node.LabDir, 0777) // generate SRL topology file err = generateSRLTopologyFile(node.Topology, node.LabDir, node.Index) if err != nil { @@ -177,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: } @@ -189,7 +206,8 @@ func (c *cLab) CreateNodeDirStructure(node *Node) (err error) { // GenerateConfig generates configuration for the nodes func (node *Node) generateConfig(dst string) error { - tpl, err := template.New("srlconfig.tpl").ParseFiles("/etc/containerlab/templates/srl/srlconfig.tpl") + 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 } diff --git a/clab/graph.go b/clab/graph.go index 92ca2db6b..f7e282478 100644 --- a/clab/graph.go +++ b/clab/graph.go @@ -13,7 +13,7 @@ var g *gographviz.Graph // GenerateGraph generates a graph of the lab topology func (c *cLab) GenerateGraph(topo string) error { - log.Info("Generating lab graph ...") + log.Info("Generating lab graph...") g = gographviz.NewGraph() if err := g.SetName(c.TopoFile.name); err != nil { return err @@ -33,18 +33,18 @@ func (c *cLab) GenerateGraph(topo string) error { attr["label"] = nodeName attr["xlabel"] = node.Kind - attr["group"] = node.Group - - if strings.Contains(node.Group, "bb") { - attr["fillcolor"] = "blue" - attr["color"] = "blue" - attr["fontcolor"] = "white" - } else if strings.Contains(node.Kind, "srl") { - attr["fillcolor"] = "green" - attr["color"] = "green" - attr["fontcolor"] = "black" + if len(strings.TrimSpace(node.Group)) != 0 { + attr["group"] = node.Group + if strings.Contains(node.Group, "bb") { + attr["fillcolor"] = "blue" + attr["color"] = "blue" + attr["fontcolor"] = "white" + } else if strings.Contains(node.Kind, "srl") { + attr["fillcolor"] = "green" + attr["color"] = "green" + attr["fontcolor"] = "black" + } } - if err := g.AddNode(c.TopoFile.name, node.ShortName, attr); err != nil { return err } @@ -72,7 +72,7 @@ func (c *cLab) GenerateGraph(topo string) error { // create graph filename dotfile := c.Dir.LabGraph + "/" + c.TopoFile.name + ".dot" createFile(dotfile, g.String()) - log.Info("Created", dotfile, "!") + log.Infof("Created %s", dotfile) pngfile := c.Dir.LabGraph + "/" + c.TopoFile.name + ".png" @@ -81,7 +81,6 @@ func (c *cLab) GenerateGraph(topo string) error { generatePngFromDot(dotfile, pngfile) log.Info("Created ", pngfile) } - log.Info("Done generating lab graph!") return nil } diff --git a/clab/test_data/topo1.yml b/clab/test_data/topo1.yml new file mode 100644 index 000000000..b8ffd637e --- /dev/null +++ b/clab/test_data/topo1.yml @@ -0,0 +1,9 @@ +name: topo1 +topology: + nodes: + node1: + kind: srl + type: ixr6 + license: node1.lic + binds: + - /node/src:/dst diff --git a/clab/test_data/topo2.yml b/clab/test_data/topo2.yml new file mode 100644 index 000000000..4aaf63eb1 --- /dev/null +++ b/clab/test_data/topo2.yml @@ -0,0 +1,12 @@ +name: topo2 +topology: + kinds: + srl: + license: kind.lic + nodes: + node1: + kind: srl + type: ixr6 + binds: + - /node/src1:/dst1 + - /node/src2:/dst2 diff --git a/clab/test_data/topo3.yml b/clab/test_data/topo3.yml new file mode 100644 index 000000000..6f2979772 --- /dev/null +++ b/clab/test_data/topo3.yml @@ -0,0 +1,10 @@ +name: topo3 +topology: + defaults: + license: default.lic + binds: + - /default/src:/dst + nodes: + node1: + kind: srl + type: ixr6 diff --git a/clab/test_data/topo4.yml b/clab/test_data/topo4.yml new file mode 100644 index 000000000..056ac8956 --- /dev/null +++ b/clab/test_data/topo4.yml @@ -0,0 +1,18 @@ +name: topo4 +topology: + defaults: + license: default.lic + binds: + - /default/src:/dst + kinds: + srl: + license: kind.lic + binds: + - /kind/src:/dst + nodes: + node1: + kind: srl + type: ixr6 + license: node1.lic + binds: + - /node/src:/dst diff --git a/clab/test_data/topo5.yml b/clab/test_data/topo5.yml new file mode 100644 index 000000000..7c6e1ad26 --- /dev/null +++ b/clab/test_data/topo5.yml @@ -0,0 +1,11 @@ +name: topo5 +topology: + kinds: + srl: + binds: + - /kind/src:/dst + nodes: + node1: + kind: srl + type: ixr6 + license: node1.lic diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index 7c53f59c6..000000000 --- a/cmd/config.go +++ /dev/null @@ -1,49 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var infra bool -var workload bool - -// configCmd represents the config command -var configCmd = &cobra.Command{ - Use: "config", - Short: "generate and apply configuration to the lab", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("config called") - }, -} - -// generateCmd represents the generate command -var generateCmd = &cobra.Command{ - Use: "generate", - Short: "generate configuration for the lab", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("generate called") - }, -} - -// applyCmd represents the apply command -var applyCmd = &cobra.Command{ - Use: "apply", - Short: "apply the generated configuration on the lab nodes", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("apply called") - }, -} - -func init() { - rootCmd.AddCommand(configCmd) - configCmd.AddCommand(generateCmd) - configCmd.AddCommand(applyCmd) - // - generateCmd.Flags().BoolVarP(&infra, "infra", "", false, "generate infra config") - generateCmd.Flags().BoolVarP(&workload, "workload", "", false, "generate workloads config") - // - applyCmd.Flags().BoolVarP(&infra, "infra", "", false, "generate infra config") - applyCmd.Flags().BoolVarP(&workload, "workload", "", false, "generate workloads config") -} diff --git a/cmd/deploy.go b/cmd/deploy.go index dbe7ba339..f5f575e55 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -24,6 +24,12 @@ var mgmtNetName string var mgmtIPv4Subnet net.IPNet var mgmtIPv6Subnet net.IPNet +// reconfigure flag +var reconfigure bool + +// max-workers flag +var maxWorkers uint + // deployCmd represents the deploy command var deployCmd = &cobra.Command{ Use: "deploy", @@ -31,22 +37,27 @@ var deployCmd = &cobra.Command{ Aliases: []string{"dep"}, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - c := clab.NewContainerLab(debug) - err := c.Init(timeout) - if err != nil { - return err + opts := []clab.ClabOption{ + clab.WithDebug(debug), + clab.WithTimeout(timeout), + clab.WithTopoFile(topo), + clab.WithEnvDockerClient(), } + c := clab.NewContainerLab(opts...) - if err = c.GetTopology(topo); err != nil { - return err - } + var err error setFlags(c.Config) log.Debugf("lab Conf: %+v", c.Config) // Parse topology information if err = c.ParseTopology(); err != nil { return err } - + if reconfigure { + err = os.RemoveAll(c.Dir.Lab) + if err != nil { + return err + } + } if err = c.VerifyBridgesExist(); err != nil { return err } @@ -81,54 +92,118 @@ var deployCmd = &cobra.Command{ if err != nil { return fmt.Errorf("failed to parse certCsrTemplate: %v", err) } - // create directory structure and container per node + + numNodes := uint(len(c.Nodes)) + numLinks := uint(len(c.Links)) + nodesMaxWorkers := maxWorkers + linksMaxWorkers := maxWorkers + + if maxWorkers == 0 { + nodesMaxWorkers = numNodes + linksMaxWorkers = numLinks + } + + if nodesMaxWorkers > numNodes { + nodesMaxWorkers = numNodes + } + if linksMaxWorkers > numLinks { + linksMaxWorkers = numLinks + } + wg := new(sync.WaitGroup) - wg.Add(len(c.Nodes)) - for _, node := range c.Nodes { - go func(node *clab.Node) { + wg.Add(int(nodesMaxWorkers)) + nodesChan := make(chan *clab.Node) + // start workers + for i := uint(0); i < nodesMaxWorkers; i++ { + go func(i uint) { defer wg.Done() - if node.Kind == "bridge" { - return - } - // create CERT - nodeCerts, err := c.GenerateCert( - path.Join(c.Dir.LabCARoot, "root-ca.pem"), - path.Join(c.Dir.LabCARoot, "root-ca-key.pem"), - certTpl, - node, - ) - if err != nil { - log.Errorf("failed to generate certificates for node %s: %v", node.ShortName, err) + for { + select { + case node := <-nodesChan: + if node == nil { + log.Debugf("Worker %d terminating...", i) + return + } + log.Debugf("Worker %d received node: %+v", i, node) + if node.Kind == "bridge" { + return + } + // create CERT + nodeCerts, err := c.GenerateCert( + path.Join(c.Dir.LabCARoot, "root-ca.pem"), + path.Join(c.Dir.LabCARoot, "root-ca-key.pem"), + certTpl, + node, + ) + 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) + if err != nil { + log.Errorf("failed to create node %s: %v", node.ShortName, err) + } + case <-ctx.Done(): + return + } } - 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) - if err != nil { - log.Errorf("failed to create node %s: %v", node.ShortName, err) - } - }(node) + }(i) + } + for _, n := range c.Nodes { + nodesChan <- n } + // close channel to terminate the workers + close(nodesChan) + // wait for all workers to finish wg.Wait() + // cleanup hanging resources if a deployment failed before + log.Debug("cleaning up interfaces...") c.InitVirtualWiring() - + wg = new(sync.WaitGroup) + wg.Add(int(linksMaxWorkers)) + linksChan := make(chan *clab.Link) + log.Debug("creating links...") // wire the links between the nodes based on cabling plan + for i := uint(0); i < linksMaxWorkers; i++ { + go func(i uint) { + defer wg.Done() + for { + select { + case link := <-linksChan: + if link == nil { + log.Debugf("Worker %d terminating...", i) + return + } + log.Debugf("Worker %d received link: %+v", i, link) + if err = c.CreateVirtualWiring(link); err != nil { + log.Error(err) + } + case <-ctx.Done(): + return + } + } + }(i) + } for _, link := range c.Links { - if err = c.CreateVirtualWiring(link); err != nil { - log.Error(err) - } + linksChan <- link } + // close channel to terminate the workers + close(linksChan) + // wait for all workers to finish + wg.Wait() + // generate graph of the lab topology if graph { if err = c.GenerateGraph(topo); err != nil { log.Error(err) } } + log.Debug("containers created, retrieving state and IP addresses...") // show topology output - - // print table summary labels = append(labels, "containerlab=lab-"+c.Config.Name) containers, err := c.ListContainers(ctx, labels) if err != nil { @@ -137,11 +212,30 @@ 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 { log.Errorf("failed to create hosts file: %v", err) } + // print table summary printContainerInspect(containers, c.Config.Mgmt.Network, format) return nil }, @@ -150,23 +244,25 @@ var deployCmd = &cobra.Command{ func init() { rootCmd.AddCommand(deployCmd) deployCmd.Flags().BoolVarP(&graph, "graph", "g", false, "generate topology graph") - deployCmd.Flags().StringVarP(&mgmtNetName, "network", "n", "", "management network name") + deployCmd.Flags().StringVarP(&mgmtNetName, "network", "", "", "management network name") deployCmd.Flags().IPNetVarP(&mgmtIPv4Subnet, "ipv4-subnet", "4", net.IPNet{}, "management network IPv4 subnet range") deployCmd.Flags().IPNetVarP(&mgmtIPv6Subnet, "ipv6-subnet", "6", net.IPNet{}, "management network IPv6 subnet range") + deployCmd.Flags().BoolVarP(&reconfigure, "reconfigure", "", false, "regenerate configuration artifacts and overwrite the previous ones if any") + deployCmd.Flags().UintVarP(&maxWorkers, "max-workers", "", 0, "limit the maximum number of workers creating nodes and virtual wires") } func setFlags(conf *clab.Config) { - if prefix != "" { - conf.Name = prefix + if name != "" { + conf.Name = name } if mgmtNetName != "" { conf.Mgmt.Network = mgmtNetName } if mgmtIPv4Subnet.String() != "" { - conf.Mgmt.Ipv4Subnet = mgmtIPv4Subnet.String() + conf.Mgmt.IPv4Subnet = mgmtIPv4Subnet.String() } if mgmtIPv6Subnet.String() != "" { - conf.Mgmt.Ipv6Subnet = mgmtIPv6Subnet.String() + conf.Mgmt.IPv6Subnet = mgmtIPv6Subnet.String() } } @@ -220,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 + } + + } +} diff --git a/cmd/destroy.go b/cmd/destroy.go index e7068b3fd..a09209d90 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -8,7 +8,6 @@ import ( "os" "strings" "sync" - "time" "github.com/docker/docker/api/types" log "github.com/sirupsen/logrus" @@ -16,28 +15,38 @@ import ( "github.com/srl-wim/container-lab/clab" ) +var cleanup bool +var graceful bool + // destroyCmd represents the destroy command var destroyCmd = &cobra.Command{ Use: "destroy", Short: "destroy a lab", Aliases: []string{"des"}, RunE: func(cmd *cobra.Command, args []string) error { - c := clab.NewContainerLab(debug) - err := c.Init(timeout) - if err != nil { - return err + opts := []clab.ClabOption{ + clab.WithDebug(debug), + clab.WithTimeout(timeout), + clab.WithTopoFile(topo), + clab.WithEnvDockerClient(), + clab.WithGracefulShutdown(graceful), } + c := clab.NewContainerLab(opts...) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() - if err = c.GetTopology(topo); err != nil { - return err - } - + var err error // Parse topology information if err = c.ParseTopology(); err != nil { return err } + if cleanup { + err = os.RemoveAll(c.Dir.Lab) + if err != nil { + log.Errorf("error deleting lab directory: %v", err) + } + } containers, err := c.ListContainers(ctx, []string{fmt.Sprintf("containerlab=lab-%s", c.Config.Name)}) if err != nil { return fmt.Errorf("could not list containers: %v", err) @@ -54,7 +63,7 @@ var destroyCmd = &cobra.Command{ name = strings.TrimLeft(cont.Names[0], "/") } log.Infof("Stopping container: %s", name) - err = c.DeleteContainer(ctx, name, 30*time.Second) + err := c.DeleteContainer(ctx, name) if err != nil { log.Errorf("could not remove container '%s': %v", name, err) } @@ -81,6 +90,8 @@ var destroyCmd = &cobra.Command{ func init() { rootCmd.AddCommand(destroyCmd) + destroyCmd.Flags().BoolVarP(&cleanup, "cleanup", "", false, "delete lab directory") + destroyCmd.Flags().BoolVarP(&graceful, "graceful", "", false, "attempt to stop containers before removing") } func deleteEntriesFromHostsFile(containers []types.Container, bridgeName string) error { diff --git a/cmd/exec.go b/cmd/exec.go index 0e4d5f480..45a92bf22 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -18,8 +18,8 @@ var execCmd = &cobra.Command{ Short: "execute a command on one or multiple containers", Run: func(cmd *cobra.Command, args []string) { - if prefix == "" && topo == "" { - fmt.Println("provide either lab prefix (--prefix) or topology file path (--topo)") + if name == "" && topo == "" { + fmt.Println("provide either lab name (--name) or topology file path (--topo)") return } log.Debugf("raw command: %v", args) @@ -27,20 +27,20 @@ var execCmd = &cobra.Command{ fmt.Println("provide command to execute") return } - c := clab.NewContainerLab(debug) - err := c.Init(timeout) - if err != nil { - log.Fatal(err) + opts := []clab.ClabOption{ + clab.WithDebug(debug), + clab.WithTimeout(timeout), + clab.WithTopoFile(topo), + clab.WithEnvDockerClient(), } - if prefix == "" { - if err = c.GetTopology(topo); err != nil { - log.Fatal(err) - } - prefix = c.Config.Name + c := clab.NewContainerLab(opts...) + + if name == "" { + name = c.Config.Name } ctx, cancel := context.WithCancel(context.Background()) defer cancel() - labels = append(labels, "containerlab=lab-"+prefix) + labels = append(labels, "containerlab=lab-"+name) containers, err := c.ListContainers(ctx, labels) if err != nil { log.Fatalf("could not list containers: %v", err) @@ -54,6 +54,9 @@ var execCmd = &cobra.Command{ cmds = append(cmds, strings.Split(a, " ")...) } for _, cont := range containers { + if cont.State != "running" { + continue + } stdout, stderr, err := c.Exec(ctx, cont.ID, cmds) if err != nil { log.Errorf("%s: failed to execute cmd: %v", cont.Names, err) diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 000000000..edf53a866 --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,306 @@ +/* +Copyright © 2020 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "errors" + "fmt" + "net" + "os" + "strconv" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/srl-wim/container-lab/clab" + "gopkg.in/yaml.v2" +) + +var interfaceFormat = map[string]string{ + "srl": "e1-%d", + "ceos": "eth%d", + "linux": "eth$d", + "bridge": "veth%d", +} +var supportedKinds = []string{"srl", "ceos", "linux", "bridge", "sonic", "crpd"} + +const ( + defaultSRLType = "ixr6" + defaultNodePrefix = "node" + defaultGroupPrefix = "tier" +) + +var errDuplicatedValue = errors.New("duplicated value definition") +var errSyntax = errors.New("syntax error") + +var image []string +var kind string +var nodes []string +var license []string +var nodePrefix string +var groupPrefix string +var file string +var deploy bool + +type nodesDef struct { + numNodes uint + kind string + typ string +} + +// generateCmd represents the generate command +var generateCmd = &cobra.Command{ + Use: "generate", + Aliases: []string{"gen"}, + Short: "generate a Clos topology file, based on provided flags", + RunE: func(cmd *cobra.Command, args []string) error { + if name == "" { + return errors.New("provide a lab name with --name flag") + } + licenses, err := parseFlag(kind, license) + if err != nil { + return err + } + log.Debugf("parsed licenses: %+v", licenses) + + images, err := parseFlag(kind, image) + if err != nil { + return err + } + log.Debugf("parsed images: %+v", images) + + nodeDefs, err := parseNodesFlag(kind, nodes...) + if err != nil { + return err + } + log.Debugf("parsed nodes definitions: %+v", nodeDefs) + + b, err := generateTopologyConfig(name, mgmtNetName, mgmtIPv4Subnet.String(), mgmtIPv6Subnet.String(), images, licenses, nodeDefs...) + if err != nil { + return err + } + log.Debugf("generated topo: %s", string(b)) + if file != "" { + err = saveTopoFile(file, b) + if err != nil { + return err + } + } + if deploy { + reconfigure = true + if file == "" { + file = fmt.Sprintf("%s.yaml", name) + err = saveTopoFile(file, b) + if err != nil { + return err + } + } + topo = file + return deployCmd.RunE(deployCmd, nil) + } + if file == "" { + fmt.Println(string(b)) + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(generateCmd) + generateCmd.Flags().StringVarP(&mgmtNetName, "network", "", "", "management network name") + generateCmd.Flags().IPNetVarP(&mgmtIPv4Subnet, "ipv4-subnet", "4", net.IPNet{}, "management network IPv4 subnet range") + 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(&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") + generateCmd.Flags().StringVarP(&file, "file", "", "", "file path to save generated topology") + generateCmd.Flags().BoolVarP(&deploy, "deploy", "", false, "deploy a fabric based on the generated topology file") +} + +func generateTopologyConfig(name, network, ipv4range, ipv6range string, images map[string]string, licenses map[string]string, nodes ...nodesDef) ([]byte, error) { + numStages := len(nodes) + config := &clab.Config{ + Name: name, + Topology: clab.Topology{ + Kinds: make(map[string]clab.NodeConfig), + Nodes: make(map[string]clab.NodeConfig), + }, + } + config.Mgmt.Network = network + if ipv4range != "" { + config.Mgmt.IPv4Subnet = ipv4range + } + if ipv6range != "" { + config.Mgmt.IPv6Subnet = ipv6range + } + for k, img := range images { + config.Topology.Kinds[k] = clab.NodeConfig{Image: img} + } + for k, lic := range licenses { + if knd, ok := config.Topology.Kinds[k]; ok { + knd.License = lic + config.Topology.Kinds[k] = knd + continue + } + config.Topology.Kinds[k] = clab.NodeConfig{License: lic} + } + if numStages == 1 { + for j := uint(0); j < nodes[0].numNodes; j++ { + node1 := fmt.Sprintf("%s1-%d", nodePrefix, j+1) + if _, ok := config.Topology.Nodes[node1]; !ok { + config.Topology.Nodes[node1] = clab.NodeConfig{ + Group: fmt.Sprintf("%s-1", groupPrefix), + Kind: nodes[0].kind, + Type: nodes[0].typ, + } + } + } + } + for i := 0; i < numStages-1; i++ { + interfaceOffset := uint(0) + if i > 0 { + interfaceOffset = nodes[i-1].numNodes + } + for j := uint(0); j < nodes[i].numNodes; j++ { + node1 := fmt.Sprintf("%s%d-%d", nodePrefix, i+1, j+1) + if _, ok := config.Topology.Nodes[node1]; !ok { + config.Topology.Nodes[node1] = clab.NodeConfig{ + Group: fmt.Sprintf("%s-%d", groupPrefix, i+1), + Kind: nodes[i].kind, + Type: nodes[i].typ, + } + } + for k := uint(0); k < nodes[i+1].numNodes; k++ { + node2 := fmt.Sprintf("%s%d-%d", nodePrefix, i+2, k+1) + if _, ok := config.Topology.Nodes[node2]; !ok { + config.Topology.Nodes[node2] = clab.NodeConfig{ + Group: fmt.Sprintf("%s-%d", groupPrefix, i+2), + Kind: nodes[i+1].kind, + Type: nodes[i+1].typ, + } + } + config.Topology.Links = append(config.Topology.Links, clab.LinkConfig{ + Endpoints: []string{ + node1 + ":" + fmt.Sprintf(interfaceFormat[nodes[i].kind], k+1+interfaceOffset), + node2 + ":" + fmt.Sprintf(interfaceFormat[nodes[i+1].kind], j+1), + }, + }) + } + } + } + return yaml.Marshal(config) +} + +func parseFlag(kind string, ls []string) (map[string]string, error) { + result := make(map[string]string) + for _, l := range ls { + items := strings.SplitN(l, "=", 2) + switch len(items) { + case 0: + log.Errorf("missing value for flag item '%s'", l) + return nil, errSyntax + case 1: + if kind == "" { + log.Errorf("no kind specified for flag item '%s'", l) + return nil, errSyntax + } + if _, ok := result[kind]; !ok { + result[kind] = items[0] + } else { + log.Errorf("duplicated flag item for kind '%s'", kind) + return nil, errDuplicatedValue + } + case 2: + if _, ok := result[items[0]]; !ok { + result[items[0]] = items[1] + } else { + log.Errorf("duplicated flag item for kind '%s'", items[0]) + return nil, errDuplicatedValue + } + } + } + return result, nil +} + +func parseNodesFlag(kind string, nodes ...string) ([]nodesDef, error) { + numStages := len(nodes) + if numStages == 0 { + log.Error("no nodes specified using --nodes") + return nil, errSyntax + } + result := make([]nodesDef, numStages) + for idx, n := range nodes { + def := nodesDef{} + items := strings.SplitN(n, ":", 3) + if len(items) == 0 { + log.Errorf("wrong --nodes format '%s'", n) + return nil, errSyntax + } + i, err := strconv.Atoi(items[0]) + if err != nil { + log.Errorf("failed converting '%s' to an integer: %v", items[0], err) + return nil, errSyntax + } + def.numNodes = uint(i) + switch len(items) { + case 1: + if kind == "" { + log.Errorf("no kind specified for nodes '%s'", n) + return nil, errSyntax + } + def.kind = kind + if kind == "srl" { + def.typ = defaultSRLType + } + case 2: + switch items[1] { + case "ceos", "linux", "bridge", "sonic", "crpd": + def.kind = items[1] + case "srl": + def.kind = items[1] + def.typ = defaultSRLType + default: + // assume second item is a type if kind set using --kind + if kind == "" { + log.Errorf("no kind specified for nodes '%s'", n) + return nil, errSyntax + } + def.kind = kind + def.typ = items[1] + } + case 3: + // srl with #nodes, kind and type + def.numNodes = uint(i) + def.kind = items[1] + def.typ = items[2] + } + result[idx] = def + } + return result, nil +} + +func saveTopoFile(path string, data []byte) error { + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) + if err != nil { + return err + } + defer f.Close() + _, err = f.Write(data) + return err +} diff --git a/cmd/generate_test.go b/cmd/generate_test.go new file mode 100644 index 000000000..8216ba839 --- /dev/null +++ b/cmd/generate_test.go @@ -0,0 +1,201 @@ +package cmd + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +type flagInput struct { + kind string + lics []string +} +type flagOutput struct { + out map[string]string + err error +} + +type nodesInput struct { + kind string + nodes []string +} +type nodesOutput struct { + out []nodesDef + err error +} + +var flagTestSet = map[string]struct { + in flagInput + out flagOutput +}{ + "no_kind": { + in: flagInput{ + kind: "srl", + lics: []string{"/path/to/license.key"}, + }, + out: flagOutput{ + out: map[string]string{"srl": "/path/to/license.key"}, + err: nil, + }, + }, + "1_item_without_kind": { + in: flagInput{ + kind: "srl", + lics: []string{"/path1"}, + }, + out: flagOutput{ + out: map[string]string{"srl": "/path1"}, + err: nil, + }, + }, + "1_item_with_kind": { + in: flagInput{ + kind: "srl", + lics: []string{"ceos=/path/to/license.key"}, + }, + out: flagOutput{ + out: map[string]string{"ceos": "/path/to/license.key"}, + err: nil, + }, + }, + "2_items_with_kind": { + in: flagInput{ + kind: "dummy", + lics: []string{"srl=/path1", "ceos=/path2"}, + }, + out: flagOutput{ + out: map[string]string{"srl": "/path1", "ceos": "/path2"}, + err: nil, + }, + }, + "2_items_without_kind": { + in: flagInput{ + kind: "srl", + lics: []string{"/path1", "/path2"}, + }, + out: flagOutput{ + out: nil, + err: errDuplicatedValue, + }, + }, + "1_item_without_kind_1_item_with_kind": { + in: flagInput{ + kind: "srl", + lics: []string{"/path1", "ceos=/path2"}, + }, + out: flagOutput{ + out: map[string]string{"srl": "/path1", "ceos": "/path2"}, + err: nil, + }, + }, +} + +var nodesTestSet = map[string]struct { + in nodesInput + out nodesOutput +}{ + "no_kind_no_nodes": { + in: nodesInput{kind: "", nodes: nil}, + out: nodesOutput{out: nil, err: errSyntax}, + }, + "no_kind_with_nodes_1": { + in: nodesInput{kind: "", + nodes: []string{"1", "2", "3"}, + }, + out: nodesOutput{out: nil, err: errSyntax}, + }, + "no_kind_with_nodes_2": { + in: nodesInput{kind: "", + nodes: []string{"1:srl", "2", "3"}, + }, + out: nodesOutput{out: nil, err: errSyntax}, + }, + "no_kind_with_nodes_3": { + in: nodesInput{kind: "", + nodes: []string{"1:srl", "2:ceos", "3"}, + }, + out: nodesOutput{out: nil, err: errSyntax}, + }, + "kind_nodes_only_uints": { + in: nodesInput{ + kind: "srl", + nodes: []string{"1", "2", "3"}, + }, + out: nodesOutput{ + out: []nodesDef{ + {numNodes: 1, kind: "srl", typ: "ixr6"}, + {numNodes: 2, kind: "srl", typ: "ixr6"}, + {numNodes: 3, kind: "srl", typ: "ixr6"}, + }, + err: nil, + }, + }, + "kind_nodes_with_kind": { + in: nodesInput{ + kind: "srl", + nodes: []string{"1:linux", "2", "3:ceos"}, + }, + out: nodesOutput{ + out: []nodesDef{ + {numNodes: 1, kind: "linux", typ: ""}, + {numNodes: 2, kind: "srl", typ: "ixr6"}, + {numNodes: 3, kind: "ceos", typ: ""}, + }, + err: nil, + }, + }, + "kind_nodes_with_kind_and_type": { + in: nodesInput{ + kind: "srl", + nodes: []string{"1:ixrd", "2", "3:ceos"}, + }, + out: nodesOutput{ + out: []nodesDef{ + {numNodes: 1, kind: "srl", typ: "ixrd"}, + {numNodes: 2, kind: "srl", typ: "ixr6"}, + {numNodes: 3, kind: "ceos", typ: ""}, + }, + err: nil, + }, + }, + "single_stage": { + in: nodesInput{ + kind: "srl", + nodes: []string{"2"}, + }, + out: nodesOutput{ + out: []nodesDef{ + {numNodes: 2, kind: "srl", typ: "ixr6"}, + }, + err: nil, + }, + }, +} + +func TestParseFlag(t *testing.T) { + for name, set := range flagTestSet { + t.Run(name, func(t *testing.T) { + result, err := parseFlag(set.in.kind, set.in.lics) + if !cmp.Equal(result, set.out.out) { + t.Errorf("failed at '%s', expected %v, got %+v", name, set.out.out, result) + } + if err != set.out.err { + t.Errorf("failed at '%s', expected error %+v, got %+v", name, set.out.err, err) + } + }) + } +} + +func TestParseNodes(t *testing.T) { + for name, set := range nodesTestSet { + t.Run(name, func(t *testing.T) { + result, err := parseNodesFlag(set.in.kind, set.in.nodes...) + if !cmp.Equal(result, set.out.out, cmp.AllowUnexported(nodesDef{})) { + t.Errorf("failed at '%s', expected %+v, got %+v", name, set.out.out, result) + } + if err != set.out.err { + t.Errorf("failed at '%s', expected error %+v, got %+v", name, set.out.err, err) + } + }) + } +} diff --git a/cmd/graph.go b/cmd/graph.go index ddcdbd1e2..176fbff21 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -1,34 +1,132 @@ package cmd import ( + "context" + "encoding/json" + "fmt" + "html/template" + "net/http" + "sort" + "strings" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/srl-wim/container-lab/clab" ) +const ( + defaultTemplatePath = "/etc/containerlab/templates/graph/index.html" +) + +var srv string +var tmpl string + +type graphTopo struct { + Nodes []containerDetails `json:"nodes,omitempty"` + Links []link `json:"links,omitempty"` +} +type link struct { + Source string `json:"source,omitempty"` + SourceEndpoint string `json:"source_endpoint,omitempty"` + Target string `json:"target,omitempty"` + TargetEndpoint string `json:"target_endpoint,omitempty"` +} + +type topoData struct { + Name string + Data template.JS +} + // graphCmd represents the graph command var graphCmd = &cobra.Command{ Use: "graph", Short: "generate a topology graph", - Run: func(cmd *cobra.Command, args []string) { - c := clab.NewContainerLab(debug) - - if err := c.GetTopology(topo); err != nil { - log.Fatal(err) + RunE: func(cmd *cobra.Command, args []string) error { + opts := []clab.ClabOption{ + clab.WithDebug(debug), + clab.WithTimeout(timeout), + clab.WithTopoFile(topo), + clab.WithEnvDockerClient(), } + c := clab.NewContainerLab(opts...) // Parse topology information if err := c.ParseTopology(); err != nil { - log.Fatal(err) + return err + } + + if srv == "" { + if err := c.GenerateGraph(topo); err != nil { + return err + } + return nil + } + gtopo := graphTopo{ + Nodes: make([]containerDetails, 0, len(c.Nodes)), + Links: make([]link, 0, len(c.Links)), + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + containers, err := c.ListContainers(ctx, []string{fmt.Sprintf("containerlab=lab-%s", c.Config.Name)}) + if err != nil { + log.Errorf("could not list containers: %v", err) + } + log.Debugf("found %d containers", len(containers)) + for _, cont := range containers { + var name string + if len(cont.Names) > 0 { + name = strings.TrimPrefix(cont.Names[0], fmt.Sprintf("/clab-%s-", c.Config.Name)) + } + log.Debugf("looking for node name %s", name) + if node, ok := c.Nodes[name]; ok { + gtopo.Nodes = append(gtopo.Nodes, containerDetails{ + Name: name, + Kind: node.Kind, + Image: cont.Image, + Group: node.Group, + State: fmt.Sprintf("%s/%s", cont.State, cont.Status), + IPv4Address: getContainerIPv4(cont, c.Config.Mgmt.Network), + IPv6Address: getContainerIPv6(cont, c.Config.Mgmt.Network), + }) + } + } + sort.Slice(gtopo.Nodes, func(i, j int) bool { + return gtopo.Nodes[i].Name < gtopo.Nodes[j].Name + }) + for _, l := range c.Links { + gtopo.Links = append(gtopo.Links, link{ + Source: l.A.Node.ShortName, + SourceEndpoint: l.A.EndpointName, + Target: l.B.Node.ShortName, + TargetEndpoint: l.B.EndpointName, + }) + } + b, err := json.Marshal(gtopo) + if err != nil { + return err + } + log.Debugf("generating graph using data: %s", string(b)) + topoD := topoData{ + Name: c.Config.Name, + Data: template.JS(string(b)), } + tmpl := template.Must(template.ParseFiles(tmpl)) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + tmpl.Execute(w, topoD) + }) - if err := c.GenerateGraph(topo); err != nil { - log.Error(err) + log.Infof("Listening on %s...", srv) + err = http.ListenAndServe(srv, nil) + if err != nil { + return err } + return nil }, } func init() { rootCmd.AddCommand(graphCmd) + graphCmd.Flags().StringVarP(&srv, "srv", "s", "", "HTTP server address to view, customize and export your topology") + graphCmd.Flags().StringVarP(&tmpl, "template", "", defaultTemplatePath, "Go html template used to generate the graph") } diff --git a/cmd/inspect.go b/cmd/inspect.go index 6cc5221f1..f0849106b 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -17,9 +17,12 @@ import ( var format string var details bool +var all bool type containerDetails struct { + LabName string `json:"lab_name,omitempty"` Name string `json:"name,omitempty"` + ContainerID string `json:"container_id,omitempty"` Image string `json:"image,omitempty"` Kind string `json:"kind,omitempty"` Group string `json:"group,omitempty"` @@ -35,24 +38,27 @@ var inspectCmd = &cobra.Command{ Short: "inspect lab details", Run: func(cmd *cobra.Command, args []string) { - if prefix == "" && topo == "" { - fmt.Println("provide either a lab prefix (--prefix) or a topology file path (--topo)") + if name == "" && topo == "" && !all { + fmt.Println("provide either a lab name (--name) or a topology file path (--topo) or the flag --all") return } - c := clab.NewContainerLab(debug) - err := c.Init(timeout) - if err != nil { - log.Fatal(err) + opts := []clab.ClabOption{ + clab.WithDebug(debug), + clab.WithTimeout(timeout), + clab.WithTopoFile(topo), + clab.WithEnvDockerClient(), } - if prefix == "" { - if err = c.GetTopology(topo); err != nil { - log.Fatal(err) - } - prefix = c.Config.Name + c := clab.NewContainerLab(opts...) + if name == "" { + name = c.Config.Name } ctx, cancel := context.WithCancel(context.Background()) defer cancel() - labels = append(labels, "containerlab=lab-"+prefix) + if all { + labels = append(labels, "containerlab") + } else { + labels = append(labels, "containerlab=lab-"+name) + } containers, err := c.ListContainers(ctx, labels) if err != nil { log.Fatalf("could not list containers: %v", err) @@ -77,15 +83,19 @@ func init() { rootCmd.AddCommand(inspectCmd) inspectCmd.Flags().BoolVarP(&details, "details", "", false, "print all details of lab containers") - inspectCmd.Flags().StringVarP(&format, "format", "f", "", "lab name prefix") + inspectCmd.Flags().StringVarP(&format, "format", "f", "table", "output format. One of [table, json]") + inspectCmd.Flags().BoolVarP(&all, "all", "a", false, "show all deployed containerlab labs") } func toTableData(det []containerDetails) [][]string { tabData := make([][]string, 0, len(det)) - for _, d := range det { - tabData = append(tabData, []string{d.Name, d.Image, d.Kind, d.Group, d.State, d.IPv4Address, d.IPv6Address}) + for i, d := range det { + if all { + tabData = append(tabData, []string{fmt.Sprintf("%d", i+1), d.LabName, d.Name, d.ContainerID, d.Image, d.Kind, d.Group, d.State, d.IPv4Address, d.IPv6Address}) + continue + } + tabData = append(tabData, []string{fmt.Sprintf("%d", i+1), d.Name, d.ContainerID, d.Image, d.Kind, d.Group, d.State, d.IPv4Address, d.IPv6Address}) } - sort.Slice(tabData, func(i, j int) bool { return tabData[i][0] < tabData[j][0] }) return tabData } @@ -93,8 +103,14 @@ func printContainerInspect(containers []types.Container, bridgeName string, form contDetails := make([]containerDetails, 0, len(containers)) for _, cont := range containers { cdet := containerDetails{ - Image: cont.Image, - State: cont.State, + LabName: strings.TrimPrefix(cont.Labels["containerlab"], "lab-"), + Image: cont.Image, + State: cont.State, + IPv4Address: getContainerIPv4(cont, bridgeName), + IPv6Address: getContainerIPv6(cont, bridgeName), + } + if len(cont.ID) > 11 { + cdet.ContainerID = cont.ID[:12] } if len(cont.Names) > 0 { cdet.Name = strings.TrimLeft(cont.Names[0], "/") @@ -105,23 +121,14 @@ func printContainerInspect(containers []types.Container, bridgeName string, form if group, ok := cont.Labels["group"]; ok { cdet.Group = group } - if cont.NetworkSettings != nil { - if bridgeName != "" { - if br, ok := cont.NetworkSettings.Networks[bridgeName]; ok { - cdet.IPv4Address = fmt.Sprintf("%s/%d", br.IPAddress, br.IPPrefixLen) - cdet.IPv6Address = fmt.Sprintf("%s/%d", br.GlobalIPv6Address, br.GlobalIPv6PrefixLen) - } - } - if cdet.IPv4Address == "" && cdet.IPv6Address == "" { - for _, br := range cont.NetworkSettings.Networks { - cdet.IPv4Address = fmt.Sprintf("%s/%d", br.IPAddress, br.IPPrefixLen) - cdet.IPv6Address = fmt.Sprintf("%s/%d", br.GlobalIPv6Address, br.GlobalIPv6PrefixLen) - break - } - } - } contDetails = append(contDetails, cdet) } + sort.Slice(contDetails, func(i, j int) bool { + if contDetails[i].LabName == contDetails[j].LabName { + return contDetails[i].Name < contDetails[j].Name + } + return contDetails[i].LabName < contDetails[j].LabName + }) if format == "json" { b, err := json.MarshalIndent(contDetails, "", " ") if err != nil { @@ -132,17 +139,60 @@ func printContainerInspect(containers []types.Container, bridgeName string, form } tabData := toTableData(contDetails) table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{ + header := []string{ + "Lab Name", "Name", + "Container ID", "Image", "Kind", "Group", "State", "IPv4 Address", "IPv6 Address", - }) + } + if all { + table.SetHeader(append([]string{"#"}, header...)) + } else { + table.SetHeader(append([]string{"#"}, header[1:]...)) + } table.SetAutoFormatHeaders(false) table.SetAutoWrapText(false) table.AppendBulk(tabData) table.Render() } + +func getContainerIPv4(container types.Container, bridgeName string) string { + if container.NetworkSettings == nil { + return "" + } + if bridgeName != "" { + if br, ok := container.NetworkSettings.Networks[bridgeName]; ok { + return fmt.Sprintf("%s/%d", br.IPAddress, br.IPPrefixLen) + } + } + for _, br := range container.NetworkSettings.Networks { + return fmt.Sprintf("%s/%d", br.IPAddress, br.IPPrefixLen) + } + return "" +} + +func getContainerIPv6(container types.Container, bridgeName string) string { + if container.NetworkSettings == nil { + return "" + } + if bridgeName != "" { + if br, ok := container.NetworkSettings.Networks[bridgeName]; ok { + if br.GlobalIPv6Address == "" { + return "NA" + } + return fmt.Sprintf("%s/%d", br.GlobalIPv6Address, br.GlobalIPv6PrefixLen) + } + } + for _, br := range container.NetworkSettings.Networks { + if br.GlobalIPv6Address == "" { + return "NA" + } + return fmt.Sprintf("%s/%d", br.GlobalIPv6Address, br.GlobalIPv6PrefixLen) + } + return "" +} diff --git a/cmd/root.go b/cmd/root.go index befe09028..33dfe3f3e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,17 +21,23 @@ var timeout time.Duration var topo string var graph bool -// lab prefix -var prefix string +// lab name +var name string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "containerlab", Short: "deploy container based lab environments with a user-defined interconnections", PersistentPreRun: func(cmd *cobra.Command, args []string) { + id := os.Geteuid() + if id != 0 { + fmt.Println("containerlab requires sudo privileges to run!") + os.Exit(1) + } if debug { log.SetLevel(log.DebugLevel) } + }, } @@ -44,16 +50,11 @@ func Execute() { } func init() { - id := os.Geteuid() - if id != 0 { - fmt.Println("containerlab requires sudo privileges to run!") - os.Exit(1) - } rootCmd.SilenceUsage = true rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "enable debug mode") rootCmd.PersistentFlags().StringVarP(&topo, "topo", "t", "", "path to the file with topology information") - rootCmd.PersistentFlags().StringVarP(&prefix, "prefix", "p", "", "lab name prefix") + rootCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "lab name") rootCmd.PersistentFlags().DurationVarP(&timeout, "timeout", "", 30*time.Second, "timeout for docker requests, e.g: 30s, 1m, 2m30s") } diff --git a/cmd/save.go b/cmd/save.go index 0a5fa411d..40c58f6bb 100644 --- a/cmd/save.go +++ b/cmd/save.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "strings" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -13,26 +14,27 @@ import ( var saveCmd = &cobra.Command{ Use: "save", Short: "save containers configuration", + Long: `save performs a configuration save. The exact command that is used to save the config depends on the node kind. +Refer to the https://containerlab.srlinux.dev/cmd/save/ documentation to see the exact command used per node's kind`, Run: func(cmd *cobra.Command, args []string) { - if prefix == "" && topo == "" { - fmt.Println("provide either lab prefix (--prefix) or topology file path (--topo)") + if name == "" && topo == "" { + fmt.Println("provide either lab name (--name) or topology file path (--topo)") return } - c := clab.NewContainerLab(debug) - err := c.Init(timeout) - if err != nil { - log.Fatal(err) + opts := []clab.ClabOption{ + clab.WithDebug(debug), + clab.WithTimeout(timeout), + clab.WithTopoFile(topo), + clab.WithEnvDockerClient(), } - if prefix == "" { - if err = c.GetTopology(topo); err != nil { - log.Fatal(err) - } - prefix = c.Config.Name + c := clab.NewContainerLab(opts...) + if name == "" { + name = c.Config.Name } ctx, cancel := context.WithCancel(context.Background()) defer cancel() - containers, err := c.ListContainers(ctx, []string{"containerlab=lab-" + prefix, "kind=srl"}) + containers, err := c.ListContainers(ctx, []string{"containerlab=lab-" + name, "kind=srl"}) if err != nil { log.Fatalf("could not list containers: %v", err) } @@ -42,6 +44,9 @@ var saveCmd = &cobra.Command{ } var saveCmd []string for _, cont := range containers { + if cont.State != "running" { + continue + } log.Debugf("container: %+v", cont) if k, ok := cont.Labels["kind"]; ok { switch k { @@ -59,10 +64,10 @@ var saveCmd = &cobra.Command{ continue } if len(stdout) > 0 { - log.Infof("%s: stdout: %s", cont.Names, string(stdout)) + log.Infof("%s output: %s", strings.TrimLeft(cont.Names[0], "/"), string(stdout)) } if len(stderr) > 0 { - log.Infof("%s: stderr: %s", cont.Names, string(stderr)) + log.Infof("%s errors: %s", strings.TrimLeft(cont.Names[0], "/"), string(stderr)) } } }, diff --git a/docs/cmd/deploy.md b/docs/cmd/deploy.md new file mode 100644 index 000000000..33f53928b --- /dev/null +++ b/docs/cmd/deploy.md @@ -0,0 +1,42 @@ +# deploy command + +### Description + +The `deploy` command spins up a lab using the topology expressed via [topology definition file](../manual/topo-def-file.md). + +### Usage + +`containerlab [global-flags] deploy [local-flags]` + +**aliases:** `dep` + +### Flags + +#### topology + +With the global `--topo | -t` flag a user sets the path to the topology definition file that will be used to spin up a lab. + +#### name + +With the global `--name | -n` flag a user sets a lab name. This value will override the lab name value passed in the topology definition file. + +#### reconfigure + +The local `--reconfigure` flag instructs containerlab to remove the lab directory and all its content (if such directory exists) and start the deploy process after that. That will result in a clean deployment where every configuration artefact will be generated (TLS, node config) from scratch. + +Without this flag present, containerlab will reuse the available configuration artifacts found in the lab directory. + +Refer to the [configuration artifacts](../manual/conf-artifacts.md) page to get more information on the lab directory contents. + +#### max-workers +With `--max-workers` flag it is possible to limit the amout of concurrent workers that create containers or wire virtual links. By default the number of workers equals the number of nodes/links to create. + +### Examples + +```bash +# deploy a lab from mylab.yml file located in the same dir +containerlab deploy -t mylab.yml + +# deploy a lab from mylab.yml file and regenerate all configuration artifacts +containerlab deploy -t mylab.yml --reconfigure +``` diff --git a/docs/cmd/destroy.md b/docs/cmd/destroy.md new file mode 100644 index 000000000..0e7ed5663 --- /dev/null +++ b/docs/cmd/destroy.md @@ -0,0 +1,38 @@ +# destroy command + +### Description + +The `destroy` command destroys a lab referenced by its [topology definition file](../manual/topo-def-file.md). + +### Usage + +`containerlab [global-flags] destroy [local-flags]` + +**aliases:** `des` + +### Flags + +#### topology + +With the global `--topo | -t` flag a user sets the path to the topology definition file that will be used get the elements of a lab that will be destroyed. + +#### cleanup + +The local `--cleanup` flag instructs containerlab to remove the lab directory and all its content. + +Without this flag present, containerlab will keep the lab directory and all files inside of it. + +Refer to the [configuration artifacts](../manual/conf-artifacts.md) page to get more information on the lab directory contents. + +#### graceful +To make containerlab attempt a graceful shutdown of the running containers, add the `--graceful` flag to destroy cmd. Without it, containers will be removed forcefully without even attempting to stop them. + +### Examples + +```bash +# destroy a lab based on mylab.yml topology file located in the same dir +containerlab destroy -t mylab.yml + +# destroy a lab and also remove the Lab Directory +containerlab destroy -t mylab.yml --cleanup +``` \ No newline at end of file diff --git a/docs/cmd/generate.md b/docs/cmd/generate.md new file mode 100644 index 000000000..aebb7db28 --- /dev/null +++ b/docs/cmd/generate.md @@ -0,0 +1,106 @@ +# generate command + +### Description + +The `generate` command generates the topology definition file based on the user input provided via CLI flags. + +With this command it is possible to generate definition file for a CLOS fabric by just providing the number of nodes on each tier. The generated topology can be saved in a file or immediately scheduled for deployment. + +It is assumed, that the interconnection between the tiers is done in a full-mesh fashion. Such as tier1 nodes are fully meshed with tier2, tier2 is meshed with tier3 and so on. + +### Usage + +`containerlab [global-flags] generate [local-flags]` + +**aliases:** `gen` + +### Flags + +#### name + +With the global `--name | -n` flag a user sets the name of the lab that will be generated. + +#### nodes +The user configures the CLOS fabric topology by using the `--nodes` flag. The flag value is a comma separated list of CLOS tiers where each tier is defined by the number of nodes, its kind and type. Multiple `--node` flags can be specified. + +
+ + + +For example, the following flag value will define a 2-tier CLOS fabric with tier1 (leafs) consists of 4x SR Linux containers of IXR6 type and the 2x Arista cEOS spines: +``` +4:srl:ixr6,2:ceos +``` + +Note, that the default kind is `srl`, so you can omit the kind for SR Linux node. The same nodes value can be expressed like that: `4:ixr6,2:ceos` + +#### kind + +With `--kind` flag it is possible to set the default kind that will be set for the nodes which do not have a kind specified in the `--nodes` flag. + +For example the following value will generate a 3-tier CLOS fabric of cEOS nodes: + +```bash +# cEOS fabric +containerlab gen -n 3tier --kind ceos --nodes 4,2,1 + +# since SR Linux kind is assumed by default +# SRL fabric command is even shorter +containerlab gen -n 3tier --nodes 4,2,1 +``` + +#### image +Use `--image` flag to specify the container image that should be used by a given kind. + +The value of this flag follows the `kind=image` pattern. For example, to set the container image `ceos:4.21.F` for the `ceos` kind the flag will be: `--image ceos=ceos:4.21.F`. + +To set images for multiple kinds repeat the flag: `--image srl=srlinux:latest --image ceos=ceos:4.21.F` or use the comma separated form: `--image srl=srlinux:latest,ceos=ceos:latest` + +If the kind information is not provided in the `image` flag, the kind value will be taken from the `--kind` flag. + +#### license +With `--license` flag it is possible to set the license path that should be used by a given kind. + +The value of this flag follows the `kind=path` pattern. For example, to set the license path for the `srl` kind: `--license srl=/tmp/license.key`. + +To set license for multiple kinds repeat the flag: `--license =/path1 --image =/path2` or use the comma separated form: `--license =/path1,=/path2` + +#### deploy +When `--deploy` flag is present, the lab deployment process starts using the generated topology definition file. + +The generated definition file is first saved by the path set with `--file` or, if file path is not set, by the default path of `.yml`. Then the equivalent of the `deploy -t --reconfigure` command is executed. + +#### file +With `--file` flag its possible to save the generated topology definition in a file by a given path. + +#### node-prefix +With `--node-prefix` flag a user sets the name prefix of every node in a lab. + +Nodes will be named by the following template: `--`. So a node named `node1-3` means this is the third node in a first tier of a topology. + +Default prefix: `node`. + +#### group-prefix +With `--group-prefix` it is possible to change the Group value of a node. Group information is used in the topology graph rendering. + +#### network +With `--network` flag a user sets the name of the management network that will be created by container orchestration system such as docker. + +Default: `clab`. + +#### ipv4-subnet | ipv6-subnet +With `--ipv4-subnet` and `ipv6-subnet` its possible to change the address ranges of the management network. Nodes will receive IP addresses from these ranges if they are configured with DHCP. + +### Examples + +```bash +# generate and deploy a lab topology for 3-tier CLOS network +# with 8 leafs, 4 spines and 2 superspines +# all using Nokia SR Linux nodes with license and image provided. +# Note that `srl` kind in the image and license flags might be omitted, +# as it is implied by default) + +containerlab generate --name 3tier --image srl=srlinux:latest \ + --license srl=license.key \ + --nodes 8,4,2 --deploy +``` \ No newline at end of file diff --git a/docs/cmd/graph.md b/docs/cmd/graph.md new file mode 100644 index 000000000..a503f17ab --- /dev/null +++ b/docs/cmd/graph.md @@ -0,0 +1,99 @@ +# graph command + +### Description + +The `graph` command generates graphical representaitons of the deployed topology. + +Two graphing options are available: + +* an HTML page with embedded graphics generated by `containerlab` based on a Go HTML template +* a [graph description file in dot format](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) that can be rendered using [Graphviz](https://graphviz.org/) or viewed [online](https://dreampuf.github.io/GraphvizOnline/). + +#### HTML + +The HTML based graph is created by rendering a [Go HTML](https://golang.org/pkg/html/template/) template against a `struct` containing the topology name as well as a `json` string where 2 lists are present: `nodes` and `links` . + +`nodes` contains data about the lab nodes, such as name, kind, type, image, state, IP addresses,... + +`links` contains the list of links defined by source node and target node, as well as the endpoint names + +```json +{ + "nodes": [ + { + "name": "node1-1", + "image": "srlinux:20.6.1-286", + "kind": "srl", + "group": "tier-1", + "state": "running/Up 21 seconds", + "ipv4_address": "172.23.23.3/24", + "ipv6_address": "2001:172:23:23::3/80" + }, + // omitted rest of nodes + ], + "links": [ + { + "source": "node1-2", + "source_endpoint": "e1-1", + "target": "node2-1", + "target_endpoint": "e1-2" + }, + { + "source": "node2-1", + "source_endpoint": "e1-4", + "target": "node3-1", + "target_endpoint": "e1-1" + }, + // omitted rest of links + ] +} +``` + +Within the template, Javascript libraries such as [**d3js directed force graph**](https://observablehq.com/collection/@d3/d3-force) or [**vis.js network**](https://visjs.github.io/vis-network/docs/network/) can be used to generate custom topology graphs. + +`containerlab` comes with a (minimalistic) default template using [d3js](https://github.com/d3/d3-force). + +After the graph generation, it's possible to move the nodes to a desired position and export the graph in PNG format. + +![default_graph](https://gitlab.com/rdodin/pics/-/wikis/uploads/5f3ade3559a5f044d4786bfd0e278b65/image.png) + +#### Graphviz + +When `graph` command is called without the `--srv` flag, containerlab will generate a [graph description file in dot format](https://en.wikipedia.org/wiki/DOT_(graph_description_language)). + +The dot file can be used to view the graphical representation of the topology either by rendering the dot file into a PNG file or using [online dot viewer](https://dreampuf.github.io/GraphvizOnline/). + +### Usage + +`containerlab [global-flags] graph [local-flags]` + +### Flags + +#### name + +With the global `--name | -n` flag a user sets the name of the lab that will be graphed. + +#### srv + +The `--srv` flag enables the HTML graph representation if present, it specifies an address a HTTP server will listen to. + +A single path `/` is served, where the graph is generated based on either a default template or on the template supplied using `--template`. + +#### template + +The `--template` flag allows to customize the HTML based graph by supplying a user defined template that will be rendered and exposed on the address specified by `--srv`. + + +### Examples + +```bash + +# generate a graph description file in dot format for topo1 topology +containerlab graph --topo /path/to/topo1.yaml + +# start an http server on :3002 where topo1 graph will be rendered using the default template +containerlab graph --topo /path/to/topo1.yaml --srv ":3002" + +# start an http server on :3002 where topo1 graph will be rendered using a custom template my_template.html +containerlab graph --topo /path/to/topo1.yaml --srv ":3002" --template my_template.html +``` diff --git a/docs/cmd/inspect.md b/docs/cmd/inspect.md new file mode 100644 index 000000000..5ed0fc9fb --- /dev/null +++ b/docs/cmd/inspect.md @@ -0,0 +1,78 @@ +# inspect command + +### Description + +The `inspect` command provides the information about the deployed labs. + +### Usage + +`containerlab [global-flags] inspect [local-flags]` + +### Flags + +#### all +With the local `--all` flag its possible to list all deployed labs in a single table. + +#### topology | name + +With the global `--topo | -t` or `--name | -n` flag a user specifies which particular lab they want to get the information about. + +#### format + +The local `--format` flag enables different output stylings. By default the table view will be used. + +Currently, the only other format option is `json` that will produce the output in the JSON format. + +#### details +The `inspect` command produces a brief summary about the running lab components. It is also possible to get a full view on the running containers by adding `--details` flag. + +With this flag inspect command will output every bit of information about the running containers. This is what `docker inspect` command provides. + +### Examples + +```bash +# list all running labs on the host +containerlab inspect --all ++---+-----------+---------------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| # | Lab Name | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | ++---+-----------+---------------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| 1 | srl01 | clab-srl01-srl | 37156faa5444 | srlinux | srl | | running | 172.20.20.2/24 | 2001:172:20:20::2/80 | +| 2 | srlceos01 | clab-srlceos01-ceos | 90bebb1e2c5f | ceos | ceos | | running | 172.20.20.4/24 | 2001:172:20:20::4/80 | +| 3 | srlceos01 | clab-srlceos01-srl | 82e9aa3c7e6b | srlinux | srl | | running | 172.20.20.3/24 | 2001:172:20:20::3/80 | ++---+-----------+---------------------+--------------+---------+------+-------+---------+----------------+----------------------+ + +# provide information about the running lab named srl02 +containerlab inspect --name srlceos01 ++---+---------------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | ++---+---------------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| 1 | clab-srlceos01-ceos | 90bebb1e2c5f | ceos | ceos | | running | 172.20.20.4/24 | 2001:172:20:20::4/80 | +| 2 | clab-srlceos01-srl | 82e9aa3c7e6b | srlinux | srl | | running | 172.20.20.3/24 | 2001:172:20:20::3/80 | ++---+---------------------+--------------+---------+------+-------+---------+----------------+----------------------+ + + +# now in json format +containerlab inspect --name srlceos01 -f json +[ + { + "lab_name": "srlceos01", + "name": "clab-srlceos01-srl", + "container_id": "82e9aa3c7e6b", + "image": "srlinux", + "kind": "srl", + "state": "running", + "ipv4_address": "172.20.20.3/24", + "ipv6_address": "2001:172:20:20::3/80" + }, + { + "lab_name": "srlceos01", + "name": "clab-srlceos01-ceos", + "container_id": "90bebb1e2c5f", + "image": "ceos", + "kind": "ceos", + "state": "running", + "ipv4_address": "172.20.20.4/24", + "ipv6_address": "2001:172:20:20::4/80" + } +] +``` \ No newline at end of file diff --git a/docs/cmd/save.md b/docs/cmd/save.md new file mode 100644 index 000000000..4e16ad83c --- /dev/null +++ b/docs/cmd/save.md @@ -0,0 +1,34 @@ +# save command + +### Description + +The `save` command perform configuration save for all the containers running in a lab. + +The exact command that performs configuration save depends on a given kind. The below table explains the method used for each kind: + +| Kind | Command | Notes | +| ------------------ | ---------------------------------------------------------- | ------------------------------------------- | +| **Nokia SR Linux** | `sr_cli -d tools system configuration generate-checkpoint` | configuration is saved in a checkpoint file | +| **Arista cEOS** | not yet implemented | | + +### Usage + +`containerlab [global-flags] save [local-flags]` + +### Flags + +#### topology | name + +With the global `--topo | -t` or `--name | -n` flag a user specifies from which lab to take the containers and perform the save configuration task. + +### Examples + +```bash +# save the configuration of the containers running in lab named srl02 +❯ container-lab save -n srl02 +INFO[0001] clab-srl02-srl1: stdout: /system: + Generated checkpoint '/etc/opt/srlinux/checkpoint/checkpoint-0.json' with name 'checkpoint-2020-11-18T09:00:54.998Z' and comment '' + +INFO[0002] clab-srl02-srl2: stdout: /system: + Generated checkpoint '/etc/opt/srlinux/checkpoint/checkpoint-0.json' with name 'checkpoint-2020-11-18T09:00:56.444Z' and comment '' +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index bb38658ae..62ce7311e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,22 +5,39 @@ --- -Containerlab provides a framework for setting up networking labs with containers. It builds a virtual wiring between the containers to create lab topologies of users choice. +With the growing number of containerized Network Operating Systems grows the demand to easily run them in the user-defined, versatile lab topologies. +A distinctive requirement of a container-based lab is the need for point-to-point interfaces interconnecting the elements. + +Unfortunately, container orchestration tools like docker/podman/etc are not a good fit for that purpose, as they do not allow a user to easily create such p2p connections between the containers. + +Containerlab provides a framework for setting up networking labs with containers. It starts the containers and builds a virtual wiring between them to create lab topologies of users choice.
-Containerlab focuses on _networking_ containers which are typically used to test network features and designs, such as: +Containerlab focuses on containerized Network Operating Systems which are typically used to test network features and designs, such as: * [Nokia SR-Linux](https://www.nokia.com/networks/products/service-router-linux-NOS/) * [Arista cEOS](https://www.arista.com/en/products/software-controlled-container-networking) - -But, of course, containerlab is perfectly capable of wiring up any typical linux container which can be used as the test clients. -
- -### Features -* Blazing fast way to create container based labs on any Debian or RHEL system. -* Labs can be attached to a Linux bridge to connect to an external environment or to build hierarchical labs. -* TLS certificates can be generated automatically for every node of a lab. -* The lab topology can be graphically visualized with [graphviz](https://graphviz.org) tool (WIP). - - \ No newline at end of file +* [SONiC](https://azure.github.io/SONiC/) +* [Juniper cRPD](https://www.juniper.net/documentation/en_US/crpd/topics/concept/understanding-crpd.html) + +But, of course, containerlab is perfectly capable of wiring up arbitrary containers which can host your network applications, virtual router or simply be a test client. +
+ +## Features +* **IaaC approach** + Declarative way of defining the labs by means of the [topology definition files](manual/topo-def-file.md). +* **Network Operating Systems centric** + Focus on containerized Network Operating Systems. The sophisticated startup requirements of various NOS containers are abstracted with [kinds](manual/kinds/kinds.md) which allows the user to focus on the use cases, rather than infrastructure. +* **Simplicity and convenience are keys** + One-click [installation](install.md) and upgrade capabilities. +* **Fast** + Blazing fast way to create container based labs on any Debian or RHEL system. +* **Automated TLS certificates provisioning** + The nodes which require TLS certs will get them automatically on start. +* **Documentation is a first-class citizen** + We do not let our users guess by making a complete, concise and clean [documentation](https://containerlab.srlinux.dev). +* **Lab catalog** + The "most-wanted" lab topologies are [documented and included](lab-examples/lab-examples.md) with containerlab installation. Based on this cherry-picked selection you can start crafting the labs answering your needs. + + diff --git a/docs/install.md b/docs/install.md index 0cf0b4637..4e40b7297 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,26 +1,36 @@ -Containerlab is distributed as a Linux deb/rpm package and can be installed on any Debian- or RHEL-like distributive. +Containerlab is distributed as a Linux deb/rpm package and can be installed on any Debian- or RHEL-like distributive in a matter of a few seconds. ### Pre-requisites -* Must be run as a root user or with `sudo`: `containerlab` sets some parameters in the linux kernel to support the various options the containers need -* [Install docker](https://docs.docker.com/engine/install/): this is used to manage the containers -* Import or Pull container images (e.g. Nokia SR-Linux, Arista cEOS) which are not available downloadable from a container registry. Containerlab will try to pull images if they do not exist at run time. +The following requirements must be satisfied in order to let containerlab tool run successfully: + +* A user should have `sudo` privileges to run containerlab. +* [Docker](https://docs.docker.com/engine/install/) must be installed. +* Import container images (e.g. Nokia SR Linux, Arista cEOS) which are not downloadable from a container registry. Containerlab will try to pull images at runtime if they do not exist locally. ### Package installation Containerlab package can be installed using the [installation script](https://github.com/srl-wim/container-lab/blob/master/get.sh) which detects the operating system type and installs the relevant package: !!! note - Continarelab is distributed via deb/rpm packages, thus only Debian- and RHEL-like distributives are supported. + Containerlab is distributed via deb/rpm packages, thus only Debian- and RHEL-like distributives are supported. ```bash # download and install the latest release -sudo curl -sL https://github.com/srl-wim/container-lab/raw/master/get.sh | \ -sudo bash +sudo curl -sL https://get-clab.srlinux.dev | sudo bash # download a specific version - 0.6.0 -sudo curl -sL https://github.com/srl-wim/container-lab/raw/master/get.sh | \ -sudo bash -s -- -v 0.6.0 +sudo curl -sL https://get-clab.srlinux.dev | sudo bash -s -- -v 0.6.0 ``` +!!!note "Manual installation" + If the usage of piped bash scripts is discouraged or restricted, the users can manually download the package from the [Github releases](https://github.com/srl-wim/container-lab/releases) page. + + example: + ``` + yum install https://github.com/srl-wim/container-lab/releases/download/v0.7.0/containerlab_0.7.0_linux_386.rpm + ``` + +The package installer will put the `containerlab` binary in the `/usr/bin` directory as well as create the `/usr/bin/clab -> /usr/bin/containerlab` symlink. The symlink allows the users to save on typing when they use containerlab: `clab `. + ### Upgrade To upgrade `containerlab` to the latest available version issue the following command: @@ -29,17 +39,3 @@ containerlab version upgrade ``` This command will fetch the installation script and will upgrade the tool to its most recent version. - -### Graphviz -Containerlab's `graph` command can render a topology graph. For the generation of PNG images out of the topology files `graphviz` tool needs to be installed. - -If you don't want to install graphviz, just create the .dot file and use an [online graphviz tool](https://dreampuf.github.io/GraphvizOnline). -```bash -# Debian / Ubuntu -sudo apt-get install graphviz - -# CentOS / Fedora / RedHat -sudo yum install graphviz -``` - -Note, that `graphviz` installation is optional and is only required when a user wants to generate PNG files on the system out of the generated `dot` files. diff --git a/docs/lab-examples/ext-bridge.md b/docs/lab-examples/ext-bridge.md index 3353bf79b..f205e2619 100644 --- a/docs/lab-examples/ext-bridge.md +++ b/docs/lab-examples/ext-bridge.md @@ -4,7 +4,7 @@ | **Components** | [Nokia SR Linux][srl] | | **Resource requirements**[^1] | :fontawesome-solid-microchip: 1
:fontawesome-solid-memory: 2 GB | | **Topology file** | [br01.yml][topofile] | -| **Prefix** | br01 | +| **Name** | br01 | ## Description This lab consists of three Nokia SR Linux nodes connected to a linux bridge. diff --git a/docs/lab-examples/lab-examples.md b/docs/lab-examples/lab-examples.md index 2c05879bb..2b10a2abb 100644 --- a/docs/lab-examples/lab-examples.md +++ b/docs/lab-examples/lab-examples.md @@ -1,15 +1,49 @@ +# About lab examples
-`containerlab` aims to provide a simple, intuitive and yet customizable way to run container based labs. To help our users to get from an "I have a bare VM" point to a running and functional lab we ship some essential lab topologies inside the `containerlab` package. +`containerlab` aims to provide a simple, intuitive and yet customizable way to run container based labs. To help our users to have a running and functional lab as quickly as possible, we ship some essential lab topologies within the `containerlab` package. -These lab examples are meant to be used as-is or as a base layer to a more customized or elaborated lab scenario. Once `containerlab` is installed, you will find the lab examples directories by the `/etc/containerlab/lab-examples` path. Copy those directories over to your working directory to start using the provided labs. +These lab examples are meant to be used as-is or as a base layer to a more customized or elaborated lab scenarios. Once `containerlab` is installed, you will find the lab examples directories by the `/etc/containerlab/lab-examples` path. Copy those directories over to your working directory to start using the provided labs. !!!note "Container images versions" The provided lab examples use the images without a tag, i.e. `image: srlinux`. This means that the image with a `latest` tag must exist. A user needs to tag the image themselves if the `latest` tag is missing. For example: `docker tag srlinux:20.6.1-286 srlinux:latest` -The source code of the lab examples is contained within the [containerlab repo](https://github.com/srl-wim/container-lab/tree/master/lab-examples); any questions or issues regarding the provided examples can be addressed via [Github issues](https://github.com/srl-wim/container-lab/issues). +The source code of the lab examples is contained within the [containerlab repo](https://github.com/srl-wim/container-lab/tree/master/lab-examples); any questions, issues or contributions related to the provided examples can be addressed via [Github issues](https://github.com/srl-wim/container-lab/issues). -Each lab comes with a definitive description that can be found in this documentation section. \ No newline at end of file +Each lab comes with a definitive description that can be found in this documentation section. + +## How to deploy a lab from the lab catalog? +Running the labs from the catalog is easy. + +#### Copy lab catalog +First, you need to copy the lab catalog to your working directory, to ensure that the changes you might make to the lab files will not be overwritten once you upgrade containerlab. To copy the entire catalog into your working directory: + +```bash +# copy over the srl02 lab files +cp -a /etc/containerlab/lab-examples/* . +``` + +as a result of this command you will get several dire + +#### Get the lab name +Every lab in the catalog has a unique short name. For example [this lab](two-srls.md) states in the summary table its name is `srl02`. You will find a folder matching this name in your working directory, change into it: +```bash +cd srl02 +``` + +#### Check images and licenses +Within the lab directory you will find the files that are used in the lab. Usually its only the [topology definition files](../manual/topo-def-file.md) and, sometimes, config files. + +If you check the topology file you will see if any license files are required and what images are specified for each node/kind. + +Either change the topology file to point to the right image/license or change the image/license to match the topo definition file values. + +#### Deploy the lab +You are ready to deploy! + +```bash +containerlab deploy -t +``` \ No newline at end of file diff --git a/docs/lab-examples/min-5clos.md b/docs/lab-examples/min-5clos.md index b4360e7f5..89da2b22d 100644 --- a/docs/lab-examples/min-5clos.md +++ b/docs/lab-examples/min-5clos.md @@ -4,12 +4,12 @@ | **Components** | [Nokia SR Linux][srl] | | **Resource requirements**[^1] | :fontawesome-solid-microchip: 4
:fontawesome-solid-memory: 8 GB | | **Topology file** | [clos02.yml][topofile] | -| **Prefix** | clos02 | +| **Name** | clos02 | ## Description This labs provides a lightweight folded 5-stage CLOS fabric with Super Spine level bridging two PODs. -
+
The topology is additionally equipped with the Linux containers connected to leaves to facilitate use cases which require access side emulation. diff --git a/docs/lab-examples/min-clos.md b/docs/lab-examples/min-clos.md index a07f75ed8..9f8d19160 100644 --- a/docs/lab-examples/min-clos.md +++ b/docs/lab-examples/min-clos.md @@ -4,7 +4,7 @@ | **Components** | [Nokia SR Linux][srl] | | **Resource requirements**[^1] | :fontawesome-solid-microchip: 1.5
:fontawesome-solid-memory: 3 GB | | **Topology file** | [clos01.yml][topofile] | -| **Prefix** | clos01 | +| **Name** | clos01 | ## Description This labs provides a lightweight folded CLOS fabric topology using a minimal set of nodes: two leaves and a single spine. diff --git a/docs/lab-examples/single-srl.md b/docs/lab-examples/single-srl.md index 693d2078a..6331ea13a 100644 --- a/docs/lab-examples/single-srl.md +++ b/docs/lab-examples/single-srl.md @@ -4,7 +4,7 @@ | **Components** | [Nokia SR Linux][srl] | | **Resource requirements**[^1] | :fontawesome-solid-microchip: 0.5
:fontawesome-solid-memory: 1 GB | | **Topology file** | [srl01.yml][topofile] | -| **Prefix** | srl01 | +| **Name** | srl01 | ## Description A lab consists of a single SR Linux container equipped with a single interface - its management interface. No other network/data interfaces are created. diff --git a/docs/lab-examples/srl-ceos.md b/docs/lab-examples/srl-ceos.md index 004f4134b..99f1aadcc 100644 --- a/docs/lab-examples/srl-ceos.md +++ b/docs/lab-examples/srl-ceos.md @@ -4,7 +4,7 @@ | **Components** | [Nokia SR Linux][srl], [Arista cEOS][ceos] | | **Resource requirements**[^1] | :fontawesome-solid-microchip: 1
:fontawesome-solid-memory: 2 GB | | **Topology file** | [srlceos01.yml][topofile] | -| **Prefix** | srlceos01 | +| **Name** | srlceos01 | ## Description A lab consists of an SR Linux node connected with Arista cEOS via a point-to-point ethernet link. Both nodes are also connected with their management interfaces to the `containerlab` docker network. diff --git a/docs/lab-examples/two-srls.md b/docs/lab-examples/two-srls.md index cea70eeb5..0398acfe9 100644 --- a/docs/lab-examples/two-srls.md +++ b/docs/lab-examples/two-srls.md @@ -4,12 +4,25 @@ | **Components** | [Nokia SR Linux][srl] | | **Resource requirements**[^1] | :fontawesome-solid-microchip: 1
:fontawesome-solid-memory: 2 GB | | **Topology file** | [srl02.yml][topofile] | -| **Prefix** | srl02 | +| **Name** | srl02 | +| **Validated versions**[^2] | `containerlab v0.8.2`,`srlinux:20.6.2-332` | ## Description -A lab consists of two SR Linux nodes connected with each other via a point-to-point link over `e1-1` interfaces. Both nodes are also connected with their management interfaces to the `containerlab` docker network. +A lab consists of two SR Linux nodes connected with each other via a point-to-point link over `e1-1` interfaces. Both nodes are also connected with their management interfaces to the `clab` docker network. -
+
+ +## Configuration +The nodes of this lab have been provided with a startup configuration by means of `config` directive in the topo definition file. The startup configuration adds loopback and interfaces addressing as per the diagram above. + +Once the lab is started, the nodes will be able to ping each other via configured interfaces: + +``` +A:srl1# ping 192.168.0.1 network-instance default +Using network instance default +PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data. +64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=5.17 ms +``` ## Use cases This lab, besides having the same objectives as [srl01](single-srl.md) lab, also enables the following scenarios: @@ -22,5 +35,6 @@ This lab, besides having the same objectives as [srl01](single-srl.md) lab, also [topofile]: https://github.com/srl-wim/container-lab/tree/master/lab-examples/srl02/srl02.yml [^1]: Resource requirements are provisional. Consult with SR Linux Software Installation guide for additional information. +[^2]: versions of respective container images or software that was used to create the lab. \ No newline at end of file diff --git a/docs/lab-examples/wan.md b/docs/lab-examples/wan.md index 9c9bb1518..c5a765529 100644 --- a/docs/lab-examples/wan.md +++ b/docs/lab-examples/wan.md @@ -4,7 +4,7 @@ | **Components** | [Nokia SR Linux][srl] | | **Resource requirements**[^1] | :fontawesome-solid-microchip: 1
:fontawesome-solid-memory: 3 GB | | **Topology file** | [srl03.yml][topofile] | -| **Prefix** | srl03 | +| **Name** | srl03 | ## Description Nokia SR Linux while focusing on the data center deployments in the first releases, will also be suitable for WAN deployments. In this lab users presented with a small WAN topology of four interconnected SR Linux nodes with multiple p2p interfaces between them. diff --git a/docs/manual/conf-artifacts.md b/docs/manual/conf-artifacts.md new file mode 100644 index 000000000..1f418a42b --- /dev/null +++ b/docs/manual/conf-artifacts.md @@ -0,0 +1,45 @@ +When containerlab deploys a lab it creates a Lab Directory in the **current working directory**. This directory is used to keep all the necessary files that are needed to run/configure the nodes. We call these files _configuration artifacts_. + +Things like: + +* Root CA certificate and per-node TLS certificate and private keys +* per-node config file +* node-specific files and directories that are required to launch the container +* license files + +all these artifacts will be generated under a Lab Directory. + +### Identifying a lab directory +The lab directory name will be constructed by the following template `clab-`. Thus if the name of your lab is `srl02` you will find the `clab-srl02` directory created after the lab deployment process is finished. + +``` +❯ ls -lah clab-srl02 +total 4.0K +drwxr-xr-x 5 root root 40 Dec 1 22:11 . +drwxr-xr-x 23 root root 4.0K Dec 1 22:11 .. +drwxr-xr-x 5 root root 42 Dec 1 22:11 ca +drwxr-xr-x 3 root root 79 Dec 1 22:11 srl1 +drwxr-xr-x 3 root root 79 Dec 1 22:11 srl2 +``` + +The contents of this directory will contain kind-specific files and directories. Containerlab will name directories after the node names and will only created those if they are needed. For instance, by default any node of kind `linux` will not have it's own directory. + +### Persistance of a lab directory +When a user first deploy a lab, the Lab Directory gets created. Depending on node kind, this directory might act as a persistent storage area for a node. A common case is having the configuration file saved when the changes are made to the node via management interfaces. + +Below is an example of the `srl1` node directory contents. It keeps a directory that is mounted to containers configuration path, as well as stores additional files needed to launch and configure the node. + +``` +~/clab/clab-srl02 +❯ ls -lah srl1 +drwxrwxrwx+ 6 1002 1002 87 Dec 1 22:11 config +-rw-r--r-- 1 root root 2.8K Dec 1 22:11 license.key +-rw-r--r-- 1 root root 4.4K Dec 1 22:11 srlinux.conf +-rw-r--r-- 1 root root 233 Dec 1 22:11 topology.yml +``` + +When a user destroys a lab without providing [`--cleanup`](../cmd/destroy.md#cleanup) flag to the `destroy` command, the Lab Directory **does not** get deleted. This means that every configuration artefact will be kept on disk. + +Moreover, when the user will deploy the same lab, containerlab will reuse the configuration artifacts if possible, which will, for example, start the nodes with the config files saved from the previous lab run. + +To be able to deploy a lab without reusing existing configuration artefact use the [`--reconfigure`](../cmd/deploy.md#reconfigure) flag with `deploy` command. With that setting, containerlab will first delete the Lab Directory and then will start the deployment process. \ No newline at end of file diff --git a/docs/manual/kinds/kinds.md b/docs/manual/kinds/kinds.md new file mode 100644 index 000000000..9362e4d2a --- /dev/null +++ b/docs/manual/kinds/kinds.md @@ -0,0 +1,39 @@ +# Kinds + +Containerlab launches, wires up and manages container based labs. The steps required to launch a `debian` or `centos` container image aren't at all different. On the other hand, Nokia SR Linux launching procedure is nothing like the one for Arista cEOS. + +Things like required syscalls, mounted directories, entrypoints and commands to execute are all different for the containerized NOS'es. To let containerlab to understand which launching sequence to use the notion of a `kind` was introduced. Essentially we remove the need to understand certain setup peculiarities of different NOS'es by abstracting them with `kinds`. + +Given the following [topology definition file](../topo-def-file.md), containerlab is able to know how to launch `node1` as SR Linux container and `node2` as a cEOS one because they are associated with the kinds: + +```yaml +name: srlceos01 + +topology: + nodes: + node1: + kind: srl # node1 is of srl kind + type: ixrd2 + image: srlinux + license: license.key + node2: + kind: ceos # node2 is of ceos kind + image: ceos + + links: + - endpoints: ["srl:e1-1", "ceos:eth1"] +``` + +Containerlab supports a fixed number of kinds. Within each predefined kind we store the necessary information that is used to launch the container successfully. The following kinds are supported or in the roadmap of containerlab: + + +| Name | Kind | Status | +| ------------------- | --------------- | --------- | +| **Nokia SR Linux** | [`srl`](srl.md) | supported | +| **Arista cEOS** | `ceos` | supported | +| **Linux container** | `linux` | supported | +| **Linux bridge** | `bridge` | supported | +| **SONiC** | `sonic` | planned | +| **Juniper cRPD** | `crpd` | planned | + +Refer to a specific kind documentation article to see the details about it. \ No newline at end of file diff --git a/docs/manual/kinds/srl.md b/docs/manual/kinds/srl.md new file mode 100644 index 000000000..7f142121a --- /dev/null +++ b/docs/manual/kinds/srl.md @@ -0,0 +1,141 @@ +# Nokia SR Linux + +[Nokia SR Linux](https://www.nokia.com/networks/products/service-router-linux-NOS/) NOS is identified with `srl` kind in the [topology file](../topo-def-file.md). A kind defines a supported feature set and a startup procedure of a node. + +## Managing SR Linux nodes +There are many ways to manage SR Linux nodes, ranging from classic CLI management all the way up to the gNMI programming. Here is a short summary on how to access those interfaces: + +=== "bash" + to connect to a `bash` shell of a running SR Linux container: + ```bash + docker exec -it bash + ``` +=== "CLI" + to connect to the SR Linux CLI + ```bash + docker exec -it sr_cli + ``` +=== "gNMI" + using the best in class [gnmic](https://gnmic.kmrd.dev) gNMI client as an example: + ```bash + gnmic -a --skip-verify \ + -u admin -p admin \ + -e json_ietf \ + get --path /system/name/host-name + ``` + +!!!info + Default user credentials: `admin:admin` + +## Features and options +### Types +For SR Linux nodes [`type`](../nodes.md#type) defines the hardware variant that this node will emulate. + +The available type values are: `ixr6`, `ixr10`, `ixrd1`, `ixrd2`, `ixrd3` which correspond to a hardware variant of Nokia 7250/7220 IXR chassis. + +By default, `ixr6` type will be used by containerlab. + +Based on the provided type, containerlab will generate the [topology file](#topology-file) that will be mounted to SR Linux container and make it boot in a chosen HW variant. +### Node configuration +SR Linux nodes have a dedicated [`config`](#config-directory) directory that is used to persist the configuration of the node. It is possible to launch nodes of `srl` kind with a basic "empty" config or to provide a custom config file that will be used as a startup config instead. +#### Default node configuration +When a node is defined without `config` statement present, containerlab will generate an empty config from [this template](https://github.com/srl-wim/container-lab/blob/master/templates/srl/srlconfig.tpl) and put it in that directory. + +```yaml +# example of a topo file that does not define a custom config +# as a result, the config will be generated from a template +# and used by this node +name: srl_lab +topology: + nodes: + srl1: + kind: srl + type: ixr6 + license: lic.key +``` + +The generated config will be saved by the path `clab-//config/config.json`. Using the example topology presented above, the exact path to the config iill be `clab-srl_lab/srl1/config/config.json`. + +#### User defined config +It is possible to make SR Linux nodes to boot up with a user-defined config instead of a built-in one. With a [`config`](../nodes.md#config) property of the node/kind a user sets the path to the config file that will be mounted to a container: + +```yaml +name: srl_lab +topology: + nodes: + srl1: + kind: srl + type: ixr6 + license: lic.key + config: myconfig.json +``` + +With such topology file containerlab is instructed to take a file `myconfig.json` from the current working directory, copy it to the lab directory for that specific node under the `config.json` name and mount that file to the container. This will result in this config to act as a startup config for the node. + +#### Saving configuration +As was explained in the [Node configuration](#node-configuration) section, SR Linux containers can make their config persist because config files are provided to the containers from the host via bind mount. There are two options to make a running configuration to be saved in a file. + +##### Rewriting startup configuration +When a user configures SR Linux node via CLI the changes are saved into the running configuration stored in memory. To save the running configuration as a startup configuration the user needs to execute the `tools system configuration save` CLI command. This will write the config to the `config.json` file that holds the startup config and is exposed to the host. + +##### Generating config checkpoint +If the startup configuration must be left intact, use an alternative method of saving the configuration checkpoint: `tools system configuration generate-checkpoint`. This command will create a `checkpoint-x.json` file that you will be able to find in the same `config` directory. + +Containerlab allows to perform a bulk configuration-save operation that can be executed with `containerlab save -t ` command. + +With this command, every node that supports the "save" operation will execute a command to save it's running configuration to a persistent location. For SR Linux nodes the `save` command will trigger the checkpoint generation: + +``` +❯ containerlab save -t srl02.yml +INFO[0000] Getting topology information from ../srl02.yml file... +INFO[0001] clab-srl02-srl1 output: /system: + Generated checkpoint '/etc/opt/srlinux/checkpoint/checkpoint-0.json' with name 'checkpoint-2020-12-03T15:12:46.854Z' and comment '' + +INFO[0004] clab-srl02-srl2 output: /system: + Generated checkpoint '/etc/opt/srlinux/checkpoint/checkpoint-0.json' with name 'checkpoint-2020-12-03T15:12:49.892Z' and comment '' +``` + +### License +SR Linux containers require a license file to be provided. With a [`license`](../nodes.md#license) directive it's possible to provide a path to a license file that will be used for srl nodes. + +## Container configuration +To start an SR Linux NOS containerlab uses the configuration that is described in [SR Linux Software Installation Guide](https://documentation.nokia.com/cgi-bin/dbaccessfilename.cgi/3HE16113AAAATQZZA01_V1_SR%20Linux%20R20.6%20Software%20Installation.pdf) + +=== "Startup command" + `sudo bash -c /opt/srlinux/bin/sr_linux` +=== "Syscalls" + ``` + net.ipv4.ip_forward = "0" + net.ipv6.conf.all.disable_ipv6 = "0" + net.ipv6.conf.all.accept_dad = "0" + net.ipv6.conf.default.accept_dad = "0" + net.ipv6.conf.all.autoconf = "0" + net.ipv6.conf.default.autoconf = "0" + ``` +=== "Environment variables" + `SRLINUX=1` +### File mounts +#### Config directory +When a user starts a lab, containerlab creates a lab directory for storing [configuration artifacts](../conf-artifacts.md). For `srl` kind containerlab creates directories for each node of that kind. + +``` +~/clab/clab-srl02 +❯ ls -lah srl1 +drwxrwxrwx+ 6 1002 1002 87 Dec 1 22:11 config +-rw-r--r-- 1 root root 2.8K Dec 1 22:11 license.key +-rw-r--r-- 1 root root 4.4K Dec 1 22:11 srlinux.conf +-rw-r--r-- 1 root root 233 Dec 1 22:11 topology.yml +``` + +The `config` directory is mounted to container's `/etc/opt/srlinux/` in `rw` mode and will effectively contain configuration that SR Linux runs of as well as the files that SR Linux keeps in its `/etc/opt/srlinux/` directory: + +``` +❯ ls srl1/config +banner cli config.json devices tls ztp +``` + +#### CLI env config +Another file that SR Linux expects to have is the `srlinux.conf` file that contains CLI environment config. Containerlab uses a [template of this file](https://github.com/srl-wim/container-lab/blob/master/templates/srl/srl_env.conf) and mounts it to `/home/admin/.srlinux.conf` in `rw` mode. + +#### Topology file +The topology file that defines the emulated hardware type is driven by the value of the kinds `type` parameter. Depending on a specified `type` the appropriate content will be populated into the `topology.yml` file that will get mounted to `/tmp/topology.yml` directory inside the container in `ro` mode. \ No newline at end of file diff --git a/docs/manual/network.md b/docs/manual/network.md new file mode 100644 index 000000000..f68ec3bf4 --- /dev/null +++ b/docs/manual/network.md @@ -0,0 +1,156 @@ + +One of the most important tasks in the process of building container based labs is to create a virtual wiring between the containers and the host. That is one of the problems that containerlab was designed to solve. + +In this document we will discuss the networking concepts that containerlab employs to provide the following connectivity scenarios: + +1. Make containers available from the lab host +2. Interconnect containers to create network topologies of users choice + +## Management network +As governed by the well-established container [networking principles](https://docs.docker.com/network/) containers are able to get network connectivity using various drivers/methods. The most common networking driver that is enabled by default for docker-managed containers is the [bridge driver](https://docs.docker.com/network/bridge/). + +The bridge driver connects containers to a linux bridge interface named `docker0` on most linux operating systems. The containers are then able to communicate with each other and the host via this virtual switch (bridge interface). + +In containerlab we follow a similar approach: containers launched by containerlab will be attached with their interface to a containerlab-managed docker network. It's best to be explained by an example which we will base on a [two nodes](../lab-examples/two-srls.md) lab from our catalog: + +```yaml +name: srl02 + +topology: + kinds: + srl: + type: ixr6 + image: srlinux + license: license.key + nodes: + srl1: + kind: srl + srl2: + kind: srl + + links: + - endpoints: ["srl1:e1-1", "srl2:e1-1"] +``` + +As seen from the topology definition file, the lab consists of the two SR Linux nodes which are interconnected via a single point-to-point link. + +
+ +The diagram above shows that these two nodes are not only interconnected between themselves, but also connected to a bridge interface on the lab host. This is driven by the containerlab default management network settings. + +### default settings +When no information about the management network is provided within the topo definition file, containerlab will do the following + +1. create, if not already created, a docker network named `clab` +2. configure the IPv4/6 addressing pertaining to this docker network + +!!!info + We often refer to `clab` docker network simply as _management network_ since its the network to which management interfaces of the containerized NOS'es are connected. + +The addressing information that containerlab will use on this network: + +* IPv4: subnet 172.20.20.0/24, gateway 172.20.20.1 +* IPv6: subnet 2001:172:20:20::/80, gateway 2001:172:20:20::1 + +With these defaults in place, the two containers from this lab will get connected to that management network and will be able to communicate using the IP addresses allocated by docker daemon. The addresses that docker carves out for each container are presented to a user once the lab deployment finishes or can be queried any time after: + +```bash +# addressing information is available once the lab deployment completes +❯ containerlab deploy -t srl02.yml +# deployment log omitted for brevity ++---+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | ++---+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| 1 | clab-srl02-srl1 | ca24bf3d23f7 | srlinux | srl | | running | 172.20.20.3/24 | 2001:172:20:20::3/80 | +| 2 | clab-srl02-srl2 | ee585eac9e65 | srlinux | srl | | running | 172.20.20.2/24 | 2001:172:20:20::2/80 | ++---+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ + +# addresses can also be fetched afterwards with `inspect` command +❯ containerlab inspect -a ++---+----------+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| # | Lab Name | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | ++---+----------+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| 1 | srl02 | clab-srl02-srl1 | ca24bf3d23f7 | srlinux | srl | | running | 172.20.20.3/24 | 2001:172:20:20::3/80 | +| 2 | srl02 | clab-srl02-srl2 | ee585eac9e65 | srlinux | srl | | running | 172.20.20.2/24 | 2001:172:20:20::2/80 | ++---+----------+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +``` + +The output above shows that srl1 container has been assigned `172.20.20.3/24 / 2001:172:20:20::3/80` IPv4/6 address. We can ensure this by querying the srl1 management interfaces address info: + +```bash +❯ docker exec clab-srl02-srl1 ip address show dummy-mgmt0 +6: dummy-mgmt0: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 2a:66:2b:09:2e:4d brd ff:ff:ff:ff:ff:ff + inet 172.20.20.3/24 brd 172.20.20.255 scope global dummy-mgmt0 + valid_lft forever preferred_lft forever + inet6 2001:172:20:20::3/80 scope global + valid_lft forever preferred_lft forever +``` + +Now it's possible to reach the assigned IP address from the lab host as well as from other containers connected to this management network. +```bash +# ping srl1 management interface from srl2 +❯ docker exec -it clab-srl02-srl2 sr_cli "ping 172.20.20.3 network-instance mgmt" +x -> $176x48 +Using network instance mgmt +PING 172.20.20.3 (172.20.20.3) 56(84) bytes of data. +64 bytes from 172.20.20.3: icmp_seq=1 ttl=64 time=2.43 ms +``` + +!!!note + If you run multiple labs without changing the default management settings, the containers of those labs will end up connecting to the same management network with their management interface. + +### configuring management network +Most of the time there is no need to change the defaults for management network configuration, but sometimes it is needed. For example, it might be that the default network ranges are overlapping with existing addressing scheme on the lab host. + +For such cases the users need to add the `mgmt` container at the top level of their topology definition file: + +```yaml +name: srl02 + +mgmt: + network: custom_mgmt # management network name + ipv4_subnet: 172.100.100.0/24 # ipv4 range + ipv6_subnet: 2001:172:100:100::/80 # ipv6 range (optional) + +topology: +# the rest of the file is omitted for brevity +``` + +With this settings in place container will get their IP addresses from the specified ranges accordingly. + +### connection details +When containerlab needs to create the management network it asks the docker daemon to do this. Docker will fullfil the request and will create a network with the underlying linux bridge interface backing it. The bridge interface name is generated by the docker daemon, but it is easy to find it: + +```bash +# list existing docker networks +# notice the presence of the `clab` network with a `bridge` driver +❯ docker network ls +NETWORK ID NAME DRIVER SCOPE +5d60b6ec8420 bridge bridge local +d2169a14e334 clab bridge local +58ec5037122a host host local +4c1491a09a1a none null local + +# the underlying linux bridge interface name follows the `br- pattern +# to find the network ID use: +❯ docker network inspect clab -f {{.ID}} | head -c 12 +d2169a14e334 + +# now the name is known and its easy to show bridge state +❯ brctl show br-d2169a14e334 +bridge name bridge id STP enabled interfaces +br-d2169a14e334 8000.0242fe382b74 no vetha57b950 + vethe9da10a +``` + +As explained in the beginning of this article, containers will connect to this docker network. This connection is carried out by the `veth` devices created and attached with one end to bridge interface in the lab host and the other end in the container namespace. This is illustrated by the bridge output above and the diagram at the beginning the of the article. + +## Point-to-point links +Management network is used to provide management access to the NOS containers, it does not carry control or dataplane traffic. In containerlab we create additional point-to-point links between the containers to provide the datapath between the lab nodes. + +
+ +The above diagram shows how links are created in the topology definition file. In this example, the datapath consists of the two virtual point-to-point wires between SR Linux and cEOS containers. These links are created on-demand by containerlab itself. + +The p2p links are provided by the `veth` device pairs where each end of the `veth` pair is attached to a respective container. \ No newline at end of file diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md new file mode 100644 index 000000000..f49fc3de9 --- /dev/null +++ b/docs/manual/nodes.md @@ -0,0 +1,88 @@ +Node object is one of the pillars of containerlab. Essentially, it is nodes and links what constitute the lab topology. To let users build flexible and customizable labs the nodes are meant to be configurable. + +The node configuration is part of the [topology definition file](topo-def-file.md) and **may** consist of the following fields that we explain in details below. + +```yaml +# part of topology definition file +topology: + nodes: + node1: # node name + kind: srl + type: ixrd2 + image: srlinux + license: license.key + config: /root/mylab/node1.cfg + binds: + - /usr/local/bin/gobgp:/root/gobgp + - /root/files:/root/files:ro + ports: + - 80:8080 + - 55555:43555/udp + - 55554:43554/tcp +``` + +### kind +The `kind` property selects which kind this node is of. Kinds are essentially a way of telling containerlab how to treat the nodes properties considering the specific flavor of the node. We dedicated a [separate section](kinds/kinds.md) to discuss kinds in details. + +!!!note + Kind **must** be defined either by setting the kind for a node specifically (as in the example above), or by setting the default kind: + ```yaml + topology: + defaults: + kind: srl + nodes: + node1: + # kind value of `srl` is inherited from defaults section + ``` + +### type +With `type` the user sets a type of the node. Types work in combination with the kinds, such as the type value of `ixrd2` sets the chassis type for SR Linux node, thus this value only makes sense to nodes of kind `srl`. + +Other nodes might treat `type` field differently, that will depend on the kind of the node. The `type` values and effects defined in the documentation for a specific kind. + +### image +The common `image` attribute sets the container image name that will be used to start the node. The image name should be provided in a well-known format of `repository(:tag)`. + +We use `` image name throughout the docs articles. This means that the image with `:latest` name will be looked up. A user will need to add the latest tag if they want to use the same loose-tag naming: + +```bash +# tagging srlinux:20.6.1-286 as srlinux:latest +# after this change its possible to use `srlinux:latest` or `srlinux` image name +docker tag srlinux:20.6.1-286 srlinux:latest +``` + +### license +Some containerized NOSes require a license to operate. With `license` property a user sets a path to a license file that a node will use. The license file will then be mounted to the container by the path that is defined by the `kind/type` of the node. + +### config +For the specific kinds its possible to pass a path to a config template file that a node will use. + +The template engine is [Go template](https://golang.org/pkg/text/template/). The [srlconfig.tpl](https://github.com/srl-wim/container-lab/blob/master/templates/srl/srlconfig.tpl) template is used by default for Nokia SR Linux nodes and can be used to create configuration templates for SR Linux nodes. + +Supported for: Nokia SR Linux. + +### binds +In order to expose host files to the containerized nodes a user can leverage the bind mount capability. + +Provide a list of binds instructions under the `binds` container of the node configuration. The string format of those binding instructions follow the same rules as the [--volume parameter](https://docs.docker.com/storage/volumes/#choose-the--v-or---mount-flag) of the docker/podman CLI. + +```yaml +binds: + # mount a file from a host to a container (implicit RW mode) + - /usr/local/bin/gobgp:/root/gobgp + # mount a directory from a host to a container in RO mode + - /root/files:/root/files:ro +``` + +### ports +To bind the ports between the lab host and the containers the users can populate the `ports` object inside the node: + +```yaml +ports: + - 80:8080 # tcp port 80 of the host is mapped to port 8080 of the container + - 55555:43555/udp + - 55554:43554/tcp +``` +The list of port bindings consists of strings in the same format that is acceptable by `docker run` command's [`-p/--export` flag](https://docs.docker.com/engine/reference/commandline/run/#publish-or-expose-port--p---expose). + +This option is only configurable under the node level. \ No newline at end of file diff --git a/docs/manual/topo-def-file.md b/docs/manual/topo-def-file.md new file mode 100644 index 000000000..ee02a8c44 --- /dev/null +++ b/docs/manual/topo-def-file.md @@ -0,0 +1,179 @@ +Containerlab builds labs based on the topology information that users pass to it. This topology information is expressed as a code contained in the _topology definition file_ which structure is the prime focus of this document. + + +
+ + + +## Topology definition components +The topology definition file is a configuration file expressed in YAML. In this document we take a pre-packaged [Nokia SR Linux and Arista cEOS](../lab-examples/srl-ceos.md) lab and explain the topology definition structure using its definition file [srlceos01.yml](https://github.com/srl-wim/container-lab/tree/master/lab-examples/srlceos01/srlceos01.yml) which is pasted below: + +```yaml +name: srlceos01 + +topology: + nodes: + srl: + kind: srl + type: ixrd2 + image: srlinux + license: license.key + ceos: + kind: ceos + image: ceos + + links: + - endpoints: ["srl:e1-1", "ceos:eth1"] +``` + +This topology results in the two nodes being started up and interconnected with each other using a single point-po-point interface: +
+ +Let's touch on the key components of the topology definition file used in this example. + +### Name +The topology must have a name associated with it. The name is used to distinct one topology from another, to allow multiple topologies to be deployed on the host without their names clashed. + +```yaml +name: srlceos01 +``` + +Its user's responsibility to give labs unique names if they plan to run multiple labs. + +The name is a free-formed string, though its recommended not to use dashes (`-`) as they are used to separate lab names from node names. + +When containerlab starts the containers, their names will be generated using the following pattern: `clab-{{lab_name}}-{{node_name}}`. The lab name here is used to make the container's names unique between two different labs even if the nodes are named the same. + +### Topology +The topology object inside the topology definition is the core element of the file. Under the `topology` element you will find all the core objects such as `nodes`, `links`, `kinds` and `defaults`. + +#### Nodes +As with every other topology the nodes are in the center of things. With nodes we tell which lab elements we want to run, in what configuration and flavor. + +Let's zoom into the two nodes we have defined in our topology: + +```yaml +topology: + nodes: + srl: # this is a name of the 1st node + kind: srl + type: ixrd2 + image: srlinux + license: license.key + ceos: # this is a name of the 2nd node + kind: ceos + image: ceos +``` + +We defined individual `nodes` under the `topology.nodes` container. The name of the node is the key under which it is defined. Following the example, our two nodes will be named `srl` and `ceos` respectively. + +Each node can be defined with a set of properties. Such as the `srl` node is defined with the following node-specific properties: + +```yaml +srl: + kind: srl + type: ixrd2 + image: srlinux + license: license.key +``` + +Refer to the [node configuration](nodes.md) document to understand which options are available for nodes and what is their meaning. + +#### Links +Although its totally fine to define the node without any links (like in [this lab](../lab-examples/single-srl.md)) most of the time we interconnect the nodes with links. One of containerlab purposes is to make the interconnection of nodes simple. + +We define the links under the `topology.links` container in the following manner: + +```yaml +# nodes configuration omitted for clarity +topology: + nodes: + srl: + ceos: + + links: + - endpoints: ["srl:e1-1", "ceos:eth1"] + - endpoints: ["srl:e1-2", "ceos:eth2"] +``` + +As you see, the `topology.links` container consists of links. The link itself is expressed as list of two `endpoints`. This might sound complicated, lets use a graphical explanation: + +
+ +As demonstrated on a diagram above, the links between the containers are the point-to-point links which are defined by a pair of interfaces. The link defined as: + +```yaml +endpoints: ["srl:e1-1", "ceos:eth1"] +``` + +translates to an intent of creation a p2p link between the node named `srl` and its `e1-1` interface and the node named `ceos` and its `eth1` interface. The p2p link is realized with a veth pair. + +#### Kinds +Kinds define the flavor of the node, it says if the node is a specific containerized Network OS or something else. We go into details of kinds in its own [document section](kinds/kinds.md), but for the sake of the topology container, we must discuss what happens when `kinds` section appears in the topology definition: + + +```yaml +topology: + kinds: + srl: + type: ixrd2 + image: srlinux + license: license.key + nodes: + srl1: + kind: srl + srl2: + kind: srl + srl3: + kind: srl +``` + +In the example above the `topology.kinds` container has the `srl` kind referenced. With this, we set some values for the properties of the `srl` kind. With a configuration like that, we say that nodes that have `srl` kind associated will also inherit its properties (type, image, license). + +Essentially, what `kinds` section allows us to do is to shorten the lab definition in cases when we have a number of nodes of a same kind. All the nodes (`srl1`, `srl2`, `srl3`) will have the same values for `type`, `image` and `license`. + +Consider how the topology would have looked like without setting the `kinds` object: + +```yaml +topology: + nodes: + srl1: + kind: srl + type: ixrd2 + image: srlinux + license: license.key + srl2: + kind: srl + type: ixrd2 + image: srlinux + license: license.key + srl3: + kind: srl + type: ixrd2 + image: srlinux + license: license.key +``` + +A lot of unnecessary repetition which is eliminated when setting `kinds`. + +#### Defaults +Since `kinds` set the values for the properties of a specific kind, we also introduced the `defaults` container, that can set values globally. + +With `defaults` you can, for example, set the default kind for all the nodes like that: + +```yaml +topology: + defaults: + kind: srl + kinds: + srl: + type: ixrd2 + image: srlinux + license: license.key + nodes: + srl1: + srl2: + srl3: +``` + +Now every node without a kind specified under it, will inherit the global default value of `srl`. \ No newline at end of file diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 000000000..a81282ef0 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,170 @@ + + +## Installation +Getting containerlab is as easy as it gets. Thanks to the trivial [installation](install.md) procedure it can be set up in a matter of a few seconds on any RHEL or Debian based OS[^1]. + +```bash +# download and install the latest release +sudo curl -sL https://get-clab.srlinux.dev | sudo bash +``` + +## Topology definition file +Once installed, containerlab manages the labs defined in the so-called [topology definition files](manual/topo-def-file.md). A user can write a topology definition file of their own, or use the [various examples](lab-examples/lab-examples.md) we provide within the containerlab package. + +In this tutorial we will be using [one of the provided labs](lab-examples/two-srls.md) which consists of two Nokia SR Linux nodes connected one to another. + +
+ +The lab topology is defined in the [srl02.yml](https://github.com/srl-wim/container-lab/blob/master/lab-examples/srl02/srl02.yml) file. To make use of this lab example, we need to copy the corresponding files to our working directory: + +```bash +# create a directory for containerlab +mkdir ~/clab-quickstart +cd ~/clab-quickstart + +# copy over the srl02 lab files +cp -a /etc/containerlab/lab-examples/srl02/* . +``` + +Let's have a look at how this lab's topology is defined: + +```yaml +# contents of srl02.yml file +# topology documentation: http://containerlab.srlinux.dev/lab-examples/two-srls/ +name: srl02 + +topology: + kinds: + srl: + type: ixr6 + image: srlinux + license: license.key + nodes: + srl1: + kind: srl + srl2: + kind: srl + + links: + - endpoints: ["srl1:e1-1", "srl2:e1-1"] +``` + +!!!info + A [topology definition deep-dive](manual/topo-def-file.md) document provides a complete reference of the topology definition syntax. In the quickstart we keep it short, glancing over the key components of the file. + +* Each lab/topology has a `name`. +* The lab topology is defined under the `topology` element. +* Topology is a set of [`nodes`](manual/nodes.md) and [`links`](manual/topo-def-file.md#links) between them. +* The nodes are always of a certain [`kind`]manual/kinds/kinds.md). The `kind` defines the node configuration and behavior. +* Containerlab supports a fixed number of `kinds`. In the example above, the `srl` kind is one of the supported kinds and it has been provided with a few additional options in the `topology.kinds.srl` element. +* `nodes` are interconnected with `links`. Each `link` is [defined](manual/topo-def-file.md#links) by a set of `endpoints`. + +## Container image +One of node's most important properties is the container [`image`](manual/nodes.md#image) they use. In the example above the container image is set under the `srl` kind. +Effectively, the nodes of `srl` kind will inherit this property and will use the `srlinux` image to boot from. + +The image name follows the same rules as the images you use with, for example, Docker client or k8s pods. For example, the provided `srlinux` image name assumes that the tag of the image is `latest`. + +!!!note "Container images versions" + The provided lab examples use the images without a tag, i.e. `image: srlinux`. This means that the image with a `latest` tag must exist. A user needs to tag the image if the `latest` tag is missing. + + For example: `docker tag srlinux:20.6.1-286 srlinux:latest` + +## License files +For the nodes/kinds which require a license to run (like Nokia SR Linux) the [`license`](manual/nodes.md#license) element must specify a path to a valid license file. +In the example we work with, the license path is set to `license.key` for `srl` kind. + +```yaml +topology: + kinds: + srl: + type: ixr6 + image: srlinux + license: license.key +``` + +That means that containerlab will look for this file by the `${PWD}/license.key` path. Before deploying our lab, we need to copy the file in the `~/clab-quickstart` directory to make it available by the specified path. + +## Deploying a lab +Now when we know what a basic topology file consists of, sorted out the container image name and license file, we can deploy this lab. To keep things easy and guessable, the command to deploy a lab is called [`deploy`](cmd/deploy.md). + +```bash +# checking that topology and license files are present in ~/clab-quickstart +❯ ls +license.key srl02.yml + +# checking that srlinux(:latest) image is available +❯ docker image ls srlinux:latest +REPOSITORY TAG IMAGE ID CREATED SIZE +srlinux latest 79019d14cfc7 3 months ago 1.32GB + +# start the lab deployment by referencing the topology file +containerlab deploy --topo srl02.yml +``` + +After a couple of seconds you will see the summary of the deployed nodes: + +``` ++---+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| # | Name | Container ID | Image | Kind | Group | State | IPv4 Address | IPv6 Address | ++---+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +| 1 | clab-srl02-srl1 | dd5c5a8dc51a | srlinux | srl | | running | 172.20.20.5/24 | 2001:172:20:20::5/80 | +| 2 | clab-srl02-srl2 | b623b3957f8f | srlinux | srl | | running | 172.20.20.4/24 | 2001:172:20:20::4/80 | ++---+-----------------+--------------+---------+------+-------+---------+----------------+----------------------+ +``` + +The node name presented in the summary table is the fully qualified node name, it is built using the following pattern: `clab-{{lab_name}}-{{node_name}}`. + +## Connecting to the nodes +Since the topology nodes are regular containers, you can connect to them just like to any other container. + +```bash +docker exec -it clab-srl02-srl1 bash +``` + +For containerized network OSes like Nokia SR Linux or Arista cEOS you can connect with SSH by either using the management address assigned to the container: + +```text +❯ ssh admin@172.20.20.3 +admin@172.20.20.3's password: +Using configuration file(s): [] +Welcome to the srlinux CLI. +Type 'help' (and press ) if you need any help using this. +--{ running }--[ ]-- +A:srl1# +``` + +or by using node's fully qualified names, for which containerlab creates `/etc/hosts` entries: + +``` +ssh admin@clab-srl02-srl1 +``` + +The following tab view aggregates the ways to open the NOS CLI per supported device: + +=== "Nokia SR Linux" + ```bash + # access CLI + docker exec -it sr_cli + # access bash + docker exec -it bash + ``` +=== "Arista cEOS" + ```bash + # access CLI + docker exec -it Cli + ``` + +## Destroying a lab +To remove the lab, use the [`destroy`](cmd/destroy.md) command that takes a topology file as an argument: + +``` +containerlab destroy --topo srl02.yml +``` + +[^1]: Make sure to satisfy lab host [pre-requisites](install.md#pre-requisites) + +## What next? +To get a broader view on the containerlab features and components, refer to the **User manual** section. + +Do not forget to check out the **Lab examples** section where we provide complete and ready-to-run topology definition files. This is a great starting point to explore containerlab by doing. \ No newline at end of file diff --git a/docs/usage.md b/docs/usage.md deleted file mode 100644 index 1f86ad5dd..000000000 --- a/docs/usage.md +++ /dev/null @@ -1,131 +0,0 @@ -### Build a lab configuration file - -To help build the lab topologies a YAML file is used with the following parameters: - -* `Prefix`: The prefix can be seen as a namespace for the lab to make them unique. -* `Docker_info`: we use docker to manage the various containers. In this section we specify the management bridge name and the IPV4 and IPV6 prefixes we use for connecting as an OOB management to the containers - * `bridge` - * `ipv4_subnet` - * `ipv6_subnet` -* `Duts`: this section provides information with respect to the dut containers that are used in the lab. The dut configuration provides an inheritance to optimize the configuration in 3 levels: global_defaults, kind_defaults, dut_specifics. - * `global_defaults`: This section specifies the global defaults and will be inherited if the parameters are not specified in the more specific sections. As an example if kind = srl is specified in the global_defaults section and the kind is not specified in the kind_defaults or dut_specifics sections, the container will be using kind = srl - * `kind`: the kind of container e.g. srl, ceos, alpine, linux or bridge - * *bridge* is a special kind and is used to connect to an external bridge - * `group`: used in the graph output, to help visualize the output - * `kind_defaults`: This section specifies the kind defaults - * `type`: the type of container. e.g. to use 7220-dx, or 7220-ixr series - * `config`: the config file that is used by default for this kind of container - * `image`: the image that is used for this kind of container - * `license`: the license file that is used for this kind of container - * `dut_specifics`: This section specifies the dut specific details. If parameters are not set the can be inherited from higher sections. - * `kind`: the kind of container e.g. srl, ceos, alpine, linux or bridge - * `group`: used in the graph output, to help visualize the output - * `kind_defaults`: This section specifies the kind defaults - * `` - * `type`: the type of container. e.g. to use 7220-dx, or 7220-ixr series - * `config`: the config file that is used for this specific dut - * `image`: the image that is used for this specific dut - * `license`: the license file that is used for this specific dut -* `Links`: Define the virtual wiring for the lab - * `endpoints`: define the virtual wire specified as: - ``` - [":", ":"] - ``` - -There are some examples in the labs sub directory - -``` -Prefix: test -Docker_info: - bridge: srlinux_bridge - ipv4_subnet: "172.19.19.0/24" - ipv6_subnet: "2001:172:19:19::/80" - -Duts: - global_defaults: - kind: srl - group: bb - kind_defaults: - srl: - type: ixr6 - config: templates/srl/config.json - image: srlinux:20.6.1-286 - license: templates/srl/license.key - alpine: - image: henderiw/client-alpine:1.0.0 - dut_specifics: - wan1: - wan2: - -Links: - - endpoints: ["wan1:e1-1", "wan2:e1-1"] -``` - -### Deploy the lab - -To deploy a lab there are a few parameters that can be used: - -``` -[henderiw@srlinux-2 clab]$ containerlab -h -deploy container based lab environments with a user-defined interconnections - -Usage: - containerlab [command] - -Available Commands: - deploy deploy a lab - destroy destroy a lab - exec execute a command on one or multiple containers - graph generate a topology graph - help Help about any command - save save containers configuration - version show containerlab version - -Flags: - -d, --debug enable debug mode - -h, --help help for containerlab - -Use "containerlab [command] --help" for more information about a command. -``` - -Example: - -``` -./containerlab deploy -t lab-examples/wan-topo.yml -``` - -### Destroy the lab - -Example: - -``` -./containerlab destroy -t lab-examples/wan-topo.yml -``` - -### Generating a graph for the lab - -containerlab has the option to generate a topology graph based on [graphviz](https://graphviz.org), which can be utilized to generate a picture of the topology. - -``` -./containerlab graph -t lab-examples/wan-topo.yml -``` - -If graphviz is installed on your system (see pre-requisites), this will generate the graphviz .dot file as well as a final png file which can be viewed in any image viewer. -If the graphviz executabe `dot` is not found on your system, just the `.dot` file is created which can then be transformed into an image via e.g. this website https://dreampuf.github.io/GraphvizOnline. - - -### Logging in into the containers - -#### SRL - -``` -* ssh admin@ -* docker exec -ti sr_cli -* docker exec -ti /bin/bash -``` -#### cEOS - -``` -* ssh admin@ -* docker exec -ti Cli -``` \ No newline at end of file diff --git a/get.sh b/get.sh index 1b9319d77..03958627b 100644 --- a/get.sh +++ b/get.sh @@ -8,7 +8,7 @@ : ${USE_SUDO:="true"} : ${USE_PKG:="true"} # default --use-pkg flag value. will use package installation by default unless the default is changed to false : ${VERIFY_CHECKSUM:="false"} -: ${BIN_INSTALL_DIR:="/usr/local/bin"} +: ${BIN_INSTALL_DIR:="/usr/bin"} : ${REPO_NAME:="srl-wim/container-lab"} : ${REPO_URL:="https://github.com/$REPO_NAME"} : ${PROJECT_URL:="https://containerlab.srlinux.dev"} diff --git a/go.mod b/go.mod index 5ca6a7ca9..0819635a7 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,11 @@ require ( github.com/cloudflare/cfssl v1.4.1 github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker v1.13.1 - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 // indirect + github.com/google/go-cmp v0.2.0 github.com/google/uuid v1.1.2 + github.com/mitchellh/go-homedir v1.1.0 github.com/olekukonko/tablewriter v0.0.4 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -18,7 +20,7 @@ require ( github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect github.com/vishvananda/netlink v1.1.0 - github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae + github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 94f54c9ab..b77bb2353 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -101,6 +102,7 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= diff --git a/lab-examples/srl02/srl02.yml b/lab-examples/srl02/srl02.yml index 1662f0607..21487f450 100644 --- a/lab-examples/srl02/srl02.yml +++ b/lab-examples/srl02/srl02.yml @@ -10,8 +10,10 @@ topology: nodes: srl1: kind: srl + config: srl1.cfg.json srl2: kind: srl + config: srl2.cfg.json links: - endpoints: ["srl1:e1-1", "srl2:e1-1"] diff --git a/lab-examples/srl02/srl1.cfg.json b/lab-examples/srl02/srl1.cfg.json new file mode 100644 index 000000000..674264e84 --- /dev/null +++ b/lab-examples/srl02/srl1.cfg.json @@ -0,0 +1,1814 @@ +{ + "_preamble": { + "header": { + "generated-by": "SRLINUX", + "name": "checkpoint-2020-12-04T11:19:49.065Z", + "comment": "", + "created": "2020-12-04T11:19:49.072Z", + "release": "v20.6.1", + "enabled-yang-features": ["srl_nokia-features:chassis", "srl_nokia-features:jericho2", "srl_nokia-features:lag", "srl_nokia-features:mpls", "srl_nokia-features:platform-7250-ixr"], + "checksum": "8c4e396c75b98f50$02157682bf03ee59c8d4d14adf77ac916f85ff94" + }, + "build": { + "git-branch": "", + "git-tag": "v20.6.1-286-g118bc27b34", + "git-sha": "118bc27b3497668dc3b3f5815182bed2f838f130" + }, + "application": [ + { + "name": "aaa_mgr_setup", + "path": "n/a", + "version": "n/a" + }, + { + "name": "aaa_mgr", + "path": "/opt/srlinux/bin/sr_aaa_mgr", + "version": "2020-07-30T18:31:30.000Z" + }, + { + "name": "acl_mgr", + "path": "/opt/srlinux/bin/sr_acl_mgr", + "version": "2020-07-30T18:32:27.000Z" + }, + { + "name": "app_mgr", + "path": "/opt/srlinux/bin/sr_app_mgr", + "version": "2020-07-30T18:32:16.000Z" + }, + { + "name": "arp_nd_mgr", + "path": "/opt/srlinux/bin/sr_arp_nd_mgr", + "version": "2020-07-30T18:32:42.000Z" + }, + { + "name": "bfd_mgr", + "path": "/opt/srlinux/bin/sr_bfd_mgr", + "version": "2020-07-30T18:32:44.000Z" + }, + { + "name": "bgp_mgr", + "path": "/opt/srlinux/bin/sr_bgp_mgr", + "version": "2020-07-30T18:32:15.000Z" + }, + { + "name": "chassis_mgr", + "path": "/opt/srlinux/bin/sr_chassis_mgr", + "version": "2020-07-30T18:31:22.000Z" + }, + { + "name": "dev_mgr", + "path": "/opt/srlinux/bin/sr_device_mgr", + "version": "2020-07-30T18:32:05.000Z" + }, + { + "name": "dhcp_client_mgr", + "path": "/opt/srlinux/bin/sr_dhcp_client_mgr", + "version": "2020-07-30T18:31:49.000Z" + }, + { + "name": "fib_mgr", + "path": "/opt/srlinux/bin/sr_fib_mgr", + "version": "2020-07-30T18:31:43.000Z" + }, + { + "name": "gnmi_server", + "path": "/opt/srlinux/bin/sr_gnmi_server", + "version": "2020-07-30T18:32:10.000Z" + }, + { + "name": "idb_server", + "path": "/opt/srlinux/bin/sr_idb_server", + "version": "2020-07-30T18:30:45.000Z" + }, + { + "name": "isis_mgr", + "path": "/opt/srlinux/bin/sr_isis_mgr", + "version": "2020-07-30T18:33:15.000Z" + }, + { + "name": "json_rpc", + "path": "/opt/srlinux/bin/sr_json_rpc", + "version": "2020-07-30T18:31:58.000Z" + }, + { + "name": "l2_mac_learn_mgr", + "path": "/opt/srlinux/bin/sr_l2_mac_learn_mgr", + "version": "2020-07-30T18:31:56.000Z" + }, + { + "name": "l2_mac_mgr", + "path": "/opt/srlinux/bin/sr_l2_mac_mgr", + "version": "2020-07-30T18:31:58.000Z" + }, + { + "name": "l2_static_mac_mgr", + "path": "/opt/srlinux/bin/sr_l2_static_mac_mgr", + "version": "2020-07-30T18:31:58.000Z" + }, + { + "name": "lag_mgr", + "path": "/opt/srlinux/bin/sr_lag_mgr", + "version": "2020-07-30T18:31:41.000Z" + }, + { + "name": "linux_mgr", + "path": "/opt/srlinux/bin/sr_linux_mgr", + "version": "2020-07-30T18:32:45.000Z" + }, + { + "name": "dnsmasq", + "path": "/usr/sbin/dnsmasq", + "version": "2019-10-18T16:01:06.000Z" + }, + { + "name": "sshd", + "path": "/usr/sbin/sshd", + "version": "2019-08-09T01:40:47.000Z" + }, + { + "name": "ntpd", + "path": "/usr/sbin/ntpd", + "version": "2019-08-08T11:48:11.000Z" + }, + { + "name": "vsftpd", + "path": "/usr/sbin/vsftpd", + "version": "2020-04-01T04:55:33.000Z" + }, + { + "name": "snmp_server", + "path": "/opt/srlinux/bin/snmp_server", + "version": "2020-07-30T18:30:50.000Z" + }, + { + "name": "timesrv", + "path": "/usr/sbin/ntpd", + "version": "2019-08-08T11:48:11.000Z" + }, + { + "name": "lldp_mgr", + "path": "/opt/srlinux/bin/sr_lldp_mgr", + "version": "2020-07-30T18:32:21.000Z" + }, + { + "name": "log_mgr", + "path": "/opt/srlinux/bin/sr_log_mgr", + "version": "2020-07-30T18:31:04.000Z" + }, + { + "name": "mcid_mgr", + "path": "/opt/srlinux/bin/sr_mcid_mgr", + "version": "2020-07-30T18:31:56.000Z" + }, + { + "name": "mgmt_server", + "path": "/opt/srlinux/bin/sr_mgmt_server", + "version": "2020-07-30T18:31:27.000Z" + }, + { + "name": "common", + "path": "n/a", + "version": "n/a" + }, + { + "name": "mpls_mgr", + "path": "/opt/srlinux/bin/sr_mpls_mgr", + "version": "2020-07-30T18:31:22.000Z" + }, + { + "name": "net_inst_mgr", + "path": "/opt/srlinux/bin/sr_net_inst_mgr", + "version": "2020-07-30T18:31:07.000Z" + }, + { + "name": "oam_mgr", + "path": "/opt/srlinux/bin/sr_oam_mgr", + "version": "2020-07-30T18:31:50.000Z" + }, + { + "name": "ospf_mgr", + "path": "/opt/srlinux/bin/sr_ospf_mgr", + "version": "2020-07-30T18:33:24.000Z" + }, + { + "name": "plcy_mgr", + "path": "/opt/srlinux/bin/sr_plcy_mgr", + "version": "2020-07-30T18:31:43.000Z" + }, + { + "name": "maint_mode_mgr", + "path": "n/a", + "version": "n/a" + }, + { + "name": "qos_mgr", + "path": "/opt/srlinux/bin/sr_qos_mgr", + "version": "2020-07-30T18:31:23.000Z" + }, + { + "name": "sdk_mgr", + "path": "/opt/srlinux/bin/sr_sdk_mgr", + "version": "2020-07-30T18:31:45.000Z" + }, + { + "name": "static_route_mgr", + "path": "/opt/srlinux/bin/sr_static_route_mgr", + "version": "2020-07-30T18:31:27.000Z" + }, + { + "name": "supportd", + "path": "/opt/srlinux/bin/sr_supportd", + "version": "2020-07-30T18:30:44.000Z" + }, + { + "name": "xdp_cpm", + "path": "/opt/srlinux/bin/sr_xdp_cpm", + "version": "2020-07-30T18:30:58.000Z" + }, + { + "name": "xdp_lc", + "path": "/opt/srlinux/bin/sr_xdp_lc", + "version": "2020-07-30T18:31:24.000Z" + } + ], + "yang-module": [ + { + "name": "srl_nokia-aaa", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-aaa-types", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-acl", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-aggregate-routes", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-app-mgmt", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-bfd", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bgp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-boot", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-duplication", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-duplication-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-learning", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-learning-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-limit", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-static-mac", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-configuration", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-dns", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ftp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-gnmi-server", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-icmp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-if-ip", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-mac-duplication-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-mac-learning-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-mac-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-statistics", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-ip-dhcp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-lag", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-nbr", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-router-adv", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-vlans", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-ip-route-tables", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-isis", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-json-rpc", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-keychains", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-lacp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-linux", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-lldp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-lldp-types", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-load-balancing", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-logging", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-maintenance-mode", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-micro-bfd", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-mpls", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-mpls-route-tables", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-mtu", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-network-instance", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-network-instance-mtu", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-next-hop-groups", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ntp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ospf", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-packet-match-types", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-acl", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-chassis", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-control", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-cpu", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-disk", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-fabric", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-fan", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-ip-mpls-fwd-resources", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-lc", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-memory", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-mtu", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-psu", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-qos", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-redundancy", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-resource-monitoring", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-qos", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-rib-bgp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-routing-policy", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-sflow", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-snmp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ssh", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-static-routes", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-system", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-system-banner", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-system-bridge-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-system-info", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-system-name", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-tcp-udp", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-timezone", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-tls", + "revision": "2019-11-30" + } + ] + }, + "srl_nokia-acl:acl": { + "cpm-filter": { + "ipv4-filter": { + "statistics-per-entry": true, + "entry": [ + { + "sequence-id": 10, + "description": "Accept incoming ICMP unreachable messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "dest-unreachable", + "code": [ + 0, + 1, + 2, + 3, + 4, + 13 + ] + } + } + }, + { + "sequence-id": 20, + "description": "Accept incoming ICMP time-exceeded messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "time-exceeded" + } + } + }, + { + "sequence-id": 30, + "description": "Accept incoming ICMP parameter problem messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "param-problem" + } + } + }, + { + "sequence-id": 40, + "description": "Accept incoming ICMP echo messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "echo" + } + } + }, + { + "sequence-id": 50, + "description": "Accept incoming ICMP echo-reply messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "echo-reply" + } + } + }, + { + "sequence-id": 60, + "description": "Accept incoming SSH when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 70, + "description": "Accept incoming SSH when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 80, + "description": "Accept incoming Telnet when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 90, + "description": "Accept incoming Telnet when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 100, + "description": "Accept incoming TACACS+ when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 110, + "description": "Accept incoming TACACS+ when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 120, + "description": "Accept incoming DNS response messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "source-port": { + "operator": "eq", + "value": 53 + } + } + }, + { + "sequence-id": 130, + "description": "Accept incoming DHCP messages targeted for BOOTP/DHCP client", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 68 + } + } + }, + { + "sequence-id": 140, + "description": "Accept incoming TFTP read-request and write-request messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 69 + } + } + }, + { + "sequence-id": 150, + "description": "Accept incoming HTTP(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 160, + "description": "Accept incoming HTTP(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 170, + "description": "Accept incoming NTP messages from servers", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "source-port": { + "operator": "eq", + "value": 123 + } + } + }, + { + "sequence-id": 180, + "description": "Accept incoming SNMP GET/GETNEXT messages from servers", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 161 + } + } + }, + { + "sequence-id": 190, + "description": "Accept incoming BGP when the other router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 200, + "description": "Accept incoming BGP when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 210, + "description": "Accept incoming HTTPS(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 220, + "description": "Accept incoming HTTPS(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 230, + "description": "Accept incoming single-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 3784 + } + } + }, + { + "sequence-id": 240, + "description": "Accept incoming multi-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 4784 + } + } + }, + { + "sequence-id": 250, + "description": "Accept incoming uBFD session messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 6784 + } + } + }, + { + "sequence-id": 260, + "description": "Accept incoming gNMI messages when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 57400 + } + } + }, + { + "sequence-id": 270, + "description": "Accept incoming UDP traceroute messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "range": { + "start": 33434, + "end": 33464 + } + } + } + }, + { + "sequence-id": 280, + "description": "Accept incoming ICMP timestamp messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "timestamp" + } + } + }, + { + "sequence-id": 290, + "description": "Drop all else", + "action": { + "drop": { + "log": true + } + } + } + ] + }, + "ipv6-filter": { + "statistics-per-entry": true, + "entry": [ + { + "sequence-id": 10, + "description": "Accept incoming ICMPv6 unreachable messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "dest-unreachable", + "code": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + } + } + }, + { + "sequence-id": 20, + "description": "Accept incoming ICMPv6 packet-too-big messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "packet-too-big" + } + } + }, + { + "sequence-id": 30, + "description": "Accept incoming ICMPv6 time-exceeded messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "time-exceeded" + } + } + }, + { + "sequence-id": 40, + "description": "Accept incoming ICMPv6 parameter problem messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "param-problem" + } + } + }, + { + "sequence-id": 50, + "description": "Accept incoming ICMPv6 echo-request messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "echo-request" + } + } + }, + { + "sequence-id": 60, + "description": "Accept incoming ICMPv6 echo-reply messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "echo-reply" + } + } + }, + { + "sequence-id": 70, + "description": "Accept incoming ICMPv6 router-advertisement messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "router-advertise" + } + } + }, + { + "sequence-id": 80, + "description": "Accept incoming ICMPv6 neighbor-solicitation messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "neighbor-solicit" + } + } + }, + { + "sequence-id": 90, + "description": "Accept incoming ICMPv6 neighbor-advertisement messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "neighbor-advertise" + } + } + }, + { + "sequence-id": 100, + "description": "Accept incoming SSH when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 110, + "description": "Accept incoming SSH when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 120, + "description": "Accept incoming Telnet when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 130, + "description": "Accept incoming Telnet when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 140, + "description": "Accept incoming TACACS+ when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 150, + "description": "Accept incoming TACACS+ when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 160, + "description": "Accept incoming DNS response messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "source-port": { + "operator": "eq", + "value": 53 + } + } + }, + { + "sequence-id": 170, + "description": "Accept incoming TFTP read-request and write-request messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 69 + } + } + }, + { + "sequence-id": 180, + "description": "Accept incoming HTTP(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 190, + "description": "Accept incoming HTTP(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 200, + "description": "Accept incoming NTP messages from servers", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "source-port": { + "operator": "eq", + "value": 123 + } + } + }, + { + "sequence-id": 210, + "description": "Accept incoming SNMP GET/GETNEXT messages from servers", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 161 + } + } + }, + { + "sequence-id": 220, + "description": "Accept incoming BGP when the other router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 230, + "description": "Accept incoming BGP when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 240, + "description": "Accept incoming HTTPS(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 250, + "description": "Accept incoming HTTPS(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 260, + "description": "Accept incoming DHCPv6 client messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 546 + } + } + }, + { + "sequence-id": 270, + "description": "Accept incoming single-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 3784 + } + } + }, + { + "sequence-id": 280, + "description": "Accept incoming multi-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 4784 + } + } + }, + { + "sequence-id": 290, + "description": "Accept incoming uBFD session messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 6784 + } + } + }, + { + "sequence-id": 300, + "description": "Accept incoming gNMI messages when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 57400 + } + } + }, + { + "sequence-id": 310, + "description": "Accept incoming UDP traceroute messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "range": { + "start": 33434, + "end": 33464 + } + } + } + }, + { + "sequence-id": 320, + "description": "Accept incoming IPV6 hop-in-hop messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": 0 + } + }, + { + "sequence-id": 330, + "description": "Accept incoming IPV6 fragment header messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": 44 + } + }, + { + "sequence-id": 340, + "description": "Drop all else", + "action": { + "drop": { + "log": true + } + } + } + ] + } + } + }, + "srl_nokia-interfaces:interface": [ + { + "name": "ethernet-1/1", + "admin-state": "enable", + "subinterface": [ + { + "index": 0, + "admin-state": "enable", + "ipv4": { + "address": [ + { + "ip-prefix": "192.168.0.0/31" + } + ] + }, + "ipv6": { + "address": [ + { + "ip-prefix": "2002::c0a8:0/31" + } + ] + } + } + ] + }, + { + "name": "lo0", + "admin-state": "enable", + "subinterface": [ + { + "index": 0, + "admin-state": "enable", + "ipv4": { + "address": [ + { + "ip-prefix": "10.0.0.1/32" + } + ] + }, + "ipv6": { + "address": [ + { + "ip-prefix": "2002::a00:1/128" + } + ] + } + } + ] + }, + { + "name": "mgmt0", + "admin-state": "enable", + "subinterface": [ + { + "index": 0, + "admin-state": "enable", + "ipv4": { + "srl_nokia-interfaces-ip-dhcp:dhcp-client": { + } + }, + "ipv6": { + "srl_nokia-interfaces-ip-dhcp:dhcp-client": { + } + } + } + ] + } + ], + "srl_nokia-system:system": { + "srl_nokia-aaa:aaa": { + "authentication": { + "authentication-method": [ + "local" + ] + }, + "server-group": [ + { + "name": "local" + } + ] + }, + "srl_nokia-gnmi-server:gnmi-server": { + "admin-state": "enable", + "timeout": 7200, + "rate-limit": 60, + "session-limit": 20, + "network-instance": [ + { + "name": "mgmt", + "admin-state": "enable", + "use-authentication": true, + "port": 57400, + "tls-profile": "tls-profile-1" + } + ] + }, + "srl_nokia-tls:tls": { + "server-profile": [ + { + "name": "tls-profile-1", + "key": "$aes$bPlcJQENAefU=$e9muPZkQ1QCD1GjOtYj4K83TVepfQuiOHhs3DCAEi7FeZas4RZFUS9FpwZpnnY0tkTnvOUG0G+NfdEC6GoqIEefUG0K+iJ73TO0W531goERMZ6HWVa60bxM+NfI+Nq8Dpbva5XWBmN8PQ6I6z0y4WLxFd+QBTKjz+8W/FIZ9kDmME+xnTEIWaDYtWonfe5L92fAN4kt/KQxxDdvtdymcTl4xe07taJ5dHTAjrKF4BhgQYI96By+i/dM1mEWpBm6JEedgcT9t7S+oPgojuWf/67RREi2U8iknumZbZ6353ch9Ysj77/+FKTHJU0IC87c3ofxLpoSCjegL9Dm3+LRsYLBiPg4OKQMr737Oeav2NqSePIIlVK/xYRtLbx6u9yk1QindO3q6ytqA1K85D+5ksjl1Np1l+KKVApTmlaORlU00tQIRWtVQWS7vwQZtMbk5h+Zs6KfAfWVJdJMhRxD6jgj6L37Oz+pJaGJ/TbJf3wO33VWBYzO3kWL7c/NIipclsD8dE56TuvabPf3SwS8T+/Q2f9ZG4CO0wHEWTdPj9/JhgAU1oRLVi7kGy56puInBlYoBDMPLUNk4mEDaU6oyO766QN9E144fonaXtU974VIlyoKa0IcAQQ+gSNK4T3IXgwkFMIqawB4ozmXE9FIk5QyQ7r1sm3kSbpgDaZP9vSBkAIemTgM4Ta664rparZn+MkXRcP8MebJtiTRbPQRt1FBcdmf5g8dQkWoDIdhuSEsJ67GZ/KNEpshByJPidzvcWwW9uOcUiWSFCOIUs4h+QI0X88URoqQDcEnrTaPLjLZuvtAgMlMz4ZTXShDgBYu3I4uE9w83EqwaQBJFBZkU0RpZYoFMu4quyxKMzEqwEUABrL5yBI9ZzysaDPBkUBoI97Rn+yxn1/g7+9q9bQqw6zGGICYgT9vF1W+3VvHpSdZHsuovN4sthbd5p2IvQYW0Rh748Qv1rkf3TfcwErDWyBdGqGwztVxrR/g0X1/3IVMNgrbJGFD5H0/U3loLZ9szE1QUkk4xo0GSSp5MV6/k8ZcwSLUhSpgfhSBb/dS/MP2dXNhBSNkUCwkgs20OYH6Nf3BmEXHdJ3OfU0yqYNIBe77DNjad8S6/60qQc/TJzRkrnxa7MX/eoYFiVdfPYJAJp7dA45CHJbaTeiRuZTsPEe9aKqWiMJEe7pnknoa9a/hBWln4ELw3Hh64RMqdTtNAw6f6dAbpqJ10HickjghNI5f7r9JKE03w7pvRxZdhb/tWvNfSypUShqfPO6YeM4cUoj26zOSi3LIEzwEuQdmqpK9m3z/u2YSR/QHDJMn+UkOyzrqxkm4LoatacCnqWvWEM+95u3S1uhxJZC2Pu2QNqaNyB1+7F/SSBbNpJF0H2lAuimw9RfNN3l1aFChsesjrWw1SeqnmMYaL7oZZsoQ/Nx6EMICjA49O+z4/1o+QeVIjlBzPsceCRGFuH0Gh9bMUwlog8lMZ9YnLz9D1KcKHCv7XRzcUErx/JF9RhQDeQPKim8eoQqIYNWQAuazmpIBp5+Z89QA/K4KZeajSNnWP7Cq6IJHZ5hY+IC3+PA+ZXOjM9GlX3ff3swKFtTqgV8TF5mj4MS5wuuukk4sOcIEfsS4E60E1vmNPFahBg0UNhCNLlDbxJMNq9u/YVeSTfMFMxOxF1KDWOurhfeVpqaDN+yeSgUPOT8dQDq+xcpfQWedIduwi3WdvzVeIZ6Hnt+xgjyt5JzKwoYEh/q3i/v/gncekRI55BvZyBTrSLXZhJZlMimh2+7rx9d7Hwqb7Fx+uLspbddUgkccaMTfjhixrfwVdOOzfqiWg19c3dJphqFTXQ3/f6Kr0kedN9ZUynQBRlevzcfJM28XzNLrI36pnnkf0xk6wfH18ID3ME229LCbL4Jv+WJult3LzVvp9SMJFuLiBuIu5jvKTP5K7zIHAXtS9Ups1NG11W/k7w7aCGnqBf3/Qh4+0kycaybp0R/ZvNt9ek+zY/QSkrGOAPP8I85mN9hhUTd5rX7+053pePvuCW/ajEYeuiylZVNj99eRlGyBNhc8nbZe8t4663LGRECMPTOXDaV5JZ/simZO8hzhgf7LMpZyEcPwvB4s3WK3GOie1PYuEUig4hW7+7jVnVUNVK5qJ7F9hOjstYZuJjn3fN9h0yvb8UOf1jJKEe37wvjXroUGPKLX8O4T0MX5lyMA+NeuByD/sRJVpqW/jij1j1jNo9TJjXX3cru+ATIES", + "certificate": "-----BEGIN CERTIFICATE-----\nMIID/jCCAuagAwIBAgIUblhf7XGz292tiCGBkI3BBUkPjtIwDQYJKoZIhvcNAQEL\nBQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5v\na2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwwMiBSb290\nIENBMB4XDTIwMTIwMTIwMDYwMFoXDTIxMTIwMTIwMDYwMFowXzELMAkGA1UEBhMC\nQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5va2lhMRYwFAYDVQQLEw1D\nb250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwxLnNybDAyLmlvMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7RWDWGlWOb/Dz14tgAHJoszJGAlrME3fst63\nkdNHBvrwGSVyvTuLLd5JkBVFAS/V85NEYl6szksRW5fmhSKzU3irq4nej//gJDG/\n6NO8JbKwWSTG72LC486Ikj4n8mxGxKMzISONx/ehL9hgSKyBPUm1nkX0k5X9CAv5\nC4PFSfvjUNtQsvw6cRvkE7tzXY8wwrYMurk6zUyWnqWuTGGDbV1NcclhoHk1wjwR\n4uRc+4BWrE0ZN/ISG2DntWzZ8nzT2Al1geHPIWnXoThuP1al/NP6lnhnDsWj4rq3\nUJ5zwRiOgPmX49RHRC2PJ68VvuJBxlCcsqQ1/pbWOxX4w20beQIDAQABo4GxMIGu\nMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\nDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUjsmXavA1gnqkHVh3LCelKv++BAEwHwYD\nVR0jBBgwFoAU/UNXlN/3/ij9bWwTiqAP3Gh4mmowLwYDVR0RBCgwJoIEc3JsMYIP\nY2xhYi1zcmwwMi1zcmwxgg1zcmwxLnNybDAyLmlvMA0GCSqGSIb3DQEBCwUAA4IB\nAQAcffWatQfoWhTyvJX4mqs9eT18OQZeERZKqfxWzsLMhGPB8n6TuyPyB6S7L72E\n4RDR0lM3t57MUCppGP6tyfMjNjnnwHRaWIzZZwBxhNiQy7c8Z1rJSR97r7vbguU/\nlStsRubAOwhCxak21hkfO4b8S/cW+0yvoVQEUhFD3/UlwH1GIwBCY6JPI6HRJ5dO\nAwPrF6aoOZ+SktOXihxdA2TbPXE5kBLrPVLA/IGcBuHRWOylrQEFiCFGxixLKYbd\n+efdNEmvc/wSXXwzluX72ug5NDkw33ywkpI4U+pkPLBQkpswShl5YWN9vk9+Eewe\nnLtmJIIwdSEnQDH1xZKn/7UK\n-----END CERTIFICATE-----\n", + "authenticate-client": false + } + ] + }, + "srl_nokia-ssh:ssh-server": { + "network-instance": [ + { + "name": "mgmt", + "admin-state": "enable" + } + ] + }, + "srl_nokia-lldp:lldp": { + "admin-state": "enable" + }, + "srl_nokia-logging:logging": { + "network-instance": "mgmt", + "buffer": [ + { + "buffer-name": "messages", + "rotate": 3, + "size": "10000000", + "facility": [ + { + "facility-name": "local6", + "priority": { + "match-above": "informational" + } + } + ] + }, + { + "buffer-name": "system", + "facility": [ + { + "facility-name": "auth", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "cron", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "daemon", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "ftp", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "kern", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "lpr", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "mail", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "news", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "syslog", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "user", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "uucp", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local0", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local1", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local2", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local3", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local4", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local5", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local7", + "priority": { + "match-above": "warning" + } + } + ] + } + ], + "file": [ + { + "file-name": "messages", + "rotate": 3, + "size": "10000000", + "facility": [ + { + "facility-name": "local6", + "priority": { + "match-above": "warning" + } + } + ] + } + ] + } + }, + "srl_nokia-network-instance:network-instance": [ + { + "name": "default", + "interface": [ + { + "name": "ethernet-1/1.0" + } + ] + }, + { + "name": "mgmt", + "type": "srl_nokia-network-instance:ip-vrf", + "admin-state": "enable", + "description": "Management network instance", + "interface": [ + { + "name": "mgmt0.0" + } + ], + "protocols": { + "srl_nokia-linux:linux": { + "import-routes": true, + "export-routes": true, + "export-neighbors": true + } + } + } + ] +} diff --git a/lab-examples/srl02/srl2.cfg.json b/lab-examples/srl02/srl2.cfg.json new file mode 100644 index 000000000..a58ba5a04 --- /dev/null +++ b/lab-examples/srl02/srl2.cfg.json @@ -0,0 +1,1814 @@ +{ + "_preamble": { + "header": { + "generated-by": "SRLINUX", + "name": "checkpoint-2020-12-04T11:19:50.570Z", + "comment": "", + "created": "2020-12-04T11:19:50.577Z", + "release": "v20.6.1", + "enabled-yang-features": ["srl_nokia-features:chassis", "srl_nokia-features:jericho2", "srl_nokia-features:lag", "srl_nokia-features:mpls", "srl_nokia-features:platform-7250-ixr"], + "checksum": "1141a1658ae61ec5$34d446e357368772aae7a6b24733664d5c8b81c4" + }, + "build": { + "git-branch": "", + "git-tag": "v20.6.1-286-g118bc27b34", + "git-sha": "118bc27b3497668dc3b3f5815182bed2f838f130" + }, + "application": [ + { + "name": "aaa_mgr_setup", + "path": "n/a", + "version": "n/a" + }, + { + "name": "aaa_mgr", + "path": "/opt/srlinux/bin/sr_aaa_mgr", + "version": "2020-07-30T18:31:30.000Z" + }, + { + "name": "acl_mgr", + "path": "/opt/srlinux/bin/sr_acl_mgr", + "version": "2020-07-30T18:32:27.000Z" + }, + { + "name": "app_mgr", + "path": "/opt/srlinux/bin/sr_app_mgr", + "version": "2020-07-30T18:32:16.000Z" + }, + { + "name": "arp_nd_mgr", + "path": "/opt/srlinux/bin/sr_arp_nd_mgr", + "version": "2020-07-30T18:32:42.000Z" + }, + { + "name": "bfd_mgr", + "path": "/opt/srlinux/bin/sr_bfd_mgr", + "version": "2020-07-30T18:32:44.000Z" + }, + { + "name": "bgp_mgr", + "path": "/opt/srlinux/bin/sr_bgp_mgr", + "version": "2020-07-30T18:32:15.000Z" + }, + { + "name": "chassis_mgr", + "path": "/opt/srlinux/bin/sr_chassis_mgr", + "version": "2020-07-30T18:31:22.000Z" + }, + { + "name": "dev_mgr", + "path": "/opt/srlinux/bin/sr_device_mgr", + "version": "2020-07-30T18:32:05.000Z" + }, + { + "name": "dhcp_client_mgr", + "path": "/opt/srlinux/bin/sr_dhcp_client_mgr", + "version": "2020-07-30T18:31:49.000Z" + }, + { + "name": "fib_mgr", + "path": "/opt/srlinux/bin/sr_fib_mgr", + "version": "2020-07-30T18:31:43.000Z" + }, + { + "name": "gnmi_server", + "path": "/opt/srlinux/bin/sr_gnmi_server", + "version": "2020-07-30T18:32:10.000Z" + }, + { + "name": "idb_server", + "path": "/opt/srlinux/bin/sr_idb_server", + "version": "2020-07-30T18:30:45.000Z" + }, + { + "name": "isis_mgr", + "path": "/opt/srlinux/bin/sr_isis_mgr", + "version": "2020-07-30T18:33:15.000Z" + }, + { + "name": "json_rpc", + "path": "/opt/srlinux/bin/sr_json_rpc", + "version": "2020-07-30T18:31:58.000Z" + }, + { + "name": "l2_mac_learn_mgr", + "path": "/opt/srlinux/bin/sr_l2_mac_learn_mgr", + "version": "2020-07-30T18:31:56.000Z" + }, + { + "name": "l2_mac_mgr", + "path": "/opt/srlinux/bin/sr_l2_mac_mgr", + "version": "2020-07-30T18:31:58.000Z" + }, + { + "name": "l2_static_mac_mgr", + "path": "/opt/srlinux/bin/sr_l2_static_mac_mgr", + "version": "2020-07-30T18:31:58.000Z" + }, + { + "name": "lag_mgr", + "path": "/opt/srlinux/bin/sr_lag_mgr", + "version": "2020-07-30T18:31:41.000Z" + }, + { + "name": "linux_mgr", + "path": "/opt/srlinux/bin/sr_linux_mgr", + "version": "2020-07-30T18:32:45.000Z" + }, + { + "name": "dnsmasq", + "path": "/usr/sbin/dnsmasq", + "version": "2019-10-18T16:01:06.000Z" + }, + { + "name": "sshd", + "path": "/usr/sbin/sshd", + "version": "2019-08-09T01:40:47.000Z" + }, + { + "name": "ntpd", + "path": "/usr/sbin/ntpd", + "version": "2019-08-08T11:48:11.000Z" + }, + { + "name": "vsftpd", + "path": "/usr/sbin/vsftpd", + "version": "2020-04-01T04:55:33.000Z" + }, + { + "name": "snmp_server", + "path": "/opt/srlinux/bin/snmp_server", + "version": "2020-07-30T18:30:50.000Z" + }, + { + "name": "timesrv", + "path": "/usr/sbin/ntpd", + "version": "2019-08-08T11:48:11.000Z" + }, + { + "name": "lldp_mgr", + "path": "/opt/srlinux/bin/sr_lldp_mgr", + "version": "2020-07-30T18:32:21.000Z" + }, + { + "name": "log_mgr", + "path": "/opt/srlinux/bin/sr_log_mgr", + "version": "2020-07-30T18:31:04.000Z" + }, + { + "name": "mcid_mgr", + "path": "/opt/srlinux/bin/sr_mcid_mgr", + "version": "2020-07-30T18:31:56.000Z" + }, + { + "name": "mgmt_server", + "path": "/opt/srlinux/bin/sr_mgmt_server", + "version": "2020-07-30T18:31:27.000Z" + }, + { + "name": "common", + "path": "n/a", + "version": "n/a" + }, + { + "name": "mpls_mgr", + "path": "/opt/srlinux/bin/sr_mpls_mgr", + "version": "2020-07-30T18:31:22.000Z" + }, + { + "name": "net_inst_mgr", + "path": "/opt/srlinux/bin/sr_net_inst_mgr", + "version": "2020-07-30T18:31:07.000Z" + }, + { + "name": "oam_mgr", + "path": "/opt/srlinux/bin/sr_oam_mgr", + "version": "2020-07-30T18:31:50.000Z" + }, + { + "name": "ospf_mgr", + "path": "/opt/srlinux/bin/sr_ospf_mgr", + "version": "2020-07-30T18:33:24.000Z" + }, + { + "name": "plcy_mgr", + "path": "/opt/srlinux/bin/sr_plcy_mgr", + "version": "2020-07-30T18:31:43.000Z" + }, + { + "name": "maint_mode_mgr", + "path": "n/a", + "version": "n/a" + }, + { + "name": "qos_mgr", + "path": "/opt/srlinux/bin/sr_qos_mgr", + "version": "2020-07-30T18:31:23.000Z" + }, + { + "name": "sdk_mgr", + "path": "/opt/srlinux/bin/sr_sdk_mgr", + "version": "2020-07-30T18:31:45.000Z" + }, + { + "name": "static_route_mgr", + "path": "/opt/srlinux/bin/sr_static_route_mgr", + "version": "2020-07-30T18:31:27.000Z" + }, + { + "name": "supportd", + "path": "/opt/srlinux/bin/sr_supportd", + "version": "2020-07-30T18:30:44.000Z" + }, + { + "name": "xdp_cpm", + "path": "/opt/srlinux/bin/sr_xdp_cpm", + "version": "2020-07-30T18:30:58.000Z" + }, + { + "name": "xdp_lc", + "path": "/opt/srlinux/bin/sr_xdp_lc", + "version": "2020-07-30T18:31:24.000Z" + } + ], + "yang-module": [ + { + "name": "srl_nokia-aaa", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-aaa-types", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-acl", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-aggregate-routes", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-app-mgmt", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-bfd", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bgp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-boot", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-duplication", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-duplication-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-learning", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-learning-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-limit", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-mac-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-bridge-table-static-mac", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-configuration", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-dns", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ftp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-gnmi-server", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-icmp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-if-ip", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-mac-duplication-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-mac-learning-entries", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-mac-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-bridge-table-statistics", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-ip-dhcp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-lag", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-nbr", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-router-adv", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-interfaces-vlans", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-ip-route-tables", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-isis", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-json-rpc", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-keychains", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-lacp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-linux", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-lldp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-lldp-types", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-load-balancing", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-logging", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-maintenance-mode", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-micro-bfd", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-mpls", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-mpls-route-tables", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-mtu", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-network-instance", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-network-instance-mtu", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-next-hop-groups", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ntp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ospf", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-packet-match-types", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-acl", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-chassis", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-control", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-cpu", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-disk", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-fabric", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-fan", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-ip-mpls-fwd-resources", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-lc", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-memory", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-mtu", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-psu", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-platform-qos", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-redundancy", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-platform-resource-monitoring", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-qos", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-rib-bgp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-routing-policy", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-sflow", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-snmp", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-ssh", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-static-routes", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-system", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-system-banner", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-system-bridge-table", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-system-info", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-system-name", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-tcp-udp", + "revision": "2019-11-30" + }, + { + "name": "srl_nokia-timezone", + "revision": "2020-06-30" + }, + { + "name": "srl_nokia-tls", + "revision": "2019-11-30" + } + ] + }, + "srl_nokia-acl:acl": { + "cpm-filter": { + "ipv4-filter": { + "statistics-per-entry": true, + "entry": [ + { + "sequence-id": 10, + "description": "Accept incoming ICMP unreachable messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "dest-unreachable", + "code": [ + 0, + 1, + 2, + 3, + 4, + 13 + ] + } + } + }, + { + "sequence-id": 20, + "description": "Accept incoming ICMP time-exceeded messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "time-exceeded" + } + } + }, + { + "sequence-id": 30, + "description": "Accept incoming ICMP parameter problem messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "param-problem" + } + } + }, + { + "sequence-id": 40, + "description": "Accept incoming ICMP echo messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "echo" + } + } + }, + { + "sequence-id": 50, + "description": "Accept incoming ICMP echo-reply messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "echo-reply" + } + } + }, + { + "sequence-id": 60, + "description": "Accept incoming SSH when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 70, + "description": "Accept incoming SSH when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 80, + "description": "Accept incoming Telnet when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 90, + "description": "Accept incoming Telnet when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 100, + "description": "Accept incoming TACACS+ when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 110, + "description": "Accept incoming TACACS+ when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 120, + "description": "Accept incoming DNS response messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "source-port": { + "operator": "eq", + "value": 53 + } + } + }, + { + "sequence-id": 130, + "description": "Accept incoming DHCP messages targeted for BOOTP/DHCP client", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 68 + } + } + }, + { + "sequence-id": 140, + "description": "Accept incoming TFTP read-request and write-request messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 69 + } + } + }, + { + "sequence-id": 150, + "description": "Accept incoming HTTP(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 160, + "description": "Accept incoming HTTP(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 170, + "description": "Accept incoming NTP messages from servers", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "source-port": { + "operator": "eq", + "value": 123 + } + } + }, + { + "sequence-id": 180, + "description": "Accept incoming SNMP GET/GETNEXT messages from servers", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 161 + } + } + }, + { + "sequence-id": 190, + "description": "Accept incoming BGP when the other router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 200, + "description": "Accept incoming BGP when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 210, + "description": "Accept incoming HTTPS(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 220, + "description": "Accept incoming HTTPS(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "source-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 230, + "description": "Accept incoming single-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 3784 + } + } + }, + { + "sequence-id": 240, + "description": "Accept incoming multi-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 4784 + } + } + }, + { + "sequence-id": 250, + "description": "Accept incoming uBFD session messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "operator": "eq", + "value": 6784 + } + } + }, + { + "sequence-id": 260, + "description": "Accept incoming gNMI messages when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "protocol": "tcp", + "destination-port": { + "operator": "eq", + "value": 57400 + } + } + }, + { + "sequence-id": 270, + "description": "Accept incoming UDP traceroute messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "udp", + "destination-port": { + "range": { + "start": 33434, + "end": 33464 + } + } + } + }, + { + "sequence-id": 280, + "description": "Accept incoming ICMP timestamp messages", + "action": { + "accept": { + } + }, + "match": { + "protocol": "icmp", + "icmp": { + "type": "timestamp" + } + } + }, + { + "sequence-id": 290, + "description": "Drop all else", + "action": { + "drop": { + "log": true + } + } + } + ] + }, + "ipv6-filter": { + "statistics-per-entry": true, + "entry": [ + { + "sequence-id": 10, + "description": "Accept incoming ICMPv6 unreachable messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "dest-unreachable", + "code": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + } + } + }, + { + "sequence-id": 20, + "description": "Accept incoming ICMPv6 packet-too-big messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "packet-too-big" + } + } + }, + { + "sequence-id": 30, + "description": "Accept incoming ICMPv6 time-exceeded messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "time-exceeded" + } + } + }, + { + "sequence-id": 40, + "description": "Accept incoming ICMPv6 parameter problem messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "param-problem" + } + } + }, + { + "sequence-id": 50, + "description": "Accept incoming ICMPv6 echo-request messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "echo-request" + } + } + }, + { + "sequence-id": 60, + "description": "Accept incoming ICMPv6 echo-reply messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "echo-reply" + } + } + }, + { + "sequence-id": 70, + "description": "Accept incoming ICMPv6 router-advertisement messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "router-advertise" + } + } + }, + { + "sequence-id": 80, + "description": "Accept incoming ICMPv6 neighbor-solicitation messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "neighbor-solicit" + } + } + }, + { + "sequence-id": 90, + "description": "Accept incoming ICMPv6 neighbor-advertisement messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "icmp6", + "icmp6": { + "type": "neighbor-advertise" + } + } + }, + { + "sequence-id": 100, + "description": "Accept incoming SSH when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 110, + "description": "Accept incoming SSH when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 22 + } + } + }, + { + "sequence-id": 120, + "description": "Accept incoming Telnet when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 130, + "description": "Accept incoming Telnet when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 23 + } + } + }, + { + "sequence-id": 140, + "description": "Accept incoming TACACS+ when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 150, + "description": "Accept incoming TACACS+ when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 49 + } + } + }, + { + "sequence-id": 160, + "description": "Accept incoming DNS response messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "source-port": { + "operator": "eq", + "value": 53 + } + } + }, + { + "sequence-id": 170, + "description": "Accept incoming TFTP read-request and write-request messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 69 + } + } + }, + { + "sequence-id": 180, + "description": "Accept incoming HTTP(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 190, + "description": "Accept incoming HTTP(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 80 + } + } + }, + { + "sequence-id": 200, + "description": "Accept incoming NTP messages from servers", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "source-port": { + "operator": "eq", + "value": 123 + } + } + }, + { + "sequence-id": 210, + "description": "Accept incoming SNMP GET/GETNEXT messages from servers", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 161 + } + } + }, + { + "sequence-id": 220, + "description": "Accept incoming BGP when the other router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 230, + "description": "Accept incoming BGP when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 179 + } + } + }, + { + "sequence-id": 240, + "description": "Accept incoming HTTPS(JSON-RPC) when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 250, + "description": "Accept incoming HTTPS(JSON-RPC) when this router initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "source-port": { + "operator": "eq", + "value": 443 + } + } + }, + { + "sequence-id": 260, + "description": "Accept incoming DHCPv6 client messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 546 + } + } + }, + { + "sequence-id": 270, + "description": "Accept incoming single-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 3784 + } + } + }, + { + "sequence-id": 280, + "description": "Accept incoming multi-hop BFD session messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 4784 + } + } + }, + { + "sequence-id": 290, + "description": "Accept incoming uBFD session messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "operator": "eq", + "value": 6784 + } + } + }, + { + "sequence-id": 300, + "description": "Accept incoming gNMI messages when the other host initiates the TCP connection", + "action": { + "accept": { + } + }, + "match": { + "next-header": "tcp", + "destination-port": { + "operator": "eq", + "value": 57400 + } + } + }, + { + "sequence-id": 310, + "description": "Accept incoming UDP traceroute messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": "udp", + "destination-port": { + "range": { + "start": 33434, + "end": 33464 + } + } + } + }, + { + "sequence-id": 320, + "description": "Accept incoming IPV6 hop-in-hop messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": 0 + } + }, + { + "sequence-id": 330, + "description": "Accept incoming IPV6 fragment header messages", + "action": { + "accept": { + } + }, + "match": { + "next-header": 44 + } + }, + { + "sequence-id": 340, + "description": "Drop all else", + "action": { + "drop": { + "log": true + } + } + } + ] + } + } + }, + "srl_nokia-interfaces:interface": [ + { + "name": "ethernet-1/1", + "admin-state": "enable", + "subinterface": [ + { + "index": 0, + "admin-state": "enable", + "ipv4": { + "address": [ + { + "ip-prefix": "192.168.0.1/31" + } + ] + }, + "ipv6": { + "address": [ + { + "ip-prefix": "2002::c0a8:1/127" + } + ] + } + } + ] + }, + { + "name": "lo0", + "admin-state": "enable", + "subinterface": [ + { + "index": 0, + "admin-state": "enable", + "ipv4": { + "address": [ + { + "ip-prefix": "10.0.0.2/32" + } + ] + }, + "ipv6": { + "address": [ + { + "ip-prefix": "2002::a00:2/128" + } + ] + } + } + ] + }, + { + "name": "mgmt0", + "admin-state": "enable", + "subinterface": [ + { + "index": 0, + "admin-state": "enable", + "ipv4": { + "srl_nokia-interfaces-ip-dhcp:dhcp-client": { + } + }, + "ipv6": { + "srl_nokia-interfaces-ip-dhcp:dhcp-client": { + } + } + } + ] + } + ], + "srl_nokia-system:system": { + "srl_nokia-aaa:aaa": { + "authentication": { + "authentication-method": [ + "local" + ] + }, + "server-group": [ + { + "name": "local" + } + ] + }, + "srl_nokia-gnmi-server:gnmi-server": { + "admin-state": "enable", + "timeout": 7200, + "rate-limit": 60, + "session-limit": 20, + "network-instance": [ + { + "name": "mgmt", + "admin-state": "enable", + "use-authentication": true, + "port": 57400, + "tls-profile": "tls-profile-1" + } + ] + }, + "srl_nokia-tls:tls": { + "server-profile": [ + { + "name": "tls-profile-1", + "key": "$aes$bLEsze1NwbPw=$YfQ19czmqbmROg9eHqNAM+zAe6VPm7+cGm0H/4N+HD77fmiKWJ6fyDX644dhzy4BhzDyjfh5PkopzMMqtt/AIShyhKqVJALsiPRogWiXyxHZ/6wtXQkFWTRJKYpfAKOnghur5iXXE4TET9jyVE+kjCwnAM1ulsdqFjpnq6uRgS3ScO8AU3DQPT/6PqqJrYEVUcsOk2hAIhmTdTz1eMG88GvSTY8zmDHbrevqgt5wMGQ7cjAdBIbGLKpWlYTgavuUzWySCyZHZrHHhf3nt/EnnCAm9VRLaHmDK7iD3k6Wt0+HjCCA0W0I0KxGUwQMI6N0TBQYwb47VXS4vhThJLHZ8LqTSzZSeHLlAv4MDGh/5Fy2A7nN7zW5lB9c8s/JOkR5imU+2im55mxKx4cuxipSIfELCsK5u78OOpz5PROCp+3pOYQTRwhAsdbcHz+K0JrXUVpUD+SswTZ6JLcTrQABR9juAgqGnsDNCbgw54IHFiBskTV93OYJ0LRxxkhN6ySRtuY8RY9Y8ToAHGzpk6TMwUvmW3VgFp8B/ntWWsHks1xc44lFQYxwK4+8k0+2Npo95CfcLuB0S1Roqo5qfhswwKBXRzA/HqjNhrM2RylYuyMkfwv0ABPb7JD17sdhsGbvmutLKlGxLyYE0+Jcq20bsMYvO1Sg/mkZbknBxLQZn3unTKq41fb6F7RU/OGLcnR9PEu/wg46GltDFdjtXc4MlbmxrSdY+3fN523L6TWV0N7HoU2H0ZqWHcLIc8pSKZ0ucHNhtOBrT+nu9IkQif12IeFuu63rDor/RS7lWjr9EAhuoqL/XGtWOpHHlwYuPN9JeTFtGKVdAsaUWAYUSu6MUJzOb2o45deEq76wvHIX2lhIsT80u+Hh2mzWLBke6yRNuUOJgI/os/PQp2RfhboO9gIGGIhlpq1zl2ovotOvcoM2wzORdhTmU/tcHYvHQlZ5oImLUXNRwVKxTqhDpM+T7jf0K3/02N67AnLAicPeYGA8zGTHhcURjPaQrod4WfvY2NqeU5cDnG9hzFCv0Czj4xpD/qWdtl6j9d5NQnLNPiRUYQBcEsX+ju+KlGvC2+aa2wI41/Gomyyjnkwu5t82aoRcDUDS377FG6/QpBPrDqMsugKA8Mvjb5qys3UOPNVMqfCGsFjJ8jA/um2/QDp845kZOl2bhw4Fx3PjzBGhjpRsqFh/3NErbB7JBSiIEvZl2tkT2blJiq9dOBFWeo7Ami7prUhehkQsr8j9CmNu8W1RWcGxOLbr3pHpCA9itrr88siF1gzL376taIvzK8dOe2nMTYLwOt3FUn2qMQtzsXqSBhnd09cpmYXTtnEW0c2b6DvfxkmtVi8dRlwjGoGfpVz42QZFhVCf97kiAbfPbE3xlF5pQXHYtUDMQfyGEuAp/PWWpir1eQucy3Lbe2yu25vJiZs/hReZWyj/dTD0XMJSlBPBLimPwRpfTFQMMxUxFFUtFQSauvhdQu/Acz3ACF0e59/r5hwEzLX3CE6p6jVqpj08PdFa4PNnXb1HybynYHaBx1tvMdiO1W8mmQUO+G4kmbvhV+Izsj5WBos5vtqJ9BDbESw1bviQRBsIHj74RtykjyMVYixtzkghBdn1BZGrTzQxV8LCh99Nh65R7MrparFWOTjOZJ/bh4GQU0xagI9uIsbngIm1yxD2N0/ik5Xfae+w+0pE0Hs4qNEfhM+chK1gpB2TcV7W8X/UMU6VakdeyPigByQJzLAmHNFmbcJ25fpdl2gbK2GeNBfSG0ILC8CrE8PAnLjdc4XtSj9jgJbsGzco+jVlFTbocwFMScGceVvR8aMI/gnj+IT3HTEylXC/gV/RRWe1sAR7xnk+Oj18fDFWfnKwAqOYOfrzh+Cyj/CCnW2yzvB3VW76AeN4CnqHwOuQgUQ1n/kfkR8ikHb5lioKexD4Gxlp1SEPQ5C2noexXKe9arQROK96wDaRjUlVBTjDZ9IHohWbhMm2XyjZdEoUM4Q2PEVcjmyQueUX6kEOZhWZPbdwZlNWAfG1UHXAP+bciZcE9PzG4CbUEdrLY57rQflRc3XrOvpOG55sPd8q0xNGOx38fAl1Mi/jpfXzNUAcztAohLbOBjODK13xjXc8vsLMclCyVPL91sjyqiEqU+uHNWD69ETt1ddDiRteG33FfriSsp4LOeBUF9/B2hPC3klN4E/gj19dRQUVhbmYNLi/qw+Vxm+FkDHRQxbbDTXnxSjFqIyRl+uj", + "certificate": "-----BEGIN CERTIFICATE-----\nMIID/jCCAuagAwIBAgIUazBo8B6WStGAng0M1Dg2UL6nvXUwDQYJKoZIhvcNAQEL\nBQAwXzELMAkGA1UEBhMCQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5v\na2lhMRYwFAYDVQQLEw1Db250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwwMiBSb290\nIENBMB4XDTIwMTIwMTIwMDYwMFoXDTIxMTIwMTIwMDYwMFowXzELMAkGA1UEBhMC\nQkUxEDAOBgNVBAcTB0FudHdlcnAxDjAMBgNVBAoTBU5va2lhMRYwFAYDVQQLEw1D\nb250YWluZXIgbGFiMRYwFAYDVQQDEw1zcmwyLnNybDAyLmlvMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxP4V14LLTpytEZUxYM+T21c3fo57Pu8gVElL\n2aEPHm9ya+5LQRQ1TP9UvjmY9HeTVde8rIEpwNAABUQc4pWNcklK8amz3BW9egtg\nTGbyx/Y4vbCRkuKIcQtREkhEYLun73E/2Q7ZFuzZQ5Zpxvfxt0ye+BJlxIin3bNK\nVitnpuqQdbK2/8/F/tN0oC6eEJr9p5jaXspzKOdwqI/nMC8CfNAkQ6N6/6hp6Aal\n3K2GAeVQqf+QZMd++Qx0JZNtajTQ53YYTEXRoRSWznQzmSb4o+TiZ7X2oWnO8Rl8\nfWp95vf1GcYJbmH25gm4eAREoidYCgReL5X04VMKt+3tTuf1rQIDAQABo4GxMIGu\nMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\nDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUHNanRdJb1OB1S8yaFP9Zv9bqpsswHwYD\nVR0jBBgwFoAU/UNXlN/3/ij9bWwTiqAP3Gh4mmowLwYDVR0RBCgwJoIEc3JsMoIP\nY2xhYi1zcmwwMi1zcmwygg1zcmwyLnNybDAyLmlvMA0GCSqGSIb3DQEBCwUAA4IB\nAQAzRziFw9hT83HudL7qa10FUdh2stgSPdVO9PlIHKY0rAJOW5ywGaYLi4/ADL4Z\nA853QY/KkqqF4Ov9rEi2+UsnlGINUQ8VbNsnBH+sF53nJFalMlq1/2LBmG1c2GsD\nZ2/YqbQTIMKlCkVjGI2rNWjFpVAvhZhJuwmvzA4iGUmXaaS0sv6i4N/9U7JOY6Ef\nJcKTPts+DHeyYKvD5lho/t6HZpnt/WzzMy7E9TcevOs1yOPhiNjnEp18DW4jrZhd\nQseBSTwx/bwm1z5KycJGYVdalKCDyHvzQyesd4lSq2tOWm4nzi+GLzm+wZa2bxDB\nVhdEh61VwMDNoBx4aszjn7gN\n-----END CERTIFICATE-----\n", + "authenticate-client": false + } + ] + }, + "srl_nokia-ssh:ssh-server": { + "network-instance": [ + { + "name": "mgmt", + "admin-state": "enable" + } + ] + }, + "srl_nokia-lldp:lldp": { + "admin-state": "enable" + }, + "srl_nokia-logging:logging": { + "network-instance": "mgmt", + "buffer": [ + { + "buffer-name": "messages", + "rotate": 3, + "size": "10000000", + "facility": [ + { + "facility-name": "local6", + "priority": { + "match-above": "informational" + } + } + ] + }, + { + "buffer-name": "system", + "facility": [ + { + "facility-name": "auth", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "cron", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "daemon", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "ftp", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "kern", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "lpr", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "mail", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "news", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "syslog", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "user", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "uucp", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local0", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local1", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local2", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local3", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local4", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local5", + "priority": { + "match-above": "warning" + } + }, + { + "facility-name": "local7", + "priority": { + "match-above": "warning" + } + } + ] + } + ], + "file": [ + { + "file-name": "messages", + "rotate": 3, + "size": "10000000", + "facility": [ + { + "facility-name": "local6", + "priority": { + "match-above": "warning" + } + } + ] + } + ] + } + }, + "srl_nokia-network-instance:network-instance": [ + { + "name": "default", + "interface": [ + { + "name": "ethernet-1/1.0" + } + ] + }, + { + "name": "mgmt", + "type": "srl_nokia-network-instance:ip-vrf", + "admin-state": "enable", + "description": "Management network instance", + "interface": [ + { + "name": "mgmt0.0" + } + ], + "protocols": { + "srl_nokia-linux:linux": { + "import-routes": true, + "export-routes": true, + "export-neighbors": true + } + } + } + ] +} diff --git a/mkdocs.yml b/mkdocs.yml index d48b1de31..51251d837 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,7 +2,22 @@ site_name: containerlab nav: - Home: index.md - Installation: install.md - - Usage: usage.md + - Quick start: quickstart.md + - User manual: + - Topology definition: manual/topo-def-file.md + - Nodes: manual/nodes.md + - Kinds: + - About: manual/kinds/kinds.md + - srl - Nokia SR Linux: manual/kinds/srl.md + - Configuration artifacts: manual/conf-artifacts.md + - Network wiring concepts: manual/network.md + - Command reference: + - deploy: cmd/deploy.md + - destroy: cmd/destroy.md + - inspect: cmd/inspect.md + - save: cmd/save.md + - generate: cmd/generate.md + - graph: cmd/graph.md - Lab examples: - About: lab-examples/lab-examples.md - Single SR Linux node: lab-examples/single-srl.md @@ -34,8 +49,8 @@ theme: language: en palette: scheme: preference - primary: black - accent: pink + primary: white + accent: cyan font: text: Manrope code: Fira Mono @@ -92,3 +107,7 @@ markdown_extensions: - pymdownx.tasklist: custom_checkbox: true - pymdownx.tilde + +google_analytics: + - UA-101537614-3 + - auto diff --git a/templates/arista/ceos.cfg.tpl b/templates/arista/ceos.cfg.tpl new file mode 100644 index 000000000..83167d0b1 --- /dev/null +++ b/templates/arista/ceos.cfg.tpl @@ -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 \ No newline at end of file diff --git a/templates/graph/index.html b/templates/graph/index.html new file mode 100644 index 000000000..cfad77069 --- /dev/null +++ b/templates/graph/index.html @@ -0,0 +1,315 @@ + + + + + + ContainerLab Topology '{{ .Name }}' + + + + +
+

ContainerLab Topology '{{ .Name }}'

+
+ +
+ +
+ + + + + + + + +
+ + \ No newline at end of file