forked from Deuxfleurs/garage
133 lines
3.4 KiB
Rust
133 lines
3.4 KiB
Rust
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
|
|
use http::header::{
|
|
ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW,
|
|
};
|
|
use hyper::{Response, StatusCode};
|
|
|
|
use garage_model::garage::Garage;
|
|
use garage_rpc::system::ClusterHealthStatus;
|
|
|
|
use garage_api_common::helpers::*;
|
|
|
|
use crate::api::{CheckDomainRequest, HealthRequest, OptionsRequest};
|
|
use crate::api_server::ResBody;
|
|
use crate::error::*;
|
|
use crate::EndpointHandler;
|
|
|
|
#[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::OK)
|
|
.header(ALLOW, "OPTIONS,GET,POST")
|
|
.header(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS,GET,POST")
|
|
.header(ACCESS_CONTROL_ALLOW_HEADERS, "authorization,content-type")
|
|
.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))?)
|
|
}
|
|
}
|