Skip to content

Add Arrcus ArcOS as a containerlab device (best-effort)#3681

Open
roc-ops wants to merge 2 commits into
ipspace:devfrom
roc-ops:arcos-device
Open

Add Arrcus ArcOS as a containerlab device (best-effort)#3681
roc-ops wants to merge 2 commits into
ipspace:devfrom
roc-ops:arcos-device

Conversation

@roc-ops

@roc-ops roc-ops commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Add Arrcus ArcOS as a containerlab device (best-effort)

This PR adds Arrcus ArcOS as a netlab device. ArcOS runs as a native
containerlab node (kind: arrcus_arcos) — there is no vrnetlab packaging and no
Vagrant box. Support level is best-effort.

Connection and validation model — no core change

ArcOS ships the official arrcus.arcos Ansible collection (the project-recommended
network_cli model), but every published version (2.0.13–2.0.18) hangs against the
tested container image: its cliconf/terminal plugins issue a config terminal /
arcos_cli mode-switch that this build's confd_cli rejects. Configuration is
therefore deployed over Ansible's built-in docker connection plugin (docker exec running confd_cli load merge / commit) — the same pattern
sonic_clab already uses. The device keeps a commented-out network_cli block to
switch to once a working collection version ships.

netlab validate reads device state over the same docker-exec path
(show <path> | display json | confd_cli, parsed as OpenConfig JSON by
netsim/validate/**/arcos.py). This is netlab's standard device-side show-command
validation — no ansible validation action and no change to any core file.
netsim/cli/connect.py, netsim/cli/validate.py, netsim/augment/validate.py,
netsim/attributes.yml, netsim/templates/provider/clab/clab.j2, and tests.py are
untouched (byte-identical to dev). This is a pure device addition.

