1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/operations/models.py

76 lines
3.5 KiB
Python

"""Long-running operations: the queue, the history and the resources jobs
hold (spec 2026-10-13, sections 1-2)."""
import uuid
from django.conf import settings
from django.db import models
class Operation(models.Model):
QUEUED = "queued"
RUNNING = "running"
CANCELLING = "cancelling"
SUCCEEDED = "succeeded"
FAILED = "failed"
CANCELLED = "cancelled"
UNCERTAIN = "uncertain"
ACTIVE = (QUEUED, RUNNING, CANCELLING)
TERMINAL = (SUCCEEDED, FAILED, CANCELLED, UNCERTAIN)
STATES = [(s, s) for s in ACTIVE + TERMINAL]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
kind = models.CharField(max_length=64, db_index=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL, related_name="operations")
username = models.CharField(max_length=150) # kept when the user is deleted
submission_key = models.CharField(max_length=64)
resource = models.CharField(max_length=255, db_index=True) # the page that shows the job
resource_label = models.CharField(max_length=255)
state = models.CharField(max_length=16, choices=STATES, default=QUEUED, db_index=True)
params = models.JSONField(default=dict)
intent = models.JSONField(default=dict) # plan facts, then what run is about to change
# set with the intent committed right before the first libvirt effect:
# recovery fails a job without it, it never reached libvirt
effects_started = models.BooleanField(default=False)
phase = models.CharField(max_length=255, blank=True)
progress_done = models.BigIntegerField(null=True)
progress_total = models.BigIntegerField(null=True)
result = models.JSONField(default=dict)
error = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
started_at = models.DateTimeField(null=True)
finished_at = models.DateTimeField(null=True)
heartbeat_at = models.DateTimeField(null=True)
worker = models.CharField(max_length=128, blank=True) # identity of the worker that owns it
time_limit = models.PositiveIntegerField(null=True) # seconds, fixed at admission
cancel_requested = models.BooleanField(default=False)
resolved_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL, related_name="+")
resolved_at = models.DateTimeField(null=True)
resolution = models.CharField(max_length=16, blank=True)
resolution_note = models.TextField(blank=True)
class Meta:
ordering = ["-created_at"]
constraints = [models.UniqueConstraint(fields=["user", "submission_key"], name="unique_submission")]
def __str__(self):
return f"{self.kind} {self.id}"
class Claim(models.Model):
SHARED = "shared"
EXCLUSIVE = "exclusive"
operation = models.ForeignKey(Operation, null=True, on_delete=models.CASCADE, related_name="claims")
resource = models.CharField(max_length=255, db_index=True)
mode = models.CharField(max_length=9, choices=[(SHARED, SHARED), (EXCLUSIVE, EXCLUSIVE)])
holder = models.CharField(max_length=128, blank=True) # transient: host:pid:thread
expires_at = models.DateTimeField(null=True) # transient only
created_at = models.DateTimeField(auto_now_add=True)
class WorkerLease(models.Model):
name = models.CharField(primary_key=True, max_length=16, default="worker")
holder = models.CharField(max_length=128)
host = models.CharField(max_length=255)
heartbeat_at = models.DateTimeField()