aboutsummaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2023-06-14 13:45:27 +0200
committerAlex Auvolat <alex@adnab.me>2023-06-14 13:45:27 +0200
commit52376d47caf747f5cf93a21e5c15e4e6b8d991ee (patch)
tree68ce9c2267aa68cab578e541089515df2f0cbc27 /src/api
parent187240e539f30036eb76785227ee8095ef4dd355 (diff)
downloadgarage-52376d47caf747f5cf93a21e5c15e4e6b8d991ee.tar.gz
garage-52376d47caf747f5cf93a21e5c15e4e6b8d991ee.zip
admin api: change cluster status/layout to use lists and not maps (fix #377)
Diffstat (limited to 'src/api')
-rw-r--r--src/api/admin/cluster.rs125
-rw-r--r--src/api/admin/router.rs6
2 files changed, 95 insertions, 36 deletions
diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs
index a2c97ee5..8a208a2c 100644
--- a/src/api/admin/cluster.rs
+++ b/src/api/admin/cluster.rs
@@ -1,4 +1,3 @@
-use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
@@ -8,7 +7,7 @@ use serde::{Deserialize, Serialize};
use garage_util::crdt::*;
use garage_util::data::*;
-use garage_rpc::layout::*;
+use garage_rpc::layout;
use garage_model::garage::Garage;
@@ -26,16 +25,12 @@ pub async fn handle_get_cluster_status(garage: &Arc<Garage>) -> Result<Response<
.system
.get_known_nodes()
.into_iter()
- .map(|i| {
- (
- hex::encode(i.id),
- KnownNodeResp {
- addr: i.addr,
- is_up: i.is_up,
- last_seen_secs_ago: i.last_seen_secs_ago,
- hostname: i.status.hostname,
- },
- )
+ .map(|i| KnownNodeResp {
+ id: hex::encode(i.id),
+ addr: i.addr,
+ is_up: i.is_up,
+ last_seen_secs_ago: i.last_seen_secs_ago,
+ hostname: i.status.hostname,
})
.collect(),
layout: get_cluster_layout(garage),
@@ -82,25 +77,49 @@ pub async fn handle_get_cluster_layout(garage: &Arc<Garage>) -> Result<Response<
fn get_cluster_layout(garage: &Arc<Garage>) -> GetClusterLayoutResponse {
let layout = garage.system.get_cluster_layout();
+ let roles = layout
+ .roles
+ .items()
+ .iter()
+ .filter_map(|(k, _, v)| v.0.clone().map(|x| (k, x)))
+ .map(|(k, v)| NodeRoleResp {
+ id: hex::encode(k),
+ zone: v.zone.clone(),
+ capacity: v.capacity,
+ tags: v.tags.clone(),
+ })
+ .collect::<Vec<_>>();
+
+ let staged_role_changes = layout
+ .staging_roles
+ .items()
+ .iter()
+ .filter(|(k, _, v)| layout.roles.get(k) != Some(v))
+ .map(|(k, _, v)| match &v.0 {
+ None => NodeRoleChange {
+ id: hex::encode(k),
+ remove: true,
+ ..Default::default()
+ },
+ Some(r) => NodeRoleChange {
+ id: hex::encode(k),
+ remove: false,
+ zone: Some(r.zone.clone()),
+ capacity: r.capacity,
+ tags: Some(r.tags.clone()),
+ },
+ })
+ .collect::<Vec<_>>();
+
GetClusterLayoutResponse {
version: layout.version,
- roles: layout
- .roles
- .items()
- .iter()
- .filter(|(_, _, v)| v.0.is_some())
- .map(|(k, _, v)| (hex::encode(k), v.0.clone()))
- .collect(),
- staged_role_changes: layout
- .staging_roles
- .items()
- .iter()
- .filter(|(k, _, v)| layout.roles.get(k) != Some(v))
- .map(|(k, _, v)| (hex::encode(k), v.0.clone()))
- .collect(),
+ roles,
+ staged_role_changes,
}
}
+// ----
+
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct GetClusterStatusResponse {
@@ -109,7 +128,7 @@ struct GetClusterStatusResponse {
garage_features: Option<&'static [&'static str]>,
rust_version: &'static str,
db_engine: String,
- known_nodes: HashMap<String, KnownNodeResp>,
+ known_nodes: Vec<KnownNodeResp>,
layout: GetClusterLayoutResponse,
}
@@ -124,19 +143,31 @@ struct ConnectClusterNodesResponse {
#[serde(rename_all = "camelCase")]
struct GetClusterLayoutResponse {
version: u64,
- roles: HashMap<String, Option<NodeRole>>,
- staged_role_changes: HashMap<String, Option<NodeRole>>,
+ roles: Vec<NodeRoleResp>,
+ staged_role_changes: Vec<NodeRoleChange>,
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct NodeRoleResp {
+ id: String,
+ zone: String,
+ capacity: Option<u64>,
+ tags: Vec<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct KnownNodeResp {
+ id: String,
addr: SocketAddr,
is_up: bool,
last_seen_secs_ago: Option<u64>,
hostname: String,
}
+// ---- update functions ----
+
pub async fn handle_update_cluster_layout(
garage: &Arc<Garage>,
req: Request<Body>,
@@ -148,13 +179,23 @@ pub async fn handle_update_cluster_layout(
let mut roles = layout.roles.clone();
roles.merge(&layout.staging_roles);
- for (node, role) in updates {
- let node = hex::decode(node).ok_or_bad_request("Invalid node identifier")?;
+ for change in updates {
+ let node = hex::decode(&change.id).ok_or_bad_request("Invalid node identifier")?;
let node = Uuid::try_from(&node).ok_or_bad_request("Invalid node identifier")?;
+ let new_role = match (change.remove, change.zone, change.capacity, change.tags) {
+ (true, None, None, None) => None,
+ (false, Some(zone), capacity, Some(tags)) => Some(layout::NodeRole {
+ zone,
+ capacity,
+ tags,
+ }),
+ _ => return Err(Error::bad_request("Invalid layout change")),
+ };
+
layout
.staging_roles
- .merge(&roles.update_mutator(node, NodeRoleV(role)));
+ .merge(&roles.update_mutator(node, layout::NodeRoleV(new_role)));
}
garage.system.update_cluster_layout(&layout).await?;
@@ -196,10 +237,28 @@ pub async fn handle_revert_cluster_layout(
.body(Body::empty())?)
}
-type UpdateClusterLayoutRequest = HashMap<String, Option<NodeRole>>;
+// ----
+
+type UpdateClusterLayoutRequest = Vec<NodeRoleChange>;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ApplyRevertLayoutRequest {
version: u64,
}
+
+// ----
+
+#[derive(Serialize, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+struct NodeRoleChange {
+ id: String,
+ #[serde(default)]
+ remove: bool,
+ #[serde(default)]
+ zone: Option<String>,
+ #[serde(default)]
+ capacity: Option<u64>,
+ #[serde(default)]
+ tags: Option<Vec<String>>,
+}
diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs
index 0dcb1546..5af3ffb5 100644
--- a/src/api/admin/router.rs
+++ b/src/api/admin/router.rs
@@ -95,12 +95,12 @@ impl Endpoint {
GET "/check" => CheckWebsiteEnabled,
GET "/health" => Health,
GET "/metrics" => Metrics,
- GET "/v0/status" => GetClusterStatus,
+ GET "/v1/status" => GetClusterStatus,
GET "/v0/health" => GetClusterHealth,
POST "/v0/connect" => ConnectClusterNodes,
// Layout endpoints
- GET "/v0/layout" => GetClusterLayout,
- POST "/v0/layout" => UpdateClusterLayout,
+ GET "/v1/layout" => GetClusterLayout,
+ POST "/v1/layout" => UpdateClusterLayout,
POST "/v0/layout/apply" => ApplyClusterLayout,
POST "/v0/layout/revert" => RevertClusterLayout,
// API key endpoints