diff --git a/.gitignore b/.gitignore index 8d73054fb..b4f2394f8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ tests/out # ignore backup clab topo files **/.*clab.y*ml.bak **/*.y*ml.bak + +.python-version diff --git a/clab/clab.go b/clab/clab.go index a1485253e..74012e8a5 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -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) @@ -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 { @@ -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(), @@ -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 @@ -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 } diff --git a/clab/hostsfile.go b/clab/hostsfile.go index 156f36a7f..9dc04c23a 100644 --- a/clab/hostsfile.go +++ b/clab/hostsfile.go @@ -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 } @@ -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 } @@ -84,6 +86,7 @@ func generateHostsEntries(containers []runtime.GenericContainer, labname string) entries.Write(v6entries.Bytes()) fmt.Fprintf(&entries, clabHostEntryPostfix, labname) entries.WriteByte('\n') + return entries.Bytes() } @@ -91,11 +94,13 @@ 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{} diff --git a/cmd/inspect.go b/cmd/inspect.go index 37607a6c5..11044c71b 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -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 @@ -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) diff --git a/docs/manual/dev/index.md b/docs/manual/dev/index.md new file mode 100644 index 000000000..d43e9e27c --- /dev/null +++ b/docs/manual/dev/index.md @@ -0,0 +1,3 @@ +# Developers Guide + +This is a start of the developers documentation. Stay tuned for more content! diff --git a/docs/manual/dev/test.md b/docs/manual/dev/test.md new file mode 100644 index 000000000..b26ea1bc2 --- /dev/null +++ b/docs/manual/dev/test.md @@ -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 +``` + +/// 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 diff --git a/mkdocs.yml b/mkdocs.yml index b3419b213..beffba417 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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 diff --git a/tests/01-smoke/01-basic-flow.robot b/tests/01-smoke/01-basic-flow.robot index 8a8317cce..98ac505f1 100644 --- a/tests/01-smoke/01-basic-flow.robot +++ b/tests/01-smoke/01-basic-flow.robot @@ -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} @@ -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 diff --git a/tests/01-smoke/02-destroy-all.robot b/tests/01-smoke/02-destroy-all.robot index 14cecda33..0f639a643 100644 --- a/tests/01-smoke/02-destroy-all.robot +++ b/tests/01-smoke/02-destroy-all.robot @@ -15,21 +15,28 @@ 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} @@ -37,6 +44,38 @@ Deploy second lab 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 diff --git a/tests/06-ext-container/01-ext-container.robot b/tests/06-ext-container/01-ext-container.robot index c3cef56cc..28c438252 100644 --- a/tests/06-ext-container/01-ext-container.robot +++ b/tests/06-ext-container/01-ext-container.robot @@ -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"