fix tests

This commit is contained in:
mpostma 2021-09-28 18:10:09 +02:00
parent ddfd7def35
commit 692c676625
24 changed files with 325 additions and 481 deletions

1
Cargo.lock generated
View File

@ -1830,7 +1830,6 @@ dependencies = [
[[package]] [[package]]
name = "milli" name = "milli"
version = "0.16.0" version = "0.16.0"
source = "git+https://github.com/meilisearch/milli.git?branch=main#b2a332599ebdbf492360ddd3e98c3a14fb84608e"
dependencies = [ dependencies = [
"bimap", "bimap",
"bincode", "bincode",

View File

@ -49,7 +49,7 @@ meilisearch-lib = { path = "../meilisearch-lib" }
meilisearch-error = { path = "../meilisearch-error" } meilisearch-error = { path = "../meilisearch-error" }
meilisearch-tokenizer = { git = "https://github.com/meilisearch/tokenizer.git", tag = "v0.2.5" } meilisearch-tokenizer = { git = "https://github.com/meilisearch/tokenizer.git", tag = "v0.2.5" }
memmap = "0.7.0" memmap = "0.7.0"
milli = { git = "https://github.com/meilisearch/milli.git", branch = "main" } milli = { path = "../../milli/milli" }
mime = "0.3.16" mime = "0.3.16"
num_cpus = "1.13.0" num_cpus = "1.13.0"
once_cell = "1.8.0" once_cell = "1.8.0"

View File

@ -1,4 +1,3 @@
//pub mod compression;
mod env; mod env;
pub use env::EnvSizer; pub use env::EnvSizer;

View File

@ -47,6 +47,9 @@ pub mod analytics;
pub mod helpers; pub mod helpers;
pub mod option; pub mod option;
pub mod routes; pub mod routes;
use std::path::Path;
use std::time::Duration;
use crate::extractors::authentication::AuthConfig; use crate::extractors::authentication::AuthConfig;
pub use option::Opt; pub use option::Opt;
@ -81,6 +84,53 @@ impl ApiKeys {
} }
} }
pub fn setup_meilisearch(opt: &Opt) -> anyhow::Result<MeiliSearch> {
let mut meilisearch = MeiliSearch::builder();
meilisearch
.set_max_index_size(opt.max_index_size.get_bytes() as usize)
.set_max_update_store_size(opt.max_udb_size.get_bytes() as usize)
.set_ignore_missing_snapshot(opt.ignore_missing_snapshot)
.set_ignore_snapshot_if_db_exists(opt.ignore_snapshot_if_db_exists)
.set_dump_dst(opt.dumps_dir.clone())
.set_snapshot_interval(Duration::from_secs(opt.snapshot_interval_sec))
.set_snapshot_dir(opt.snapshot_dir.clone());
if let Some(ref path) = opt.import_snapshot {
meilisearch.set_import_snapshot(path.clone());
}
if let Some(ref path) = opt.import_dump {
meilisearch.set_dump_src(path.clone());
}
if opt.schedule_snapshot {
meilisearch.set_schedule_snapshot();
}
meilisearch.build(opt.db_path.clone(), opt.indexer_options.clone())
}
/// Cleans and setup the temporary file folder in the database directory. This must be done after
/// the meilisearch instance has been created, to not interfere with the snapshot and dump loading.
pub fn setup_temp_dir(db_path: impl AsRef<Path>) -> anyhow::Result<()> {
// Set the tempfile directory in the current db path, to avoid cross device references. Also
// remove the previous outstanding files found there
//
// TODO: if two processes open the same db, one might delete the other tmpdir. Need to make
// sure that no one is using it before deleting it.
let temp_path = db_path.as_ref().join("tmp");
// Ignore error if tempdir doesn't exist
let _ = std::fs::remove_dir_all(&temp_path);
std::fs::create_dir_all(&temp_path)?;
if cfg!(windows) {
std::env::set_var("TMP", temp_path);
} else {
std::env::set_var("TMPDIR", temp_path);
}
Ok(())
}
pub fn configure_data( pub fn configure_data(
config: &mut web::ServiceConfig, config: &mut web::ServiceConfig,
data: MeiliSearch, data: MeiliSearch,

View File

@ -1,7 +1,7 @@
use std::{env, path::Path, time::Duration}; use std::env;
use actix_web::HttpServer; use actix_web::HttpServer;
use meilisearch_http::{create_app, Opt}; use meilisearch_http::{Opt, create_app, setup_meilisearch};
use meilisearch_lib::MeiliSearch; use meilisearch_lib::MeiliSearch;
use structopt::StructOpt; use structopt::StructOpt;
@ -27,53 +27,6 @@ fn setup(opt: &Opt) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
/// Cleans and setup the temporary file folder in the database directory. This must be done after
/// the meilisearch instance has been created, to not interfere with the snapshot and dump loading.
fn setup_temp_dir(db_path: impl AsRef<Path>) -> anyhow::Result<()> {
// Set the tempfile directory in the current db path, to avoid cross device references. Also
// remove the previous outstanding files found there
//
// TODO: if two processes open the same db, one might delete the other tmpdir. Need to make
// sure that no one is using it before deleting it.
let temp_path = db_path.as_ref().join("tmp");
// Ignore error if tempdir doesn't exist
let _ = std::fs::remove_dir_all(&temp_path);
std::fs::create_dir_all(&temp_path)?;
if cfg!(windows) {
std::env::set_var("TMP", temp_path);
} else {
std::env::set_var("TMPDIR", temp_path);
}
Ok(())
}
fn setup_meilisearch(opt: &Opt) -> anyhow::Result<MeiliSearch> {
let mut meilisearch = MeiliSearch::builder();
meilisearch
.set_max_index_size(opt.max_index_size.get_bytes() as usize)
.set_max_update_store_size(opt.max_udb_size.get_bytes() as usize)
.set_ignore_missing_snapshot(opt.ignore_missing_snapshot)
.set_ignore_snapshot_if_db_exists(opt.ignore_snapshot_if_db_exists)
.set_dump_dst(opt.dumps_dir.clone())
.set_snapshot_interval(Duration::from_secs(opt.snapshot_interval_sec))
.set_snapshot_dir(opt.snapshot_dir.clone());
if let Some(ref path) = opt.import_snapshot {
meilisearch.set_import_snapshot(path.clone());
}
if let Some(ref path) = opt.import_dump {
meilisearch.set_dump_src(path.clone());
}
if opt.schedule_snapshot {
meilisearch.set_schedule_snapshot();
}
meilisearch.build(opt.db_path.clone(), opt.indexer_options.clone())
}
#[actix_web::main] #[actix_web::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let opt = Opt::from_args(); let opt = Opt::from_args();
@ -92,7 +45,9 @@ async fn main() -> anyhow::Result<()> {
let meilisearch = setup_meilisearch(&opt)?; let meilisearch = setup_meilisearch(&opt)?;
setup_temp_dir(&opt.db_path)?; // Setup the temp directory to be in the db folder. This is important, since temporary file
// don't support to be persisted accross filesystem boundaries.
meilisearch_http::setup_temp_dir(&opt.db_path)?;
#[cfg(all(not(debug_assertions), feature = "analytics"))] #[cfg(all(not(debug_assertions), feature = "analytics"))]
if !opt.no_analytics { if !opt.no_analytics {

View File

@ -1,9 +1,5 @@
use byte_unit::ByteError;
use std::fmt;
use std::io::{BufReader, Read}; use std::io::{BufReader, Read};
use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::fs; use std::fs;
@ -14,7 +10,6 @@ use rustls::{
RootCertStore, RootCertStore,
}; };
use structopt::StructOpt; use structopt::StructOpt;
use sysinfo::{RefreshKind, System, SystemExt};
use meilisearch_lib::options::IndexerOpts; use meilisearch_lib::options::IndexerOpts;
const POSSIBLE_ENV: [&str; 2] = ["development", "production"]; const POSSIBLE_ENV: [&str; 2] = ["development", "production"];
@ -177,63 +172,6 @@ impl Opt {
} }
} }
/// A type used to detect the max memory available and use 2/3 of it.
#[derive(Debug, Clone, Copy)]
pub struct MaxMemory(Option<Byte>);
impl FromStr for MaxMemory {
type Err = ByteError;
fn from_str(s: &str) -> Result<MaxMemory, ByteError> {
Byte::from_str(s).map(Some).map(MaxMemory)
}
}
impl Default for MaxMemory {
fn default() -> MaxMemory {
MaxMemory(
total_memory_bytes()
.map(|bytes| bytes * 2 / 3)
.map(Byte::from_bytes),
)
}
}
impl fmt::Display for MaxMemory {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Some(memory) => write!(f, "{}", memory.get_appropriate_unit(true)),
None => f.write_str("unknown"),
}
}
}
impl Deref for MaxMemory {
type Target = Option<Byte>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl MaxMemory {
pub fn unlimited() -> Self {
Self(None)
}
}
/// Returns the total amount of bytes available or `None` if this system isn't supported.
fn total_memory_bytes() -> Option<u64> {
if System::IS_SUPPORTED {
let memory_kind = RefreshKind::new().with_memory();
let mut system = System::new_with_specifics(memory_kind);
system.refresh_memory();
Some(system.total_memory() * 1024) // KiB into bytes
} else {
None
}
}
fn load_certs(filename: PathBuf) -> anyhow::Result<Vec<rustls::Certificate>> { fn load_certs(filename: PathBuf) -> anyhow::Result<Vec<rustls::Certificate>> {
let certfile = fs::File::open(filename).map_err(|_| anyhow::anyhow!("cannot open certificate file"))?; let certfile = fs::File::open(filename).map_err(|_| anyhow::anyhow!("cannot open certificate file"))?;
let mut reader = BufReader::new(certfile); let mut reader = BufReader::new(certfile);

View File

@ -106,7 +106,7 @@ pub async fn delete_document(
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let DocumentParam { document_id, index_uid } = path.into_inner(); let DocumentParam { document_id, index_uid } = path.into_inner();
let update = Update::DeleteDocuments(vec![document_id]); let update = Update::DeleteDocuments(vec![document_id]);
let update_status = meilisearch.register_update(index_uid, update).await?; let update_status = meilisearch.register_update(index_uid, update, false).await?;
debug!("returns: {:?}", update_status); debug!("returns: {:?}", update_status);
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
} }
@ -170,7 +170,7 @@ pub async fn add_documents(
format: DocumentAdditionFormat::Json, format: DocumentAdditionFormat::Json,
}; };
let update_status = meilisearch let update_status = meilisearch
.register_update(path.into_inner().index_uid, update) .register_update(path.into_inner().index_uid, update, true)
.await?; .await?;
debug!("returns: {:?}", update_status); debug!("returns: {:?}", update_status);
@ -193,7 +193,7 @@ pub async fn update_documents(
format: DocumentAdditionFormat::Json, format: DocumentAdditionFormat::Json,
}; };
let update_status = meilisearch let update_status = meilisearch
.register_update(path.into_inner().index_uid, update) .register_update(path.into_inner().index_uid, update, true)
.await?; .await?;
debug!("returns: {:?}", update_status); debug!("returns: {:?}", update_status);
@ -216,7 +216,7 @@ pub async fn delete_documents(
.collect(); .collect();
let update = Update::DeleteDocuments(ids); let update = Update::DeleteDocuments(ids);
let update_status = meilisearch.register_update(path.into_inner().index_uid, update).await?; let update_status = meilisearch.register_update(path.into_inner().index_uid, update, false).await?;
debug!("returns: {:?}", update_status); debug!("returns: {:?}", update_status);
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
} }
@ -226,7 +226,7 @@ pub async fn clear_all_documents(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let update = Update::ClearDocuments; let update = Update::ClearDocuments;
let update_status = meilisearch.register_update(path.into_inner().index_uid, update).await?; let update_status = meilisearch.register_update(path.into_inner().index_uid, update, false).await?;
debug!("returns: {:?}", update_status); debug!("returns: {:?}", update_status);
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
} }

