Skip to content

Introduce Containerlab integration tests for VyOS gateway configuration #34

@jmgilman

Description

@jmgilman

Context

After completing HOM-23 (migrating VyOS image builds from Packer to vyos-build), we now have a build pipeline that produces both a raw disk image for production and an ISO with an extractable squashfs filesystem. This creates an opportunity to introduce integration testing for the VyOS gateway configuration using Containerlab.

Currently, VyOS configuration changes in infrastructure/network/vyos/configs/gateway.conf are validated only via:

  • Ansible-lint and syntax checks (PR validation)
  • Manual testing after deployment to production

This leaves a gap: we cannot validate that firewall rules actually block traffic, DHCP actually serves leases, or BGP neighbors are correctly configured until the config hits production.

Goal

Introduce a Containerlab-based integration test suite that validates the VyOS gateway configuration before it reaches production. Tests should run on PRs that modify VyOS configuration.

Technical Approach

1. Container Image from vyos-build

The vyos-build pipeline (from HOM-23) produces build/live/filesystem.squashfs as an intermediate artifact. This can be converted to a container image:

# Extract squashfs to tarball
sqfs2tar build/live/filesystem.squashfs > rootfs.tar

# Build container
docker build -t vyos-gateway:test -f Dockerfile.containerlab .

Dockerfile.containerlab:

FROM scratch
ADD rootfs.tar /

RUN for service in getty.target auditd.service; do systemctl mask $service; done && \
    systemctl disable kea-dhcp-ddns-server.service

HEALTHCHECK --start-period=10s CMD systemctl is-system-running
CMD ["/sbin/init"]

This container uses the exact same rootfs as the production raw image, ensuring test fidelity.

2. Containerlab Topology

Create a minimal topology that simulates the lab network segments:

# infrastructure/network/vyos/tests/topology.clab.yml
name: vyos-gateway-test

topology:
  nodes:
    gateway:
      kind: vyosnetworks_vyos
      image: vyos-gateway:test
      # Config is baked in via vyos-build flavor

    # Simulated clients for functional tests
    mgmt-client:
      kind: linux
      image: alpine:latest
      exec:
        - ip addr add 10.10.10.100/24 dev eth1
        - ip route add default via 10.10.10.1

    platform-client:
      kind: linux
      image: alpine:latest
      exec:
        - ip addr add 10.10.30.100/24 dev eth1
        - ip route add default via 10.10.30.1

  links:
    - endpoints: ["gateway:eth1", "mgmt-client:eth1"]      # VLAN 10 simulation
    - endpoints: ["gateway:eth2", "platform-client:eth1"]  # VLAN 30 simulation

3. Test Suite (pytest + scrapli)

Use pytest with scrapli for SSH-based validation:

# infrastructure/network/vyos/tests/test_gateway.py
import pytest
from scrapli.driver.network import VyOSDriver

@pytest.fixture(scope="module")
def vyos():
    conn = VyOSDriver(
        host="clab-vyos-gateway-test-gateway",
        auth_username="vyos",
        auth_password="vyos",
        auth_strict_key=False,
    )
    conn.open()
    yield conn
    conn.close()

class TestFirewallConfiguration:
    def test_firewall_groups_exist(self, vyos):
        result = vyos.send_command("show firewall group")
        assert "HOME_NETWORK" in result.result
        assert "LAB_NETWORKS" in result.result
        assert "RFC1918" in result.result

    def test_wan_to_lab_rules(self, vyos):
        result = vyos.send_command("show firewall ipv4 name WAN_TO_LAB")
        assert "default-action drop" in result.result
        assert "Allow established/related" in result.result

    def test_lab_to_wan_rules(self, vyos):
        result = vyos.send_command("show firewall ipv4 name LAB_TO_WAN")
        assert "Block new connections to home network" in result.result

