mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-22 10:46:38 +00:00
admin api: small refactor + add comments to layout-related calls
This commit is contained in:
+91
-15
@@ -168,23 +168,39 @@ pub struct GetClusterStatusRequest;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetClusterStatusResponse {
|
||||
/// Current version number of the cluster layout
|
||||
pub layout_version: u64,
|
||||
/// List of nodes that are either currently connected, part of the
|
||||
/// current cluster layout, or part of an older cluster layout that
|
||||
/// is still active in the cluster (being drained).
|
||||
pub nodes: Vec<NodeResp>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NodeResp {
|
||||
/// Full-length node identifier
|
||||
pub id: String,
|
||||
/// Role assigned to this node in the current cluster layout
|
||||
pub role: Option<NodeAssignedRole>,
|
||||
#[schema(value_type = Option<String> )]
|
||||
/// Socket address used by other nodes to connect to this node for RPC
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub addr: Option<SocketAddr>,
|
||||
/// Hostname of the node
|
||||
pub hostname: Option<String>,
|
||||
/// Whether this node is connected in the cluster
|
||||
pub is_up: bool,
|
||||
/// For disconnected nodes, the number of seconds since last contact,
|
||||
/// or `null` if no contact was established since Garage restarted.
|
||||
pub last_seen_secs_ago: Option<u64>,
|
||||
/// Whether this node is part of an older layout version and is draining data.
|
||||
pub draining: bool,
|
||||
/// Total and available space on the disk partition(s) containing the data
|
||||
/// directory(ies)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub data_partition: Option<FreeSpaceResp>,
|
||||
/// Total and available space on the disk partition containing the
|
||||
/// metadata directory
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub metadata_partition: Option<FreeSpaceResp>,
|
||||
}
|
||||
@@ -192,16 +208,21 @@ pub struct NodeResp {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NodeAssignedRole {
|
||||
pub id: String,
|
||||
/// Zone name assigned by the cluster administrator
|
||||
pub zone: String,
|
||||
pub capacity: Option<u64>,
|
||||
/// List of tags assigned by the cluster administrator
|
||||
pub tags: Vec<String>,
|
||||
/// Capacity (in bytes) assigned by the cluster administrator,
|
||||
/// absent for gateway nodes
|
||||
pub capacity: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FreeSpaceResp {
|
||||
/// Number of bytes available
|
||||
pub available: u64,
|
||||
/// Total number of bytes
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
@@ -273,22 +294,40 @@ pub struct GetClusterLayoutRequest;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetClusterLayoutResponse {
|
||||
/// The current version number of the cluster layout
|
||||
pub version: u64,
|
||||
/// List of nodes that currently have a role in the cluster layout
|
||||
pub roles: Vec<LayoutNodeRole>,
|
||||
pub partition_size: u64,
|
||||
/// Layout parameters used when the current layout was computed
|
||||
pub parameters: LayoutParameters,
|
||||
/// The size, in bytes, of one Garage partition (= a shard)
|
||||
pub partition_size: u64,
|
||||
/// List of nodes that will have a new role or whose role will be
|
||||
/// removed in the next version of the cluster layout
|
||||
pub staged_role_changes: Vec<NodeRoleChange>,
|
||||
/// Layout parameters to use when computing the next version of
|
||||
/// the cluster layout
|
||||
pub staged_parameters: Option<LayoutParameters>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LayoutNodeRole {
|
||||
/// Identifier of the node
|
||||
pub id: String,
|
||||
/// Zone name assigned by the cluster administrator
|
||||
pub zone: String,
|
||||
pub capacity: Option<u64>,
|
||||
pub usable_capacity: Option<u64>,
|
||||
/// List of tags assigned by the cluster administrator
|
||||
pub tags: Vec<String>,
|
||||
/// Capacity (in bytes) assigned by the cluster administrator,
|
||||
/// absent for gateway nodes
|
||||
pub capacity: Option<u64>,
|
||||
/// Number of partitions stored on this node
|
||||
/// (a result of the layout computation)
|
||||
pub stored_partitions: Option<u64>,
|
||||
/// Capacity (in bytes) that is actually usable on this node in the current
|
||||
/// layout, which is equal to `stored_partitions` × `partition_size`
|
||||
pub usable_capacity: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
@@ -309,26 +348,25 @@ pub enum NodeRoleChangeEnum {
|
||||
remove: bool,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Update {
|
||||
/// New zone of the node
|
||||
zone: String,
|
||||
/// New capacity (in bytes) of the node
|
||||
capacity: Option<u64>,
|
||||
/// New tags of the node
|
||||
tags: Vec<String>,
|
||||
},
|
||||
Update(NodeAssignedRole),
|
||||
}
|
||||
|
||||
#[derive(Copy, Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LayoutParameters {
|
||||
/// Minimum number of zones in which a data partition must be replicated
|
||||
pub zone_redundancy: ZoneRedundancy,
|
||||
}
|
||||
|
||||
#[derive(Copy, Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ZoneRedundancy {
|
||||
/// Partitions must be replicated in at least this number of
|
||||
/// distinct zones.
|
||||
AtLeast(usize),
|
||||
/// Partitions must be replicated in as many zones as possible:
|
||||
/// as many zones as there are replicas, if there are enough distinct
|
||||
/// zones, or at least one in each zone otherwise.
|
||||
Maximum,
|
||||
}
|
||||
|
||||
@@ -340,25 +378,42 @@ pub struct GetClusterLayoutHistoryRequest;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetClusterLayoutHistoryResponse {
|
||||
/// The current version number of the cluster layout
|
||||
pub current_version: u64,
|
||||
/// All nodes in the cluster are aware of layout versions up to
|
||||
/// this version number (at least)
|
||||
pub min_ack: u64,
|
||||
/// Layout version history
|
||||
pub versions: Vec<ClusterLayoutVersion>,
|
||||
/// Detailed update trackers for nodes (see
|
||||
/// `https://garagehq.deuxfleurs.fr/blog/2023-12-preserving-read-after-write-consistency/`)
|
||||
pub update_trackers: Option<HashMap<String, NodeUpdateTrackers>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClusterLayoutVersion {
|
||||
/// Version number of this layout version
|
||||
pub version: u64,
|
||||
/// Status of this layout version
|
||||
pub status: ClusterLayoutVersionStatus,
|
||||
/// Number of nodes with an assigned storage capacity in this layout version
|
||||
pub storage_nodes: u64,
|
||||
/// Number of nodes with a gateway role in this layout version
|
||||
pub gateway_nodes: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub enum ClusterLayoutVersionStatus {
|
||||
/// This is the most up-to-date layout version
|
||||
Current,
|
||||
/// This version is still active in the cluster because metadata
|
||||
/// is being rebalanced or migrated from old nodes
|
||||
Draining,
|
||||
/// This version is no longer active in the cluster for metadata
|
||||
/// reads and writes. Note that there is still the possibility
|
||||
/// that data blocks are being migrated away from nodes in this
|
||||
/// layout version.
|
||||
Historical,
|
||||
}
|
||||
|
||||
@@ -374,8 +429,10 @@ pub struct NodeUpdateTrackers {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateClusterLayoutRequest {
|
||||
/// New node roles to assign or remove in the cluster layout
|
||||
#[serde(default)]
|
||||
pub roles: Vec<NodeRoleChange>,
|
||||
/// New layout computation parameters to use
|
||||
#[serde(default)]
|
||||
pub parameters: Option<LayoutParameters>,
|
||||
}
|
||||
@@ -392,10 +449,17 @@ pub struct PreviewClusterLayoutChangesRequest;
|
||||
#[serde(untagged)]
|
||||
pub enum PreviewClusterLayoutChangesResponse {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Error { error: String },
|
||||
Error {
|
||||
/// Error message indicating that the layout could not be computed
|
||||
/// with the provided configuration
|
||||
error: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Success {
|
||||
/// Plain-text information about the layout computation
|
||||
/// (do not try to parse this)
|
||||
message: Vec<String>,
|
||||
/// Details about the new cluster layout
|
||||
new_layout: GetClusterLayoutResponse,
|
||||
},
|
||||
}
|
||||
@@ -405,13 +469,18 @@ pub enum PreviewClusterLayoutChangesResponse {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplyClusterLayoutRequest {
|
||||
/// As a safety measure, the new version number of the layout must
|
||||
/// be specified here
|
||||
pub version: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplyClusterLayoutResponse {
|
||||
/// Plain-text information about the layout computation
|
||||
/// (do not try to parse this)
|
||||
pub message: Vec<String>,
|
||||
/// Details about the new cluster layout
|
||||
pub layout: GetClusterLayoutResponse,
|
||||
}
|
||||
|
||||
@@ -428,14 +497,21 @@ pub struct RevertClusterLayoutResponse(pub GetClusterLayoutResponse);
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClusterLayoutSkipDeadNodesRequest {
|
||||
/// Version number of the layout to assume is currently up-to-date.
|
||||
/// This will generally be the current layout version.
|
||||
pub version: u64,
|
||||
/// Allow the skip even if a quorum of nodes could not be found for
|
||||
/// the data among the remaining nodes
|
||||
pub allow_missing_data: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClusterLayoutSkipDeadNodesResponse {
|
||||
/// Nodes for which the ACK update tracker has been updated to `version`
|
||||
pub ack_updated: Vec<String>,
|
||||
/// If `allow_missing_data` is set,
|
||||
/// nodes for which the SYNC update tracker has been updated to `version`
|
||||
pub sync_updated: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -56,7 +56,6 @@ impl RequestHandler for GetClusterStatusRequest {
|
||||
for (id, _, role) in layout.current().roles.items().iter() {
|
||||
if let layout::NodeRoleV(Some(r)) = role {
|
||||
let role = NodeAssignedRole {
|
||||
id: hex::encode(id),
|
||||
zone: r.zone.to_string(),
|
||||
capacity: r.capacity,
|
||||
tags: r.tags.clone(),
|
||||
@@ -189,15 +188,16 @@ fn format_cluster_layout(layout: &layout::LayoutHistory) -> GetClusterLayoutResp
|
||||
.items()
|
||||
.iter()
|
||||
.filter_map(|(k, _, v)| v.0.clone().map(|x| (k, x)))
|
||||
.map(|(k, v)| LayoutNodeRole {
|
||||
id: hex::encode(k),
|
||||
zone: v.zone.clone(),
|
||||
capacity: v.capacity,
|
||||
usable_capacity: current
|
||||
.get_node_usage(k)
|
||||
.ok()
|
||||
.map(|x| x as u64 * current.partition_size),
|
||||
tags: v.tags.clone(),
|
||||
.map(|(k, v)| {
|
||||
let stored_partitions = current.get_node_usage(k).ok().map(|x| x as u64);
|
||||
LayoutNodeRole {
|
||||
id: hex::encode(k),
|
||||
zone: v.zone.clone(),
|
||||
capacity: v.capacity,
|
||||
stored_partitions,
|
||||
usable_capacity: stored_partitions.map(|x| x * current.partition_size),
|
||||
tags: v.tags.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -215,11 +215,11 @@ fn format_cluster_layout(layout: &layout::LayoutHistory) -> GetClusterLayoutResp
|
||||
},
|
||||
Some(r) => NodeRoleChange {
|
||||
id: hex::encode(k),
|
||||
action: NodeRoleChangeEnum::Update {
|
||||
action: NodeRoleChangeEnum::Update(NodeAssignedRole {
|
||||
zone: r.zone.clone(),
|
||||
capacity: r.capacity,
|
||||
tags: r.tags.clone(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -346,11 +346,11 @@ impl RequestHandler for UpdateClusterLayoutRequest {
|
||||
|
||||
let new_role = match change.action {
|
||||
NodeRoleChangeEnum::Remove { remove: true } => None,
|
||||
NodeRoleChangeEnum::Update {
|
||||
NodeRoleChangeEnum::Update(NodeAssignedRole {
|
||||
zone,
|
||||
capacity,
|
||||
tags,
|
||||
} => {
|
||||
}) => {
|
||||
if matches!(capacity, Some(cap) if cap < 1024) {
|
||||
return Err(Error::bad_request("Capacity should be at least 1K (1024)"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user