summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoromnom62 <omnom62@outlook.com>2026-06-29 08:52:51 +1000
committeromnom62 <omnom62@outlook.com>2026-06-29 08:52:51 +1000
commita0393f9be79bdb45e7a4dcd51b50cdb3d840988c (patch)
treec9679c09d6a6b163c01fe4c049fd7a3d8bc6fe28
parent6be14447461593f2c71088b54fe523cb468e32dd (diff)
downloadrest.vyos-a0393f9be79bdb45e7a4dcd51b50cdb3d840988c.tar.gz
rest.vyos-a0393f9be79bdb45e7a4dcd51b50cdb3d840988c.zip
T8989: ospf_v3 module
-rw-r--r--README.md1
-rw-r--r--plugins/modules/vyos_ospfv3.py360
-rw-r--r--tests/integration/targets/vyos_ospfv3/aliases1
-rw-r--r--tests/integration/targets/vyos_ospfv3/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_ospfv3/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml17
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml5
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml29
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml21
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml48
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml35
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml35
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml60
-rw-r--r--tests/integration/targets/vyos_ospfv3/vars/main.yaml2
-rw-r--r--tests/unit/fixtures/ospfv3_running.json19
-rw-r--r--tests/unit/modules/test_vyos_ospfv3.py140
17 files changed, 802 insertions, 0 deletions
diff --git a/README.md b/README.md
index c5bc5f1..ef91f32 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,7 @@ Name | Description
[vyos.rest.vyos_lldp_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_lldp_interfaces_module.rst)|Manage LLDP interface configuration on VyOS devices via REST API.
[vyos.rest.vyos_logging_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_logging_global_module.rst)|Manage syslog configuration on VyOS devices using REST API
[vyos.rest.vyos_ntp_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ntp_global_module.rst)|Manage NTP configuration on VyOS devices using REST API
+[vyos.rest.vyos_ospfv3](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospfv3_module.rst)|Manage OSPFv3 configuration on VyOS devices using REST API
[vyos.rest.vyos_route_maps](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_route_maps_module.rst)|Manage route-map configuration on VyOS devices using REST API
[vyos.rest.vyos_snmp_server](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_snmp_server_module.rst)|Manage SNMP server configuration on VyOS devices using REST API
diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py
new file mode 100644
index 0000000..fcf404e
--- /dev/null
+++ b/plugins/modules/vyos_ospfv3.py
@@ -0,0 +1,360 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_ospfv3
+short_description: Manage OSPFv3 configuration on VyOS devices using REST API
+description:
+ - Manages OSPFv3 configuration on VyOS devices via the REST API.
+ - Uses REST API (C(connection=httpapi)) instead of CLI.
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: OSPFv3 configuration.
+ type: dict
+ suboptions:
+ areas:
+ description: OSPFv3 areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description: Area identity.
+ type: str
+ required: true
+ export_list:
+ description: Name of export-list.
+ type: str
+ import_list:
+ description: Name of import-list.
+ type: str
+ range:
+ description: Summarize routes matching prefix.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: IPv6 prefix.
+ type: str
+ required: true
+ advertise:
+ description: Advertise this range.
+ type: bool
+ not_advertise:
+ description: Do not advertise this range.
+ type: bool
+ parameters:
+ description: OSPFv3 global parameters.
+ type: dict
+ suboptions:
+ router_id:
+ description: Router ID (IPv4 address format).
+ type: str
+ redistribute:
+ description: Redistribute routes from another protocol.
+ type: list
+ elements: dict
+ suboptions:
+ route_type:
+ description: Protocol to redistribute.
+ type: str
+ choices: [bgp, connected, kernel, ripng, static]
+ route_map:
+ description: Route map to apply.
+ type: str
+ state:
+ description:
+ - Desired state of the OSPFv3 configuration.
+ - C(merged) adds or updates without removing existing config.
+ - C(replaced) replaces the entire OSPFv3 configuration.
+ - C(deleted) removes OSPFv3 configuration.
+ - C(gathered) returns current configuration as structured data.
+ type: str
+ choices: [merged, replaced, deleted, gathered]
+ default: merged
+notes:
+ - Requires C(ansible_connection=httpapi) with the VyOS httpapi plugin.
+ - C(ansible_network_os) must be set to C(vyos.rest.vyos).
+"""
+
+EXAMPLES = r"""
+- name: Merge OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ import_list: import1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+
+- name: Delete all OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+- name: Gather current OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: OSPFv3 configuration before this module ran.
+ returned: always
+ type: dict
+after:
+ description: OSPFv3 configuration after this module ran.
+ returned: when changed
+ type: dict
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current OSPFv3 configuration as structured data.
+ returned: when state is gathered
+ type: dict
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_BASE = ["protocols", "ospfv3"]
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(_BASE)
+ if not raw or not isinstance(raw, dict):
+ return {}
+ return _parse_ospfv3(raw)
+
+
+def _parse_ospfv3(raw):
+ result = {}
+
+ # parameters
+ params = raw.get("parameters", {})
+ if params:
+ result["parameters"] = {}
+ if "router-id" in params:
+ result["parameters"]["router_id"] = params["router-id"]
+
+ # redistribute
+ redist_raw = raw.get("redistribute", {})
+ if redist_raw and isinstance(redist_raw, dict):
+ redist = []
+ for route_type, data in sorted(redist_raw.items()):
+ entry = {"route_type": route_type}
+ if isinstance(data, dict) and data.get("route-map"):
+ entry["route_map"] = data["route-map"]
+ redist.append(entry)
+ if redist:
+ result["redistribute"] = redist
+
+ # areas
+ area_raw = raw.get("area", {})
+ if area_raw and isinstance(area_raw, dict):
+ areas = []
+ for area_id, area_data in sorted(area_raw.items()):
+ area = {"area_id": area_id}
+ area_data = area_data or {}
+ if area_data.get("export-list"):
+ area["export_list"] = area_data["export-list"]
+ if area_data.get("import-list"):
+ area["import_list"] = area_data["import-list"]
+ range_raw = area_data.get("range", {})
+ if range_raw and isinstance(range_raw, dict):
+ ranges = []
+ for prefix, rdata in sorted(range_raw.items()):
+ r = {"address": prefix}
+ rdata = rdata or {}
+ if "advertise" in rdata:
+ r["advertise"] = True
+ if "not-advertise" in rdata:
+ r["not_advertise"] = True
+ ranges.append(r)
+ if ranges:
+ area["range"] = ranges
+ areas.append(area)
+ if areas:
+ result["areas"] = areas
+
+ return result
+
+
+def build_commands(config, have, state):
+ cmds = []
+
+ if state == "deleted":
+ if have:
+ cmds.append(("delete", _BASE))
+ return cmds
+
+ if state == "replaced":
+ # Build what we would set from scratch and compare to have
+ would_set = build_commands(config, {}, "merged")
+ have_set = build_commands(have, {}, "merged")
+ if would_set == have_set:
+ return []
+ if have:
+ cmds.append(("delete", _BASE))
+ have = {}
+
+ # parameters
+ want_params = (config or {}).get("parameters") or {}
+ have_params = have.get("parameters") or {}
+ if want_params.get("router_id") and want_params["router_id"] != have_params.get("router_id"):
+ cmds.append(("set", _BASE + ["parameters", "router-id", want_params["router_id"]]))
+
+ # redistribute
+ want_redist = {r["route_type"]: r for r in ((config or {}).get("redistribute") or [])}
+ have_redist = {r["route_type"]: r for r in (have.get("redistribute") or [])}
+
+ for rt in set(have_redist) - set(want_redist):
+ if state == "merged":
+ pass # merged doesn't remove
+ for rt, entry in want_redist.items():
+ if rt not in have_redist:
+ cmds.append(("set", _BASE + ["redistribute", rt]))
+ if entry.get("route_map"):
+ have_rm = have_redist.get(rt, {}).get("route_map")
+ if entry["route_map"] != have_rm:
+ cmds.append(("set", _BASE + ["redistribute", rt, "route-map", entry["route_map"]]))
+
+ # areas
+ want_areas = {a["area_id"]: a for a in ((config or {}).get("areas") or [])}
+ have_areas = {a["area_id"]: a for a in (have.get("areas") or [])}
+
+ for area_id, want_area in want_areas.items():
+ have_area = have_areas.get(area_id, {})
+ abase = _BASE + ["area", area_id]
+
+ if want_area.get("export_list") and want_area["export_list"] != have_area.get(
+ "export_list",
+ ):
+ cmds.append(("set", abase + ["export-list", want_area["export_list"]]))
+ if want_area.get("import_list") and want_area["import_list"] != have_area.get(
+ "import_list",
+ ):
+ cmds.append(("set", abase + ["import-list", want_area["import_list"]]))
+
+ want_ranges = {r["address"]: r for r in (want_area.get("range") or [])}
+ have_ranges = {r["address"]: r for r in (have_area.get("range") or [])}
+
+ for addr in want_ranges:
+ if addr not in have_ranges:
+ cmds.append(("set", abase + ["range", addr]))
+ r = want_ranges[addr]
+ if r.get("not_advertise"):
+ cmds.append(("set", abase + ["range", addr, "not-advertise"]))
+ elif r.get("advertise"):
+ cmds.append(("set", abase + ["range", addr, "advertise"]))
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="dict",
+ options=dict(
+ areas=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ area_id=dict(type="str", required=True),
+ export_list=dict(type="str"),
+ import_list=dict(type="str"),
+ range=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ address=dict(type="str", required=True),
+ advertise=dict(type="bool"),
+ not_advertise=dict(type="bool"),
+ ),
+ ),
+ ),
+ ),
+ parameters=dict(
+ type="dict",
+ options=dict(
+ router_id=dict(type="str"),
+ ),
+ ),
+ redistribute=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ route_type=dict(
+ type="str",
+ choices=["bgp", "connected", "kernel", "ripng", "static"],
+ ),
+ route_map=dict(type="str"),
+ ),
+ ),
+ ),
+ ),
+ state=dict(
+ default="merged",
+ choices=["merged", "replaced", "deleted", "gathered"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True)
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or {}
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/integration/targets/vyos_ospfv3/aliases b/tests/integration/targets/vyos_ospfv3/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_ospfv3/defaults/main.yaml b/tests/integration/targets/vyos_ospfv3/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml b/tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml
new file mode 100644
index 0000000..4147e6d
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml
@@ -0,0 +1,21 @@
+---
+- name: Collect all httpapi test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/httpapi"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=httpapi)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.httpapi
+ ansible_network_os: vyos.rest.vyos
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_ospfv3/tasks/main.yaml b/tests/integration/targets/vyos_ospfv3/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run httpapi tests
+ ansible.builtin.include_tasks: httpapi.yaml
+ tags:
+ - httpapi
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..19a61c7
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,17 @@
+---
+- name: Populate ospfv3 config for testing
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ import_list: import1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..fe60eaf
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,5 @@
+---
+- name: Remove pre-existing ospfv3 config
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..9b09ccd
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml
@@ -0,0 +1,29 @@
+---
+- debug:
+ msg: START vyos_ospfv3 deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..bf2cc0c
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml
@@ -0,0 +1,21 @@
+---
+- debug:
+ msg: START vyos_ospfv3 gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.parameters.router_id == "192.0.2.10"
+ - result.gathered.redistribute | selectattr('route_type', 'eq', 'bgp') | list | length == 1
+ - result.gathered.areas | selectattr('area_id', 'eq', '2') | list | length == 1
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..10702e4
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml
@@ -0,0 +1,48 @@
+---
+- debug:
+ msg: START vyos_ospfv3 merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..609e7ff
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml
@@ -0,0 +1,35 @@
+---
+- debug:
+ msg: START vyos_ospfv3 overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3: &id001
+ config:
+ parameters:
+ router_id: 192.0.2.11
+ areas:
+ - area_id: "3"
+ range:
+ - address: "2001:db40::/32"
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..3f1be83
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml
@@ -0,0 +1,35 @@
+---
+- debug:
+ msg: START vyos_ospfv3 replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3: &id001
+ config:
+ parameters:
+ router_id: 192.0.2.11
+ areas:
+ - area_id: "3"
+ range:
+ - address: "2001:db40::/32"
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..7636c23
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml
@@ -0,0 +1,60 @@
+---
+- debug:
+ msg: START vyos_ospfv3 round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered.parameters.router_id == "192.0.2.10"
+ - "'bgp' in gathered.gathered.redistribute | map(attribute='route_type') | list"
+ - gathered.gathered.areas | selectattr('area_id', 'eq', '2') | list | length == 1
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.11
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered.parameters.router_id == "192.0.2.11"
+ - gathered2.gathered.redistribute is not defined or gathered2.gathered.redistribute | length == 0
+ - gathered2.gathered.areas | selectattr('area_id', 'eq', '2') | map(attribute='range') | first | length == 1
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/vars/main.yaml b/tests/integration/targets/vyos_ospfv3/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/unit/fixtures/ospfv3_running.json b/tests/unit/fixtures/ospfv3_running.json
new file mode 100644
index 0000000..44a1bcd
--- /dev/null
+++ b/tests/unit/fixtures/ospfv3_running.json
@@ -0,0 +1,19 @@
+{
+ "parameters": { "router-id": "192.0.2.10" },
+ "redistribute": { "bgp": {}, "connected": { "route-map": "RM1" } },
+ "area": {
+ "2": {
+ "export-list": "export1",
+ "import-list": "import1",
+ "range": {
+ "2001:db10::/32": {},
+ "2001:db20::/32": { "not-advertise": {} }
+ }
+ },
+ "3": {
+ "range": {
+ "2001:db40::/32": {}
+ }
+ }
+ }
+}
diff --git a/tests/unit/modules/test_vyos_ospfv3.py b/tests/unit/modules/test_vyos_ospfv3.py
new file mode 100644
index 0000000..a84041b
--- /dev/null
+++ b/tests/unit/modules/test_vyos_ospfv3.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_ospfv3 import (
+ build_commands,
+ get_running_config,
+)
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ with open(os.path.join(fixtures_dir, filename)) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.mock_vyos.get_config = MagicMock(return_value={})
+
+ def set_running_config(self, data):
+ self.mock_vyos.get_config.return_value = data
+
+
+class TestVyOSOspfv3Parse(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("ospfv3_running.json")
+
+ def test_parses_parameters(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result["parameters"]["router_id"], "192.0.2.10")
+
+ def test_parses_redistribute(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ route_types = [r["route_type"] for r in result["redistribute"]]
+ self.assertIn("bgp", route_types)
+ self.assertIn("connected", route_types)
+ connected = next(r for r in result["redistribute"] if r["route_type"] == "connected")
+ self.assertEqual(connected["route_map"], "RM1")
+
+ def test_parses_areas(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(len(result["areas"]), 2)
+ area2 = next(a for a in result["areas"] if a["area_id"] == "2")
+ self.assertEqual(area2["export_list"], "export1")
+ self.assertEqual(area2["import_list"], "import1")
+ self.assertEqual(len(area2["range"]), 2)
+ not_adv = next(r for r in area2["range"] if r["address"] == "2001:db20::/32")
+ self.assertTrue(not_adv["not_advertise"])
+
+ def test_empty_config_returns_empty(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, {})
+
+
+class TestVyOSOspfv3BuildCommands(unittest.TestCase):
+
+ def test_deleted_with_have(self):
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands({}, have, "deleted")
+ self.assertEqual(cmds, [("delete", ["protocols", "ospfv3"])])
+
+ def test_deleted_without_have(self):
+ cmds = build_commands({}, {}, "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_merged_parameters(self):
+ config = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "parameters", "router-id", "192.0.2.10"]),
+ cmds,
+ )
+
+ def test_merged_redistribute(self):
+ config = {"redistribute": [{"route_type": "bgp"}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "redistribute", "bgp"]),
+ cmds,
+ )
+
+ def test_merged_idempotent(self):
+ config = {"parameters": {"router_id": "192.0.2.10"}}
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, have, "merged")
+ self.assertEqual(cmds, [])
+
+ def test_merged_area_range(self):
+ config = {
+ "areas": [{"area_id": "2", "range": [{"address": "2001:db10::/32"}]}],
+ }
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "area", "2", "range", "2001:db10::/32"]),
+ cmds,
+ )
+
+ def test_replaced_idempotent(self):
+ config = {"parameters": {"router_id": "192.0.2.10"}}
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, have, "replaced")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_rebuilds_on_change(self):
+ config = {"parameters": {"router_id": "192.0.2.11"}}
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, have, "replaced")
+ self.assertEqual(cmds[0], ("delete", ["protocols", "ospfv3"]))
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "parameters", "router-id", "192.0.2.11"]),
+ cmds,
+ )
+
+ def test_merged_area_export_list(self):
+ config = {"areas": [{"area_id": "2", "export_list": "export1"}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "area", "2", "export-list", "export1"]),
+ cmds,
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()