forked from Deuxfleurs/garage
admin: uniformize layout api and improve code
This commit is contained in:
parent
7895f99d3a
commit
a83a092c03
3 changed files with 71 additions and 42 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in a new issue