Skip to content

Commit e214795

Browse files
committed
Assert cache lookup storage tiers
1 parent 21a023d commit e214795

3 files changed

Lines changed: 90 additions & 6 deletions

File tree

docs/cache-backends.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ fluxheim --config /etc/fluxheim/fluxheim.toml cache-lookup \
443443
--path /assets/img/logo.png \
444444
--query v=1 \
445445
--require-object \
446+
--expect-tier disk \
446447
--expect-status 200 \
447448
--expect-freshness-state fresh
448449
```
@@ -464,8 +465,10 @@ stdout, or dump stored header values. Use repeated `--header "Name: value"`
464465
options to inspect negotiated cache variants that depend on safe request
465466
headers such as `Accept-Language` or `Accept-Encoding`; use `--host` for the
466467
Host header. For release scripts, `cache-lookup --require-object` fails when
467-
the selected key has no cached object, repeated `--expect-status` flags fail
468-
when no matching object has an allowed cached HTTP status, and repeated
468+
the selected key has no cached object, repeated `--expect-tier memory|disk`
469+
flags fail when no matching object is present in an allowed storage tier,
470+
repeated `--expect-status` flags fail when no matching object has an allowed
471+
cached HTTP status, and repeated
469472
`--expect-freshness-state fresh|stale|expired` flags fail when none of the
470473
matching objects has an allowed freshness state.
471474

docs/versioning-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ Stable scope:
430430
bounded safe request headers such as `Accept-Language` and
431431
`Accept-Encoding`, so operators can debug negotiated `Vary` variants
432432
without contacting upstreams. `cache-lookup` can fail closed for deploy
433-
checks with `--require-object`, `--expect-status`, and
433+
checks with `--require-object`, `--expect-tier`, `--expect-status`, and
434434
`--expect-freshness-state fresh|stale|expired`, so scripts can assert that
435435
warm objects exist and are in an acceptable serving state without
436436
contacting upstreams;

src/cli.rs

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ pub enum CliCommand {
244244
/// Required cached-object HTTP status. May be repeated.
245245
#[arg(long = "expect-status", value_name = "STATUS")]
246246
expect_statuses: Vec<u16>,
247+
248+
/// Required cached-object storage tier. May be repeated: memory, disk.
249+
#[arg(long = "expect-tier", value_name = "TIER")]
250+
expect_tiers: Vec<String>,
247251
},
248252
}
249253

@@ -472,6 +476,7 @@ fn run_command(
472476
require_object,
473477
expect_freshness_states,
474478
expect_statuses,
479+
expect_tiers,
475480
} => run_cache_lookup_command(CacheLookupOptions {
476481
config_path,
477482
host: host.clone(),
@@ -482,6 +487,7 @@ fn run_command(
482487
require_object: *require_object,
483488
expect_freshness_states: expect_freshness_states.clone(),
484489
expect_statuses: expect_statuses.clone(),
490+
expect_tiers: expect_tiers.clone(),
485491
}),
486492
}
487493
}
@@ -526,6 +532,7 @@ struct CacheLookupOptions<'a> {
526532
require_object: bool,
527533
expect_freshness_states: Vec<String>,
528534
expect_statuses: Vec<u16>,
535+
expect_tiers: Vec<String>,
529536
}
530537

531538
#[cfg(feature = "cache")]
@@ -850,6 +857,7 @@ fn run_cache_lookup_command(
850857
};
851858
let require_object = options.require_object;
852859
let expected_states = parse_cache_lookup_freshness_states(&options.expect_freshness_states)?;
860+
let expected_tiers = parse_cache_lookup_tiers(&options.expect_tiers)?;
853861
validate_cache_lookup_expected_statuses(&options.expect_statuses)?;
854862
let (config, request) = cache_key_command_request(&cache_key_options)?;
855863
let proxy = crate::proxy::FluxProxy::from_config(&config)?;
@@ -861,6 +869,7 @@ fn run_cache_lookup_command(
861869
require_object,
862870
&expected_states,
863871
&options.expect_statuses,
872+
&expected_tiers,
864873
)?;
865874

866875
println!("cache object lookup:");
@@ -940,12 +949,30 @@ fn parse_cache_lookup_freshness_states(
940949
.collect()
941950
}
942951

952+
#[cfg(all(feature = "cache", feature = "proxy"))]
953+
fn parse_cache_lookup_tiers(
954+
tiers: &[String],
955+
) -> Result<Vec<crate::cache::CacheObjectTier>, Box<dyn Error + Send + Sync>> {
956+
tiers
957+
.iter()
958+
.map(|tier| match tier.as_str() {
959+
"memory" => Ok(crate::cache::CacheObjectTier::Memory),
960+
"disk" => Ok(crate::cache::CacheObjectTier::Disk),
961+
other => Err(format!(
962+
"cache-lookup --expect-tier must be memory or disk; got {other:?}"
963+
)
964+
.into()),
965+
})
966+
.collect()
967+
}
968+
943969
#[cfg(all(feature = "cache", feature = "proxy"))]
944970
fn validate_cache_lookup_expectations(
945971
lookup: &crate::proxy::CacheObjectLookup,
946972
require_object: bool,
947973
expected_states: &[crate::cache::CacheObjectFreshnessState],
948974
expected_statuses: &[u16],
975+
expected_tiers: &[crate::cache::CacheObjectTier],
949976
) -> Result<(), Box<dyn Error + Send + Sync>> {
950977
if require_object && lookup.objects.is_empty() {
951978
return Err("cache-lookup expected at least one cached object, found none".into());
@@ -1000,6 +1027,30 @@ fn validate_cache_lookup_expectations(
10001027
return Err(format!("cache-lookup expected status {expected}, found {found}").into());
10011028
}
10021029
}
1030+
if !expected_tiers.is_empty() {
1031+
let matched = lookup
1032+
.objects
1033+
.iter()
1034+
.any(|object| expected_tiers.contains(&object.tier));
1035+
if !matched {
1036+
let expected = expected_tiers
1037+
.iter()
1038+
.map(|tier| tier.as_str())
1039+
.collect::<Vec<_>>()
1040+
.join(",");
1041+
let found = if lookup.objects.is_empty() {
1042+
"none".to_owned()
1043+
} else {
1044+
lookup
1045+
.objects
1046+
.iter()
1047+
.map(|object| object.tier.as_str())
1048+
.collect::<Vec<_>>()
1049+
.join(",")
1050+
};
1051+
return Err(format!("cache-lookup expected tier {expected}, found {found}").into());
1052+
}
1053+
}
10031054
Ok(())
10041055
}
10051056

@@ -1046,9 +1097,15 @@ fn run_cache_lookup_command(
10461097
require_object,
10471098
expect_freshness_states,
10481099
expect_statuses,
1100+
expect_tiers,
10491101
} = options;
10501102
let _ = (config_path, host, headers, method, path, query);
1051-
let _ = (require_object, expect_freshness_states, expect_statuses);
1103+
let _ = (
1104+
require_object,
1105+
expect_freshness_states,
1106+
expect_statuses,
1107+
expect_tiers,
1108+
);
10521109
Err("cache-lookup requires the proxy and cache features".into())
10531110
}
10541111

@@ -2725,37 +2782,61 @@ mod tests {
27252782
fn cache_lookup_expectations_validate_object_and_freshness_state() {
27262783
let lookup = cache_lookup_with_state(crate::cache::CacheObjectFreshnessState::Stale);
27272784
let states = super::parse_cache_lookup_freshness_states(&["stale".to_owned()]).unwrap();
2785+
let tiers = super::parse_cache_lookup_tiers(&["memory".to_owned()]).unwrap();
27282786

2729-
assert!(super::validate_cache_lookup_expectations(&lookup, true, &states, &[200]).is_ok());
2787+
assert!(
2788+
super::validate_cache_lookup_expectations(&lookup, true, &states, &[200], &tiers)
2789+
.is_ok()
2790+
);
27302791
assert!(
27312792
super::validate_cache_lookup_expectations(
27322793
&lookup,
27332794
false,
27342795
&[crate::cache::CacheObjectFreshnessState::Fresh],
2796+
&[],
27352797
&[]
27362798
)
27372799
.unwrap_err()
27382800
.to_string()
27392801
.contains("expected freshness state fresh, found stale")
27402802
);
27412803
assert!(
2742-
super::validate_cache_lookup_expectations(&lookup, false, &[], &[404])
2804+
super::validate_cache_lookup_expectations(&lookup, false, &[], &[404], &[])
27432805
.unwrap_err()
27442806
.to_string()
27452807
.contains("expected status 404, found 200")
27462808
);
2809+
assert!(
2810+
super::validate_cache_lookup_expectations(
2811+
&lookup,
2812+
false,
2813+
&[],
2814+
&[],
2815+
&[crate::cache::CacheObjectTier::Disk]
2816+
)
2817+
.unwrap_err()
2818+
.to_string()
2819+
.contains("expected tier disk, found memory")
2820+
);
27472821
assert!(
27482822
super::parse_cache_lookup_freshness_states(&["invalid".to_owned()])
27492823
.unwrap_err()
27502824
.to_string()
27512825
.contains("fresh, stale, or expired")
27522826
);
2827+
assert!(
2828+
super::parse_cache_lookup_tiers(&["invalid".to_owned()])
2829+
.unwrap_err()
2830+
.to_string()
2831+
.contains("memory or disk")
2832+
);
27532833
assert!(super::validate_cache_lookup_expected_statuses(&[99]).is_err());
27542834
assert!(
27552835
super::validate_cache_lookup_expectations(
27562836
&cache_lookup_without_objects(),
27572837
true,
27582838
&[],
2839+
&[],
27592840
&[]
27602841
)
27612842
.unwrap_err()

0 commit comments

Comments
 (0)