feat(enroll): opt-in IPvlan per-agent for Wi-Fi-bridged VMs
Wi-Fi APs bind one MAC per associated station, so VirtualBox/VMware guests bridged over Wi-Fi rotate the VM's DHCP lease when Docker's macvlan starts emitting container-MAC frames through the vNIC. Adds a `use_ipvlan` toggle on the Agent Enrollment tab (mirrors the updater daemon checkbox): flips the flag on SwarmHost, bakes `ipvlan=true` into the agent's decnet.ini, and `_worker_config` forces ipvlan=True on the per-host shard at dispatch. Safe no-op on wired/bare-metal agents.
This commit is contained in:
@@ -154,6 +154,28 @@ async def test_deploy_automode_resets_stale_host_uuid(client, auth_token, monkey
|
||||
await repo.set_state("deployment", None)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_deploy_automode_flips_ipvlan_for_opted_in_host(client, auth_token, monkeypatch):
|
||||
"""A host enrolled with use_ipvlan=True must receive a DecnetConfig with
|
||||
ipvlan=True in its shard — sharding is per-host, so _worker_config flips it."""
|
||||
from decnet.web.router.swarm.api_deploy_swarm import _worker_config
|
||||
from decnet.config import DecnetConfig, DeckyConfig
|
||||
|
||||
base = DecnetConfig(
|
||||
mode="swarm", interface="eth0", subnet="192.168.1.0/24", gateway="192.168.1.1",
|
||||
ipvlan=False,
|
||||
deckies=[DeckyConfig(
|
||||
name="decky-1", ip="192.168.1.10", services=["ssh"],
|
||||
distro="debian", base_image="debian:bookworm-slim", hostname="decky-1",
|
||||
host_uuid="h1",
|
||||
)],
|
||||
)
|
||||
opted_in = {"uuid": "h1", "name": "w1", "use_ipvlan": True}
|
||||
opted_out = {"uuid": "h1", "name": "w1", "use_ipvlan": False}
|
||||
assert _worker_config(base, base.deckies, opted_in).ipvlan is True
|
||||
assert _worker_config(base, base.deckies, opted_out).ipvlan is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_deployment_mode_endpoint(client, auth_token, monkeypatch):
|
||||
monkeypatch.setenv("DECNET_MODE", "master")
|
||||
|
||||
@@ -78,6 +78,42 @@ async def test_bundle_urls_use_master_host_not_request_base(client, auth_token):
|
||||
assert "testserver" not in sh
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_use_ipvlan_opt_in_persists_and_bakes_into_ini(client, auth_token):
|
||||
"""use_ipvlan=True must persist on the SwarmHost row AND bake `ipvlan = true`
|
||||
into the agent's decnet.ini so locally-initiated deploys also use IPvlan."""
|
||||
from decnet.web.dependencies import repo
|
||||
|
||||
resp = await _post(client, auth_token, agent_name="ipv-node", use_ipvlan=True)
|
||||
assert resp.status_code == 201
|
||||
host_uuid = resp.json()["host_uuid"]
|
||||
token = resp.json()["token"]
|
||||
|
||||
row = await repo.get_swarm_host_by_uuid(host_uuid)
|
||||
assert row["use_ipvlan"] is True
|
||||
|
||||
tgz = await client.get(f"/api/v1/swarm/enroll-bundle/{token}.tgz")
|
||||
assert tgz.status_code == 200
|
||||
with tarfile.open(fileobj=io.BytesIO(tgz.content), mode="r:gz") as tar:
|
||||
ini = tar.extractfile("etc/decnet/decnet.ini").read().decode()
|
||||
assert "ipvlan = true" in ini
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_use_ipvlan_default_false(client, auth_token):
|
||||
from decnet.web.dependencies import repo
|
||||
|
||||
resp = await _post(client, auth_token, agent_name="macv-node")
|
||||
assert resp.status_code == 201
|
||||
row = await repo.get_swarm_host_by_uuid(resp.json()["host_uuid"])
|
||||
assert row["use_ipvlan"] is False
|
||||
|
||||
tgz = await client.get(f"/api/v1/swarm/enroll-bundle/{resp.json()['token']}.tgz")
|
||||
with tarfile.open(fileobj=io.BytesIO(tgz.content), mode="r:gz") as tar:
|
||||
ini = tar.extractfile("etc/decnet/decnet.ini").read().decode()
|
||||
assert "ipvlan = false" in ini
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_duplicate_agent_name_409(client, auth_token):
|
||||
r1 = await _post(client, auth_token, agent_name="dup-node")
|
||||
|
||||
Reference in New Issue
Block a user