admin api: report avilable space numerically in GetClusterStatistics

This commit is contained in:
Alex Auvolat
2026-03-06 09:49:00 +01:00
committed by Alex
parent 836657565e
commit 03e6020c6b
3 changed files with 66 additions and 27 deletions

View File

@@ -2581,8 +2581,25 @@
"freeform"
],
"properties": {
"dataAvail": {
"type": "integer",
"format": "int64",
"description": "available storage space for object data in the entire cluster, in bytes",
"minimum": 0
},
"freeform": {
"type": "string"
"type": "string",
"description": "cluster statistics as a free-form string, kept for compatibility with nodes\nrunning older v2.x versions of garage"
},
"incompleteInfo": {
"type": "boolean",
"description": "true if the available storage space statistics are imprecise due to missing\ninformation of disconnected nodes. When this is the case, the actual\nspace available in the cluster might be lower than the reported values."
},
"metadataAvail": {
"type": "integer",
"format": "int64",
"description": "available storage space for object metadata in the entire cluster, in bytes",
"minimum": 0
}
}
},
@@ -4117,7 +4134,7 @@
"items": {
"type": "string"
},
"description": "Scope of the admin API token, a list of admin endpoint names (such as\n`GetClusterStatus`, etc), or the special value `*` to allow all\nadmin endpoints. **WARNING:** Granting a scope of `CreateAdminToken` or\n`UpdateAdminToken` trivially allows for privilege escalation, and is thus\nfunctionnally equivalent to granting a scope of `*`."
"description": "Scope of the admin API token, a list of admin endpoint names (such as\n`GetClusterStatus`, etc), or the special value `*` to allow all\nadmin endpoints. **WARNING:** Granting a scope of `CreateAdminToken` or\n`UpdateAdminToken` trivially allows for privilege escalation, and is thus\nfunctionally equivalent to granting a scope of `*`."
}
}
},

View File

@@ -282,8 +282,22 @@ pub struct GetClusterHealthResponse {
pub struct GetClusterStatisticsRequest;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct GetClusterStatisticsResponse {
/// cluster statistics as a free-form string, kept for compatibility with nodes
/// running older v2.x versions of garage
pub freeform: String,
/// available storage space for object data in the entire cluster, in bytes
#[serde(default)]
pub data_avail: u64,
/// available storage space for object metadata in the entire cluster, in bytes
#[serde(default)]
pub metadata_avail: u64,
/// true if the available storage space statistics are imprecise due to missing
/// information of disconnected nodes. When this is the case, the actual
/// space available in the cluster might be lower than the reported values.
#[serde(default)]
pub incomplete_info: bool,
}
// ---- ConnectClusterNodes ----

View File

@@ -231,33 +231,41 @@ impl RequestHandler for GetClusterStatisticsRequest {
.map(|c| c.0 / *parts)
})
.collect::<Vec<_>>();
if !meta_part_avail.is_empty() && !data_part_avail.is_empty() {
let meta_avail =
bytesize::ByteSize(meta_part_avail.iter().min().unwrap() * (1 << PARTITION_BITS));
let data_avail =
bytesize::ByteSize(data_part_avail.iter().min().unwrap() * (1 << PARTITION_BITS));
writeln!(
&mut ret,
"\nEstimated available storage space cluster-wide (might be lower in practice):"
)
.unwrap();
if meta_part_avail.len() < node_partition_count.len()
|| data_part_avail.len() < node_partition_count.len()
{
ret += &format_table_to_string(vec![
format!(" data: < {}", data_avail),
format!(" metadata: < {}", meta_avail),
]);
writeln!(&mut ret, "A precise estimate could not be given as information is missing for some storage nodes.").unwrap();
} else {
ret += &format_table_to_string(vec![
format!(" data: {}", data_avail),
format!(" metadata: {}", meta_avail),
]);
}
let metadata_avail: u64 =
meta_part_avail.iter().min().unwrap_or(&0) * (1 << PARTITION_BITS);
let data_avail: u64 = data_part_avail.iter().min().unwrap_or(&0) * (1 << PARTITION_BITS);
let metadata_avail_str = bytesize::ByteSize(metadata_avail);
let data_avail_str = bytesize::ByteSize(data_avail);
let incomplete_info = meta_part_avail.len() < node_partition_count.len()
|| data_part_avail.len() < node_partition_count.len();
writeln!(
&mut ret,
"\nEstimated available storage space cluster-wide (might be lower in practice):"
)
.unwrap();
if incomplete_info {
ret += &format_table_to_string(vec![
format!(" data: < {}", data_avail_str),
format!(" metadata: < {}", metadata_avail_str),
]);
writeln!(&mut ret, "A precise estimate could not be given as information is missing for some storage nodes.").unwrap();
} else {
ret += &format_table_to_string(vec![
format!(" data: {}", data_avail_str),
format!(" metadata: {}", metadata_avail_str),
]);
}
Ok(GetClusterStatisticsResponse { freeform: ret })
Ok(GetClusterStatisticsResponse {
freeform: ret,
metadata_avail,
data_avail,
incomplete_info,
})
}
}