129 lines
3.1 KiB
Rust
129 lines
3.1 KiB
Rust
|
use std::sync::Arc;
|
||
|
|
||
|
use async_trait::async_trait;
|
||
|
|
||
|
use futures::future::Future;
|
||
|
use http::header::CONTENT_TYPE;
|
||
|
use hyper::{Body, Request, Response};
|
||
|
|
||
|
use opentelemetry::trace::{SpanRef, Tracer};
|
||
|
use opentelemetry_prometheus::PrometheusExporter;
|
||
|
use prometheus::{Encoder, TextEncoder};
|
||
|
|
||
|
use garage_model::garage::Garage;
|
||
|
use garage_util::error::Error as GarageError;
|
||
|
|
||
|
use crate::error::*;
|
||
|
use crate::generic_server::*;
|
||
|
|
||
|
use crate::admin::router::{Authorization, Endpoint};
|
||
|
|
||
|
pub struct AdminApiServer {
|
||
|
garage: Arc<Garage>,
|
||
|
exporter: PrometheusExporter,
|
||
|
metrics_token: Option<String>,
|
||
|
admin_token: Option<String>,
|
||
|
}
|
||
|
|
||
|
impl AdminApiServer {
|
||
|
pub fn new(garage: Arc<Garage>) -> Self {
|
||
|
let exporter = opentelemetry_prometheus::exporter().init();
|
||
|
let cfg = &garage.config.admin;
|
||
|
let metrics_token = cfg
|
||
|
.metrics_token
|
||
|
.as_ref()
|
||
|
.map(|tok| format!("Bearer {}", tok));
|
||
|
let admin_token = cfg
|
||
|
.admin_token
|
||
|
.as_ref()
|
||
|
.map(|tok| format!("Bearer {}", tok));
|
||
|
Self {
|
||
|
garage,
|
||
|
exporter,
|
||
|
metrics_token,
|
||
|
admin_token,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub async fn run(self, shutdown_signal: impl Future<Output = ()>) -> Result<(), GarageError> {
|
||
|
if let Some(bind_addr) = self.garage.config.admin.api_bind_addr {
|
||
|
let region = self.garage.config.s3_api.s3_region.clone();
|
||
|
ApiServer::new(region, self)
|
||
|
.run_server(bind_addr, shutdown_signal)
|
||
|
.await
|
||
|
} else {
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn handle_metrics(&self) -> Result<Response<Body>, Error> {
|
||
|
let mut buffer = vec![];
|
||
|
let encoder = TextEncoder::new();
|
||
|
|
||
|
let tracer = opentelemetry::global::tracer("garage");
|
||
|
let metric_families = tracer.in_span("admin/gather_metrics", |_| {
|
||
|
self.exporter.registry().gather()
|
||
|
});
|
||
|
|
||
|
encoder
|
||
|
.encode(&metric_families, &mut buffer)
|
||
|
.ok_or_internal_error("Could not serialize metrics")?;
|
||
|
|
||
|
Ok(Response::builder()
|
||
|
.status(200)
|
||
|
.header(CONTENT_TYPE, encoder.format_type())
|
||
|
.body(Body::from(buffer))?)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[async_trait]
|
||
|
impl ApiHandler for AdminApiServer {
|
||
|
const API_NAME: &'static str = "admin";
|
||
|
const API_NAME_DISPLAY: &'static str = "Admin";
|
||
|
|
||
|
type Endpoint = Endpoint;
|
||
|
|
||
|
fn parse_endpoint(&self, req: &Request<Body>) -> Result<Endpoint, Error> {
|
||
|
Endpoint::from_request(req)
|
||
|
}
|
||
|
|
||
|
async fn handle(
|
||
|
&self,
|
||
|
req: Request<Body>,
|
||
|
endpoint: Endpoint,
|
||
|
) -> Result<Response<Body>, Error> {
|
||
|
let expected_auth_header = match endpoint.authorization_type() {
|
||
|
Authorization::MetricsToken => self.metrics_token.as_ref(),
|
||
|
Authorization::AdminToken => self.admin_token.as_ref(),
|
||
|
};
|
||
|
|
||
|
if let Some(h) = expected_auth_header {
|
||
|
match req.headers().get("Authorization") {
|
||
|
None => Err(Error::Forbidden(
|
||
|
"Authorization token must be provided".into(),
|
||
|
)),
|
||
|
Some(v) if v.to_str().map(|hv| hv == h).unwrap_or(false) => Ok(()),
|
||
|
_ => Err(Error::Forbidden(
|
||
|
"Invalid authorization token provided".into(),
|
||
|
)),
|
||
|
}?;
|
||
|
}
|
||
|
|
||
|
match endpoint {
|
||
|
Endpoint::Metrics => self.handle_metrics(),
|
||
|
_ => Err(Error::NotImplemented(format!(
|
||
|
"Admin endpoint {} not implemented yet",
|
||
|
endpoint.name()
|
||
|
))),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl ApiEndpoint for Endpoint {
|
||
|
fn name(&self) -> &'static str {
|
||
|
Endpoint::name(self)
|
||
|
}
|
||
|
|
||
|
fn add_span_attributes(&self, _span: SpanRef<'_>) {}
|
||
|
}
|