use std::sync::Arc;
use async_trait::async_trait;
use http::{HeaderMap, Method, Response, Uri};
use pingora_core::{
apps::http_app::ServeHttp,
protocols::http::ServerSession,
server::{Server, ShutdownWatch},
services::{
background::{BackgroundService, background_service},
listening::Service,
},
};
use praxis_core::{PingoraServerRuntime, health::HealthRegistry};
use tracing::info;
use super::{
admin_host,
admin_request::{AdminRequest, BodyReadError},
listener_meta::ListenerMetaStore,
service::{
AdminEndpointOptions, PROMETHEUS_UPKEEP_INTERVAL, PingoraAdminService, PingoraHealthService,
PrometheusAdminRecorder, install_prometheus_admin_recorder, run_prometheus_upkeep,
},
};
impl AdminRequest for ServerSession {
fn method(&self) -> &Method {
&self.req_header().method
}
fn uri(&self) -> &Uri {
&self.req_header().uri
}
fn headers(&self) -> &HeaderMap {
&self.req_header().headers
}
async fn read_body(&mut self, limit: usize) -> Result<Vec<u8>, BodyReadError> {
let mut buf = Vec::new();
loop {
match self.read_request_body().await {
Ok(Some(chunk)) => {
if buf.len().saturating_add(chunk.len()) > limit {
return Err(BodyReadError::TooLarge);
}
buf.extend_from_slice(&chunk);
},
Ok(None) => break,
Err(error) => return Err(BodyReadError::Io(error.to_string())),
}
}
Ok(buf)
}
}
#[async_trait]
impl ServeHttp for PingoraHealthService {
async fn response(&self, http_session: &mut ServerSession) -> Response<Vec<u8>> {
self.respond(http_session)
}
}
#[async_trait]
impl ServeHttp for PingoraAdminService {
async fn response(&self, http_session: &mut ServerSession) -> Response<Vec<u8>> {
self.respond(http_session).await
}
}
pub fn add_admin_api(runtime: &mut PingoraServerRuntime, admin_addr: &str, options: AdminEndpointOptions) {
add_admin_api_to_pingora_server(runtime.server_mut(), admin_addr, options);
}
pub fn add_health_endpoint(
runtime: &mut PingoraServerRuntime,
metrics_addr: &str,
registry: Option<HealthRegistry>,
verbose: bool,
) {
add_health_endpoint_to_pingora_server(runtime.server_mut(), metrics_addr, registry, verbose);
}
pub fn add_health_endpoint_with_pipelines(
runtime: &mut PingoraServerRuntime,
metrics_addr: &str,
registry: Option<HealthRegistry>,
verbose: bool,
pipelines: Option<(Arc<crate::ListenerPipelines>, ListenerMetaStore)>,
) {
add_health_endpoint_to_pingora_server_with_pipelines(
runtime.server_mut(),
metrics_addr,
registry,
verbose,
pipelines,
);
}
pub fn add_prometheus_upkeep(runtime: &mut PingoraServerRuntime, recorder: PrometheusAdminRecorder) {
add_prometheus_upkeep_to_pingora_server(runtime.server_mut(), recorder);
}
pub fn add_admin_api_to_pingora_server(server: &mut Server, admin_addr: &str, options: AdminEndpointOptions) {
let verbose = options.verbose;
let require_loopback_host = admin_host::is_loopback_host(admin_addr);
let admin = PingoraAdminService::from_options(admin_addr, options);
let mut service = Service::new("admin".to_owned(), admin);
service.add_tcp(admin_addr);
info!(
address = %admin_addr,
verbose,
require_loopback_host,
"admin endpoints enabled (kv + pipelines + log-level + stats)"
);
server.add_service(service);
}
pub fn add_health_endpoint_to_pingora_server(
server: &mut Server,
metrics_addr: &str,
registry: Option<HealthRegistry>,
verbose: bool,
) {
let recorder = install_prometheus_admin_recorder();
add_prometheus_upkeep_to_pingora_server(server, recorder);
add_health_endpoint_to_pingora_server_with_pipelines(server, metrics_addr, registry, verbose, None);
}
pub fn add_health_endpoint_to_pingora_server_with_pipelines(
server: &mut Server,
metrics_addr: &str,
registry: Option<HealthRegistry>,
verbose: bool,
pipelines: Option<(Arc<crate::ListenerPipelines>, ListenerMetaStore)>,
) {
let require_loopback_host = admin_host::is_loopback_host(metrics_addr);
let health = PingoraHealthService::new(registry, verbose).require_loopback_host(require_loopback_host);
let health = match pipelines {
Some((pipelines, meta)) => health.with_live_pipelines(pipelines, meta),
None => health,
};
let mut service = Service::new("metrics".to_owned(), health);
service.add_tcp(metrics_addr);
info!(address = %metrics_addr, verbose, "metrics endpoints enabled (health + metrics)");
server.add_service(service);
}
pub fn add_prometheus_upkeep_to_pingora_server(server: &mut Server, recorder: PrometheusAdminRecorder) {
let handle = recorder.handle;
let upkeep = PrometheusUpkeepService { handle };
server.add_service(background_service("Prometheus upkeep", upkeep));
}
struct PrometheusUpkeepService {
handle: metrics_exporter_prometheus::PrometheusHandle,
}
#[async_trait]
impl BackgroundService for PrometheusUpkeepService {
async fn start(&self, shutdown: ShutdownWatch) {
let handle = self.handle.clone();
run_prometheus_upkeep(
shutdown,
PROMETHEUS_UPKEEP_INTERVAL,
Arc::new(move || handle.run_upkeep()),
)
.await;
}
}