test: ipv6 end-to-end scenarios

vm01 gains a primary IPv6 next to its IPv4, one standalone IPv6 address
joins the dataset; covers ip_version on both sources, combination with
filters, the IPv6-preferring behaviour of netbox's combined primary IP,
and the invalid value error path.
This commit is contained in:
2026-08-25 14:53:24 +02:00
parent c1f0b4f724
commit 927bcabc97
3 changed files with 90 additions and 11 deletions

View File

@@ -59,6 +59,7 @@ def run_inventory(stack):
source: str = "",
filter_name: str = "",
filter_value: str = "",
ip_version: str = "",
args: tuple[str, ...] = ("--list",),
token: str | None = None,
):
@@ -71,6 +72,7 @@ def run_inventory(stack):
"NETBOX_INVENTORY_SOURCE": source or "ip-addresses",
"NETBOX_INVENTORY_FILTER": filter_name,
"NETBOX_INVENTORY_FILTER_VALUE": filter_value,
"NETBOX_INVENTORY_IP_VERSION": ip_version or "both",
}
result = subprocess.run(
[SCRIPT, *args], capture_output=True, text=True, timeout=120, env=env

View File

@@ -192,13 +192,13 @@ def seed(url: str, admin_token: str) -> dict[str, str]:
},
)
# give vm01 a primary IP: interface -> assigned address -> primary_ip4
# give vm01 primary IPs: interface -> assigned addresses -> primary_ip4/6
eth0 = api.ensure(
"virtualization/interfaces",
{"virtual_machine_id": vm01["id"], "name": "eth0"},
{"virtual_machine": vm01["id"], "name": "eth0"},
)
vm01_ip = api.ensure(
vm01_ip4 = api.ensure(
"ipam/ip-addresses",
{"address": "192.0.2.20/24"},
{
@@ -208,12 +208,27 @@ def seed(url: str, admin_token: str) -> dict[str, str]:
"assigned_object_id": eth0["id"],
},
)
api.patch("virtualization/virtual-machines", vm01["id"], {"primary_ip4": vm01_ip["id"]})
vm01_ip6 = api.ensure(
"ipam/ip-addresses",
{"address": "2001:db8::20/64"},
{
"address": "2001:db8::20/64",
"dns_name": "vm01-v6.example.com",
"assigned_object_type": "virtualization.vminterface",
"assigned_object_id": eth0["id"],
},
)
api.patch(
"virtualization/virtual-machines",
vm01["id"],
{"primary_ip4": vm01_ip4["id"], "primary_ip6": vm01_ip6["id"]},
)
standalone_addresses = [
{"address": "192.0.2.10/24", "dns_name": "web.example.com", "tenant": customer1["id"]},
{"address": "192.0.2.11/24", "vrf": internal["id"]},
{"address": "192.0.2.12/24", "dns_name": "web.example.com"},
{"address": "2001:db8::10/64", "dns_name": "v6.example.com"},
{
"address": "192.0.2.13/24",
"dns_name": "db.other.org",

View File

@@ -5,11 +5,12 @@ End-to-end tests: every source/filter scenario against a live NetBox.
The dataset these expectations rest on is created by seed.py:
tenants customer1/customer2, sites milan/turin, clusters cluster1 (milan) and
cluster2 (turin), VRF internal, VMs vm01 (cluster1, customer1, primary IP
192.0.2.20 named vm01.example.com), vm02 (cluster1, customer2, no primary IP)
and vm03.example.com (cluster2), plus standalone addresses 192.0.2.10
(web.example.com, customer1), 192.0.2.11 (unnamed, internal), 192.0.2.12
(web.example.com duplicate) and 192.0.2.13 (db.other.org, customer2, internal).
cluster2 (turin), VRF internal, VMs vm01 (cluster1, customer1, primary IPs
192.0.2.20 vm01.example.com and 2001:db8::20 vm01-v6.example.com), vm02
(cluster1, customer2, no primary IP) and vm03.example.com (cluster2), plus
standalone addresses 192.0.2.10 (web.example.com, customer1), 192.0.2.11
(unnamed, internal), 192.0.2.12 (web.example.com duplicate), 2001:db8::10
(v6.example.com) and 192.0.2.13 (db.other.org, customer2, internal).
"""
@@ -32,6 +33,8 @@ class TestIpAddressSource:
"192.0.2.12",
"db.other.org",
"vm01.example.com",
"v6.example.com",
"vm01-v6.example.com",
}
web = hostvars(result)["web.example.com"]
assert web["ansible_host"] == "192.0.2.10"
@@ -48,7 +51,13 @@ class TestIpAddressSource:
def test_named_filter_deduplicates(self, run_inventory):
result = run_inventory(source="ip-addresses", filter_name="named")
assert result.returncode == 0, result.stderr
assert hosts(result) == {"web.example.com", "db.other.org", "vm01.example.com"}
assert hosts(result) == {
"web.example.com",
"db.other.org",
"vm01.example.com",
"v6.example.com",
"vm01-v6.example.com",
}
def test_named_false_keeps_unnamed(self, run_inventory):
result = run_inventory(source="ip-addresses", filter_name="named", filter_value="false")
@@ -65,7 +74,13 @@ class TestIpAddressSource:
source="ip-addresses", filter_name="domain", filter_value="example.com"
)
assert result.returncode == 0, result.stderr
assert hosts(result) == {"web.example.com", "192.0.2.12", "vm01.example.com"}
assert hosts(result) == {
"web.example.com",
"192.0.2.12",
"vm01.example.com",
"v6.example.com",
"vm01-v6.example.com",
}
def test_domain_filter_other_domain(self, run_inventory):
result = run_inventory(
@@ -74,6 +89,33 @@ class TestIpAddressSource:
assert result.returncode == 0, result.stderr
assert hosts(result) == {"db.other.org"}
def test_ip_version_v4(self, run_inventory):
result = run_inventory(source="ip-addresses", ip_version="v4")
assert result.returncode == 0, result.stderr
assert hosts(result) == {
"web.example.com",
"192.0.2.11",
"192.0.2.12",
"db.other.org",
"vm01.example.com",
}
def test_ip_version_v6(self, run_inventory):
result = run_inventory(source="ip-addresses", ip_version="v6")
assert result.returncode == 0, result.stderr
assert hosts(result) == {"v6.example.com", "vm01-v6.example.com"}
assert hostvars(result)["v6.example.com"]["ansible_host"] == "2001:db8::10"
def test_ip_version_combines_with_filters(self, run_inventory):
result = run_inventory(
source="ip-addresses",
filter_name="domain",
filter_value="example.com",
ip_version="v4",
)
assert result.returncode == 0, result.stderr
assert hosts(result) == {"web.example.com", "192.0.2.12", "vm01.example.com"}
class TestVirtualMachineSource:
def test_all_virtual_machines(self, run_inventory):
@@ -81,7 +123,8 @@ class TestVirtualMachineSource:
assert result.returncode == 0, result.stderr
assert hosts(result) == {"vm01", "vm02", "vm03.example.com"}
vm01 = hostvars(result)["vm01"]
assert vm01["ansible_host"] == "192.0.2.20"
# with both families set, NetBox's primary_ip prefers IPv6 by default
assert vm01["ansible_host"] == "2001:db8::20"
assert vm01["netbox_cluster"] == "cluster1"
assert vm01["netbox_site"] == "milan"
assert vm01["netbox_tenant"] == "customer1"
@@ -115,6 +158,20 @@ class TestVirtualMachineSource:
assert result.returncode == 0, result.stderr
assert hosts(result) == {"vm03.example.com"}
def test_ip_version_v4_uses_primary_ip4(self, run_inventory):
result = run_inventory(source="virtual-machines", ip_version="v4")
assert result.returncode == 0, result.stderr
assert hosts(result) == {"vm01", "vm02", "vm03.example.com"}
assert hostvars(result)["vm01"]["ansible_host"] == "192.0.2.20"
def test_ip_version_v6_uses_primary_ip6(self, run_inventory):
result = run_inventory(source="virtual-machines", ip_version="v6")
assert result.returncode == 0, result.stderr
assert hostvars(result)["vm01"]["ansible_host"] == "2001:db8::20"
# VMs without a primary IP of that family keep no ansible_host
assert "ansible_host" not in hostvars(result)["vm02"]
assert "ansible_host" not in hostvars(result)["vm03.example.com"]
class TestErrorScenarios:
def test_unknown_tenant_name(self, run_inventory):
@@ -144,6 +201,11 @@ class TestErrorScenarios:
assert result.returncode == 1
assert "requires a value" in result.stderr
def test_invalid_ip_version(self, run_inventory):
result = run_inventory(source="ip-addresses", ip_version="ipv4")
assert result.returncode == 1
assert "Unknown ip_version" in result.stderr
def test_invalid_token(self, run_inventory):
result = run_inventory(source="ip-addresses", token="wrong-token")
assert result.returncode == 1