Compare commits

...

45 Commits

Author SHA1 Message Date
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
Pim van Pelt
ef79717ebe Add a simple containerlab 2025-05-04 12:24:26 +02:00
Pim van Pelt
1667677f72 Move docker build stuff into a subdirectory. Update structure to retire manual-{pre,post} 2025-05-04 12:19:44 +02:00
Pim van Pelt
f4f38646fd Add mtr and traceroute 2025-05-04 11:03:50 +02:00
Pim van Pelt
47eed50e30 Add bird2 + skeleton config 2025-05-04 10:49:55 +02:00
Pim van Pelt
e0f336df88 Move VPP files into their own subdir 2025-05-04 09:28:45 +02:00
Pim van Pelt
b5e04e427f Move container image to git.ipng.ch 2025-05-04 09:20:15 +02:00
Pim van Pelt
3c64e1392e Update README with current state 2025-05-03 22:24:59 +02:00
Pim van Pelt
a52354d5b2 Sort interfaces to ensure a reliable creation order 2025-05-03 22:06:11 +02:00
Pim van Pelt
1b6e2c4726 Add SSH and create default credentials 'root:vpp' 2025-05-03 21:38:16 +02:00
Pim van Pelt
49b5d58bd6 JIT compile the clab.vpp file based on all veth interfaces except eth0 2025-05-03 20:51:05 +02:00
Pim van Pelt
1c603ab583 Cleanup. Ensure the docker build is un-cached 2025-05-03 15:07:49 +02:00
Pim van Pelt
07fc441234 Add vim 2025-05-03 15:07:31 +02:00
Pim van Pelt
e71451f1c5 Add Linux CP: create 'dataplane' network namespace, move to init-container.sh to do the plumbing 2025-05-03 14:22:43 +02:00
Pim van Pelt
234ff1d1d3 Move files to their own directory 2025-05-03 13:30:04 +02:00
Pim van Pelt
e8331cd3af Add some notes on mechanics and VFIO kernel driver 2025-05-03 11:36:28 +02:00
Pim van Pelt
ff50a5c29b Add a note about VFIO and how to add devices in the containerlab idiom 2025-05-03 11:18:49 +02:00
49 changed files with 1417 additions and 99 deletions

4
.gitignore vendored Normal file
View File

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

168
BUILDING.md Normal file
View File

@@ -0,0 +1,168 @@
# 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-ospf
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-ospf/e2e-lab/vpp.clab.yml
```
Then inspect live state:
```bash
# OSPF neighbour state
containerlab exec -t tests/01-vpp-ospf/e2e-lab/vpp.clab.yml \
--label clab-node-name=vpp1 --cmd "birdc show ospf neighbor"
# Manual ping
containerlab exec -t tests/01-vpp-ospf/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-ospf/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
neighbours reach state `Full`.
**Increase robot verbosity:** add `--loglevel DEBUG` to the `robot` invocation in
`tests/rf-run.sh` temporarily.

View File

@@ -1,20 +0,0 @@
FROM debian:bookworm
ARG DEBIAN_FRONTEND=noninteractive
ARG VPP_INSTALL_SKIP_SYSCTL=true
ARG REPO=release
RUN apt-get update
RUN apt-get -y install curl procps tcpdump iproute2 iptables binutils \
bridge-utils iputils-ping netcat-traditional net-tools nmap \
python3 python3-dev python3-pip
RUN mkdir -p /var/log/vpp
RUN curl -s https://packagecloud.io/install/repositories/fdio/${REPO}/script.deb.sh | bash
RUN apt-get update
RUN apt-get -y install vpp vpp-plugin-core
RUN apt-get -y clean
COPY startup.conf /etc/vpp/startup.conf
COPY bootstrap.vpp /etc/vpp/bootstrap.vpp
COPY manual-pre.vpp /etc/vpp/manual-pre.vpp
COPY clab.vpp /etc/vpp/clab.vpp
COPY manual-post.vpp /etc/vpp/manual-post.vpp
CMD ["/usr/bin/vpp","-c","/etc/vpp/startup.conf"]

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

