From ceac3713d6639f9170fc3b4475fae4a30b34483c Mon Sep 17 00:00:00 2001 From: Mendes Date: Wed, 5 Oct 2022 15:29:48 +0200 Subject: modifications in several files to : - have consistent error return types - store the zone redundancy in a Lww - print the error and message in the CLI (TODO: for the server Api, should msg be returned in the body response?) --- src/api/admin/cluster.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index 99c6e332..630179b5 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -162,7 +162,12 @@ pub async fn handle_apply_cluster_layout( let param = parse_json_body::(req).await?; let layout = garage.system.get_cluster_layout(); - let layout = layout.apply_staged_changes(Some(param.version))?; + let (layout, msg) = layout.apply_staged_changes(Some(param.version))?; + //TODO : how to display msg ? Should it be in the Body Response ? + for s in msg.iter() { + println!("{}", s); + } + garage.system.update_cluster_layout(&layout).await?; Ok(Response::builder() -- cgit v1.2.3 From 4abab246f1113a9a1988fdfca81c1dd8ffa323c8 Mon Sep 17 00:00:00 2001 From: Mendes Date: Mon, 10 Oct 2022 17:21:13 +0200 Subject: cargo fmt --- src/api/admin/cluster.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index 630179b5..da3d8c44 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -163,10 +163,10 @@ pub async fn handle_apply_cluster_layout( let layout = garage.system.get_cluster_layout(); let (layout, msg) = layout.apply_staged_changes(Some(param.version))?; - //TODO : how to display msg ? Should it be in the Body Response ? - for s in msg.iter() { - println!("{}", s); - } + //TODO : how to display msg ? Should it be in the Body Response ? + for s in msg.iter() { + println!("{}", s); + } garage.system.update_cluster_layout(&layout).await?; -- cgit v1.2.3 From ea5afc251106b3f6e2d07f942ba1f88abeef8765 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Mon, 7 Nov 2022 19:34:40 +0100 Subject: Style improvements --- src/api/admin/cluster.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index 61bfb8c5..040778b1 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -86,7 +86,7 @@ fn get_cluster_layout(garage: &Arc) -> GetClusterLayoutResponse { .map(|(k, _, v)| (hex::encode(k), v.0.clone())) .collect(), staged_role_changes: layout - .staging + .staging_roles .items() .iter() .filter(|(k, _, v)| layout.roles.get(k) != Some(v)) @@ -137,14 +137,14 @@ pub async fn handle_update_cluster_layout( let mut layout = garage.system.get_cluster_layout(); let mut roles = layout.roles.clone(); - roles.merge(&layout.staging); + roles.merge(&layout.staging_roles); for (node, role) in updates { let node = hex::decode(node).ok_or_bad_request("Invalid node identifier")?; let node = Uuid::try_from(&node).ok_or_bad_request("Invalid node identifier")?; layout - .staging + .staging_roles .merge(&roles.update_mutator(node, NodeRoleV(role))); } -- cgit v1.2.3 From d75b37b018fc0ce8e3832c8531d9556ff7a345c9 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Tue, 8 Nov 2022 14:23:08 +0100 Subject: Return more info when layout's .check() fails, fix compilation, fix test --- src/api/admin/cluster.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index 040778b1..7b91f709 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -163,16 +163,13 @@ pub async fn handle_apply_cluster_layout( let layout = garage.system.get_cluster_layout(); let (layout, msg) = layout.apply_staged_changes(Some(param.version))?; - //TODO : how to display msg ? Should it be in the Body Response ? - for s in msg.iter() { - println!("{}", s); - } garage.system.update_cluster_layout(&layout).await?; Ok(Response::builder() .status(StatusCode::NO_CONTENT) - .body(Body::empty())?) + .header(http::header::CONTENT_TYPE, "text/plain") + .body(Body::from(msg.join("\n")))?) } pub async fn handle_revert_cluster_layout( -- cgit v1.2.3 From 217abdca18ff15190c0407b2b8b1ea204edcfb99 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Tue, 8 Nov 2022 15:38:53 +0100 Subject: Fix HTTP return code --- src/api/admin/cluster.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index 7b91f709..4386c0cc 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -167,7 +167,7 @@ pub async fn handle_apply_cluster_layout( garage.system.update_cluster_layout(&layout).await?; Ok(Response::builder() - .status(StatusCode::NO_CONTENT) + .status(StatusCode::OK) .header(http::header::CONTENT_TYPE, "text/plain") .body(Body::from(msg.join("\n")))?) } -- cgit v1.2.3 From 511e07ecd489fa72040171fe908323873a57ac19 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 4 May 2023 11:49:23 +0200 Subject: fix mpu counter (add missing workers) and report info at appropriate places --- src/api/admin/bucket.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/bucket.rs b/src/api/admin/bucket.rs index e60f07ca..6ccc3808 100644 --- a/src/api/admin/bucket.rs +++ b/src/api/admin/bucket.rs @@ -14,6 +14,7 @@ use garage_model::bucket_alias_table::*; use garage_model::bucket_table::*; use garage_model::garage::Garage; use garage_model::permission::*; +use garage_model::s3::mpu_table; use garage_model::s3::object_table::*; use crate::admin::error::*; @@ -124,6 +125,14 @@ async fn bucket_info_results( .map(|x| x.filtered_values(&garage.system.ring.borrow())) .unwrap_or_default(); + let mpu_counters = garage + .mpu_counter_table + .table + .get(&bucket_id, &EmptyKey) + .await? + .map(|x| x.filtered_values(&garage.system.ring.borrow())) + .unwrap_or_default(); + let mut relevant_keys = HashMap::new(); for (k, _) in bucket .state @@ -208,12 +217,12 @@ async fn bucket_info_results( } }) .collect::>(), - objects: counters.get(OBJECTS).cloned().unwrap_or_default(), - bytes: counters.get(BYTES).cloned().unwrap_or_default(), - unfinished_uploads: counters - .get(UNFINISHED_UPLOADS) - .cloned() - .unwrap_or_default(), + objects: *counters.get(OBJECTS).unwrap_or(&0), + bytes: *counters.get(BYTES).unwrap_or(&0), + unfinished_uploads: *counters.get(UNFINISHED_UPLOADS).unwrap_or(&0), + unfinished_multipart_uploads: *mpu_counters.get(mpu_table::UPLOADS).unwrap_or(&0), + unfinished_multipart_upload_parts: *mpu_counters.get(mpu_table::PARTS).unwrap_or(&0), + unfinished_multipart_upload_bytes: *mpu_counters.get(mpu_table::BYTES).unwrap_or(&0), quotas: ApiBucketQuotas { max_size: quotas.max_size, max_objects: quotas.max_objects, @@ -235,6 +244,9 @@ struct GetBucketInfoResult { objects: i64, bytes: i64, unfinished_uploads: i64, + unfinished_multipart_uploads: i64, + unfinished_multipart_upload_parts: i64, + unfinished_multipart_upload_bytes: i64, quotas: ApiBucketQuotas, } -- cgit v1.2.3 From 412ab77b0815f165539fe41713c0155a9878672f Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 4 May 2023 19:44:01 +0200 Subject: comments and clippy lint fixes --- src/api/admin/bucket.rs | 4 ++-- src/api/admin/key.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/bucket.rs b/src/api/admin/bucket.rs index 6ccc3808..17f46c30 100644 --- a/src/api/admin/bucket.rs +++ b/src/api/admin/bucket.rs @@ -192,8 +192,8 @@ async fn bucket_info_results( } }), keys: relevant_keys - .into_iter() - .map(|(_, key)| { + .into_values() + .map(|key| { let p = key.state.as_option().unwrap(); GetBucketInfoKey { access_key_id: key.key_id, diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index 2bbabb7b..d74ca361 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -183,8 +183,8 @@ async fn key_info_results(garage: &Arc, key: Key) -> Result Date: Tue, 13 Jun 2023 16:15:50 +0200 Subject: admin API: add missing camelCase conversions (fix #381) --- src/api/admin/cluster.rs | 3 +++ src/api/admin/key.rs | 4 ++++ 2 files changed, 7 insertions(+) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index b2508d2e..a2c97ee5 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -114,6 +114,7 @@ struct GetClusterStatusResponse { } #[derive(Serialize)] +#[serde(rename_all = "camelCase")] struct ConnectClusterNodesResponse { success: bool, error: Option, @@ -128,6 +129,7 @@ struct GetClusterLayoutResponse { } #[derive(Serialize)] +#[serde(rename_all = "camelCase")] struct KnownNodeResp { addr: SocketAddr, is_up: bool, @@ -197,6 +199,7 @@ pub async fn handle_revert_cluster_layout( type UpdateClusterLayoutRequest = HashMap>; #[derive(Deserialize)] +#[serde(rename_all = "camelCase")] struct ApplyRevertLayoutRequest { version: u64, } diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index d74ca361..25ba76f8 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -34,6 +34,7 @@ pub async fn handle_list_keys(garage: &Arc) -> Result, Er } #[derive(Serialize)] +#[serde(rename_all = "camelCase")] struct ListKeyResultItem { id: String, name: String, @@ -71,6 +72,7 @@ pub async fn handle_create_key( } #[derive(Deserialize)] +#[serde(rename_all = "camelCase")] struct CreateKeyRequest { name: String, } @@ -131,6 +133,7 @@ pub async fn handle_update_key( } #[derive(Deserialize)] +#[serde(rename_all = "camelCase")] struct UpdateKeyRequest { name: Option, allow: Option, @@ -246,6 +249,7 @@ struct KeyInfoBucketResult { } #[derive(Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] pub(crate) struct ApiBucketKeyPerm { #[serde(default)] pub(crate) read: bool, -- cgit v1.2.3 From 52376d47caf747f5cf93a21e5c15e4e6b8d991ee Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 13:45:27 +0200 Subject: admin api: change cluster status/layout to use lists and not maps (fix #377) --- src/api/admin/cluster.rs | 125 ++++++++++++++++++++++++++++++++++------------- src/api/admin/router.rs | 6 +-- 2 files changed, 95 insertions(+), 36 deletions(-) (limited to 'src/api/admin') 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) -> Result) -> Result) -> 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::>(); + + 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::>(); + 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, + known_nodes: Vec, layout: GetClusterLayoutResponse, } @@ -124,19 +143,31 @@ struct ConnectClusterNodesResponse { #[serde(rename_all = "camelCase")] struct GetClusterLayoutResponse { version: u64, - roles: HashMap>, - staged_role_changes: HashMap>, + roles: Vec, + staged_role_changes: Vec, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct NodeRoleResp { + id: String, + zone: String, + capacity: Option, + tags: Vec, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct KnownNodeResp { + id: String, addr: SocketAddr, is_up: bool, last_seen_secs_ago: Option, hostname: String, } +// ---- update functions ---- + pub async fn handle_update_cluster_layout( garage: &Arc, req: Request, @@ -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>; +// ---- + +type UpdateClusterLayoutRequest = Vec; #[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, + #[serde(default)] + capacity: Option, + #[serde(default)] + tags: Option>, +} 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 -- cgit v1.2.3 From 35c108b85d2b70ad28cd93bfd412607a89b9acf9 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 13:53:19 +0200 Subject: admin api: switch GetClusterHealth to camelcase (fix #381 again) --- src/api/admin/cluster.rs | 28 ++++++++++++++++++++++++++++ src/api/admin/router.rs | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index 8a208a2c..90203043 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -40,7 +40,22 @@ pub async fn handle_get_cluster_status(garage: &Arc) -> Result) -> Result, Error> { + use garage_rpc::system::ClusterHealthStatus; let health = garage.system.health(); + let health = ClusterHealth { + status: match health.status { + ClusterHealthStatus::Healthy => "healthy", + ClusterHealthStatus::Degraded => "degraded", + ClusterHealthStatus::Unavailable => "unavailable", + }, + known_nodes: health.known_nodes, + connected_nodes: health.connected_nodes, + storage_nodes: health.storage_nodes, + storage_nodes_ok: health.storage_nodes_ok, + partitions: health.partitions, + partitions_quorum: health.partitions_quorum, + partitions_all_ok: health.partitions_all_ok, + }; Ok(json_ok_response(&health)?) } @@ -120,6 +135,19 @@ 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, +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct GetClusterStatusResponse { diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs index 5af3ffb5..b98db284 100644 --- a/src/api/admin/router.rs +++ b/src/api/admin/router.rs @@ -96,7 +96,7 @@ impl Endpoint { GET "/health" => Health, GET "/metrics" => Metrics, GET "/v1/status" => GetClusterStatus, - GET "/v0/health" => GetClusterHealth, + GET "/v1/health" => GetClusterHealth, POST "/v0/connect" => ConnectClusterNodes, // Layout endpoints GET "/v1/layout" => GetClusterLayout, -- cgit v1.2.3 From 28cc9f178a1368e53143aab663577b0ffbf50b35 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 13:56:37 +0200 Subject: admin api: make name optionnal for CreateKey --- src/api/admin/key.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index 25ba76f8..664fde4c 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -65,7 +65,7 @@ pub async fn handle_create_key( ) -> Result, Error> { let req = parse_json_body::(req).await?; - let key = Key::new(&req.name); + let key = Key::new(req.name.as_deref().unwrap_or("Unnamed key")); garage.key_table.insert(&key).await?; key_info_results(garage, key).await @@ -74,7 +74,7 @@ pub async fn handle_create_key( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct CreateKeyRequest { - name: String, + name: Option, } pub async fn handle_import_key( -- cgit v1.2.3 From 4a82f6380e6a7d7c841477fc914fd96e6c09adad Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 14:15:51 +0200 Subject: admin api: move all endpoints to v1/ by default (v0/ still supported) --- src/api/admin/router.rs | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs index b98db284..077509e3 100644 --- a/src/api/admin/router.rs +++ b/src/api/admin/router.rs @@ -97,35 +97,35 @@ impl Endpoint { GET "/metrics" => Metrics, GET "/v1/status" => GetClusterStatus, GET "/v1/health" => GetClusterHealth, - POST "/v0/connect" => ConnectClusterNodes, + POST ("/v0/connect" | "/v1/connect") => ConnectClusterNodes, // Layout endpoints GET "/v1/layout" => GetClusterLayout, POST "/v1/layout" => UpdateClusterLayout, - POST "/v0/layout/apply" => ApplyClusterLayout, - POST "/v0/layout/revert" => RevertClusterLayout, + POST ("/v0/layout/apply" | "/v1/layout/apply") => ApplyClusterLayout, + POST ("/v0/layout/revert" | "/v1/layout/revert") => RevertClusterLayout, // API key endpoints - GET "/v0/key" if id => GetKeyInfo (query_opt::id, query_opt::search), - GET "/v0/key" if search => GetKeyInfo (query_opt::id, query_opt::search), - POST "/v0/key" if id => UpdateKey (query::id), - POST "/v0/key" => CreateKey, - POST "/v0/key/import" => ImportKey, - DELETE "/v0/key" if id => DeleteKey (query::id), - GET "/v0/key" => ListKeys, + GET ("/v0/key" | "/v1/key") if id => GetKeyInfo (query_opt::id, query_opt::search), + GET ("/v0/key" | "/v1/key") if search => GetKeyInfo (query_opt::id, query_opt::search), + POST ("/v0/key" | "/v1/key") if id => UpdateKey (query::id), + POST ("/v0/key" | "/v1/key") => CreateKey, + POST ("/v0/key/import" | "/v1/key/import") => ImportKey, + DELETE ("/v0/key" | "/v1/key") if id => DeleteKey (query::id), + GET ("/v0/key" | "/v1/key") => ListKeys, // Bucket endpoints - GET "/v0/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias), - GET "/v0/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias), - GET "/v0/bucket" => ListBuckets, - POST "/v0/bucket" => CreateBucket, - DELETE "/v0/bucket" if id => DeleteBucket (query::id), - PUT "/v0/bucket" if id => UpdateBucket (query::id), + GET ("/v0/bucket" | "/v1/bucket") if id => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET ("/v0/bucket" | "/v1/bucket") if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET ("/v0/bucket" | "/v1/bucket") => ListBuckets, + POST ("/v0/bucket" | "/v1/bucket") => CreateBucket, + DELETE ("/v0/bucket" | "/v1/bucket") if id => DeleteBucket (query::id), + PUT ("/v0/bucket" | "/v1/bucket") if id => UpdateBucket (query::id), // Bucket-key permissions - POST "/v0/bucket/allow" => BucketAllowKey, - POST "/v0/bucket/deny" => BucketDenyKey, + POST ("/v0/bucket/allow" | "/v1/bucket/allow") => BucketAllowKey, + POST ("/v0/bucket/deny" | "/v1/bucket/deny") => BucketDenyKey, // Bucket aliases - PUT "/v0/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias), - DELETE "/v0/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias), - PUT "/v0/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias), - DELETE "/v0/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias), + PUT ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalAliasBucket (query::id, query::alias), + DELETE ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalUnaliasBucket (query::id, query::alias), + PUT ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalAliasBucket (query::id, query::access_key_id, query::alias), + DELETE ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalUnaliasBucket (query::id, query::access_key_id, query::alias), ]); if let Some(message) = query.nonempty_message() { -- cgit v1.2.3 From 7895f99d3afc6e97f62f52abe06a6ee8d0f0617f Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 16:56:15 +0200 Subject: admin and cli: hide secret keys unless asked --- src/api/admin/api_server.rs | 9 +++++++-- src/api/admin/key.rs | 34 ++++++++++++++++++++++++---------- src/api/admin/router.rs | 14 ++++++++------ 3 files changed, 39 insertions(+), 18 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs index b0dfdfb7..6819e28e 100644 --- a/src/api/admin/api_server.rs +++ b/src/api/admin/api_server.rs @@ -242,8 +242,13 @@ impl ApiHandler for AdminApiServer { Endpoint::RevertClusterLayout => handle_revert_cluster_layout(&self.garage, req).await, // Keys Endpoint::ListKeys => handle_list_keys(&self.garage).await, - Endpoint::GetKeyInfo { id, search } => { - handle_get_key_info(&self.garage, id, search).await + Endpoint::GetKeyInfo { + id, + search, + show_secret_key, + } => { + let show_secret_key = show_secret_key.map(|x| x == "true").unwrap_or(false); + handle_get_key_info(&self.garage, id, search, show_secret_key).await } Endpoint::CreateKey => handle_create_key(&self.garage, req).await, Endpoint::ImportKey => handle_import_key(&self.garage, req).await, diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index 664fde4c..0d1f799b 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -10,7 +10,7 @@ use garage_model::garage::Garage; use garage_model::key_table::*; use crate::admin::error::*; -use crate::helpers::{json_ok_response, parse_json_body}; +use crate::helpers::{is_default, json_ok_response, parse_json_body}; pub async fn handle_list_keys(garage: &Arc) -> Result, Error> { let res = garage @@ -44,6 +44,7 @@ pub async fn handle_get_key_info( garage: &Arc, id: Option, search: Option, + show_secret_key: bool, ) -> Result, Error> { let key = if let Some(id) = id { garage.key_helper().get_existing_key(&id).await? @@ -56,7 +57,7 @@ pub async fn handle_get_key_info( unreachable!(); }; - key_info_results(garage, key).await + key_info_results(garage, key, show_secret_key).await } pub async fn handle_create_key( @@ -68,7 +69,7 @@ pub async fn handle_create_key( let key = Key::new(req.name.as_deref().unwrap_or("Unnamed key")); garage.key_table.insert(&key).await?; - key_info_results(garage, key).await + key_info_results(garage, key, true).await } #[derive(Deserialize)] @@ -88,10 +89,14 @@ pub async fn handle_import_key( return Err(Error::KeyAlreadyExists(req.access_key_id.to_string())); } - let imported_key = Key::import(&req.access_key_id, &req.secret_access_key, &req.name); + let imported_key = Key::import( + &req.access_key_id, + &req.secret_access_key, + req.name.as_deref().unwrap_or("Imported key"), + ); garage.key_table.insert(&imported_key).await?; - key_info_results(garage, imported_key).await + key_info_results(garage, imported_key, false).await } #[derive(Deserialize)] @@ -99,7 +104,7 @@ pub async fn handle_import_key( struct ImportKeyRequest { access_key_id: String, secret_access_key: String, - name: String, + name: Option, } pub async fn handle_update_key( @@ -129,7 +134,7 @@ pub async fn handle_update_key( garage.key_table.insert(&key).await?; - key_info_results(garage, key).await + key_info_results(garage, key, false).await } #[derive(Deserialize)] @@ -152,7 +157,11 @@ pub async fn handle_delete_key(garage: &Arc, id: String) -> Result, key: Key) -> Result, Error> { +async fn key_info_results( + garage: &Arc, + key: Key, + show_secret: bool, +) -> Result, Error> { let mut relevant_buckets = HashMap::new(); let key_state = key.state.as_option().unwrap(); @@ -181,7 +190,11 @@ async fn key_info_results(garage: &Arc, key: Key) -> Result, key: Key) -> Result, permissions: KeyPerm, buckets: Vec, } diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs index 077509e3..97ad6f76 100644 --- a/src/api/admin/router.rs +++ b/src/api/admin/router.rs @@ -35,6 +35,7 @@ pub enum Endpoint { GetKeyInfo { id: Option, search: Option, + show_secret_key: Option, }, DeleteKey { id: String, @@ -104,11 +105,11 @@ impl Endpoint { POST ("/v0/layout/apply" | "/v1/layout/apply") => ApplyClusterLayout, POST ("/v0/layout/revert" | "/v1/layout/revert") => RevertClusterLayout, // API key endpoints - GET ("/v0/key" | "/v1/key") if id => GetKeyInfo (query_opt::id, query_opt::search), - GET ("/v0/key" | "/v1/key") if search => GetKeyInfo (query_opt::id, query_opt::search), - POST ("/v0/key" | "/v1/key") if id => UpdateKey (query::id), - POST ("/v0/key" | "/v1/key") => CreateKey, - POST ("/v0/key/import" | "/v1/key/import") => ImportKey, + GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key), + GET "/v1/key" if search => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key), + POST "/v1/key" if id => UpdateKey (query::id), + POST "/v1/key" => CreateKey, + POST "/v1/key/import" => ImportKey, DELETE ("/v0/key" | "/v1/key") if id => DeleteKey (query::id), GET ("/v0/key" | "/v1/key") => ListKeys, // Bucket endpoints @@ -153,6 +154,7 @@ generateQueryParameters! { "search" => search, "globalAlias" => global_alias, "alias" => alias, - "accessKeyId" => access_key_id + "accessKeyId" => access_key_id, + "showSecretKey" => show_secret_key ] } -- cgit v1.2.3 From a83a092c032058728f191119de99f38844aa74f5 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 17:12:37 +0200 Subject: admin: uniformize layout api and improve code --- src/api/admin/cluster.rs | 97 ++++++++++++++++++++++++++++-------------------- src/api/admin/router.rs | 2 +- 2 files changed, 57 insertions(+), 42 deletions(-) (limited to 'src/api/admin') 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), -- cgit v1.2.3 From 8ef42c9609bcefc642cc9739acb921dffba49b89 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 14 Jun 2023 17:13:41 +0200 Subject: admin docs: reformatting, key admin: add check --- src/api/admin/cluster.rs | 18 +++++++++--------- src/api/admin/key.rs | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'src/api/admin') diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs index ae4a1cc3..c8107b82 100644 --- a/src/api/admin/cluster.rs +++ b/src/api/admin/cluster.rs @@ -1,7 +1,7 @@ use std::net::SocketAddr; use std::sync::Arc; -use hyper::{Body, Request, Response, StatusCode}; +use hyper::{Body, Request, Response}; use serde::{Deserialize, Serialize}; use garage_util::crdt::*; @@ -161,8 +161,8 @@ struct GetClusterStatusResponse { #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct ApplyClusterLayoutResponse { - message: Vec, - layout: GetClusterLayoutResponse, + message: Vec, + layout: GetClusterLayoutResponse, } #[derive(Serialize)] @@ -238,7 +238,7 @@ pub async fn handle_update_cluster_layout( garage.system.update_cluster_layout(&layout).await?; let res = format_cluster_layout(&layout); - Ok(json_ok_response(&res)?) + Ok(json_ok_response(&res)?) } pub async fn handle_apply_cluster_layout( @@ -253,10 +253,10 @@ pub async fn handle_apply_cluster_layout( garage.system.update_cluster_layout(&layout).await?; let res = ApplyClusterLayoutResponse { - message: msg, - layout: format_cluster_layout(&layout), - }; - Ok(json_ok_response(&res)?) + message: msg, + layout: format_cluster_layout(&layout), + }; + Ok(json_ok_response(&res)?) } pub async fn handle_revert_cluster_layout( @@ -270,7 +270,7 @@ pub async fn handle_revert_cluster_layout( garage.system.update_cluster_layout(&layout).await?; let res = format_cluster_layout(&layout); - Ok(json_ok_response(&res)?) + Ok(json_ok_response(&res)?) } // ---- diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index 0d1f799b..8d1c6890 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -93,7 +93,8 @@ pub async fn handle_import_key( &req.access_key_id, &req.secret_access_key, req.name.as_deref().unwrap_or("Imported key"), - ); + ) + .ok_or_bad_request("Invalid key format")?; garage.key_table.insert(&imported_key).await?; key_info_results(garage, imported_key, false).await -- cgit v1.2.3 From 1c13135f253007dfd5c56b2ddf3412c9d66458ec Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 5 Oct 2023 16:27:16 +0200 Subject: admin api: remove broken GET /v0/key router rule --- src/api/admin/router.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/admin') diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs index 254aff12..691d1932 100644 --- a/src/api/admin/router.rs +++ b/src/api/admin/router.rs @@ -111,7 +111,7 @@ impl Endpoint { POST "/v1/key" => CreateKey, POST "/v1/key/import" => ImportKey, DELETE ("/v0/key" | "/v1/key") if id => DeleteKey (query::id), - GET ("/v0/key" | "/v1/key") => ListKeys, + GET "/v1/key" => ListKeys, // Bucket endpoints GET ("/v0/bucket" | "/v1/bucket") if id => GetBucketInfo (query_opt::id, query_opt::global_alias), GET ("/v0/bucket" | "/v1/bucket") if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias), -- cgit v1.2.3 From 0c431b0c035f4de8ea9d1d9bd0b419bfc74ceabf Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 5 Oct 2023 16:56:13 +0200 Subject: admin api: increased compatibility for v0/ endpoints --- src/api/admin/api_server.rs | 10 +- src/api/admin/mod.rs | 3 +- src/api/admin/router.rs | 160 ------------------------------ src/api/admin/router_v0.rs | 143 +++++++++++++++++++++++++++ src/api/admin/router_v1.rs | 235 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 388 insertions(+), 163 deletions(-) delete mode 100644 src/api/admin/router.rs create mode 100644 src/api/admin/router_v0.rs create mode 100644 src/api/admin/router_v1.rs (limited to 'src/api/admin') diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs index 53503220..4779f924 100644 --- a/src/api/admin/api_server.rs +++ b/src/api/admin/api_server.rs @@ -25,7 +25,8 @@ use crate::admin::bucket::*; use crate::admin::cluster::*; use crate::admin::error::*; use crate::admin::key::*; -use crate::admin::router::{Authorization, Endpoint}; +use crate::admin::router_v0; +use crate::admin::router_v1::{Authorization, Endpoint}; use crate::helpers::host_to_bucket; pub struct AdminApiServer { @@ -229,7 +230,12 @@ impl ApiHandler for AdminApiServer { type Error = Error; fn parse_endpoint(&self, req: &Request) -> Result { - Endpoint::from_request(req) + if req.uri().path().starts_with("/v0/") { + let endpoint_v0 = router_v0::Endpoint::from_request(req)?; + Endpoint::from_v0(endpoint_v0) + } else { + Endpoint::from_request(req) + } } async fn handle( diff --git a/src/api/admin/mod.rs b/src/api/admin/mod.rs index c4857c10..43a8c59c 100644 --- a/src/api/admin/mod.rs +++ b/src/api/admin/mod.rs @@ -1,6 +1,7 @@ pub mod api_server; mod error; -mod router; +mod router_v0; +mod router_v1; mod bucket; mod cluster; diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs deleted file mode 100644 index 691d1932..00000000 --- a/src/api/admin/router.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::borrow::Cow; - -use hyper::{Method, Request}; - -use crate::admin::error::*; -use crate::router_macros::*; - -pub enum Authorization { - None, - MetricsToken, - AdminToken, -} - -router_match! {@func - -/// List of all Admin API endpoints. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Endpoint { - Options, - CheckDomain, - Health, - Metrics, - GetClusterStatus, - GetClusterHealth, - ConnectClusterNodes, - // Layout - GetClusterLayout, - UpdateClusterLayout, - ApplyClusterLayout, - RevertClusterLayout, - // Keys - ListKeys, - CreateKey, - ImportKey, - GetKeyInfo { - id: Option, - search: Option, - show_secret_key: Option, - }, - DeleteKey { - id: String, - }, - UpdateKey { - id: String, - }, - // Buckets - ListBuckets, - CreateBucket, - GetBucketInfo { - id: Option, - global_alias: Option, - }, - DeleteBucket { - id: String, - }, - UpdateBucket { - id: String, - }, - // Bucket-Key Permissions - BucketAllowKey, - BucketDenyKey, - // Bucket aliases - GlobalAliasBucket { - id: String, - alias: String, - }, - GlobalUnaliasBucket { - id: String, - alias: String, - }, - LocalAliasBucket { - id: String, - access_key_id: String, - alias: String, - }, - LocalUnaliasBucket { - id: String, - access_key_id: String, - alias: String, - }, -}} - -impl Endpoint { - /// Determine which S3 endpoint a request is for using the request, and a bucket which was - /// possibly extracted from the Host header. - /// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets - pub fn from_request(req: &Request) -> Result { - let uri = req.uri(); - let path = uri.path(); - let query = uri.query(); - - let mut query = QueryParameters::from_query(query.unwrap_or_default())?; - - let res = router_match!(@gen_path_parser (req.method(), path, query) [ - OPTIONS _ => Options, - GET "/check" => CheckDomain, - GET "/health" => Health, - GET "/metrics" => Metrics, - GET "/v1/status" => GetClusterStatus, - GET "/v1/health" => GetClusterHealth, - POST ("/v0/connect" | "/v1/connect") => ConnectClusterNodes, - // Layout endpoints - GET "/v1/layout" => GetClusterLayout, - POST "/v1/layout" => UpdateClusterLayout, - 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), - GET "/v1/key" if search => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key), - POST "/v1/key" if id => UpdateKey (query::id), - POST "/v1/key" => CreateKey, - POST "/v1/key/import" => ImportKey, - DELETE ("/v0/key" | "/v1/key") if id => DeleteKey (query::id), - GET "/v1/key" => ListKeys, - // Bucket endpoints - GET ("/v0/bucket" | "/v1/bucket") if id => GetBucketInfo (query_opt::id, query_opt::global_alias), - GET ("/v0/bucket" | "/v1/bucket") if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias), - GET ("/v0/bucket" | "/v1/bucket") => ListBuckets, - POST ("/v0/bucket" | "/v1/bucket") => CreateBucket, - DELETE ("/v0/bucket" | "/v1/bucket") if id => DeleteBucket (query::id), - PUT ("/v0/bucket" | "/v1/bucket") if id => UpdateBucket (query::id), - // Bucket-key permissions - POST ("/v0/bucket/allow" | "/v1/bucket/allow") => BucketAllowKey, - POST ("/v0/bucket/deny" | "/v1/bucket/deny") => BucketDenyKey, - // Bucket aliases - PUT ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalAliasBucket (query::id, query::alias), - DELETE ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalUnaliasBucket (query::id, query::alias), - PUT ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalAliasBucket (query::id, query::access_key_id, query::alias), - DELETE ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalUnaliasBucket (query::id, query::access_key_id, query::alias), - ]); - - if let Some(message) = query.nonempty_message() { - debug!("Unused query parameter: {}", message) - } - - Ok(res) - } - /// Get the kind of authorization which is required to perform the operation. - pub fn authorization_type(&self) -> Authorization { - match self { - Self::Health => Authorization::None, - Self::CheckDomain => Authorization::None, - Self::Metrics => Authorization::MetricsToken, - _ => Authorization::AdminToken, - } - } -} - -generateQueryParameters! { - keywords: [], - fields: [ - "format" => format, - "id" => id, - "search" => search, - "globalAlias" => global_alias, - "alias" => alias, - "accessKeyId" => access_key_id, - "showSecretKey" => show_secret_key - ] -} diff --git a/src/api/admin/router_v0.rs b/src/api/admin/router_v0.rs new file mode 100644 index 00000000..68676445 --- /dev/null +++ b/src/api/admin/router_v0.rs @@ -0,0 +1,143 @@ +use std::borrow::Cow; + +use hyper::{Method, Request}; + +use crate::admin::error::*; +use crate::router_macros::*; + +router_match! {@func + +/// List of all Admin API endpoints. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Endpoint { + Options, + CheckDomain, + Health, + Metrics, + GetClusterStatus, + GetClusterHealth, + ConnectClusterNodes, + // Layout + GetClusterLayout, + UpdateClusterLayout, + ApplyClusterLayout, + RevertClusterLayout, + // Keys + ListKeys, + CreateKey, + ImportKey, + GetKeyInfo { + id: Option, + search: Option, + }, + DeleteKey { + id: String, + }, + UpdateKey { + id: String, + }, + // Buckets + ListBuckets, + CreateBucket, + GetBucketInfo { + id: Option, + global_alias: Option, + }, + DeleteBucket { + id: String, + }, + UpdateBucket { + id: String, + }, + // Bucket-Key Permissions + BucketAllowKey, + BucketDenyKey, + // Bucket aliases + GlobalAliasBucket { + id: String, + alias: String, + }, + GlobalUnaliasBucket { + id: String, + alias: String, + }, + LocalAliasBucket { + id: String, + access_key_id: String, + alias: String, + }, + LocalUnaliasBucket { + id: String, + access_key_id: String, + alias: String, + }, +}} + +impl Endpoint { + /// Determine which S3 endpoint a request is for using the request, and a bucket which was + /// possibly extracted from the Host header. + /// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets + pub fn from_request(req: &Request) -> Result { + let uri = req.uri(); + let path = uri.path(); + let query = uri.query(); + + let mut query = QueryParameters::from_query(query.unwrap_or_default())?; + + let res = router_match!(@gen_path_parser (req.method(), path, query) [ + OPTIONS _ => Options, + GET "/check" => CheckDomain, + GET "/health" => Health, + GET "/metrics" => Metrics, + GET "/v0/status" => GetClusterStatus, + GET "/v0/health" => GetClusterHealth, + POST "/v0/connect" => ConnectClusterNodes, + // Layout endpoints + GET "/v0/layout" => GetClusterLayout, + POST "/v0/layout" => UpdateClusterLayout, + POST "/v0/layout/apply" => ApplyClusterLayout, + POST "/v0/layout/revert" => RevertClusterLayout, + // API key endpoints + GET "/v0/key" if id => GetKeyInfo (query_opt::id, query_opt::search), + GET "/v0/key" if search => GetKeyInfo (query_opt::id, query_opt::search), + POST "/v0/key" if id => UpdateKey (query::id), + POST "/v0/key" => CreateKey, + POST "/v0/key/import" => ImportKey, + DELETE "/v0/key" if id => DeleteKey (query::id), + GET "/v0/key" => ListKeys, + // Bucket endpoints + GET "/v0/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET "/v0/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET "/v0/bucket" => ListBuckets, + POST "/v0/bucket" => CreateBucket, + DELETE "/v0/bucket" if id => DeleteBucket (query::id), + PUT "/v0/bucket" if id => UpdateBucket (query::id), + // Bucket-key permissions + POST "/v0/bucket/allow" => BucketAllowKey, + POST "/v0/bucket/deny" => BucketDenyKey, + // Bucket aliases + PUT "/v0/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias), + DELETE "/v0/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias), + PUT "/v0/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias), + DELETE "/v0/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias), + ]); + + if let Some(message) = query.nonempty_message() { + debug!("Unused query parameter: {}", message) + } + + Ok(res) + } +} + +generateQueryParameters! { + keywords: [], + fields: [ + "format" => format, + "id" => id, + "search" => search, + "globalAlias" => global_alias, + "alias" => alias, + "accessKeyId" => access_key_id + ] +} diff --git a/src/api/admin/router_v1.rs b/src/api/admin/router_v1.rs new file mode 100644 index 00000000..cc5ff2ec --- /dev/null +++ b/src/api/admin/router_v1.rs @@ -0,0 +1,235 @@ +use std::borrow::Cow; + +use hyper::{Method, Request}; + +use crate::admin::error::*; +use crate::admin::router_v0; +use crate::router_macros::*; + +pub enum Authorization { + None, + MetricsToken, + AdminToken, +} + +router_match! {@func + +/// List of all Admin API endpoints. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Endpoint { + Options, + CheckDomain, + Health, + Metrics, + GetClusterStatus, + GetClusterHealth, + ConnectClusterNodes, + // Layout + GetClusterLayout, + UpdateClusterLayout, + ApplyClusterLayout, + RevertClusterLayout, + // Keys + ListKeys, + CreateKey, + ImportKey, + GetKeyInfo { + id: Option, + search: Option, + show_secret_key: Option, + }, + DeleteKey { + id: String, + }, + UpdateKey { + id: String, + }, + // Buckets + ListBuckets, + CreateBucket, + GetBucketInfo { + id: Option, + global_alias: Option, + }, + DeleteBucket { + id: String, + }, + UpdateBucket { + id: String, + }, + // Bucket-Key Permissions + BucketAllowKey, + BucketDenyKey, + // Bucket aliases + GlobalAliasBucket { + id: String, + alias: String, + }, + GlobalUnaliasBucket { + id: String, + alias: String, + }, + LocalAliasBucket { + id: String, + access_key_id: String, + alias: String, + }, + LocalUnaliasBucket { + id: String, + access_key_id: String, + alias: String, + }, +}} + +impl Endpoint { + /// Determine which S3 endpoint a request is for using the request, and a bucket which was + /// possibly extracted from the Host header. + /// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets + pub fn from_request(req: &Request) -> Result { + let uri = req.uri(); + let path = uri.path(); + let query = uri.query(); + + let mut query = QueryParameters::from_query(query.unwrap_or_default())?; + + let res = router_match!(@gen_path_parser (req.method(), path, query) [ + OPTIONS _ => Options, + GET "/check" => CheckDomain, + GET "/health" => Health, + GET "/metrics" => Metrics, + GET "/v1/status" => GetClusterStatus, + GET "/v1/health" => GetClusterHealth, + POST "/v1/connect" => ConnectClusterNodes, + // Layout endpoints + GET "/v1/layout" => GetClusterLayout, + POST "/v1/layout" => UpdateClusterLayout, + POST "/v1/layout/apply" => ApplyClusterLayout, + POST "/v1/layout/revert" => RevertClusterLayout, + // API key endpoints + GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key), + GET "/v1/key" if search => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key), + POST "/v1/key" if id => UpdateKey (query::id), + POST "/v1/key" => CreateKey, + POST "/v1/key/import" => ImportKey, + DELETE "/v1/key" if id => DeleteKey (query::id), + GET "/v1/key" => ListKeys, + // Bucket endpoints + GET "/v1/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET "/v1/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET "/v1/bucket" => ListBuckets, + POST "/v1/bucket" => CreateBucket, + DELETE "/v1/bucket" if id => DeleteBucket (query::id), + PUT "/v1/bucket" if id => UpdateBucket (query::id), + // Bucket-key permissions + POST "/v1/bucket/allow" => BucketAllowKey, + POST "/v1/bucket/deny" => BucketDenyKey, + // Bucket aliases + PUT "/v1/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias), + DELETE "/v1/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias), + PUT "/v1/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias), + DELETE "/v1/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias), + ]); + + if let Some(message) = query.nonempty_message() { + debug!("Unused query parameter: {}", message) + } + + Ok(res) + } + /// Some endpoints work exactly the same in their v1/ version as they did in their v0/ version. + /// For these endpoints, we can convert a v0/ call to its equivalent as if it was made using + /// its v1/ URL. + pub fn from_v0(v0_endpoint: router_v0::Endpoint) -> Result { + match v0_endpoint { + // Cluster endpoints + router_v0::Endpoint::ConnectClusterNodes => Ok(Self::ConnectClusterNodes), + // - GetClusterStatus: response format changed + // - GetClusterHealth: response format changed + + // Layout endpoints + router_v0::Endpoint::RevertClusterLayout => Ok(Self::RevertClusterLayout), + // - GetClusterLayout: response format changed + // - UpdateClusterLayout: query format changed + // - ApplyCusterLayout: response format changed + + // Key endpoints + router_v0::Endpoint::ListKeys => Ok(Self::ListKeys), + router_v0::Endpoint::CreateKey => Ok(Self::CreateKey), + router_v0::Endpoint::GetKeyInfo { id, search } => Ok(Self::GetKeyInfo { + id, + search, + show_secret_key: Some("true".into()), + }), + router_v0::Endpoint::DeleteKey { id } => Ok(Self::DeleteKey { id }), + // - UpdateKey: response format changed (secret key no longer returned) + + // Bucket endpoints + router_v0::Endpoint::GetBucketInfo { id, global_alias } => { + Ok(Self::GetBucketInfo { id, global_alias }) + } + router_v0::Endpoint::ListBuckets => Ok(Self::ListBuckets), + router_v0::Endpoint::CreateBucket => Ok(Self::CreateBucket), + router_v0::Endpoint::DeleteBucket { id } => Ok(Self::DeleteBucket { id }), + router_v0::Endpoint::UpdateBucket { id } => Ok(Self::UpdateBucket { id }), + + // Bucket-key permissions + router_v0::Endpoint::BucketAllowKey => Ok(Self::BucketAllowKey), + router_v0::Endpoint::BucketDenyKey => Ok(Self::BucketDenyKey), + + // Bucket alias endpoints + router_v0::Endpoint::GlobalAliasBucket { id, alias } => { + Ok(Self::GlobalAliasBucket { id, alias }) + } + router_v0::Endpoint::GlobalUnaliasBucket { id, alias } => { + Ok(Self::GlobalUnaliasBucket { id, alias }) + } + router_v0::Endpoint::LocalAliasBucket { + id, + access_key_id, + alias, + } => Ok(Self::LocalAliasBucket { + id, + access_key_id, + alias, + }), + router_v0::Endpoint::LocalUnaliasBucket { + id, + access_key_id, + alias, + } => Ok(Self::LocalUnaliasBucket { + id, + access_key_id, + alias, + }), + + // For endpoints that have different body content syntax, issue + // deprecation warning + _ => Err(Error::bad_request(format!( + "v0/ endpoint is no longer supported: {}", + v0_endpoint.name() + ))), + } + } + /// Get the kind of authorization which is required to perform the operation. + pub fn authorization_type(&self) -> Authorization { + match self { + Self::Health => Authorization::None, + Self::CheckDomain => Authorization::None, + Self::Metrics => Authorization::MetricsToken, + _ => Authorization::AdminToken, + } + } +} + +generateQueryParameters! { + keywords: [], + fields: [ + "format" => format, + "id" => id, + "search" => search, + "globalAlias" => global_alias, + "alias" => alias, + "accessKeyId" => access_key_id, + "showSecretKey" => show_secret_key + ] +} -- cgit v1.2.3