mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-10 04:06:47 +00:00
112 lines
3 KiB
Python
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])
|