Skip to content

community.routeros.api_modify: False-positive diff in check mode for interface ethernet fec-mode #403

@DonGiovanni83

Description

@DonGiovanni83
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"
  1. On a RouterOS device with SFP-class interfaces, ensure /interface ethernet print detail shows fec-mode: auto for
    an SFP port.
  2. Run the play above in check mode with diff enabled; observe that fec-mode appears removed in the diff.
  3. Run in normal mode; observe ok status and that the device still reports fec-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 as off (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".

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 auto as a disable/remove action.
  • Still allows non-FEC capable ports to present !fec-mode (disabled form) due to can_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions