feat(#2391): btrfs SnapshotAgentSubvolume/DeleteAgentSnapshot priv ops
Adds the first missing piece from #2391's migration-gaps list: a read-only btrfs snapshot priv op so hivectl migrate can freeze a consistent point-in-time copy of an agent's state subvolume for btrfs send, without stopping the live agent. - PrivRequest::SnapshotAgentSubvolume / DeleteAgentSnapshot (hive-sh4re) - hive-priv handlers: btrfs subvolume snapshot -r / delete, sibling dot-prefixed path (<AGENT_STATE_ROOT>/.<agent>.snapshot.<label>) - hive-c0re::priv_client wrappers - hivectl subvol snapshot / delete-snapshot verbs (no agent stop needed — btrfs snapshots are atomic against a live subvolume) Does not yet wire actual btrfs send/receive or the hivectl migrate verb — those stay tracked on #2391 as separate follow-up pieces.
This commit is contained in:
parent
0bfe47458c
commit
7799e0762a
4 changed files with 219 additions and 0 deletions
|
|
@ -620,6 +620,24 @@ enum SubvolCmd {
|
|||
#[arg(long)]
|
||||
yes: bool,
|
||||
},
|
||||
/// Create a read-only snapshot of an agent's state subvolume — the
|
||||
/// first step of the (in-progress) inter-hive migration path, or a
|
||||
/// manual point-in-time backup. Agent must already be a subvolume
|
||||
/// (`subvol upgrade` first). Prints the snapshot's host path.
|
||||
Snapshot {
|
||||
/// Agent name (e.g. `damocles`, `iris`).
|
||||
name: String,
|
||||
/// Snapshot label (`[A-Za-z0-9_.-]`); defaults to a UTC timestamp.
|
||||
#[arg(long)]
|
||||
label: Option<String>,
|
||||
},
|
||||
/// Delete a snapshot created by `subvol snapshot`.
|
||||
DeleteSnapshot {
|
||||
/// Agent name the snapshot belongs to.
|
||||
name: String,
|
||||
/// Snapshot label passed to `subvol snapshot --label`.
|
||||
label: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -693,6 +711,10 @@ async fn main() -> Result<()> {
|
|||
Cmd::Restart { scope, graceful } => restart(&socket, scope.to_scope(), graceful).await,
|
||||
Cmd::Subvol { cmd } => match cmd {
|
||||
SubvolCmd::Upgrade { name, yes } => subvol_upgrade(&socket, &name, yes).await,
|
||||
SubvolCmd::Snapshot { name, label } => subvol_snapshot(&name, label).await,
|
||||
SubvolCmd::DeleteSnapshot { name, label } => {
|
||||
subvol_delete_snapshot(&name, &label).await
|
||||
}
|
||||
},
|
||||
Cmd::Choom {
|
||||
name,
|
||||
|
|
@ -1703,6 +1725,38 @@ async fn subvol_upgrade(socket: &Path, name: &str, yes: bool) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// `subvol snapshot <agent> [--label <label>]` — create a read-only btrfs
|
||||
/// snapshot of an agent's state subvolume. Unlike `upgrade`, this does NOT
|
||||
/// stop the agent: btrfs snapshots are atomic + consistent to take against a
|
||||
/// live subvolume. Default label is a unix-timestamp so repeated calls don't
|
||||
/// collide without the caller having to think of a name.
|
||||
async fn subvol_snapshot(name: &str, label: Option<String>) -> Result<()> {
|
||||
if !agent_exists(name)? {
|
||||
bail!("no agent named {name:?} (no state dir under the agents root)");
|
||||
}
|
||||
let label = label.unwrap_or_else(|| {
|
||||
let secs = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_or(0, |d| d.as_secs());
|
||||
format!("snap-{secs}")
|
||||
});
|
||||
let path = hive_c0re::priv_client::snapshot_agent_subvolume(name, &label)
|
||||
.await
|
||||
.with_context(|| format!("snapshot {name} state subvolume (label {label:?})"))?;
|
||||
println!("{path}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `subvol delete-snapshot <agent> <label>` — remove a snapshot created by
|
||||
/// `subvol snapshot`.
|
||||
async fn subvol_delete_snapshot(name: &str, label: &str) -> Result<()> {
|
||||
hive_c0re::priv_client::delete_agent_snapshot(name, label)
|
||||
.await
|
||||
.with_context(|| format!("delete {name} snapshot (label {label:?})"))?;
|
||||
println!("deleted snapshot {label:?} for {name}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render a hive-wide stop/start response: one `<verb>: <name>` line per
|
||||
/// touched container, then surface any aggregated per-target failure as a
|
||||
/// non-zero exit. `verb` is the past-tense word printed per item
|
||||
|
|
|
|||
Loading…
Reference in a new issue