swarm-controller: refuse a cap systemd would ignore; GET creates no bucket
systemd ignores a CPUQuota= or MemoryMax= it can't parse ("Invalid ...,
ignoring") and runs the unit without that limit. A swarm cap replaces
the hive's answer, so such a value left the agent uncapped while the
route, GET and the dashboard all said it was capped. argus on #4268 (R3)
found values the route took that systemd ignores: 12.345%, 400.123%,
0.001%, 17000000T, 99999999999999999999T and 99999999999999999999%.
The validators now accept a subset of what systemd parses, measured
against `systemd-analyze verify` (systemd 260):
- a percentage takes at most two decimals and is above zero, up to 100%
for memory_max and 21474836.47% for cpu_quota (systemd keeps the quota
in hundredths of a percent in a C int, not a u64)
- a size is one parse_size term: digits, an optional fraction, then
nothing, B, K, M, G or T, case-sensitive. The whole part and the
fraction digits fit u64, the whole part (plus one with a fraction)
fits u64 once scaled, and the byte count is at least 1 and below
2^64 - 1, as systemd checks them.
P and E, which systemd reads, stay refused because hive-c0re's validator
refuses them, and one value the hive refuses stops every cap update on
that hive. Values are stored as given: lower-case suffixes and a B
after a multiplier are refused rather than normalized, as systemd
refuses them.
The newline-injection values from the hive's test are in both bad lists
and go through the router in a_cap_with_a_second_line_is_a_400_and_the_caps_stay.
GET reads through a new agent_limits::open that returns None for a
missing bucket, answered as {}, instead of creating the bucket.
This commit is contained in:
parent
fccf2ab892
commit
028ca1bf1f
4 changed files with 259 additions and 69 deletions
|
|
@ -71,6 +71,30 @@ pub async fn open_or_create(
|
|||
.await
|
||||
}
|
||||
|
||||
/// Open the caps bucket for reading, or `None` when nothing has created it
|
||||
/// yet, which reads the same as no caps.
|
||||
///
|
||||
/// **Controller-side**, for reads that must not create the bucket.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`crate::Error::OpenBucket`] for every failure other than the bucket not
|
||||
/// existing.
|
||||
#[cfg(feature = "kv")]
|
||||
pub async fn open(
|
||||
client: &async_nats::Client,
|
||||
) -> Result<Option<async_nats::jetstream::kv::Store>, crate::Error> {
|
||||
let js = async_nats::jetstream::new(client.clone());
|
||||
match js.get_key_value(BUCKET).await {
|
||||
Ok(store) => Ok(Some(store)),
|
||||
Err(e) if crate::wanted::is_bucket_not_found(&e) => Ok(None),
|
||||
Err(source) => Err(crate::Error::OpenBucket {
|
||||
bucket: BUCKET.to_owned(),
|
||||
source,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// What reading one agent's key found.
|
||||
#[cfg(feature = "agent-limits")]
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ pub async fn open_read_only(
|
|||
/// `get_key_value` reports a missing bucket as `GetBucket` wrapping the
|
||||
/// stream lookup's `STREAM_NOT_FOUND`; every other kind is a real failure.
|
||||
#[cfg(feature = "kv")]
|
||||
fn is_bucket_not_found(e: &async_nats::jetstream::context::KeyValueError) -> bool {
|
||||
pub(crate) fn is_bucket_not_found(e: &async_nats::jetstream::context::KeyValueError) -> bool {
|
||||
use async_nats::jetstream::context::{GetStreamError, GetStreamErrorKind, KeyValueErrorKind};
|
||||
|
||||
e.kind() == KeyValueErrorKind::GetBucket
|
||||
|
|
|
|||
Loading…
Reference in a new issue