View File

@@ -1,50 +1,79 @@
# VPP Containerlab Docker image
This docker container creates a VPP instance based on the latest release. It starts up as per
normal, using /etc/vpp/startup.conf (which Containerlab will replace), and once started, it'll
execute /etc/vpp/bootstrap.vpp within the dataplane. There are three relevant files:
## User Documentation
1. `manual-pre.vpp` -- can be supplied by the user, to run any configuration statements before
containerlab takes control.
The file `vpp.clab.yml` contains an example topology existing of two VPP instances connected each to
one Alpine linux container, in the following topology:
1. `clab.vpp` -- generated by containerlab. Its purpose is to bind the `vethpair` interfaces
into theo dataplane (see below).
![learn-vpp](learn-vpp.png)
1. `manual-post.vpp` -- can be supplied by the user, to run any configuration statements after
containerlab is finished with its per-lab statements.
This container ships with both Bird2 and FRRouting as controlplane agents.
## Building
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
docker build -f Dockerfile.bookworm . -t pimvanpelt/vpp-containerlab
pim@summer:~/src/vpp-containerlab$ ssh root@vpp1
root@vpp1:~# nsenter --net=/var/run/netns/dataplane
root@vpp1:~# ip -br a
lo DOWN
loop0 UP 10.82.98.0/32 2001:db8:8298::/128 fe80::dcad:ff:fe00:0/64
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
```
## Starting the container
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:
```
docker network create --driver=bridge network2 --subnet=172.19.1.0/24
docker rm clab-pim
docker run --cap-add=NET_ADMIN --cap-add=SYS_NICE --cap-add=SYS_PTRACE \
--device=/dev/net/tun:/dev/net/tun --device=/dev/vfio/vfio:/dev/vfio/vfio \
--device=/dev/vhost-net:/dev/vhost-net \
--privileged=True --name clab-pim \
docker.io/pimvanpelt/vpp-containerlab
docker network connect network2 clab-pim
```bash
pim@summer:~/src/vpp-containerlab$ docker exec -it client1 sh
/ # ip addr show dev eth1
531235: eth1@if531234: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 9500 qdisc noqueue state UP
link/ether 00:c1:ab:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 10.82.98.66/28 scope global eth1
valid_lft forever preferred_lft forever
inet6 2001:db8:8298:101::2/64 scope global
valid_lft forever preferred_lft forever
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
```
## Configuring VPP
From the vantage point of `client1`, the first hop represents the `vpp1` node, which forwards to
`vpp2`, which finally forwards to `client2`.
```
docker exec -it clab-pim vppctl
```
## Developer Documentation
and then within the VPP control shell:
```
vpp-clab# create host-interface v2 name eth1
vpp-clab# set interface name host-eth1 eth1
vpp-clab# set interface mtu 1500 eth1
vpp-clab# set interface ip address eth1 172.19.1.2/24
vpp-clab# set interface ip address eth1 fec0::2/64
vpp-clab# set interface state eth1 up
```
See [BUILDING.md](BUILDING.md) for instructions on building the image, sideloading locally built
VPP packages, multiarch builds, testing, and configuring VPP.

View File

@@ -1,3 +0,0 @@
exec /etc/vpp/manual-pre.vpp
exec /etc/vpp/clab.vpp
exec /etc/vpp/manual-post.vpp

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

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

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; };
};
}

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
!

16
config/vpp1/vppcfg.yaml Normal file
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; };
};
}

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
!

16
config/vpp2/vppcfg.yaml Normal file
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 ]

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

@@ -0,0 +1 @@
# Containerlab user overrides go in this file.

View File

@@ -0,0 +1,26 @@
# Bird2 configuration for VPP Containerlab
# router id 192.0.2.0;
timeformat base iso long;
timeformat log iso long;
timeformat protocol iso long;
timeformat route iso long;
log "/var/log/bird/bird.log" { debug, trace, info, remote, warning, error, auth, fatal, bug };
protocol device { scan time 30; }
protocol direct { ipv4; ipv6; check link yes; }
protocol kernel kernel4 {
ipv4 { import none; export where source != RTS_DEVICE; };
learn off; scan time 300;
}
protocol kernel kernel6 {
ipv6 { import none; export where source != RTS_DEVICE; };
learn off; scan time 300;
}
include "bird-local.conf";

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

