Skip to content

Commit

Permalink
Implement clab save for IOL. (#2403)
Browse files Browse the repository at this point in the history
* Simplify `AllowedIntfRegexp` regexp

There was redundant regex for the `eX/X` and `EthernetX/X` checks.

The regexp has been simplified so "e" vs "Ethernet" and the slot/port checks are in two separate capture groups.

* Implement "clab save" for IOL

`SaveConfig()` func is used which allows the "clab save" command to be used.

Uses the scrapligo network driver to send a basic `write memory` command which copies the running config -> startup config.

* Add tests for "clab save" on IOL

Some basic tests which use "containerlab save" to save all running configurations to the startup-configuration (in NVRAM).

The test then verifies the config was saved to the startup-config by checking the startup-config location contains a configuration with the nodes hostname.

* Add IOL to 'clab save' doc table

* Fix test path

* Makefile: Don't require specific value for `PUBLIC` in `serve-docs-full`

* Fix regexp to make codecov happy

* Polish
  • Loading branch information
kaelemc authored Jan 17, 2025
1 parent afc759e commit 0bd6b5a
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ site:
# when PUBLIC=yes is not set, the mkdocs-material insiders image is used with all the dependencies included.
.PHONY: serve-docs-full
serve-docs-full:
ifeq ($(PUBLIC),yes)
ifdef PUBLIC
@{ \
sed -i 's/^ - typeset/#- typeset/g' mkdocs.yml; \
}
Expand Down
1 change: 1 addition & 0 deletions docs/cmd/save.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The exact command that performs configuration save depends on a given kind. The
| **Nokia SR Linux** | `sr_cli -d tools system configuration save` | |
| **Nokia SR OS** | | delivered via netconf RPC `copy-config running startup` |
| **Arista cEOS** | `Cli -p 15 -c wr` | |
| **Cisco IOL** | `write memory` | |

### Usage

Expand Down
49 changes: 43 additions & 6 deletions nodes/iol/iol.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (

"github.com/hairyhenderson/gomplate/v3"
"github.com/hairyhenderson/gomplate/v3/data"
"github.com/scrapli/scrapligo/driver/options"
"github.com/scrapli/scrapligo/platform"
log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/links"
"github.com/srl-labs/containerlab/nodes"
Expand Down Expand Up @@ -47,11 +49,11 @@ var (
// IntfRegexp with named capture groups for extracting slot and port.
CapturingIntfRegexp = regexp.MustCompile(`(?:e|Ethernet)\s?(?P<slot>\d+)/(?P<port>\d+)$`)
// ethX naming is the "raw" or "default" interface naming.
DefaultIntfRegexp = regexp.MustCompile(`eth[1-9][0-9]*$`)
DefaultIntfRegexp = regexp.MustCompile(`eth[1-9]\d*$`)
// Match on the management interface.
MgmtIntfRegexp = regexp.MustCompile(`(eth0|e0/0|Ethernet0/0)$`)
// Matches on any allowed/legal interface name.
AllowedIntfRegexp = regexp.MustCompile("Ethernet((0/[1-3])|([1-9]/[0-3]))$|e((0/[1-3])|([1-9]/[0-9]))$|eth[1-9][0-9]*$")
AllowedIntfRegexp = regexp.MustCompile(`(e|Ethernet)((0/[123])|([1-9]/[0-3]))$|eth[1-9]\d*$`)
IntfHelpMsg = "Interfaces should follow Ethernet<slot>/<port> or e<slot>/<port> naming convention, where <slot> is a number from 0-9 and <port> is a number from 0-3. You can also use ethX-based interface naming."

validTypes = []string{typeIOL, typeL2}
Expand Down Expand Up @@ -138,7 +140,7 @@ func (n *iol) PreDeploy(ctx context.Context, params *nodes.PreDeployParams) erro
return n.CreateIOLFiles(ctx)
}

func (n *iol) PostDeploy(ctx context.Context, params *nodes.PostDeployParams) error {
func (n *iol) PostDeploy(ctx context.Context, _ *nodes.PostDeployParams) error {
log.Infof("Running postdeploy actions for Cisco IOL '%s' node", n.Cfg.ShortName)

n.GenBootConfig(ctx)
Expand Down Expand Up @@ -179,7 +181,7 @@ func (n *iol) GenInterfaceConfig(_ context.Context) error {
slot, port := 0, 0

// Regexp to pull number out of linux'ethX' interface naming
IntfRegExpr := regexp.MustCompile("[0-9]+")
IntfRegExpr := regexp.MustCompile(`\d+`)

for _, intf := range n.Endpoints {

Expand Down Expand Up @@ -272,7 +274,7 @@ type IOLTemplateData struct {
PartialCfg string
}

// IOLinterface struct stores mapping info between
// IOLInterface struct stores mapping info between
// IOL interface name and linux container interface.
type IOLInterface struct {
IfaceName string
Expand All @@ -281,7 +283,7 @@ type IOLInterface struct {
Port int
}

func (n *iol) GetMappedInterfaceName(ifName string) (string, error) {
func (*iol) GetMappedInterfaceName(ifName string) (string, error) {
captureGroups, err := utils.GetRegexpCaptureGroups(CapturingIntfRegexp, ifName)
if err != nil {
return "", err
Expand Down Expand Up @@ -378,3 +380,38 @@ func (n *iol) UpdateMgmtIntf(ctx context.Context) error {

return n.Runtime.WriteToStdinNoWait(ctx, n.Cfg.ContainerID, []byte(mgmt_str))
}

// SaveConfig is used for "clab save" functionality -- it saves the running config to the startup configuration
func (n *iol) SaveConfig(_ context.Context) error {
p, err := platform.NewPlatform(
"cisco_iosxe",
n.Cfg.LongName,
options.WithAuthNoStrictKey(),
options.WithAuthUsername(defaultCredentials.GetUsername()),
options.WithAuthPassword(defaultCredentials.GetPassword()),
)

if err != nil {
return fmt.Errorf("failed to create platform; error: %+v", err)
}

d, err := p.GetNetworkDriver()
if err != nil {
return fmt.Errorf("failed to fetch network driver from the platform; error: %+v", err)
}

err = d.Open()
if err != nil {
return fmt.Errorf("failed to open driver; error: %+v", err)
}

defer d.Close()

_, err = d.SendCommand("write memory")
if err != nil {
return fmt.Errorf("failed to send command; error: %+v", err)
}

log.Infof("Successfully copied running configuration to startup configuration file for node: %q\n", n.Cfg.ShortName)
return nil
}
42 changes: 24 additions & 18 deletions tests/10-basic-cisco_iol/01-iol.robot
Original file line number Diff line number Diff line change
Expand Up @@ -53,36 +53,42 @@ Verify full startup configuration on router3
Should Be Equal As Integers ${rc} 0
Should Contain ${output} FULL_STARTUP_CFG-router3

Write configuration to NVRAM on router1
Save running-config to startup-config to NVRAM with clab save
${rc} ${output} = Run And Return Rc And Output
... sshpass -p "admin" ssh -o "IdentitiesOnly=yes" admin@clab-iol-router1 "write memory"
... sudo -E ${CLAB_BIN} --runtime ${runtime} save -t ${CURDIR}/${lab-file-name}
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Contain ${output} [OK]

Verify startup-config is saved to NVRAM on router1
${rc} ${output} = Run And Return Rc And Output
... sshpass -p "admin" ssh -o "IdentitiesOnly=yes" admin@clab-iol-router1 "sh startup-configuration | inc hostname"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Contain ${output} router1

Verify startup-config is saved to NVRAM on switch
${rc} ${output} = Run And Return Rc And Output
... sshpass -p "admin" ssh -o "IdentitiesOnly=yes" admin@clab-iol-switch "sh startup-configuration | inc hostname"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Contain ${output} switch

Log IP addresses for router1
${rc} ${ipv4_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq '.nodes.router1."mgmt-ipv4-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq '.nodes.router1."mgmt-ipv4-address"'
Log \n--> LOG: IPv4 addr - ${ipv4_addr} console=True
Should Be Equal As Integers ${rc} 0
${rc} ${ipv6_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq '.nodes.router1."mgmt-ipv6-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq '.nodes.router1."mgmt-ipv6-address"'
Log \n--> LOG: IPv6 addr - ${ipv6_addr} console=True

Write configuration to NVRAM on switch
${rc} ${output} = Run And Return Rc And Output
... sshpass -p "admin" ssh -o "IdentitiesOnly=yes" admin@clab-iol-switch "write memory"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Contain ${output} [OK]

Log IP addresses for switch
${rc} ${ipv4_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq '.nodes.switch."mgmt-ipv4-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq '.nodes.switch."mgmt-ipv4-address"'
Log \n--> LOG: IPv4 addr - ${ipv4_addr} console=True
Should Be Equal As Integers ${rc} 0
${rc} ${ipv6_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq '.nodes.switch."mgmt-ipv6-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq '.nodes.switch."mgmt-ipv6-address"'
Log \n--> LOG: IPv6 addr - ${ipv6_addr} console=True

Destroy ${lab-name} lab
Expand All @@ -104,12 +110,12 @@ Wait 60s for nodes to boot

Verify connectivity via new management addresses on router1
${rc} ${ipv4_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq -r '.nodes.router1."mgmt-ipv4-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq -r '.nodes.router1."mgmt-ipv4-address"'
Should Be Equal As Integers ${rc} 0
Log \n--> LOG: IPv4 addr - ${ipv4_addr} console=True

${rc} ${ipv6_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq -r '.nodes.router1."mgmt-ipv6-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq -r '.nodes.router1."mgmt-ipv6-address"'
Should Be Equal As Integers ${rc} 0
Log \n--> LOG: IPv6 addr - ${ipv6_addr} console=True

Expand All @@ -122,12 +128,12 @@ Verify connectivity via new management addresses on router1

Verify connectivity via new management addresses on switch
${rc} ${ipv4_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq -r '.nodes.switch."mgmt-ipv4-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq -r '.nodes.switch."mgmt-ipv4-address"'
Should Be Equal As Integers ${rc} 0
Log \n--> LOG: IPv4 addr - ${ipv4_addr} console=True

${rc} ${ipv6_addr} = Run And Return Rc And Output
... cat clab-${lab-name}/topology-data.json | jq -r '.nodes.switch."mgmt-ipv6-address"'
... cat ${CURDIR}/clab-${lab-name}/topology-data.json | jq -r '.nodes.switch."mgmt-ipv6-address"'
Should Be Equal As Integers ${rc} 0
Log \n--> LOG: IPv6 addr - ${ipv6_addr} console=True

Expand Down

0 comments on commit 0bd6b5a

Please sign in to comment.