compute
The compute: parent of the pod and the patient-data API.
Compute is a library object hosted by whichever process is at the top of
the tree -- the orchestrator on the desktop, bitfount run_compute in Docker.
It owns the compute's identity, its registration with the hub, its TLS
certificate, and the lifecycles of its two children. The pod and the API are
siblings under it; neither knows about the other (ADR 0011).
This module must not import from bitfount.federated: that package's
top-level import pulls in the pod, which imports the patient API. Anything
that needs the pod belongs in the host, handed in as a ChildSpec.
First implementation: a child that exits is reported in status() and not
restarted. Restart policy is a later change.
Module
Global variables
POD_INSTANCE_ID_ENV_VAR : Final[str]- The environment variable carrying a pod child's identity to it, which the pod reads asconfig.settings.private.pod_instance_idand reports on its control server's/health._BITFOUNT_-prefixed so the settings read it as private. Minted fresh for every pod child the compute starts, so a host can tell its own live pod from one a previous generation left behind.
REGISTRATION_INTERVAL_SECONDS : Final[float]- How often the compute re-registers with the hub and checks its certificate. Fast enough for an IP change to reach DNS within a minute; deliberately not the 10-second "dataset online" signal, which stays with the pod.
Classes
ChildStatus
class ChildStatus( started: bool, alive: bool, exit_code: int | None, pid: int | None = None, instance_id: str | None = None,):Whether a child is running, and how it ended if not.
Arguments
started: Whether the child has been started since the last stop.alive: Whether it is running now.exit_code: How it ended, if it has.pid: Its process id, if it was started.instance_id: The identity it was handed, for the pod child only; seePOD_INSTANCE_ID_ENV_VAR.
Variables
- static
alive : bool
- static
exit_code : int | None
- static
instance_id : str | None
- static
pid : int | None
- static
started : bool
Compute
class Compute( *, identity: ComputeIdentity, hub: BitfountHub, pod_child: ChildSpec, api_child: ChildSpec | None = None, registration_interval_seconds: float = 60.0,):Owns the compute's identity, registration, certificate and children.
The key pair is loaded, and created on a first run, here: before either child starts, so neither child ever creates it. Creation is first-writer-wins in any case.
Arguments
identity: Who this compute is and where its files live.hub: The compute's authenticated hub session.pod_child: What to run as the pod. The only child a host describes: the desktop's pod takes the app's request and a token queue, Docker's a config path.api_child: What to run as the patient-data API. Defaults to serving it foridentity, which is all any host has needed.registration_interval_seconds: How often to re-register and check the certificate.
Methods
pod_status
def pod_status(self) ‑> ChildStatus:The pod child alone, cheaply.
status also reports whether a servable certificate is on disk, which
reads and parses it; a host polling the pod every few seconds wants
neither. This reads only the process handle.
start
def start(self, *, pod_spec: ChildSpec | None = None) ‑> None:Bring the compute up: registration, certificate, API, then pod.
The API is started before the pod so nothing about the pod's startup -- however long its datasets take -- delays it. Neither child's failure to start stops the other.
Arguments
pod_spec: A replacement for what runs as the pod; seestart_pod.
start_pod
def start_pod(self, spec: ChildSpec | None = None) ‑> None:Start the pod child if it is not running.
Every pod child started here is handed a freshly minted identity (see
POD_INSTANCE_ID_ENV_VAR), reported as status().pod.instance_id. A
host that needs to know whether a call actually restarted the pod
compares that before and after; a running pod keeps its id.
Arguments
spec: A replacement for what runs as the pod, used for this start and every later one. A host whose pod configuration arrives with each start request (the desktop app posts it) passes the fresh one here; a running pod is left alone either way.
status
def status(self) ‑> ComputeStatus:A snapshot of the compute and its children.
stop
def stop(self) ‑> None:Take the compute down: both children, then registration.
stop_pod
def stop_pod(self) ‑> None:Stop the pod child if it is running. The API is untouched.
Discards the handle once stopped, so status() reports the pod as
never started rather than as crashed with its SIGTERM exit code --
a poll after a deliberate stop must read the same as one that never
found a pod, not as one that found a dead one.
tick
def tick(self) ‑> None:One registration + certificate pass. Public for hosts and tests.
ComputeStatus
class ComputeStatus( instance_id: str, compute_id: str | None, domain: str | None, registered: bool, certificate_present: bool, pod: ChildStatus, patient_api: ChildStatus,):A snapshot of the compute and its children.
Variables
- static
certificate_present : bool
- static
compute_id : str | None
- static
domain : str | None
- static
instance_id : str
- static
patient_api : ChildStatus
- static
pod : ChildStatus
- static
registered : bool