admin: uniformize layout api and improve code

This commit is contained in:
Alex Auvolat 2023-06-14 17:12:37 +02:00
parent 7895f99d3a
commit a83a092c03
3 changed files with 71 additions and 42 deletions

View File

@ -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 `capacity`, `zone` and `tags`, when calling this API all of these
values must be specified. values must be specified.
This returns the new cluster layout with the proposed staged changes,
as returned by GetClusterLayout.
#### ApplyClusterLayout `POST /v1/layout/apply` #### 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 that will be created, which MUST be 1 + the value of the currently
existing layout in the cluster. 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` #### RevertClusterLayout `POST /v1/layout/revert`
Clears all of the staged layout changes. 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 version number, which MUST be 1 + the value of the currently
existing layout in the cluster. existing layout in the cluster.
This returns the new cluster layout with all changes reverted,
as returned by GetClusterLayout.
### Access key operations ### 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` #### ImportKey `POST /v1/key/import`
Imports an existing API key. 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=<acces key id>` #### GetKeyInfo `GET /v1/key?id=<acces key id>`
#### GetKeyInfo `GET /v1/key?search=<pattern>` #### GetKeyInfo `GET /v1/key?search=<pattern>`
@ -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. 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`. 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 ### Bucket operations

View File

@ -33,7 +33,7 @@ pub async fn handle_get_cluster_status(garage: &Arc<Garage>) -> Result<Response<
hostname: i.status.hostname, hostname: i.status.hostname,
}) })
.collect(), .collect(),
layout: get_cluster_layout(garage), layout: format_cluster_layout(&garage.system.get_cluster_layout()),
}; };
Ok(json_ok_response(&res)?) Ok(json_ok_response(&res)?)
@ -84,14 +84,12 @@ pub async fn handle_connect_cluster_nodes(
} }
pub async fn handle_get_cluster_layout(garage: &Arc<Garage>) -> Result<Response<Body>, Error> { pub async fn handle_get_cluster_layout(garage: &Arc<Garage>) -> Result<Response<Body>, Error> {
let res = get_cluster_layout(garage); let res = format_cluster_layout(&garage.system.get_cluster_layout());
Ok(json_ok_response(&res)?) Ok(json_ok_response(&res)?)
} }
fn get_cluster_layout(garage: &Arc<Garage>) -> GetClusterLayoutResponse { fn format_cluster_layout(layout: &layout::ClusterLayout) -> GetClusterLayoutResponse {
let layout = garage.system.get_cluster_layout();
let roles = layout let roles = layout
.roles .roles
.items() .items()
@ -113,15 +111,15 @@ fn get_cluster_layout(garage: &Arc<Garage>) -> GetClusterLayoutResponse {
.map(|(k, _, v)| match &v.0 { .map(|(k, _, v)| match &v.0 {
None => NodeRoleChange { None => NodeRoleChange {
id: hex::encode(k), id: hex::encode(k),
remove: true, action: NodeRoleChangeEnum::Remove { remove: true },
..Default::default()
}, },
Some(r) => NodeRoleChange { Some(r) => NodeRoleChange {
id: hex::encode(k), id: hex::encode(k),
remove: false, action: NodeRoleChangeEnum::Update {
zone: Some(r.zone.clone()), zone: r.zone.clone(),
capacity: r.capacity, capacity: r.capacity,
tags: Some(r.tags.clone()), tags: r.tags.clone(),
},
}, },
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -138,14 +136,14 @@ fn get_cluster_layout(garage: &Arc<Garage>) -> GetClusterLayoutResponse {
#[derive(Debug, Clone, Copy, Serialize)] #[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ClusterHealth { pub struct ClusterHealth {
pub status: &'static str, status: &'static str,
pub known_nodes: usize, known_nodes: usize,
pub connected_nodes: usize, connected_nodes: usize,
pub storage_nodes: usize, storage_nodes: usize,
pub storage_nodes_ok: usize, storage_nodes_ok: usize,
pub partitions: usize, partitions: usize,
pub partitions_quorum: usize, partitions_quorum: usize,
pub partitions_all_ok: usize, partitions_all_ok: usize,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -160,6 +158,13 @@ struct GetClusterStatusResponse {
layout: GetClusterLayoutResponse, layout: GetClusterLayoutResponse,
} }
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ApplyClusterLayoutResponse {
message: Vec<String>,
layout: GetClusterLayoutResponse,
}
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct ConnectClusterNodesResponse { 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 = 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 node = Uuid::try_from(&node).ok_or_bad_request("Invalid node identifier")?;
let new_role = match (change.remove, change.zone, change.capacity, change.tags) { let new_role = match change.action {
(true, None, None, None) => None, NodeRoleChangeEnum::Remove { remove: true } => None,
(false, Some(zone), capacity, Some(tags)) => Some(layout::NodeRole { NodeRoleChangeEnum::Update {
zone,
capacity,
tags,
} => Some(layout::NodeRole {
zone, zone,
capacity, capacity,
tags, tags,
@ -228,9 +237,8 @@ pub async fn handle_update_cluster_layout(
garage.system.update_cluster_layout(&layout).await?; garage.system.update_cluster_layout(&layout).await?;
Ok(Response::builder() let res = format_cluster_layout(&layout);
.status(StatusCode::NO_CONTENT) Ok(json_ok_response(&res)?)
.body(Body::empty())?)
} }
pub async fn handle_apply_cluster_layout( 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?; garage.system.update_cluster_layout(&layout).await?;
Ok(Response::builder() let res = ApplyClusterLayoutResponse {
.status(StatusCode::OK) message: msg,
.header(http::header::CONTENT_TYPE, "text/plain") layout: format_cluster_layout(&layout),
.body(Body::from(msg.join("\n")))?) };
Ok(json_ok_response(&res)?)
} }
pub async fn handle_revert_cluster_layout( 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))?; let layout = layout.revert_staged_changes(Some(param.version))?;
garage.system.update_cluster_layout(&layout).await?; garage.system.update_cluster_layout(&layout).await?;
Ok(Response::builder() let res = format_cluster_layout(&layout);
.status(StatusCode::NO_CONTENT) Ok(json_ok_response(&res)?)
.body(Body::empty())?)
} }
// ---- // ----
@ -277,16 +285,23 @@ struct ApplyRevertLayoutRequest {
// ---- // ----
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct NodeRoleChange { struct NodeRoleChange {
id: String, id: String,
#[serde(default)] #[serde(flatten)]
remove: bool, action: NodeRoleChangeEnum,
#[serde(default)] }
zone: Option<String>,
#[serde(default)] #[derive(Serialize, Deserialize)]
capacity: Option<u64>, #[serde(untagged)]
#[serde(default)] enum NodeRoleChangeEnum {
tags: Option<Vec<String>>, #[serde(rename_all = "camelCase")]
Remove { remove: bool },
#[serde(rename_all = "camelCase")]
Update {
zone: String,
capacity: Option<u64>,
tags: Vec<String>,
},
} }

View File

@ -102,7 +102,7 @@ impl Endpoint {
// Layout endpoints // Layout endpoints
GET "/v1/layout" => GetClusterLayout, GET "/v1/layout" => GetClusterLayout,
POST "/v1/layout" => UpdateClusterLayout, 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, POST ("/v0/layout/revert" | "/v1/layout/revert") => RevertClusterLayout,
// API key endpoints // API key endpoints
GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key), GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),