@@ -0,0 +1,13 @@
mpls table add 0
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

@@ -0,0 +1,55 @@
# This is the vpp-containerlab default startup.conf, which is meant to be
# overridden by Containerlab's node/fdio_vpp/vpp_startup_config.go.tpl
unix {
interactive
log /var/log/vpp/vpp.log
full-coredump
cli-listen /run/vpp/cli.sock
cli-prompt vpp-clab#
cli-no-pager
poll-sleep-usec 100
exec /config/vpp/bootstrap.vpp
}
api-trace {
on
}
memory {
main-heap-size 512M
main-heap-page-size 4k
}
buffers {
buffers-per-numa 16000
default data-size 2048
page-size 4k
}
statseg {
size 64M
page-size 4k
per-node-counters on
}
plugins {
plugin default { 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_nl_plugin.so { enable }
plugin sflow_plugin.so { enable }
plugin tap_plugin.so { enable }
plugin vxlan_plugin.so { enable }
}
linux-cp {
default netns dataplane
lcp-sync
lcp-auto-subint
del-static-on-link-down
del-dynamic-on-link-down
}

89
docker/files/init-container.sh Executable file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env bash
STARTUP_CONFIG=${STARTUP_CONFIG:="/config/vpp/startup.conf"}
VPPCFG_YAML_FILE=${VPPCFG_YAML_FILE:="/config/vpp/vppcfg.yaml"}
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"}
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"
/usr/bin/mkdir -p /etc/netns/$NETNS
/usr/bin/touch /etc/netns/$NETNS/resolv.conf
/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"
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
/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
echo "Starting Bird in $NETNS"
mkdir -p /run/bird /var/log/bird
chown bird:bird /var/log/bird
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," $BIRD_CONFIG
/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
echo "Generating $CLAB_VPP_FILE"
: > $CLAB_VPP_FILE
MTU=9216
for IFNAME in $(ip -br link show type veth | cut -f1 -d@ | grep -v '^eth0$' | sort); do
MAC=$(ip -br link show dev $IFNAME | awk '{ print $3 }')
echo " * $IFNAME hw-addr $MAC mtu $MTU"
ip link set $IFNAME up mtu $MTU
cat << EOF >> $CLAB_VPP_FILE
create host-interface name $IFNAME hw-addr $MAC
set interface name host-$IFNAME $IFNAME
set interface mtu $MTU $IFNAME
set interface state $IFNAME up
EOF
done
if [ -s $VPPCFG_YAML_FILE ]; then
echo "Generating $VPPCFG_YAML_FILE into $VPPCFG_VPP_FILE"
: > $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
echo "Starting VPP"
exec /usr/bin/vpp -c $STARTUP_CONFIG

BIN
learn-vpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -1 +0,0 @@
comment { These commands are executed after Containerlab stuff }

View File

@@ -1 +0,0 @@
comment { These commands are executed before Containerlab stuff }

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

@@ -1,39 +0,0 @@
unix {
interactive
log /var/log/vpp/vpp.log
full-coredump
cli-listen /run/vpp/cli.sock
cli-prompt vpp-clab#
cli-no-pager
poll-sleep-usec 100
exec /etc/vpp/bootstrap.vpp
}
api-trace {
on
}
memory {
main-heap-size 512M
main-heap-page-size 4k
}
buffers {
buffers-per-numa 16000
default data-size 2048
page-size 4k
}
statseg {
size 64M
page-size 4k
per-node-counters on
}
plugins {
plugin default { enable }
plugin dpdk_plugin.so { disable }
plugin linux_cp_plugin.so { enable }
plugin linux_nl_plugin.so { enable }
plugin sflow_plugin.so { enable }
}

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"]

1
vpp.clab.yml Symbolic link
View File

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