This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only supervisor/worker preemption architecture model."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
MAX_COPY_SIZE = 128 * 1024 * 1024
|
||||
MAX_TICKS = 256
|
||||
MAX_EVENTS = 16
|
||||
|
||||
CREATE_WORKER = "CREATE_WORKER"
|
||||
VERIFY_WORKER = "VERIFY_WORKER"
|
||||
START_COPY = "START_COPY"
|
||||
RECEIVE_RESULT = "RECEIVE_RESULT"
|
||||
DEADLINE = "DEADLINE"
|
||||
TERMINATE_WORKER = "TERMINATE_WORKER"
|
||||
REAP_WORKER = "REAP_WORKER"
|
||||
TERMINATE_CHILD = "TERMINATE_CHILD"
|
||||
REAP_CHILD = "REAP_CHILD"
|
||||
OK = "OK"
|
||||
ERROR = "ERROR"
|
||||
SUCCESS = "SUCCESS"
|
||||
COPY_ERROR = "COPY_ERROR"
|
||||
RESTORE_ERROR = "RESTORE_ERROR"
|
||||
|
||||
OPERATIONS = {
|
||||
CREATE_WORKER, VERIFY_WORKER, START_COPY, RECEIVE_RESULT, DEADLINE,
|
||||
TERMINATE_WORKER, REAP_WORKER, TERMINATE_CHILD, REAP_CHILD,
|
||||
}
|
||||
|
||||
|
||||
class SupervisorError(RuntimeError):
|
||||
"""The synthetic supervisor cannot prove containment."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WorkerPlan:
|
||||
worker_id: int
|
||||
child_id: int
|
||||
copy_length: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
for value in (self.worker_id, self.child_id, self.copy_length):
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value <= 0:
|
||||
raise SupervisorError("worker plan value is invalid")
|
||||
if self.worker_id == self.child_id or self.copy_length > MAX_COPY_SIZE:
|
||||
raise SupervisorError("worker plan identity or size is invalid")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WorkerResult:
|
||||
worker_id: int
|
||||
status: str
|
||||
copied: int
|
||||
restore_failure_bits: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.worker_id, int) or isinstance(self.worker_id, bool) \
|
||||
or self.worker_id <= 0 or self.status not in {
|
||||
SUCCESS, COPY_ERROR, RESTORE_ERROR}:
|
||||
raise SupervisorError("worker result identity/status is invalid")
|
||||
if not isinstance(self.copied, int) or isinstance(self.copied, bool) \
|
||||
or self.copied < 0 or not isinstance(self.restore_failure_bits, int) \
|
||||
or isinstance(self.restore_failure_bits, bool) \
|
||||
or not 0 <= self.restore_failure_bits <= 3:
|
||||
raise SupervisorError("worker result fields are invalid")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FakeSupervisorEvent:
|
||||
operation: str
|
||||
result: str
|
||||
worker_result: WorkerResult | None = None
|
||||
ticks: int = 1
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.operation not in OPERATIONS or self.result not in {OK, ERROR}:
|
||||
raise SupervisorError("supervisor event is invalid")
|
||||
if (self.operation == RECEIVE_RESULT) != (self.worker_result is not None):
|
||||
raise SupervisorError("worker result binding is invalid")
|
||||
if not isinstance(self.ticks, int) or isinstance(self.ticks, bool) \
|
||||
or not 1 <= self.ticks <= MAX_TICKS:
|
||||
raise SupervisorError("supervisor event ticks are invalid")
|
||||
|
||||
|
||||
class FakeSupervisorFacade:
|
||||
"""Exact fake operations with no process, signal, clock or IPC access."""
|
||||
|
||||
def __init__(self, events: tuple[FakeSupervisorEvent, ...]) -> None:
|
||||
if not isinstance(events, tuple) or not 1 <= len(events) <= MAX_EVENTS \
|
||||
or any(type(item) is not FakeSupervisorEvent for item in events):
|
||||
raise SupervisorError("supervisor script is invalid")
|
||||
self._events = list(events)
|
||||
self.ticks = 0
|
||||
|
||||
def invoke(self, operation: str, terminal: bool = False) -> FakeSupervisorEvent:
|
||||
if not self._events:
|
||||
raise SupervisorError("supervisor script is exhausted")
|
||||
event = self._events.pop(0)
|
||||
if event.operation != operation:
|
||||
raise SupervisorError("supervisor event ordering differs")
|
||||
if not terminal and self.ticks + event.ticks > MAX_TICKS:
|
||||
raise SupervisorError("supervisor deadline precedes operation")
|
||||
self.ticks += event.ticks
|
||||
return event
|
||||
|
||||
@property
|
||||
def remaining(self) -> int:
|
||||
return len(self._events)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SupervisorOutcome:
|
||||
classification: str
|
||||
success: bool
|
||||
service_alive: bool
|
||||
worker_alive: bool
|
||||
child_alive: bool
|
||||
copied: int
|
||||
automatic_restart: bool = False
|
||||
target_action_performed: bool = False
|
||||
firmware_behavior_proven: bool = False
|
||||
|
||||
|
||||
def _terminal(facade: FakeSupervisorFacade, operation: str) -> None:
|
||||
if facade.invoke(operation, terminal=True).result != OK:
|
||||
raise SupervisorError(f"terminal operation failed: {operation}")
|
||||
|
||||
|
||||
def run_supervisor(plan: WorkerPlan, facade: FakeSupervisorFacade,
|
||||
deadline_first: bool = False) -> SupervisorOutcome:
|
||||
"""Run one synthetic, non-restarting worker attempt."""
|
||||
if type(plan) is not WorkerPlan or type(facade) is not FakeSupervisorFacade \
|
||||
or type(deadline_first) is not bool:
|
||||
raise SupervisorError("supervisor model boundary is invalid")
|
||||
worker = started = False
|
||||
copied = 0
|
||||
failed = deadline = False
|
||||
|
||||
if facade.invoke(CREATE_WORKER).result != OK:
|
||||
failed = True
|
||||
else:
|
||||
worker = True
|
||||
if facade.invoke(VERIFY_WORKER).result != OK:
|
||||
failed = True
|
||||
elif facade.invoke(START_COPY).result != OK:
|
||||
started = True
|
||||
failed = True
|
||||
else:
|
||||
started = True
|
||||
if deadline_first:
|
||||
if facade.invoke(DEADLINE).result != OK:
|
||||
raise SupervisorError("deadline event failed")
|
||||
deadline = failed = True
|
||||
else:
|
||||
event = facade.invoke(RECEIVE_RESULT)
|
||||
result = event.worker_result
|
||||
if event.result != OK or result is None:
|
||||
failed = True
|
||||
else:
|
||||
copied = result.copied
|
||||
valid = (result.worker_id == plan.worker_id and
|
||||
result.status == SUCCESS and
|
||||
result.copied == plan.copy_length and
|
||||
result.restore_failure_bits == 0)
|
||||
failed = not valid
|
||||
|
||||
if worker:
|
||||
if failed:
|
||||
_terminal(facade, TERMINATE_WORKER)
|
||||
_terminal(facade, REAP_WORKER)
|
||||
worker = False
|
||||
if failed and started:
|
||||
_terminal(facade, TERMINATE_CHILD)
|
||||
_terminal(facade, REAP_CHILD)
|
||||
if facade.remaining:
|
||||
raise SupervisorError("supervisor script has unused operations")
|
||||
|
||||
if not failed:
|
||||
return SupervisorOutcome("OFFLINE_WORKER_RESULT_ACCEPTED", True, True,
|
||||
False, True, copied)
|
||||
classification = ("OFFLINE_DEADLINE_CONTAINED" if deadline else
|
||||
"OFFLINE_WORKER_FAILURE_CONTAINED")
|
||||
return SupervisorOutcome(classification, False, True, False,
|
||||
not started, copied)
|
||||
Reference in New Issue
Block a user