Skip to content

Commit c75a5ac

Browse files
committed
fix tests
1 parent 71f98a7 commit c75a5ac

6 files changed

Lines changed: 133 additions & 9 deletions

File tree

src/controllers/overlay/storage.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
22
use kube_quantity::ParsedQuantity;
33

4+
use crate::quantity::{ParsedQuantityExt, QuantityUnit};
5+
46
/// Compute overlay storage size from a snapshot size quantity string.
57
///
68
/// Formula: `5Gi + ceil(snapshot_size_bytes / 10)`, rounded up to whole Gi.
79
pub fn compute_overlay_storage_size(snapshot_size: &Quantity) -> Quantity {
8-
let base_size: ParsedQuantity = Quantity("5Gi".into()).try_into().unwrap_or_default();
9-
let snapshot_size: ParsedQuantity = snapshot_size.try_into().unwrap_or_default();
10-
(base_size + snapshot_size / 10).into()
10+
let base = ParsedQuantity::from_unit(5, QuantityUnit::Gi);
11+
let extra = ParsedQuantity::try_from(snapshot_size).unwrap_or_default() / 10;
12+
((base + extra).ceil_to(QuantityUnit::Gi)).into()
1113
}
1214

1315
/// Apply ratchet logic: only increase, never shrink.

src/controllers/replica.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ pub async fn reconcile(replica: Arc<PostgresPhysicalReplica>, ctx: Arc<Context>)
412412
let should_restore = never_restored || replica.should_trigger_scheduled_restore();
413413

414414
if should_restore {
415-
if let Some(next) = replica.compute_next_scheduled_restore(now.into()) {
415+
if let Some(next) = replica.compute_next_scheduled_restore(now) {
416416
replica
417417
.update_status_field(client, "nextScheduledRestore", Time(next))
418418
.await?;

src/controllers/replica/scheduling.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ use crate::types::*;
1010

1111
impl PostgresPhysicalReplica {
1212
pub fn compute_next_scheduled_restore(&self, now: Timestamp) -> Option<Timestamp> {
13-
let Some(schedule) = self.spec.schedule.as_deref() else {
14-
return None;
15-
};
13+
let schedule = self.spec.schedule.as_deref()?;
1614

1715
let Ok(cron) = parse_crontab_with(schedule, {
1816
let mut options = ParseOptions::default();

src/controllers/replica/status.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,7 @@ impl PostgresPhysicalReplica {
218218
});
219219
if let Err(e) = replicas
220220
.patch_status(
221-
&self
222-
.metadata
221+
self.metadata
223222
.name
224223
.as_deref()
225224
.expect("cannot be called on new resource"),

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ pub mod error;
44
pub mod kopia;
55
pub mod metrics;
66
pub mod notifications;
7+
pub mod quantity;
78
pub mod types;
89
pub mod util;

src/quantity.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use std::fmt;
2+
3+
use kube_quantity::ParsedQuantity;
4+
5+
/// Kubernetes resource quantity units.
6+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7+
pub enum QuantityUnit {
8+
Ki,
9+
Mi,
10+
Gi,
11+
Ti,
12+
Pi,
13+
Ei,
14+
}
15+
16+
impl QuantityUnit {
17+
fn bytes(self) -> u64 {
18+
match self {
19+
Self::Ki => 1 << 10,
20+
Self::Mi => 1 << 20,
21+
Self::Gi => 1 << 30,
22+
Self::Ti => 1 << 40,
23+
Self::Pi => 1 << 50,
24+
Self::Ei => 1 << 60,
25+
}
26+
}
27+
}
28+
29+
impl fmt::Display for QuantityUnit {
30+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31+
f.write_str(match self {
32+
Self::Ki => "Ki",
33+
Self::Mi => "Mi",
34+
Self::Gi => "Gi",
35+
Self::Ti => "Ti",
36+
Self::Pi => "Pi",
37+
Self::Ei => "Ei",
38+
})
39+
}
40+
}
41+
42+
pub trait ParsedQuantityExt {
43+
/// Round up to the nearest whole multiple of `unit`.
44+
fn ceil_to(self, unit: QuantityUnit) -> ParsedQuantity;
45+
46+
/// Create a quantity of the given value and unit
47+
fn from_unit(value: i64, unit: QuantityUnit) -> ParsedQuantity {
48+
// this is very dumb, we should be able to do it with Decimal
49+
// directly, but i can't figure it out right at this moment
50+
format!("{value}{unit}")
51+
.as_str()
52+
.try_into()
53+
.expect("formatted quantity must parse")
54+
}
55+
}
56+
57+
impl ParsedQuantityExt for ParsedQuantity {
58+
fn ceil_to(self, unit: QuantityUnit) -> ParsedQuantity {
59+
let bytes = self.to_bytes_f64().unwrap_or(0.0);
60+
let unit_bytes = unit.bytes() as f64;
61+
let whole = bytes.div_euclid(unit_bytes);
62+
let has_remainder = bytes.rem_euclid(unit_bytes) > 0.0;
63+
let ceiled = whole as u64 + has_remainder as u64;
64+
format!("{ceiled}{unit}")
65+
.as_str()
66+
.try_into()
67+
.expect("formatted quantity must parse")
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
mod tests {
73+
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
74+
75+
use super::*;
76+
77+
fn ceil(input: &str, unit: QuantityUnit) -> String {
78+
let pq: ParsedQuantity = input.try_into().unwrap();
79+
let result: Quantity = pq.ceil_to(unit).into();
80+
result.0
81+
}
82+
83+
#[test]
84+
fn exact_gi() {
85+
assert_eq!(ceil("5Gi", QuantityUnit::Gi), "5Gi");
86+
}
87+
88+
#[test]
89+
fn fractional_gi_rounds_up() {
90+
assert_eq!(ceil("5120Mi", QuantityUnit::Gi), "5Gi");
91+
assert_eq!(ceil("5121Mi", QuantityUnit::Gi), "6Gi");
92+
}
93+
94+
#[test]
95+
fn sub_unit_rounds_up_to_one() {
96+
assert_eq!(ceil("500Mi", QuantityUnit::Gi), "1Gi");
97+
assert_eq!(ceil("1Mi", QuantityUnit::Gi), "1Gi");
98+
}
99+
100+
#[test]
101+
fn zero_stays_zero() {
102+
assert_eq!(ceil("0", QuantityUnit::Gi), "0Gi");
103+
}
104+
105+
#[test]
106+
fn mi_rounding() {
107+
assert_eq!(ceil("1025Ki", QuantityUnit::Mi), "2Mi");
108+
assert_eq!(ceil("1024Ki", QuantityUnit::Mi), "1Mi");
109+
}
110+
111+
#[test]
112+
fn large_value_ti() {
113+
assert_eq!(ceil("2048Gi", QuantityUnit::Ti), "2Ti");
114+
assert_eq!(ceil("2049Gi", QuantityUnit::Ti), "3Ti");
115+
}
116+
117+
#[test]
118+
fn cross_format_decimal_to_binary() {
119+
// 1G = 1_000_000_000 bytes, ceil(1_000_000_000 / 1Gi) = 1
120+
assert_eq!(ceil("1G", QuantityUnit::Gi), "1Gi");
121+
// 2G = 2_000_000_000 bytes, ceil(2_000_000_000 / 1Gi) = 2
122+
assert_eq!(ceil("2G", QuantityUnit::Gi), "2Gi");
123+
}
124+
}

0 commit comments

Comments
 (0)