1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 04:06:47 +00:00
webvirtcloud/operations/jobs.py

112 lines
3 KiB
Python

"""The contract every kind of job follows (spec section 5), the kinds by
name (filled by each app's jobs.py via register) and the context a running
job reports through."""
from dataclasses import dataclass, field
from . import utils
from .models import LeaseLost, Operation, fenced
KINDS = {}
@dataclass
class Plan:
claims: dict
resource: str
label: str
intent: dict = field(default_factory=dict)
@dataclass
class Outcome:
"""What a run or an inspection decided. RUNNING: libvirt still works;
stalled: its work reports nothing left to do but does not end;
progress: (done, total) of that work, when libvirt tells."""
state: str
result: dict = field(default_factory=dict)
error: str = ""
phase: str = ""
stalled: bool = False
progress: tuple | None = None
class Cancelled(Exception):
"""Raised by a kind's run when a cancel stopped its work."""
def get_kind(name):
return KINDS[name]
def label_of(name):
kind = KINDS.get(name)
return kind.label if kind is not None else name
def register(cls):
KINDS[cls.name] = cls()
return cls
class Kind:
name = ""
label = ""
cancellable = False
time_limit = None
progress_bytes = False # progress counts bytes: the job page shows sizes
def normalize(self, raw):
"""Plain, allowlisted values from the request, without libvirt."""
raise NotImplementedError
def validate(self, user, params):
"""In the request: permissions and libvirt state. Raise to refuse."""
def plan(self, user, params):
raise NotImplementedError
def precheck(self, op, ctx):
"""In the worker, before any effect: permission and state again."""
def run(self, op, ctx):
raise NotImplementedError
def progress(self, op):
return None
def cancel(self, op):
raise NotImplementedError
def reconcile(self, op):
raise NotImplementedError
def log_entry(self, op, state):
"""(host, instance, message) of the Logs row written with each end of
the job (in the same transaction); its host and instance also go on
the row of a resolution. None: no row."""
return None
class Context:
def __init__(self, op, worker):
self.op = op
self.worker = worker
self.effects_started = False
def _write(self, **fields):
with fenced(self.worker.identity):
if not Operation.objects.filter(pk=self.op.pk, worker=self.worker.identity).update(**fields):
raise LeaseLost("the job is no longer this worker's")
for name, value in fields.items():
setattr(self.op, name, value)
def record_intent(self, **values):
"""Commit what run is about to change; the first libvirt effect may
follow only after this returns."""
self._write(intent={**self.op.intent, **values}, effects_started=True)
self.effects_started = True
def set_phase(self, text):
self._write(phase=utils.safe_text(text)[:255])