diff --git a/docs/caveats.md b/docs/caveats.md index 0781ff5913..f1c3a9a894 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -520,6 +520,16 @@ ansible_httpapi_port: 80 * An OSPFv3 ABR running FRR release 10.3 does not originate summary external routes from NSSA areas * As of FRRouting release 10.6.0, the FRR implementation of EVPN for VXLAN-over-IPv6 is incompatible with the Arista EOS implementation due to different encodings of the PMSI_TUNNEL_ATTRIBUTE. +(caveats-ocnos)= +## IP Infusion OcNOS + +* _netlab_ runs IP Infusion OcNOS as a *vrnetlab* container (*containerlab* kind `ipinfusion_ocnos`). Download the OcNOS VM image for KVM (free, registration required) from the [IP Infusion web site](https://www.ipinfusion.com/free-software-demos/?vm=KVM&src=netlab). Decompress it (`xz -d`), copy the `.qcow2` file into the `ipinfusion/ocnos` directory of the [vrnetlab fork](https://github.com/srl-labs/vrnetlab), and run `make docker-image`. +* We tested _netlab_ with the `OcNOS-SP-PLUS-x86-7.0.0-262-GA.qcow2` image; `make docker-image` builds it into the `vrnetlab/ipinfusion_ocnos:7.0.0-262` container image. +* _netlab_ uses the default credentials of the *containerlab* kind (`admin`/`admin@123`). +* The management interface (`eth0`) is in the `management` VRF, so the *vrnetlab* management subnet (`10.0.0.0/24`) does not conflict with the _netlab_ loopback addresses. The data-plane interfaces start with `eth1`. +* The loopback interface `lo` always has 127.0.0.1/8 as its primary IPv4 address; _netlab_ configures the loopback IPv4 address as a secondary address. Additional loopback interfaces are named `loopback`. +* OcNOS assigns OSPFv2 areas with **network** statements in the OSPF routing process. + (caveats-junos)= ## Common Junos caveats diff --git a/docs/dev/config/netmiko.md b/docs/dev/config/netmiko.md index 0860a9ba87..24669e1b3b 100644 --- a/docs/dev/config/netmiko.md +++ b/docs/dev/config/netmiko.md @@ -12,6 +12,7 @@ This document describes how the *netmiko* configuration method works and which d | Cisco IOS/IOS XE[^IOS] | `ios.yml` | `cisco_ios` | Ansible | | Cisco Nexus OS | `nxos.yml` | `cisco_nxos` | **netmiko** | | Cisco IOS XR[^XR] | `xr.yml` | `cisco_xr` | **netmiko** | +| IP Infusion OcNOS | `ocnos.yml` | `ipinfusion_ocnos` | **netmiko** | [^IOS]: Including all devices using **ios** as their parent device: Cisco IOSv, IOSvL2, CSR 1000v, Catalyst 8000v, IOL, and IOL L2 image. @@ -107,6 +108,16 @@ netmiko: step_config: True ``` +**IP Infusion OcNOS** (`ocnos.yml`): + +``` +netmiko: + device_type: ipinfusion_ocnos + error_regexp: # OcNOS errors start with '%'; '% Warning' lines are informational + - "^%(?! Warning).*" + step_config: true +``` + ```{tip} The **error_regexp** regular expression should match the whole error message, not just its first few characters. The matched text is included in the error message _netlab_ displays when the configuration deployment fails. ``` @@ -183,7 +194,7 @@ You can use any integration test (see [](dev-config-initial-tests)) to test the netlab up -d eos -s defaults.devices.eos.group_vars.netlab_config_mode=netmiko ``` -The `tests/config` directory contains topologies that can be used to test the error detection on Cisco IOS (`ios-netmiko.yml`) and Nexus OS (`nxos-netmiko.yml`) with the **netlab config** command. For example, to verify that the *netmiko* configuration method detects invalid configuration commands on Cisco IOS: +The `tests/config` directory contains topologies that can be used to test the error detection on Cisco IOS (`ios-netmiko.yml`), Nexus OS (`nxos-netmiko.yml`), and OcNOS (`ocnos-netmiko.yml`) with the **netlab config** command. For example, to verify that the *netmiko* configuration method detects invalid configuration commands on Cisco IOS: * Start the lab with `netlab up ios-netmiko.yml` * Execute `netlab config invalid` to deploy an invalid configuration command; the configuration deployment should fail. diff --git a/docs/labs/clab.md b/docs/labs/clab.md index 3db96ff417..4a4e29466c 100644 --- a/docs/labs/clab.md +++ b/docs/labs/clab.md @@ -50,6 +50,7 @@ Lab topology file created by **[netlab up](../netlab/up.md)** or **[netlab creat | Extreme VOSS | vrnetlab/extreme_voss:9.4.0.0 | | Fortinet FortiOS | vrnetlab/vr-fortios:7.4.8 | | FRR | quay.io/frrouting/frr:10.1.2 | +| IP Infusion OcNOS [❗](caveats-ocnos) | vrnetlab/ipinfusion_ocnos:7.0.0-262 | | Juniper vMX | vrnetlab/vr-vmx:18.2R1.9 | | Juniper vPTX | vrnetlab/juniper_vjunosevolved:23.4R2-S2.1 | | Juniper vSRX | vrnetlab/vr-vsrx:23.1R1.8 | @@ -69,7 +70,7 @@ Lab topology file created by **[netlab up](../netlab/up.md)** or **[netlab creat * Follow Cisco's documentation to [install the IOS XRd container](build-iosxr-clab) (you have to change topology defaults to use Cisco IOS XRd vRouter), making sure the container image name matches the one _netlab_ uses (alternatively, [change the default image name](default-device-image) for the IOS XRd container). * Cisco 8000v containerlab image (once you manage to get it) has to be [installed](https://containerlab.dev/manual/kinds/c8000/#getting-cisco-8000-containerlab-docker-images) with the **docker image load** command. -You can also use [vrnetlab](https://github.com/srl-labs/vrnetlab) to build VM-in-container images for Catalyst 8000v, Cisco CSR 1000v, Cisco IOSv, Cisco IOS on Linux (including layer-2 image), Nexus 9300v, IOS XR, Mikrotik RouterOS, Arista vEOS, Juniper vMX, vPTX, vQFX, and a few other devices. +You can also use [vrnetlab](https://github.com/srl-labs/vrnetlab) to build VM-in-container images for Catalyst 8000v, Cisco CSR 1000v, Cisco IOSv, Cisco IOS on Linux (including layer-2 image), Nexus 9300v, IOS XR, Mikrotik RouterOS, Arista vEOS, IP Infusion OcNOS, Juniper vMX, vPTX, vQFX, and a few other devices. ```{warning} * You might have to change the default loopback address pool when using _vrnetlab_ images. See [](clab-vrnetlab) for details. diff --git a/docs/platforms.md b/docs/platforms.md index 36f92a6b6c..8a14236129 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -34,6 +34,7 @@ | Fortinet FortiOS [❗](caveats-fortios) | fortios | minimal | | FRRouting (FRR) [❗](caveats-frr) | frr | full | | [Generic Linux host](generic-linux-devices) | linux | full | +| IP Infusion OcNOS [❗](caveats-ocnos) | ocnos | minimal | | Juniper cRPD | crpd | full | | Juniper cSRX [❗](caveats-csrx) | csrx | minimal | | Juniper vMX [❗](caveats-vmx) | vmx | best effort | @@ -138,6 +139,7 @@ You cannot use all supported network devices with all virtualization providers. | Fortinet FortiOS | [✅](build-fortios) | ✅ | | FRR | [✅](build-frr)[❗](caveats-frr) | ✅ | | Generic Linux (Ubuntu/Alpine) | [❗](labs/linux.md) | ✅ | +| IP Infusion OcNOS | ❌ | ✅[❗](caveats-ocnos) | | Juniper cRPD | ❌ | ✅ | | Juniper cSRX | ❌ | ✅ | | Juniper vMX | ❌ | ✅[❗](clab-vrnetlab) | @@ -306,6 +308,7 @@ The following system-wide features are configured on supported network operating | Fortinet FortiOS | ✅ | ❌ | ✅ | ✅ | ✅ | | FRR | ✅ | ✅[^HIF] | ❌ | ✅ | ✅ | | Generic Linux | ✅ | ✅[^HIF] | ✅[❗](linux-lldp) | ✅ | ✅ | +| IP Infusion OcNOS | ✅ | ❌ | ❌ | ✅ | ✅ | | Juniper cSRX | ✅ | ✅ | ❌ | ❌ | ❌ | | Junos[^Junos] | ✅ | ❌ | ✅ | ✅ | ✅ | | Mikrotik RouterOS 6 | ✅ | ✅ | ✅[❗](caveats-routeros6) | ✅ | ✅ | @@ -340,6 +343,7 @@ The following interface parameters are configured on supported network operating | Fortinet FortiOS | ✅ | ✅ | ✅[❗](caveats-fortios) | ❌ | | FRR | ✅ | ✅ | ✅ | ✅ | | Generic Linux | ❌ | ❌ | ✅ | ❌ | +| IP Infusion OcNOS | ✅ | ❌ | ✅ | ✅ | | Juniper cSRX | ✅ | ❌ | ✅ | ❌ | | Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | | Mikrotik RouterOS 6 | ✅ | ❌ | ✅ | ❌ | @@ -371,6 +375,7 @@ The following interface addresses are supported on various platforms; most daemo | Fortinet FortiOS | ✅ | ✅ | ❌ | ❌ | | FRR | ✅ | ✅ | ✅ | ✅ | | Generic Linux | ✅ | ✅ | ❌ | ❌ | +| IP Infusion OcNOS | ✅ | ✅ | ❌ | ❌ | | Juniper cSRX | ✅ | ✅ | ❌ | ❌ | | Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | | Mikrotik RouterOS 6 | ✅ | ✅ | ❌ | ❌ | @@ -421,6 +426,7 @@ Routing protocol [configuration modules](module-reference.md) are supported on t | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | | Fortinet FortiOS | ✅ [❗](caveats-fortios) | ❌ | ❌ | ✅ | ❌ | | FRR | ✅ | ✅ | ❌ | ✅ | ✅ | +| IP Infusion OcNOS | ✅ | ✅ | ❌ | ✅ | ❌ | | Junos[^Junos] | ✅ | ✅ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 6 | ✅ | ❌ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ | @@ -550,6 +556,7 @@ Core *netlab* functionality and all multi-protocol routing protocol configuratio | Extreme Networks EXOS | ✅ | ❌ | ❌ | ❌ | ❌ | | Fortinet FortiOS | ✅ | ❌ | ❌ | ✅ | ❌ | | FRR | ✅ | ✅ | ❌ | ✅ | ❌ | +| IP Infusion OcNOS | ✅ | ✅ | ❌ | ✅ | ❌ | | Junos[^Junos] | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | Mikrotik RouterOS 6 | ❌ | ❌ | ❌ | ✅ | ❌ | | Mikrotik RouterOS 7 | ✅ | ❌ | ❌ | ✅ | ❌ | diff --git a/netsim/ansible/tasks/deploy-config/ocnos.yml b/netsim/ansible/tasks/deploy-config/ocnos.yml new file mode 100644 index 0000000000..07b0c14bf9 --- /dev/null +++ b/netsim/ansible/tasks/deploy-config/ocnos.yml @@ -0,0 +1,6 @@ +--- +- name: "ocnos_config: deploying {{ netsim_action }} from {{ config_template }}" + ipinfusion.ocnos.ocnos_config: + src: "{{ config_template }}" + save_when: "{{ 'modified' if netlab_save_config|default(True) else 'never' }}" + tags: [ print_action, always ] diff --git a/netsim/ansible/tasks/fetch-config/ocnos.yml b/netsim/ansible/tasks/fetch-config/ocnos.yml new file mode 100644 index 0000000000..eeff69813a --- /dev/null +++ b/netsim/ansible/tasks/fetch-config/ocnos.yml @@ -0,0 +1,9 @@ +# fetch IP Infusion OcNOS configuration with ocnos_command +# +--- +- ipinfusion.ocnos.ocnos_command: + commands: [ show running-config ] + register: ocnos_running_config + +- set_fact: + ansible_net_config: "{{ ocnos_running_config.stdout[0] }}" diff --git a/netsim/ansible/templates/bgp/ocnos.j2 b/netsim/ansible/templates/bgp/ocnos.j2 new file mode 100644 index 0000000000..104f0e2acd --- /dev/null +++ b/netsim/ansible/templates/bgp/ocnos.j2 @@ -0,0 +1,32 @@ +{% import "ocnos.macro.j2" as bgpcfg %} +{# + Legacy bgp.originate (IPv4 prefixes): OcNOS advertises a "network" prefix only when it + is in the routing table, so add a discard route for each originated prefix +#} +{% for pfx in bgp.originate|default([]) %} +ip route {{ pfx }} null +{% endfor %} +! +router bgp {{ bgp.as }} +{% if bgp.router_id is defined %} + bgp router-id {{ bgp.router_id }} +{% endif %} +{% if bgp.rr|default(False) and bgp.rr_cluster_id|default(False) %} + bgp cluster-id {{ bgp.rr_cluster_id }} +{% endif %} +{% for n in bgp.neighbors %} +{% for af in ['ipv4','ipv6'] if n[af] is defined %} +{{ bgpcfg.neighbor_global(n,n[af]) }} +{% endfor %} +{% endfor %} +{% for af in ['ipv4','ipv6'] if bgp[af] is defined %} + address-family {{ af }} unicast +{% for pfx in bgp.advertise|default([]) if af in pfx %} + network {{ pfx[af] }} +{% endfor %} +{% for n in bgp.neighbors if n.activate[af]|default(false) %} +{{ bgpcfg.neighbor_af(n,n[af],bgp) }} +{% endfor %} + exit-address-family +{% endfor %} +! diff --git a/netsim/ansible/templates/bgp/ocnos.macro.j2 b/netsim/ansible/templates/bgp/ocnos.macro.j2 new file mode 100644 index 0000000000..bbcaa42fdd --- /dev/null +++ b/netsim/ansible/templates/bgp/ocnos.macro.j2 @@ -0,0 +1,29 @@ +{# + Global BGP neighbor definition. OcNOS sends standard and extended communities by default + ("send-community" on an enabled type is rejected), and advertisement-interval is a + router-level neighbor setting. +#} +{% macro neighbor_global(n,ip) %} + neighbor {{ ip }} remote-as {{ n.as }} + neighbor {{ ip }} description {{ n.name }} + neighbor {{ ip }} advertisement-interval 0 +{% if n._source_intf is defined %} + neighbor {{ ip }} update-source {{ n._source_intf.ifname }} +{% endif %} +{%- endmacro %} +{# + Address family BGP neighbor definition +#} +{% macro neighbor_af(n,ip,bgp) %} + neighbor {{ ip }} activate +{% if n.next_hop_self|default(False) %} + neighbor {{ ip }} next-hop-self +{% endif %} +{% if n.rr_client|default(False) %} + neighbor {{ ip }} route-reflector-client +{% endif %} +{% set c_list = bgp.community[n.type]|default([]) %} +{% for c_type in ['standard','extended'] if c_type not in c_list %} + no neighbor {{ ip }} send-community {{ c_type }} +{% endfor %} +{%- endmacro %} diff --git a/netsim/ansible/templates/initial/ocnos.j2 b/netsim/ansible/templates/initial/ocnos.j2 new file mode 100644 index 0000000000..748da2d668 --- /dev/null +++ b/netsim/ansible/templates/initial/ocnos.j2 @@ -0,0 +1,24 @@ +hostname {{ inventory_hostname.replace("_","-") }} +! +{% for l in netlab_interfaces %} +interface {{ l.ifname }} +{% if l.name is defined %} + description {{ l.name|replace('"','') }}{{ " [" + l.role + "]" if l.role is defined else "" }} +{% elif l.type|default("") == "stub" %} + description Stub interface +{% endif %} +{% if l.mtu is defined and l.type|default("") != "loopback" %} + mtu {{ l.mtu }} +{% endif %} +{% if 'ipv4' in l and l.ipv4 is string %} +{# "lo" already has 127.0.0.1/8 as its primary address #} + ip address {{ l.ipv4 }}{{ " secondary" if l.ifname == "lo" else "" }} +{% endif %} +{% if 'ipv6' in l and l.ipv6 is string %} + ipv6 address {{ l.ipv6 }} +{% endif %} +{% if l.type|default("") != "loopback" %} + {{ "shutdown" if l.shutdown|default(False) else "no shutdown" }} +{% endif %} +! +{% endfor %} diff --git a/netsim/ansible/templates/isis/ocnos.j2 b/netsim/ansible/templates/isis/ocnos.j2 new file mode 100644 index 0000000000..7219904323 --- /dev/null +++ b/netsim/ansible/templates/isis/ocnos.j2 @@ -0,0 +1,41 @@ +{# + OcNOS uses "level-2-only" where netlab uses "level-2", and "isis metric" takes narrow + metrics only (1-63); wide metrics are set with "isis wide-metric". +#} +{% set lvl = { 'level-1': 'level-1', 'level-2': 'level-2-only', 'level-1-2': 'level-1-2' } %} +{% set mt_lvl = { 'level-1': 'level-1', 'level-2': 'level-2', 'level-1-2': 'level-1-2' } %} +{% set wide = isis.wide_metrics|default(True) %} +! +router isis {{ isis.instance }} + is-type {{ lvl[isis.type]|default(isis.type) }} + net {{ isis.net }} + metric-style {{ 'wide' if wide else 'narrow' }} + dynamic-hostname +{% for l in netlab_interfaces if l.isis.passive|default(False) and 'vrf' not in l %} + passive-interface {{ l.ifname }} +{% endfor %} +{% if isis.af.ipv6 is defined %} + address-family ipv6 unicast + multi-topology {{ mt_lvl[isis.type]|default('level-1-2') }} + exit-address-family +{% endif %} +! +{% for l in netlab_interfaces if 'isis' in l and 'vrf' not in l %} +interface {{ l.ifname }} +{% if 'ipv4' in l and isis.af.ipv4 is defined %} + ip router isis {{ isis.instance }} +{% endif %} +{% if 'ipv6' in l and isis.af.ipv6 is defined %} + ipv6 router isis {{ isis.instance }} +{% endif %} +{% if l.isis.network_type is defined %} + isis network {{ l.isis.network_type }} +{% endif %} +{% if l.isis.type is defined %} + isis circuit-type {{ lvl[l.isis.type]|default(l.isis.type) }} +{% endif %} +{% if l.isis.metric is defined %} + isis {{ 'wide-metric' if wide else 'metric' }} {{ l.isis.metric }} +{% endif %} +! +{% endfor %} diff --git a/netsim/ansible/templates/ospf/ocnos.j2 b/netsim/ansible/templates/ospf/ocnos.j2 new file mode 100644 index 0000000000..21e79b4553 --- /dev/null +++ b/netsim/ansible/templates/ospf/ocnos.j2 @@ -0,0 +1,79 @@ +{# + OcNOS assigns OSPF areas with "network" statements in the router configuration, + not with interface commands. A host prefix (interface address) is accepted and + normalized to the subnet. +#} +{% set ospf_pid = ospf.process|default(1) %} +{% if ospf.af.ipv4 is defined %} +router ospf {{ ospf_pid }} +{% if ospf.router_id is defined %} + ospf router-id {{ ospf.router_id }} +{% endif %} +{% if ospf.reference_bandwidth is defined %} + auto-cost reference-bandwidth {{ ospf.reference_bandwidth }} +{% endif %} +{% for l in netlab_interfaces if 'ospf' in l and l.ipv4 is string and 'vrf' not in l %} + network {{ l.ipv4 }} area {{ l.ospf.area }} +{% endfor %} +{% for l in netlab_interfaces if l.ospf.passive|default(False) and 'vrf' not in l %} + passive-interface {{ l.ifname }} enable +{% endfor %} +! +{% for l in netlab_interfaces if 'ospf' in l and l.ipv4 is string and 'vrf' not in l %} +interface {{ l.ifname }} +{% if l.ospf.network_type is defined %} + ip ospf network {{ l.ospf.network_type }} +{% endif %} +{% if l.ospf.cost is defined %} + ip ospf cost {{ l.ospf.cost }} +{% endif %} +{% if l.ospf.timers.hello is defined %} + ip ospf hello-interval {{ l.ospf.timers.hello }} +{% endif %} +{% if l.ospf.timers.dead is defined %} + ip ospf dead-interval {{ l.ospf.timers.dead }} +{% endif %} +{% if l.ospf.priority is defined %} + ip ospf priority {{ l.ospf.priority }} +{% endif %} +{% if l.ospf.password is defined %} + ip ospf authentication + ip ospf authentication-key {{ l.ospf.password }} +{% endif %} +! +{% endfor %} +{% endif %} +{# + OSPFv3: the process tag is the OSPF process ID; interfaces join an area with + "ipv6 router ospf area tag " +#} +{% if ospf.af.ipv6 is defined %} +router ipv6 ospf {{ ospf_pid }} +{% if ospf.router_id is defined %} + router-id {{ ospf.router_id }} +{% endif %} +{% for l in netlab_interfaces if l.ospf.passive|default(False) and 'ipv6' in l and 'vrf' not in l %} + passive-interface {{ l.ifname }} enable +{% endfor %} +! +{% for l in netlab_interfaces if 'ospf' in l and 'ipv6' in l and 'vrf' not in l %} +interface {{ l.ifname }} + ipv6 router ospf area {{ l.ospf.area }} tag {{ ospf_pid }} +{% if l.ospf.network_type is defined %} + ipv6 ospf network {{ l.ospf.network_type }} +{% endif %} +{% if l.ospf.cost is defined %} + ipv6 ospf cost {{ l.ospf.cost }} +{% endif %} +{% if l.ospf.timers.hello is defined %} + ipv6 ospf hello-interval {{ l.ospf.timers.hello }} +{% endif %} +{% if l.ospf.timers.dead is defined %} + ipv6 ospf dead-interval {{ l.ospf.timers.dead }} +{% endif %} +{% if l.ospf.priority is defined %} + ipv6 ospf priority {{ l.ospf.priority }} +{% endif %} +! +{% endfor %} +{% endif %} diff --git a/netsim/devices/ocnos.yml b/netsim/devices/ocnos.yml new file mode 100644 index 0000000000..548ea8ce12 --- /dev/null +++ b/netsim/devices/ocnos.yml @@ -0,0 +1,56 @@ +--- +# IP Infusion OcNOS (OcNOS-SP virtual machine) -- containerlab ipinfusion_ocnos kind, built with vrnetlab. +# +# Tested with OcNOS-SP-PLUS-x86-7.0.0-262-GA (the free OcNOS VM) packaged by vrnetlab/ipinfusion/ocnos. +# Data ports are eth1..ethN (container ethN maps 1:1), eth0 is the management port in the "management" VRF. +# The loopback interface "lo" already carries 127.0.0.1, so netlab loopback addresses are added as secondary. +# Configuration is pushed over SSH with netmiko (ipinfusion_ocnos driver, which commits the transaction). +# The ipinfusion.ocnos Ansible collection (ocnos_config) is the alternative deployment method. +description: IP Infusion OcNOS +interface_name: eth{ifindex} +mgmt_if: eth0 +loopback_interface_name: '{"loopback" + str(ifindex) if ifindex else "lo"}' +role: router +support: + level: minimal +group_vars: + netlab_device_type: ocnos + ansible_network_os: ipinfusion.ocnos.ocnos + ansible_connection: network_cli + ansible_user: admin + ansible_ssh_pass: admin@123 + netlab_check_retries: 30 + netlab_check_delay: 10 + netlab_config_mode: netmiko +features: + initial: + ipv4: + unnumbered: false + ipv6: + lla: false + config_mode: [ netmiko ] + netmiko: + device_type: ipinfusion_ocnos + error_regexp: # OcNOS errors start with '%'; '% Warning' lines are informational + - "^%(?! Warning).*" + step_config: true + bgp: + activate_af: true + advertise: true + isis: + circuit_type: true + ospf: + password: true + priority: true + timers: true +clab: + image: vrnetlab/ipinfusion_ocnos:7.0.0-262 + build: https://containerlab.dev/manual/kinds/ipinfusion-ocnos/ + features: + initial: + healthcheck: true + node: + kind: ipinfusion_ocnos + interface: + name: eth{ifindex} +graphite.icon: router diff --git a/tests/config/ocnos-netmiko.yml b/tests/config/ocnos-netmiko.yml new file mode 100644 index 0000000000..47680a2e4e --- /dev/null +++ b/tests/config/ocnos-netmiko.yml @@ -0,0 +1,16 @@ +plugin: [ files ] + +nodes: + r1: + device: ocnos + provider: clab + netlab_config_mode: netmiko + +files: + invalid.j2: | + nocommand noarg + commit.j2: | + interface lo.1 + ip address 192.0.2.1/32 + +links: [ r1, r1 ]