agents: read every MemoryMax= size systemd enforces
`parse_bytes` knew only K/M/G/T, in either case. A size with the bare `B` unit (`1048576B`), which `validate_memory_max` stores and systemd enforces, became `infinity` in the agent's cap file: the cgroup was capped while the agent was told there was no cap. It now takes exactly the single-term forms systemd's `parse_size` does: no suffix, `B`, or K/M/G/T/P/E, case-sensitive. Lowercase units and `4GB`, which systemd refuses and so leaves the unit uncapped, read as `infinity` too. A test runs every suffix form the validator accepts through it and checks the stored value parses to the right byte count. Part of #5081 (slice S0-2).
This commit is contained in:
parent
eaca3c4eb3
commit
fb5eb9edc1
2 changed files with 61 additions and 14 deletions
|
|
@ -123,9 +123,12 @@ fn resolve(limits: &AgentLimits, hive_cpu_quota: &str, hive_memory_max: &str) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a systemd `MemoryMax=`-style byte-size value (`"4G"`, `"512M"`,
|
/// Parse a systemd `MemoryMax=`-style byte-size value (`"4G"`, `"512M"`,
|
||||||
/// a bare byte count, optionally with a decimal like `"1.5G"`) into a
|
/// `"1048576B"`, a bare byte count, optionally with a decimal like `"1.5G"`)
|
||||||
/// raw byte count. systemd's `K`/`M`/`G`/`T` suffixes are IEC binary
|
/// into a raw byte count, accepting exactly the single-term forms systemd's
|
||||||
/// (1024-based), not decimal — this matches. Pure integer arithmetic
|
/// `parse_size` does: a `K`/`M`/`G`/`T`/`P`/`E` suffix, IEC binary
|
||||||
|
/// (1024-based), a `B` suffix, or none. Suffixes are case-sensitive, and a
|
||||||
|
/// redundant `B` after a multiplier (`4GB`) is rejected, as systemd rejects
|
||||||
|
/// both and then leaves the unit uncapped. Pure integer arithmetic
|
||||||
/// throughout (via `u128` headroom) rather than `f64`: a `MemoryMax=`
|
/// throughout (via `u128` headroom) rather than `f64`: a `MemoryMax=`
|
||||||
/// value is always a small non-negative decimal (enforced by
|
/// value is always a small non-negative decimal (enforced by
|
||||||
/// [`is_plain_number`] upstream in [`validate_memory_max`]), so floats
|
/// [`is_plain_number`] upstream in [`validate_memory_max`]), so floats
|
||||||
|
|
@ -137,14 +140,18 @@ pub fn parse_bytes(value: &str) -> Option<u64> {
|
||||||
if value == "infinity" || value.ends_with('%') {
|
if value == "infinity" || value.ends_with('%') {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let (mantissa, exponent) = [('K', 1u32), ('M', 2), ('G', 3), ('T', 4)]
|
let (mantissa, exponent) = [
|
||||||
.into_iter()
|
('E', 6u32),
|
||||||
.find_map(|(suffix, exp)| {
|
('P', 5),
|
||||||
value
|
('T', 4),
|
||||||
.strip_suffix([suffix, suffix.to_ascii_lowercase()])
|
('G', 3),
|
||||||
.map(|m| (m, exp))
|
('M', 2),
|
||||||
})
|
('K', 1),
|
||||||
.unwrap_or((value, 0));
|
('B', 0),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|(suffix, exp)| value.strip_suffix(suffix).map(|m| (m, exp)))
|
||||||
|
.unwrap_or((value, 0));
|
||||||
let scale = u128::from(1024u64.checked_pow(exponent)?);
|
let scale = u128::from(1024u64.checked_pow(exponent)?);
|
||||||
let (int_part, frac_part) = mantissa.split_once('.').unwrap_or((mantissa, ""));
|
let (int_part, frac_part) = mantissa.split_once('.').unwrap_or((mantissa, ""));
|
||||||
let whole: u128 = int_part.parse().ok()?;
|
let whole: u128 = int_part.parse().ok()?;
|
||||||
|
|
@ -466,8 +473,14 @@ mod tests {
|
||||||
parse_bytes("1.5G"),
|
parse_bytes("1.5G"),
|
||||||
Some(1024 * 1024 * 1024 + 512 * 1024 * 1024)
|
Some(1024 * 1024 * 1024 + 512 * 1024 * 1024)
|
||||||
);
|
);
|
||||||
// Lowercase suffixes accepted, matching `normalized_byte_size`.
|
assert_eq!(parse_bytes("1048576B"), Some(1_048_576));
|
||||||
assert_eq!(parse_bytes("4g"), Some(4 * 1024 * 1024 * 1024));
|
assert_eq!(parse_bytes("3P"), Some(3 << 50));
|
||||||
|
assert_eq!(parse_bytes("2E"), Some(2 << 60));
|
||||||
|
assert_eq!(
|
||||||
|
parse_bytes("16E"),
|
||||||
|
None,
|
||||||
|
"past u64, which systemd refuses too"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -478,8 +491,38 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_bytes_rejects_garbage() {
|
fn parse_bytes_rejects_garbage() {
|
||||||
for v in ["", "abc", "-8G", "8Gi"] {
|
// systemd's suffixes are case-sensitive and take no `B` after a
|
||||||
|
// multiplier, so it leaves a unit with any of these uncapped.
|
||||||
|
for v in [
|
||||||
|
"", "abc", "-8G", "8Gi", "4g", "4k", "1024b", "4GB", "4MB", "B", ".5G",
|
||||||
|
] {
|
||||||
assert_eq!(parse_bytes(v), None, "{v} should not parse");
|
assert_eq!(parse_bytes(v), None, "{v} should not parse");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_size_the_validator_stores_has_a_byte_count() {
|
||||||
|
for (input, bytes) in [
|
||||||
|
("1048576", 1_048_576),
|
||||||
|
("1048576B", 1_048_576),
|
||||||
|
("1048576b", 1_048_576),
|
||||||
|
("4096K", 4096 << 10),
|
||||||
|
("4096kb", 4096 << 10),
|
||||||
|
("512M", 512 << 20),
|
||||||
|
("512mB", 512 << 20),
|
||||||
|
("4G", 4 << 30),
|
||||||
|
("4g", 4 << 30),
|
||||||
|
("4GB", 4 << 30),
|
||||||
|
("1.5G", 3 << 29),
|
||||||
|
("2T", 2 << 40),
|
||||||
|
("2tb", 2 << 40),
|
||||||
|
] {
|
||||||
|
let stored = validate_memory_max(input).expect(input);
|
||||||
|
assert_eq!(
|
||||||
|
parse_bytes(&stored),
|
||||||
|
Some(bytes),
|
||||||
|
"{input} stored as {stored}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -947,6 +947,8 @@ mod tests {
|
||||||
fn a_cap_is_read_the_way_systemd_reads_memory_max() {
|
fn a_cap_is_read_the_way_systemd_reads_memory_max() {
|
||||||
for (memory_max, cap) in [
|
for (memory_max, cap) in [
|
||||||
("4G", "4294967296"),
|
("4G", "4294967296"),
|
||||||
|
("1048576B", "1048576"),
|
||||||
|
("3P", "3377699720527872"),
|
||||||
("infinity", "infinity"),
|
("infinity", "infinity"),
|
||||||
("50%", "8192004096"),
|
("50%", "8192004096"),
|
||||||
("33.33%", "5460787200"),
|
("33.33%", "5460787200"),
|
||||||
|
|
@ -954,6 +956,8 @@ mod tests {
|
||||||
("100%", "16384012288"),
|
("100%", "16384012288"),
|
||||||
// systemd ignores these and the unit runs uncapped.
|
// systemd ignores these and the unit runs uncapped.
|
||||||
("0", "infinity"),
|
("0", "infinity"),
|
||||||
|
("4g", "infinity"),
|
||||||
|
("4GB", "infinity"),
|
||||||
("0%", "infinity"),
|
("0%", "infinity"),
|
||||||
("150%", "infinity"),
|
("150%", "infinity"),
|
||||||
("12.345%", "infinity"),
|
("12.345%", "infinity"),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue