mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
76 lines
3.5 KiB
Python
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()
|