diff options
author | Alex Auvolat <lx@deuxfleurs.fr> | 2025-01-28 15:12:03 +0100 |
---|---|---|
committer | Alex Auvolat <lx@deuxfleurs.fr> | 2025-01-29 19:26:16 +0100 |
commit | c99bfe69ea19497895d32669fd15c689b86035d8 (patch) | |
tree | fd70d3d92c45de20f28c078b902c083cee91a037 /src/api/admin/special.rs | |
parent | 831f2b0207f128d67f061e6f7084337b1cbfefa4 (diff) | |
download | garage-c99bfe69ea19497895d32669fd15c689b86035d8.tar.gz garage-c99bfe69ea19497895d32669fd15c689b86035d8.zip |
admin api: new router_v2 with unified path syntax
Diffstat (limited to 'src/api/admin/special.rs')
-rw-r--r-- | src/api/admin/special.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/src/api/admin/special.rs b/src/api/admin/special.rs new file mode 100644 index 00000000..0239021a --- /dev/null +++ b/src/api/admin/special.rs @@ -0,0 +1,129 @@ +use std::sync::Arc; + +use async_trait::async_trait; + +use http::header::{ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW}; +use hyper::{Response, StatusCode}; + +use garage_model::garage::Garage; +use garage_rpc::system::ClusterHealthStatus; + +use crate::admin::api::{CheckDomainRequest, HealthRequest, OptionsRequest}; +use crate::admin::api_server::ResBody; +use crate::admin::error::*; +use crate::admin::EndpointHandler; +use crate::helpers::*; + +#[async_trait] +impl EndpointHandler for OptionsRequest { + type Response = Response<ResBody>; + + async fn handle(self, _garage: &Arc<Garage>) -> Result<Response<ResBody>, Error> { + Ok(Response::builder() + .status(StatusCode::NO_CONTENT) + .header(ALLOW, "OPTIONS, GET, POST") + .header(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS, GET, POST") + .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .body(empty_body())?) + } +} + +#[async_trait] +impl EndpointHandler for CheckDomainRequest { + type Response = Response<ResBody>; + + async fn handle(self, garage: &Arc<Garage>) -> Result<Response<ResBody>, Error> { + if check_domain(garage, &self.domain).await? { + Ok(Response::builder() + .status(StatusCode::OK) + .body(string_body(format!( + "Domain '{}' is managed by Garage", + self.domain + )))?) + } else { + Err(Error::bad_request(format!( + "Domain '{}' is not managed by Garage", + self.domain + ))) + } + } +} + +async fn check_domain(garage: &Arc<Garage>, domain: &str) -> Result<bool, Error> { + // Resolve bucket from domain name, inferring if the website must be activated for the + // domain to be valid. + let (bucket_name, must_check_website) = if let Some(bname) = garage + .config + .s3_api + .root_domain + .as_ref() + .and_then(|rd| host_to_bucket(domain, rd)) + { + (bname.to_string(), false) + } else if let Some(bname) = garage + .config + .s3_web + .as_ref() + .and_then(|sw| host_to_bucket(domain, sw.root_domain.as_str())) + { + (bname.to_string(), true) + } else { + (domain.to_string(), true) + }; + + let bucket_id = match garage + .bucket_helper() + .resolve_global_bucket_name(&bucket_name) + .await? + { + Some(bucket_id) => bucket_id, + None => return Ok(false), + }; + + if !must_check_website { + return Ok(true); + } + + let bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + + let bucket_state = bucket.state.as_option().unwrap(); + let bucket_website_config = bucket_state.website_config.get(); + + match bucket_website_config { + Some(_v) => Ok(true), + None => Ok(false), + } +} + +#[async_trait] +impl EndpointHandler for HealthRequest { + type Response = Response<ResBody>; + + async fn handle(self, garage: &Arc<Garage>) -> Result<Response<ResBody>, Error> { + let health = garage.system.health(); + + let (status, status_str) = match health.status { + ClusterHealthStatus::Healthy => (StatusCode::OK, "Garage is fully operational"), + ClusterHealthStatus::Degraded => ( + StatusCode::OK, + "Garage is operational but some storage nodes are unavailable", + ), + ClusterHealthStatus::Unavailable => ( + StatusCode::SERVICE_UNAVAILABLE, + "Quorum is not available for some/all partitions, reads and writes will fail", + ), + }; + let status_str = format!( + "{}\nConsult the full health check API endpoint at /v2/GetClusterHealth for more details\n", + status_str + ); + + Ok(Response::builder() + .status(status) + .header(http::header::CONTENT_TYPE, "text/plain") + .body(string_body(status_str))?) + } +} |