The tested image boots with SSH/NETCONF/gNMI disabled and refuses to enable
interfaces until the factory admin-user password is changed; both are handled with
the native netlab_start_exec group_var (containerlab's post-start exec:) — no
custom deploy code.

Module coverage

initial, ospf (v2/v3), bgp, isis, vrf (+ospf/isis/bgp), vlan, lag,
gateway (VRRP), dhcp/relay, bfd, routing (static/prefix-set/policy),
mpls (LDP), sr (SR-MPLS via IS-IS), srv6, vxlan, and evpn (L2VNI).

Honest limitations are documented in docs/caveats.md (e.g. EVPN symmetric IRB/L3VNI,
VRF route-target leaking, and OSPFv2 area authentication are genuinely unsupported and
not declared; MPLS/SR/SRv6 bring up the control plane only on the native container;
DHCP relay renders but its datapath is not exercised on a clab-only host, so ArcOS is
omitted from the DHCP support table).

Verification

Verified on real ArcOS containers (image arcos:8.2.1A.P2), containerlab provider,
against this branch:

Test Result
02-ospf — OSPFv2 adjacency 2/2 PASS (FULL both directions)
03-bgp — eBGP session + prefixes 4/4 PASS (ESTABLISHED + peer loopbacks in RIB)
04-isis — IS-IS adjacency + reachability 4/4 PASS
05-routing — static route 2/2 PASS; kernel FIB (proto static) installed and loopback-to-loopback ping 0% loss
combined ospf+bfd+vrf deploys clean; per-VRF OSPF reaches FULL; BFD session UP both directions

netlab initial commits every module cleanly (idempotent on re-run) and netlab collect retrieves the running configuration. The rendered CLI and the show/get paths
used by the validators were cross-checked against the vendor ArcOS S8.5.1A CLI
reference.

Files

  • Device: netsim/devices/arcos.yml
  • Deploy/collect tasks: netsim/ansible/tasks/{deploy-config,fetch-config}/arcos.yml
  • Templates: netsim/ansible/templates/*/arcos.j2 (16 modules)
  • Validators: netsim/validate/arcos.py + netsim/validate/{ospf,bgp,isis,routing}/arcos.py
    (adds the netsim/validate/routing/ package)
  • Integration tests: tests/integration/platform/arcos/
  • Docs: docs/platforms.md, docs/caveats.md, docs/install/arcos.md,
    docs/release/26.07.md

This PR also removes netsim/templates/provider/libvirt/arcos-domain.j2, an orphaned
libvirt domain template from a much older, never-completed ArcOS device (no
netsim/devices/arcos.yml ever existed for it); the new device is containerlab-only.

ArcOS is a commercial NOS, so users supply their own container image (as with
sros/dellos10/arubacx).

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before going into the details: I think you should change the configuration deployment method.

It looks like you're generating a bash script that takes a config and runs confd_cli to deploy it. If that's the case, you don't need Ansible -- netlab can generate bash scripts, map them to the container, and then execute them inside the container. You can also map templates into container files to create scripts (like what you have in the shell command) that are then executed.

FRR uses the "run a bash script" approach together with "let's add a shebang when needed" trick. cRPD maps a template to a container script to generate the deployment script (/config/netlab/netlab-config.sh).

More @ https://netlab.tools/dev/config/deploy/#linux-configuration-scripts

@ipspace

ipspace commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Next major item: we don't have per-platform integration tests (what you added in tests/integration/platform). Please remove those.

The device implementations are tested with regular integration tests (see https://netlab.tools/dev/integration-tests/). If you want to test the validation plugins, you'll find some tests in tests/platform-integration/validate.

Comment thread netsim/ansible/templates/bgp/arcos.j2 Outdated
global afi-safi IPV6_UNICAST
{% endif %}
{% for a in bgp.advertise|default([]) %}
{% if a.ipv4 is defined %}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more generic comment -- I strongly recommend you use the same indentation trick we use in other Jinja2 templates to make the templates more readable. Unfortunately, that's the best I could come up with, and it looks like some vendors picked it up as well.

For example:

{% for x %}
{%   if y is defined %}
{%   endif %}
{% endfor %}

roc-ops added 2 commits July 22, 2026 19:33
Modernize the legacy ArcOS stub into a full containerlab (arrcus_arcos)
device: initial, ospf (v2/v3), bgp, isis, vrf, vlan, lag, gateway (VRRP),
dhcp/relay, bfd, routing (static/prefix/policy), mpls (LDP), sr (SR-MPLS via
IS-IS), srv6, vxlan and evpn (L2VNI) configuration templates, plus OpenConfig
JSON validation plugins (ospf/bgp/isis/routing).

Configuration is deployed with netlab native "sh" config mode: each module is
rendered into /config/netlab/NN-<module>.sh with a
#!/config/netlab/netlab-config.sh shebang and executed via docker exec, which
hands the rendered config to the mapped wrapper
templates/provider/clab/arcos/netlab-config.j2 to load through confd_cli
(load merge / commit). This is the same mapped-script deploy pattern Juniper
cRPD uses; no Ansible is involved in the deploy path. ansible_connection:
docker is retained only for the validation/collect path, since the tested
build has SSH/NETCONF/gNMI disabled.

Drop the legacy Ansible deploy-config task and the unused libvirt domain
template (the device is clab-only).
Install guide, caveats, platform table entry and release note for the ArcOS
containerlab device, documenting the native sh-mode confd_cli deploy path and
the docker-exec validation model.
@roc-ops

roc-ops commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review, Ivan — all three addressed.

1. Deploy method. Switched the ArcOS device from the Ansible deploy task to netlab's native container-script (sh) config mode, following cRPD: a wrapper netsim/templates/provider/clab/arcos/netlab-config.j2 is mapped to /config/netlab/netlab-config.sh, and each module is rendered with a #!/config/netlab/netlab-config.sh shebang and executed via docker exec, which hands the rendered config to the wrapper to load through confd_cli (load merge/commit). netsim/ansible/tasks/deploy-config/arcos.yml is removed; no Ansible is involved in deployment. I kept the docker connection only for the validation/collect path (the tested build has SSH/NETCONF/gNMI disabled). Re-verified on a real arcos:8.2.1A.P2 container: netlab up deploys via the mapped script and OSPF reaches FULL / iBGP ESTABLISHED.

2. Platform tests. Removed tests/integration/platform/arcos/ entirely — the device is exercised by the regular integration tests.

3. Jinja2 indentation. Applied the readable nested-indentation convention (tag keyword indented with the block) across the ArcOS templates.

Rebased onto current dev.

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of nits and a few major showstoppers:

A) Changes to documentation (file placement, caveats)
B) IPv4/IPv6 feature parity
C) Correct configuration of IPv4-only, dual-stack and IPv6-only environments. I'm pretty sure we're not testing every potential error, so incorrect configurations could pass the integration tests just because we never thought that someone could do X.
D) Global/VRF feature parity. We expect people to use same/similar configuration for global and VRF routing protocols, and thus do not check every detail like support for areas or OSPF cost in VRF OSPF instances.
E) OSPF areas

Also, while I cannot test stuff (and wouldn't even bother with a vendor that does not have a way to download images for non-customers), I would strongly recommend you run the full set of integration tests. I'm sure some of them (like OSPF multi-area test) would fail.

Comment thread docs/install/arcos.md
@@ -0,0 +1,54 @@
# Installing Arrcus ArcOS

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be in docs/labs. The "docs/install" directory is for netlab installation, the "docs/labs" directory is for providers and devices.

Comment thread docs/release/26.07.md
* The [**WireGuard tunnel** plugin](plugin-tunnel-wireguard) supports WireGuard tunnels on FRR.
* The [**bgp.session** plugin](plugin-bgp-session) and the [OSPF module](module-ospf) support graceful restart on Arista EOS, BIRD, FortiOS, and FRR
* The [**bgp.policy** plugin](plugin-bgp-policy) supports the **bgp.role** attribute on FRR and BIRD.
* [Arrcus ArcOS](https://arrcus.com/) is now supported (best-effort) as a native containerlab node, with configuration deployment and validation over a `docker exec` + `confd_cli` path.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in 26.08. 26.07 is already out.

Comment thread docs/caveats.md
## Arrcus ArcOS

* ArcOS ships a commercial NOS image; there is no public Vagrant/Containerlab box. ArcOS runs as a **native** containerlab node (`kind: arrcus_arcos`) -- no vrnetlab packaging. Provide your own container image and tag it as in `netsim/devices/arcos.yml` (`clab.image`), or override `clab.image` in your topology.
* The tested container image boots with **SSH, NETCONF, and gNMI all disabled** (its baked-in startup config fails to load a built-in prefix-set). *netlab* bootstraps the node with the native `netlab_start_exec` group_var (containerlab's post-start `exec:`), which enables `ssh-server`, changes the factory-default admin-user password (ArcOS refuses to enable interfaces until this is done), and creates an AAA user. No custom deploy code and no SSH is used to run it.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just point to the labs/arcos.md document instead of repeating the points already made there.

Comment thread docs/caveats.md
* The tested container image boots with **SSH, NETCONF, and gNMI all disabled** (its baked-in startup config fails to load a built-in prefix-set). *netlab* bootstraps the node with the native `netlab_start_exec` group_var (containerlab's post-start `exec:`), which enables `ssh-server`, changes the factory-default admin-user password (ArcOS refuses to enable interfaces until this is done), and creates an AAA user. No custom deploy code and no SSH is used to run it.
* Configuration is deployed with netlab's native containerlab **"sh" config mode**: each module is rendered into `/config/netlab/NN-<module>.sh` and executed via `docker exec`, loading config through `confd_cli` (`load merge` / `commit`) via the mapped wrapper `netsim/templates/provider/clab/arcos/netlab-config.j2` -- the same mapped-script pattern Juniper cRPD uses, with no Ansible in the deploy path. Validation/collect use `ansible_connection: docker` (`docker exec` running `confd_cli`). ArcOS ships the official `arrcus.arcos` `network_cli` collection (the project-recommended model), but every published version (2.0.13-2.0.18) hangs against this image: its cliconf/terminal plugins issue a `config terminal` / `arcos_cli` mode-switch that this build's `confd_cli` rejects, so it is not used.
* `netlab validate` reads ArcOS state over the **same** docker-exec path, running `show <path> | display json | confd_cli` and parsing the OpenConfig JSON (`netsim/validate/**/arcos.py`). This uses netlab's standard device-side show-command validation -- **no** `ansible` validation action, and no SSH/NETCONF/gNMI.
* **MTU:** the `interface <if> mtu <n>` leaf is not a pass-through -- ArcOS programs the real kernel netdev MTU (and the value it advertises in OSPF DBD packets) to `<n> - 26` on every commit. `netsim/ansible/templates/initial/arcos.j2` therefore sets the leaf to `l.mtu + 26` so ArcOS lands on netlab's intended MTU; this alone makes an ArcOS<->FRR OSPF adjacency converge, with no containerlab/core change.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just say that initial device configuration sets ArcOS to lab topology layer-3 MTU (interface.mtu) + 26.

Comment thread docs/caveats.md
* Configuration is deployed with netlab's native containerlab **"sh" config mode**: each module is rendered into `/config/netlab/NN-<module>.sh` and executed via `docker exec`, loading config through `confd_cli` (`load merge` / `commit`) via the mapped wrapper `netsim/templates/provider/clab/arcos/netlab-config.j2` -- the same mapped-script pattern Juniper cRPD uses, with no Ansible in the deploy path. Validation/collect use `ansible_connection: docker` (`docker exec` running `confd_cli`). ArcOS ships the official `arrcus.arcos` `network_cli` collection (the project-recommended model), but every published version (2.0.13-2.0.18) hangs against this image: its cliconf/terminal plugins issue a `config terminal` / `arcos_cli` mode-switch that this build's `confd_cli` rejects, so it is not used.
* `netlab validate` reads ArcOS state over the **same** docker-exec path, running `show <path> | display json | confd_cli` and parsing the OpenConfig JSON (`netsim/validate/**/arcos.py`). This uses netlab's standard device-side show-command validation -- **no** `ansible` validation action, and no SSH/NETCONF/gNMI.
* **MTU:** the `interface <if> mtu <n>` leaf is not a pass-through -- ArcOS programs the real kernel netdev MTU (and the value it advertises in OSPF DBD packets) to `<n> - 26` on every commit. `netsim/ansible/templates/initial/arcos.j2` therefore sets the leaf to `l.mtu + 26` so ArcOS lands on netlab's intended MTU; this alone makes an ArcOS<->FRR OSPF adjacency converge, with no containerlab/core change.
* **Static routing validation** reads the committed route from the running-config JSON rather than an operational RIB: this virtual image does not populate an OpenConfig STATIC operational-state/RIB tree. A present, complete static-route entry proves netlab rendered it and the device accepted and committed it; kernel FIB install and forwarding were verified separately.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't go into so many details. I would just say something along the lines of "netlab configures static routes and they are used for traffic forwarding, but the tested ArcOS image does not report them in OpenConfig RIB tree"

{% endif %}
{% if routing.policy|default({}) %}
{% for p_name, p_entries in routing.policy.items() %}
routing-policy policy-definition {{ p_name }}-ipv4

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be IPv4-only. We can do both protocols.

{% endfor %}
{% endif %}
{% if routing.static|default([]) %}
network-instance default protocol STATIC default

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yet again, seems to be the exact same configs for IPv4 and IPv6, so maybe use an inner loop.

{% if l.type|default("") == "svi" %}
interface {{ l.ifname }} enabled true
interface {{ l.ifname }} autostate false
{% if l.ipv4 is defined and l.ipv4 %}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about IPv6 over VLANs? Also: MTU?

{% set nt = l.ospf.network_type|default('point-to-point' if l.type|default('') == 'p2p' else '') %}
{% if nt == 'point-to-point' %}
network-type POINT_TO_POINT_NETWORK
{% endif %}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about cost, passive interfaces, and the like? We usually define macros in OSPF/BGP/IS-IS templates and then reuse them in VRFs

network-instance default protocol OSPF3 p{{ pid }}
global router-id {{ ospf.router_id }}
global log-adjacency-changes LOG_ADJ_ENABLE_DETAILED
area {{ area3 }}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where you define other areas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants