feat: harden runtime capability snapshots (#3784)

* feat(admin): expose runtime capability snapshots

* feat(runtime): refine workload admission snapshots

* test(ci): align architecture migration checks

* build(deps): bump memmap2 for RUSTSEC-2026-0186

* build(deps): refresh cargo deny lockfile
This commit is contained in:
houseme
2026-06-23 18:10:49 +08:00
committed by GitHub
parent fd2ab38581
commit 46775ae019
13 changed files with 646 additions and 91 deletions
+2
View File
@@ -14,6 +14,7 @@ categories = ["concurrency", "filesystem"]
# Internal crates
rustfs-io-core = { workspace = true }
rustfs-io-metrics = { workspace = true }
serde = { workspace = true }
# Async runtime
tokio = { workspace = true, features = ["sync", "time", "rt"] }
@@ -26,6 +27,7 @@ thiserror = { workspace = true }
tracing = { workspace = true }
[dev-dependencies]
serde_json = { workspace = true }
tokio = { workspace = true, features = ["test-util","macros","rt-multi-thread"] }
[features]
+46 -4
View File
@@ -14,8 +14,11 @@
//! Runtime workload admission contract types.
use serde::{Deserialize, Serialize};
/// Stable workload classes used by future runtime admission snapshots.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WorkloadClass {
/// Foreground read requests.
ForegroundRead,
@@ -62,7 +65,8 @@ impl std::fmt::Display for WorkloadClass {
}
/// Read-only admission state for a workload class.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AdmissionState {
/// Admission is open for this workload class.
Open,
@@ -85,7 +89,8 @@ impl AdmissionState {
}
/// Read-only admission snapshot for one workload class.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkloadAdmissionSnapshot {
/// Workload class described by this snapshot.
pub class: WorkloadClass,
@@ -130,7 +135,8 @@ impl WorkloadAdmissionSnapshot {
}
/// Read-only admission registry snapshot.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkloadAdmissionRegistrySnapshot {
entries: Vec<WorkloadAdmissionSnapshot>,
}
@@ -175,6 +181,7 @@ pub trait WorkloadAdmissionSnapshotProvider {
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn workload_contract_covers_required_classes() {
@@ -249,4 +256,39 @@ mod tests {
Some(AdmissionState::Open)
);
}
#[test]
fn workload_admission_snapshot_round_trips_with_snake_case_enums() {
let snapshot = WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundWrite, AdmissionState::Disabled)
.with_counts(Some(0), Some(0), Some(0))
.with_reason("foreground write admission not exposed");
let encoded = serde_json::to_value(&snapshot).expect("serialize workload admission snapshot");
assert_eq!(encoded["class"], json!("foreground_write"));
assert_eq!(encoded["state"], json!("disabled"));
let decoded: WorkloadAdmissionSnapshot =
serde_json::from_value(encoded).expect("deserialize workload admission snapshot");
assert_eq!(decoded, snapshot);
}
#[test]
fn workload_admission_registry_rejects_unknown_fields() {
let raw = json!({
"entries": [{
"class": "foreground_read",
"state": "open",
"active": 1,
"queued": 0,
"limit": 8,
"reason": null,
"unexpected": true
}]
});
let err = serde_json::from_value::<WorkloadAdmissionRegistrySnapshot>(raw)
.expect_err("unknown workload admission fields must be rejected");
assert!(err.to_string().contains("unexpected"));
}
}
+3
View File
@@ -387,6 +387,7 @@ fn endpoint_labels(endpoint: &Endpoint) -> TopologyLabels {
additional.insert(LOCAL_ENDPOINT_LABEL.to_owned(), endpoint.is_local.to_string());
TopologyLabels {
node: Some(endpoint_node_id(endpoint)),
additional,
..TopologyLabels::default()
}
@@ -453,6 +454,7 @@ mod tests {
.map(String::as_str),
Some("path")
);
assert_eq!(snapshot.pools[0].sets[0].disks[0].labels.node.as_deref(), Some(LOCAL_NODE_ID));
let encoded = serde_json::to_string(&snapshot).expect("serialize topology snapshot");
assert!(!encoded.contains("/tmp/rustfs-cluster-control-plane"));
@@ -464,6 +466,7 @@ mod tests {
let snapshot = topology_snapshot_from_endpoint_pools(&endpoint_pools);
assert_eq!(snapshot.pools[0].sets[0].disks[0].disk_id.as_deref(), Some("node1.example:9000"));
assert_eq!(snapshot.pools[0].sets[0].disks[0].labels.node.as_deref(), Some("node1.example:9000"));
assert_eq!(
snapshot.pools[0].sets[0].disks[0]
.labels