Service node: a server kept running on a worker (openpanel V2)
- `service` node type: command, port, worker label, health path, env and
weights (an artifact digest the worker fetches as {weights}). Its flow
starts it; it restarts with backoff, reads degraded while starting or
without a worker and down after three starts that never got healthy, and
publishes http://<worker>:<port> once /health answers 200, "" when it ends.
- Worker protocol 3: a `serve` call, every call in its own process group,
killed when the engine ends the call, the socket drops or the agent gets
SIGTERM. `--service-host` overrides the address the engine sees.
- Services and compiles take no `--parallel` slot, and a flow's detail is
built off the event loop, so a save no longer stalls the engine.
This commit is contained in:
@@ -220,6 +220,11 @@ def _endpoints(controller: FlowController, definition: FlowDef) -> list[Endpoint
|
||||
|
||||
|
||||
def _detail(controller: FlowController, definition: FlowDef) -> FlowDetail:
|
||||
"""The flow and how its nodes are doing. Blocking: call it off the loop.
|
||||
|
||||
A draft is built to report on it, which asks a worker to compile its
|
||||
nodes, and that waits on the socket the loop serves.
|
||||
"""
|
||||
name = definition.name
|
||||
running = {
|
||||
"enabled": controller.is_enabled(name),
|
||||
@@ -534,7 +539,7 @@ async def save_flow(
|
||||
status_code=409,
|
||||
detail={"message": str(exc), "current_version": exc.current},
|
||||
)
|
||||
return _detail(controller, stored)
|
||||
return await run_in_threadpool(_detail, controller, stored)
|
||||
|
||||
|
||||
@router.post("/{name}/publish", response_model=FlowDetail)
|
||||
@@ -561,7 +566,7 @@ async def publish_flow(
|
||||
)
|
||||
_audit("published", name, user)
|
||||
await controller.reload_flow(name)
|
||||
return _detail(controller, published)
|
||||
return await run_in_threadpool(_detail, controller, published)
|
||||
|
||||
|
||||
@router.post("/{name}/discard-draft", response_model=FlowDetail)
|
||||
@@ -581,7 +586,7 @@ async def discard_draft(name: str, controller: FlowControllerDep) -> Any:
|
||||
),
|
||||
)
|
||||
published = await run_in_threadpool(controller.store.discard_draft, name)
|
||||
return _detail(controller, published)
|
||||
return await run_in_threadpool(_detail, controller, published)
|
||||
|
||||
|
||||
@router.delete("/{name}", response_model=Message)
|
||||
@@ -644,7 +649,7 @@ async def rename_flow(
|
||||
# run, so this is cleanup rather than a migration.
|
||||
await run_in_threadpool(controller.forget_flow, name)
|
||||
await controller.reload()
|
||||
return _detail(controller, renamed)
|
||||
return await run_in_threadpool(_detail, controller, renamed)
|
||||
|
||||
|
||||
@router.post("/{name}/duplicate", response_model=FlowDetail)
|
||||
@@ -662,7 +667,7 @@ async def duplicate_flow(
|
||||
copy = await run_in_threadpool(controller.store.duplicate_flow, name)
|
||||
except FlowNotFound:
|
||||
raise HTTPException(status_code=404, detail=f"No flow named '{name}'")
|
||||
return _detail(controller, copy)
|
||||
return await run_in_threadpool(_detail, controller, copy)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -810,7 +815,7 @@ async def share_node(
|
||||
)
|
||||
except LibExists as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc))
|
||||
return _detail(controller, _read_flow(controller, name))
|
||||
return await run_in_threadpool(_detail, controller, _read_flow(controller, name))
|
||||
|
||||
|
||||
@router.post("/{name}/nodes/{node_id}/unshare", response_model=FlowDetail)
|
||||
@@ -825,7 +830,7 @@ async def unshare_node(
|
||||
await run_in_threadpool(controller.store.unshare_node, name, node_id)
|
||||
except LibNotFound as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc))
|
||||
return _detail(controller, _read_flow(controller, name))
|
||||
return await run_in_threadpool(_detail, controller, _read_flow(controller, name))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -841,7 +846,7 @@ async def start_flow(
|
||||
_read_flow(controller, name)
|
||||
await controller.set_enabled(name, True)
|
||||
_audit("started", name, user)
|
||||
return _detail(controller, _read_flow(controller, name))
|
||||
return await run_in_threadpool(_detail, controller, _read_flow(controller, name))
|
||||
|
||||
|
||||
@router.post("/{name}/stop", response_model=FlowDetail)
|
||||
@@ -850,7 +855,7 @@ async def stop_flow(name: str, controller: FlowControllerDep, user: CurrentUser)
|
||||
_read_flow(controller, name)
|
||||
await controller.set_enabled(name, False)
|
||||
_audit("stopped", name, user)
|
||||
return _detail(controller, _read_flow(controller, name))
|
||||
return await run_in_threadpool(_detail, controller, _read_flow(controller, name))
|
||||
|
||||
|
||||
@router.post("/{name}/pause", response_model=Message)
|
||||
|
||||
@@ -337,6 +337,10 @@ async def _attached_session(websocket: WebSocket, claims: dict[str, Any]) -> Non
|
||||
"version": hello.get("version"),
|
||||
},
|
||||
inventory=WorkerInventory.from_hello(hello.get("inventory")),
|
||||
host=str(
|
||||
hello.get("service_host")
|
||||
or (websocket.client.host if websocket.client else "")
|
||||
),
|
||||
)
|
||||
hub.attach(worker)
|
||||
_behind.pop(name, None)
|
||||
|
||||
Reference in New Issue
Block a user