diff --git a/.github/workflows/pull-request-management.yml b/.github/workflows/pull-request-management.yml index b355b7a5429..4632d2a57fc 100644 --- a/.github/workflows/pull-request-management.yml +++ b/.github/workflows/pull-request-management.yml @@ -248,6 +248,8 @@ jobs: avd_scenario: - "ansible_only" - "digital_twin" + - "digital_twin_containerlab" + - "digital_twin_containerlab_negative_tests" - "eos_designs_deprecated_vars" - "eos_designs-l2ls" - "eos_designs-mpls-isis-sr-ldp" diff --git a/.github/yamllintrc b/.github/yamllintrc index ce8dd4c9224..cf96e2d16bf 100644 --- a/.github/yamllintrc +++ b/.github/yamllintrc @@ -58,6 +58,7 @@ rules: - /ansible_collections/arista/avd/**/intended/structured_configs/ # Auto generated - /ansible_collections/arista/avd/tests/inventory/**/expected_output/ # Need to compare with auto generated structure config - /ansible_collections/arista/avd/extensions/molecule/**/digital_twin/documentation/ # Auto generated topology files + - /ansible_collections/arista/avd/extensions/molecule/**/digital_twin_containerlab/digital_twin/ # Auto generated cLab topology file - /python-avd/tests/**/artifacts/ # Need to compare with auto generated structure config - /python-avd/pyavd/**/schema/eos_*.schema.yml # Auto generated octal-values: diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN-topology.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN-topology.yml index f762e3dc190..46cb9191a2c 100644 --- a/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN-topology.yml +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN-topology.yml @@ -1,3 +1,4 @@ +--- nodes: - digital-twin-adjust-oob-mgmt-1: node_type: veos diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN_SINGLE_SWITCH_FABRIC-topology.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN_SINGLE_SWITCH_FABRIC-topology.yml index 976ce9d2b54..382902a970b 100644 --- a/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN_SINGLE_SWITCH_FABRIC-topology.yml +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin/digital_twin/documentation/fabric/DIGITAL_TWIN_SINGLE_SWITCH_FABRIC-topology.yml @@ -1,3 +1,4 @@ +--- nodes: - digital-twin-single-switch-fabric-1: node_type: veos diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/converge.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/converge.yml new file mode 100644 index 00000000000..5b31d53cdea --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/converge.yml @@ -0,0 +1,20 @@ +--- +- name: Converge + hosts: DIGITAL_TWIN_CONTAINERLAB + gather_facts: false + connection: local + vars: + output_dir_name: "digital_twin/intended" + documentation_dir_name: "digital_twin/documentation" + avd_digital_twin_mode: true + avd_ignore_requirements: true + tasks: + - name: Generate intended variables in Containerlab Digital Twin mode + delegate_to: 127.0.0.1 + ansible.builtin.import_role: + name: arista.avd.eos_designs + + - name: Generate intended EOS configs in Containerlab Digital Twin mode + delegate_to: 127.0.0.1 + ansible.builtin.import_role: + name: arista.avd.eos_cli_config_gen diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/create.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/create.yml new file mode 100644 index 00000000000..08483ed5105 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/create.yml @@ -0,0 +1,13 @@ +--- +- name: Configure local folders + hosts: DIGITAL_TWIN_CONTAINERLAB + gather_facts: false + connection: local + vars: + root_dir: '{{ playbook_dir }}' + tasks: + - name: Create local output folders + delegate_to: 127.0.0.1 + ansible.builtin.import_role: + name: arista.avd.build_output_folders + run_once: true diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/destroy.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/destroy.yml new file mode 100644 index 00000000000..de65d33f4d2 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/destroy.yml @@ -0,0 +1,18 @@ +--- +- name: Remove output folders + hosts: DIGITAL_TWIN_CONTAINERLAB + gather_facts: false + connection: local + tasks: + - name: Delete local folders + delegate_to: 127.0.0.1 + run_once: true + ansible.builtin.file: + path: "{{ root_dir }}/{{ item }}" + state: absent + with_items: + - documentation + - intended + - config_backup + - reports + - digital_twin diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/DIGITAL_TWIN_CONTAINERLAB-topology.clab.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/DIGITAL_TWIN_CONTAINERLAB-topology.clab.yml new file mode 100644 index 00000000000..d2014a2e3c2 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/DIGITAL_TWIN_CONTAINERLAB-topology.clab.yml @@ -0,0 +1,41 @@ +--- +name: DIGITAL_TWIN_CONTAINERLAB +prefix: '' +mgmt: + network: custom_mgmt + ipv4-subnet: 172.16.1.0/24 +topology: + defaults: + kind: arista_ceos + kinds: + arista_ceos: + enforce-startup-config: true + image: arista/ceos:latest + binds: + - interface_mapping.json:/mnt/flash/EosIntfMapping.json:ro + nodes: + dc1-leaf1a: + mgmt-ipv4: 172.16.1.101 + startup-config: intended/configs/dc1-leaf1a.cfg + dc1-leaf2a: + mgmt-ipv4: 172.16.1.102 + startup-config: intended/configs/dc1-leaf2a.cfg + dc1-spine1: + mgmt-ipv4: 172.16.1.11 + startup-config: intended/configs/dc1-spine1.cfg + dc1-spine2: + mgmt-ipv4: 172.16.1.12 + startup-config: intended/configs/dc1-spine2.cfg + links: + - endpoints: + - dc1-leaf1a:eth1 + - dc1-spine1:eth1 + - endpoints: + - dc1-leaf1a:eth1_2 + - dc1-spine2:eth1_2 + - endpoints: + - dc1-leaf2a:eth1_1 + - dc1-spine1:eth1_3 + - endpoints: + - dc1-leaf2a:eth2 + - dc1-spine2:eth2 diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-leaf1a.md b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-leaf1a.md new file mode 100644 index 00000000000..7a0ac1b764f --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-leaf1a.md @@ -0,0 +1,304 @@ +# dc1-leaf1a + +## Table of Contents + +- [Management](#management) + - [Management Interfaces](#management-interfaces) + - [Management API HTTP](#management-api-http) +- [Authentication](#authentication) + - [Enable Password](#enable-password) +- [Internal VLAN Allocation Policy](#internal-vlan-allocation-policy) + - [Internal VLAN Allocation Policy Summary](#internal-vlan-allocation-policy-summary) + - [Internal VLAN Allocation Policy Device Configuration](#internal-vlan-allocation-policy-device-configuration) +- [Interfaces](#interfaces) + - [Ethernet Interfaces](#ethernet-interfaces) + - [Loopback Interfaces](#loopback-interfaces) +- [Routing](#routing) + - [Service Routing Protocols Model](#service-routing-protocols-model) + - [IP Routing](#ip-routing) + - [IPv6 Routing](#ipv6-routing) + - [Static Routes](#static-routes) + - [Router BGP](#router-bgp) +- [Multicast](#multicast) + - [IP IGMP Snooping](#ip-igmp-snooping) +- [VRF Instances](#vrf-instances) + - [VRF Instances Summary](#vrf-instances-summary) + - [VRF Instances Device Configuration](#vrf-instances-device-configuration) + +## Management + +### Management Interfaces + +#### Management Interfaces Summary + +##### IPv4 + +| Management Interface | Description | Type | VRF | IP Address | Gateway | +| -------------------- | ----------- | ---- | --- | ---------- | ------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | 172.16.1.101/24 | 172.16.1.1 | + +##### IPv6 + +| Management Interface | Description | Type | VRF | IPv6 Address | IPv6 Gateway | ND RA Disabled | ND RA RX Accept | ND Managed Config Flag | ND Other Config Flag | ND Cache | ND RA DNS Servers | +| -------------------- | ----------- | ---- | --- | ------------ | ------------ | -------------- | --------------- | ---------------------- | -------------------- | -------- | ----------------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | - | - | - | - | - | - | - | - | + +#### Management Interfaces Device Configuration + +```eos +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.101/24 +``` + +### Management API HTTP + +#### Management API HTTP Summary + +| HTTP | HTTPS | UNIX-Socket | Default Services | Session Timeout | +| ---- | ----- | ----------- | ---------------- | --------------- | +| False | True | - | - | 1440 minutes | + +#### Management API VRF Access + +| VRF Name | IPv4 ACL | IPv6 ACL | +| -------- | -------- | -------- | +| MGMT | - | - | + +#### Management API HTTP Device Configuration + +```eos +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +``` + +## Authentication + +### Enable Password + +Enable password has been disabled + +## Internal VLAN Allocation Policy + +### Internal VLAN Allocation Policy Summary + +| Policy Allocation | Range Beginning | Range Ending | +| ----------------- | --------------- | ------------ | +| ascending | 1006 | 1199 | + +### Internal VLAN Allocation Policy Device Configuration + +```eos +! +vlan internal order ascending range 1006 1199 +``` + +## Interfaces + +### Ethernet Interfaces + +#### Ethernet Interfaces Summary + +##### L2 + +| Interface | Description | Mode | VLANs | Native VLAN | Trunk Group | Channel-Group | +| --------- | ----------- | ---- | ----- | ----------- | ----------- | ------------- | + +*Inherited from Port-Channel Interface + +##### IPv4 + +| Interface | Description | Channel Group | IP Address | VRF | MTU | Shutdown | ACL In | ACL Out | +| --------- | ----------- | ------------- | ---------- | --- | --- | -------- | ------ | ------- | +| Ethernet1 | P2P_dc1-spine1_Ethernet1 | - | 10.255.255.1/31 | default | 9214 | False | - | - | +| Ethernet1/2 | P2P_dc1-spine2_Ethernet1/2 | - | 10.255.255.3/31 | default | 9214 | False | - | - | + +#### Ethernet Interfaces Device Configuration + +```eos +! +interface Ethernet1 + description P2P_dc1-spine1_Ethernet1 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.1/31 +! +interface Ethernet1/2 + description P2P_dc1-spine2_Ethernet1/2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.3/31 +``` + +### Loopback Interfaces + +#### Loopback Interfaces Summary + +##### IPv4 + +| Interface | Description | VRF | IP Address | +| --------- | ----------- | --- | ---------- | +| Loopback0 | ROUTER_ID | default | 10.255.1.1/32 | + +##### IPv6 + +| Interface | Description | VRF | IPv6 Addresses | +| --------- | ----------- | --- | -------------- | +| Loopback0 | ROUTER_ID | default | - | + +#### Loopback Interfaces Device Configuration + +```eos +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.1.1/32 +``` + +## Routing + +### Service Routing Protocols Model + +Multi agent routing protocol model enabled + +```eos +! +service routing protocols model multi-agent +``` + +### IP Routing + +#### IP Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | True | +| MGMT | False | + +#### IP Routing Device Configuration + +```eos +! +ip routing +no ip routing vrf MGMT +``` + +### IPv6 Routing + +#### IPv6 Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | False | +| MGMT | false | + +### Static Routes + +#### Static Routes Summary + +| VRF | Destination Prefix | Next Hop IP | Exit interface | Administrative Distance | Tag | Route Name | Metric | +| --- | ------------------ | ----------- | -------------- | ----------------------- | --- | ---------- | ------ | +| MGMT | 0.0.0.0/0 | 172.16.1.1 | - | 1 | - | - | - | + +#### Static Routes Device Configuration + +```eos +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +``` + +### Router BGP + +ASN Notation: asplain + +#### Router BGP Summary + +| BGP AS | Router ID | +| ------ | --------- | +| 65101 | 10.255.1.1 | + +| BGP Tuning | +| ---------- | +| no bgp default ipv4-unicast | +| maximum-paths 4 | + +#### Router BGP Peer Groups + +##### IPv4-UNDERLAY-PEERS + +| Settings | Value | +| -------- | ----- | +| Address Family | ipv4 | +| Send community | all | +| Maximum routes | 256000 | + +#### BGP Neighbors + +| Neighbor | Remote AS | VRF | Shutdown | Send-community | Maximum-routes | Allowas-in | BFD | RIB Pre-Policy Retain | Route-Reflector Client | Passive | TTL Max Hops | +| -------- | --------- | --- | -------- | -------------- | -------------- | ---------- | --- | --------------------- | ---------------------- | ------- | ------------ | +| 10.255.255.0 | 65100 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | +| 10.255.255.2 | 65100 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | + +#### Router BGP Device Configuration + +```eos +! +router bgp 65101 + router-id 10.255.1.1 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.0 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.0 remote-as 65100 + neighbor 10.255.255.0 description dc1-spine1_Ethernet1 + neighbor 10.255.255.2 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.2 remote-as 65100 + neighbor 10.255.255.2 description dc1-spine2_Ethernet1/2 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +``` + +## Multicast + +### IP IGMP Snooping + +#### IP IGMP Snooping Summary + +| IGMP Snooping | Fast Leave | Interface Restart Query | Proxy | Restart Query Interval | Robustness Variable | +| ------------- | ---------- | ----------------------- | ----- | ---------------------- | ------------------- | +| Enabled | - | - | - | - | - | + +#### IP IGMP Snooping Device Configuration + +```eos +``` + +## VRF Instances + +### VRF Instances Summary + +| VRF Name | IP Routing | +| -------- | ---------- | +| MGMT | disabled | + +### VRF Instances Device Configuration + +```eos +! +vrf instance MGMT +``` diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-leaf2a.md b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-leaf2a.md new file mode 100644 index 00000000000..5be95bfb64c --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-leaf2a.md @@ -0,0 +1,304 @@ +# dc1-leaf2a + +## Table of Contents + +- [Management](#management) + - [Management Interfaces](#management-interfaces) + - [Management API HTTP](#management-api-http) +- [Authentication](#authentication) + - [Enable Password](#enable-password) +- [Internal VLAN Allocation Policy](#internal-vlan-allocation-policy) + - [Internal VLAN Allocation Policy Summary](#internal-vlan-allocation-policy-summary) + - [Internal VLAN Allocation Policy Device Configuration](#internal-vlan-allocation-policy-device-configuration) +- [Interfaces](#interfaces) + - [Ethernet Interfaces](#ethernet-interfaces) + - [Loopback Interfaces](#loopback-interfaces) +- [Routing](#routing) + - [Service Routing Protocols Model](#service-routing-protocols-model) + - [IP Routing](#ip-routing) + - [IPv6 Routing](#ipv6-routing) + - [Static Routes](#static-routes) + - [Router BGP](#router-bgp) +- [Multicast](#multicast) + - [IP IGMP Snooping](#ip-igmp-snooping) +- [VRF Instances](#vrf-instances) + - [VRF Instances Summary](#vrf-instances-summary) + - [VRF Instances Device Configuration](#vrf-instances-device-configuration) + +## Management + +### Management Interfaces + +#### Management Interfaces Summary + +##### IPv4 + +| Management Interface | Description | Type | VRF | IP Address | Gateway | +| -------------------- | ----------- | ---- | --- | ---------- | ------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | 172.16.1.102/24 | 172.16.1.1 | + +##### IPv6 + +| Management Interface | Description | Type | VRF | IPv6 Address | IPv6 Gateway | ND RA Disabled | ND RA RX Accept | ND Managed Config Flag | ND Other Config Flag | ND Cache | ND RA DNS Servers | +| -------------------- | ----------- | ---- | --- | ------------ | ------------ | -------------- | --------------- | ---------------------- | -------------------- | -------- | ----------------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | - | - | - | - | - | - | - | - | + +#### Management Interfaces Device Configuration + +```eos +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.102/24 +``` + +### Management API HTTP + +#### Management API HTTP Summary + +| HTTP | HTTPS | UNIX-Socket | Default Services | Session Timeout | +| ---- | ----- | ----------- | ---------------- | --------------- | +| False | True | - | - | 1440 minutes | + +#### Management API VRF Access + +| VRF Name | IPv4 ACL | IPv6 ACL | +| -------- | -------- | -------- | +| MGMT | - | - | + +#### Management API HTTP Device Configuration + +```eos +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +``` + +## Authentication + +### Enable Password + +Enable password has been disabled + +## Internal VLAN Allocation Policy + +### Internal VLAN Allocation Policy Summary + +| Policy Allocation | Range Beginning | Range Ending | +| ----------------- | --------------- | ------------ | +| ascending | 1006 | 1199 | + +### Internal VLAN Allocation Policy Device Configuration + +```eos +! +vlan internal order ascending range 1006 1199 +``` + +## Interfaces + +### Ethernet Interfaces + +#### Ethernet Interfaces Summary + +##### L2 + +| Interface | Description | Mode | VLANs | Native VLAN | Trunk Group | Channel-Group | +| --------- | ----------- | ---- | ----- | ----------- | ----------- | ------------- | + +*Inherited from Port-Channel Interface + +##### IPv4 + +| Interface | Description | Channel Group | IP Address | VRF | MTU | Shutdown | ACL In | ACL Out | +| --------- | ----------- | ------------- | ---------- | --- | --- | -------- | ------ | ------- | +| Ethernet1/1 | P2P_dc1-spine1_Ethernet1/3 | - | 10.255.255.5/31 | default | 9214 | False | - | - | +| Ethernet2 | P2P_dc1-spine2_Ethernet2 | - | 10.255.255.7/31 | default | 9214 | False | - | - | + +#### Ethernet Interfaces Device Configuration + +```eos +! +interface Ethernet1/1 + description P2P_dc1-spine1_Ethernet1/3 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.5/31 +! +interface Ethernet2 + description P2P_dc1-spine2_Ethernet2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.7/31 +``` + +### Loopback Interfaces + +#### Loopback Interfaces Summary + +##### IPv4 + +| Interface | Description | VRF | IP Address | +| --------- | ----------- | --- | ---------- | +| Loopback0 | ROUTER_ID | default | 10.255.1.2/32 | + +##### IPv6 + +| Interface | Description | VRF | IPv6 Addresses | +| --------- | ----------- | --- | -------------- | +| Loopback0 | ROUTER_ID | default | - | + +#### Loopback Interfaces Device Configuration + +```eos +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.1.2/32 +``` + +## Routing + +### Service Routing Protocols Model + +Multi agent routing protocol model enabled + +```eos +! +service routing protocols model multi-agent +``` + +### IP Routing + +#### IP Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | True | +| MGMT | False | + +#### IP Routing Device Configuration + +```eos +! +ip routing +no ip routing vrf MGMT +``` + +### IPv6 Routing + +#### IPv6 Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | False | +| MGMT | false | + +### Static Routes + +#### Static Routes Summary + +| VRF | Destination Prefix | Next Hop IP | Exit interface | Administrative Distance | Tag | Route Name | Metric | +| --- | ------------------ | ----------- | -------------- | ----------------------- | --- | ---------- | ------ | +| MGMT | 0.0.0.0/0 | 172.16.1.1 | - | 1 | - | - | - | + +#### Static Routes Device Configuration + +```eos +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +``` + +### Router BGP + +ASN Notation: asplain + +#### Router BGP Summary + +| BGP AS | Router ID | +| ------ | --------- | +| 65102 | 10.255.1.2 | + +| BGP Tuning | +| ---------- | +| no bgp default ipv4-unicast | +| maximum-paths 4 | + +#### Router BGP Peer Groups + +##### IPv4-UNDERLAY-PEERS + +| Settings | Value | +| -------- | ----- | +| Address Family | ipv4 | +| Send community | all | +| Maximum routes | 256000 | + +#### BGP Neighbors + +| Neighbor | Remote AS | VRF | Shutdown | Send-community | Maximum-routes | Allowas-in | BFD | RIB Pre-Policy Retain | Route-Reflector Client | Passive | TTL Max Hops | +| -------- | --------- | --- | -------- | -------------- | -------------- | ---------- | --- | --------------------- | ---------------------- | ------- | ------------ | +| 10.255.255.4 | 65100 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | +| 10.255.255.6 | 65100 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | + +#### Router BGP Device Configuration + +```eos +! +router bgp 65102 + router-id 10.255.1.2 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.4 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.4 remote-as 65100 + neighbor 10.255.255.4 description dc1-spine1_Ethernet1/3 + neighbor 10.255.255.6 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.6 remote-as 65100 + neighbor 10.255.255.6 description dc1-spine2_Ethernet2 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +``` + +## Multicast + +### IP IGMP Snooping + +#### IP IGMP Snooping Summary + +| IGMP Snooping | Fast Leave | Interface Restart Query | Proxy | Restart Query Interval | Robustness Variable | +| ------------- | ---------- | ----------------------- | ----- | ---------------------- | ------------------- | +| Enabled | - | - | - | - | - | + +#### IP IGMP Snooping Device Configuration + +```eos +``` + +## VRF Instances + +### VRF Instances Summary + +| VRF Name | IP Routing | +| -------- | ---------- | +| MGMT | disabled | + +### VRF Instances Device Configuration + +```eos +! +vrf instance MGMT +``` diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-spine1.md b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-spine1.md new file mode 100644 index 00000000000..d4f70cb43e4 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-spine1.md @@ -0,0 +1,303 @@ +# dc1-spine1 + +## Table of Contents + +- [Management](#management) + - [Management Interfaces](#management-interfaces) + - [Management API HTTP](#management-api-http) +- [Authentication](#authentication) + - [Enable Password](#enable-password) +- [Spanning Tree](#spanning-tree) + - [Spanning Tree Summary](#spanning-tree-summary) + - [Spanning Tree Device Configuration](#spanning-tree-device-configuration) +- [Internal VLAN Allocation Policy](#internal-vlan-allocation-policy) + - [Internal VLAN Allocation Policy Summary](#internal-vlan-allocation-policy-summary) + - [Internal VLAN Allocation Policy Device Configuration](#internal-vlan-allocation-policy-device-configuration) +- [Interfaces](#interfaces) + - [Ethernet Interfaces](#ethernet-interfaces) + - [Loopback Interfaces](#loopback-interfaces) +- [Routing](#routing) + - [Service Routing Protocols Model](#service-routing-protocols-model) + - [IP Routing](#ip-routing) + - [IPv6 Routing](#ipv6-routing) + - [Static Routes](#static-routes) + - [Router BGP](#router-bgp) +- [VRF Instances](#vrf-instances) + - [VRF Instances Summary](#vrf-instances-summary) + - [VRF Instances Device Configuration](#vrf-instances-device-configuration) + +## Management + +### Management Interfaces + +#### Management Interfaces Summary + +##### IPv4 + +| Management Interface | Description | Type | VRF | IP Address | Gateway | +| -------------------- | ----------- | ---- | --- | ---------- | ------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | 172.16.1.11/24 | 172.16.1.1 | + +##### IPv6 + +| Management Interface | Description | Type | VRF | IPv6 Address | IPv6 Gateway | ND RA Disabled | ND RA RX Accept | ND Managed Config Flag | ND Other Config Flag | ND Cache | ND RA DNS Servers | +| -------------------- | ----------- | ---- | --- | ------------ | ------------ | -------------- | --------------- | ---------------------- | -------------------- | -------- | ----------------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | - | - | - | - | - | - | - | - | + +#### Management Interfaces Device Configuration + +```eos +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.11/24 +``` + +### Management API HTTP + +#### Management API HTTP Summary + +| HTTP | HTTPS | UNIX-Socket | Default Services | Session Timeout | +| ---- | ----- | ----------- | ---------------- | --------------- | +| False | True | - | - | 1440 minutes | + +#### Management API VRF Access + +| VRF Name | IPv4 ACL | IPv6 ACL | +| -------- | -------- | -------- | +| MGMT | - | - | + +#### Management API HTTP Device Configuration + +```eos +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +``` + +## Authentication + +### Enable Password + +Enable password has been disabled + +## Spanning Tree + +### Spanning Tree Summary + +STP mode: **none** + +### Spanning Tree Device Configuration + +```eos +! +spanning-tree mode none +``` + +## Internal VLAN Allocation Policy + +### Internal VLAN Allocation Policy Summary + +| Policy Allocation | Range Beginning | Range Ending | +| ----------------- | --------------- | ------------ | +| ascending | 1006 | 1199 | + +### Internal VLAN Allocation Policy Device Configuration + +```eos +! +vlan internal order ascending range 1006 1199 +``` + +## Interfaces + +### Ethernet Interfaces + +#### Ethernet Interfaces Summary + +##### L2 + +| Interface | Description | Mode | VLANs | Native VLAN | Trunk Group | Channel-Group | +| --------- | ----------- | ---- | ----- | ----------- | ----------- | ------------- | + +*Inherited from Port-Channel Interface + +##### IPv4 + +| Interface | Description | Channel Group | IP Address | VRF | MTU | Shutdown | ACL In | ACL Out | +| --------- | ----------- | ------------- | ---------- | --- | --- | -------- | ------ | ------- | +| Ethernet1 | P2P_dc1-leaf1a_Ethernet1 | - | 10.255.255.0/31 | default | 9214 | False | - | - | +| Ethernet1/3 | P2P_dc1-leaf2a_Ethernet1/1 | - | 10.255.255.4/31 | default | 9214 | False | - | - | + +#### Ethernet Interfaces Device Configuration + +```eos +! +interface Ethernet1 + description P2P_dc1-leaf1a_Ethernet1 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.0/31 +! +interface Ethernet1/3 + description P2P_dc1-leaf2a_Ethernet1/1 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.4/31 +``` + +### Loopback Interfaces + +#### Loopback Interfaces Summary + +##### IPv4 + +| Interface | Description | VRF | IP Address | +| --------- | ----------- | --- | ---------- | +| Loopback0 | ROUTER_ID | default | 10.255.0.1/32 | + +##### IPv6 + +| Interface | Description | VRF | IPv6 Addresses | +| --------- | ----------- | --- | -------------- | +| Loopback0 | ROUTER_ID | default | - | + +#### Loopback Interfaces Device Configuration + +```eos +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.0.1/32 +``` + +## Routing + +### Service Routing Protocols Model + +Multi agent routing protocol model enabled + +```eos +! +service routing protocols model multi-agent +``` + +### IP Routing + +#### IP Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | True | +| MGMT | False | + +#### IP Routing Device Configuration + +```eos +! +ip routing +no ip routing vrf MGMT +``` + +### IPv6 Routing + +#### IPv6 Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | False | +| MGMT | false | + +### Static Routes + +#### Static Routes Summary + +| VRF | Destination Prefix | Next Hop IP | Exit interface | Administrative Distance | Tag | Route Name | Metric | +| --- | ------------------ | ----------- | -------------- | ----------------------- | --- | ---------- | ------ | +| MGMT | 0.0.0.0/0 | 172.16.1.1 | - | 1 | - | - | - | + +#### Static Routes Device Configuration + +```eos +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +``` + +### Router BGP + +ASN Notation: asplain + +#### Router BGP Summary + +| BGP AS | Router ID | +| ------ | --------- | +| 65100 | 10.255.0.1 | + +| BGP Tuning | +| ---------- | +| no bgp default ipv4-unicast | +| maximum-paths 4 | + +#### Router BGP Peer Groups + +##### IPv4-UNDERLAY-PEERS + +| Settings | Value | +| -------- | ----- | +| Address Family | ipv4 | +| Send community | all | +| Maximum routes | 256000 | + +#### BGP Neighbors + +| Neighbor | Remote AS | VRF | Shutdown | Send-community | Maximum-routes | Allowas-in | BFD | RIB Pre-Policy Retain | Route-Reflector Client | Passive | TTL Max Hops | +| -------- | --------- | --- | -------- | -------------- | -------------- | ---------- | --- | --------------------- | ---------------------- | ------- | ------------ | +| 10.255.255.1 | 65101 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | +| 10.255.255.5 | 65102 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | + +#### Router BGP Device Configuration + +```eos +! +router bgp 65100 + router-id 10.255.0.1 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.1 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.1 remote-as 65101 + neighbor 10.255.255.1 description dc1-leaf1a_Ethernet1 + neighbor 10.255.255.5 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.5 remote-as 65102 + neighbor 10.255.255.5 description dc1-leaf2a_Ethernet1/1 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +``` + +## VRF Instances + +### VRF Instances Summary + +| VRF Name | IP Routing | +| -------- | ---------- | +| MGMT | disabled | + +### VRF Instances Device Configuration + +```eos +! +vrf instance MGMT +``` diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-spine2.md b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-spine2.md new file mode 100644 index 00000000000..31b2c5f32ce --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/devices/dc1-spine2.md @@ -0,0 +1,303 @@ +# dc1-spine2 + +## Table of Contents + +- [Management](#management) + - [Management Interfaces](#management-interfaces) + - [Management API HTTP](#management-api-http) +- [Authentication](#authentication) + - [Enable Password](#enable-password) +- [Spanning Tree](#spanning-tree) + - [Spanning Tree Summary](#spanning-tree-summary) + - [Spanning Tree Device Configuration](#spanning-tree-device-configuration) +- [Internal VLAN Allocation Policy](#internal-vlan-allocation-policy) + - [Internal VLAN Allocation Policy Summary](#internal-vlan-allocation-policy-summary) + - [Internal VLAN Allocation Policy Device Configuration](#internal-vlan-allocation-policy-device-configuration) +- [Interfaces](#interfaces) + - [Ethernet Interfaces](#ethernet-interfaces) + - [Loopback Interfaces](#loopback-interfaces) +- [Routing](#routing) + - [Service Routing Protocols Model](#service-routing-protocols-model) + - [IP Routing](#ip-routing) + - [IPv6 Routing](#ipv6-routing) + - [Static Routes](#static-routes) + - [Router BGP](#router-bgp) +- [VRF Instances](#vrf-instances) + - [VRF Instances Summary](#vrf-instances-summary) + - [VRF Instances Device Configuration](#vrf-instances-device-configuration) + +## Management + +### Management Interfaces + +#### Management Interfaces Summary + +##### IPv4 + +| Management Interface | Description | Type | VRF | IP Address | Gateway | +| -------------------- | ----------- | ---- | --- | ---------- | ------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | 172.16.1.12/24 | 172.16.1.1 | + +##### IPv6 + +| Management Interface | Description | Type | VRF | IPv6 Address | IPv6 Gateway | ND RA Disabled | ND RA RX Accept | ND Managed Config Flag | ND Other Config Flag | ND Cache | ND RA DNS Servers | +| -------------------- | ----------- | ---- | --- | ------------ | ------------ | -------------- | --------------- | ---------------------- | -------------------- | -------- | ----------------- | +| Management1 | OOB_MANAGEMENT | oob | MGMT | - | - | - | - | - | - | - | - | + +#### Management Interfaces Device Configuration + +```eos +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.12/24 +``` + +### Management API HTTP + +#### Management API HTTP Summary + +| HTTP | HTTPS | UNIX-Socket | Default Services | Session Timeout | +| ---- | ----- | ----------- | ---------------- | --------------- | +| False | True | - | - | 1440 minutes | + +#### Management API VRF Access + +| VRF Name | IPv4 ACL | IPv6 ACL | +| -------- | -------- | -------- | +| MGMT | - | - | + +#### Management API HTTP Device Configuration + +```eos +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +``` + +## Authentication + +### Enable Password + +Enable password has been disabled + +## Spanning Tree + +### Spanning Tree Summary + +STP mode: **none** + +### Spanning Tree Device Configuration + +```eos +! +spanning-tree mode none +``` + +## Internal VLAN Allocation Policy + +### Internal VLAN Allocation Policy Summary + +| Policy Allocation | Range Beginning | Range Ending | +| ----------------- | --------------- | ------------ | +| ascending | 1006 | 1199 | + +### Internal VLAN Allocation Policy Device Configuration + +```eos +! +vlan internal order ascending range 1006 1199 +``` + +## Interfaces + +### Ethernet Interfaces + +#### Ethernet Interfaces Summary + +##### L2 + +| Interface | Description | Mode | VLANs | Native VLAN | Trunk Group | Channel-Group | +| --------- | ----------- | ---- | ----- | ----------- | ----------- | ------------- | + +*Inherited from Port-Channel Interface + +##### IPv4 + +| Interface | Description | Channel Group | IP Address | VRF | MTU | Shutdown | ACL In | ACL Out | +| --------- | ----------- | ------------- | ---------- | --- | --- | -------- | ------ | ------- | +| Ethernet1/2 | P2P_dc1-leaf1a_Ethernet1/2 | - | 10.255.255.2/31 | default | 9214 | False | - | - | +| Ethernet2 | P2P_dc1-leaf2a_Ethernet2 | - | 10.255.255.6/31 | default | 9214 | False | - | - | + +#### Ethernet Interfaces Device Configuration + +```eos +! +interface Ethernet1/2 + description P2P_dc1-leaf1a_Ethernet1/2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.2/31 +! +interface Ethernet2 + description P2P_dc1-leaf2a_Ethernet2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.6/31 +``` + +### Loopback Interfaces + +#### Loopback Interfaces Summary + +##### IPv4 + +| Interface | Description | VRF | IP Address | +| --------- | ----------- | --- | ---------- | +| Loopback0 | ROUTER_ID | default | 10.255.0.2/32 | + +##### IPv6 + +| Interface | Description | VRF | IPv6 Addresses | +| --------- | ----------- | --- | -------------- | +| Loopback0 | ROUTER_ID | default | - | + +#### Loopback Interfaces Device Configuration + +```eos +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.0.2/32 +``` + +## Routing + +### Service Routing Protocols Model + +Multi agent routing protocol model enabled + +```eos +! +service routing protocols model multi-agent +``` + +### IP Routing + +#### IP Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | True | +| MGMT | False | + +#### IP Routing Device Configuration + +```eos +! +ip routing +no ip routing vrf MGMT +``` + +### IPv6 Routing + +#### IPv6 Routing Summary + +| VRF | Routing Enabled | +| --- | --------------- | +| default | False | +| MGMT | false | + +### Static Routes + +#### Static Routes Summary + +| VRF | Destination Prefix | Next Hop IP | Exit interface | Administrative Distance | Tag | Route Name | Metric | +| --- | ------------------ | ----------- | -------------- | ----------------------- | --- | ---------- | ------ | +| MGMT | 0.0.0.0/0 | 172.16.1.1 | - | 1 | - | - | - | + +#### Static Routes Device Configuration + +```eos +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +``` + +### Router BGP + +ASN Notation: asplain + +#### Router BGP Summary + +| BGP AS | Router ID | +| ------ | --------- | +| 65100 | 10.255.0.2 | + +| BGP Tuning | +| ---------- | +| no bgp default ipv4-unicast | +| maximum-paths 4 | + +#### Router BGP Peer Groups + +##### IPv4-UNDERLAY-PEERS + +| Settings | Value | +| -------- | ----- | +| Address Family | ipv4 | +| Send community | all | +| Maximum routes | 256000 | + +#### BGP Neighbors + +| Neighbor | Remote AS | VRF | Shutdown | Send-community | Maximum-routes | Allowas-in | BFD | RIB Pre-Policy Retain | Route-Reflector Client | Passive | TTL Max Hops | +| -------- | --------- | --- | -------- | -------------- | -------------- | ---------- | --- | --------------------- | ---------------------- | ------- | ------------ | +| 10.255.255.3 | 65101 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | +| 10.255.255.7 | 65102 | default | - | Inherited from peer group IPv4-UNDERLAY-PEERS | Inherited from peer group IPv4-UNDERLAY-PEERS | - | - | - | - | - | - | + +#### Router BGP Device Configuration + +```eos +! +router bgp 65100 + router-id 10.255.0.2 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.3 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.3 remote-as 65101 + neighbor 10.255.255.3 description dc1-leaf1a_Ethernet1/2 + neighbor 10.255.255.7 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.7 remote-as 65102 + neighbor 10.255.255.7 description dc1-leaf2a_Ethernet2 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +``` + +## VRF Instances + +### VRF Instances Summary + +| VRF Name | IP Routing | +| -------- | ---------- | +| MGMT | disabled | + +### VRF Instances Device Configuration + +```eos +! +vrf instance MGMT +``` diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-documentation.md b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-documentation.md new file mode 100644 index 00000000000..af670650fb9 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-documentation.md @@ -0,0 +1,83 @@ +# DIGITAL_TWIN_CONTAINERLAB + +## Table of Contents + +- [Fabric Switches and Management IP](#fabric-switches-and-management-ip) + - [Fabric Switches with inband Management IP](#fabric-switches-with-inband-management-ip) +- [Fabric Topology](#fabric-topology) +- [Fabric IP Allocation](#fabric-ip-allocation) + - [Fabric Point-To-Point Links](#fabric-point-to-point-links) + - [Point-To-Point Links Node Allocation](#point-to-point-links-node-allocation) + - [Loopback Interfaces (BGP EVPN Peering)](#loopback-interfaces-bgp-evpn-peering) + - [Loopback0 Interfaces Node Allocation](#loopback0-interfaces-node-allocation) + - [VTEP Loopback VXLAN Tunnel Source Interfaces (VTEPs Only)](#vtep-loopback-vxlan-tunnel-source-interfaces-vteps-only) + - [VTEP Loopback Node allocation](#vtep-loopback-node-allocation) + +## Fabric Switches and Management IP + +| POD | Type | Node | Management IP | Platform | Provisioned in CloudVision | Serial Number | +| --- | ---- | ---- | ------------- | -------- | -------------------------- | ------------- | +| DIGITAL_TWIN_CONTAINERLAB | l3leaf | dc1-leaf1a | 172.16.1.101/24 | cEOSLab | Provisioned | - | +| DIGITAL_TWIN_CONTAINERLAB | l3leaf | dc1-leaf2a | 172.16.1.102/24 | cEOSLab | Provisioned | - | +| DIGITAL_TWIN_CONTAINERLAB | spine | dc1-spine1 | 172.16.1.11/24 | cEOSLab | Provisioned | - | +| DIGITAL_TWIN_CONTAINERLAB | spine | dc1-spine2 | 172.16.1.12/24 | cEOSLab | Provisioned | - | + +> Provision status is based on Ansible inventory declaration and do not represent real status from CloudVision. + +### Fabric Switches with inband Management IP + +| POD | Type | Node | Management IP | Inband Interface | +| --- | ---- | ---- | ------------- | ---------------- | + +## Fabric Topology + +| Type | Node | Node Interface | Peer Type | Peer Node | Peer Interface | +| ---- | ---- | -------------- | --------- | --------- | -------------- | +| l3leaf | dc1-leaf1a | Ethernet1 | spine | dc1-spine1 | Ethernet1 | +| l3leaf | dc1-leaf1a | Ethernet1/2 | spine | dc1-spine2 | Ethernet1/2 | +| l3leaf | dc1-leaf2a | Ethernet1/1 | spine | dc1-spine1 | Ethernet1/3 | +| l3leaf | dc1-leaf2a | Ethernet2 | spine | dc1-spine2 | Ethernet2 | + +## Fabric IP Allocation + +### Fabric Point-To-Point Links + +| Uplink IPv4 Pool | Available Addresses | Assigned addresses | Assigned Address % | +| ---------------- | ------------------- | ------------------ | ------------------ | +| 10.255.255.0/26 | 64 | 8 | 12.5 % | + +### Point-To-Point Links Node Allocation + +| Node | Node Interface | Node IP Address | Peer Node | Peer Interface | Peer IP Address | +| ---- | -------------- | --------------- | --------- | -------------- | --------------- | +| dc1-leaf1a | Ethernet1 | 10.255.255.1/31 | dc1-spine1 | Ethernet1 | 10.255.255.0/31 | +| dc1-leaf1a | Ethernet1/2 | 10.255.255.3/31 | dc1-spine2 | Ethernet1/2 | 10.255.255.2/31 | +| dc1-leaf2a | Ethernet1/1 | 10.255.255.5/31 | dc1-spine1 | Ethernet1/3 | 10.255.255.4/31 | +| dc1-leaf2a | Ethernet2 | 10.255.255.7/31 | dc1-spine2 | Ethernet2 | 10.255.255.6/31 | + +### Loopback Interfaces (BGP EVPN Peering) + +| Loopback Pool | Available Addresses | Assigned addresses | Assigned Address % | +| ------------- | ------------------- | ------------------ | ------------------ | +| 10.255.0.0/27 | 32 | 2 | 6.25 % | +| 10.255.1.0/27 | 32 | 2 | 6.25 % | + +### Loopback0 Interfaces Node Allocation + +| POD | Node | Loopback0 | +| --- | ---- | --------- | +| DIGITAL_TWIN_CONTAINERLAB | dc1-leaf1a | 10.255.1.1/32 | +| DIGITAL_TWIN_CONTAINERLAB | dc1-leaf2a | 10.255.1.2/32 | +| DIGITAL_TWIN_CONTAINERLAB | dc1-spine1 | 10.255.0.1/32 | +| DIGITAL_TWIN_CONTAINERLAB | dc1-spine2 | 10.255.0.2/32 | + +### VTEP Loopback VXLAN Tunnel Source Interfaces (VTEPs Only) + +| VTEP Loopback Pool | Available Addresses | Assigned addresses | Assigned Address % | +| ------------------ | ------------------- | ------------------ | ------------------ | +| 10.255.2.0/27 | 32 | 0 | 0.0 % | + +### VTEP Loopback Node allocation + +| POD | Node | Loopback1 | +| --- | ---- | --------- | diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-p2p-links.csv b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-p2p-links.csv new file mode 100644 index 00000000000..f856c1423b8 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-p2p-links.csv @@ -0,0 +1,5 @@ +Type,Node,Serial Number,Node Interface,Leaf IP Address,Peer Type,Peer Node,Peer Serial Number,Peer Interface,Peer IP Address +l3leaf,dc1-leaf1a,,Ethernet1,10.255.255.1/31,spine,dc1-spine1,,Ethernet1,10.255.255.0/31 +l3leaf,dc1-leaf1a,,Ethernet1/2,10.255.255.3/31,spine,dc1-spine2,,Ethernet1/2,10.255.255.2/31 +l3leaf,dc1-leaf2a,,Ethernet1/1,10.255.255.5/31,spine,dc1-spine1,,Ethernet1/3,10.255.255.4/31 +l3leaf,dc1-leaf2a,,Ethernet2,10.255.255.7/31,spine,dc1-spine2,,Ethernet2,10.255.255.6/31 diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-topology.csv b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-topology.csv new file mode 100644 index 00000000000..31951f5255c --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/documentation/fabric/DIGITAL_TWIN_CONTAINERLAB-topology.csv @@ -0,0 +1,9 @@ +Node Type,Node,Serial Number,Node Interface,Peer Type,Peer Node,Peer Serial Number,Peer Interface,Node Interface Enabled +l3leaf,dc1-leaf1a,,Ethernet1,spine,dc1-spine1,,Ethernet1,True +l3leaf,dc1-leaf1a,,Ethernet1/2,spine,dc1-spine2,,Ethernet1/2,True +l3leaf,dc1-leaf2a,,Ethernet1/1,spine,dc1-spine1,,Ethernet1/3,True +l3leaf,dc1-leaf2a,,Ethernet2,spine,dc1-spine2,,Ethernet2,True +spine,dc1-spine1,,Ethernet1,l3leaf,dc1-leaf1a,,Ethernet1,True +spine,dc1-spine1,,Ethernet1/3,l3leaf,dc1-leaf2a,,Ethernet1/1,True +spine,dc1-spine2,,Ethernet1/2,l3leaf,dc1-leaf1a,,Ethernet1/2,True +spine,dc1-spine2,,Ethernet2,l3leaf,dc1-leaf2a,,Ethernet2,True diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-leaf1a.cfg b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-leaf1a.cfg new file mode 100644 index 00000000000..b46cbe4f397 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-leaf1a.cfg @@ -0,0 +1,70 @@ +! +no enable password +no aaa root +! +vlan internal order ascending range 1006 1199 +! +transceiver qsfp default-mode 4x10G +! +service routing protocols model multi-agent +! +hostname dc1-leaf1a +! +vrf instance MGMT +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +! +interface Ethernet1 + description P2P_dc1-spine1_Ethernet1 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.1/31 +! +interface Ethernet1/2 + description P2P_dc1-spine2_Ethernet1/2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.3/31 +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.1.1/32 +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.101/24 +! +ip routing +no ip routing vrf MGMT +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +! +router bgp 65101 + router-id 10.255.1.1 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.0 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.0 remote-as 65100 + neighbor 10.255.255.0 description dc1-spine1_Ethernet1 + neighbor 10.255.255.2 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.2 remote-as 65100 + neighbor 10.255.255.2 description dc1-spine2_Ethernet1/2 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +! +end diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-leaf2a.cfg b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-leaf2a.cfg new file mode 100644 index 00000000000..a642f6dc0c6 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-leaf2a.cfg @@ -0,0 +1,70 @@ +! +no enable password +no aaa root +! +vlan internal order ascending range 1006 1199 +! +transceiver qsfp default-mode 4x10G +! +service routing protocols model multi-agent +! +hostname dc1-leaf2a +! +vrf instance MGMT +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +! +interface Ethernet1/1 + description P2P_dc1-spine1_Ethernet1/3 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.5/31 +! +interface Ethernet2 + description P2P_dc1-spine2_Ethernet2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.7/31 +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.1.2/32 +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.102/24 +! +ip routing +no ip routing vrf MGMT +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +! +router bgp 65102 + router-id 10.255.1.2 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.4 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.4 remote-as 65100 + neighbor 10.255.255.4 description dc1-spine1_Ethernet1/3 + neighbor 10.255.255.6 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.6 remote-as 65100 + neighbor 10.255.255.6 description dc1-spine2_Ethernet2 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +! +end diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-spine1.cfg b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-spine1.cfg new file mode 100644 index 00000000000..81f60203847 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-spine1.cfg @@ -0,0 +1,72 @@ +! +no enable password +no aaa root +! +vlan internal order ascending range 1006 1199 +! +transceiver qsfp default-mode 4x10G +! +service routing protocols model multi-agent +! +hostname dc1-spine1 +! +spanning-tree mode none +! +vrf instance MGMT +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +! +interface Ethernet1 + description P2P_dc1-leaf1a_Ethernet1 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.0/31 +! +interface Ethernet1/3 + description P2P_dc1-leaf2a_Ethernet1/1 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.4/31 +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.0.1/32 +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.11/24 +! +ip routing +no ip routing vrf MGMT +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +! +router bgp 65100 + router-id 10.255.0.1 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.1 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.1 remote-as 65101 + neighbor 10.255.255.1 description dc1-leaf1a_Ethernet1 + neighbor 10.255.255.5 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.5 remote-as 65102 + neighbor 10.255.255.5 description dc1-leaf2a_Ethernet1/1 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +! +end diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-spine2.cfg b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-spine2.cfg new file mode 100644 index 00000000000..223e0a38bca --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/configs/dc1-spine2.cfg @@ -0,0 +1,72 @@ +! +no enable password +no aaa root +! +vlan internal order ascending range 1006 1199 +! +transceiver qsfp default-mode 4x10G +! +service routing protocols model multi-agent +! +hostname dc1-spine2 +! +spanning-tree mode none +! +vrf instance MGMT +! +management api http-commands + protocol https + no shutdown + ! + vrf MGMT + no shutdown +! +interface Ethernet1/2 + description P2P_dc1-leaf1a_Ethernet1/2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.2/31 +! +interface Ethernet2 + description P2P_dc1-leaf2a_Ethernet2 + no shutdown + mtu 9214 + no switchport + ip address 10.255.255.6/31 +! +interface Loopback0 + description ROUTER_ID + no shutdown + ip address 10.255.0.2/32 +! +interface Management1 + description OOB_MANAGEMENT + no shutdown + vrf MGMT + ip address 172.16.1.12/24 +! +ip routing +no ip routing vrf MGMT +! +ip route vrf MGMT 0.0.0.0/0 172.16.1.1 +! +router bgp 65100 + router-id 10.255.0.2 + no bgp default ipv4-unicast + maximum-paths 4 + neighbor IPv4-UNDERLAY-PEERS peer group + neighbor IPv4-UNDERLAY-PEERS send-community + neighbor IPv4-UNDERLAY-PEERS maximum-routes 256000 + neighbor 10.255.255.3 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.3 remote-as 65101 + neighbor 10.255.255.3 description dc1-leaf1a_Ethernet1/2 + neighbor 10.255.255.7 peer group IPv4-UNDERLAY-PEERS + neighbor 10.255.255.7 remote-as 65102 + neighbor 10.255.255.7 description dc1-leaf2a_Ethernet2 + redistribute connected + ! + address-family ipv4 + neighbor IPv4-UNDERLAY-PEERS activate +! +end diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-dhcp-mgmt.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-dhcp-mgmt.yml new file mode 100644 index 00000000000..86c2e6daf35 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-dhcp-mgmt.yml @@ -0,0 +1,72 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-dhcp-mgmt +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: dhcp + type: oob +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-empty-mgmt.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-empty-mgmt.yml new file mode 100644 index 00000000000..7c01fab61c3 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-empty-mgmt.yml @@ -0,0 +1,65 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-empty-mgmt +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-null-mgmt.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-null-mgmt.yml new file mode 100644 index 00000000000..4ec6ca7a8d6 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-null-mgmt.yml @@ -0,0 +1,61 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-null-mgmt +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-subnet-a.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-subnet-a.yml new file mode 100644 index 00000000000..c01795563ea --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-subnet-a.yml @@ -0,0 +1,72 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-subnet-a +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.1.201/24 + type: oob +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-subnet-b.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-subnet-b.yml new file mode 100644 index 00000000000..e67b83616a5 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/containerlab-subnet-b.yml @@ -0,0 +1,72 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-subnet-b +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.2/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.2.202/24 + type: oob +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.2 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-leaf1a.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-leaf1a.yml new file mode 100644 index 00000000000..8a0dd5583df --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-leaf1a.yml @@ -0,0 +1,105 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +ethernet_interfaces: +- name: Ethernet1 + description: P2P_dc1-spine1_Ethernet1 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.1/31 + metadata: + peer: dc1-spine1 + peer_interface: Ethernet1 + peer_type: spine + switchport: + enabled: false +- name: Ethernet1/2 + description: P2P_dc1-spine2_Ethernet1/2 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.3/31 + metadata: + peer: dc1-spine2 + peer_interface: Ethernet1/2 + peer_type: spine + switchport: + enabled: false +hostname: dc1-leaf1a +ip_igmp_snooping: + globally_enabled: true +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.1.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.1.101/24 + type: oob + gateway: 172.16.1.1 +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +router_bgp: + as: '65101' + router_id: 10.255.1.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + peer_groups: + - name: IPv4-UNDERLAY-PEERS + metadata: + type: ipv4 + send_community: all + maximum_routes: 256000 + neighbors: + - ip_address: 10.255.255.0 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65100' + metadata: + peer: dc1-spine1 + description: dc1-spine1_Ethernet1 + - ip_address: 10.255.255.2 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65100' + metadata: + peer: dc1-spine2 + description: dc1-spine2_Ethernet1/2 + redistribute: + connected: + enabled: true + address_family_ipv4: + peer_groups: + - name: IPv4-UNDERLAY-PEERS + activate: true +service_routing_protocols_model: multi-agent +static_routes: +- vrf: MGMT + prefix: 0.0.0.0/0 + next_hop: 172.16.1.1 +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-leaf2a.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-leaf2a.yml new file mode 100644 index 00000000000..63f1dbbc7ed --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-leaf2a.yml @@ -0,0 +1,105 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +ethernet_interfaces: +- name: Ethernet1/1 + description: P2P_dc1-spine1_Ethernet1/3 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.5/31 + metadata: + peer: dc1-spine1 + peer_interface: Ethernet1/3 + peer_type: spine + switchport: + enabled: false +- name: Ethernet2 + description: P2P_dc1-spine2_Ethernet2 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.7/31 + metadata: + peer: dc1-spine2 + peer_interface: Ethernet2 + peer_type: spine + switchport: + enabled: false +hostname: dc1-leaf2a +ip_igmp_snooping: + globally_enabled: true +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.1.2/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.1.102/24 + type: oob + gateway: 172.16.1.1 +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +router_bgp: + as: '65102' + router_id: 10.255.1.2 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + peer_groups: + - name: IPv4-UNDERLAY-PEERS + metadata: + type: ipv4 + send_community: all + maximum_routes: 256000 + neighbors: + - ip_address: 10.255.255.4 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65100' + metadata: + peer: dc1-spine1 + description: dc1-spine1_Ethernet1/3 + - ip_address: 10.255.255.6 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65100' + metadata: + peer: dc1-spine2 + description: dc1-spine2_Ethernet2 + redistribute: + connected: + enabled: true + address_family_ipv4: + peer_groups: + - name: IPv4-UNDERLAY-PEERS + activate: true +service_routing_protocols_model: multi-agent +static_routes: +- vrf: MGMT + prefix: 0.0.0.0/0 + next_hop: 172.16.1.1 +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-spine1.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-spine1.yml new file mode 100644 index 00000000000..4635492dd9d --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-spine1.yml @@ -0,0 +1,105 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +ethernet_interfaces: +- name: Ethernet1 + description: P2P_dc1-leaf1a_Ethernet1 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.0/31 + metadata: + peer: dc1-leaf1a + peer_interface: Ethernet1 + peer_type: l3leaf + switchport: + enabled: false +- name: Ethernet1/3 + description: P2P_dc1-leaf2a_Ethernet1/1 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.4/31 + metadata: + peer: dc1-leaf2a + peer_interface: Ethernet1/1 + peer_type: l3leaf + switchport: + enabled: false +hostname: dc1-spine1 +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.1.11/24 + type: oob + gateway: 172.16.1.1 +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + peer_groups: + - name: IPv4-UNDERLAY-PEERS + metadata: + type: ipv4 + send_community: all + maximum_routes: 256000 + neighbors: + - ip_address: 10.255.255.1 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65101' + metadata: + peer: dc1-leaf1a + description: dc1-leaf1a_Ethernet1 + - ip_address: 10.255.255.5 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65102' + metadata: + peer: dc1-leaf2a + description: dc1-leaf2a_Ethernet1/1 + redistribute: + connected: + enabled: true + address_family_ipv4: + peer_groups: + - name: IPv4-UNDERLAY-PEERS + activate: true +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +static_routes: +- vrf: MGMT + prefix: 0.0.0.0/0 + next_hop: 172.16.1.1 +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-spine2.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-spine2.yml new file mode 100644 index 00000000000..af673284b75 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/intended/structured_configs/dc1-spine2.yml @@ -0,0 +1,105 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +ethernet_interfaces: +- name: Ethernet1/2 + description: P2P_dc1-leaf1a_Ethernet1/2 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.2/31 + metadata: + peer: dc1-leaf1a + peer_interface: Ethernet1/2 + peer_type: l3leaf + switchport: + enabled: false +- name: Ethernet2 + description: P2P_dc1-leaf2a_Ethernet2 + shutdown: false + mtu: 9214 + ip_address: 10.255.255.6/31 + metadata: + peer: dc1-leaf2a + peer_interface: Ethernet2 + peer_type: l3leaf + switchport: + enabled: false +hostname: dc1-spine2 +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.2/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.1.12/24 + type: oob + gateway: 172.16.1.1 +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +router_bgp: + as: '65100' + router_id: 10.255.0.2 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + peer_groups: + - name: IPv4-UNDERLAY-PEERS + metadata: + type: ipv4 + send_community: all + maximum_routes: 256000 + neighbors: + - ip_address: 10.255.255.3 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65101' + metadata: + peer: dc1-leaf1a + description: dc1-leaf1a_Ethernet1/2 + - ip_address: 10.255.255.7 + peer_group: IPv4-UNDERLAY-PEERS + remote_as: '65102' + metadata: + peer: dc1-leaf2a + description: dc1-leaf2a_Ethernet2 + redistribute: + connected: + enabled: true + address_family_ipv4: + peer_groups: + - name: IPv4-UNDERLAY-PEERS + activate: true +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +static_routes: +- vrf: MGMT + prefix: 0.0.0.0/0 + next_hop: 172.16.1.1 +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/interface_mapping.json b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/interface_mapping.json new file mode 100644 index 00000000000..0afd6febdaa --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/digital_twin/interface_mapping.json @@ -0,0 +1,12 @@ +{ + "ManagementIntf": { + "eth0": "Management1" + }, + "EthernetIntf": { + "eth1": "Ethernet1", + "eth1_1": "Ethernet1/1", + "eth1_2": "Ethernet1/2", + "eth1_3": "Ethernet1/3", + "eth2": "Ethernet2" + } +} diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1.yml new file mode 100644 index 00000000000..3acc7b8dff7 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1.yml @@ -0,0 +1,2 @@ +--- +mgmt_gateway: 172.16.1.1 diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1_L3_LEAVES.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1_L3_LEAVES.yml new file mode 100644 index 00000000000..16c08b27222 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1_L3_LEAVES.yml @@ -0,0 +1,23 @@ +--- +type: l3leaf + +l3leaf: + defaults: + platform: cEOSLab + loopback_ipv4_pool: 10.255.1.0/27 + vtep_loopback_ipv4_pool: 10.255.2.0/27 + uplink_switches: [dc1-spine1, dc1-spine2] + uplink_ipv4_pool: 10.255.255.0/26 + nodes: + - name: dc1-leaf1a + id: 1 + bgp_as: 65101 + mgmt_ip: 172.16.1.101/24 + uplink_interfaces: [Ethernet1, Ethernet1/2] + uplink_switch_interfaces: [Ethernet1, Ethernet1/2] + - name: dc1-leaf2a + id: 2 + bgp_as: 65102 + mgmt_ip: 172.16.1.102/24 + uplink_interfaces: [Ethernet1/1, Ethernet2] + uplink_switch_interfaces: [Ethernet1/3, Ethernet2] diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1_SPINES.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1_SPINES.yml new file mode 100644 index 00000000000..cb0f31da821 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DC1_SPINES.yml @@ -0,0 +1,15 @@ +--- +type: spine + +spine: + defaults: + platform: cEOSLab + loopback_ipv4_pool: 10.255.0.0/27 + bgp_as: 65100 + nodes: + - name: dc1-spine1 + id: 1 + mgmt_ip: 172.16.1.11/24 + - name: dc1-spine2 + id: 2 + mgmt_ip: 172.16.1.12/24 diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB.yml new file mode 100644 index 00000000000..b76e5ae5b9b --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB.yml @@ -0,0 +1,14 @@ +--- +fabric_name: DIGITAL_TWIN_CONTAINERLAB + +management_eapi: + enabled: true + +eos_designs_documentation: + topology_csv: true + p2p_links_csv: true + +digital_twin: + environment: containerlab + # TODO: remove `fabric` once #5649 is ready + fabric: {} diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_FABRIC.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_FABRIC.yml new file mode 100644 index 00000000000..f77228f6565 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_FABRIC.yml @@ -0,0 +1,11 @@ +--- +underlay_routing_protocol: ebgp +overlay_routing_protocol: none + +default_interfaces: + - types: [spine] + platforms: [default] + downlink_interfaces: [Ethernet1-2] + - types: [l3leaf] + platforms: [default] + uplink_interfaces: [Ethernet1-2] diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/all.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/all.yml new file mode 100644 index 00000000000..e7e19d60245 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/group_vars/all.yml @@ -0,0 +1,2 @@ +--- +root_dir: '{{ playbook_dir }}' diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/host_vars/all.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/host_vars/all.yml new file mode 100644 index 00000000000..e7e19d60245 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/host_vars/all.yml @@ -0,0 +1,2 @@ +--- +root_dir: '{{ playbook_dir }}' diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/hosts.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/hosts.yml new file mode 100644 index 00000000000..bd822221aff --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/inventory/hosts.yml @@ -0,0 +1,21 @@ +--- +all: + children: + DIGITAL_TWIN_CONTAINERLAB: + children: + DIGITAL_TWIN_CONTAINERLAB_FABRIC: + children: + DC1: + children: + DC1_SPINES: + hosts: + dc1-spine1: + ansible_host: 172.16.1.11 + dc1-spine2: + ansible_host: 172.16.1.12 + DC1_L3_LEAVES: + hosts: + dc1-leaf1a: + ansible_host: 172.16.1.101 + dc1-leaf2a: + ansible_host: 172.16.1.102 diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/molecule.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/molecule.yml new file mode 100644 index 00000000000..0abb4f0654e --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/molecule.yml @@ -0,0 +1,30 @@ +--- +ansible: + executor: + backend: ansible-playbook + args: + ansible_playbook: + - --inventory=inventory/ + env: + ANSIBLE_FORCE_COLOR: true + cfg: + defaults: + gathering: explicit +scenario: + converge_sequence: + - syntax + - converge + - verify + test_sequence: + - syntax + - create + - converge + - idempotence + - verify + cleanup_sequence: + - destroy +dependency: + name: galaxy + enabled: false +verifier: + name: ansible diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/verify.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/verify.yml new file mode 100644 index 00000000000..f5a2cbc895c --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab/verify.yml @@ -0,0 +1,26 @@ +--- +- name: Verify + hosts: DIGITAL_TWIN_CONTAINERLAB + gather_facts: false + connection: local + tasks: + - name: Assert Containerlab topology file exists + delegate_to: 127.0.0.1 + ansible.builtin.stat: + path: "{{ root_dir }}/digital_twin/{{ fabric_name }}-topology.clab.yml" + register: containerlab_topology_file + run_once: true + + - name: Assert Containerlab interface mapping file exists + delegate_to: 127.0.0.1 + ansible.builtin.stat: + path: "{{ root_dir }}/digital_twin/interface_mapping.json" + register: containerlab_interface_mapping_file + run_once: true + + - name: Validate generated Containerlab topology artifact + ansible.builtin.assert: + that: + - "containerlab_topology_file.stat.exists" + - "containerlab_interface_mapping_file.stat.exists" + run_once: true diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/converge.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/converge.yml new file mode 100644 index 00000000000..1a0d731a392 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/converge.yml @@ -0,0 +1,75 @@ +--- +- name: Converge expected failure for Containerlab Digital Twin without static management IP + hosts: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP + gather_facts: false + connection: local + vars: &unsupported_mgmt_ip_vars + output_dir_name: "digital_twin/intended" + documentation_dir_name: "digital_twin/documentation" + avd_digital_twin_mode: true + avd_ignore_requirements: true + eos_designs_documentation: + enable: false + topology_csv: false + p2p_links_csv: false + tasks: &unsupported_mgmt_ip_tasks + - name: Run expected failure scenario + block: + - name: Generate intended variables in Containerlab Digital Twin mode + delegate_to: 127.0.0.1 + ansible.builtin.import_role: + name: arista.avd.eos_designs + + - name: Fail if unsupported management IP did not fail + ansible.builtin.fail: + msg: "Expected Containerlab Digital Twin generation to fail for unsupported management IP." + + rescue: + - name: Assert Containerlab Digital Twin failed with the expected error message + run_once: true + ansible.builtin.assert: + that: + - ansible_failed_result is defined + - expected_error_message in ansible_failed_result.msg + +- name: Converge expected failure for Containerlab Digital Twin with empty management IP + hosts: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY + gather_facts: false + connection: local + vars: *unsupported_mgmt_ip_vars + tasks: *unsupported_mgmt_ip_tasks + +- name: Converge expected failure for Containerlab Digital Twin with null management IP + hosts: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL + gather_facts: false + connection: local + vars: *unsupported_mgmt_ip_vars + tasks: *unsupported_mgmt_ip_tasks + +- name: Converge expected failure for Containerlab Digital Twin with multiple management subnets + hosts: DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS + gather_facts: false + connection: local + vars: *unsupported_mgmt_ip_vars + tasks: + - name: Run expected failure scenario + block: + - name: Generate intended variables in Containerlab Digital Twin mode + delegate_to: 127.0.0.1 + ansible.builtin.import_role: + name: arista.avd.eos_designs + + - name: Fail if multiple management subnets did not fail + ansible.builtin.fail: + msg: "Expected Containerlab Digital Twin generation to fail for multiple management subnets." + + rescue: + - name: Assert Containerlab Digital Twin failed with the expected multiple subnet error message + run_once: true + ansible.builtin.assert: + that: + - ansible_failed_result is defined + - expected_error_message in ansible_failed_result.msg + - "'Found multiple subnets:' in ansible_failed_result.msg" + - expected_mgmt_subnets[0] in ansible_failed_result.msg + - expected_mgmt_subnets[1] in ansible_failed_result.msg diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-dhcp-mgmt.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-dhcp-mgmt.yml new file mode 100644 index 00000000000..86c2e6daf35 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-dhcp-mgmt.yml @@ -0,0 +1,72 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-dhcp-mgmt +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: dhcp + type: oob +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-empty-mgmt.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-empty-mgmt.yml new file mode 100644 index 00000000000..7c01fab61c3 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-empty-mgmt.yml @@ -0,0 +1,65 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-empty-mgmt +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-null-mgmt.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-null-mgmt.yml new file mode 100644 index 00000000000..4ec6ca7a8d6 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-null-mgmt.yml @@ -0,0 +1,61 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-null-mgmt +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-subnet-a.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-subnet-a.yml new file mode 100644 index 00000000000..c01795563ea --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-subnet-a.yml @@ -0,0 +1,72 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-subnet-a +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.1/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.1.201/24 + type: oob +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.1 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-subnet-b.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-subnet-b.yml new file mode 100644 index 00000000000..e67b83616a5 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/digital_twin/intended/structured_configs/containerlab-subnet-b.yml @@ -0,0 +1,72 @@ +aaa_root: + disabled: true +config_end: true +enable_password: + disabled: true +hostname: containerlab-subnet-b +ip_routing: true +loopback_interfaces: +- name: Loopback0 + description: ROUTER_ID + shutdown: false + ip_address: 10.255.0.2/32 +management_api_http: + enable_https: true + enable_vrfs: + - name: MGMT +management_interfaces: +- name: Management1 + description: OOB_MANAGEMENT + shutdown: false + vrf: MGMT + ip_address: 172.16.2.202/24 + type: oob +metadata: + is_deployed: true + platform: cEOSLab + fabric_name: DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS + validate_hardware: + enabled: false + digital_twin: + environment: containerlab +prefix_lists: +- name: PL-LOOPBACKS-EVPN-OVERLAY + sequence_numbers: + - sequence: 10 + action: permit 10.255.0.0/27 eq 32 +route_maps: +- name: RM-CONN-2-BGP + sequence_numbers: + - sequence: 10 + type: permit + match: + - ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY +router_bfd: + multihop: + interval: 300 + min_rx: 300 + multiplier: 3 +router_bgp: + as: '65100' + router_id: 10.255.0.2 + maximum_paths: + paths: 4 + bgp: + default: + ipv4_unicast: false + redistribute: + connected: + enabled: true + route_map: RM-CONN-2-BGP +service_routing_protocols_model: multi-agent +spanning_tree: + mode: none +transceiver_qsfp_default_mode_4x10: true +vlan_internal_order: + allocation: ascending + range: + beginning: 1006 + ending: 1199 +vrfs: +- name: MGMT + ip_routing: false diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS.yml new file mode 100644 index 00000000000..c0296792aef --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS.yml @@ -0,0 +1,36 @@ +--- +fabric_name: DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS + +management_eapi: + enabled: true + +eos_designs_documentation: + enable: false + topology_csv: false + p2p_links_csv: false + +digital_twin: + environment: containerlab + # TODO: remove `fabric` once #5649 is ready + fabric: {} + +type: spine + +spine: + defaults: + platform: cEOSLab + loopback_ipv4_pool: 10.255.0.0/27 + bgp_as: 65100 + nodes: + - name: containerlab-subnet-a + id: 1 + mgmt_ip: 172.16.1.201/24 + - name: containerlab-subnet-b + id: 2 + mgmt_ip: 172.16.2.202/24 + +expected_error_message: >- + Containerlab Digital Twin requires all node management IPv4 addresses to belong to the same subnet. +expected_mgmt_subnets: + - 172.16.1.0/24 + - 172.16.2.0/24 diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP.yml new file mode 100644 index 00000000000..5ab2e86e514 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP.yml @@ -0,0 +1,31 @@ +--- +fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP + +management_eapi: + enabled: true + +eos_designs_documentation: + enable: false + topology_csv: false + p2p_links_csv: false + +digital_twin: + environment: containerlab + # TODO: remove `fabric` once #5649 is ready + fabric: {} + +type: spine + +spine: + defaults: + platform: cEOSLab + loopback_ipv4_pool: 10.255.0.0/27 + bgp_as: 65100 + nodes: + - name: containerlab-dhcp-mgmt + id: 1 + mgmt_ip: dhcp + +expected_error_message: >- + Containerlab Digital Twin is unsupported for nodes without a static management IPv4 address. + Found unsupported management IPv4 address settings: containerlab-dhcp-mgmt. diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY.yml new file mode 100644 index 00000000000..a1255cab0ac --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY.yml @@ -0,0 +1,31 @@ +--- +fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY + +management_eapi: + enabled: true + +eos_designs_documentation: + enable: false + topology_csv: false + p2p_links_csv: false + +digital_twin: + environment: containerlab + # TODO: remove `fabric` once #5649 is ready + fabric: {} + +type: spine + +spine: + defaults: + platform: cEOSLab + loopback_ipv4_pool: 10.255.0.0/27 + bgp_as: 65100 + nodes: + - name: containerlab-empty-mgmt + id: 1 + mgmt_ip: "" + +expected_error_message: >- + Containerlab Digital Twin is unsupported for nodes without a static management IPv4 address. + Found unsupported management IPv4 address settings: containerlab-empty-mgmt. diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL.yml new file mode 100644 index 00000000000..e413de80ea0 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL.yml @@ -0,0 +1,31 @@ +--- +fabric_name: DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL + +management_eapi: + enabled: false + +eos_designs_documentation: + enable: false + topology_csv: false + p2p_links_csv: false + +digital_twin: + environment: containerlab + # TODO: remove `fabric` once #5649 is ready + fabric: {} + +type: spine + +spine: + defaults: + platform: cEOSLab + loopback_ipv4_pool: 10.255.0.0/27 + bgp_as: 65100 + nodes: + - name: containerlab-null-mgmt + id: 1 + mgmt_ip: + +expected_error_message: >- + Containerlab Digital Twin is unsupported for nodes without a static management IPv4 address. + Found unsupported management IPv4 address settings: containerlab-null-mgmt. diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/all.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/all.yml new file mode 100644 index 00000000000..e7e19d60245 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/group_vars/all.yml @@ -0,0 +1,2 @@ +--- +root_dir: '{{ playbook_dir }}' diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/hosts.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/hosts.yml new file mode 100644 index 00000000000..aade27cea52 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/inventory/hosts.yml @@ -0,0 +1,23 @@ +--- +all: + children: + DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP: + children: + DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_DHCP: + hosts: + containerlab-dhcp-mgmt: + ansible_host: containerlab-dhcp-mgmt + DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_EMPTY: + hosts: + containerlab-empty-mgmt: + ansible_host: containerlab-empty-mgmt + DIGITAL_TWIN_CONTAINERLAB_UNSUPPORTED_MGMT_IP_NULL: + hosts: + containerlab-null-mgmt: + ansible_host: containerlab-null-mgmt + DIGITAL_TWIN_CONTAINERLAB_MULTIPLE_MGMT_SUBNETS: + hosts: + containerlab-subnet-a: + ansible_host: containerlab-subnet-a + containerlab-subnet-b: + ansible_host: containerlab-subnet-b diff --git a/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/molecule.yml b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/molecule.yml new file mode 100644 index 00000000000..9f7510b6f56 --- /dev/null +++ b/ansible_collections/arista/avd/extensions/molecule/digital_twin_containerlab_negative_tests/molecule.yml @@ -0,0 +1,24 @@ +--- +ansible: + executor: + backend: ansible-playbook + args: + ansible_playbook: + - --inventory=inventory/hosts.yml + env: + ANSIBLE_FORCE_COLOR: true + cfg: + defaults: + gathering: explicit +scenario: + converge_sequence: + - syntax + - converge + test_sequence: + - syntax + - converge +dependency: + name: galaxy + enabled: false +verifier: + name: ansible diff --git a/ansible_collections/arista/avd/extensions/molecule/eos_designs-twodc-5stage-clos/digital_twin/documentation/fabric/TWODC_5STAGE_CLOS-topology.yml b/ansible_collections/arista/avd/extensions/molecule/eos_designs-twodc-5stage-clos/digital_twin/documentation/fabric/TWODC_5STAGE_CLOS-topology.yml index fe109c5c3c6..c108090baf9 100644 --- a/ansible_collections/arista/avd/extensions/molecule/eos_designs-twodc-5stage-clos/digital_twin/documentation/fabric/TWODC_5STAGE_CLOS-topology.yml +++ b/ansible_collections/arista/avd/extensions/molecule/eos_designs-twodc-5stage-clos/digital_twin/documentation/fabric/TWODC_5STAGE_CLOS-topology.yml @@ -1,3 +1,4 @@ +--- nodes: - DC1-POD1-L2LEAF1A: node_type: cloudeos diff --git a/ansible_collections/arista/avd/plugins/action/eos_designs_documentation.py b/ansible_collections/arista/avd/plugins/action/eos_designs_documentation.py index b82032a1472..a2ff00ecc61 100644 --- a/ansible_collections/arista/avd/plugins/action/eos_designs_documentation.py +++ b/ansible_collections/arista/avd/plugins/action/eos_designs_documentation.py @@ -4,7 +4,7 @@ from __future__ import annotations import json -from dataclasses import asdict +from dataclasses import fields, is_dataclass from pathlib import Path from typing import TYPE_CHECKING, Any @@ -51,6 +51,20 @@ } +def _normalize_yaml_data(data: Any) -> Any: + """Recursively normalize data for YAML output while honoring per-field YAML key aliases on dataclasses.""" + if is_dataclass(data): + return { + str(dataclass_field.metadata.get("yaml_key", dataclass_field.name)): _normalize_yaml_data(getattr(data, dataclass_field.name)) + for dataclass_field in fields(data) + } + if isinstance(data, dict): + return {str(key): _normalize_yaml_data(value) for key, value in data.items()} + if isinstance(data, tuple | list): + return [_normalize_yaml_data(value) for value in data] + return data + + class ActionModule(AVDActionPlugin): """Action Module for eos_designs_documentation.""" @@ -114,11 +128,29 @@ def main(self, task_vars: dict[str, Any]) -> None: self.result["changed"] = self.result["changed"] or changed if output.digital_twin: - content = strip_empties_from_dict( - {str(key).replace("_", "-"): list(value) if isinstance(value, tuple) else value for key, value in asdict(output.digital_twin).items()} - ) + content = strip_empties_from_dict(_normalize_yaml_data(output.digital_twin)) + # for cLab we want empty `prefix` at all times in the topology to avoid modifying hostnames + if get(task_vars, "digital_twin.environment") == "containerlab" and hasattr(output.digital_twin, "prefix"): + interface_mapping = content.pop("interface_mapping", None) + if interface_mapping: + changed = write_file( + content=json.dumps(interface_mapping, indent=4) + "\n", + filename=str(Path(validated_args["digital_twin_file"]).parent / "interface_mapping.json"), + file_mode=validated_args["mode"], + ) + self.result["changed"] = self.result["changed"] or changed + + content["topology"]["nodes"] = { + node_name: { + "mgmt-ipv4": node_settings["mgmt-ipv4"], + "startup-config": f"intended/configs/{node_name}.cfg", + } + for node_name, node_settings in content["topology"]["nodes"].items() + } + # add keys in a very specific order - name, prefix, everything else + content = {"name": content["name"], "prefix": output.digital_twin.prefix, **{key: value for key, value in content.items() if key != "name"}} changed = write_file( - content=yaml.dump(content, Dumper=AnsibleDumper, sort_keys=False, indent=2, width=130), + content=yaml.dump(content, Dumper=AnsibleDumper, sort_keys=False, indent=2, width=130, explicit_start=True), filename=validated_args["digital_twin_file"], file_mode=validated_args["mode"], ) diff --git a/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/metadata.md b/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/metadata.md index 4056e7b9a6a..ef5ec060d8a 100644 --- a/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/metadata.md +++ b/ansible_collections/arista/avd/roles/eos_cli_config_gen/docs/tables/metadata.md @@ -136,7 +136,7 @@ | [          - name](## "metadata.cv_pathfinder.applications.categories.user_defined_applications.[].name") | String | | | | | | [            category](## "metadata.cv_pathfinder.applications.categories.user_defined_applications.[].category") | String | | | | | | [  digital_twin](## "metadata.digital_twin") | Dictionary | | | | Metadata used to generate Digital Twin topology. | - | [    environment](## "metadata.digital_twin.environment") | String | | | Valid Values:
- act | Targeted Digital Twin environment. | + | [    environment](## "metadata.digital_twin.environment") | String | | | Valid Values:
- act
- containerlab | Targeted Digital Twin environment. | | [    node_type](## "metadata.digital_twin.node_type") | String | | | | Digital Twin's internal device type used for deploying a replica of the fabric device.
Possible values depend on the target Digital Twin environment. | | [    ip_addr](## "metadata.digital_twin.ip_addr") | String | | | | Management IPv4_address/Mask assigned to a replica of the fabric device within the Digital Twin environment. | | [    version](## "metadata.digital_twin.version") | String | | | | OS version used for deploying a replica of the fabric device within the Digital Twin environment. | @@ -326,7 +326,7 @@ digital_twin: # Targeted Digital Twin environment. - environment: + environment: # Digital Twin's internal device type used for deploying a replica of the fabric device. # Possible values depend on the target Digital Twin environment. diff --git a/ansible_collections/arista/avd/roles/eos_designs/docs/data-models.md b/ansible_collections/arista/avd/roles/eos_designs/docs/data-models.md index a5d028cf8a3..75e997b77f4 100644 --- a/ansible_collections/arista/avd/roles/eos_designs/docs/data-models.md +++ b/ansible_collections/arista/avd/roles/eos_designs/docs/data-models.md @@ -1838,8 +1838,9 @@ The generated artifacts are automatically optimized for the specific Digital Twi AVD currently supports the following Digital Twin environments: - ACT (Arista Cloud Test) +- Containerlab -To generate the ACT Digital Twin artifacts, run the `eos_designs` and `eos_cli_config_gen` roles with the `avd_digital_twin_mode` flag set to `true` in your Ansible playbook: +To generate Digital Twin artifacts, run the `eos_designs` and `eos_cli_config_gen` roles with the `avd_digital_twin_mode` flag set to `true` in your Ansible playbook: ```yaml --- @@ -1880,7 +1881,7 @@ To generate the ACT Digital Twin artifacts, run the `eos_designs` and `eos_cli_c ``` -Produced artifacts: +Produced artifacts (ACT Digital Twin): ```text . @@ -1921,6 +1922,177 @@ ansible_collections/arista/avd/roles/eos_designs/docs/tables/digital-twin-config ansible_collections/arista/avd/roles/eos_designs/docs/tables/node-type-digital-twin-configuration.md --8<-- +### Containerlab Digital Twin + +Produced artifacts (Containerlab Digital Twin): + +```text +. +├── digital_twin +│ ├── -topology.clab.yml +│ ├── interface_mapping.json +│ ├── documentation +│ │ ├── devices +│ │ │ ├── .md +│ │ │ └── ... +│ │ └── fabric +│ │ ├── -documentation.md +│ │ ├── -p2p-links.csv +│ │ └── -topology.csv +│ └── intended +│ ├── configs +│ │ ├── .cfg +│ │ └── ... +│ └── structured_configs +│ ├── .yml +│ └── ... +``` + +Containerlab Digital Twin is available as a `PREVIEW` feature. It is not ready for production use and is intended for user testing only. + +To enable it, set `digital_twin.environment: containerlab` and run the same Digital Twin playbook shown above with the environment set to `containerlab`: + +```yaml +--- +- name: Build Containerlab Digital Twin artifacts + hosts: FABRIC + gather_facts: false + vars: + output_dir_name: "digital_twin/intended" + documentation_dir_name: "digital_twin/documentation" + avd_digital_twin_mode: true + digital_twin: + environment: containerlab + fabric: {} + tasks: + - name: Generate AVD Structured Configurations and Fabric Documentation + ansible.builtin.import_role: + name: arista.avd.eos_designs + + - name: Generate Device Configurations and Documentation + ansible.builtin.import_role: + name: arista.avd.eos_cli_config_gen +``` + +For Containerlab, `digital_twin.fabric` is still required for schema validation, even if it is left empty as `fabric: {}`. This is a temporary limitation and it will be removed once the shared Digital Twin schema is updated. + +Important caveats for the current Containerlab implementation: + +- It is a `PREVIEW` feature and behavior may still change. +- Only out-of-band management is supported. +- Only a single IPv4 management subnet is supported across the lab. +- Management IP addresses in the lab and production must currently match. +- Each node must have a static `mgmt_ip`. Unset management IPs and `mgmt_ip: dhcp` are not supported. +- `digital_twin.mgmt_ip` is not currently used by the Containerlab topology generator. Instead, topology generation uses each node `mgmt_ip` value from the AVD fabric facts. +- The generated topology includes fabric nodes and point-to-point fabric links. Connected endpoints and non-point-to-point links are not modeled. +- The generated topology sets `prefix: ""` intentionally to preserve the original AVD hostnames. +- The generated topology currently defaults to `arista_ceos` with `image: arista/ceos:latest`. Please set up your lab environment accordingly, or update the generated topology file if needed. + +!!! warning + Make sure your lab environment is isolated from production. The management addresses are reused, so take the necessary precautions to avoid pushing configurations to the wrong environment. + +The generated topology file is written to the Digital Twin root directory as `{{ output_dir | dirname }}/{{ fabric_name }}-topology.clab.yml`. The generated intended configs remain under `{{ output_dir }}/configs/.cfg`, and the topology references them as `intended/configs/.cfg`. + +Example generated topology: + +```yaml +--- +name: FABRIC, Containerlab Digital Twin +prefix: '' +mgmt: + network: custom_mgmt + ipv4-subnet: 172.16.1.0/24 +topology: + defaults: + kind: arista_ceos + kinds: + arista_ceos: + enforce-startup-config: true + image: arista/ceos:latest + binds: + - interface_mapping.json:/mnt/flash/EosIntfMapping.json:ro + nodes: + dc1-leaf1a: + mgmt-ipv4: 172.16.1.101 + startup-config: intended/configs/dc1-leaf1a.cfg + dc1-leaf1b: + mgmt-ipv4: 172.16.1.102 + startup-config: intended/configs/dc1-leaf1b.cfg + dc1-leaf1c: + mgmt-ipv4: 172.16.1.151 + startup-config: intended/configs/dc1-leaf1c.cfg + dc1-leaf2a: + mgmt-ipv4: 172.16.1.103 + startup-config: intended/configs/dc1-leaf2a.cfg + dc1-leaf2b: + mgmt-ipv4: 172.16.1.104 + startup-config: intended/configs/dc1-leaf2b.cfg + dc1-leaf2c: + mgmt-ipv4: 172.16.1.152 + startup-config: intended/configs/dc1-leaf2c.cfg + dc1-spine1: + mgmt-ipv4: 172.16.1.11 + startup-config: intended/configs/dc1-spine1.cfg + dc1-spine2: + mgmt-ipv4: 172.16.1.12 + startup-config: intended/configs/dc1-spine2.cfg + links: + - endpoints: + - dc1-leaf1a:eth1 + - dc1-spine1:eth1 + - endpoints: + - dc1-leaf1a:eth2 + - dc1-spine2:eth1 + - endpoints: + - dc1-leaf1a:eth3 + - dc1-leaf1b:eth3 + - endpoints: + - dc1-leaf1a:eth4 + - dc1-leaf1b:eth4 + - endpoints: + - dc1-leaf1a:eth8 + - dc1-leaf1c:eth1 + - endpoints: + - dc1-leaf1b:eth1 + - dc1-spine1:eth2 + - endpoints: + - dc1-leaf1b:eth2 + - dc1-spine2:eth2 + - endpoints: + - dc1-leaf1b:eth8 + - dc1-leaf1c:eth2 + - endpoints: + - dc1-leaf2a:eth1 + - dc1-spine1:eth3 + - endpoints: + - dc1-leaf2a:eth2 + - dc1-spine2:eth3 + - endpoints: + - dc1-leaf2a:eth3 + - dc1-leaf2b:eth3 + - endpoints: + - dc1-leaf2a:eth4 + - dc1-leaf2b:eth4 + - endpoints: + - dc1-leaf2a:eth8 + - dc1-leaf2c:eth1 + - endpoints: + - dc1-leaf2b:eth1 + - dc1-spine1:eth4 + - endpoints: + - dc1-leaf2b:eth2 + - dc1-spine2:eth4 + - endpoints: + - dc1-leaf2b:eth8 + - dc1-leaf2c:eth2 +``` + +To launch the generated topology use following command: + +```bash +containerlab deploy -t digital_twin/documentation/fabric/-topology.clab.yml +``` + ## PREVIEW - New devices models See the [Node type settings](#node-type-settings) section for available keys. diff --git a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/digital-twin-configuration.md b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/digital-twin-configuration.md index b6eec35d882..5a99d7c0fef 100644 --- a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/digital-twin-configuration.md +++ b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/digital-twin-configuration.md @@ -8,7 +8,7 @@ | Variable | Type | Required | Default | Value Restrictions | Description | | -------- | ---- | -------- | ------- | ------------------ | ----------- | | [digital_twin](## "digital_twin") | Dictionary | | | | PREVIEW: This option is marked as "preview", meaning the data models or generated configuration can change at any time.
Global settings to configure the Digital Twin of the Fabric. | - | [  environment](## "digital_twin.environment") | String | | `act` | Valid Values:
- act | Targeted Digital Twin environment. | + | [  environment](## "digital_twin.environment") | String | | `act` | Valid Values:
- act
- containerlab | Targeted Digital Twin environment. | | [  fabric](## "digital_twin.fabric") | Dictionary | Required | | | Settings for Digital Twin fabric devices. | | [    act_os_version](## "digital_twin.fabric.act_os_version") | String | | | | OS version for ACT Digital Twin fabric devices. | | [    act_username](## "digital_twin.fabric.act_username") | String | | `cvpadmin` | | Username for ACT Digital Twin fabric devices. | @@ -25,7 +25,7 @@ digital_twin: # Targeted Digital Twin environment. - environment: + environment: # Settings for Digital Twin fabric devices. fabric: # required diff --git a/ansible_collections/arista/avd/roles/eos_designs/tasks/main.yml b/ansible_collections/arista/avd/roles/eos_designs/tasks/main.yml index 82edf905be4..24ae27adc9d 100644 --- a/ansible_collections/arista/avd/roles/eos_designs/tasks/main.yml +++ b/ansible_collections/arista/avd/roles/eos_designs/tasks/main.yml @@ -83,7 +83,13 @@ p2p_links_csv_file: "{{ fabric_dir }}/{{ fabric_name }}-p2p-links.csv" p2p_links_csv: "{{ eos_designs_documentation.p2p_links_csv | arista.avd.default(false) }}" toc: "{{ eos_designs_documentation.toc | arista.avd.default(true) }}" - digital_twin_file: "{{ fabric_dir }}/{{ fabric_name }}-topology.yml" + digital_twin_file: >- + {{ + (output_dir | dirname) ~ '/' ~ fabric_name ~ '-topology.clab.yml' + if avd_digital_twin_mode | arista.avd.default(false) + and digital_twin.environment | arista.avd.default('act') == 'containerlab' + else fabric_dir ~ '/' ~ fabric_name ~ '-topology.yml' + }} digital_twin: "{{ avd_digital_twin_mode | arista.avd.default(false) }}" mode: "0o664" delegate_to: localhost diff --git a/ansible_collections/arista/avd/tests/unit/action/test_eos_designs_documentation.py b/ansible_collections/arista/avd/tests/unit/action/test_eos_designs_documentation.py index 7fb546ffcf4..d7ecf077bf7 100644 --- a/ansible_collections/arista/avd/tests/unit/action/test_eos_designs_documentation.py +++ b/ansible_collections/arista/avd/tests/unit/action/test_eos_designs_documentation.py @@ -5,13 +5,24 @@ import json import logging +from dataclasses import dataclass, field from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch import pytest from ansible.errors import AnsibleActionFail -from ansible_collections.arista.avd.plugins.action.eos_designs_documentation import ActionModule +from ansible_collections.arista.avd.plugins.action.eos_designs_documentation import ActionModule, _normalize_yaml_data +from pyavd.api.fabric_documentation import ( + ACTDigitalTwin, + ActNodeSettings, + ContainerlabDefaults, + ContainerlabDigitalTwin, + ContainerlabKind, + ContainerlabMgmt, + ContainerlabNode, + ContainerlabTopology, +) if TYPE_CHECKING: from collections.abc import Callable @@ -41,6 +52,53 @@ } +def test_normalize_yaml_data_recursively_honors_dataclass_yaml_keys() -> None: + """Normalize nested cLab-shaped dataclasses into YAML-ready data while preserving explicit YAML key aliases.""" + + @dataclass(frozen=True) + class ContainerlabNode: + mgmt_ipv4: str = field(metadata={"yaml_key": "mgmt-ipv4"}) + + @dataclass(frozen=True) + class ContainerlabKind: + enforce_startup_config: bool = field(metadata={"yaml_key": "enforce-startup-config"}) + image: str + + @dataclass(frozen=True) + class ContainerlabMgmt: + network: str + ipv4_subnet: str = field(metadata={"yaml_key": "ipv4-subnet"}) + + @dataclass(frozen=True) + class ContainerlabTopology: + nodes: dict[object, ContainerlabNode] + kinds: tuple[ContainerlabKind, ...] + endpoint_lists: list[tuple[str, str]] + + @dataclass(frozen=True) + class ContainerlabDigitalTwin: + mgmt: ContainerlabMgmt + topology: ContainerlabTopology + + data = ContainerlabDigitalTwin( + mgmt=ContainerlabMgmt(network="clab-mgmt", ipv4_subnet="172.16.1.0/24"), + topology=ContainerlabTopology( + nodes={1: ContainerlabNode(mgmt_ipv4="172.16.1.101")}, + kinds=(ContainerlabKind(enforce_startup_config=True, image="ceos:latest"),), + endpoint_lists=[("leaf1:eth1", "spine1:eth1")], + ), + ) + + assert _normalize_yaml_data(data) == { + "mgmt": {"network": "clab-mgmt", "ipv4-subnet": "172.16.1.0/24"}, + "topology": { + "nodes": {"1": {"mgmt-ipv4": "172.16.1.101"}}, + "kinds": [{"enforce-startup-config": True, "image": "ceos:latest"}], + "endpoint_lists": [["leaf1:eth1", "spine1:eth1"]], + }, + } + + def _empty_output() -> MagicMock: """Return a mock FabricDocumentation with all outputs empty so no files are written.""" output = MagicMock() @@ -99,6 +157,190 @@ def test_run_routes_missing_device_warning_to_display(action_module: Callable[.. assert result.get("failed") is not True +def test_main_marks_changed_when_p2p_links_csv_changes(action_module: Callable[..., ActionModule], tmp_path: Path) -> None: + """Cover changed aggregation for p2p link CSV output.""" + module = action_module(ActionModule) + module.result["changed"] = False + p2p_links_csv_file = tmp_path / "p2p-links.csv" + output = _empty_output() + output.p2p_links_csv = "node,peer\nleaf1,spine1\n" + written_files: dict[str, str] = {} + + def mock_write_file(content: str, filename: str, file_mode: str) -> bool: + assert file_mode == "0o664" + written_files[filename] = content + return True + + with ( + patch.object( + module, + "_validate_args", + return_value={ + **BASE_VALIDATED_ARGS, + "p2p_links_csv": True, + "p2p_links_csv_file": str(p2p_links_csv_file), + "mode": "0o664", + }, + ), + patch.object(module, "load_facts", return_value={}), + patch.object(module, "read_structured_configs", return_value={}), + patch(f"{MODULE_PATH}.get_fabric_documentation", return_value=output), + patch(f"{MODULE_PATH}.write_file", side_effect=mock_write_file), + ): + module.main(task_vars={"fabric_name": FABRIC_NAME}) + + assert written_files == {str(p2p_links_csv_file): "node,peer\nleaf1,spine1\n"} + assert module.result["changed"] is True + + +def test_main_writes_containerlab_topology_with_ordered_name_and_prefix(action_module: Callable[..., ActionModule], tmp_path: Path) -> None: + """Cover the cLab-only topology key ordering: name and prefix must be emitted first.""" + module = action_module(ActionModule) + module.result["changed"] = False + topology_file = tmp_path / "fabric.clab.yml" + output = _empty_output() + output.digital_twin = ContainerlabDigitalTwin( + name="DC1", + prefix="", + mgmt=ContainerlabMgmt(network="clab-mgmt", ipv4_subnet="172.16.1.0/24"), + topology=ContainerlabTopology( + defaults=ContainerlabDefaults(kind="arista_ceos"), + kinds={"arista_ceos": ContainerlabKind(enforce_startup_config=True, image="ceos:latest")}, + nodes={"leaf1": ContainerlabNode(mgmt_ipv4="172.16.1.101")}, + links=(), + ), + interface_mapping={ + "ManagementIntf": {"eth0": "Management1"}, + "EthernetIntf": {"eth1": "Ethernet1"}, + }, + ) + written_files: dict[str, str] = {} + + def mock_write_file(content: str, filename: str, file_mode: str) -> bool: + assert file_mode == "0o664" + written_files[filename] = content + return True + + with ( + patch.object( + module, + "_validate_args", + return_value={ + **BASE_VALIDATED_ARGS, + "digital_twin": True, + "digital_twin_file": str(topology_file), + "mode": "0o664", + }, + ), + patch.object(module, "load_facts", return_value={}), + patch.object(module, "read_structured_configs", return_value={}), + patch(f"{MODULE_PATH}.get_fabric_documentation", return_value=output), + patch(f"{MODULE_PATH}.write_file", side_effect=mock_write_file), + ): + module.main(task_vars={"fabric_name": FABRIC_NAME, "digital_twin": {"environment": "containerlab"}}) + + assert written_files[str(topology_file)].splitlines()[:3] == ["---", "name: DC1", "prefix: ''"] + assert json.loads(written_files[str(tmp_path / "interface_mapping.json")]) == { + "ManagementIntf": {"eth0": "Management1"}, + "EthernetIntf": {"eth1": "Ethernet1"}, + } + + +def test_main_writes_containerlab_topology_without_interface_mapping(action_module: Callable[..., ActionModule], tmp_path: Path) -> None: + """Cover cLab topology rendering when no interface mapping sidecar is present.""" + module = action_module(ActionModule) + module.result["changed"] = False + topology_file = tmp_path / "fabric.clab.yml" + output = _empty_output() + output.digital_twin = ContainerlabDigitalTwin( + name="DC1", + prefix="", + mgmt=ContainerlabMgmt(network="clab-mgmt", ipv4_subnet="172.16.1.0/24"), + topology=ContainerlabTopology( + defaults=ContainerlabDefaults(kind="arista_ceos"), + kinds={"arista_ceos": ContainerlabKind(enforce_startup_config=True, image="ceos:latest")}, + nodes={"leaf1": ContainerlabNode(mgmt_ipv4="172.16.1.101")}, + links=(), + ), + interface_mapping=None, + ) + written_files: dict[str, str] = {} + + def mock_write_file(content: str, filename: str, file_mode: str) -> bool: + assert file_mode == "0o664" + written_files[filename] = content + return True + + with ( + patch.object( + module, + "_validate_args", + return_value={ + **BASE_VALIDATED_ARGS, + "digital_twin": True, + "digital_twin_file": str(topology_file), + "mode": "0o664", + }, + ), + patch.object(module, "load_facts", return_value={}), + patch.object(module, "read_structured_configs", return_value={}), + patch(f"{MODULE_PATH}.get_fabric_documentation", return_value=output), + patch(f"{MODULE_PATH}.write_file", side_effect=mock_write_file), + ): + module.main(task_vars={"fabric_name": FABRIC_NAME, "digital_twin": {"environment": "containerlab"}}) + + assert list(written_files) == [str(topology_file)] + assert written_files[str(topology_file)].splitlines()[:3] == ["---", "name: DC1", "prefix: ''"] + + +def test_main_writes_act_topology_without_containerlab_post_processing(action_module: Callable[..., ActionModule], tmp_path: Path) -> None: + """Cover the generic Digital Twin serialization path for non-cLab topologies.""" + module = action_module(ActionModule) + module.result["changed"] = False + topology_file = tmp_path / "fabric.yml" + output = _empty_output() + output.digital_twin = ACTDigitalTwin( + nodes=( + { + "leaf1": ActNodeSettings( + node_type="veos", + ip_addr="192.0.2.1", + version="4.33.1.1F", + internet_access=None, + ports=None, + ) + }, + ), + ) + written_files: dict[str, str] = {} + + def mock_write_file(content: str, filename: str, file_mode: str) -> bool: + assert file_mode == "0o664" + written_files[filename] = content + return True + + with ( + patch.object( + module, + "_validate_args", + return_value={ + **BASE_VALIDATED_ARGS, + "digital_twin": True, + "digital_twin_file": str(topology_file), + "mode": "0o664", + }, + ), + patch.object(module, "load_facts", return_value={}), + patch.object(module, "read_structured_configs", return_value={}), + patch(f"{MODULE_PATH}.get_fabric_documentation", return_value=output), + patch(f"{MODULE_PATH}.write_file", side_effect=mock_write_file), + ): + module.main(task_vars={"fabric_name": FABRIC_NAME, "digital_twin": {"environment": "act"}}) + + assert list(written_files) == [str(topology_file)] + assert written_files[str(topology_file)].splitlines()[:2] == ["---", "nodes:"] + + # --------------------------------------------------------------------------- # Exceptions: user-visible failure messages. # --------------------------------------------------------------------------- diff --git a/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py b/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py index 27cb963fe58..7f43890e51b 100644 --- a/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py +++ b/python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py @@ -29279,7 +29279,7 @@ def __init__( class DigitalTwin(AvdModel): """Subclass of AvdModel.""" - Environment: TypeAlias = Literal["act"] + Environment: TypeAlias = Literal["act", "containerlab"] _fields: ClassVar[dict] = { "environment": {"type": str}, "node_type": {"type": str}, diff --git a/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml b/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml index 5cb852a96cd..e7ce2644879 100644 --- a/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml +++ b/python-avd/pyavd/_eos_cli_config_gen/schema/eos_cli_config_gen.schema.yml @@ -11240,6 +11240,7 @@ keys: description: Targeted Digital Twin environment. valid_values: - act + - containerlab node_type: type: str description: 'Digital Twin''s internal device type used for deploying diff --git a/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/metadata.schema.yml b/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/metadata.schema.yml index 23b1864c63d..2db7663d48f 100644 --- a/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/metadata.schema.yml +++ b/python-avd/pyavd/_eos_cli_config_gen/schema/schema_fragments/metadata.schema.yml @@ -434,6 +434,7 @@ keys: description: Targeted Digital Twin environment. valid_values: - act + - containerlab node_type: type: str description: |- diff --git a/python-avd/pyavd/_eos_designs/schema/__init__.py b/python-avd/pyavd/_eos_designs/schema/__init__.py index d802f792a31..f0e232e0851 100644 --- a/python-avd/pyavd/_eos_designs/schema/__init__.py +++ b/python-avd/pyavd/_eos_designs/schema/__init__.py @@ -17968,7 +17968,7 @@ class Devices(AvdIndexedList[str, DevicesItem]): class DigitalTwin(AvdModel): """Subclass of AvdModel.""" - Environment: TypeAlias = Literal["act"] + Environment: TypeAlias = Literal["act", "containerlab"] class Fabric(AvdModel): """Subclass of AvdModel.""" diff --git a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml index 89dc6d14777..b036d3d0e9f 100644 --- a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml @@ -2297,6 +2297,7 @@ keys: description: Targeted Digital Twin environment. valid_values: - act + - containerlab default: act fabric: type: dict diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/digital_twin.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/digital_twin.schema.yml index 4d5426a152c..b6a3794a6a4 100644 --- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/digital_twin.schema.yml +++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/digital_twin.schema.yml @@ -20,6 +20,7 @@ keys: Targeted Digital Twin environment. valid_values: - act + - containerlab default: act fabric: type: dict diff --git a/python-avd/pyavd/_eos_designs/structured_config/metadata/digital_twin.py b/python-avd/pyavd/_eos_designs/structured_config/metadata/digital_twin.py index 57f8b193c88..7bd91445098 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/metadata/digital_twin.py +++ b/python-avd/pyavd/_eos_designs/structured_config/metadata/digital_twin.py @@ -85,3 +85,5 @@ def _set_digital_twin(self: AvdStructuredConfigMetadataProtocol) -> None: internet_access=act_internet_access, ) return + case "containerlab": + self.structured_config.metadata.digital_twin._update(environment=environment) diff --git a/python-avd/pyavd/api/fabric_documentation/__init__.py b/python-avd/pyavd/api/fabric_documentation/__init__.py index 77003f60562..8de2f033ef4 100644 --- a/python-avd/pyavd/api/fabric_documentation/__init__.py +++ b/python-avd/pyavd/api/fabric_documentation/__init__.py @@ -2,7 +2,7 @@ # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. -from dataclasses import dataclass +from dataclasses import dataclass, field @dataclass(frozen=True) @@ -10,6 +10,42 @@ class ActLinkSettings: connection: tuple[str, str] +@dataclass(frozen=True) +class ContainerlabLinkSettings: + endpoints: tuple[str, str] + + +@dataclass(frozen=True) +class ContainerlabNode: + mgmt_ipv4: str = field(metadata={"yaml_key": "mgmt-ipv4"}) + + +@dataclass(frozen=True) +class ContainerlabDefaults: + kind: str + + +@dataclass(frozen=True) +class ContainerlabKind: + enforce_startup_config: bool = field(metadata={"yaml_key": "enforce-startup-config"}) + image: str + binds: tuple[str, ...] | None = None + + +@dataclass(frozen=True) +class ContainerlabMgmt: + network: str + ipv4_subnet: str = field(metadata={"yaml_key": "ipv4-subnet"}) + + +@dataclass(frozen=True) +class ContainerlabTopology: + defaults: ContainerlabDefaults + kinds: dict[str, ContainerlabKind] + nodes: dict[str, ContainerlabNode] + links: tuple[ContainerlabLinkSettings, ...] + + @dataclass(frozen=True) class ActNodeTypeSettings: username: str @@ -34,12 +70,23 @@ class ACTDigitalTwin: cloudeos: ActNodeTypeSettings | None = None cvp: ActNodeTypeSettings | None = None generic: ActNodeTypeSettings | None = None - third_party: ActNodeTypeSettings | None = None - tools_server: ActNodeTypeSettings | None = None + third_party: ActNodeTypeSettings | None = field(default=None, metadata={"yaml_key": "third-party"}) + tools_server: ActNodeTypeSettings | None = field(default=None, metadata={"yaml_key": "tools-server"}) veos: ActNodeTypeSettings | None = None links: tuple[ActLinkSettings, ...] | None = None +@dataclass(frozen=True) +class ContainerlabDigitalTwin: + """Containerlab Digital Twin fabric documentation dataclass.""" + + name: str + prefix: str + mgmt: ContainerlabMgmt + topology: ContainerlabTopology + interface_mapping: dict[str, dict[str, str]] | None = None + + class FabricDocumentation: """ Object containing the requested documentation. @@ -54,4 +101,4 @@ class FabricDocumentation: fabric_documentation: str = "" topology_csv: str = "" p2p_links_csv: str = "" - digital_twin: ACTDigitalTwin | None = None + digital_twin: ACTDigitalTwin | ContainerlabDigitalTwin | None = None diff --git a/python-avd/pyavd/get_fabric_documentation.py b/python-avd/pyavd/get_fabric_documentation.py index 01232301213..48fb264caaf 100644 --- a/python-avd/pyavd/get_fabric_documentation.py +++ b/python-avd/pyavd/get_fabric_documentation.py @@ -3,15 +3,23 @@ # that can be found in the LICENSE file. from __future__ import annotations +from ipaddress import ip_network from re import findall as re_findall from typing import TYPE_CHECKING, cast -from pyavd._utils import get +from pyavd._utils import get, get_ip_from_ip_prefix from pyavd.api.fabric_documentation import ( ACTDigitalTwin, ActLinkSettings, ActNodeSettings, ActNodeTypeSettings, + ContainerlabDefaults, + ContainerlabDigitalTwin, + ContainerlabKind, + ContainerlabLinkSettings, + ContainerlabMgmt, + ContainerlabNode, + ContainerlabTopology, FabricDocumentation, ) @@ -147,7 +155,7 @@ def _get_p2p_links_csv(fabric_documentation_facts: FabricDocumentationFacts) -> return csv_content.read() -def _get_digital_twin(fabric_documentation_facts: FabricDocumentationFacts) -> ACTDigitalTwin | None: +def _get_digital_twin(fabric_documentation_facts: FabricDocumentationFacts) -> ACTDigitalTwin | ContainerlabDigitalTwin | None: digital_twin_env = next( ( environment @@ -159,10 +167,100 @@ def _get_digital_twin(fabric_documentation_facts: FabricDocumentationFacts) -> A match digital_twin_env: case "act": return _get_digital_twin_act(fabric_documentation_facts) + case "containerlab": + return _get_digital_twin_containerlab(fabric_documentation_facts) case _: return None +def _is_p2p_link(topology_link: dict) -> bool: + # Skip connections where at least one of the contributing sources is not a non-empty string + return bool( + isinstance(topology_link["node"], str) + and topology_link["node"] + and isinstance(topology_link["node_interface"], str) + and topology_link["node_interface"] + and "." not in topology_link["node_interface"] + and isinstance(topology_link["peer"], str) + and topology_link["peer"] + and isinstance(topology_link["peer_interface"], str) + and topology_link["peer_interface"] + and "." not in topology_link["peer_interface"] + ) + + +def _get_digital_twin_containerlab(fabric_documentation_facts: FabricDocumentationFacts) -> ContainerlabDigitalTwin: + """ + Build and return the Containerlab topology data. + + The returned object contains the minimal information required to render + Containerlab nodes with management addresses under `topology.nodes` and + inter-switch links under `topology.links`. + """ + from pyavd._errors import AristaAvdError # noqa: PLC0415 + + sorted_avd_facts = sorted(fabric_documentation_facts.avd_facts.items()) + + unsupported_devices = [device for device, facts in sorted_avd_facts if not facts.mgmt_ip or facts.mgmt_ip == "dhcp"] + if unsupported_devices: + msg = ( + "Containerlab Digital Twin is unsupported for nodes without a static management IPv4 address. " + f"Found unsupported management IPv4 address settings: {', '.join(unsupported_devices)}." + ) + raise AristaAvdError(msg) + + mgmt_ips = [(device, cast("str", facts.mgmt_ip)) for device, facts in sorted_avd_facts] + nodes = {device: ContainerlabNode(mgmt_ipv4=get_ip_from_ip_prefix(mgmt_ip)) for device, mgmt_ip in mgmt_ips} + + links = [ + ContainerlabLinkSettings( + endpoints=( + f"{topology_link['node']}:{topology_link['node_interface'].replace('Ethernet', 'eth').replace('/', '_')}", + f"{topology_link['peer']}:{topology_link['peer_interface'].replace('Ethernet', 'eth').replace('/', '_')}", + ) + ) + for topology_link in fabric_documentation_facts.topology_links + if _is_p2p_link(topology_link) + ] + ethernet_interface_mapping = { + endpoint_interface: endpoint_interface.replace("eth", "Ethernet", 1).replace("_", "/") + for link in links + for endpoint in link.endpoints + for endpoint_interface in [endpoint.rsplit(":", maxsplit=1)[1]] + } + default_kind = "arista_ceos" + + # find Containerlab mgmt network and raise and error if nodes are not in the same subnet + unique_mgmt_networks = {ip_network(mgmt_ip, strict=False) for _, mgmt_ip in mgmt_ips} + + if len(unique_mgmt_networks) > 1: + mgmt_networks = ", ".join(f"{network}" for network in unique_mgmt_networks) + msg = f"Containerlab Digital Twin requires all node management IPv4 addresses to belong to the same subnet. Found multiple subnets: {mgmt_networks}." + raise AristaAvdError(msg) + + return ContainerlabDigitalTwin( + name=f"{fabric_documentation_facts.fabric_name}", + prefix="", + mgmt=ContainerlabMgmt(network="custom_mgmt", ipv4_subnet=str(next(iter(unique_mgmt_networks)))), + topology=ContainerlabTopology( + defaults=ContainerlabDefaults(kind=default_kind), + kinds={ + default_kind: ContainerlabKind( + enforce_startup_config=True, + image="arista/ceos:latest", + binds=("interface_mapping.json:/mnt/flash/EosIntfMapping.json:ro",), + ) + }, + nodes=nodes, + links=tuple(links), + ), + interface_mapping={ + "ManagementIntf": {"eth0": "Management1"}, + "EthernetIntf": dict(sorted(ethernet_interface_mapping.items())), + }, + ) + + def _get_digital_twin_act(fabric_documentation_facts: FabricDocumentationFacts) -> ACTDigitalTwin: """ Build and return the ACT topology data. @@ -206,23 +304,6 @@ def _get_digital_twin_act(fabric_documentation_facts: FabricDocumentationFacts) } digital_twin_devices: list[dict[str, ActNodeSettings]] = [] device_list: list[str] = list(fabric_documentation_facts.avd_facts) - verified_topology_links: list[dict] = [ - topology_link - for topology_link in fabric_documentation_facts.topology_links - # Skip connections where at least one of the contributing sources is not a non-empty string - if ( - isinstance(topology_link["node"], str) - and topology_link["node"] - and isinstance(topology_link["node_interface"], str) - and "." not in topology_link["node_interface"] - and topology_link["node_interface"] - and isinstance(topology_link["peer"], str) - and topology_link["peer"] - and isinstance(topology_link["peer_interface"], str) - and "." not in topology_link["peer_interface"] - and topology_link["peer_interface"] - ) - ] for device in sorted(device_list): if ( digital_twin_node_type := get(fabric_documentation_facts.structured_configs, f"{device}..metadata..digital_twin..node_type", separator="..") @@ -277,7 +358,8 @@ def _get_digital_twin_act(fabric_documentation_facts: FabricDocumentationFacts) ActLinkSettings( connection=(f"{topology_link['node']}:{topology_link['node_interface']}", f"{topology_link['peer']}:{topology_link['peer_interface']}") ) - for topology_link in verified_topology_links + for topology_link in fabric_documentation_facts.topology_links + if _is_p2p_link(topology_link) ), cloudeos=digital_twin_node_types["cloudeos"], cvp=digital_twin_node_types["cvp"], diff --git a/python-avd/tests/pyavd/molecule_scenarios/test_get_device_structured_config.py b/python-avd/tests/pyavd/molecule_scenarios/test_get_device_structured_config.py index 06b8e65a1bf..660347b2f21 100644 --- a/python-avd/tests/pyavd/molecule_scenarios/test_get_device_structured_config.py +++ b/python-avd/tests/pyavd/molecule_scenarios/test_get_device_structured_config.py @@ -32,7 +32,7 @@ ) # TODO: Remove inline jinja # @pytest.mark.digital_twin_molecule_scenarios("eos_designs-twodc-5stage-clos") -@pytest.mark.digital_twin_molecule_scenarios("digital_twin") +@pytest.mark.digital_twin_molecule_scenarios("digital_twin", "digital_twin_containerlab") def test_get_device_structured_config(molecule_host: MoleculeHost) -> None: """Test get_device_structured_config.""" hostvars = molecule_host.hostvars diff --git a/python-avd/tests/pyavd/molecule_scenarios/test_get_fabric_documentation.py b/python-avd/tests/pyavd/molecule_scenarios/test_get_fabric_documentation.py index 805977c826a..dcb03e8b097 100644 --- a/python-avd/tests/pyavd/molecule_scenarios/test_get_fabric_documentation.py +++ b/python-avd/tests/pyavd/molecule_scenarios/test_get_fabric_documentation.py @@ -3,13 +3,15 @@ # that can be found in the LICENSE file. import sys from copy import deepcopy +from types import SimpleNamespace from unittest.mock import patch import pytest from pyavd import get_fabric_documentation from pyavd._utils import get -from pyavd.api.fabric_documentation import ACTDigitalTwin, FabricDocumentation +from pyavd.api.fabric_documentation import ACTDigitalTwin, ContainerlabDigitalTwin, FabricDocumentation +from pyavd.get_fabric_documentation import _get_digital_twin from tests.models import MoleculeScenario @@ -48,6 +50,13 @@ def render(self) -> dict[str, object]: assert "## Connected Endpoints\n\nNo connected endpoint configured!" in fabric_documentation_obj.fabric_documentation +def test_get_digital_twin_returns_none_for_unsupported_environment() -> None: + """Test fallback path for unsupported Digital Twin environment.""" + fabric_documentation_facts = SimpleNamespace(structured_configs={"leaf1": {"metadata": {"digital_twin": {"environment": "unsupported"}}}}) + + assert _get_digital_twin(fabric_documentation_facts) is None + + @pytest.mark.molecule_scenarios( "digital_twin", "eos_designs_unit_tests", @@ -67,7 +76,7 @@ def render(self) -> dict[str, object]: "example-single-dc-l3ls", "example-single-dc-l3ls-ipv6", ) -@pytest.mark.digital_twin_molecule_scenarios("eos_designs-twodc-5stage-clos", "digital_twin") +@pytest.mark.digital_twin_molecule_scenarios("eos_designs-twodc-5stage-clos", "digital_twin", "digital_twin_containerlab") def test_get_fabric_documentation(molecule_scenario: MoleculeScenario) -> None: """Test get_fabric_documentation.""" with patch("sys.path", [*sys.path, *molecule_scenario.extra_python_paths]): @@ -126,7 +135,11 @@ def test_get_fabric_documentation(molecule_scenario: MoleculeScenario) -> None: if molecule_scenario.digital_twin: # We expect digital twin topology - assert isinstance(fabric_documentation_obj.digital_twin, ACTDigitalTwin) + digital_twin_environment = get(first_hostvars, "digital_twin.environment", default="act") + if digital_twin_environment == "containerlab": + assert isinstance(fabric_documentation_obj.digital_twin, ContainerlabDigitalTwin) + else: + assert isinstance(fabric_documentation_obj.digital_twin, ACTDigitalTwin) # TODO: add shortcut to the digital twin topology file contents in the MoleculeScenario object and assert that it matches. else: # No digital twin topology diff --git a/python-avd/tests/pyavd/molecule_scenarios/test_negative_eos_designs.py b/python-avd/tests/pyavd/molecule_scenarios/test_negative_eos_designs.py index bdfb8068473..6ea8f976cb7 100644 --- a/python-avd/tests/pyavd/molecule_scenarios/test_negative_eos_designs.py +++ b/python-avd/tests/pyavd/molecule_scenarios/test_negative_eos_designs.py @@ -2,11 +2,15 @@ # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. import re +import sys from copy import deepcopy +from types import SimpleNamespace +from unittest.mock import MagicMock, patch import pytest -from pyavd import get_avd_facts, get_device_structured_config, validate_inputs +from pyavd import get_avd_facts, get_device_structured_config, get_fabric_documentation, validate_inputs +from pyavd._eos_designs.structured_config.metadata.digital_twin import DigitalTwinMixin from pyavd._errors import AristaAvdError from tests.models import MoleculeHost, MoleculeScenario @@ -47,3 +51,62 @@ def test_negative_eos_designs(molecule_scenario: MoleculeScenario, molecule_host # Run get_device_structured_config excepting an error to be raised. with pytest.raises(Exception, match=re.escape(host_inputs["expected_error_message"])): _ = get_device_structured_config(molecule_host.name, host_inputs, avd_facts, digital_twin=_is_digital_twin_host) + + +@pytest.mark.molecule_scenarios("digital_twin_containerlab_negative_tests") +def test_negative_containerlab_fabric_documentation(molecule_scenario: MoleculeScenario) -> None: + """Test get_fabric_documentation errors for invalid Containerlab Digital Twin inputs.""" + tested_fabrics = set() + + with patch("sys.path", [*sys.path, *molecule_scenario.extra_python_paths]): + for molecule_host in molecule_scenario.hosts: + fabric_name = molecule_host.hostvars["fabric_name"] + if fabric_name in tested_fabrics: + continue + tested_fabrics.add(fabric_name) + + fabric_hosts = molecule_host.hostvars["groups"][fabric_name] + fabric_inputs = {host.name: host for host in molecule_scenario.hosts if host.name in fabric_hosts} + molecule_structured_configs = {host.name: deepcopy(host.structured_config) for host in fabric_inputs.values()} + for structured_config in molecule_structured_configs.values(): + structured_config.setdefault("metadata", {}).setdefault("digital_twin", {})["environment"] = "containerlab" + molecule_avd_facts = {host.name: molecule_scenario.avd_facts[host.name] for host in fabric_inputs.values()} + + with pytest.raises(AristaAvdError, match=re.escape(molecule_host.hostvars["expected_error_message"])) as exc_info: + get_fabric_documentation( + avd_facts=molecule_avd_facts, + structured_configs=molecule_structured_configs, + fabric_name=fabric_name, + fabric_documentation=False, + topology_csv=False, + p2p_links_csv=False, + digital_twin=True, + ) + + for expected_mgmt_subnet in molecule_host.hostvars.get("expected_mgmt_subnets", []): + assert expected_mgmt_subnet in str(exc_info.value) + + +def test_set_digital_twin_containerlab_and_unsupported_environment() -> None: + """Test metadata Digital Twin match handling for cLab and unsupported environments.""" + digital_twin_metadata = MagicMock() + structured_config = SimpleNamespace(metadata=SimpleNamespace(digital_twin=digital_twin_metadata)) + + DigitalTwinMixin._set_digital_twin( + SimpleNamespace( + inputs=SimpleNamespace(digital_twin=SimpleNamespace(environment="containerlab")), + structured_config=structured_config, + ) + ) + + digital_twin_metadata._update.assert_called_once_with(environment="containerlab") + digital_twin_metadata.reset_mock() + + DigitalTwinMixin._set_digital_twin( + SimpleNamespace( + inputs=SimpleNamespace(digital_twin=SimpleNamespace(environment="unsupported")), + structured_config=structured_config, + ) + ) + + digital_twin_metadata._update.assert_not_called()