feat(vlans): VLAN-aware bridge, ports, mgmt interface (mechanism)
Implements Task 7. Deliberate lockout-safe ordering (vlan-filtering LAST) with
:if [find] guards that adopt the existing defconf bridge/ports rather than
recreating them. Membership Jinja: trunk ports tagged per tagged_vlans, access
ports untagged per pvid, bridge/CPU tagged only on the mgmt VLAN; else={set} makes
membership declarative. Jinja render validated offline against the placeholder
topology. Device run DEFERRED to an on-site session with a recovery channel
(remote bench has no serial/WinBox-MAC fallback). Topology stays placeholder.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
39a12ae23b
commit
33dc378c3c
2 changed files with 109 additions and 4 deletions
|
|
@ -23,7 +23,15 @@ switch_ntp_servers: "10.0.99.1"
|
|||
|
||||
switch_admin_user: "sjat"
|
||||
|
||||
# Real VLAN/port topology (EDIT to the makerspace plan when known)
|
||||
# PLACEHOLDER VLAN/port topology — vlans.yml is correct mechanism, but these IDs
|
||||
# and the per-port map are NOT the real makerspace plan. Replace with the real
|
||||
# VLAN ids + full ether1-8/sfp map before any on-site VLAN run. Notes:
|
||||
# - mode: access -> untagged member of `pvid`; mode: trunk -> tagged member of
|
||||
# each id in `tagged_vlans`, with `pvid` as the native (untagged) VLAN.
|
||||
# - trunk pvid: 1 means untagged frames on the uplink land in VLAN 1 (unused in a
|
||||
# hardened design). Decide deliberately whether the uplink should carry any
|
||||
# untagged traffic; set pvid to an intended native VLAN or leave 1 as a dead end.
|
||||
# - the bridge (CPU) is tagged ONLY on switch_mgmt_vlan_id (see vlans.yml).
|
||||
switch_vlans:
|
||||
- {id: 99, name: "mgmt"}
|
||||
- {id: 10, name: "members"}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,101 @@
|
|||
---
|
||||
- name: Placeholder
|
||||
ansible.builtin.debug:
|
||||
msg: "not yet implemented"
|
||||
# VLAN-aware bridge, access/trunk ports, and the management VLAN interface.
|
||||
#
|
||||
# ORDERING IS DELIBERATE (lockout safety): bridge (filtering OFF) -> ports+pvid ->
|
||||
# VLAN membership -> mgmt VLAN iface + IP -> default route -> vlan-filtering LAST.
|
||||
# Enabling vlan-filtering is the point at which a wrong management path strands the
|
||||
# switch, so it runs only after the mgmt VLAN/IP exist. Keep a serial/WinBox-MAC
|
||||
# recovery channel open when running this against a live device.
|
||||
#
|
||||
# DEFCONF NOTE: on a factory-default CRS310 the `bridge` already exists with every
|
||||
# port as an untagged member and the management IP sits directly on `bridge`
|
||||
# (192.168.88.1/24). This role does NOT delete that legacy IP — after you have
|
||||
# proven reachability on the new mgmt VLAN, remove the old bridge IP on-site so the
|
||||
# device is reachable only via vlan-mgmt. The guards below adopt the existing bridge
|
||||
# and ports rather than recreating them.
|
||||
#
|
||||
# Idempotency comes from the RouterOS `:if [find]` guards (changed_when: false).
|
||||
|
||||
- name: Create VLAN-aware bridge (filtering off initially)
|
||||
community.routeros.command:
|
||||
commands:
|
||||
- >-
|
||||
:if ([:len [/interface/bridge/find name="{{ switch_bridge_name }}"]] = 0)
|
||||
do={ /interface/bridge/add name="{{ switch_bridge_name }}"
|
||||
vlan-filtering=no }
|
||||
changed_when: false
|
||||
|
||||
- name: Add or adopt bridge ports and set their PVID
|
||||
community.routeros.command:
|
||||
commands:
|
||||
- >-
|
||||
:if ([:len [/interface/bridge/port/find interface="{{ item.interface }}"]] = 0)
|
||||
do={ /interface/bridge/port/add bridge="{{ switch_bridge_name }}"
|
||||
interface="{{ item.interface }}" pvid={{ item.pvid }} }
|
||||
else={ /interface/bridge/port/set [find interface="{{ item.interface }}"]
|
||||
pvid={{ item.pvid }} }
|
||||
loop: "{{ switch_bridge_ports }}"
|
||||
loop_control:
|
||||
label: "{{ item.interface }} (pvid {{ item.pvid }})"
|
||||
changed_when: false
|
||||
|
||||
# tagged = trunk ports whose tagged_vlans include this id, plus the bridge (CPU)
|
||||
# ONLY on the management VLAN so the vlan-mgmt interface is reachable.
|
||||
# untagged = access ports whose pvid equals this id.
|
||||
- name: Define bridge VLANs (tagged/untagged membership)
|
||||
community.routeros.command:
|
||||
commands:
|
||||
- >-
|
||||
:local tagged "{{ ((switch_bridge_ports
|
||||
| selectattr('mode', 'equalto', 'trunk')
|
||||
| selectattr('tagged_vlans', 'defined')
|
||||
| selectattr('tagged_vlans', 'contains', item.id)
|
||||
| map(attribute='interface') | list)
|
||||
+ ([switch_bridge_name] if item.id == switch_mgmt_vlan_id else []))
|
||||
| join(',') }}";
|
||||
:local untagged "{{ switch_bridge_ports
|
||||
| selectattr('mode', 'equalto', 'access')
|
||||
| selectattr('pvid', 'equalto', item.id)
|
||||
| map(attribute='interface') | list | join(',') }}";
|
||||
:if ([:len [/interface/bridge/vlan/find vlan-ids={{ item.id }}]] = 0)
|
||||
do={ /interface/bridge/vlan/add bridge="{{ switch_bridge_name }}"
|
||||
vlan-ids={{ item.id }} tagged=$tagged untagged=$untagged }
|
||||
else={ /interface/bridge/vlan/set [find vlan-ids={{ item.id }}]
|
||||
tagged=$tagged untagged=$untagged }
|
||||
loop: "{{ switch_vlans }}"
|
||||
loop_control:
|
||||
label: "vlan {{ item.id }} ({{ item.name }})"
|
||||
changed_when: false
|
||||
|
||||
- name: Create the management VLAN interface
|
||||
community.routeros.command:
|
||||
commands:
|
||||
- >-
|
||||
:if ([:len [/interface/vlan/find name="vlan-mgmt"]] = 0)
|
||||
do={ /interface/vlan/add name="vlan-mgmt"
|
||||
interface="{{ switch_bridge_name }}" vlan-id={{ switch_mgmt_vlan_id }} }
|
||||
changed_when: false
|
||||
|
||||
- name: Assign the management IP address
|
||||
community.routeros.command:
|
||||
commands:
|
||||
- >-
|
||||
:if ([:len [/ip/address/find interface="vlan-mgmt"]] = 0)
|
||||
do={ /ip/address/add address="{{ switch_mgmt_address }}"
|
||||
interface="vlan-mgmt" }
|
||||
changed_when: false
|
||||
|
||||
- name: Set the default gateway route
|
||||
community.routeros.command:
|
||||
commands:
|
||||
- >-
|
||||
:if ([:len [/ip/route/find dst-address="0.0.0.0/0"]] = 0)
|
||||
do={ /ip/route/add dst-address=0.0.0.0/0
|
||||
gateway="{{ switch_mgmt_gateway }}" }
|
||||
changed_when: false
|
||||
|
||||
- name: Enable VLAN filtering (LAST — prove mgmt reachability first)
|
||||
community.routeros.command:
|
||||
commands:
|
||||
- /interface/bridge/set "{{ switch_bridge_name }}" vlan-filtering=yes
|
||||
changed_when: false
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue