summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authoromnom62 <75066712+omnom62@users.noreply.github.com>2026-09-28 22:34:13 +1000
committerGitHub <noreply@github.com>2026-09-28 13:34:13 +0100
commit7cfca28e5857b480920c22ae12557f55ca2a8a19 (patch)
tree8e3bfc20dc621371af85b9d62e89b4f3e1838c01 /tests
parent916b2cc86b29aabe3778be8fa88393f6a5324832 (diff)
downloadrest.vyos-7cfca28e5857b480920c22ae12557f55ca2a8a19.tar.gz
rest.vyos-7cfca28e5857b480920c22ae12557f55ca2a8a19.zip
T8989: vyos vrf module (#39)HEADmain
* T8989: vyos_vrf module * T8989: vyos_vrf module * T8989: vyos_vrf module unit tests * T8989: vyos_vrf module integration tests * T8989: vyos_vrf module integration tests * T8989: vyos_vrf module changelog * T8989: fix pylint disallowed-name issues * T8989: fix pylint disallowed-name issues * T8989: fix lint issues for vrf * T8989: vrf AI comment fixed * T8989: vrf AI comment fixed --------- Co-authored-by: Daniil Baturin <daniil@vyos.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/targets/vyos_vrf/aliases1
-rw-r--r--tests/integration/targets/vyos_vrf/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_vrf/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_vrf/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/_populate_config.yaml23
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/_remove_config.yaml4
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/deleted.yaml50
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/gathered.yaml27
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/merged.yaml59
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/merged_protocols.yaml117
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/overridden.yaml50
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/replaced.yaml52
-rw-r--r--tests/integration/targets/vyos_vrf/tests/httpapi/rtt.yaml82
-rw-r--r--tests/integration/targets/vyos_vrf/vars/main.yaml2
-rw-r--r--tests/unit/fixtures/vrf_running.json42
-rw-r--r--tests/unit/modules/test_vyos_vrf.py369
16 files changed, 907 insertions, 0 deletions
diff --git a/tests/integration/targets/vyos_vrf/aliases b/tests/integration/targets/vyos_vrf/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_vrf/defaults/main.yaml b/tests/integration/targets/vyos_vrf/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_vrf/tasks/httpapi.yaml b/tests/integration/targets/vyos_vrf/tasks/httpapi.yaml
new file mode 100644
index 0000000..0ed3e42
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/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 | sort }}"
+
+- 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_vrf/tasks/main.yaml b/tests/integration/targets/vyos_vrf/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/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_vrf/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..69222b6
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,23 @@
+---
+- name: Populate VRF configuration
+ vyos.rest.vyos_vrf:
+ config:
+ bind_to_all: true
+ instances:
+ - name: vrf1
+ description: red
+ table_id: 101
+ vni: 501
+ disable: true
+ - name: vrf2
+ description: blah2
+ table_id: 102
+ vni: 102
+ address_family:
+ - afi: ipv4
+ disable_forwarding: true
+ nht_no_resolve_via_default: true
+ - afi: ipv6
+ disable_forwarding: true
+ nht_no_resolve_via_default: true
+ state: merged
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..da63347
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,4 @@
+---
+- name: Remove VRF configuration
+ vyos.rest.vyos_vrf:
+ state: deleted
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..22eb43f
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/deleted.yaml
@@ -0,0 +1,50 @@
+---
+- debug:
+ msg: START vyos_vrf deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete specific VRF
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf1
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Gather and verify vrf1 deleted
+ register: result
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.instances | length == 1
+ - result.gathered.instances[0].name == "vrf2"
+
+ - name: Delete all VRF configuration
+ register: result
+ vyos.rest.vyos_vrf:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete all VRF (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_vrf:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..83f543d
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/gathered.yaml
@@ -0,0 +1,27 @@
+---
+- debug:
+ msg: START vyos_vrf gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather VRF configuration
+ register: result
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.gathered.bind_to_all == true
+ - result.gathered.instances | length == 2
+ - result.gathered.instances[0].name == "vrf1"
+ - result.gathered.instances[0].table_id == 101
+ - result.gathered.instances[0].vni == 501
+ - result.gathered.instances[0].disable == true
+ - result.gathered.instances[1].name == "vrf2"
+ - result.gathered.instances[1].table_id == 102
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..2655ce3
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/merged.yaml
@@ -0,0 +1,59 @@
+---
+- debug:
+ msg: START vyos_vrf merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge VRF configuration
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ bind_to_all: true
+ instances:
+ - name: vrf1
+ description: red
+ table_id: 101
+ vni: 501
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.after.instances | length == 1
+ - result.after.instances[0].name == "vrf1"
+ - result.after.instances[0].table_id == 101
+
+ - name: Merge VRF configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ bind_to_all: true
+ instances:
+ - name: vrf1
+ description: red
+ table_id: 101
+ vni: 501
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ - name: Merge second VRF instance
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf2
+ table_id: 102
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.after.instances | length == 2
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/merged_protocols.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/merged_protocols.yaml
new file mode 100644
index 0000000..f600f84
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/merged_protocols.yaml
@@ -0,0 +1,117 @@
+---
+- debug:
+ msg: START vyos_vrf merged protocols integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge VRF with BGP
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf1
+ table_id: 101
+ protocols:
+ bgp:
+ system_as: 65001
+ neighbor:
+ - address: 10.0.0.1
+ remote_as: 65002
+ description: peer1
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge VRF with BGP (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf1
+ table_id: 101
+ protocols:
+ bgp:
+ system_as: 65001
+ neighbor:
+ - address: 10.0.0.1
+ remote_as: 65002
+ description: peer1
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ - name: Merge VRF with OSPF and static routes
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf1
+ table_id: 101
+ protocols:
+ ospf:
+ areas:
+ - area_id: "0"
+ networks:
+ - 10.0.0.0/24
+ parameters:
+ router_id: 10.0.0.1
+ static:
+ routes:
+ - dest: 192.168.10.0/24
+ next_hops:
+ - 10.0.0.254
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Gather and verify protocols
+ register: result
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.instances[0].protocols.bgp.system_as == 65001
+ - result.gathered.instances[0].protocols.ospf.areas | length == 1
+ - result.gathered.instances[0].protocols.static.routes | length == 1
+
+ - name: Change an already-set OSPF router_id
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf1
+ table_id: 101
+ protocols:
+ ospf:
+ areas:
+ - area_id: "0"
+ networks:
+ - 10.0.0.0/24
+ parameters:
+ router_id: 10.0.0.99
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Gather and verify router_id was actually changed
+ register: result
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.instances[0].protocols.ospf.parameters.router_id == "10.0.0.99"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..da72aac
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/overridden.yaml
@@ -0,0 +1,50 @@
+---
+- debug:
+ msg: START vyos_vrf overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override with single VRF
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf3
+ table_id: 200
+ vni: 2000
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Gather and verify override
+ register: result
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.instances | length == 1
+ - result.gathered.instances[0].name == "vrf3"
+ - result.gathered.instances[0].table_id == 200
+
+ - name: Override (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf3
+ table_id: 200
+ vni: 2000
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..53714fc
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/replaced.yaml
@@ -0,0 +1,52 @@
+---
+- debug:
+ msg: START vyos_vrf replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace vrf1 configuration
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf1
+ description: updated
+ table_id: 101
+ vni: 999
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Gather and verify replacement
+ register: result
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.instances[0].vni == 999
+ - result.gathered.instances[0].description == "updated"
+ - result.gathered.instances[0].disable is not defined or result.gathered.instances[0].disable == false
+
+ - name: Replace vrf1 (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_vrf:
+ config:
+ instances:
+ - name: vrf1
+ description: updated
+ table_id: 101
+ vni: 999
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vrf/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_vrf/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..9d16bc6
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/tests/httpapi/rtt.yaml
@@ -0,0 +1,82 @@
+---
+- debug:
+ msg: START vyos_vrf round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_vrf:
+ config:
+ bind_to_all: true
+ instances:
+ - name: vrf1
+ description: red
+ table_id: 101
+ vni: 501
+ protocols:
+ bgp:
+ system_as: 65001
+ neighbor:
+ - address: 10.0.0.1
+ remote_as: 65002
+ ospf:
+ areas:
+ - area_id: "0"
+ networks:
+ - 10.0.0.0/24
+ parameters:
+ router_id: 10.0.0.1
+ static:
+ routes:
+ - dest: 192.168.10.0/24
+ next_hops:
+ - 10.0.0.254
+ state: merged
+
+ - name: RTT - Gather
+ register: gathered
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - gathered.gathered.bind_to_all == true
+ - gathered.gathered.instances[0].name == "vrf1"
+ - gathered.gathered.instances[0].table_id == 101
+ - gathered.gathered.instances[0].protocols.bgp.system_as == 65001
+ - gathered.gathered.instances[0].protocols.ospf.parameters.router_id == "10.0.0.1"
+ - gathered.gathered.instances[0].protocols.static.routes[0].dest == "192.168.10.0/24"
+
+ - name: RTT - Modify description and add BGP neighbor
+ vyos.rest.vyos_vrf:
+ config:
+ bind_to_all: true
+ instances:
+ - name: vrf1
+ description: red-updated
+ table_id: 101
+ vni: 501
+ protocols:
+ bgp:
+ system_as: 65001
+ neighbor:
+ - address: 10.0.0.1
+ remote_as: 65002
+ - address: 10.0.0.2
+ remote_as: 65003
+ state: replaced
+
+ - name: RTT - Gather after modify
+ register: gathered2
+ vyos.rest.vyos_vrf:
+ state: gathered
+
+ - assert:
+ that:
+ - gathered2.gathered.instances[0].description == "red-updated"
+ - gathered2.gathered.instances[0].protocols.bgp.neighbor | length == 2
+ - gathered2.gathered.instances[0].protocols.ospf is not defined
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_vrf/vars/main.yaml b/tests/integration/targets/vyos_vrf/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_vrf/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/unit/fixtures/vrf_running.json b/tests/unit/fixtures/vrf_running.json
new file mode 100644
index 0000000..602c292
--- /dev/null
+++ b/tests/unit/fixtures/vrf_running.json
@@ -0,0 +1,42 @@
+{
+ "bind-to-all": {},
+ "name": {
+ "vrf1": {
+ "description": "red",
+ "disable": {},
+ "table": "101",
+ "vni": "501",
+ "protocols": {
+ "bgp": {
+ "system-as": "65001",
+ "neighbor": {
+ "10.0.0.1": { "remote-as": "65002", "description": "peer1" }
+ }
+ },
+ "ospf": {
+ "area": { "0": { "network": ["10.0.0.0/24", "172.16.0.0/24"] } },
+ "parameters": { "router-id": "10.0.0.1" }
+ },
+ "static": {
+ "route": {
+ "192.168.10.0/24": { "next-hop": { "10.0.0.254": {} } }
+ }
+ }
+ }
+ },
+ "vrf2": {
+ "description": "blah2",
+ "disable": {},
+ "table": "102",
+ "vni": "102",
+ "ip": {
+ "disable-forwarding": {},
+ "nht": { "no-resolve-via-default": {} }
+ },
+ "ipv6": {
+ "disable-forwarding": {},
+ "nht": { "no-resolve-via-default": {} }
+ }
+ }
+ }
+}
diff --git a/tests/unit/modules/test_vyos_vrf.py b/tests/unit/modules/test_vyos_vrf.py
new file mode 100644
index 0000000..e17bcd1
--- /dev/null
+++ b/tests/unit/modules/test_vyos_vrf.py
@@ -0,0 +1,369 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import unittest
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_vrf import (
+ _device_to_argspec,
+ _proto_from_device,
+ _proto_to_device,
+ _protocols_from_device,
+ build_commands,
+)
+
+from .base import load_fixture
+
+
+_RAW_HAVE = load_fixture("vrf_running.json")
+
+
+class TestDeviceToArgspec(unittest.TestCase):
+ def setUp(self):
+ self.result = _device_to_argspec(_RAW_HAVE)
+
+ def test_bind_to_all(self):
+ self.assertTrue(self.result["bind_to_all"])
+
+ def test_instances_count(self):
+ self.assertEqual(len(self.result["instances"]), 2)
+
+ def test_vrf1_properties(self):
+ vrf1 = next(i for i in self.result["instances"] if i["name"] == "vrf1")
+ self.assertEqual(vrf1["description"], "red")
+ self.assertTrue(vrf1["disable"])
+ self.assertEqual(vrf1["table_id"], 101)
+ self.assertEqual(vrf1["vni"], 501)
+
+ def test_vrf2_address_family(self):
+ vrf2 = next(i for i in self.result["instances"] if i["name"] == "vrf2")
+ afis = {af["afi"]: af for af in vrf2["address_family"]}
+ self.assertIn("ipv4", afis)
+ self.assertTrue(afis["ipv4"]["disable_forwarding"])
+ self.assertTrue(afis["ipv4"]["nht_no_resolve_via_default"])
+ self.assertIn("ipv6", afis)
+ self.assertTrue(afis["ipv6"]["disable_forwarding"])
+ self.assertTrue(afis["ipv6"]["nht_no_resolve_via_default"])
+
+ def test_empty_input(self):
+ self.assertEqual(_device_to_argspec({}), {})
+ self.assertEqual(_device_to_argspec(None), {})
+
+
+class TestBgpFromDevice(unittest.TestCase):
+ def setUp(self):
+ self.raw = _RAW_HAVE["name"]["vrf1"]["protocols"]["bgp"]
+ self.result = _proto_from_device(self.raw, "bgp")
+
+ def test_system_as(self):
+ self.assertEqual(self.result["system_as"], 65001)
+
+ def test_neighbor_list(self):
+ self.assertEqual(len(self.result["neighbor"]), 1)
+ n = self.result["neighbor"][0]
+ self.assertEqual(n["address"], "10.0.0.1")
+ self.assertEqual(n["remote_as"], 65002)
+ self.assertEqual(n["description"], "peer1")
+
+ def test_empty_input(self):
+ self.assertEqual(_proto_from_device({}, "bgp"), {})
+ self.assertEqual(_proto_from_device(None, "bgp"), {})
+
+
+class TestBgpToDevice(unittest.TestCase):
+ def test_system_as_to_device(self):
+ result = _proto_to_device({"system_as": 65001}, "bgp")
+ self.assertIn("system-as", result)
+ self.assertEqual(result["system-as"], 65001)
+
+ def test_neighbor_to_device(self):
+ result = _proto_to_device(
+ {
+ "system_as": 65001,
+ "neighbor": [{"address": "10.0.0.1", "remote_as": 65002}],
+ },
+ "bgp",
+ )
+ self.assertIn("neighbor", result)
+ self.assertIn("10.0.0.1", result["neighbor"])
+ self.assertEqual(result["neighbor"]["10.0.0.1"]["remote-as"], 65002)
+
+ def test_idempotent(self):
+ want = _proto_from_device(_RAW_HAVE["name"]["vrf1"]["protocols"]["bgp"], "bgp")
+ cmds = build_commands(
+ {"instances": [{"name": "vrf1", "table_id": 101, "protocols": {"bgp": want}}]},
+ _RAW_HAVE,
+ "merged",
+ )
+ bgp_cmds = [c for c in cmds if "bgp" in str(c)]
+ self.assertEqual(bgp_cmds, [])
+
+ def test_add_neighbor(self):
+ want_bgp = {
+ "system_as": 65001,
+ "neighbor": [
+ {"address": "10.0.0.1", "remote_as": 65002, "description": "peer1"},
+ {"address": "10.0.0.2", "remote_as": 65003},
+ ],
+ }
+ cmds = build_commands(
+ {"instances": [{"name": "vrf1", "table_id": 101, "protocols": {"bgp": want_bgp}}]},
+ _RAW_HAVE,
+ "merged",
+ )
+ paths = [c[1] for c in cmds]
+ self.assertIn(
+ [
+ "vrf",
+ "name",
+ "vrf1",
+ "protocols",
+ "bgp",
+ "neighbor",
+ "10.0.0.2",
+ "remote-as",
+ "65003",
+ ],
+ paths,
+ )
+ self.assertNotIn(
+ [
+ "vrf",
+ "name",
+ "vrf1",
+ "protocols",
+ "bgp",
+ "neighbor",
+ "10.0.0.1",
+ "remote-as",
+ "65002",
+ ],
+ paths,
+ )
+
+
+class TestOspfFromDevice(unittest.TestCase):
+ def setUp(self):
+ self.raw = _RAW_HAVE["name"]["vrf1"]["protocols"]["ospf"]
+ self.result = _proto_from_device(self.raw, "ospf")
+
+ def test_areas(self):
+ self.assertEqual(len(self.result["areas"]), 1)
+ area = self.result["areas"][0]
+ self.assertEqual(area["area_id"], "0")
+ self.assertIn("10.0.0.0/24", area["networks"])
+ self.assertIn("172.16.0.0/24", area["networks"])
+
+ def test_parameters(self):
+ self.assertEqual(self.result["parameters"]["router_id"], "10.0.0.1")
+
+ def test_empty_input(self):
+ self.assertEqual(_proto_from_device({}, "ospf"), {})
+
+
+class TestOspfBuildCommands(unittest.TestCase):
+ def test_idempotent(self):
+ want = _proto_from_device(_RAW_HAVE["name"]["vrf1"]["protocols"]["ospf"], "ospf")
+ cmds = build_commands(
+ {"instances": [{"name": "vrf1", "table_id": 101, "protocols": {"ospf": want}}]},
+ _RAW_HAVE,
+ "merged",
+ )
+ ospf_cmds = [c for c in cmds if "ospf" in str(c)]
+ self.assertEqual(ospf_cmds, [])
+
+ def test_add_network(self):
+ want_ospf = {
+ "areas": [
+ {"area_id": "0", "networks": ["10.0.0.0/24", "172.16.0.0/24", "192.168.0.0/24"]},
+ ],
+ }
+ cmds = build_commands(
+ {"instances": [{"name": "vrf1", "table_id": 101, "protocols": {"ospf": want_ospf}}]},
+ _RAW_HAVE,
+ "merged",
+ )
+ paths = [c[1] for c in cmds]
+ self.assertIn(
+ ["vrf", "name", "vrf1", "protocols", "ospf", "area", "0", "network", "192.168.0.0/24"],
+ paths,
+ )
+
+ def test_add_area(self):
+ want_ospf = {
+ "areas": [
+ {"area_id": "0", "networks": ["10.0.0.0/24", "172.16.0.0/24"]},
+ {"area_id": "1", "networks": ["10.1.0.0/24"]},
+ ],
+ }
+ cmds = build_commands(
+ {"instances": [{"name": "vrf1", "table_id": 101, "protocols": {"ospf": want_ospf}}]},
+ _RAW_HAVE,
+ "merged",
+ )
+ paths = [c[1] for c in cmds]
+ self.assertIn(
+ ["vrf", "name", "vrf1", "protocols", "ospf", "area", "1", "network", "10.1.0.0/24"],
+ paths,
+ )
+
+ def test_change_router_id(self):
+ """Regression test: confirmed bug where router_id was missing
+ from _DEVICE_RENAMES. A brand-new router_id happened to work
+ via dict_op's own fallback conversion, and an unchanged value
+ happened to stay idempotent since both sides of the comparison
+ shared the same (wrong) key -- only *changing* an existing
+ router_id actually exposed the corrupted "router_id" (no
+ hyphen) device path, which VyOS would reject."""
+ want_ospf = {
+ "areas": [
+ {"area_id": "0", "networks": ["10.0.0.0/24", "172.16.0.0/24"]},
+ ],
+ "parameters": {"router_id": "10.0.0.99"},
+ }
+ cmds = build_commands(
+ {"instances": [{"name": "vrf1", "table_id": 101, "protocols": {"ospf": want_ospf}}]},
+ _RAW_HAVE,
+ "merged",
+ )
+ self.assertIn(
+ (
+ "set",
+ [
+ "vrf",
+ "name",
+ "vrf1",
+ "protocols",
+ "ospf",
+ "parameters",
+ "router-id",
+ "10.0.0.99",
+ ],
+ ),
+ cmds,
+ )
+ paths = [c[1] for c in cmds]
+ self.assertFalse(
+ any("router_id" in p for p in paths),
+ "router_id (underscore) must never appear in a device path",
+ )
+
+
+class TestStaticFromDevice(unittest.TestCase):
+ def setUp(self):
+ self.raw = _RAW_HAVE["name"]["vrf1"]["protocols"]["static"]
+ self.result = _proto_from_device(self.raw, "static")
+
+ def test_routes(self):
+ self.assertEqual(len(self.result["routes"]), 1)
+ route = self.result["routes"][0]
+ self.assertEqual(route["dest"], "192.168.10.0/24")
+ self.assertIn("10.0.0.254", route["next_hops"])
+
+ def test_empty_input(self):
+ self.assertEqual(_proto_from_device({}, "static"), {})
+
+
+class TestStaticBuildCommands(unittest.TestCase):
+ def test_idempotent(self):
+ want = _proto_from_device(_RAW_HAVE["name"]["vrf1"]["protocols"]["static"], "static")
+ cmds = build_commands(
+ {"instances": [{"name": "vrf1", "table_id": 101, "protocols": {"static": want}}]},
+ _RAW_HAVE,
+ "merged",
+ )
+ static_cmds = [c for c in cmds if "static" in str(c)]
+ self.assertEqual(static_cmds, [])
+
+ def test_add_route(self):
+ want_static = {
+ "routes": [
+ {"dest": "192.168.10.0/24", "next_hops": ["10.0.0.254"]},
+ {"dest": "192.168.20.0/24", "next_hops": ["10.0.0.254"]},
+ ],
+ }
+ cmds = build_commands(
+ {
+ "instances": [
+ {"name": "vrf1", "table_id": 101, "protocols": {"static": want_static}},
+ ],
+ },
+ _RAW_HAVE,
+ "merged",
+ )
+ paths = [c[1] for c in cmds]
+ self.assertIn(
+ [
+ "vrf",
+ "name",
+ "vrf1",
+ "protocols",
+ "static",
+ "route",
+ "192.168.20.0/24",
+ "next-hop",
+ "10.0.0.254",
+ ],
+ paths,
+ )
+
+
+class TestProtocolsFromDevice(unittest.TestCase):
+ def test_all_protocols(self):
+ raw_vrf = _RAW_HAVE["name"]["vrf1"]
+ result = _protocols_from_device(raw_vrf)
+ self.assertIn("bgp", result)
+ self.assertIn("ospf", result)
+ self.assertIn("static", result)
+
+ def test_no_protocols(self):
+ raw_vrf = _RAW_HAVE["name"]["vrf2"]
+ result = _protocols_from_device(raw_vrf)
+ self.assertIsNone(result)
+
+
+class TestBuildCommands(unittest.TestCase):
+ def test_merged_new_vrf(self):
+ config = {"instances": [{"name": "vrf3", "table_id": 200, "vni": 2000}]}
+ cmds = build_commands(config, _RAW_HAVE, "merged")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["vrf", "name", "vrf3", "table", "200"], paths)
+ self.assertIn(["vrf", "name", "vrf3", "vni", "2000"], paths)
+
+ def test_merged_idempotent(self):
+ config = {
+ "bind_to_all": True,
+ "instances": [
+ {"name": "vrf1", "description": "red", "table_id": 101, "vni": 501},
+ ],
+ }
+ cmds = build_commands(config, _RAW_HAVE, "merged")
+ self.assertEqual(cmds, [])
+
+ def test_deleted_specific_vrf(self):
+ config = {"instances": [{"name": "vrf1"}]}
+ cmds = build_commands(config, _RAW_HAVE, "deleted")
+ self.assertIn(("delete", ["vrf", "name", "vrf1"]), cmds)
+ self.assertNotIn(("delete", ["vrf", "name", "vrf2"]), cmds)
+
+ def test_deleted_all(self):
+ cmds = build_commands({}, _RAW_HAVE, "deleted")
+ self.assertIn(("delete", ["vrf"]), cmds)
+
+ def test_overridden_removes_extra_vrf(self):
+ config = {"instances": [{"name": "vrf1", "table_id": 101}]}
+ cmds = build_commands(config, _RAW_HAVE, "overridden")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["vrf", "name", "vrf2"], paths)
+
+ def test_merged_does_not_delete_unreferenced_vrf(self):
+ config = {"instances": [{"name": "vrf1", "table_id": 101}]}
+ cmds = build_commands(config, _RAW_HAVE, "merged")
+ paths = [c[1] for c in cmds]
+ self.assertNotIn(["vrf", "name", "vrf2"], paths)
+
+
+if __name__ == "__main__":
+ unittest.main()