diff options
Diffstat (limited to 'tests')
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() |