class TestInterfaceConfiguration:
    def test_vlan_interfaces_configured(self, vyos):
        result = vyos.send_command("show interfaces")
        assert "eth5.10" in result.result  # LAB_MGMT
        assert "eth5.20" in result.result  # LAB_PROV
        assert "eth5.30" in result.result  # LAB_PLATFORM
        assert "eth5.50" in result.result  # LAB_SERVICE

    def test_interface_addresses(self, vyos):
        result = vyos.send_command("show interfaces ethernet eth5 vif 10")
        assert "10.10.10.1/24" in result.result

class TestServicesConfiguration:
    def test_dhcp_server_configured(self, vyos):
        result = vyos.send_command("show dhcp server leases")
        # Command should succeed (service running)
        assert result.failed is False

    def test_dns_forwarding_configured(self, vyos):
        result = vyos.send_command("show dns forwarding statistics")
        assert result.failed is False

    def test_bgp_neighbors_configured(self, vyos):
        result = vyos.send_command("show protocols bgp neighbor")
        assert "10.10.30.10" in result.result  # platform-cp-1
        assert "10.10.30.11" in result.result  # platform-cp-2
        assert "10.10.30.12" in result.result  # platform-cp-3

class TestNATConfiguration:
    def test_masquerade_rule_exists(self, vyos):
        result = vyos.send_command("show nat source rules")
        assert "masquerade" in result.result.lower()
        assert "10.10.0.0/16" in result.result

4. GitHub Actions Integration

Extend the vyos-build workflow to run integration tests on PRs:

# In .github/workflows/vyos-build.yml (or new vyos-integration-test.yml)

integration-test:
  needs: build
  if: github.event_name == 'pull_request'
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - name: Download container image
      uses: actions/download-artifact@v4
      with:
        name: vyos-container-image

    - name: Load container image
      run: docker load -i vyos-gateway.tar

    - name: Install Containerlab
      run: bash -c "$(curl -sL https://get.containerlab.dev)"

    - name: Install test dependencies
      run: pip install pytest scrapli

    - name: Deploy topology
      run: sudo containerlab deploy -t infrastructure/network/vyos/tests/topology.clab.yml

    - name: Wait for VyOS boot
      run: |
        echo "Waiting for VyOS to boot..."
        sleep 90
        # Optionally: poll for SSH availability

    - name: Run integration tests
      run: pytest infrastructure/network/vyos/tests/ -v --tb=short

    - name: Cleanup
      if: always()
      run: sudo containerlab destroy -t infrastructure/network/vyos/tests/topology.clab.yml --cleanup

Implementation Steps

  1. Extend vyos-build workflow to produce and upload container image artifact
  2. Create Containerlab topology file at infrastructure/network/vyos/tests/topology.clab.yml
  3. Create Dockerfile.containerlab for container image build
  4. Write pytest test suite covering firewall, interfaces, services, NAT, and BGP
  5. Add integration-test job to GitHub Actions workflow
  6. Document the testing approach in ADR-003 or a new testing doc

Considerations

VyOS Boot Time

VyOS containers take 45-90 seconds to fully boot. The workflow should include adequate wait time or poll for readiness.

Privileged Containers

VyOS requires --privileged mode. GitHub-hosted runners support this.

Interface Mapping

Containerlab uses eth0 for management. The topology must map test interfaces to eth1+ to avoid conflicts with the production config's interface expectations.

Test Scope

Start with configuration validation tests (verifying config is loaded correctly). Functional tests (e.g., actually testing DHCP lease acquisition) can be added later.

Acceptance Criteria

  • Container image is built from vyos-build squashfs artifact
  • Containerlab topology deploys successfully with gateway + test clients
  • pytest suite validates: firewall rules, interface config, DHCP, DNS, BGP, NAT
  • Integration tests run automatically on PRs modifying infrastructure/network/vyos/**
  • Tests complete within reasonable time (~3-5 minutes)
  • Documentation updated to reflect new testing capability

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions