container stats: bound hive-c0re's wait for the disk measurement, no total for an agent with no paths
hive-c0re waited on hive-priv's answer with no timeout, so a hung hive-priv would stall the disk sampler indefinitely. The wait is now hive-priv's own 10 min bound plus a minute (the bound is shared as MEASURE_AGENT_DISK_TIMEOUT); a timeout is an error, so the agent shows unknown. An agent with none of state/, work/ or the rootfs has no measurable size. hive-priv now answers with an error for it instead of a total of 0, which read as a real measurement.
This commit is contained in:
parent
98ccc31e2b
commit
960cf9ce30
3 changed files with 131 additions and 36 deletions
|
|
@ -8,8 +8,9 @@
|
|||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use hive_priv_sock::{
|
||||
BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery, NetworkIsolation,
|
||||
PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream,
|
||||
BindMount, CredentialMount, InfraAction, InfraContainer, JournalQuery,
|
||||
MEASURE_AGENT_DISK_TIMEOUT, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse,
|
||||
PrivStream,
|
||||
};
|
||||
use std::os::fd::{AsRawFd as _, OwnedFd, RawFd};
|
||||
|
||||
|
|
@ -429,25 +430,44 @@ pub async fn read_subvolume_usage(agent_name: &str) -> Result<(u64, u64)> {
|
|||
parse_qgroup_usage(&stdout).with_context(|| format!("parse qgroup usage for {agent_name}"))
|
||||
}
|
||||
|
||||
/// How long hive-c0re waits for hive-priv's answer to a disk measurement:
|
||||
/// hive-priv's own bound plus a margin, so a hung hive-priv can't stall the
|
||||
/// caller while a slow walk still arrives as hive-priv's timeout error.
|
||||
const MEASURE_AGENT_DISK_WAIT: std::time::Duration =
|
||||
MEASURE_AGENT_DISK_TIMEOUT.saturating_add(std::time::Duration::from_mins(1));
|
||||
|
||||
/// Measure an agent's disk usage in bytes (`state/` + `work/` + container
|
||||
/// rootfs, one bounded `du` run by hive-priv as root).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the hive-priv call fails, the `du` walk times out or
|
||||
/// exits non-zero, or the returned total isn't a number.
|
||||
/// Returns an error if the hive-priv call fails or doesn't answer within
|
||||
/// [`MEASURE_AGENT_DISK_WAIT`], the `du` walk times out or exits non-zero,
|
||||
/// the agent has none of the paths, or the returned total isn't a number.
|
||||
pub async fn measure_agent_disk(agent_name: &str) -> Result<u64> {
|
||||
let (stdout, _) = check(
|
||||
let resp = within(
|
||||
MEASURE_AGENT_DISK_WAIT,
|
||||
call(&PrivRequest::MeasureAgentDisk {
|
||||
agent_name: agent_name.to_owned(),
|
||||
})
|
||||
.await?,
|
||||
)?;
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let (stdout, _) = check(resp)?;
|
||||
stdout
|
||||
.trim()
|
||||
.parse()
|
||||
.with_context(|| format!("parse disk usage for {agent_name}: {stdout:?}"))
|
||||
}
|
||||
|
||||
/// Await `fut`, failing if it takes longer than `limit`.
|
||||
async fn within<T>(
|
||||
limit: std::time::Duration,
|
||||
fut: impl std::future::Future<Output = Result<T>>,
|
||||
) -> Result<T> {
|
||||
tokio::time::timeout(limit, fut)
|
||||
.await
|
||||
.map_err(|_| anyhow::anyhow!("hive-priv did not answer within {}s", limit.as_secs()))?
|
||||
}
|
||||
|
||||
/// Set or clear a btrfs qgroup size limit on an agent's state subvolume.
|
||||
/// `limit_bytes = None` clears it. Requires quota enabled. Via hive-priv.
|
||||
///
|
||||
|
|
@ -577,3 +597,26 @@ fn ok(resp: PrivResponse) -> Result<()> {
|
|||
check(resp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::within;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_silent_hive_priv_times_out_as_an_error() {
|
||||
let err = within(
|
||||
Duration::from_millis(20),
|
||||
std::future::pending::<Result<u64, _>>(),
|
||||
)
|
||||
.await
|
||||
.expect_err("a call that never answers must fail");
|
||||
assert!(err.to_string().contains("did not answer"), "{err}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_answer_inside_the_limit_passes_through() {
|
||||
let got = within(Duration::from_secs(5), async { Ok(7_u64) }).await;
|
||||
assert_eq!(got.expect("answered in time"), 7);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue