split hivectl main.rs into per-domain modules (#2509)
This commit is contained in:
parent
673aea4e50
commit
fc7720572b
16 changed files with 1922 additions and 1771 deletions
112
hivectl/src/quota.rs
Normal file
112
hivectl/src/quota.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
//! `hivectl quota` — per-agent disk accounting + optional quotas via
|
||||
//! btrfs qgroups. The daemon holds the privileged helper that reads /
|
||||
//! sets qgroups; hivectl relays the request and formats the reply.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
|
||||
use crate::util::daemon_request;
|
||||
|
||||
/// `quota enable` — turn on btrfs qgroup accounting (operator opt-in).
|
||||
pub(crate) async fn quota_enable(socket: &Path) -> Result<()> {
|
||||
daemon_request(socket, hive_host_sock::HostRequest::QuotaEnable, "quota").await
|
||||
}
|
||||
|
||||
/// `quota show [name]` — report per-agent disk usage from btrfs qgroups.
|
||||
/// The daemon resolves the agent set + reads each subvolume's usage (it
|
||||
/// holds the privileged helper); the client just formats the returned rows.
|
||||
pub(crate) async fn quota_show(socket: &Path, name: Option<&str>) -> Result<()> {
|
||||
let resp = crate::client::request(
|
||||
socket,
|
||||
hive_host_sock::HostRequest::QuotaShow {
|
||||
name: name.map(str::to_owned),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
||||
if !resp.ok {
|
||||
// Carries the "btrfs quota not enabled — run `hivectl quota enable`
|
||||
// first" hint when qgroups are off.
|
||||
bail!("{}", resp.error.as_deref().unwrap_or("quota show failed"));
|
||||
}
|
||||
let rows = resp.quota.unwrap_or_default();
|
||||
if rows.is_empty() {
|
||||
println!("no agents with a state dir found");
|
||||
return Ok(());
|
||||
}
|
||||
for row in &rows {
|
||||
match (row.referenced, row.exclusive) {
|
||||
(Some(rfer), Some(excl)) => println!(
|
||||
"{:<12} referenced {:>10} exclusive {:>10}",
|
||||
row.agent,
|
||||
human_bytes(rfer),
|
||||
human_bytes(excl)
|
||||
),
|
||||
// Plain-dir agent (no qgroup) — the daemon set an explanatory note.
|
||||
_ => println!(
|
||||
"{:<12} ({})",
|
||||
row.agent,
|
||||
row.note.as_deref().unwrap_or("no qgroup data")
|
||||
),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `quota limit <name> <size>` — set or clear an agent's disk quota.
|
||||
pub(crate) async fn quota_limit(socket: &Path, name: &str, size: &str) -> Result<()> {
|
||||
let limit = parse_quota_size(size)?;
|
||||
daemon_request(
|
||||
socket,
|
||||
hive_host_sock::HostRequest::QuotaLimit {
|
||||
name: name.to_owned(),
|
||||
limit,
|
||||
},
|
||||
"quota",
|
||||
)
|
||||
.await?;
|
||||
match limit {
|
||||
Some(n) => println!("set {name} quota to {} ({n} bytes)", human_bytes(n)),
|
||||
None => println!("cleared {name} quota"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse a quota size: a byte count, a `K`/`M`/`G`/`T`-suffixed value
|
||||
/// (powers of 1024), or `none` to clear. Rejects overflow + bad input.
|
||||
fn parse_quota_size(s: &str) -> Result<Option<u64>> {
|
||||
let t = s.trim();
|
||||
if t.eq_ignore_ascii_case("none") {
|
||||
return Ok(None);
|
||||
}
|
||||
let (num, mult) = match t.chars().last() {
|
||||
Some('K' | 'k') => (&t[..t.len() - 1], 1024u64),
|
||||
Some('M' | 'm') => (&t[..t.len() - 1], 1024 * 1024),
|
||||
Some('G' | 'g') => (&t[..t.len() - 1], 1024 * 1024 * 1024),
|
||||
Some('T' | 't') => (&t[..t.len() - 1], 1024u64 * 1024 * 1024 * 1024),
|
||||
_ => (t, 1),
|
||||
};
|
||||
let n: u64 = num.trim().parse().with_context(|| {
|
||||
format!("invalid size {s:?} — use a byte count, a K/M/G/T suffix (e.g. 5G), or `none`")
|
||||
})?;
|
||||
n.checked_mul(mult)
|
||||
.map(Some)
|
||||
.with_context(|| format!("size {s:?} overflows u64"))
|
||||
}
|
||||
|
||||
/// Format a byte count as a human-friendly KiB/MiB/GiB/TiB string.
|
||||
#[allow(clippy::cast_precision_loss)] // display-only; precision loss is cosmetic
|
||||
fn human_bytes(n: u64) -> String {
|
||||
const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||
if n < 1024 {
|
||||
return format!("{n} B");
|
||||
}
|
||||
let mut v = n as f64;
|
||||
let mut i = 0;
|
||||
while v >= 1024.0 && i < UNITS.len() - 1 {
|
||||
v /= 1024.0;
|
||||
i += 1;
|
||||
}
|
||||
format!("{v:.1} {}", UNITS[i])
|
||||
}
|
||||
Loading…
Reference in a new issue