Stand down when the portal revokes this instance

The connector treats close code 4410 from the hub as final: it stops
dialling, keeps cloud.json so a new claim code replaces it, and reports
revoked in /cloud/status, which Settings → Remote access shows as Removed
at the portal. Enrolling again, or a restart, brings the link back. An
older hub still answers 403 and the link keeps retrying as before.
This commit is contained in:
2026-10-15 10:30:38 +02:00
parent 21cfed7d39
commit dd50d7a16a
5 changed files with 112 additions and 11 deletions
+50
View File
@@ -641,3 +641,53 @@ async def test_the_link_sends_its_credential_as_a_header() -> None:
await CloudConnector(Mock())._session(config) # type: ignore[arg-type]
assert seen == {"path": "/api/v1/tunnel/attach", "auth": "Bearer t0k"}
@pytest.mark.anyio
async def test_a_revoked_link_stands_down(enrolled: User) -> None: # noqa: ARG001
"""A deleted instance is told so once, rather than refused forever.
The config stays, so enrolling again is all it takes to come back.
"""
from websockets.asyncio.server import ServerConnection, serve
from fluksio.cloud import connector
dials = 0
async def hub(socket: ServerConnection) -> None:
nonlocal dials
dials += 1
await socket.recv()
await socket.close(code=connector.REVOKED_CLOSE, reason="revoked")
async with serve(hub, "127.0.0.1", 0) as server:
port = next(iter(server.sockets)).getsockname()[1]
config = cloud_config.load()
assert config is not None
cloud_config.save(replace(config, ws_url=f"ws://127.0.0.1:{port}/attach"))
link = connector.CloudConnector(Mock())
await asyncio.wait_for(link.serve_forever(), timeout=5)
assert dials == 1
assert link.status()["revoked"] is True
assert cloud_config.load() is not None
# And the watcher leaves it down until the enrolment changes.
app = Mock()
done = asyncio.get_running_loop().create_future()
done.set_result(None)
app.state = Mock(
cloud_task=done,
cloud_connector=link,
cloud_identity=connector._identity(cloud_config.load()),
)
started: list[object] = []
with (
patch.object(connector, "ENROL_POLL_S", 0.01),
patch.object(connector, "start", lambda one: started.append(one)),
):
watcher = asyncio.create_task(connector.watch_enrolment(app))
await asyncio.sleep(0.05)
watcher.cancel()
assert not started