"""carp_virtual_ip: one CARP address as a virtual IP, with its holder in the reading. CARP differs from vip-manager and keepalived in what the caller can see. The address is configured on every member, and the interface list of a firewall need not show it at all -- so the interfaces cannot say who holds it. CARP's own state can: the member in MASTER holds it, a BACKUP stands by. That is CARP protocol knowledge, so it lives with the driver that speaks CARP (netOrk #1121), not in napalm-device-types. """ from __future__ import annotations import pytest from napalm_opnsense.virtual_ips import carp_virtual_ip def _entry(**overrides): fields = { "address": "172.22.8.1", "prefix_length": 24, "vhid": "108", "state": "MASTER", "instance": "MGMT", "interface": "vtnet0", } fields.update(overrides) return carp_virtual_ip(**fields) def test_a_master_holds_the_address(): assert _entry() == { "mechanism": "carp", "instance": "MGMT", "address": "172.22.8.1", "prefix_length": 24, "interface": "vtnet0", "group_key": "vhid:108", "running": True, "holds": True, } def test_a_backup_stands_by(): entry = _entry(state="BACKUP") assert entry is not None assert (entry["running"], entry["holds"]) == (True, False) @pytest.mark.parametrize("state", ["INIT", "DISABLED"]) def test_a_member_whose_carp_is_not_up_neither_runs_nor_holds(state): entry = _entry(state=state) assert entry is not None assert (entry["running"], entry["holds"]) == (False, False) @pytest.mark.parametrize("state", [None, "", "SOMETHING-NEW"]) def test_an_unknown_state_says_nothing_about_the_holder(state): """None leaves the holder to the interfaces, as for the other mechanisms.""" entry = _entry(state=state) assert entry is not None assert (entry["running"], entry["holds"]) == (None, None) def test_the_state_is_read_case_blind(): entry = _entry(state=" master ") assert entry is not None assert entry["holds"] is True def test_the_address_is_normalised(): entry = _entry(address="FD00::0001", prefix_length=64) assert entry is not None assert entry["address"] == "fd00::1" def test_an_address_written_with_its_prefix_takes_that_prefix(): entry = _entry(address="10.42.100.1/24", prefix_length=None) assert entry is not None assert (entry["address"], entry["prefix_length"]) == ("10.42.100.1", 24) @pytest.mark.parametrize("address", ["", "not-an-address", "10.0.0.300"]) def test_what_is_no_address_is_no_virtual_ip(address): assert _entry(address=address) is None @pytest.mark.parametrize("prefix", [33, -1]) def test_an_impossible_prefix_is_dropped(prefix): entry = _entry(prefix_length=prefix) assert entry is not None assert entry["prefix_length"] is None @pytest.mark.parametrize("vhid", [None, "", "abc"]) def test_without_a_numeric_vhid_there_is_no_group(vhid): entry = _entry(vhid=vhid) assert entry is not None assert entry["group_key"] is None def test_a_vhid_given_as_a_number_groups_the_same_way(): entry = _entry(vhid=108) assert entry is not None assert entry["group_key"] == "vhid:108" def test_an_empty_instance_falls_back_to_the_vhid(): entry = _entry(instance="") assert entry is not None assert entry["instance"] == "vhid 108"