aboutsummaryrefslogblamecommitdiff
path: root/src/api/admin/special.rs
blob: da3764d98a72b85e04aaffe3b4108ab40f7e7ada (plain) (tree)
1
2
3
4
5
6
7



                             


                                                                                                       
















                                                                                          



                                                                                           







































































































                                                                                                                    
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 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::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))?)
	}
}