admin api: implement GetClusterLayoutHistory and use it in CLI

This commit is contained in:
Alex Auvolat
2025-03-06 18:33:05 +01:00
parent 004866caac
commit 3d94eb8d4b
7 changed files with 340 additions and 114 deletions
+52 -13
View File
@@ -51,6 +51,7 @@ admin_endpoints![
// Layout operations
GetClusterLayout,
GetClusterLayoutHistory,
UpdateClusterLayout,
PreviewClusterLayoutChanges,
ApplyClusterLayout,
@@ -330,6 +331,57 @@ pub enum ZoneRedundancy {
Maximum,
}
// ---- GetClusterLayoutHistory ----
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetClusterLayoutHistoryRequest;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct GetClusterLayoutHistoryResponse {
pub current_version: u64,
pub min_ack: u64,
pub versions: Vec<ClusterLayoutVersion>,
pub update_trackers: Option<HashMap<String, NodeUpdateTrackers>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ClusterLayoutVersion {
pub version: u64,
pub status: ClusterLayoutVersionStatus,
pub storage_nodes: u64,
pub gateway_nodes: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub enum ClusterLayoutVersionStatus {
Current,
Draining,
Historical,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct NodeUpdateTrackers {
pub ack: u64,
pub sync: u64,
pub sync_ack: u64,
}
// ---- UpdateClusterLayout ----
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdateClusterLayoutRequest {
#[serde(default)]
pub roles: Vec<NodeRoleChange>,
#[serde(default)]
pub parameters: Option<LayoutParameters>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdateClusterLayoutResponse(pub GetClusterLayoutResponse);
// ---- PreviewClusterLayoutChanges ----
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -347,19 +399,6 @@ pub enum PreviewClusterLayoutChangesResponse {
},
}
// ---- UpdateClusterLayout ----
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdateClusterLayoutRequest {
#[serde(default)]
pub roles: Vec<NodeRoleChange>,
#[serde(default)]
pub parameters: Option<LayoutParameters>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdateClusterLayoutResponse(pub GetClusterLayoutResponse);
// ---- ApplyClusterLayout ----
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+83
View File
@@ -240,6 +240,89 @@ fn format_cluster_layout(layout: &layout::LayoutHistory) -> GetClusterLayoutResp
}
}
impl RequestHandler for GetClusterLayoutHistoryRequest {
type Response = GetClusterLayoutHistoryResponse;
async fn handle(
self,
garage: &Arc<Garage>,
_admin: &Admin,
) -> Result<GetClusterLayoutHistoryResponse, Error> {
let layout = garage.system.cluster_layout();
let layout = layout.inner();
let min_stored = layout.min_stored();
let versions = layout
.versions
.iter()
.rev()
.chain(layout.old_versions.iter().rev())
.map(|ver| {
let status = if ver.version == layout.current().version {
ClusterLayoutVersionStatus::Current
} else if ver.version >= min_stored {
ClusterLayoutVersionStatus::Draining
} else {
ClusterLayoutVersionStatus::Historical
};
ClusterLayoutVersion {
version: ver.version,
status,
storage_nodes: ver
.roles
.items()
.iter()
.filter(
|(_, _, x)| matches!(x, layout::NodeRoleV(Some(c)) if c.capacity.is_some()),
)
.count() as u64,
gateway_nodes: ver
.roles
.items()
.iter()
.filter(
|(_, _, x)| matches!(x, layout::NodeRoleV(Some(c)) if c.capacity.is_none()),
)
.count() as u64,
}
})
.collect::<Vec<_>>();
let all_nodes = layout.get_all_nodes();
let min_ack = layout
.update_trackers
.ack_map
.min_among(&all_nodes, layout.min_stored());
let update_trackers = if layout.versions.len() > 1 {
Some(
all_nodes
.iter()
.map(|node| {
(
hex::encode(&node),
NodeUpdateTrackers {
ack: layout.update_trackers.ack_map.get(node, min_stored),
sync: layout.update_trackers.sync_map.get(node, min_stored),
sync_ack: layout.update_trackers.sync_ack_map.get(node, min_stored),
},
)
})
.collect(),
)
} else {
None
};
Ok(GetClusterLayoutHistoryResponse {
current_version: layout.current().version,
min_ack,
versions,
update_trackers,
})
}
}
// ----
// ---- update functions ----
+14
View File
@@ -88,6 +88,19 @@ Returns the cluster's current layout, including:
)]
fn GetClusterLayout() -> () {}
#[utoipa::path(get,
path = "/v2/GetClusterLayoutHistory",
tag = "Cluster layout",
description = "
Returns the history of layouts in the cluster
",
responses(
(status = 200, description = "Cluster layout history", body = GetClusterLayoutHistoryResponse),
(status = 500, description = "Internal server error")
),
)]
fn GetClusterLayoutHistory() -> () {}
#[utoipa::path(post,
path = "/v2/UpdateClusterLayout",
tag = "Cluster layout",
@@ -700,6 +713,7 @@ impl Modify for SecurityAddon {
ConnectClusterNodes,
// Layout operations
GetClusterLayout,
GetClusterLayoutHistory,
UpdateClusterLayout,
PreviewClusterLayoutChanges,
ApplyClusterLayout,
+1
View File
@@ -36,6 +36,7 @@ impl AdminApiRequest {
POST ConnectClusterNodes (body),
// Layout endpoints
GET GetClusterLayout (),
GET GetClusterLayoutHistory (),
POST UpdateClusterLayout (body),
POST PreviewClusterLayoutChanges (),
POST ApplyClusterLayout (body),