test(openspec): map cloud integration scenarios
This commit is contained in:
@@ -72,7 +72,11 @@ def create_internal_router(
|
||||
request: Request,
|
||||
) -> HeartbeatResponse:
|
||||
authorize_host(request, host_id)
|
||||
_validate_snapshot(pool, host_id=host_id, payload=payload)
|
||||
allow_device_takeover = _validate_snapshot(
|
||||
pool,
|
||||
host_id=host_id,
|
||||
payload=payload,
|
||||
)
|
||||
devices = [
|
||||
Device(
|
||||
id=device.device_id,
|
||||
@@ -82,7 +86,12 @@ def create_internal_router(
|
||||
)
|
||||
for device in payload.devices
|
||||
]
|
||||
pool.sync_host_devices(host_id, devices, address=payload.address)
|
||||
pool.sync_host_devices(
|
||||
host_id,
|
||||
devices,
|
||||
address=payload.address,
|
||||
allow_device_takeover=allow_device_takeover,
|
||||
)
|
||||
return HeartbeatResponse(
|
||||
host_id=host_id,
|
||||
accepted_devices=len(devices),
|
||||
@@ -228,7 +237,7 @@ def _validate_snapshot(
|
||||
*,
|
||||
host_id: str,
|
||||
payload: HeartbeatRequest,
|
||||
) -> None:
|
||||
) -> bool:
|
||||
if payload.host_id != host_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
@@ -244,6 +253,7 @@ def _validate_snapshot(
|
||||
now = utc_now()
|
||||
hosts = {host.host_id: host for host in pool.store.list_hosts()}
|
||||
conflicts: list[str] = []
|
||||
stale_owner_found = False
|
||||
requested_ids = set(device_ids)
|
||||
for device in pool.store.list_devices():
|
||||
if device.device_id not in requested_ids or device.host_id == host_id:
|
||||
@@ -254,8 +264,11 @@ def _validate_snapshot(
|
||||
age_seconds = (now - owner.last_seen_at).total_seconds()
|
||||
if age_seconds <= pool.config.stale_after_seconds:
|
||||
conflicts.append(device.device_id)
|
||||
else:
|
||||
stale_owner_found = True
|
||||
if conflicts:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"device ownership conflict: {sorted(set(conflicts))}",
|
||||
)
|
||||
return stale_owner_found
|
||||
|
||||
@@ -61,6 +61,7 @@ class DevicePool:
|
||||
snapshot: list[Device],
|
||||
*,
|
||||
address: str | None = None,
|
||||
allow_device_takeover: bool = False,
|
||||
) -> None:
|
||||
"""Push a host's current device snapshot into the pool.
|
||||
|
||||
@@ -71,7 +72,14 @@ class DevicePool:
|
||||
now = utc_now()
|
||||
self.store.upsert_host(host_id, address=address, last_seen_at=now)
|
||||
devices = [self._to_pooled(device, host_id, now) for device in snapshot]
|
||||
self.store.replace_host_devices(host_id, devices)
|
||||
if allow_device_takeover:
|
||||
self.store.replace_host_devices(
|
||||
host_id,
|
||||
devices,
|
||||
allow_device_takeover=True,
|
||||
)
|
||||
else:
|
||||
self.store.replace_host_devices(host_id, devices)
|
||||
|
||||
def list_devices(self) -> list[PooledDevice]:
|
||||
devices = self.store.list_devices()
|
||||
@@ -105,7 +113,9 @@ class DevicePool:
|
||||
host_id: str,
|
||||
synced_at: datetime,
|
||||
) -> PooledDevice:
|
||||
raw_status = device.status if device.status in _HOST_REPORTED_STATUSES else "idle"
|
||||
raw_status = (
|
||||
device.status if device.status in _HOST_REPORTED_STATUSES else "idle"
|
||||
)
|
||||
tags = list(device.capability_tags or [])
|
||||
return PooledDevice(
|
||||
device_id=device.id,
|
||||
|
||||
@@ -58,6 +58,8 @@ class CloudRepository(Protocol):
|
||||
self,
|
||||
host_id: str,
|
||||
devices: list[PooledDevice],
|
||||
*,
|
||||
allow_device_takeover: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
def list_hosts(self) -> list[HostRegistration]: ...
|
||||
|
||||
@@ -59,11 +59,22 @@ class SQLAlchemyCloudRepository:
|
||||
self,
|
||||
host_id: str,
|
||||
devices: list[Any],
|
||||
*,
|
||||
allow_device_takeover: bool = False,
|
||||
) -> None:
|
||||
with self._sessions.begin() as session:
|
||||
session.execute(
|
||||
delete(PooledDeviceRow).where(PooledDeviceRow.host_id == host_id)
|
||||
)
|
||||
if allow_device_takeover:
|
||||
device_ids = [device.device_id for device in devices]
|
||||
if device_ids:
|
||||
session.execute(
|
||||
delete(PooledDeviceRow).where(
|
||||
PooledDeviceRow.host_id != host_id,
|
||||
PooledDeviceRow.device_id.in_(device_ids),
|
||||
)
|
||||
)
|
||||
session.add_all(
|
||||
[
|
||||
PooledDeviceRow(
|
||||
|
||||
Reference in New Issue
Block a user