sleet 0.1.0

A fleet manager for SlateDB databases
Documentation
//! Response types for one-shot subcommands run with `--format json`.
//!
//! These structs are the source of truth for `schema/cli.schema.json`;
//! `tests/schema_sync.rs` regenerates it and fails if the two drift.
//! Text rendering lives in `crate::render`.

use schemars::JsonSchema;
use serde::Serialize;

use crate::config::{HumanDuration, Service};

/// The subcommand response JSON Schema, pretty-printed.
pub fn schema_json() -> String {
    crate::schema_pretty::<Response>()
}

/// A response from any subcommand run with `--format json`, one variant
/// per command. Exists to generate the single response schema: each
/// command's response is a named definition under `$defs`, so consumers
/// validate against e.g. `#/$defs/StatusResponse`.
#[derive(Clone, Debug, Serialize, JsonSchema)]
#[serde(untagged)]
#[schemars(title = "sleet response")]
pub enum Response {
    /// `sleet status`.
    Status(StatusResponse),
    /// `sleet register`.
    Register(RegisterResponse),
    /// `sleet mirror sync`.
    MirrorSync(MirrorSyncResponse),
    /// `sleet mirror restore`.
    MirrorRestore(MirrorRestoreResponse),
}

/// The `sleet status` response, derived from the fleet root: node
/// liveness, roles, and versions from `nodes/`, registered databases
/// from `dbs/`, and placement by computing the same rendezvous ranking
/// the nodes do.
#[derive(Clone, Debug, Serialize, JsonSchema)]
#[schemars(title = "sleet status response")]
pub struct StatusResponse {
    /// Every fleet member with a heartbeat object.
    pub nodes: Vec<NodeStatus>,

    /// Registered databases and their service placement.
    pub databases: Vec<DatabaseStatus>,

    /// Per-target mirror lag; present only with `sleet status
    /// --mirrors`.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub mirrors: Vec<MirrorStatus>,

    /// Fleet-level problems: registry entries that alias the same
    /// database, services no live node offers, and the like.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub warnings: Vec<String>,
}

/// One `(database, target)` mirror's lag, from the source and
/// destination heads.
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct MirrorStatus {
    /// The source database's canonical URL.
    pub database: String,

    /// The target's name.
    pub target: String,

    /// The destination root the target maps this database to.
    pub destination: String,

    /// The source's latest manifest id.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_manifest_id: Option<u64>,

    /// The destination's latest manifest id (the watermark).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dest_manifest_id: Option<u64>,

    /// Manifests the destination is behind.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub manifests_behind: Option<u64>,

    /// WAL ids the destination is behind.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub wal_behind: Option<u64>,

    /// Estimated seconds of lag: source and target sequence numbers
    /// mapped through the source's sequence tracker.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub seconds_behind: Option<u64>,

    /// Why lag could not be read, if it could not.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

/// One fleet member.
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct NodeStatus {
    /// The node's id, from its heartbeat object name.
    pub node_id: String,

    /// Whether the heartbeat is younger than `heartbeat_timeout`.
    pub live: bool,

    /// Age of the heartbeat object.
    pub heartbeat_age: HumanDuration,

    /// Services the node offers, from its heartbeat object name.
    pub services: Vec<Service>,

    /// The sleet version the node runs, from the heartbeat body; absent
    /// if the body was unreadable.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sleet_version: Option<String>,

    /// The slatedb version the node runs, from the heartbeat body;
    /// absent if the body was unreadable.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slatedb_version: Option<String>,
}

/// One registered database and its service placement.
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct DatabaseStatus {
    /// The database's canonical URL.
    pub url: String,

    /// Placement of each configured service.
    pub services: Vec<ServicePlacement>,

    /// Compaction queue depth from `.compactions`; present only with
    /// `sleet status --compactions`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub queue: Option<QueueStatus>,
}

/// Compaction queue depth for one database.
#[derive(Clone, Copy, Debug, Serialize, JsonSchema)]
pub struct QueueStatus {
    /// Jobs waiting for a worker.
    pub claimable: u64,
    /// Jobs a worker is executing.
    pub running: u64,
}

/// Where one database service runs: the top of the service's rendezvous
/// ranking. One node for `gc` and `compactor-coordinator`, the top
/// `count` nodes for `compaction-workers`. Empty means no live node
/// offers the service.
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub struct ServicePlacement {
    /// The placed service.
    pub service: Service,

    /// The owning nodes, best-ranked first.
    pub nodes: Vec<String>,
}

/// The `sleet register` response.
#[derive(Clone, Debug, Serialize, JsonSchema)]
#[schemars(title = "sleet register response")]
pub struct RegisterResponse {
    /// The canonicalized database URL.
    pub url: String,

    /// The registry object written, relative to the fleet root.
    pub file: String,

    /// False if the database was already registered.
    pub created: bool,
}

/// The `sleet mirror sync` response: one pass, plus the prune that
/// follows it when retention is set.
#[derive(Clone, Debug, Serialize, JsonSchema)]
#[schemars(title = "sleet mirror sync response")]
pub struct MirrorSyncResponse {
    /// The source database's canonical URL.
    pub database: String,

    /// The target's name.
    pub target: String,

    /// The destination root.
    pub destination: String,

    /// The manifest id the destination head ended at.
    pub head: u64,

    /// False when the destination was already at the source's head.
    pub committed: bool,

    /// Manifests written to the destination.
    pub manifests_committed: u64,

    /// Data objects copied.
    pub objects_copied: u64,

    /// Data bytes copied; zero for the rclone copier.
    pub bytes_copied: u64,

    /// Manifests the prune deleted; zero without retention.
    pub pruned_manifests: u64,

    /// Data objects the prune deleted; zero without retention.
    pub pruned_objects: u64,
}

/// The `sleet mirror restore` response.
#[derive(Clone, Debug, Serialize, JsonSchema)]
#[schemars(title = "sleet mirror restore response")]
pub struct MirrorRestoreResponse {
    /// The backup root restored from.
    pub backup: String,

    /// The destination root restored into.
    pub destination: String,

    /// The restore point committed as the destination's head.
    pub manifest_id: u64,

    /// Manifests committed.
    pub manifests_committed: u64,

    /// Data objects copied.
    pub objects_copied: u64,

    /// Data bytes copied.
    pub bytes_copied: u64,
}