Improve the health route by ensuring lmdb is not down

And refactorize slightly the auth controller.
This commit is contained in:
Tamo 2023-04-06 13:38:47 +02:00
parent b4c01581cd
commit 4d308d5237
13 changed files with 64 additions and 26 deletions

View File

@ -429,6 +429,13 @@ impl IndexScheduler {
Ok(this) Ok(this)
} }
/// Return `Ok(())` if the index scheduler is able to access one of its database.
pub fn health(&self) -> Result<()> {
let rtxn = self.env.read_txn()?;
self.all_tasks.first(&rtxn)?;
Ok(())
}
fn index_budget( fn index_budget(
tasks_path: &Path, tasks_path: &Path,
base_map_size: usize, base_map_size: usize,

View File

@ -34,6 +34,12 @@ impl AuthController {
Ok(Self { store: Arc::new(store), master_key: master_key.clone() }) Ok(Self { store: Arc::new(store), master_key: master_key.clone() })
} }
/// Return `Ok(())` if the auth controller is able to access one of its database.
pub fn health(&self) -> Result<()> {
self.store.health()?;
Ok(())
}
/// Return the size of the `AuthController` database in bytes. /// Return the size of the `AuthController` database in bytes.
pub fn size(&self) -> Result<u64> { pub fn size(&self) -> Result<u64> {
self.store.size() self.store.size()

View File

@ -61,6 +61,13 @@ impl HeedAuthStore {
Ok(Self { env, keys, action_keyid_index_expiration, should_close_on_drop: true }) Ok(Self { env, keys, action_keyid_index_expiration, should_close_on_drop: true })
} }
/// Return `Ok(())` if the auth store is able to access one of its database.
pub fn health(&self) -> Result<()> {
let rtxn = self.env.read_txn()?;
self.keys.first(&rtxn)?;
Ok(())
}
/// Return the size in bytes of database /// Return the size in bytes of database
pub fn size(&self) -> Result<u64> { pub fn size(&self) -> Result<u64> {
Ok(self.env.real_disk_size()?) Ok(self.env.real_disk_size()?)

View File

@ -86,7 +86,7 @@ impl SegmentAnalytics {
pub async fn new( pub async fn new(
opt: &Opt, opt: &Opt,
index_scheduler: Arc<IndexScheduler>, index_scheduler: Arc<IndexScheduler>,
auth_controller: AuthController, auth_controller: Arc<AuthController>,
) -> Arc<dyn Analytics> { ) -> Arc<dyn Analytics> {
let instance_uid = super::find_user_id(&opt.db_path); let instance_uid = super::find_user_id(&opt.db_path);
let first_time_run = instance_uid.is_none(); let first_time_run = instance_uid.is_none();
@ -376,7 +376,11 @@ impl Segment {
}) })
} }
async fn run(mut self, index_scheduler: Arc<IndexScheduler>, auth_controller: AuthController) { async fn run(
mut self,
index_scheduler: Arc<IndexScheduler>,
auth_controller: Arc<AuthController>,
) {
const INTERVAL: Duration = Duration::from_secs(60 * 60); // one hour const INTERVAL: Duration = Duration::from_secs(60 * 60); // one hour
// The first batch must be sent after one hour. // The first batch must be sent after one hour.
let mut interval = let mut interval =
@ -408,10 +412,10 @@ impl Segment {
async fn tick( async fn tick(
&mut self, &mut self,
index_scheduler: Arc<IndexScheduler>, index_scheduler: Arc<IndexScheduler>,
auth_controller: AuthController, auth_controller: Arc<AuthController>,
) { ) {
if let Ok(stats) = if let Ok(stats) =
create_all_stats(index_scheduler.into(), auth_controller, &AuthFilter::default()) create_all_stats(index_scheduler.into(), auth_controller.into(), &AuthFilter::default())
{ {
// Replace the version number with the prototype name if any. // Replace the version number with the prototype name if any.
let version = if let Some(prototype) = crate::prototype_name() { let version = if let Some(prototype) = crate::prototype_name() {

View File

@ -4,6 +4,7 @@ use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use std::pin::Pin; use std::pin::Pin;
use actix_web::web::Data;
use actix_web::FromRequest; use actix_web::FromRequest;
pub use error::AuthenticationError; pub use error::AuthenticationError;
use futures::future::err; use futures::future::err;
@ -23,7 +24,7 @@ impl<P, D> GuardedData<P, D> {
} }
async fn auth_bearer( async fn auth_bearer(
auth: AuthController, auth: Data<AuthController>,
token: String, token: String,
index: Option<String>, index: Option<String>,
data: Option<D>, data: Option<D>,
@ -43,7 +44,7 @@ impl<P, D> GuardedData<P, D> {
} }
} }
async fn auth_token(auth: AuthController, data: Option<D>) -> Result<Self, ResponseError> async fn auth_token(auth: Data<AuthController>, data: Option<D>) -> Result<Self, ResponseError>
where where
P: Policy + 'static, P: Policy + 'static,
{ {
@ -60,7 +61,7 @@ impl<P, D> GuardedData<P, D> {
} }
async fn authenticate( async fn authenticate(
auth: AuthController, auth: Data<AuthController>,
token: String, token: String,
index: Option<String>, index: Option<String>,
) -> Result<Option<AuthFilter>, ResponseError> ) -> Result<Option<AuthFilter>, ResponseError>
@ -90,7 +91,7 @@ impl<P: Policy + 'static, D: 'static + Clone> FromRequest for GuardedData<P, D>
req: &actix_web::HttpRequest, req: &actix_web::HttpRequest,
_payload: &mut actix_web::dev::Payload, _payload: &mut actix_web::dev::Payload,
) -> Self::Future { ) -> Self::Future {
match req.app_data::<AuthController>().cloned() { match req.app_data::<Data<AuthController>>().cloned() {
Some(auth) => match req Some(auth) => match req
.headers() .headers()
.get("Authorization") .get("Authorization")
@ -122,10 +123,15 @@ impl<P: Policy + 'static, D: 'static + Clone> FromRequest for GuardedData<P, D>
} }
pub trait Policy { pub trait Policy {
fn authenticate(auth: AuthController, token: &str, index: Option<&str>) -> Option<AuthFilter>; fn authenticate(
auth: Data<AuthController>,
token: &str,
index: Option<&str>,
) -> Option<AuthFilter>;
} }
pub mod policies { pub mod policies {
use actix_web::web::Data;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use meilisearch_auth::{AuthController, AuthFilter, SearchRules}; use meilisearch_auth::{AuthController, AuthFilter, SearchRules};
// reexport actions in policies in order to be used in routes configuration. // reexport actions in policies in order to be used in routes configuration.
@ -178,7 +184,7 @@ pub mod policies {
/// Otherwise, returns an object containing the generated permissions: the search filters to add to a search, and the list of allowed indexes /// Otherwise, returns an object containing the generated permissions: the search filters to add to a search, and the list of allowed indexes
/// (that may contain more indexes than requested). /// (that may contain more indexes than requested).
fn authenticate( fn authenticate(
auth: AuthController, auth: Data<AuthController>,
token: &str, token: &str,
index: Option<&str>, index: Option<&str>,
) -> Option<AuthFilter> { ) -> Option<AuthFilter> {

View File

@ -88,7 +88,7 @@ fn is_empty_db(db_path: impl AsRef<Path>) -> bool {
pub fn create_app( pub fn create_app(
index_scheduler: Data<IndexScheduler>, index_scheduler: Data<IndexScheduler>,
auth_controller: AuthController, auth_controller: Data<AuthController>,
opt: Opt, opt: Opt,
analytics: Arc<dyn Analytics>, analytics: Arc<dyn Analytics>,
enable_dashboard: bool, enable_dashboard: bool,
@ -136,7 +136,7 @@ enum OnFailure {
KeepDb, KeepDb,
} }
pub fn setup_meilisearch(opt: &Opt) -> anyhow::Result<(Arc<IndexScheduler>, AuthController)> { pub fn setup_meilisearch(opt: &Opt) -> anyhow::Result<(Arc<IndexScheduler>, Arc<AuthController>)> {
let empty_db = is_empty_db(&opt.db_path); let empty_db = is_empty_db(&opt.db_path);
let (index_scheduler, auth_controller) = if let Some(ref snapshot_path) = opt.import_snapshot { let (index_scheduler, auth_controller) = if let Some(ref snapshot_path) = opt.import_snapshot {
let snapshot_path_exists = snapshot_path.exists(); let snapshot_path_exists = snapshot_path.exists();
@ -195,6 +195,7 @@ pub fn setup_meilisearch(opt: &Opt) -> anyhow::Result<(Arc<IndexScheduler>, Auth
// We create a loop in a thread that registers snapshotCreation tasks // We create a loop in a thread that registers snapshotCreation tasks
let index_scheduler = Arc::new(index_scheduler); let index_scheduler = Arc::new(index_scheduler);
let auth_controller = Arc::new(auth_controller);
if let ScheduleSnapshot::Enabled(snapshot_delay) = opt.schedule_snapshot { if let ScheduleSnapshot::Enabled(snapshot_delay) = opt.schedule_snapshot {
let snapshot_delay = Duration::from_secs(snapshot_delay); let snapshot_delay = Duration::from_secs(snapshot_delay);
let index_scheduler = index_scheduler.clone(); let index_scheduler = index_scheduler.clone();
@ -380,7 +381,7 @@ fn import_dump(
pub fn configure_data( pub fn configure_data(
config: &mut web::ServiceConfig, config: &mut web::ServiceConfig,
index_scheduler: Data<IndexScheduler>, index_scheduler: Data<IndexScheduler>,
auth: AuthController, auth: Data<AuthController>,
opt: &Opt, opt: &Opt,
analytics: Arc<dyn Analytics>, analytics: Arc<dyn Analytics>,
) { ) {

View File

@ -74,13 +74,14 @@ async fn main() -> anyhow::Result<()> {
async fn run_http( async fn run_http(
index_scheduler: Arc<IndexScheduler>, index_scheduler: Arc<IndexScheduler>,
auth_controller: AuthController, auth_controller: Arc<AuthController>,
opt: Opt, opt: Opt,
analytics: Arc<dyn Analytics>, analytics: Arc<dyn Analytics>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let enable_dashboard = &opt.env == "development"; let enable_dashboard = &opt.env == "development";
let opt_clone = opt.clone(); let opt_clone = opt.clone();
let index_scheduler = Data::from(index_scheduler); let index_scheduler = Data::from(index_scheduler);
let auth_controller = Data::from(auth_controller);
let http_server = HttpServer::new(move || { let http_server = HttpServer::new(move || {
create_app( create_app(

View File

@ -1,5 +1,6 @@
use std::str; use std::str;
use actix_web::web::Data;
use actix_web::{web, HttpRequest, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use deserr::actix_web::{AwebJson, AwebQueryParameter}; use deserr::actix_web::{AwebJson, AwebQueryParameter};
use deserr::Deserr; use deserr::Deserr;
@ -35,7 +36,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
} }
pub async fn create_api_key( pub async fn create_api_key(
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_CREATE }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_CREATE }>, Data<AuthController>>,
body: AwebJson<CreateApiKey, DeserrJsonError>, body: AwebJson<CreateApiKey, DeserrJsonError>,
_req: HttpRequest, _req: HttpRequest,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
@ -66,7 +67,7 @@ impl ListApiKeys {
} }
pub async fn list_api_keys( pub async fn list_api_keys(
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_GET }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_GET }>, Data<AuthController>>,
list_api_keys: AwebQueryParameter<ListApiKeys, DeserrQueryParamError>, list_api_keys: AwebQueryParameter<ListApiKeys, DeserrQueryParamError>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let paginate = list_api_keys.into_inner().as_pagination(); let paginate = list_api_keys.into_inner().as_pagination();
@ -84,7 +85,7 @@ pub async fn list_api_keys(
} }
pub async fn get_api_key( pub async fn get_api_key(
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_GET }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_GET }>, Data<AuthController>>,
path: web::Path<AuthParam>, path: web::Path<AuthParam>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let key = path.into_inner().key; let key = path.into_inner().key;
@ -103,7 +104,7 @@ pub async fn get_api_key(
} }
pub async fn patch_api_key( pub async fn patch_api_key(
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_UPDATE }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_UPDATE }>, Data<AuthController>>,
body: AwebJson<PatchApiKey, DeserrJsonError>, body: AwebJson<PatchApiKey, DeserrJsonError>,
path: web::Path<AuthParam>, path: web::Path<AuthParam>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
@ -123,7 +124,7 @@ pub async fn patch_api_key(
} }
pub async fn delete_api_key( pub async fn delete_api_key(
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_DELETE }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_DELETE }>, Data<AuthController>>,
path: web::Path<AuthParam>, path: web::Path<AuthParam>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let key = path.into_inner().key; let key = path.into_inner().key;

View File

@ -19,7 +19,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
pub async fn create_dump( pub async fn create_dump(
index_scheduler: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<IndexScheduler>>,
auth_controller: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<AuthController>>,
req: HttpRequest, req: HttpRequest,
analytics: web::Data<dyn Analytics>, analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {

View File

@ -17,7 +17,7 @@ pub fn configure(config: &mut web::ServiceConfig) {
pub async fn get_metrics( pub async fn get_metrics(
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
auth_controller: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<AuthController>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let auth_filters = index_scheduler.filters(); let auth_filters = index_scheduler.filters();
if !auth_filters.all_indexes_authorized() { if !auth_filters.all_indexes_authorized() {

View File

@ -238,7 +238,7 @@ pub struct Stats {
async fn get_stats( async fn get_stats(
index_scheduler: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<IndexScheduler>>,
auth_controller: GuardedData<ActionPolicy<{ actions::STATS_GET }>, AuthController>, auth_controller: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<AuthController>>,
req: HttpRequest, req: HttpRequest,
analytics: web::Data<dyn Analytics>, analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
@ -253,7 +253,7 @@ async fn get_stats(
pub fn create_all_stats( pub fn create_all_stats(
index_scheduler: Data<IndexScheduler>, index_scheduler: Data<IndexScheduler>,
auth_controller: AuthController, auth_controller: Data<AuthController>,
filters: &meilisearch_auth::AuthFilter, filters: &meilisearch_auth::AuthFilter,
) -> Result<Stats, ResponseError> { ) -> Result<Stats, ResponseError> {
let mut last_task: Option<OffsetDateTime> = None; let mut last_task: Option<OffsetDateTime> = None;
@ -318,9 +318,14 @@ struct KeysResponse {
pub async fn get_health( pub async fn get_health(
req: HttpRequest, req: HttpRequest,
index_scheduler: Data<IndexScheduler>,
auth_controller: Data<AuthController>,
analytics: web::Data<dyn Analytics>, analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
analytics.health_seen(&req); analytics.health_seen(&req);
index_scheduler.health().unwrap();
auth_controller.health().unwrap();
Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" }))) Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" })))
} }

View File

@ -82,7 +82,7 @@ impl Server {
> { > {
actix_web::test::init_service(create_app( actix_web::test::init_service(create_app(
self.service.index_scheduler.clone().into(), self.service.index_scheduler.clone().into(),
self.service.auth.clone(), self.service.auth.clone().into(),
self.service.options.clone(), self.service.options.clone(),
analytics::MockAnalytics::new(&self.service.options), analytics::MockAnalytics::new(&self.service.options),
true, true,

View File

@ -13,7 +13,7 @@ use crate::common::encoder::Encoder;
pub struct Service { pub struct Service {
pub index_scheduler: Arc<IndexScheduler>, pub index_scheduler: Arc<IndexScheduler>,
pub auth: AuthController, pub auth: Arc<AuthController>,
pub options: Opt, pub options: Opt,
pub api_key: Option<String>, pub api_key: Option<String>,
} }
@ -107,7 +107,7 @@ impl Service {
pub async fn request(&self, mut req: test::TestRequest) -> (Value, StatusCode) { pub async fn request(&self, mut req: test::TestRequest) -> (Value, StatusCode) {
let app = test::init_service(create_app( let app = test::init_service(create_app(
self.index_scheduler.clone().into(), self.index_scheduler.clone().into(),
self.auth.clone(), self.auth.clone().into(),
self.options.clone(), self.options.clone(),
analytics::MockAnalytics::new(&self.options), analytics::MockAnalytics::new(&self.options),
true, true,