From a83a092c032058728f191119de99f38844aa74f5 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 17:12:37 +0200 Subject: [PATCH] admin: uniformize layout api and improve code --- doc/drafts/admin-api.md | 14 ++++++ src/api/admin/cluster.rs | 97 +++++++++++++++++++++++----------------- src/api/admin/router.rs | 2 +- 3 files changed, 71 insertions(+), 42 deletions(-) diff --git a/doc/drafts/admin-api.md b/doc/drafts/admin-api.md index 340f4583..cb491945 100644 --- a/doc/drafts/admin-api.md +++ b/doc/drafts/admin-api.md @@ -318,6 +318,9 @@ Contrary to the CLI that may update only a subset of the fields `capacity`, `zone` and `tags`, when calling this API all of these values must be specified. +This returns the new cluster layout with the proposed staged changes, +as returned by GetClusterLayout. + #### ApplyClusterLayout `POST /v1/layout/apply` @@ -336,6 +339,9 @@ Similarly to the CLI, the body must include the version of the new layout that will be created, which MUST be 1 + the value of the currently existing layout in the cluster. +This returns the message describing all the calculations done to compute the new +layout, as well as the description of the layout as returned by GetClusterLayout. + #### RevertClusterLayout `POST /v1/layout/revert` Clears all of the staged layout changes. @@ -354,6 +360,8 @@ Similarly to the CLI, the body must include the incremented version number, which MUST be 1 + the value of the currently existing layout in the cluster. +This returns the new cluster layout with all changes reverted, +as returned by GetClusterLayout. ### Access key operations @@ -388,6 +396,9 @@ Request body format: } ``` +This returns the key info, including the created secret key, +in the same format as the result of GetKeyInfo. + #### ImportKey `POST /v1/key/import` Imports an existing API key. @@ -402,6 +413,8 @@ Request body format: } ``` +This returns the key info in the same format as the result of GetKeyInfo. + #### GetKeyInfo `GET /v1/key?id=` #### GetKeyInfo `GET /v1/key?search=` @@ -501,6 +514,7 @@ All fields (`name`, `allow` and `deny`) are optionnal. If they are present, the corresponding modifications are applied to the key, otherwise nothing is changed. The possible flags in `allow` and `deny` are: `createBucket`. +This returns the key info in the same format as the result of GetKeyInfo. ### Bucket operations diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index 90203043..ae4a1cc3 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -33,7 +33,7 @@ pub async fn handle_get_cluster_status(garage: &Arc) -> Result) -> Result, Error> { - let res = get_cluster_layout(garage); + let res = format_cluster_layout(&garage.system.get_cluster_layout()); Ok(json_ok_response(&res)?) } -fn get_cluster_layout(garage: &Arc) -> GetClusterLayoutResponse { - let layout = garage.system.get_cluster_layout(); - +fn format_cluster_layout(layout: &layout::ClusterLayout) -> GetClusterLayoutResponse { let roles = layout .roles .items() @@ -113,15 +111,15 @@ fn get_cluster_layout(garage: &Arc) -> GetClusterLayoutResponse { .map(|(k, _, v)| match &v.0 { None => NodeRoleChange { id: hex::encode(k), - remove: true, - ..Default::default() + action: NodeRoleChangeEnum::Remove { remove: true }, }, Some(r) => NodeRoleChange { id: hex::encode(k), - remove: false, - zone: Some(r.zone.clone()), - capacity: r.capacity, - tags: Some(r.tags.clone()), + action: NodeRoleChangeEnum::Update { + zone: r.zone.clone(), + capacity: r.capacity, + tags: r.tags.clone(), + }, }, }) .collect::>(); @@ -138,14 +136,14 @@ fn get_cluster_layout(garage: &Arc) -> GetClusterLayoutResponse { #[derive(Debug, Clone, Copy, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClusterHealth { - pub status: &'static str, - pub known_nodes: usize, - pub connected_nodes: usize, - pub storage_nodes: usize, - pub storage_nodes_ok: usize, - pub partitions: usize, - pub partitions_quorum: usize, - pub partitions_all_ok: usize, + status: &'static str, + known_nodes: usize, + connected_nodes: usize, + storage_nodes: usize, + storage_nodes_ok: usize, + partitions: usize, + partitions_quorum: usize, + partitions_all_ok: usize, } #[derive(Serialize)] @@ -160,6 +158,13 @@ struct GetClusterStatusResponse { layout: GetClusterLayoutResponse, } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct ApplyClusterLayoutResponse { + message: Vec, + layout: GetClusterLayoutResponse, +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct ConnectClusterNodesResponse { @@ -211,9 +216,13 @@ pub async fn handle_update_cluster_layout( 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 { + let new_role = match change.action { + NodeRoleChangeEnum::Remove { remove: true } => None, + NodeRoleChangeEnum::Update { + zone, + capacity, + tags, + } => Some(layout::NodeRole { zone, capacity, tags, @@ -228,9 +237,8 @@ pub async fn handle_update_cluster_layout( garage.system.update_cluster_layout(&layout).await?; - Ok(Response::builder() - .status(StatusCode::NO_CONTENT) - .body(Body::empty())?) + let res = format_cluster_layout(&layout); + Ok(json_ok_response(&res)?) } pub async fn handle_apply_cluster_layout( @@ -244,10 +252,11 @@ pub async fn handle_apply_cluster_layout( garage.system.update_cluster_layout(&layout).await?; - Ok(Response::builder() - .status(StatusCode::OK) - .header(http::header::CONTENT_TYPE, "text/plain") - .body(Body::from(msg.join("\n")))?) + let res = ApplyClusterLayoutResponse { + message: msg, + layout: format_cluster_layout(&layout), + }; + Ok(json_ok_response(&res)?) } pub async fn handle_revert_cluster_layout( @@ -260,9 +269,8 @@ pub async fn handle_revert_cluster_layout( let layout = layout.revert_staged_changes(Some(param.version))?; garage.system.update_cluster_layout(&layout).await?; - Ok(Response::builder() - .status(StatusCode::NO_CONTENT) - .body(Body::empty())?) + let res = format_cluster_layout(&layout); + Ok(json_ok_response(&res)?) } // ---- @@ -277,16 +285,23 @@ struct ApplyRevertLayoutRequest { // ---- -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct NodeRoleChange { id: String, - #[serde(default)] - remove: bool, - #[serde(default)] - zone: Option, - #[serde(default)] - capacity: Option, - #[serde(default)] - tags: Option>, + #[serde(flatten)] + action: NodeRoleChangeEnum, +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum NodeRoleChangeEnum { + #[serde(rename_all = "camelCase")] + Remove { remove: bool }, + #[serde(rename_all = "camelCase")] + Update { + zone: String, + capacity: Option, + tags: Vec, + }, } diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs index 97ad6f76..d54dabe8 100644 --- a/src/api/admin/router.rs +++ b/src/api/admin/router.rs @@ -102,7 +102,7 @@ impl Endpoint { // Layout endpoints GET "/v1/layout" => GetClusterLayout, POST "/v1/layout" => UpdateClusterLayout, - POST ("/v0/layout/apply" | "/v1/layout/apply") => ApplyClusterLayout, + POST "/v1/layout/apply" => ApplyClusterLayout, POST ("/v0/layout/revert" | "/v1/layout/revert") => RevertClusterLayout, // API key endpoints GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),