Name update
This commit is contained in:
+28
-24
@@ -1,16 +1,16 @@
|
||||
//! Queued Deployment queue.
|
||||
//!
|
||||
//! When a client selects "Queued Deployment" at the PXE menu, iPXE POSTs to
|
||||
//! `/api/queue/join` and receives a gate position. It then enters a poll
|
||||
//! `/api/queue/join` and receives a queue position. It then enters a poll
|
||||
//! loop hitting `/api/queue/poll/<id>`; the server holds the request open
|
||||
//! until either (a) the operator assigns an ISO from the WebUI, in which
|
||||
//! case the poll returns an iPXE `chain` URL, or (b) the poll times out
|
||||
//! (iPXE's HTTP client has its own timeout), in which case iPXE re-POSTs.
|
||||
//!
|
||||
//! The WebUI shows the queue (`GET /api/gate`) and issues
|
||||
//! The WebUI shows the queue (`GET /api/queue`) and issues
|
||||
//! `POST /api/queue/assign { iso_id, entry_ids: [...] }` to launch a single
|
||||
//! ISO across many gated clients at once. This is the "horse-race gate"
|
||||
//! UX the user asked for — every horse leaves the line simultaneously.
|
||||
//! ISO across many queued clients at once. Every waiting machine receives
|
||||
//! the assignment without operator visits at the rack.
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -23,11 +23,11 @@ use uuid::Uuid;
|
||||
|
||||
use crate::ClientArch;
|
||||
|
||||
/// Per-gate state visible to the WebUI.
|
||||
/// Per-client queue state visible to the WebUI.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Gate {
|
||||
pub struct QueueEntry {
|
||||
pub id: String,
|
||||
/// 1-based race-gate position — position 1 is whoever got there first.
|
||||
/// 1-based queue position — position 1 is whoever got there first.
|
||||
pub position: u32,
|
||||
pub mac: String,
|
||||
pub ip: Option<IpAddr>,
|
||||
@@ -55,8 +55,8 @@ struct QueueEntryInner {
|
||||
}
|
||||
|
||||
impl QueueEntryInner {
|
||||
fn snapshot(&self) -> Gate {
|
||||
Gate {
|
||||
fn snapshot(&self) -> QueueEntry {
|
||||
QueueEntry {
|
||||
id: self.id.clone(),
|
||||
position: self.position,
|
||||
mac: self.mac.clone(),
|
||||
@@ -80,21 +80,25 @@ impl DeploymentQueue {
|
||||
Arc::new(Self::default())
|
||||
}
|
||||
|
||||
/// Add a client to the gate. Returns the new `Gate` snapshot. If the
|
||||
/// MAC is already queued, the existing gate is returned unchanged —
|
||||
/// Add a client to the queue. Returns the current queue snapshot. If the
|
||||
/// MAC is already queued, the existing entry is returned unchanged —
|
||||
/// retrying iPXE clients don't duplicate their slot.
|
||||
pub fn join(&self, mac: &str, ip: Option<IpAddr>, arch: Option<ClientArch>) -> Gate {
|
||||
pub fn join(&self, mac: &str, ip: Option<IpAddr>, arch: Option<ClientArch>) -> QueueEntry {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let mut guard = self.inner.write();
|
||||
|
||||
if let Some(existing) = guard.values_mut().find(|g| g.mac == mac) {
|
||||
existing.last_poll_at = now;
|
||||
if ip.is_some() { existing.ip = ip; }
|
||||
if arch.is_some() { existing.arch = arch; }
|
||||
if ip.is_some() {
|
||||
existing.ip = ip;
|
||||
}
|
||||
if arch.is_some() {
|
||||
existing.arch = arch;
|
||||
}
|
||||
return existing.snapshot();
|
||||
}
|
||||
|
||||
// Race position = max(position) + 1, or 1 if empty.
|
||||
// Queue position = max(position) + 1, or 1 if empty.
|
||||
let next_pos = guard.values().map(|g| g.position).max().unwrap_or(0) + 1;
|
||||
let id = Uuid::new_v4().to_string();
|
||||
let inner = QueueEntryInner {
|
||||
@@ -113,24 +117,24 @@ impl DeploymentQueue {
|
||||
snap
|
||||
}
|
||||
|
||||
/// Look up the `Notify` primitive for a given gate id, for long-polling.
|
||||
/// Look up the `Notify` primitive for a given queue entry id, for long-polling.
|
||||
#[must_use]
|
||||
pub fn notifier(&self, entry_id: &str) -> Option<Arc<Notify>> {
|
||||
self.inner.read().get(entry_id).map(|g| g.notify.clone())
|
||||
}
|
||||
|
||||
/// Update the last-poll timestamp (keeps the gate's "live" indicator
|
||||
/// Update the last-poll timestamp (keeps the queue's "live" indicator
|
||||
/// fresh in the UI) and return the current snapshot. Returns None if
|
||||
/// the gate was released/expired between requests.
|
||||
pub fn touch(&self, entry_id: &str) -> Option<Gate> {
|
||||
/// the entry was released/expired between requests.
|
||||
pub fn touch(&self, entry_id: &str) -> Option<QueueEntry> {
|
||||
let mut guard = self.inner.write();
|
||||
let g = guard.get_mut(entry_id)?;
|
||||
g.last_poll_at = OffsetDateTime::now_utc();
|
||||
Some(g.snapshot())
|
||||
}
|
||||
|
||||
/// Operator assigns an ISO entry (boot_entry id) to one or more gates.
|
||||
/// Returns the number of gates that were updated. Gates not in the
|
||||
/// Operator assigns an ISO entry (boot_entry id) to one or more clients.
|
||||
/// Returns the number of queue entries that were updated. Entries not in the
|
||||
/// queue are silently skipped.
|
||||
pub fn assign(&self, entry_ids: &[String], target: &str) -> usize {
|
||||
let mut guard = self.inner.write();
|
||||
@@ -145,9 +149,9 @@ impl DeploymentQueue {
|
||||
updated
|
||||
}
|
||||
|
||||
/// Remove a gate and return its final snapshot. Called after the client
|
||||
/// Remove a queue entry and return its final snapshot. Called after the client
|
||||
/// has successfully chained onto its assignment.
|
||||
pub fn release(&self, entry_id: &str) -> Option<Gate> {
|
||||
pub fn release(&self, entry_id: &str) -> Option<QueueEntry> {
|
||||
let mut guard = self.inner.write();
|
||||
let g = guard.remove(entry_id)?;
|
||||
g.notify.notify_waiters();
|
||||
@@ -162,7 +166,7 @@ impl DeploymentQueue {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn list(&self) -> Vec<Gate> {
|
||||
pub fn list(&self) -> Vec<QueueEntry> {
|
||||
let guard = self.inner.read();
|
||||
let mut v: Vec<_> = guard.values().map(QueueEntryInner::snapshot).collect();
|
||||
v.sort_by_key(|g| g.position);
|
||||
|
||||
Reference in New Issue
Block a user