View File

@ -18,7 +18,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service( cfg.service(
web::resource("") web::resource("")
.route(web::get().to(list_indexes)) .route(web::get().to(list_indexes))
//.route(web::post().to(create_index)), .route(web::post().to(create_index)),
) )
.service( .service(
web::scope("/{index_uid}") web::scope("/{index_uid}")
@ -26,7 +26,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
web::resource("") web::resource("")
.route(web::get().to(get_index)) .route(web::get().to(get_index))
.route(web::put().to(update_index)) .route(web::put().to(update_index))
//.route(web::delete().to(delete_index)), .route(web::delete().to(delete_index)),
) )
.service(web::resource("/stats").route(web::get().to(get_index_stats))) .service(web::resource("/stats").route(web::get().to(get_index_stats)))
.service(web::scope("/documents").configure(documents::configure)) .service(web::scope("/documents").configure(documents::configure))
@ -49,14 +49,14 @@ pub struct IndexCreateRequest {
primary_key: Option<String>, primary_key: Option<String>,
} }
//pub async fn create_index( pub async fn create_index(
//data: GuardedData<Private, MeiliSearch>, meilisearch: GuardedData<Private, MeiliSearch>,
//body: web::Json<IndexCreateRequest>, body: web::Json<IndexCreateRequest>,
//) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
//let body = body.into_inner(); let body = body.into_inner();
//let meta = data.create_index(body.uid, body.primary_key).await?; let meta = meilisearch.create_index(body.uid, body.primary_key).await?;
//Ok(HttpResponse::Created().json(meta)) Ok(HttpResponse::Created().json(meta))
//} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)] #[serde(rename_all = "camelCase", deny_unknown_fields)]
@ -102,13 +102,13 @@ pub async fn update_index(
Ok(HttpResponse::Ok().json(meta)) Ok(HttpResponse::Ok().json(meta))
} }
//pub async fn delete_index( pub async fn delete_index(
//data: GuardedData<Private, MeiliSearch>, meilisearch: GuardedData<Private, MeiliSearch>,
//path: web::Path<IndexParam>, path: web::Path<IndexParam>,
//) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
//data.delete_index(path.index_uid.clone()).await?; meilisearch.delete_index(path.index_uid.clone()).await?;
//Ok(HttpResponse::NoContent().finish()) Ok(HttpResponse::NoContent().finish())
//} }
pub async fn get_index_stats( pub async fn get_index_stats(
meilisearch: GuardedData<Private, MeiliSearch>, meilisearch: GuardedData<Private, MeiliSearch>,

View File

@ -30,7 +30,7 @@ macro_rules! make_setting_route {
..Default::default() ..Default::default()
}; };
let update = Update::Settings(settings); let update = Update::Settings(settings);
let update_status = meilisearch.register_update(index_uid.into_inner(), update).await?; let update_status = meilisearch.register_update(index_uid.into_inner(), update, false).await?;
debug!("returns: {:?}", update_status); debug!("returns: {:?}", update_status);
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
} }
@ -49,7 +49,7 @@ macro_rules! make_setting_route {
}; };
let update = Update::Settings(settings); let update = Update::Settings(settings);
let update_status = meilisearch.register_update(index_uid.into_inner(), update).await?; let update_status = meilisearch.register_update(index_uid.into_inner(), update, true).await?;
debug!("returns: {:?}", update_status); debug!("returns: {:?}", update_status);
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
} }
@ -159,7 +159,7 @@ pub async fn update_all(
let update = Update::Settings(settings); let update = Update::Settings(settings);
let update_result = meilisearch let update_result = meilisearch
.register_update(index_uid.into_inner(), update) .register_update(index_uid.into_inner(), update, true)
.await?; .await?;
let json = serde_json::json!({ "updateId": update_result.id() }); let json = serde_json::json!({ "updateId": update_result.id() });
debug!("returns: {:?}", json); debug!("returns: {:?}", json);
@ -183,7 +183,7 @@ pub async fn delete_all(
let update = Update::Settings(settings.into_unchecked()); let update = Update::Settings(settings.into_unchecked());
let update_result = data let update_result = data
.register_update(index_uid.into_inner(), update) .register_update(index_uid.into_inner(), update, false)
.await?; .await?;
let json = serde_json::json!({ "updateId": update_result.id() }); let json = serde_json::json!({ "updateId": update_result.id() });
debug!("returns: {:?}", json); debug!("returns: {:?}", json);

View File

@ -280,18 +280,17 @@ pub async fn get_health() -> Result<HttpResponse, ResponseError> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::data::Data;
use crate::extractors::authentication::GuardedData; use crate::extractors::authentication::GuardedData;
/// A type implemented for a route that uses a authentication policy `Policy`. /// A type implemented for a route that uses a authentication policy `Policy`.
/// ///
/// This trait is used for regression testing of route authenticaton policies. /// This trait is used for regression testing of route authenticaton policies.
trait Is<Policy, T> {} trait Is<Policy, Data, T> {}
macro_rules! impl_is_policy { macro_rules! impl_is_policy {
($($param:ident)*) => { ($($param:ident)*) => {
impl<Policy, Func, $($param,)* Res> Is<Policy, (($($param,)*), Res)> for Func impl<Policy, Func, Data, $($param,)* Res> Is<Policy, Data, (($($param,)*), Res)> for Func
where Func: Fn(GuardedData<Policy, MeiliSearch>, $($param,)*) -> Res {} where Func: Fn(GuardedData<Policy, Data>, $($param,)*) -> Res {}
}; };
} }
@ -310,7 +309,7 @@ mod test {
($($policy:ident => { $($route:expr,)*})*) => { ($($policy:ident => { $($route:expr,)*})*) => {
#[test] #[test]
fn test_auth() { fn test_auth() {
$($(let _: &dyn Is<$policy, _> = &$route;)*)* $($(let _: &dyn Is<$policy, _, _> = &$route;)*)*
} }
}; };
} }

View File

@ -2,12 +2,14 @@ use std::path::Path;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use byte_unit::{Byte, ByteUnit}; use byte_unit::{Byte, ByteUnit};
use meilisearch_http::setup_meilisearch;
use meilisearch_lib::options::{IndexerOpts, MaxMemory};
use once_cell::sync::Lazy;
use serde_json::Value; use serde_json::Value;
use tempdir::TempDir; use tempfile::TempDir;
use urlencoding::encode; use urlencoding::encode;
use meilisearch_http::data::Data; use meilisearch_http::option::Opt;
use meilisearch_http::option::{IndexerOpts, MaxMemory, Opt};
use super::index::Index; use super::index::Index;
use super::service::Service; use super::service::Service;
@ -15,17 +17,25 @@ use super::service::Service;
pub struct Server { pub struct Server {
pub service: Service, pub service: Service,
// hold ownership to the tempdir while we use the server instance. // hold ownership to the tempdir while we use the server instance.
_dir: Option<tempdir::TempDir>, _dir: Option<TempDir>,
} }
static TEST_TEMP_DIR: Lazy<TempDir> = Lazy::new(|| TempDir::new().unwrap());
impl Server { impl Server {
pub async fn new() -> Self { pub async fn new() -> Self {
let dir = TempDir::new("meilisearch").unwrap(); let dir = TempDir::new().unwrap();
let opt = default_settings(dir.path()); if cfg!(windows) {
std::env::set_var("TMP", TEST_TEMP_DIR.path());
} else {
std::env::set_var("TMPDIR", TEST_TEMP_DIR.path());
}
let data = Data::new(opt).unwrap(); let options = default_settings(dir.path());
let service = Service(data);
let meilisearch = setup_meilisearch(&options).unwrap();
let service = Service { meilisearch, options };
Server { Server {
service, service,
@ -33,9 +43,9 @@ impl Server {
} }
} }
pub async fn new_with_options(opt: Opt) -> Self { pub async fn new_with_options(options: Opt) -> Self {
let data = Data::new(opt).unwrap(); let meilisearch = setup_meilisearch(&options).unwrap();
let service = Service(data); let service = Service { meilisearch, options };
Server { Server {
service, service,

View File

@ -1,14 +1,17 @@
use actix_web::{http::StatusCode, test}; use actix_web::{http::StatusCode, test};
use meilisearch_lib::MeiliSearch;
use serde_json::Value; use serde_json::Value;
use meilisearch_http::create_app; use meilisearch_http::{Opt, create_app};
use meilisearch_http::data::Data;
pub struct Service(pub Data); pub struct Service {
pub meilisearch: MeiliSearch,
pub options: Opt,
}
impl Service { impl Service {
pub async fn post(&self, url: impl AsRef<str>, body: Value) -> (Value, StatusCode) { pub async fn post(&self, url: impl AsRef<str>, body: Value) -> (Value, StatusCode) {
let app = test::init_service(create_app!(&self.0, true)).await; let app = test::init_service(create_app!(&self.meilisearch, true, &self.options)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri(url.as_ref()) .uri(url.as_ref())
@ -28,7 +31,7 @@ impl Service {
url: impl AsRef<str>, url: impl AsRef<str>,
body: impl AsRef<str>, body: impl AsRef<str>,
) -> (Value, StatusCode) { ) -> (Value, StatusCode) {
let app = test::init_service(create_app!(&self.0, true)).await; let app = test::init_service(create_app!(&self.meilisearch, true, &self.options)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri(url.as_ref()) .uri(url.as_ref())
@ -44,7 +47,7 @@ impl Service {
} }
pub async fn get(&self, url: impl AsRef<str>) -> (Value, StatusCode) { pub async fn get(&self, url: impl AsRef<str>) -> (Value, StatusCode) {
let app = test::init_service(create_app!(&self.0, true)).await; let app = test::init_service(create_app!(&self.meilisearch, true, &self.options)).await;
let req = test::TestRequest::get().uri(url.as_ref()).to_request(); let req = test::TestRequest::get().uri(url.as_ref()).to_request();
let res = test::call_service(&app, req).await; let res = test::call_service(&app, req).await;
@ -56,7 +59,7 @@ impl Service {
} }
pub async fn put(&self, url: impl AsRef<str>, body: Value) -> (Value, StatusCode) { pub async fn put(&self, url: impl AsRef<str>, body: Value) -> (Value, StatusCode) {
let app = test::init_service(create_app!(&self.0, true)).await; let app = test::init_service(create_app!(&self.meilisearch, true, &self.options)).await;
let req = test::TestRequest::put() let req = test::TestRequest::put()
.uri(url.as_ref()) .uri(url.as_ref())
@ -71,7 +74,7 @@ impl Service {
} }
pub async fn delete(&self, url: impl AsRef<str>) -> (Value, StatusCode) { pub async fn delete(&self, url: impl AsRef<str>) -> (Value, StatusCode) {
let app = test::init_service(create_app!(&self.0, true)).await; let app = test::init_service(create_app!(&self.meilisearch, true, &self.options)).await;
let req = test::TestRequest::delete().uri(url.as_ref()).to_request(); let req = test::TestRequest::delete().uri(url.as_ref()).to_request();
let res = test::call_service(&app, req).await; let res = test::call_service(&app, req).await;

View File

@ -16,7 +16,7 @@ async fn add_documents_test_json_content_types() {
// this is a what is expected and should work // this is a what is expected and should work
let server = Server::new().await; let server = Server::new().await;
let app = test::init_service(create_app!(&server.service.0, true)).await; let app = test::init_service(create_app!(&server.service.meilisearch, true, &server.service.options)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri("/indexes/dog/documents") .uri("/indexes/dog/documents")
.set_payload(document.to_string()) .set_payload(document.to_string())
@ -41,7 +41,7 @@ async fn add_documents_test_no_content_types() {
]); ]);
let server = Server::new().await; let server = Server::new().await;
let app = test::init_service(create_app!(&server.service.0, true)).await; let app = test::init_service(create_app!(&server.service.meilisearch, true, &server.service.options)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri("/indexes/dog/documents") .uri("/indexes/dog/documents")
.set_payload(document.to_string()) .set_payload(document.to_string())
@ -67,7 +67,7 @@ async fn add_documents_test_bad_content_types() {
]); ]);
let server = Server::new().await; let server = Server::new().await;
let app = test::init_service(create_app!(&server.service.0, true)).await; let app = test::init_service(create_app!(&server.service.meilisearch, true, &server.service.options)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri("/indexes/dog/documents") .uri("/indexes/dog/documents")
.set_payload(document.to_string()) .set_payload(document.to_string())

View File

@ -30,8 +30,8 @@ static DEFAULT_SETTINGS_VALUES: Lazy<HashMap<&'static str, Value>> = Lazy::new(|
#[actix_rt::test] #[actix_rt::test]
async fn get_settings_unexisting_index() { async fn get_settings_unexisting_index() {
let server = Server::new().await; let server = Server::new().await;
let (_response, code) = server.index("test").settings().await; let (response, code) = server.index("test").settings().await;
assert_eq!(code, 404) assert_eq!(code, 404, "{}", response)
} }
#[actix_rt::test] #[actix_rt::test]
@ -167,8 +167,8 @@ async fn update_setting_unexisting_index() {
async fn update_setting_unexisting_index_invalid_uid() { async fn update_setting_unexisting_index_invalid_uid() {
let server = Server::new().await; let server = Server::new().await;
let index = server.index("test##! "); let index = server.index("test##! ");
let (_response, code) = index.update_settings(json!({})).await; let (response, code) = index.update_settings(json!({})).await;
assert_eq!(code, 400); assert_eq!(code, 400, "{}", response);
} }
macro_rules! test_setting_routes { macro_rules! test_setting_routes {

View File

@ -9,8 +9,8 @@ use meilisearch_http::Opt;
#[actix_rt::test] #[actix_rt::test]
async fn perform_snapshot() { async fn perform_snapshot() {
let temp = tempfile::tempdir_in(".").unwrap(); let temp = tempfile::tempdir().unwrap();
let snapshot_dir = tempfile::tempdir_in(".").unwrap(); let snapshot_dir = tempfile::tempdir().unwrap();
let options = Opt { let options = Opt {
snapshot_dir: snapshot_dir.path().to_owned(), snapshot_dir: snapshot_dir.path().to_owned(),
@ -29,7 +29,7 @@ async fn perform_snapshot() {
sleep(Duration::from_secs(2)).await; sleep(Duration::from_secs(2)).await;
let temp = tempfile::tempdir_in(".").unwrap(); let temp = tempfile::tempdir().unwrap();
let snapshot_path = snapshot_dir let snapshot_path = snapshot_dir
.path() .path()

View File

@ -33,7 +33,8 @@ main_error = "0.1.1"
meilisearch-error = { path = "../meilisearch-error" } meilisearch-error = { path = "../meilisearch-error" }
meilisearch-tokenizer = { git = "https://github.com/meilisearch/tokenizer.git", tag = "v0.2.5" } meilisearch-tokenizer = { git = "https://github.com/meilisearch/tokenizer.git", tag = "v0.2.5" }
memmap = "0.7.0" memmap = "0.7.0"
milli = { git = "https://github.com/meilisearch/milli.git", branch = "main" } #milli = { git = "https://github.com/meilisearch/milli.git", branch = "main" }
milli = { path = "../../milli/milli" }
mime = "0.3.16" mime = "0.3.16"
num_cpus = "1.13.0" num_cpus = "1.13.0"
once_cell = "1.8.0" once_cell = "1.8.0"

View File

@ -7,8 +7,7 @@ use heed::RoTxn;
use indexmap::IndexMap; use indexmap::IndexMap;
use meilisearch_tokenizer::{Analyzer, AnalyzerConfig, Token}; use meilisearch_tokenizer::{Analyzer, AnalyzerConfig, Token};
use milli::{ use milli::{
AscDesc, AscDescError, FieldId, FieldsIdsMap, FilterCondition, MatchingWords, SortError, AscDesc, FieldId, FieldsIdsMap, FilterCondition, MatchingWords, SortError
UserError,
}; };
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -2,7 +2,6 @@ use std::fs::File;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use log::{info, trace, warn}; use log::{info, trace, warn};
#[cfg(test)] #[cfg(test)]
@ -112,22 +111,16 @@ pub fn load_dump(
update_db_size: usize, update_db_size: usize,
indexer_opts: &IndexerOpts, indexer_opts: &IndexerOpts,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let tmp_src = tempfile::tempdir_in(".")?; let tmp_src = tempfile::tempdir()?;
let tmp_src_path = tmp_src.path(); let tmp_src_path = tmp_src.path();
println!("importing to {}", dst_path.as_ref().display());
crate::from_tar_gz(&src_path, tmp_src_path)?; crate::from_tar_gz(&src_path, tmp_src_path)?;
let meta_path = tmp_src_path.join(META_FILE_NAME); let meta_path = tmp_src_path.join(META_FILE_NAME);
let mut meta_file = File::open(&meta_path)?; let mut meta_file = File::open(&meta_path)?;
let meta: Metadata = serde_json::from_reader(&mut meta_file)?; let meta: Metadata = serde_json::from_reader(&mut meta_file)?;
let dst_dir = dst_path let tmp_dst = tempfile::tempdir()?;
.as_ref()
.parent()
.with_context(|| format!("Invalid db path: {}", dst_path.as_ref().display()))?;
let tmp_dst = tempfile::tempdir_in(dst_dir)?;
match meta { match meta {
Metadata::V1(meta) => { Metadata::V1(meta) => {
@ -168,9 +161,8 @@ impl DumpTask {
create_dir_all(&self.path).await?; create_dir_all(&self.path).await?;
let path_clone = self.path.clone();
let temp_dump_dir = let temp_dump_dir =
tokio::task::spawn_blocking(|| tempfile::TempDir::new_in(path_clone)).await??; tokio::task::spawn_blocking(|| tempfile::TempDir::new()).await??;
let temp_dump_path = temp_dump_dir.path().to_owned(); let temp_dump_path = temp_dump_dir.path().to_owned();
let meta = Metadata::new_v2(self.index_db_size, self.update_db_size); let meta = Metadata::new_v2(self.index_db_size, self.update_db_size);
@ -183,7 +175,7 @@ impl DumpTask {
UpdateMsg::dump(&self.update_handle, uuids, temp_dump_path.clone()).await?; UpdateMsg::dump(&self.update_handle, uuids, temp_dump_path.clone()).await?;
let dump_path = tokio::task::spawn_blocking(move || -> Result<PathBuf> { let dump_path = tokio::task::spawn_blocking(move || -> Result<PathBuf> {
let temp_dump_file = tempfile::NamedTempFile::new_in(&self.path)?; let temp_dump_file = tempfile::NamedTempFile::new()?;
crate::to_tar_gz(temp_dump_path, temp_dump_file.path()) crate::to_tar_gz(temp_dump_path, temp_dump_file.path())
.map_err(|e| DumpActorError::Internal(e.into()))?; .map_err(|e| DumpActorError::Internal(e.into()))?;

View File

@ -84,11 +84,14 @@ where U: UuidStore,
Ok(indexes) Ok(indexes)
} }
pub async fn create_index(&self, uid: String, primary_key: Option<String>) -> Result<(Uuid, Index)> { pub async fn create_index(&self, uid: String, primary_key: Option<String>) -> Result<Index> {
if !is_index_uid_valid(&uid) {
return Err(IndexResolverError::BadlyFormatted(uid));
}
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let index = self.index_store.create(uuid, primary_key).await?; let index = self.index_store.create(uuid, primary_key).await?;
self.index_uuid_store.insert(uid, uuid).await?; self.index_uuid_store.insert(uid, uuid).await?;
Ok((uuid, index)) Ok(index)
} }
pub async fn list(&self) -> Result<Vec<(String, Index)>> { pub async fn list(&self) -> Result<Vec<(String, Index)>> {
@ -109,11 +112,11 @@ where U: UuidStore,
Ok(indexes) Ok(indexes)
} }
pub async fn delete_index(&self, uid: String) -> Result<()> { pub async fn delete_index(&self, uid: String) -> Result<Uuid> {
match self.index_uuid_store.delete(uid.clone()).await? { match self.index_uuid_store.delete(uid.clone()).await? {
Some(uuid) => { Some(uuid) => {
let _ = self.index_store.delete(uuid).await; let _ = self.index_store.delete(uuid).await;
Ok(()) Ok(uuid)
} }
None => Err(IndexResolverError::UnexistingIndex(uid)), None => Err(IndexResolverError::UnexistingIndex(uid)),
} }
@ -148,3 +151,8 @@ where U: UuidStore,
} }
} }
} }
fn is_index_uid_valid(uid: &str) -> bool {
uid.chars()
.all(|x| x.is_ascii_alphanumeric() || x == '-' || x == '_')
}

View File

@ -23,7 +23,7 @@ use crate::index_controller::index_resolver::create_index_resolver;
use crate::index_controller::snapshot::SnapshotService; use crate::index_controller::snapshot::SnapshotService;
use crate::options::IndexerOpts; use crate::options::IndexerOpts;
use error::Result; use error::Result;
use crate::index::error::Result as IndexResult; use crate::index::error::{Result as IndexResult};
use self::dump_actor::load_dump; use self::dump_actor::load_dump;
use self::index_resolver::HardStateIndexResolver; use self::index_resolver::HardStateIndexResolver;
@ -33,29 +33,15 @@ use self::updates::UpdateMsg;
mod dump_actor; mod dump_actor;
pub mod error; pub mod error;
//pub mod indexes;
mod snapshot; mod snapshot;
pub mod update_file_store; pub mod update_file_store;
pub mod updates; pub mod updates;
//mod uuid_resolver;
mod index_resolver; mod index_resolver;
pub type Payload = Box< pub type Payload = Box<
dyn Stream<Item = std::result::Result<Bytes, PayloadError>> + Send + Sync + 'static + Unpin, dyn Stream<Item = std::result::Result<Bytes, PayloadError>> + Send + Sync + 'static + Unpin,
>; >;
macro_rules! time {
($e:expr) => {
{
let now = std::time::Instant::now();
let result = $e;
let elapsed = now.elapsed();
println!("elapsed at line {}: {}ms ({}ns)", line!(), elapsed.as_millis(), elapsed.as_nanos());
result
}
};
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct IndexMetadata { pub struct IndexMetadata {
@ -260,148 +246,27 @@ impl IndexController {
IndexControllerBuilder::default() IndexControllerBuilder::default()
} }
pub async fn register_update(&self, uid: String, update: Update) -> Result<UpdateStatus> { pub async fn register_update(&self, uid: String, update: Update, create_index: bool) -> Result<UpdateStatus> {
match self.index_resolver.get_uuid(uid).await { match self.index_resolver.get_uuid(uid).await {
Ok(uuid) => { Ok(uuid) => {
let update_result = UpdateMsg::update(&self.update_sender, uuid, update).await?; let update_result = UpdateMsg::update(&self.update_sender, uuid, update).await?;
Ok(update_result) Ok(update_result)
} }
Err(IndexResolverError::UnexistingIndex(name)) => { Err(IndexResolverError::UnexistingIndex(name)) => {
let (uuid, _) = self.index_resolver.create_index(name, None).await?; if create_index {
let update_result = UpdateMsg::update(&self.update_sender, uuid, update).await?; let index = self.index_resolver.create_index(name, None).await?;
let update_result = UpdateMsg::update(&self.update_sender, index.uuid, update).await?;
// ignore if index creation fails now, since it may already have been created // ignore if index creation fails now, since it may already have been created
Ok(update_result) Ok(update_result)
} else {
Err(IndexResolverError::UnexistingIndex(name).into())
}
} }
Err(e) => Err(e.into()), Err(e) => Err(e.into()),
} }
} }
//pub async fn add_documents(
//&self,
//uid: String,
//method: milli::update::IndexDocumentsMethod,
//payload: Payload,
//primary_key: Option<String>,
//) -> Result<UpdateStatus> {
//let perform_update = |uuid| async move {
//let meta = UpdateMeta::DocumentsAddition {
//method,
//primary_key,
//};
//let (sender, receiver) = mpsc::channel(10);
//// It is necessary to spawn a local task to send the payload to the update handle to
//// prevent dead_locking between the update_handle::update that waits for the update to be
//// registered and the update_actor that waits for the the payload to be sent to it.
//tokio::task::spawn_local(async move {
//payload
//.for_each(|r| async {
//let _ = sender.send(r).await;
//})
//.await
//});
//// This must be done *AFTER* spawning the task.
//self.update_handle.update(meta, receiver, uuid).await
//};
//match self.uuid_resolver.get(uid).await {
//Ok(uuid) => Ok(perform_update(uuid).await?),
//Err(UuidResolverError::UnexistingIndex(name)) => {
//let uuid = Uuid::new_v4();
//let status = perform_update(uuid).await?;
//// ignore if index creation fails now, since it may already have been created
//let _ = self.index_handle.create_index(uuid, None).await;
//self.uuid_resolver.insert(name, uuid).await?;
//Ok(status)
//}
//Err(e) => Err(e.into()),
//}
//}
//pub async fn clear_documents(&self, uid: String) -> Result<UpdateStatus> {
//let uuid = self.uuid_resolver.get(uid).await?;
//let meta = UpdateMeta::ClearDocuments;
//let (_, receiver) = mpsc::channel(1);
//let status = self.update_handle.update(meta, receiver, uuid).await?;
//Ok(status)
//}
//pub async fn delete_documents(
//&self,
//uid: String,
//documents: Vec<String>,
//) -> Result<UpdateStatus> {
//let uuid = self.uuid_resolver.get(uid).await?;
//let meta = UpdateMeta::DeleteDocuments { ids: documents };
//let (_, receiver) = mpsc::channel(1);
//let status = self.update_handle.update(meta, receiver, uuid).await?;
//Ok(status)
//}
//pub async fn update_settings(
//&self,
//uid: String,
//settings: Settings<Checked>,
//create: bool,
//) -> Result<UpdateStatus> {
//let perform_udpate = |uuid| async move {
//let meta = UpdateMeta::Settings(settings.into_unchecked());
//// Nothing so send, drop the sender right away, as not to block the update actor.
//let (_, receiver) = mpsc::channel(1);
//self.update_handle.update(meta, receiver, uuid).await
//};
//match self.uuid_resolver.get(uid).await {
//Ok(uuid) => Ok(perform_udpate(uuid).await?),
//Err(UuidResolverError::UnexistingIndex(name)) if create => {
//let uuid = Uuid::new_v4();
//let status = perform_udpate(uuid).await?;
//// ignore if index creation fails now, since it may already have been created
//let _ = self.index_handle.create_index(uuid, None).await;
//self.uuid_resolver.insert(name, uuid).await?;
//Ok(status)
//}
//Err(e) => Err(e.into()),
//}
//}
//pub async fn create_index(&self, index_settings: IndexSettings) -> Result<IndexMetadata> {
//let IndexSettings { uid, primary_key } = index_settings;
//let uid = uid.ok_or(IndexControllerError::MissingUid)?;
//let uuid = Uuid::new_v4();
//let meta = self.index_handle.create_index(uuid, primary_key).await?;
//self.uuid_resolver.insert(uid.clone(), uuid).await?;
//let meta = IndexMetadata {
//uuid,
//name: uid.clone(),
//uid,
//meta,
//};
//Ok(meta)
//}
//pub async fn delete_index(&self, uid: String) -> Result<()> {
//let uuid = self.uuid_resolver.delete(uid).await?;
//// We remove the index from the resolver synchronously, and effectively perform the index
//// deletion as a background task.
//let update_handle = self.update_handle.clone();
//let index_handle = self.index_handle.clone();
//tokio::spawn(async move {
//if let Err(e) = update_handle.delete(uuid).await {
//error!("Error while deleting index: {}", e);
//}
//if let Err(e) = index_handle.delete(uuid).await {
//error!("Error while deleting index: {}", e);
//}
//});
//Ok(())
//}
pub async fn update_status(&self, uid: String, id: u64) -> Result<UpdateStatus> { pub async fn update_status(&self, uid: String, id: u64) -> Result<UpdateStatus> {
let uuid = self.index_resolver.get_uuid(uid).await?; let uuid = self.index_resolver.get_uuid(uid).await?;
let result = UpdateMsg::get_update(&self.update_sender, uuid, id).await?; let result = UpdateMsg::get_update(&self.update_sender, uuid, id).await?;
@ -481,8 +346,8 @@ impl IndexController {
} }
pub async fn search(&self, uid: String, query: SearchQuery) -> Result<SearchResult> { pub async fn search(&self, uid: String, query: SearchQuery) -> Result<SearchResult> {
let index = time!(self.index_resolver.get_index(uid.clone()).await?); let index = self.index_resolver.get_index(uid.clone()).await?;
let result = time!(spawn_blocking(move || time!(index.perform_search(query))).await??); let result = spawn_blocking(move || index.perform_search(query)).await??;
Ok(result) Ok(result)
} }
@ -549,6 +414,33 @@ impl IndexController {
pub async fn dump_info(&self, uid: String) -> Result<DumpInfo> { pub async fn dump_info(&self, uid: String) -> Result<DumpInfo> {
Ok(self.dump_handle.dump_info(uid).await?) Ok(self.dump_handle.dump_info(uid).await?)
} }
pub async fn create_index(&self, uid: String, primary_key: Option<String>) -> Result<IndexMetadata> {
let index = self.index_resolver.create_index(uid.clone(), primary_key).await?;
let meta = spawn_blocking(move || -> IndexResult<_> {
let meta = index.meta()?;
let meta = IndexMetadata {
uuid: index.uuid,
uid: uid.clone(),
name: uid,
meta,
};
Ok(meta)
}).await??;
Ok(meta)
}
pub async fn delete_index(&self, uid: String) -> Result<()> {
let uuid = self.index_resolver.delete_index(uid).await?;
let update_sender = self.update_sender.clone();
tokio::spawn(async move {
let _ = UpdateMsg::delete(&update_sender, uuid).await;
});
Ok(())
}
} }
pub async fn get_arc_ownership_blocking<T>(mut item: Arc<T>) -> T { pub async fn get_arc_ownership_blocking<T>(mut item: Arc<T>) -> T {

View File

@ -57,7 +57,7 @@ impl SnapshotService {
let snapshot_dir = self.snapshot_path.clone(); let snapshot_dir = self.snapshot_path.clone();
fs::create_dir_all(&snapshot_dir).await?; fs::create_dir_all(&snapshot_dir).await?;
let temp_snapshot_dir = let temp_snapshot_dir =
spawn_blocking(move || tempfile::tempdir_in(snapshot_dir)).await??; spawn_blocking(move || tempfile::tempdir()).await??;
let temp_snapshot_path = temp_snapshot_dir.path().to_owned(); let temp_snapshot_path = temp_snapshot_dir.path().to_owned();
let indexes = self let indexes = self
@ -71,12 +71,11 @@ impl SnapshotService {
UpdateMsg::snapshot(&self.update_sender, temp_snapshot_path.clone(), indexes).await?; UpdateMsg::snapshot(&self.update_sender, temp_snapshot_path.clone(), indexes).await?;
let snapshot_dir = self.snapshot_path.clone();
let snapshot_path = self let snapshot_path = self
.snapshot_path .snapshot_path
.join(format!("{}.snapshot", self.db_name)); .join(format!("{}.snapshot", self.db_name));
let snapshot_path = spawn_blocking(move || -> anyhow::Result<PathBuf> { let snapshot_path = spawn_blocking(move || -> anyhow::Result<PathBuf> {
let temp_snapshot_file = tempfile::NamedTempFile::new_in(snapshot_dir)?; let temp_snapshot_file = tempfile::NamedTempFile::new()?;
let temp_snapshot_file_path = temp_snapshot_file.path().to_owned(); let temp_snapshot_file_path = temp_snapshot_file.path().to_owned();
crate::compression::to_tar_gz(temp_snapshot_path, temp_snapshot_file_path)?; crate::compression::to_tar_gz(temp_snapshot_path, temp_snapshot_file_path)?;
temp_snapshot_file.persist(&snapshot_path)?; temp_snapshot_file.persist(&snapshot_path)?;
@ -137,13 +136,6 @@ pub fn load_snapshot(
//use uuid::Uuid; //use uuid::Uuid;
//use super::*; //use super::*;
//use crate::index_controller::index_actor::MockIndexActorHandle;
//use crate::index_controller::updates::{
//error::UpdateActorError, MockUpdateActorHandle, UpdateActorHandleImpl,
//};
//use crate::index_controller::uuid_resolver::{
//error::UuidResolverError, MockUuidResolverHandle,
//};
//#[actix_rt::test] //#[actix_rt::test]
//async fn test_normal() { //async fn test_normal() {
@ -191,7 +183,7 @@ pub fn load_snapshot(
//uuid_resolver //uuid_resolver
//.expect_snapshot() //.expect_snapshot()
//.times(1) //.times(1)
//abitrary error ////abitrary error
//.returning(|_| Box::pin(err(UuidResolverError::NameAlreadyExist))); //.returning(|_| Box::pin(err(UuidResolverError::NameAlreadyExist)));
//let update_handle = MockUpdateActorHandle::new(); //let update_handle = MockUpdateActorHandle::new();
@ -206,7 +198,7 @@ pub fn load_snapshot(
//); //);
//assert!(snapshot_service.perform_snapshot().await.is_err()); //assert!(snapshot_service.perform_snapshot().await.is_err());
//Nothing was written to the file ////Nothing was written to the file
//assert!(!snapshot_path.path().join("data.ms.snapshot").exists()); //assert!(!snapshot_path.path().join("data.ms.snapshot").exists());
//} //}
@ -222,7 +214,7 @@ pub fn load_snapshot(
//let mut update_handle = MockUpdateActorHandle::new(); //let mut update_handle = MockUpdateActorHandle::new();
//update_handle //update_handle
//.expect_snapshot() //.expect_snapshot()
//abitrary error ////abitrary error
//.returning(|_, _| Box::pin(err(UpdateActorError::UnexistingUpdate(0)))); //.returning(|_, _| Box::pin(err(UpdateActorError::UnexistingUpdate(0))));
//let snapshot_path = tempfile::tempdir_in(".").unwrap(); //let snapshot_path = tempfile::tempdir_in(".").unwrap();
@ -235,7 +227,7 @@ pub fn load_snapshot(
//); //);
//assert!(snapshot_service.perform_snapshot().await.is_err()); //assert!(snapshot_service.perform_snapshot().await.is_err());
//Nothing was written to the file ////Nothing was written to the file
//assert!(!snapshot_path.path().join("data.ms.snapshot").exists()); //assert!(!snapshot_path.path().join("data.ms.snapshot").exists());
//} //}
@ -244,9 +236,9 @@ pub fn load_snapshot(
//let mut uuid_resolver = MockUuidResolverHandle::new(); //let mut uuid_resolver = MockUuidResolverHandle::new();
//uuid_resolver //uuid_resolver
//.expect_snapshot() //.expect_snapshot()
//we expect the funtion to be called between 2 and 3 time in the given interval. ////we expect the funtion to be called between 2 and 3 time in the given interval.
//.times(2..4) //.times(2..4)
//abitrary error, to short-circuit the function ////abitrary error, to short-circuit the function
//.returning(move |_| Box::pin(err(UuidResolverError::NameAlreadyExist))); //.returning(move |_| Box::pin(err(UuidResolverError::NameAlreadyExist)));
//let update_handle = MockUpdateActorHandle::new(); //let update_handle = MockUpdateActorHandle::new();

View File

@ -24,7 +24,7 @@ pub enum UpdateMsg {
ret: oneshot::Sender<Result<UpdateStatus>>, ret: oneshot::Sender<Result<UpdateStatus>>,
id: u64, id: u64,
}, },
Delete { DeleteIndex {
uuid: Uuid, uuid: Uuid,
ret: oneshot::Sender<Result<()>>, ret: oneshot::Sender<Result<()>>,
}, },
@ -99,4 +99,11 @@ impl UpdateMsg {
sender.send(msg).await?; sender.send(msg).await?;
rcv.await? rcv.await?
} }
pub async fn delete(sender: &mpsc::Sender<Self>, uuid: Uuid) -> Result<()> {
let (ret, rcv) = oneshot::channel();
let msg = Self::DeleteIndex { ret, uuid };
sender.send(msg).await?;
rcv.await?
}
} }

View File

@ -172,7 +172,7 @@ impl UpdateLoop {
GetUpdate { uuid, ret, id } => { GetUpdate { uuid, ret, id } => {
let _ = ret.send(self.handle_get_update(uuid, id).await); let _ = ret.send(self.handle_get_update(uuid, id).await);
} }
Delete { uuid, ret } => { DeleteIndex { uuid, ret } => {
let _ = ret.send(self.handle_delete(uuid).await); let _ = ret.send(self.handle_delete(uuid).await);
} }
Snapshot { indexes, path, ret } => { Snapshot { indexes, path, ret } => {

View File

@ -552,149 +552,149 @@ impl UpdateStore {
} }
} }
#[cfg(test)] //#[cfg(test)]
mod test { //mod test {
use super::*; //use super::*;
use crate::index_controller::{ //use crate::index_controller::{
index_actor::{error::IndexActorError, MockIndexActorHandle}, //index_actor::{error::IndexActorError, MockIndexActorHandle},
UpdateResult, //UpdateResult,
}; //};
use futures::future::ok; //use futures::future::ok;
#[actix_rt::test] //#[actix_rt::test]
async fn test_next_id() { //async fn test_next_id() {
let dir = tempfile::tempdir_in(".").unwrap(); //let dir = tempfile::tempdir_in(".").unwrap();
let mut options = EnvOpenOptions::new(); //let mut options = EnvOpenOptions::new();
let handle = Arc::new(MockIndexActorHandle::new()); //let handle = Arc::new(MockIndexActorHandle::new());
options.map_size(4096 * 100); //options.map_size(4096 * 100);
let update_store = UpdateStore::open( //let update_store = UpdateStore::open(
options, //options,
dir.path(), //dir.path(),
handle, //handle,
Arc::new(AtomicBool::new(false)), //Arc::new(AtomicBool::new(false)),
) //)
.unwrap(); //.unwrap();
let index1_uuid = Uuid::new_v4(); //let index1_uuid = Uuid::new_v4();
let index2_uuid = Uuid::new_v4(); //let index2_uuid = Uuid::new_v4();
let mut txn = update_store.env.write_txn().unwrap(); //let mut txn = update_store.env.write_txn().unwrap();
let ids = update_store.next_update_id(&mut txn, index1_uuid).unwrap(); //let ids = update_store.next_update_id(&mut txn, index1_uuid).unwrap();
txn.commit().unwrap(); //txn.commit().unwrap();
assert_eq!((0, 0), ids); //assert_eq!((0, 0), ids);
let mut txn = update_store.env.write_txn().unwrap(); //let mut txn = update_store.env.write_txn().unwrap();
let ids = update_store.next_update_id(&mut txn, index2_uuid).unwrap(); //let ids = update_store.next_update_id(&mut txn, index2_uuid).unwrap();
txn.commit().unwrap(); //txn.commit().unwrap();
assert_eq!((1, 0), ids); //assert_eq!((1, 0), ids);
let mut txn = update_store.env.write_txn().unwrap(); //let mut txn = update_store.env.write_txn().unwrap();
let ids = update_store.next_update_id(&mut txn, index1_uuid).unwrap(); //let ids = update_store.next_update_id(&mut txn, index1_uuid).unwrap();
txn.commit().unwrap(); //txn.commit().unwrap();
assert_eq!((2, 1), ids); //assert_eq!((2, 1), ids);
} //}
#[actix_rt::test] //#[actix_rt::test]
async fn test_register_update() { //async fn test_register_update() {
let dir = tempfile::tempdir_in(".").unwrap(); //let dir = tempfile::tempdir_in(".").unwrap();
let mut options = EnvOpenOptions::new(); //let mut options = EnvOpenOptions::new();
let handle = Arc::new(MockIndexActorHandle::new()); //let handle = Arc::new(MockIndexActorHandle::new());
options.map_size(4096 * 100); //options.map_size(4096 * 100);
let update_store = UpdateStore::open( //let update_store = UpdateStore::open(
options, //options,
dir.path(), //dir.path(),
handle, //handle,
Arc::new(AtomicBool::new(false)), //Arc::new(AtomicBool::new(false)),
) //)
.unwrap(); //.unwrap();
let meta = UpdateMeta::ClearDocuments; //let meta = UpdateMeta::ClearDocuments;
let uuid = Uuid::new_v4(); //let uuid = Uuid::new_v4();
let store_clone = update_store.clone(); //let store_clone = update_store.clone();
tokio::task::spawn_blocking(move || { //tokio::task::spawn_blocking(move || {
store_clone.register_update(meta, None, uuid).unwrap(); //store_clone.register_update(meta, None, uuid).unwrap();
}) //})
.await //.await
.unwrap(); //.unwrap();
let txn = update_store.env.read_txn().unwrap(); //let txn = update_store.env.read_txn().unwrap();
assert!(update_store //assert!(update_store
.pending_queue //.pending_queue
.get(&txn, &(0, uuid, 0)) //.get(&txn, &(0, uuid, 0))
.unwrap() //.unwrap()
.is_some()); //.is_some());
} //}
#[actix_rt::test] //#[actix_rt::test]
async fn test_process_update() { //async fn test_process_update() {
let dir = tempfile::tempdir_in(".").unwrap(); //let dir = tempfile::tempdir_in(".").unwrap();
let mut handle = MockIndexActorHandle::new(); //let mut handle = MockIndexActorHandle::new();
handle //handle
.expect_update() //.expect_update()
.times(2) //.times(2)
.returning(|_index_uuid, processing, _file| { //.returning(|_index_uuid, processing, _file| {
if processing.id() == 0 { //if processing.id() == 0 {
Box::pin(ok(Ok(processing.process(UpdateResult::Other)))) //Box::pin(ok(Ok(processing.process(UpdateResult::Other))))
} else { //} else {
Box::pin(ok(Err( //Box::pin(ok(Err(
processing.fail(IndexActorError::ExistingPrimaryKey.into()) //processing.fail(IndexActorError::ExistingPrimaryKey.into())
))) //)))
} //}
}); //});
let handle = Arc::new(handle); //let handle = Arc::new(handle);
let mut options = EnvOpenOptions::new(); //let mut options = EnvOpenOptions::new();
options.map_size(4096 * 100); //options.map_size(4096 * 100);
let store = UpdateStore::open( //let store = UpdateStore::open(
options, //options,
dir.path(), //dir.path(),
handle.clone(), //handle.clone(),
Arc::new(AtomicBool::new(false)), //Arc::new(AtomicBool::new(false)),
) //)
.unwrap(); //.unwrap();
// wait a bit for the event loop exit. //// wait a bit for the event loop exit.
tokio::time::sleep(std::time::Duration::from_millis(50)).await; //tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let mut txn = store.env.write_txn().unwrap(); //let mut txn = store.env.write_txn().unwrap();
let update = Enqueued::new(UpdateMeta::ClearDocuments, 0, None); //let update = Enqueued::new(UpdateMeta::ClearDocuments, 0, None);
let uuid = Uuid::new_v4(); //let uuid = Uuid::new_v4();
store //store
.pending_queue //.pending_queue
.put(&mut txn, &(0, uuid, 0), &update) //.put(&mut txn, &(0, uuid, 0), &update)
.unwrap(); //.unwrap();
let update = Enqueued::new(UpdateMeta::ClearDocuments, 1, None); //let update = Enqueued::new(UpdateMeta::ClearDocuments, 1, None);
store //store
.pending_queue //.pending_queue
.put(&mut txn, &(1, uuid, 1), &update) //.put(&mut txn, &(1, uuid, 1), &update)
.unwrap(); //.unwrap();
txn.commit().unwrap(); //txn.commit().unwrap();
// Process the pending, and check that it has been moved to the update databases, and //// Process the pending, and check that it has been moved to the update databases, and
// removed from the pending database. //// removed from the pending database.
let store_clone = store.clone(); //let store_clone = store.clone();
tokio::task::spawn_blocking(move || { //tokio::task::spawn_blocking(move || {
store_clone.process_pending_update(handle.clone()).unwrap(); //store_clone.process_pending_update(handle.clone()).unwrap();
store_clone.process_pending_update(handle).unwrap(); //store_clone.process_pending_update(handle).unwrap();
}) //})
.await //.await
.unwrap(); //.unwrap();
let txn = store.env.read_txn().unwrap(); //let txn = store.env.read_txn().unwrap();
assert!(store.pending_queue.first(&txn).unwrap().is_none()); //assert!(store.pending_queue.first(&txn).unwrap().is_none());
let update = store.updates.get(&txn, &(uuid, 0)).unwrap().unwrap(); //let update = store.updates.get(&txn, &(uuid, 0)).unwrap().unwrap();
assert!(matches!(update, UpdateStatus::Processed(_))); //assert!(matches!(update, UpdateStatus::Processed(_)));
let update = store.updates.get(&txn, &(uuid, 1)).unwrap().unwrap(); //let update = store.updates.get(&txn, &(uuid, 1)).unwrap().unwrap();
assert!(matches!(update, UpdateStatus::Failed(_))); //assert!(matches!(update, UpdateStatus::Failed(_)));
} //}
} //}