-
Notifications
You must be signed in to change notification settings - Fork 63
Description
SUMMARY
In check mode, community.routeros.api_modify shows a diff removing fec-mode for SFP-class ports under
path: interface ethernet, while a normal (non-check) run correctly reports no changes. The check-mode simulation drops the key because its value equals the field’s remove_value (auto). RouterOS however reports fec-mode: "auto" explicitly on FEC-capable ports, so removing it in the simulated "after" state produces a misleading diff.
ISSUE TYPE
- Bug Report
COMPONENT NAME
community.routeros.api_modify
ANSIBLE VERSION
ansible [core 2.16.14]
config file = /path/to/repo/ansible.cfg
configured module search path = ['~/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = ~/.virtualenvs/<venv-name>/lib/python3.13/site-packages/ansible
ansible collection location = /path/to/repo/collections_galaxy:/opt/puzzle/ansible/collections
executable location = ~/.virtualenvs/<venv-name>/bin/ansible
python version = 3.13.2 [GCC 11.4.0] (~/.virtualenvs/<venv-name>/bin/python)
jinja version = 3.1.6
libyaml = True
COLLECTION VERSION
Collection Version
------------------ -------
community.routeros 3.11.0
OS / ENVIRONMENT
- Python: 3.13
- RouterOS: 7.16.1
- Models: CRS518-16XS-2XQ-RM, CRS354-48G-4S+2Q+RM
STEPS TO REPRODUCE
Run this playbook against a RouterOS host:
- name: Prepare interface data
ansible.builtin.set_fact:
ethernet_interfaces:
- default-name: sfp28-7
auto-negotiation: true
disabled: false
mtu: 1500
l2mtu: 1584
fec-mode: auto
- name: Configure interfaces
community.routeros.api_modify:
hostname: "{{ ansible_host }}"
username: "{{ routeros_user.username }}"
password: "{{ routeros_user.password }}"
path: interface ethernet
data: "{{ ethernet_interfaces }}"
register: interface_ethernet_result
changed_when: "interface_ethernet_result.new_data != interface_ethernet_result.old_data"
- On a RouterOS device with SFP-class interfaces, ensure
/interface ethernet print detailshowsfec-mode: autofor
an SFP port. - Run the play above in check mode with diff enabled; observe that
fec-modeappears removed in the diff. - Run in normal mode; observe
okstatus and that the device still reportsfec-mode: "auto".
EXPECTED RESULTS
Check-mode diff should reflect the actual device state after a run. Since the non-check run results in no change and the device reports fec-mode: "auto" on SFP ports, check-mode should not show the key being removed.
ACTUAL RESULTS
- Check mode diff (note
fec-mode"disappears"):
TASK [mikrotik_switches : Configure interfaces]
--- before
+++ after
@@
"default-name": "sfp28-7",
"disabled": false,
- "fec-mode": "auto",
"l2mtu": 1584,
"loop-protect": "default",
"loop-protect-disable-time": "5m",
changed: [switch01]
- Non-check mode (correctly no change):
TASK [mikrotik_switches : Configure interfaces]
ok: [switch01]
RouterOS property details for fec-mode
- From RouterOS docs (SFP28/QSFP+/QSFP28):
auto— same asoff(disabled)fec74— IEEE 802.3 clause 74 (FC-FEC)fec91— IEEE 802.3 clause 91 (RS-FEC)off— disabled
- Field availability depends on interface type:
- Non-FEC-capable ports (e.g., copper
ether1) show the field disabled/absent:!fec-mode: null. - SFP-class ports explicitly include the field with a value, e.g.,
fec-mode: "auto".
- Non-FEC-capable ports (e.g., copper
Examples (from /interface ethernet print detail via API) ():
- Non-FEC port (
ether1):
{
".id": "*1A",
"name": "ether1",
"default-name": "ether1",
"l2mtu": 1598,
"advertise": "10M-baseT-half,10M-baseT-full,100M-baseT-half,100M-baseT-full",
"!combo-mode": null,
"!comment": null,
"!fec-mode": null,
"!full-duplex": null,
"!mdix-enable": null,
"!poe-out": null,
"!poe-priority": null,
"!poe-voltage": null,
"!power-cycle-interval": null,
"!power-cycle-ping-address": null,
"!power-cycle-ping-enabled": null,
"!power-cycle-ping-timeout": null,
"!sfp-rate-select": null,
"!sfp-shutdown-temperature": null,
"!speed": null
}
- SFP28 port (
sfp28-7):
{
".id": "*10",
"name": "sfp28-7",
"default-name": "sfp28-7",
"l2mtu": 1584,
"advertise": "10M-baseT-half,10M-baseT-full,100M-baseT-half,100M-baseT-full,1G-baseT-half,1G-baseT-full,1G-baseX,2.5G-baseT,2.5G-baseX,5G-baseT,10G-baseT,10G-baseSR-LR,10G-baseCR,25G-baseSR-LR,25G-baseCR",
"fec-mode": "auto",
"!combo-mode": null,
"!comment": null,
"!full-duplex": null,
"!mdix-enable": null,
"!poe-out": null,
"!poe-priority": null,
"!poe-voltage": null,
"!power-cycle-interval": null,
"!power-cycle-ping-address": null,
"!power-cycle-ping-enabled": null,
"!power-cycle-ping-timeout": null,
"!speed": null
}
Analysis
The root cause seems to be in the ('interface','ethernet') field where fec-mode is defined as 'fec-mode': KeyInfo(can_disable=True, remove_value='auto'). find_modifications() treats value == remove_value as disabled and drops the key in the simulated after state. RouterOS, however, reports fec-mode: "auto" explicitly on FEC-capable ports.
In check mode, a removal of the fec-mode property is shown, creating a false-positive diff. Non-check mode reads back from the device and returns ok.
Proposed fixes
Change fec-mode to default to auto instead of using remove_value.
In plugins/module_utils/_api_data.py under the ('interface', 'ethernet') path, change:
'fec-mode': KeyInfo(can_disable=True, remove_value='auto'),
to:
'fec-mode': KeyInfo(default='auto', can_disable=True),
Rationale:
- Matches RouterOS behavior on FEC-capable ports, which explicitly report
fec-mode: "auto". - Prevents check-mode from interpreting
autoas a disable/remove action. - Still allows non-FEC capable ports to present
!fec-mode(disabled form) due tocan_disable=True.
Thanks
Let me know if this sounds like a good fix, and I'll be happy to submit a PR and test the changes.