Compare commits

...

30 Commits

Author SHA1 Message Date
Pim van Pelt
043d58675d Fix test name 2026-04-05 17:50:37 +02:00
Pim van Pelt
dfbe5815c9 Revert last commit, we need to wait on ARP reply 2026-04-05 17:42:48 +02:00
Pim van Pelt
677ef6a5e3 Faster ping interval, faster test results 2026-04-05 17:41:06 +02:00
Pim van Pelt
f6afc4212c Switch to the stable image 2026-04-05 17:34:55 +02:00
Pim van Pelt
9fc41679fd Add a release pipeline including tests for Bird2 and VPP 2026-04-05 17:34:30 +02:00
Pim van Pelt
a7fb7db978 Move to interface-based OSPF config 2026-04-05 17:21:51 +02:00
Pim van Pelt
d623bbd79f Invert logic to detect clab 0.75 2026-04-04 05:22:07 +02:00
Pim van Pelt
bb4e55c062 Allow container to interop with pre 0.75 containerlab 2026-04-04 05:04:31 +02:00
Pim van Pelt
d8369e2c10 Initialize /config from /etc, except for existing files (like bind-mounts). Move to using /config in VPP, even though there is a backwards compat symlink from /etc/{frr,bird,vpp} to /config 2026-04-02 00:16:49 +02:00
Pim van Pelt
039d09d358 Consolidate FRR+Bird+VPP into /config for bind-mounting; Move to multitool for the linux client, it has SSH 2026-04-01 22:39:08 +02:00
Pim van Pelt
ad72dae812 Add python and crypto 2026-03-29 20:21:23 +02:00
Pim van Pelt
77ed63e577 Refactor docs, add buildx multiarch in its own BUILDING.md 2026-03-29 20:15:48 +02:00
Pim van Pelt
f116a08aa0 Add multi-arch build instructions, on Summer (linux/amd64) and Jessica-Orb (linux/arm64) 2026-03-06 06:47:28 +01:00
Pim van Pelt
a5b19b3139 Allow sideloading vppdebs, tighten enabled modules in docker startup.conf, noting that clab generates its own 2026-03-06 00:05:11 +01:00
Pim van Pelt
20ddc553e1 Update vpp-frr.clab.yml
Set client<->vpp MTU to 1500
2026-01-19 02:46:47 +00:00
Pim van Pelt
0a38cd20c1 Update vpp-bird.clab.yml
set mtu between client<->vpp to 1500
2026-01-19 02:46:18 +00:00
Pim van Pelt
f0d00fad0d Pin Noble, install Bird2 from upstream nic.cz 2025-11-03 04:59:37 +01:00
Pim van Pelt
e2cac9e288 Make versioned releases explicit with REPO arg 2025-11-03 00:32:42 +01:00
Pim van Pelt
ea8cd89de9 Update the build to multi-arch packages with buildx and qemu on arm64,amd64 2025-05-11 16:53:40 +02:00
Pim van Pelt
2b03aad9bc Remove manual-{pre,post}.vpp 2025-05-08 20:56:38 +02:00
Pim van Pelt
17c3977873 Move to Ubuntu Noble, which allows for arm64 images 2025-05-08 18:32:25 +02:00
Pim van Pelt
e5889b22e2 Reduce docker layers, rename the Dockerfile in prep for move to Ubuntu 2025-05-08 18:29:59 +02:00
Pim van Pelt
49b8df9709 Add FRRouting support to VPP Containerlab 2025-05-07 20:31:28 +02:00
Pim van Pelt
dc1840a6ec Drop a hint about --build-arg to set the REPO variant 2025-05-04 20:22:46 +02:00
Pim van Pelt
7114b24331 Move to MTU 9216 2025-05-04 20:13:47 +02:00
Pim van Pelt
4c640d7f10 Move to startup-config and move the binds fields to the fdio_vpp kind 2025-05-04 20:10:56 +02:00
Pim van Pelt
b16599d267 Update vppcfg location to git.ipng.ch, h/t @hellt 2025-05-04 18:59:41 +02:00
Pim van Pelt
88ee8a2ae8 Add pre languages 2025-05-04 12:45:10 +02:00
Pim van Pelt
647030927a Update README to add user guide for a simple containerlab 2025-05-04 12:42:41 +02:00
Pim van Pelt
659ae59a3b Add an example topo 2025-05-04 12:31:46 +02:00
41 changed files with 1234 additions and 171 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
clab-* clab-*
**/*.bak **/*.bak
tests/.venv/
tests/out/

171
BUILDING.md Normal file
View File

@@ -0,0 +1,171 @@
# Building vpp-containerlab
This document describes how to build, test and release the `vpp-containerlab` Docker image.
The image is built natively on two machines and combined into a multi-arch manifest:
- `summer` — amd64, Linux (local machine)
- `jessica-orb` — arm64, OrbStack VM on macOS, reachable via `ssh jessica-orb`
The pipeline sideloads locally-built VPP `.deb` packages rather than pulling from packagecloud,
so VPP must be compiled on both machines before building the image.
## Prerequisites
### SSH access to jessica-orb
The Docker daemon on `jessica` runs inside OrbStack's Linux VM. OrbStack listens on
`127.0.0.1:32222`; add a jump-host entry to `~/.ssh/config` on `summer` to reach it:
```
Host jessica-orb
HostName 127.0.0.1
Port 32222
User pim
ProxyCommand ssh jessica -W 127.0.0.1:32222
IdentityFile ~/.ssh/jessica-orb-key
IdentitiesOnly yes
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
```
Copy OrbStack's SSH key from `jessica` to `summer`:
```bash
scp jessica:~/.orbstack/ssh/id_ed25519 ~/.ssh/jessica-orb-key
chmod 600 ~/.ssh/jessica-orb-key
```
Verify the full chain works:
```bash
ssh jessica-orb 'uname -m && docker info | head -3'
# expected: aarch64
```
### One-time setup
Install the Robot Framework venv for running tests:
```bash
make venv
```
This only needs to be re-run if `tests/requirements.txt` changes.
### Before every release
Build VPP on both machines (`make pkg-deb` in your VPP source tree on both `summer` and the
OrbStack VM on `jessica`), then verify both machines have a consistent set of `.deb` packages:
```bash
make preflight
```
This checks that `~/src/vpp/build-root` on each machine contains exactly one version of each
required package and that the version on `summer` matches the version on `jessica-orb`.
Override the path if your build root is elsewhere:
```bash
make preflight VPPDEBS=~/src/vpp/other-build-root
```
## Release pipeline
The full pipeline runs in this order:
```
preflight → build → test → push → release
```
Run everything in one shot:
```bash
make all
```
Or step through it manually:
| Step | Command | What it does |
|------|---------|--------------|
| 1 | `make preflight` | Validate VPP debs on summer and jessica-orb |
| 2 | `make build-amd64` | Build image locally for amd64 |
| 3 | `make test-amd64` | Run e2e tests against the amd64 image |
| 4 | `make sync-arm64` | Rsync working tree to jessica-orb |
| 5 | `make build-arm64` | Build image on jessica-orb for arm64 |
| 6 | `make test-arm64` | Run e2e tests on jessica-orb against the arm64 image |
| 7 | `make push-amd64` | Tag and push `:latest-amd64` to the registry |
| 8 | `make push-arm64` | Tag and push `:latest-arm64` to the registry |
| 9 | `make release` | Combine into a single `:latest` multi-arch manifest |
Convenience targets:
```bash
make build # steps 2+4+5 (both platforms)
make test # steps 3+6 (both platforms)
make push # steps 7+8 (both platforms)
```
### Promoting to :stable
`:stable` is only promoted **after** a successful `make all` — meaning both amd64 and arm64
have been built, tested, pushed and combined into `:latest`. Do not run `make stable` unless
the full pipeline completed without errors.
```bash
make all && make stable
```
`make stable` points `:stable` at the same manifest as the current `:latest-amd64` and
`:latest-arm64`, so it is always in sync with a fully tested release.
## Running a single test suite
Pass `TEST=` to restrict which suite is run:
```bash
make test-amd64 TEST=tests/01-vpp-bird
make test TEST=tests/02-vpp-frr
```
The default is `tests/` (all suites).
## Debugging test failures
**Read the HTML log** — written after every run regardless of outcome:
```bash
xdg-open tests/out/tests-docker-log.html
```
**Deploy the topology manually** to keep containers running for inspection:
```bash
IMAGE=git.ipng.ch/ipng/vpp-containerlab:latest-amd64-test \
containerlab deploy -t tests/01-vpp-bird/e2e-lab/vpp.clab.yml
```
Then inspect live state:
```bash
# Enter the VPP1 container:
containerlab tools dc -t tests/01-vpp-bird/e2e-lab/vpp.clab.yml vpp1
# OSPF neighbor state
containerlab exec -t tests/01-vpp-bird/e2e-lab/vpp.clab.yml \
--label clab-node-name=vpp1 --cmd "birdc show ospf neighbor"
# Manual ping
containerlab exec -t tests/01-vpp-bird/e2e-lab/vpp.clab.yml \
--label clab-node-name=client1 --cmd "ping -c 5 10.82.98.82"
# Tear down when done
containerlab destroy -t tests/01-vpp-bird/e2e-lab/vpp.clab.yml --cleanup
```
**Common cause — OSPF convergence time:** 100% ping loss usually means routing is not up yet.
Tune the `Sleep` duration in the relevant `.robot` file by deploying manually and watching
`birdc show ospf neighbor` (or `vtysh -c "show ip ospf neighbor"` for FRR) until all
neighbors reach state `Full`.
**Increase robot verbosity:** add `--loglevel DEBUG` to the `robot` invocation in
`tests/rf-run.sh` temporarily.

131
Makefile Normal file
View File

@@ -0,0 +1,131 @@
IMG := git.ipng.ch/ipng/vpp-containerlab
BUILDHOST := jessica-orb
BUILDDIR := ~/src/.vpp-containerlab-build
VPPDEBS := $(HOME)/src/vpp/build-root
TEST ?= tests/
.PHONY: all help preflight build build-amd64 build-arm64 sync-arm64 test test-amd64 test-arm64 \
push push-amd64 push-arm64 release stable venv
help:
@echo "vpp-containerlab build, test and release"
@echo ""
@echo "Typical workflow:"
@echo " 1. make venv Set up local Robot Framework venv (once, or after requirements change)"
@echo " 2. make preflight Verify VPP debs in VPPDEBS= on summer and jessica-orb"
@echo " 3. make build-amd64 Build image locally for amd64 (sideloading VPPDEBS)"
@echo " 4. make test-amd64 Run e2e tests locally against the amd64 image"
@echo " 5. make sync-arm64 Rsync working tree to jessica-orb and set up venv there"
@echo " 6. make build-arm64 Build image on jessica-orb for arm64 (sideloading VPPDEBS)"
@echo " 7. make test-arm64 Run e2e tests on jessica-orb against the arm64 image"
@echo " 8. make push-amd64 Tag and push :latest-amd64 to the registry"
@echo " 9. make push-arm64 Tag and push :latest-arm64 to the registry (runs on jessica-orb)"
@echo " 10. make release Combine into a single :latest multi-arch manifest"
@echo ""
@echo " make all Run steps 2-10 in one go (venv must already exist)"
@echo " make build Run steps 3+5+6 (sync-arm64 + both builds)"
@echo " make test Run steps 4+7 (both test suites)"
@echo " make push Run steps 8+9 (both pushes)"
@echo " make release Run step 10 (publish into a multi-arch manifest)"
@echo " make stable When all tests pass on amd64+arm64, push this ':latest' as :stable'"
@echo ""
@echo "Variables (override on command line):"
@echo " VPPDEBS=~/src/vpp/build-root Directory of locally-built VPP .deb packages"
@echo " TEST=tests/02-vpp-frr Run only a specific test suite (default: all)"
# Build both platforms, test both, push both, then combine into :latest.
all: preflight build test push release
build: build-amd64 sync-arm64 build-arm64
test: test-amd64 test-arm64
push: push-amd64 push-arm64
# -------------------------------------------------------------------------
# Preflight — validate VPP debs on summer and jessica-orb
# -------------------------------------------------------------------------
# Check locally, then pipe the same script to jessica-orb over SSH,
# passing the local version so jessica-orb can assert both machines match.
preflight:
python3 scripts/check-vppdebs.py $(VPPDEBS)
$(eval VPP_VERSION := $(shell python3 scripts/check-vppdebs.py --print-version $(VPPDEBS)))
ssh $(BUILDHOST) python3 - --assert-version $(VPP_VERSION) $(VPPDEBS) < scripts/check-vppdebs.py
# -------------------------------------------------------------------------
# Local venv (summer)
# -------------------------------------------------------------------------
tests/.venv: tests/requirements.txt
python3 -m venv tests/.venv
tests/.venv/bin/pip install -q -r tests/requirements.txt
venv: tests/.venv
# -------------------------------------------------------------------------
# amd64 — runs locally on summer
# -------------------------------------------------------------------------
build-amd64:
docker buildx build --no-cache --load --platform linux/amd64 \
--build-context vppdebs=$(VPPDEBS) \
--tag $(IMG):latest-amd64-test \
-f docker/Dockerfile docker/
test-amd64: tests/.venv
IMAGE=$(IMG):latest-amd64-test tests/rf-run.sh docker $(TEST)
push-amd64:
docker tag $(IMG):latest-amd64-test $(IMG):latest-amd64
docker push $(IMG):latest-amd64
# -------------------------------------------------------------------------
# arm64 — runs on jessica-orb via rsync + SSH
# -------------------------------------------------------------------------
# Wipe and re-sync summer's working tree to a stable directory on jessica-orb,
# then set up the robot venv there.
sync-arm64:
@case "$(BUILDDIR)" in \
.*|/*) echo "ERROR: BUILDDIR '$(BUILDDIR)' must not start with '.' or '/'" >&2; exit 1;; \
esac
ssh $(BUILDHOST) "rm -rf $(BUILDDIR) && mkdir -p $(BUILDDIR)"
rsync -a --exclude='.git' --exclude='tests/.venv' --exclude='tests/out' \
./ $(BUILDHOST):$(BUILDDIR)/
ssh $(BUILDHOST) "cd $(BUILDDIR) && \
python3 -m venv tests/.venv && \
tests/.venv/bin/pip install -q -r tests/requirements.txt"
build-arm64:
ssh $(BUILDHOST) "cd $(BUILDDIR) && \
docker buildx build --no-cache --load --platform linux/arm64 \
--build-context vppdebs=$(VPPDEBS) \
--tag $(IMG):latest-arm64-test \
-f docker/Dockerfile docker/"
test-arm64:
ssh $(BUILDHOST) "cd $(BUILDDIR) && \
IMAGE=$(IMG):latest-arm64-test \
tests/rf-run.sh docker $(TEST)"
push-arm64:
ssh $(BUILDHOST) "docker tag $(IMG):latest-arm64-test $(IMG):latest-arm64 && \
docker push $(IMG):latest-arm64"
# -------------------------------------------------------------------------
# Release — combine amd64 + arm64 into a single :latest manifest
# -------------------------------------------------------------------------
release:
docker buildx imagetools create \
--tag $(IMG):latest \
$(IMG):latest-amd64 \
$(IMG):latest-arm64
# -------------------------------------------------------------------------
# Stable — mark latest release as stable. Only do this if all tests pass
# -------------------------------------------------------------------------
stable:
docker buildx imagetools create \
--tag $(IMG):stable \
$(IMG):latest-amd64 \
$(IMG):latest-arm64

152
README.md
View File

@@ -1,103 +1,79 @@
# VPP Containerlab Docker image # VPP Containerlab Docker image
This docker container creates a VPP instance based on the latest VPP release. It starts up as per ## User Documentation
normal, using /etc/vpp/startup.conf (which Containerlab might replace when it starts its
containers). Once started, it'll execute `/etc/vpp/bootstrap.vpp` within the dataplane. There are
two relevant files:
1. `clab.vpp` -- generated by `files/init-container.sh`. Its purpose is to bind the `veth` The file `vpp.clab.yml` contains an example topology existing of two VPP instances connected each to
interfaces that containerlab has added to the container into the VPP dataplane (see below). one Alpine linux container, in the following topology:
1. `vppcfg.vpp` -- generated by `files/init-container.sh`. Its purpose is to read the user
specified `vppcfg.yaml` file and convert it into VPP CLI commands. If no YAML file is
specified, or if it is not syntactically valid, an empty file is generated instead.
For Containerlab users who wish to have more control over their VPP bootstrap, it's possible to ![learn-vpp](learn-vpp.png)
bind-mount `/etc/vpp/bootstrap.vpp`.
## Building This container ships with both Bird2 and FRRouting as controlplane agents.
You can deploy:
* Bird2: `containerlab deploy --topo vpp-bird.clab.yml`.
* FRR: `containerlab deploy --topo vpp-frr.clab.yml`.
three relevant files for VPP are included in this repository:
1. `config/vpp*/vppcfg.yaml` configures the dataplane interfaces, including a loopback address.
1. `config/vpp*/bird-local.conf` configures the controlplane to enable BFD and OSPF.
1. `config/vpp*/frr.conf` configures the controlplane to enable BFD and OSPF.
Once the lab comes up, you can SSH to the VPP containers (`vpp1` and `vpp2`) which will have your
SSH keys installed (if available). Otherwise, you can log in as user `root` using password `vpp`.
VPP runs its own network namespace called `dataplane`, which is very similar to SR Linux default
`network-instance`. You can join it to take a look:
```bash ```bash
IMG=git.ipng.ch/ipng/vpp-containerlab pim@summer:~/src/vpp-containerlab$ ssh root@vpp1
TAG=latest root@vpp1:~# nsenter --net=/var/run/netns/dataplane
docker build --no-cache -f docker/Dockerfile.bookworm -t $IMG docker/ root@vpp1:~# ip -br a
docker image tag $IMG $IMG:$TAG lo DOWN
docker push $IMG loop0 UP 10.82.98.0/32 2001:db8:8298::/128 fe80::dcad:ff:fe00:0/64
docker push $IMG:$TAG eth1 UNKNOWN 10.82.98.65/28 2001:db8:8298:101::1/64 fe80::a8c1:abff:fe77:acb9/64
eth2 UNKNOWN 10.82.98.16/31 2001:db8:8298:1::1/64 fe80::a8c1:abff:fef0:7125/64
root@vpp1:~# ping 10.82.98.1 ## The vpp2 IPv4 loopback address
PING 10.82.98.1 (10.82.98.1) 56(84) bytes of data.
64 bytes from 10.82.98.1: icmp_seq=1 ttl=64 time=9.53 ms
64 bytes from 10.82.98.1: icmp_seq=2 ttl=64 time=15.9 ms
^C
--- 10.82.98.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 9.530/12.735/15.941/3.205 ms
``` ```
## Testing the container standalone The two clients are running a minimalistic Alpine Linux container, which doesn't ship with SSH by
default. You can enter the containers as following:
```bash ```bash
docker network create --driver=bridge clab-network --subnet=192.0.2.0/24 \ pim@summer:~/src/vpp-containerlab$ docker exec -it client1 sh
--ipv6 --subnet=2001:db8::/64 / # ip addr show dev eth1
docker rm clab-pim 531235: eth1@if531234: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 9500 qdisc noqueue state UP
docker run --cap-add=NET_ADMIN --cap-add=SYS_NICE --cap-add=SYS_PTRACE \ link/ether 00:c1:ab:00:00:01 brd ff:ff:ff:ff:ff:ff
--device=/dev/net/tun:/dev/net/tun \ inet 10.82.98.66/28 scope global eth1
--device=/dev/vhost-net:/dev/vhost-net \ valid_lft forever preferred_lft forever
--privileged --name clab-pim \ inet6 2001:db8:8298:101::2/64 scope global
docker.io/pimvanpelt/vpp-containerlab:latest valid_lft forever preferred_lft forever
docker network connect clab-network clab-pim inet6 fe80::2c1:abff:fe00:1/64 scope link
valid_lft forever preferred_lft forever
/ # traceroute 10.82.98.82
traceroute to 10.82.98.82 (10.82.98.82), 30 hops max, 46 byte packets
1 10.82.98.65 (10.82.98.65) 5.906 ms 7.086 ms 7.868 ms
2 10.82.98.17 (10.82.98.17) 24.007 ms 23.349 ms 15.933 ms
3 10.82.98.82 (10.82.98.82) 39.978 ms 31.127 ms 31.854 ms
/ # traceroute 2001:db8:8298:102::2
traceroute to 2001:db8:8298:102::2 (2001:db8:8298:102::2), 30 hops max, 72 byte packets
1 2001:db8:8298:101::1 (2001:db8:8298:101::1) 0.701 ms 7.144 ms 7.900 ms
2 2001:db8:8298:1::2 (2001:db8:8298:1::2) 23.909 ms 22.943 ms 23.893 ms
3 2001:db8:8298:102::2 (2001:db8:8298:102::2) 31.964 ms 30.814 ms 32.000 ms
``` ```
### A note on DPDK From the vantage point of `client1`, the first hop represents the `vpp1` node, which forwards to
`vpp2`, which finally forwards to `client2`.
DPDK will be disabled by default as it requires hugepages and VFIO and/or UIO to use physical ## Developer Documentation
network cards. If DPDK at some future point is desired, mapping VFIO can be done by adding this:
```
--device=/dev/vfio/vfio:/dev/vfio/vfio
```
or in Containerlab, using the `devices` feature: See [BUILDING.md](BUILDING.md) for instructions on building the image, sideloading locally built
VPP packages, multiarch builds, testing, and configuring VPP.
```
my-node:
image: vpp-containerlab:latest
kind: vpp
devices:
- /dev/vfio/vfio
- /dev/net/tun
- /dev/vhost-net
```
If using DPDK in a container, one of the userspace IO kernel drivers must be loaded in the host
kernel. Options are `igb_uio`, `vfio_pci`, or `uio_pci_generic`:
```bash
$ sudo modprobe igb_uio
$ sudo modprobe vfio_pci
$ sudo modprobe uio_pci_generic
```
Particularly the VFIO driver needs to be present before one can attempt to bindmount
`/dev/vfio/vfio` into the container!
## Configuring VPP
When Containerlab starts the docker containers, it'll offer one or more `veth` point to point
network links, which will show up as `eth1` and further. `eth0` is the default NIC that belongs to
the management plane in Containerlab (the one which you'll see with `containerlab inspect`). Before
VPP can use these `veth` interfaces, it needs to bind them, like so:
```
docker exec -it clab-pim vppctl
```
and then within the VPP control shell:
```
create host-interface v2 name eth1
set interface name host-eth1 eth1
set interface mtu 1500 eth1
set interface ip address eth1 192.0.2.2/24
set interface ip address eth1 2001:db8::2/64
set interface state eth1 up
```
Containerlab will attach these `veth` pairs to the container, and replace our Docker CMD with one
that waits for all of these interfaces to be added (typically called `if-wait.sh`). In our own CMD,
we then generate a config file called `/etc/vpp/clab.vpp` which contains the necessary VPP commands
to take control over these `veth` pairs.
In addition, you can add more commands that'll execute on startup by copying in
`/etc/vpp/manual-pre.vpp` (to be executed _before_ the containerlab stuff) or
`/etc/vpp/manual-post.vpp` (to be executed _after_ the containerlab stuff).

2
config/lab-frr.env Normal file
View File

@@ -0,0 +1,2 @@
BIRD_ENABLED=false
FRR_ENABLED=true

31
config/vpp1/frr.conf Normal file
View File

@@ -0,0 +1,31 @@
frr version 10.3
frr defaults traditional
hostname vpp1
log syslog informational
service integrated-vtysh-config
!
ip router-id 10.82.98.0
!
interface eth2
ip ospf area 0
ip ospf bfd
ip ospf cost 10
ip ospf network point-to-point
ipv6 ospf6 area 0
ipv6 ospf6 bfd
ipv6 ospf6 cost 10
ipv6 ospf6 network point-to-point
exit
!
interface loop0
ip ospf passive
exit
!
router ospf
redistribute connected
exit
!
router ospf6
redistribute connected
exit
!

View File

@@ -6,7 +6,7 @@ interfaces:
addresses: [ 10.82.98.65/28, 2001:db8:8298:101::1/64 ] addresses: [ 10.82.98.65/28, 2001:db8:8298:101::1/64 ]
eth2: eth2:
description: 'To vpp2' description: 'To vpp2'
mtu: 9000 mtu: 9216
lcp: eth2 lcp: eth2
addresses: [ 10.82.98.16/31, 2001:db8:8298:1::1/64 ] addresses: [ 10.82.98.16/31, 2001:db8:8298:1::1/64 ]
loopbacks: loopbacks:

31
config/vpp2/frr.conf Normal file
View File

@@ -0,0 +1,31 @@
frr version 10.3
frr defaults traditional
hostname vpp2
log syslog informational
service integrated-vtysh-config
!
ip router-id 10.82.98.1
!
interface eth2
ip ospf area 0
ip ospf bfd
ip ospf cost 10
ip ospf network point-to-point
ipv6 ospf6 area 0
ipv6 ospf6 bfd
ipv6 ospf6 cost 10
ipv6 ospf6 network point-to-point
exit
!
interface loop0
ip ospf passive
exit
!
router ospf
redistribute connected
exit
!
router ospf6
redistribute connected
exit
!

View File

@@ -6,7 +6,7 @@ interfaces:
addresses: [ 10.82.98.81/28, 2001:db8:8298:102::1/64 ] addresses: [ 10.82.98.81/28, 2001:db8:8298:102::1/64 ]
eth2: eth2:
description: 'To vpp1' description: 'To vpp1'
mtu: 9000 mtu: 9216
lcp: eth2 lcp: eth2
addresses: [ 10.82.98.17/31, 2001:db8:8298:1::2/64 ] addresses: [ 10.82.98.17/31, 2001:db8:8298:1::2/64 ]
loopbacks: loopbacks:

52
docker/Dockerfile Normal file
View File

@@ -0,0 +1,52 @@
# Default empty stage for local VPP debs. Override at build time with:
# --build-context vppdebs=/path/to/debs (e.g. ~/src/vpp/build-root/)
# If not overridden, falls back to installing VPP from packagecloud (ARG REPO).
FROM scratch AS vppdebs
FROM ubuntu:noble
ARG DEBIAN_FRONTEND=noninteractive
ARG VPP_INSTALL_SKIP_SYSCTL=true
ARG REPO=release
EXPOSE 22/tcp
RUN apt-get update && apt-get -y install curl procps tcpdump iproute2 iptables \
iputils-ping net-tools git python3 python3-pip vim-tiny nano joe openssh-server \
mtr-tiny traceroute rsync && apt-get clean
# Install VPP - sideload from local debs if --build-context vppdebs=<path> is provided,
# otherwise install from packagecloud. Debs are bind-mounted and never stored in a layer.
RUN --mount=type=bind,from=vppdebs,target=/tmp/vpp-debs \
mkdir -p /var/log/vpp /root/.ssh/ && \
if ls /tmp/vpp-debs/vpp_*.deb 1>/dev/null 2>&1; then \
apt-get -y install /tmp/vpp-debs/libvppinfra_*.deb \
/tmp/vpp-debs/python3-vpp-api_*.deb \
/tmp/vpp-debs/vpp_*.deb \
/tmp/vpp-debs/vpp-crypto-engines_*.deb \
/tmp/vpp-debs/vpp-plugin-core_*.deb; \
else \
curl -s https://packagecloud.io/install/repositories/fdio/${REPO}/script.deb.sh | bash && \
apt-get -y install vpp vpp-plugin-core; \
fi && \
apt-get clean
# Build vppcfg
RUN pip install --break-system-packages build netaddr yamale argparse pyyaml ipaddress && \
git clone https://git.ipng.ch/ipng/vppcfg.git && cd vppcfg && python3 -m build && \
pip install --break-system-packages dist/vppcfg-*-py3-none-any.whl
# Install FRR
RUN curl -s -o /usr/share/keyrings/frrouting.gpg https://deb.frrouting.org/frr/keys.gpg && \
echo deb '[signed-by=/usr/share/keyrings/frrouting.gpg]' https://deb.frrouting.org/frr noble frr-stable \
> /etc/apt/sources.list.d/frr.list && \
apt -y update && apt -y install frr frr-pythontools && apt clean
# Install Bird2
RUN curl -s -o /usr/share/keyrings/cznic-labs-pkg.gpg https://pkg.labs.nic.cz/gpg && \
echo "deb [signed-by=/usr/share/keyrings/cznic-labs-pkg.gpg] https://pkg.labs.nic.cz/bird2 noble main" \
> /etc/apt/sources.list.d/cznic-labs-bird2.list && \
apt -y update && apt -y install bird2 && apt clean
# Config files
COPY files/etc/ /etc/
COPY files/init-container.sh /sbin/
RUN chmod 755 /sbin/init-container.sh
CMD ["/sbin/init-container.sh"]

View File

@@ -1,25 +0,0 @@
FROM debian:bookworm
ARG DEBIAN_FRONTEND=noninteractive
ARG VPP_INSTALL_SKIP_SYSCTL=true
ARG REPO=release
EXPOSE 22/tcp
RUN apt-get update && apt-get -y install curl procps tcpdump iproute2 iptables \
iputils-ping net-tools git python3 python3-pip vim-tiny openssh-server bird2 \
mtr-tiny traceroute && apt-get clean
# Install VPP
RUN mkdir -p /var/log/vpp /root/.ssh/
RUN curl -s https://packagecloud.io/install/repositories/fdio/${REPO}/script.deb.sh | bash
RUN apt-get update && apt-get -y install vpp vpp-plugin-core && apt-get clean
# Build vppcfg
RUN pip install --break-system-packages build netaddr yamale argparse pyyaml ipaddress
RUN git clone https://github.com/pimvanpelt/vppcfg.git && cd vppcfg && python3 -m build && \
pip install --break-system-packages dist/vppcfg-*-py3-none-any.whl
# Config files
COPY files/etc/vpp/* /etc/vpp/
COPY files/etc/bird/* /etc/bird/
COPY files/init-container.sh /sbin/
RUN chmod 755 /sbin/init-container.sh
CMD ["/sbin/init-container.sh"]

View File

@@ -0,0 +1,17 @@
# These are the daemons that FRR will use for VPP Containerlab
# NOTE: we need to run in the 'dataplane' network namespace, and use the `dplane_fpm_nl` plugin
bgpd=yes
ospfd=yes
ospf6d=yes
bfdd=yes
ldpd=yes
vtysh_enable=yes
watchfrr_options="--netns=dataplane"
zebra_options=" -A 127.0.0.1 -s 67108864 -M dplane_fpm_nl"
bgpd_options=" -A 127.0.0.1"
ospfd_options=" -A 127.0.0.1"
ospf6d_options=" -A ::1"
staticd_options="-A 127.0.0.1"
bfdd_options=" -A 127.0.0.1"
ldpd_options=" -A 127.0.0.1"

View File

@@ -0,0 +1,10 @@
# This is the VPP Containerlab default FRR configuration.
frr defaults traditional
log syslog informational
ip forwarding
ipv6 forwarding
service integrated-vtysh-config
!
ip router-id 192.0.2.1
!

View File

@@ -1,2 +1,13 @@
exec /etc/vpp/clab.vpp mpls table add 0
exec /etc/vpp/vppcfg.vpp set ip neighbor-config ip4 age 900
set ip neighbor-config ip6 age 900
lcp default netns dataplane
lcp lcp-sync on
lcp lcp-sync-unnumbered on
lcp param del-static-on-link-down on
lcp param del-dynamic-on-link-down on
lcp lcp-auto-subint off
exec /config/vpp/config/manual-pre.vpp
exec /config/vpp/config/clab.vpp
exec /config/vpp/config/vppcfg.vpp
exec /config/vpp/config/manual-post.vpp

View File

@@ -0,0 +1 @@
comment { You can add commands here that will execute after vppcfg.vpp }

View File

@@ -0,0 +1 @@
comment { You can add commands here that will execute before clab.vpp }

View File

@@ -0,0 +1 @@
comment { This file will be overwritten / generated by containerlab upon deploy }

View File

@@ -9,7 +9,7 @@ unix {
cli-prompt vpp-clab# cli-prompt vpp-clab#
cli-no-pager cli-no-pager
poll-sleep-usec 100 poll-sleep-usec 100
exec /etc/vpp/bootstrap.vpp exec /config/vpp/bootstrap.vpp
} }
api-trace { api-trace {
@@ -34,11 +34,16 @@ statseg {
} }
plugins { plugins {
plugin default { enable } plugin default { disable }
plugin dpdk_plugin.so { disable } plugin acl_plugin.so { enable }
plugin geneve_plugin.so { enable }
plugin gre_plugin.so { enable }
plugin ipip_plugin.so { enable }
plugin linux_cp_plugin.so { enable } plugin linux_cp_plugin.so { enable }
plugin linux_nl_plugin.so { enable } plugin linux_nl_plugin.so { enable }
plugin sflow_plugin.so { enable } plugin sflow_plugin.so { enable }
plugin tap_plugin.so { enable }
plugin vxlan_plugin.so { enable }
} }
linux-cp { linux-cp {

View File

@@ -1,28 +1,61 @@
#!/usr/bin/env bash #!/usr/bin/env bash
STARTUP_CONFIG=${STARTUP_CONFIG:="/etc/vpp/startup.conf"} STARTUP_CONFIG=${STARTUP_CONFIG:="/config/vpp/startup.conf"}
CLAB_VPP_FILE=${CLAB_VPP_FILE:=/etc/vpp/clab.vpp} VPPCFG_YAML_FILE=${VPPCFG_YAML_FILE:="/config/vpp/vppcfg.yaml"}
VPPCFG_VPP_FILE=${VPPCFG_VPP_FILE:=/etc/vpp/vppcfg.vpp} VPPCFG_VPP_FILE=${VPPCFG_VPP_FILE:="/config/vpp/config/vppcfg.vpp"}
CLAB_VPP_FILE=${CLAB_VPP_FILE:="/config/vpp/config/clab.vpp"}
NETNS=${NETNS:="dataplane"} NETNS=${NETNS:="dataplane"}
BIRD_ENABLED=${BIRD_ENABLED:="true"} BIRD_ENABLED=${BIRD_ENABLED:="true"}
BIRD_CONFIG=${BIRD_CONFIG:="/config/bird/bird.conf"}
FRR_ENABLED=${FRR_ENABLED:="false"}
FRR_CONFIG=${FRR_CONFIG:="/config/frr/frr.conf"}
echo "Creating dataplane namespace" echo "Creating dataplane namespace"
/usr/bin/mkdir -p /etc/netns/$NETNS /usr/bin/mkdir -p /etc/netns/$NETNS
/usr/bin/touch /etc/netns/$NETNS/resolv.conf /usr/bin/touch /etc/netns/$NETNS/resolv.conf
/usr/sbin/ip netns add $NETNS /usr/sbin/ip netns add $NETNS
/usr/bin/nsenter --net=/run/netns/$NETNS /usr/sbin/ip link set lo up
echo "Starting SSH, with credentials root:vpp" echo "Starting SSH, with credentials root:vpp"
sed -i -e 's,^#PermitRootLogin prohibit-password,PermitRootLogin yes,' /etc/ssh/sshd_config sed -i -e 's,^#PermitRootLogin prohibit-password,PermitRootLogin yes,' /etc/ssh/sshd_config
sed -i -e 's,^root:.*,root:$y$j9T$kG8pyZEVmwLXEtXekQCRK.$9iJxq/bEx5buni1hrC8VmvkDHRy7ZMsw9wYvwrzexID:20211::::::,' /etc/shadow sed -i -e 's,^root:.*,root:$y$j9T$kG8pyZEVmwLXEtXekQCRK.$9iJxq/bEx5buni1hrC8VmvkDHRy7ZMsw9wYvwrzexID:20211::::::,' /etc/shadow
/etc/init.d/ssh start /etc/init.d/ssh start
# TODO(pim): Remove this after containerlab 0.75 is released
if [ ! -r /config/vpp/startup.conf ]; then
echo "Detected old containerlab binary - invoking backwards compatible behavior"
STARTUP_CONFIG="/etc/vpp/startup.conf"
VPPCFG_YAML_FILE="/etc/vpp/vppcfg.yaml"
VPPCFG_VPP_FILE="/config/vpp/config/vppcfg.vpp"
CLAB_VPP_FILE="/config/vpp/config/clab.vpp"
BIRD_CONFIG="/etc/bird/bird.conf"
FFR_CONFIG="/etc/frr/frr.conf"
mkdir -p /config/vpp/config/
echo "comment { please upgrade to containerlab 0.75+ }" > /config/vpp/config/manual-pre.vpp
echo "comment { please upgrade to containerlab 0.75+ }" > /config/vpp/config/manual-post.vpp
else
echo "Initializing /config"
for dir in vpp bird frr; do
rsync -av --ignore-existing /etc/$dir/ /config/$dir/
rm -rf /etc/$dir/
ln -s /config/$dir /etc/$dir
done
fi
if [ "$BIRD_ENABLED" == "true" ]; then if [ "$BIRD_ENABLED" == "true" ]; then
echo "Starting Bird in $NETNS" echo "Starting Bird in $NETNS"
mkdir -p /run/bird /var/log/bird mkdir -p /run/bird /var/log/bird
chown bird:bird /var/log/bird chown bird:bird /var/log/bird
ROUTERID=$(ip -br a show eth0 | awk '{ print $3 }' | cut -f1 -d/) ROUTERID=$(ip -br a show eth0 | awk '{ print $3 }' | cut -f1 -d/)
sed -i -e "s,.*router id .*,router id $ROUTERID; # Set by container-init.sh," /etc/bird/bird.conf sed -i -e "s,.*router id .*,router id $ROUTERID; # Set by container-init.sh," $BIRD_CONFIG
/usr/bin/nsenter --net=/var/run/netns/$NETNS /usr/sbin/bird -u bird -g bird /usr/bin/nsenter --net=/run/netns/$NETNS /usr/sbin/bird -u bird -g vpp -c $BIRD_CONFIG
fi
if [ "$FRR_ENABLED" == "true" ]; then
echo "Starting FRRouting in $NETNS"
ROUTERID=$(ip -br a show eth0 | awk '{ print $3 }' | cut -f1 -d/)
sed -i -e "s,^ip router-id .*,ip router-id $ROUTERID," $FRR_CONFIG
/etc/init.d/frr start
fi fi
echo "Generating $CLAB_VPP_FILE" echo "Generating $CLAB_VPP_FILE"
@@ -41,10 +74,15 @@ set interface state $IFNAME up
EOF EOF
done done
echo "Generating $VPPCFG_VPP_FILE" if [ -s $VPPCFG_YAML_FILE ]; then
: > $VPPCFG_VPP_FILE echo "Generating $VPPCFG_YAML_FILE into $VPPCFG_VPP_FILE"
if [ -r /etc/vpp/vppcfg.yaml ]; then : > $VPPCFG_VPP_FILE
vppcfg plan --novpp -c /etc/vpp/vppcfg.yaml -o $VPPCFG_VPP_FILE if [ -r $VPPCFG_YAML_FILE ]; then
vppcfg plan --novpp -c $VPPCFG_YAML_FILE -o $VPPCFG_VPP_FILE
fi
else
echo "Generating empty $VPPCFG_VPP_FILE due to missing or empty $VPPCFG_YAML_FILE"
echo "comment { please provide a vppcfg.yaml file }" > $VPPCFG_VPP_FILE
fi fi
echo "Starting VPP" echo "Starting VPP"

BIN
learn-vpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

118
scripts/check-vppdebs.py Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""
Validate a directory of locally-built VPP .deb packages before a Docker build.
Checks:
- The directory exists
- Exactly one *.changes file
- Exactly one *.buildinfo file
- Exactly one of each required .deb package
- All packages carry the same version string
- Optionally: version matches an expected value (for cross-machine consistency)
Usage:
check-vppdebs.py [--print-version] [--assert-version VERSION] [directory]
--print-version Print only the detected version string and exit (no other output).
--assert-version VER After all checks pass, assert the detected version equals VER.
Use this to verify summer and jessica-orb have the same build.
directory Path to check (default: ~/src/vpp/build-root).
"""
import sys
import glob
import argparse
from pathlib import Path
REQUIRED_DEBS = [
"libvppinfra_*.deb",
"python3-vpp-api_*.deb",
"vpp_*.deb",
"vpp-crypto-engines_*.deb",
"vpp-plugin-core_*.deb",
]
def find(directory, pattern):
return sorted(glob.glob(str(directory / pattern)))
def version_from_deb(path):
"""Extract the version field from a deb filename: name_VERSION_arch.deb"""
return Path(path).stem.split("_")[1]
def main():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--print-version", action="store_true")
parser.add_argument("--assert-version", metavar="VERSION", default=None)
parser.add_argument("directory", nargs="?", default="~/src/vpp/build-root")
args = parser.parse_args()
directory = Path(args.directory).expanduser()
errors = []
versions = []
if not args.print_version:
print(f"Checking VPP debs in: {directory}")
if not directory.is_dir():
print(f" ERROR: directory does not exist: {directory}")
sys.exit(1)
# *.changes
changes = find(directory, "*.changes")
if len(changes) == 1:
if not args.print_version:
print(f" OK changes : {Path(changes[0]).name}")
else:
errors.append(f"expected exactly 1 *.changes, found {len(changes)}: {[Path(f).name for f in changes]}")
# *.buildinfo
buildinfo = find(directory, "*.buildinfo")
if len(buildinfo) == 1:
if not args.print_version:
print(f" OK buildinfo : {Path(buildinfo[0]).name}")
else:
errors.append(f"expected exactly 1 *.buildinfo, found {len(buildinfo)}: {[Path(f).name for f in buildinfo]}")
# required debs
for pattern in REQUIRED_DEBS:
matches = find(directory, pattern)
if len(matches) == 1:
ver = version_from_deb(matches[0])
versions.append(ver)
if not args.print_version:
print(f" OK {pattern:<30s}: {Path(matches[0]).name}")
else:
errors.append(f"expected exactly 1 {pattern}, found {len(matches)}: {[Path(f).name for f in matches]}")
# version consistency within this directory
if versions and len(set(versions)) > 1:
errors.append(f"debs carry mixed versions: {sorted(set(versions))}")
if errors:
if not args.print_version:
print()
for e in errors:
print(f" ERROR: {e}")
sys.exit(1)
detected = versions[0] if versions else None
if args.print_version:
print(detected or "")
sys.exit(0)
print(f" OK version : {detected}")
# cross-machine version assertion
if args.assert_version:
if detected == args.assert_version:
print(f" OK matches summer : {detected}")
else:
print(f" ERROR: version mismatch: this machine={detected}, summer={args.assert_version}")
sys.exit(1)
print()
print("Preflight OK.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,57 @@
*** Settings ***
Library OperatingSystem
Resource ../ssh.robot
Resource ../common.robot
Suite Teardown Run Keyword Cleanup
*** Variables ***
${lab-name} e2e-vpp
${lab-file-name} e2e-lab/vpp.clab.yml
${runtime} docker
*** Test Cases ***
Deploy ${lab-name} lab
Log ${CURDIR}
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name}
Log ${output}
Should Be Equal As Integers ${rc} 0
Pause to let OSPF converge
Sleep 20s
Check BFD Adjacencies
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=vpp2 --cmd "birdc show bfd ses"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Match Regexp ${output} (?m)fe80::.*eth2.*Up
Should Match Regexp ${output} (?m)10\.82\.98\..*eth2.*Up
Check OSPF IPv4 Adjacency
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=vpp1 --cmd "birdc show ospf nei ospf4"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Match Regexp ${output} (?m)Full/PtP.*eth2
Check OSPF IPv6 Adjacency
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=vpp2 --cmd "birdc show ospf nei ospf6"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Match Regexp ${output} (?m)Full/PtP.*eth2
Ensure client1 can ping client2
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=client1 --cmd "ping -c 5 10.82.98.82"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Contain ${output} 5 packets transmitted, 4 packets received, 20% packet loss
*** Keywords ***
Cleanup
Run ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --cleanup

View File

@@ -0,0 +1,19 @@
protocol bfd bfd1 {
interface "eth2" { interval 100 ms; multiplier 30; };
}
protocol ospf v2 ospf4 {
ipv4 { import all; export all; };
area 0 {
interface "loop0" { stub yes; };
interface "eth2" { type pointopoint; cost 10; bfd on; };
};
}
protocol ospf v3 ospf6 {
ipv6 { import all; export all; };
area 0 {
interface "loop0" { stub yes; };
interface "eth2" { type pointopoint; cost 10; bfd on; };
};
}

View File

@@ -0,0 +1,16 @@
interfaces:
eth1:
description: "To client1"
mtu: 1500
lcp: eth1
addresses: [10.82.98.65/28, 2001:db8:8298:101::1/64]
eth2:
description: "To vpp2"
mtu: 9216
lcp: eth2
addresses: [10.82.98.16/31, 2001:db8:8298:1::1/64]
loopbacks:
loop0:
description: "vpp1"
lcp: loop0
addresses: [10.82.98.0/32, 2001:db8:8298::/128]

View File

@@ -0,0 +1,19 @@
protocol bfd bfd1 {
interface "eth2" { interval 100 ms; multiplier 30; };
}
protocol ospf v2 ospf4 {
ipv4 { import all; export all; };
area 0 {
interface "loop0" { stub yes; };
interface "eth2" { type pointopoint; cost 10; bfd on; };
};
}
protocol ospf v3 ospf6 {
ipv6 { import all; export all; };
area 0 {
interface "loop0" { stub yes; };
interface "eth2" { type pointopoint; cost 10; bfd on; };
};
}

View File

@@ -0,0 +1,16 @@
interfaces:
eth1:
description: "To client2"
mtu: 1500
lcp: eth1
addresses: [10.82.98.81/28, 2001:db8:8298:102::1/64]
eth2:
description: "To vpp1"
mtu: 9216
lcp: eth2
addresses: [10.82.98.17/31, 2001:db8:8298:1::2/64]
loopbacks:
loop0:
description: "vpp2"
lcp: loop0
addresses: [10.82.98.1/32, 2001:db8:8298::1/128]

View File

@@ -0,0 +1,38 @@
name: e2e-vpp
topology:
kinds:
fdio_vpp:
image: ${IMAGE}
startup-config: config/__clabNodeName__/vppcfg.yaml
binds:
- config/__clabNodeName__/bird-local.conf:/config/bird/bird-local.conf:ro
linux:
image: alpine:latest
nodes:
vpp1:
kind: fdio_vpp
vpp2:
kind: fdio_vpp
client1:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:01 dev eth1
- ip addr add 10.82.98.66/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.65
- ip addr add 2001:db8:8298:101::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:101::1
client2:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:02 dev eth1
- ip addr add 10.82.98.82/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.81
- ip addr add 2001:db8:8298:102::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:102::1
links:
- endpoints: ["vpp1:eth2", "vpp2:eth2"]
- endpoints: ["client1:eth1", "vpp1:eth1"]
- endpoints: ["client2:eth1", "vpp2:eth1"]

View File

@@ -0,0 +1,58 @@
*** Settings ***
Library OperatingSystem
Resource ../ssh.robot
Resource ../common.robot
Suite Teardown Run Keyword Cleanup
*** Variables ***
${lab-name} e2e-vpp
${lab-file-name} e2e-lab/vpp.clab.yml
${runtime} docker
*** Test Cases ***
Deploy ${lab-name} lab
Log ${CURDIR}
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name}
Log ${output}
Should Be Equal As Integers ${rc} 0
Pause to let OSPF converge
Sleep 20s
Check BFD Adjacencies
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=vpp1 --cmd "vtysh -c 'show bfd peers brief'"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Match Regexp ${output} (?m)10\.82\.98\..*10\.82\.98\..*up
Should Match Regexp ${output} (?m)fe80::.*fe80::.*up
Check OSPF IPv4 Adjacency
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=vpp1 --cmd "vtysh -c 'show ip ospf nei'"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Match Regexp ${output} (?m)Full/.*eth2
Check OSPF IPv6 Adjacency
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=vpp2 --cmd "vtysh -c 'show ipv6 ospf nei'"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Match Regexp ${output} (?m)Full/.*eth2
Ensure client1 can ping client2
${rc} ${output} = Run And Return Rc And Output
... ${CLAB_BIN} --runtime ${runtime} exec -t ${CURDIR}/${lab-file-name} --label clab-node-name\=client1 --cmd "ping -c 5 10.82.98.82"
Log ${output}
Should Be Equal As Integers ${rc} 0
Should Contain ${output} 5 packets transmitted, 4 packets received, 20% packet loss
*** Keywords ***
Cleanup
Run ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --cleanup

View File

@@ -0,0 +1,31 @@
frr version 10.3
frr defaults traditional
hostname vpp1
log syslog informational
service integrated-vtysh-config
!
ip router-id 10.82.98.0
!
interface eth2
ip ospf area 0
ip ospf bfd
ip ospf cost 10
ip ospf network point-to-point
ipv6 ospf6 area 0
ipv6 ospf6 bfd
ipv6 ospf6 cost 10
ipv6 ospf6 network point-to-point
exit
!
interface loop0
ip ospf passive
exit
!
router ospf
redistribute connected
exit
!
router ospf6
redistribute connected
exit
!

View File

@@ -0,0 +1,16 @@
interfaces:
eth1:
description: "To client1"
mtu: 1500
lcp: eth1
addresses: [10.82.98.65/28, 2001:db8:8298:101::1/64]
eth2:
description: "To vpp2"
mtu: 9216
lcp: eth2
addresses: [10.82.98.16/31, 2001:db8:8298:1::1/64]
loopbacks:
loop0:
description: "vpp1"
lcp: loop0
addresses: [10.82.98.0/32, 2001:db8:8298::/128]

View File

@@ -0,0 +1,31 @@
frr version 10.3
frr defaults traditional
hostname vpp2
log syslog informational
service integrated-vtysh-config
!
ip router-id 10.82.98.1
!
interface eth2
ip ospf area 0
ip ospf bfd
ip ospf cost 10
ip ospf network point-to-point
ipv6 ospf6 area 0
ipv6 ospf6 bfd
ipv6 ospf6 cost 10
ipv6 ospf6 network point-to-point
exit
!
interface loop0
ip ospf passive
exit
!
router ospf
redistribute connected
exit
!
router ospf6
redistribute connected
exit
!

View File

@@ -0,0 +1,16 @@
interfaces:
eth1:
description: "To client2"
mtu: 1500
lcp: eth1
addresses: [10.82.98.81/28, 2001:db8:8298:102::1/64]
eth2:
description: "To vpp1"
mtu: 9216
lcp: eth2
addresses: [10.82.98.17/31, 2001:db8:8298:1::2/64]
loopbacks:
loop0:
description: "vpp2"
lcp: loop0
addresses: [10.82.98.1/32, 2001:db8:8298::1/128]

View File

@@ -0,0 +1,41 @@
name: e2e-vpp
topology:
kinds:
fdio_vpp:
image: ${IMAGE}
startup-config: config/__clabNodeName__/vppcfg.yaml
binds:
- config/__clabNodeName__/frr.conf:/config/frr/frr.conf:ro
env:
BIRD_ENABLED: false
FRR_ENABLED: true
linux:
image: alpine:latest
nodes:
vpp1:
kind: fdio_vpp
vpp2:
kind: fdio_vpp
client1:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:01 dev eth1
- ip addr add 10.82.98.66/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.65
- ip addr add 2001:db8:8298:101::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:101::1
client2:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:02 dev eth1
- ip addr add 10.82.98.82/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.81
- ip addr add 2001:db8:8298:102::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:102::1
links:
- endpoints: ["vpp1:eth2", "vpp2:eth2"]
- endpoints: ["client1:eth1", "vpp1:eth1"]
- endpoints: ["client2:eth1", "vpp2:eth1"]

2
tests/common.robot Normal file
View File

@@ -0,0 +1,2 @@
*** Variables ***
${CLAB_BIN} containerlab

2
tests/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
robotframework
robotframework-sshlibrary

45
tests/rf-run.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/bin/bash
# Run Robot Framework tests for vpp-containerlab.
# Arguments:
# $1 - container runtime: [docker, podman]
# $2 - test suite path (directory or .robot file)
#
# Environment variables:
# CLAB_BIN - path to containerlab binary (default: containerlab)
# IMAGE - docker image to use in topology (must be set)
set -e
if [ -z "${CLAB_BIN}" ]; then
CLAB_BIN=containerlab
fi
if [ -z "${IMAGE}" ]; then
echo "ERROR: IMAGE environment variable must be set" >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
mkdir -p "${SCRIPT_DIR}/out"
source "${SCRIPT_DIR}/.venv/bin/activate"
function get_logname() {
path=$1
filename=$(basename "$path")
if [[ "$filename" == *.* ]]; then
dirname=$(dirname "$path")
basename=$(basename "$path" | cut -d. -f1)
echo "${dirname##*/}-${basename}"
else
echo "${filename}"
fi
}
robot --consolecolors on -r none \
--variable CLAB_BIN:"${CLAB_BIN}" \
--variable runtime:"$1" \
-l "${SCRIPT_DIR}/out/$(get_logname $2)-$1-log" \
--output "${SCRIPT_DIR}/out/$(get_logname $2)-$1-out.xml" \
"$2"

44
tests/ssh.robot Normal file
View File

@@ -0,0 +1,44 @@
*** Settings ***
Library SSHLibrary
*** Keywords ***
Login via SSH with username and password
[Arguments]
... ${address}=${None}
... ${port}=22
... ${username}=${None}
... ${password}=${None}
# seconds to try and successfully login
... ${try_for}=4
... ${conn_timeout}=3
FOR ${i} IN RANGE ${try_for}
SSHLibrary.Open Connection ${address} timeout=${conn_timeout}
${status}= Run Keyword And Return Status SSHLibrary.Login ${username} ${password}
IF ${status} BREAK
Sleep 1s
END
IF $status!=True
Fail Unable to connect to ${address} via SSH in ${try_for} attempts
END
Log Exited the loop.
Login via SSH with public key
[Arguments]
... ${address}=${None}
... ${port}=22
... ${username}=${None}
... ${keyfile}=${None}
... ${try_for}=4
... ${conn_timeout}=3
FOR ${i} IN RANGE ${try_for}
SSHLibrary.Open Connection ${address} timeout=${conn_timeout}
${status}= Run Keyword And Return Status SSHLibrary.Login With Public Key
... ${username} ${keyfile}
IF ${status} BREAK
Sleep 1s
END
IF $status!=True
Fail Unable to connect to ${address} via SSH in ${try_for} attempts
END
Log Exited the loop.

39
vpp-bird.clab.yml Normal file
View File

@@ -0,0 +1,39 @@
name: learn-vpp
prefix: ""
topology:
kinds:
fdio_vpp:
image: git.ipng.ch/ipng/vpp-containerlab:stable
startup-config: config/__clabNodeName__/vppcfg.yaml
binds:
- config/__clabNodeName__/bird-local.conf:/config/bird/bird-local.conf:ro
linux:
image: ghcr.io/srl-labs/network-multitool:latest
nodes:
vpp1:
kind: fdio_vpp
vpp2:
kind: fdio_vpp
client1:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:01 mtu 1500 dev eth1
- ip addr add 10.82.98.66/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.65
- ip addr add 2001:db8:8298:101::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:101::1
client2:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:02 mtu 1500 dev eth1
- ip addr add 10.82.98.82/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.81
- ip addr add 2001:db8:8298:102::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:102::1
links:
- endpoints: ["vpp1:eth2", "vpp2:eth2"]
- endpoints: ["client1:eth1", "vpp1:eth1"]
- endpoints: ["client2:eth1", "vpp2:eth1"]

41
vpp-frr.clab.yml Normal file
View File

@@ -0,0 +1,41 @@
name: learn-vpp
prefix: ""
topology:
kinds:
fdio_vpp:
image: git.ipng.ch/ipng/vpp-containerlab:stable
startup-config: config/__clabNodeName__/vppcfg.yaml
binds:
- config/__clabNodeName__/frr.conf:/config/frr/frr.conf:rw
env-files:
- config/lab-frr.env
linux:
image: alpine:latest
nodes:
vpp1:
kind: fdio_vpp
vpp2:
kind: fdio_vpp
client1:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:01 mtu 1500 dev eth1
- ip addr add 10.82.98.66/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.65
- ip addr add 2001:db8:8298:101::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:101::1
client2:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:02 mtu 1500 dev eth1
- ip addr add 10.82.98.82/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.81
- ip addr add 2001:db8:8298:102::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:102::1
links:
- endpoints: ["vpp1:eth2", "vpp2:eth2"]
- endpoints: ["client1:eth1", "vpp1:eth1"]
- endpoints: ["client2:eth1", "vpp2:eth1"]

View File

@@ -1,42 +0,0 @@
name: learn-vpp
prefix: ""
topology:
kinds:
fdio_vpp:
image: git.ipng.ch/ipng/vpp-containerlab:latest
linux:
image: alpine:latest
nodes:
vpp1:
kind: fdio_vpp
binds:
- config/vpp1/vppcfg.yaml:/etc/vpp/vppcfg.yaml:ro
- config/vpp1/bird-local.conf:/etc/bird/bird-local.conf:ro
vpp2:
kind: fdio_vpp
binds:
- config/vpp2/vppcfg.yaml:/etc/vpp/vppcfg.yaml:ro
- config/vpp2/bird-local.conf:/etc/bird/bird-local.conf:ro
client1:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:01 dev eth1
- ip addr add 10.82.98.66/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.65
- ip addr add 2001:db8:8298:101::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:101::1
client2:
kind: linux
exec:
- ip link set address 00:c1:ab:00:00:02 dev eth1
- ip addr add 10.82.98.82/28 dev eth1
- ip route add 10.82.98.0/24 via 10.82.98.81
- ip addr add 2001:db8:8298:102::2/64 dev eth1
- ip route add 2001:db8:8298::/48 via 2001:db8:8298:102::1
links:
- endpoints: ["vpp1:eth2", "vpp2:eth2"]
- endpoints: ["client1:eth1", "vpp1:eth1"]
- endpoints: ["client2:eth1", "vpp2:eth1"]

1
vpp.clab.yml Symbolic link
View File

@@ -0,0 +1 @@
vpp-bird.clab.yml