Skip to content

Commit

Permalink
Inspect: fixes all, by name and topology file (#1984)
Browse files Browse the repository at this point in the history
* inspect: fixes all, by name and topology file

* use topology file if provided not just the label

* added integration tests for basic inspect command

* wrongly added topo

* more accidentally not ignored stuff

* added a test for the inspect command with ext containers

* remove grepping for the lab node to make sure the tables doesn't have extra lines

* address linter complaints

---------

Co-authored-by: Roman Dodin <dodin.roman@gmail.com>
  • Loading branch information
steiler and hellt committed Apr 11, 2024
1 parent 28d8acf commit d455379
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 16 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ tests/out
# ignore backup clab topo files
**/.*clab.y*ml.bak
**/*.y*ml.bak

.python-version
8 changes: 5 additions & 3 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ func (c *CLab) createWaitForDependency() error {
return nil
}

// skipcq: GO-R1005
func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int, skipPostDeploy bool) (*sync.WaitGroup, *exec.ExecCollection) {
concurrentChan := make(chan *depMgr.DependencyNode)

Expand Down Expand Up @@ -828,7 +829,7 @@ func (c *CLab) ListContainers(ctx context.Context, filter []*types.GenericFilter
}

// ListNodesContainers lists all containers based on the nodes stored in clab instance.
func (c *CLab) listNodesContainers(ctx context.Context) ([]runtime.GenericContainer, error) {
func (c *CLab) ListNodesContainers(ctx context.Context) ([]runtime.GenericContainer, error) {
var containers []runtime.GenericContainer

for _, n := range c.Nodes {
Expand Down Expand Up @@ -882,7 +883,7 @@ func (c *CLab) getLinkNodes() map[string]links.Node {
// GetSpecialLinkNodes returns a map of special nodes that are used to resolve links.
// Special nodes are host and mgmt-bridge nodes that are not typically present in the topology file
// but are required to resolve links.
func (c *CLab) getSpecialLinkNodes() map[string]links.Node {
func (*CLab) getSpecialLinkNodes() map[string]links.Node {
// add the virtual host and mgmt-bridge nodes to the resolve nodes
specialNodes := map[string]links.Node{
"host": links.GetHostLinkNode(),
Expand Down Expand Up @@ -957,6 +958,7 @@ func (c *CLab) extractDNSServers(filesys fs.FS) error {
}

// Deploy the given topology.
// skipcq: GO-R1005
func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.GenericContainer, error) {
var err error

Expand Down Expand Up @@ -1088,7 +1090,7 @@ func (c *CLab) Deploy(ctx context.Context, options *DeployOptions) ([]runtime.Ge
}
}

containers, err := c.listNodesContainers(ctx)
containers, err := c.ListNodesContainers(ctx)
if err != nil {
return nil, err
}
Expand Down
11 changes: 8 additions & 3 deletions clab/hostsfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (c *CLab) appendHostsFileEntries(ctx context.Context) error {
return err
}

containers, err := c.listNodesContainers(ctx)
containers, err := c.ListNodesContainers(ctx)
if err != nil {
return err
}
Expand All @@ -53,11 +53,13 @@ func (c *CLab) appendHostsFileEntries(ctx context.Context) error {
return err
}

defer f.Close()
defer f.Close() // skipcq: GO-S2307

_, err = f.Write(data)
if err != nil {
return err
}

return nil
}

Expand All @@ -84,18 +86,21 @@ func generateHostsEntries(containers []runtime.GenericContainer, labname string)
entries.Write(v6entries.Bytes())
fmt.Fprintf(&entries, clabHostEntryPostfix, labname)
entries.WriteByte('\n')

return entries.Bytes()
}

func (c *CLab) DeleteEntriesFromHostsFile() error {
if c.Config.Name == "" {
return errors.New("missing containerlab name")
}

f, err := os.OpenFile(clabHostsFilename, os.O_RDWR, 0644) // skipcq: GSC-G302
if err != nil {
return err
}
defer f.Close()
defer f.Close() // skipcq: GO-S2307

reader := bufio.NewReader(f)
skiplines := false
output := bytes.Buffer{}
Expand Down
9 changes: 3 additions & 6 deletions cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,21 @@ func inspectFn(_ *cobra.Command, _ []string) error {
)
}

if name != "" {
opts = append(opts, clab.WithLabName(name))
}

c, err := clab.NewContainerLab(opts...)
if err != nil {
return fmt.Errorf("could not parse the topology file: %v", err)
}

var containers []runtime.GenericContainer
var glabels []*types.GenericFilter

// if the topo file is available, use it
if topo != "" {
containers, err = c.ListContainers(ctx, nil)
containers, err = c.ListNodesContainers(ctx)
if err != nil {
return fmt.Errorf("failed to list containers: %s", err)
}
} else {
var glabels []*types.GenericFilter
// or when just the name is given
if name != "" {
// if name is set, filter for name
Expand All @@ -106,6 +102,7 @@ func inspectFn(_ *cobra.Command, _ []string) error {
Field: labels.Containerlab, Operator: "exists",
}}
}

containers, err = c.ListContainers(ctx, glabels)
if err != nil {
return fmt.Errorf("failed to list containers: %s", err)
Expand Down
3 changes: 3 additions & 0 deletions docs/manual/dev/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Developers Guide

This is a start of the developers documentation. Stay tuned for more content!
71 changes: 71 additions & 0 deletions docs/manual/dev/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Testing Containerlab

Containerlab's test program largely consists of:

- Go-based unit tests
- RobotFramework-based integration tests

## Integration Tests

The integration tests are written in RobotFramework and are located in the [`tests`][tests-dir] directory. The tests are run using the [`rf-run`][rf-run] command that wraps `robot` command. The tests are run in a Docker container, so you don't need to install RobotFramework on your local machine.

### Local execution

To execute the integration tests locally you have to install the python environment with the required dependencies. If you're using `pyenv` you can use the following commands:

1. Create a venv and activate it

```bash
pyenv virtualenv 3.11 clab-rf
pyenv shell clab-rf
```

2. Install the dependencies

```bash
pip install -r tests/requirements.txt
```

Usually you would run the tests using the locally built containerlab binary that contains the unreleased changes. The typical workflow then starts with building the containerlab binary:

```bash
make build
```

The newly built binary is located in the `bin` directory. In order to let the test runner script know where to find the binary, you have to set the `CLAB_BIN` environment variable before calling the `rf-run` script:

```bash
CLAB_BIN=$(pwd)/bin/containerlab ./tests/rf-run.sh <runtime> <test suite>
```

/// note
The test runner script requires you to specify the runtime as its first argument. The runtime can be either `docker` or `podman`. Containerlab primarily uses Docker as the default runtime, hence the number of tests written for docker outnumber the podman tests.
///

#### Selecting the test suite

Containerlab's integration tests are grouped by a topic, and each topic is mapped to a directory under the [`tests`][tests-dir] directory and RobotFramework allows for a flexible selection of tests/test suites to run. For example, to run all the smoke test cases, you can use the following command:

```bash
CLAB_BIN=$(pwd)/bin/containerlab ./tests/rf-run.sh docker tests/01-smoke
```

since [`01-smoke`][01-smoke-dir] is a directory containing all the smoke test suites.

Consequently, in order to run a specific test suite you just need to provide a path to it. E.g. running the `01-basic-flow.robot` test suite from the `01-smoke` directory:

```bash
CLAB_BIN=$(pwd)/bin/containerlab ./tests/rf-run.sh docker tests/01-smoke/01-basic-flow.robot
```

/// note
Selecting a specific test case in a test suite is not supported, since test suites are written in a way that test cases depend on previous ones.
///

#### Inspecting the test results

RobotFramework generates a detailed report in HTML and XML formats that can be found in the `tests/out` directory. The exact paths to the reports are printed to the console after the test run.

[tests-dir]: https://github.com/srl-labs/containerlab/tree/main/tests
[rf-run]: https://github.com/srl-labs/containerlab/blob/main/tests/rf-run.sh
[01-smoke-dir]: https://github.com/srl-labs/containerlab/tree/main/tests/01-smoke
3 changes: 3 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ nav:
- Certificate management: manual/cert.md
- Inventory: manual/inventory.md
- Image management: manual/images.md
- Developers guide:
- manual/dev/index.md
- Testing: manual/dev/test.md
- Command reference:
- deploy: cmd/deploy.md
- destroy: cmd/destroy.md
Expand Down
7 changes: 5 additions & 2 deletions tests/01-smoke/01-basic-flow.robot
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Ensure CLAB_INTFS env var is set
# the result is printed today.
Should Contain ${output.stderr} stdout:\\n3

Inspect ${lab-name} lab
Inspect ${lab-name} lab using its name
${rc} ${output} = Run And Return Rc And Output
... sudo -E ${CLAB_BIN} --runtime ${runtime} inspect --name ${lab-name}
Log ${output}
Expand Down Expand Up @@ -271,7 +271,10 @@ Verify iptables allow rule is set
${ipt} = Run
... sudo iptables -vnL DOCKER-USER
Log ${ipt}
Should Contain ${ipt} ACCEPT all -- * ${MgmtBr}
# debian 12 uses `0` for protocol, while previous versions use `all`
Should Contain Any ${ipt}
... ACCEPT all -- * ${MgmtBr}
... ACCEPT 0 -- * ${MgmtBr}
... ignore_case=True
... collapse_spaces=True

Expand Down
43 changes: 41 additions & 2 deletions tests/01-smoke/02-destroy-all.robot
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,67 @@ Suite Teardown Run sudo -E ${CLAB_BIN} --runtime ${runtime} destroy --al

*** Variables ***
${runtime} docker
${lab1-file} 01-linux-nodes.clab.yml
${lab1-name} 2-linux-nodes
${lab2-file} 01-linux-single-node.clab.yml
${lab2-name} single-node
# ${orig_dir} ${EMPTY}


*** Test Cases ***
Deploy first lab
${result} = Run Process
... sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/01-linux-nodes.clab.yml
... sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab1-file}
... shell=True
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Exist %{PWD}/clab-2-linux-nodes

Set Suite Variable ${orig_dir} ${CURDIR}

Deploy second lab
${result} = Run Process
... sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/01-linux-single-node.clab.yml
... sudo -E ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab2-file}
... cwd=/tmp # using a different cwd to check lab resolution via container labels
... shell=True
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Exist /tmp/clab-single-node

Inspect ${lab2-name} lab using its name
${rc} ${output} = Run And Return Rc And Output
... sudo -E ${CLAB_BIN} --runtime ${runtime} inspect --name ${lab2-name}
Log ${output}
Should Be Equal As Integers ${rc} 0

${num_lines} = Run bash -c "echo '${output}' | wc -l"
# lab2 only has 1 nodes and therefore inspect output should contain only 1 node (+4 lines for the table header and footer)
Should Be Equal As Integers ${num_lines} 5

Inspect ${lab2-name} lab using topology file reference
${result} = Run Process
... sudo -E ${CLAB_BIN} --runtime ${runtime} inspect -t ${orig_dir}/${lab2-file}
... shell=True
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0

${num_lines} = Run bash -c "echo '${result.stdout}' | wc -l"
# lab2 only has 1 nodes and therefore inspect output should contain only 1 node
Should Be Equal As Integers ${num_lines} 5

Inspect all
${rc} ${output} = Run And Return Rc And Output
... sudo -E ${CLAB_BIN} --runtime ${runtime} inspect --all
Log ${output}
Should Be Equal As Integers ${rc} 0

${num_lines} = Run bash -c "echo '${output}' | wc -l"
# 3 nodes in lab1 and 1 node in lab2 (+4 lines for the header and footer)
Should Be Equal As Integers ${num_lines} 8

Verify host mode networking for node l3
# l3 node is launched with host mode networking
# since it is the nginx container, by launching it in the host mode
Expand Down
12 changes: 12 additions & 0 deletions tests/06-ext-container/01-ext-container.robot
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ Verify ip and thereby exec on ext1
Should Be Equal As Integers ${rc} 0
Should Contain ${output} 192.168.0.1/24

Inspect the lab using topology file reference
${result} = Run Process
... sudo -E ${CLAB_BIN} --runtime ${runtime} inspect -t ${CURDIR}/${lab-file-name}
... shell=True
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0

${num_nodes} = Run bash -c "echo '${result.stdout}' | wc -l"
# the inspect command should return only two external nodes (+4 lines for the header and footer)
Should Be Equal As Integers ${num_nodes} 6

Verify links in node ext2
${rc} ${output} = Run And Return Rc And Output
... sudo -E ${CLAB_BIN} --runtime ${runtime} exec --label clab-node-name\=ext2 --cmd "ip link show dev eth1"
Expand Down

0 comments on commit d455379

Please sign in to comment.