This commit is contained in:
Louis Dureuil
2025-11-27 11:31:04 +01:00
parent 72dcaee29a
commit e3d5b1025e
4 changed files with 57 additions and 8 deletions

View File

@@ -38,6 +38,10 @@ impl RoFeatures {
Self { runtime }
}
pub fn from_runtime_features(features: RuntimeTogglableFeatures) -> Self {
Self { runtime: features }
}
pub fn runtime_features(&self) -> RuntimeTogglableFeatures {
self.runtime
}

View File

@@ -398,6 +398,7 @@ TooManyOpenFiles , System , UNPROCES
TooManyVectors , InvalidRequest , BAD_REQUEST ;
UnexpectedNetworkPreviousRemotes , InvalidRequest , BAD_REQUEST ;
NetworkVersionTooOld , InvalidRequest , BAD_REQUEST ;
UnprocessedNetworkTask , InvalidRequest , BAD_REQUEST ;
UnretrievableDocument , Internal , BAD_REQUEST ;
UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ;
UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;

View File

@@ -1,6 +1,7 @@
use actix_web as aweb;
use aweb::error::{JsonPayloadError, QueryPayloadError};
use byte_unit::{Byte, UnitType};
use index_scheduler::error::FeatureNotEnabledError;
use meilisearch_types::document_formats::{DocumentFormatError, PayloadType};
use meilisearch_types::error::{Code, ErrorCode, ResponseError};
use meilisearch_types::index_uid::{IndexUid, IndexUidFormatError};
@@ -143,6 +144,13 @@ pub enum MeilisearchHttpError {
UnexpectedNetworkPreviousRemotes,
#[error("The network version in request is too old.\n - Received: {received}\n - Expected at least: {expected_at_least}")]
NetworkVersionTooOld { received: Uuid, expected_at_least: Uuid },
#[error("Remote `{remote}` encountered an error: {error}")]
RemoteIndexScheduler { remote: String, error: index_scheduler::Error },
#[error("{if_remote}Already has a pending network task with uid {task_uid}.\n - Note: No network task can be registered while any previous network task is not done processing.\n - Hint: Wait for task {task_uid} to complete or cancel it.",
if_remote=if let Some(remote) = remote {
format!("Remote `{remote}` encountered an error: ")
} else {"".into()} )]
UnprocessedNetworkTask { remote: Option<String>, task_uid: meilisearch_types::tasks::TaskId },
}
impl MeilisearchHttpError {
@@ -170,6 +178,7 @@ impl ErrorCode for MeilisearchHttpError {
MeilisearchHttpError::SerdeJson(_) => Code::Internal,
MeilisearchHttpError::HeedError(_) => Code::Internal,
MeilisearchHttpError::IndexScheduler(e) => e.error_code(),
MeilisearchHttpError::RemoteIndexScheduler { error, .. } => error.error_code(),
MeilisearchHttpError::Milli { error, .. } => error.error_code(),
MeilisearchHttpError::Payload(e) => e.error_code(),
MeilisearchHttpError::FileStore(_) => Code::Internal,
@@ -202,6 +211,7 @@ impl ErrorCode for MeilisearchHttpError {
Code::UnexpectedNetworkPreviousRemotes
}
MeilisearchHttpError::NetworkVersionTooOld { .. } => Code::NetworkVersionTooOld,
MeilisearchHttpError::UnprocessedNetworkTask { .. } => Code::UnprocessedNetworkTask,
}
}
}

View File

@@ -4,8 +4,9 @@ use actix_web::web::{self, Data};
use actix_web::{HttpRequest, HttpResponse};
use deserr::actix_web::AwebJson;
use deserr::Deserr;
use index_scheduler::IndexScheduler;
use index_scheduler::{IndexScheduler, Query, RoFeatures};
use itertools::{EitherOrBoth, Itertools};
use meilisearch_auth::AuthFilter;
use meilisearch_types::deserr::DeserrJsonError;
use meilisearch_types::enterprise_edition::network::{Network as DbNetwork, Remote as DbRemote};
use meilisearch_types::error::deserr_codes::{
@@ -288,7 +289,33 @@ async fn patch_network_without_origin(
let merged_network = merge_networks(old_network.clone(), new_network)?;
// When a network task must be created, perform some sanity checks against common errors:
// - missing experimental feature on an host from the network
// - a network task is already enqueued
//
// These checks are by no mean perfect (they are not atomic since the network is involved), but they should
// help preventing a bad situation.
if merged_network.leader.is_some() {
let query = Query {
statuses: Some(vec![
meilisearch_types::tasks::Status::Enqueued,
meilisearch_types::tasks::Status::Processing,
]),
types: Some(vec![meilisearch_types::tasks::Kind::NetworkTopologyChange]),
..Default::default()
};
let filters = AuthFilter::default();
let (tasks, _) = index_scheduler.get_task_ids_from_authorized_indexes(&query, &filters)?;
if let Some(first) = tasks.min() {
return Err(MeilisearchHttpError::UnprocessedNetworkTask {
remote: None,
task_uid: first,
}
.into());
}
for eob in old_network
.remotes
.iter()
@@ -308,10 +335,14 @@ async fn patch_network_without_origin(
remote,
)
.await?;
if !remote_features.network {
/// TODO: clean error!
panic!("boom")
}
let remote_features = RoFeatures::from_runtime_features(remote_features);
remote_features.check_network("receiving a proxied network task").map_err(
|error| MeilisearchHttpError::RemoteIndexScheduler {
remote: remote_name.to_owned(),
error,
},
)?;
// 2. check whether there are any unfinished network task
let network_tasks: AllTasks = proxy::send_request(
"/tasks?types=networkTopologyChanges&statuses=enqueued,processing&limit=1",
@@ -323,9 +354,12 @@ async fn patch_network_without_origin(
)
.await?;
if network_tasks.total != 0 {
/// TODO: clean error!
panic!("boom")
if let [first, ..] = network_tasks.results.as_slice() {
return Err(MeilisearchHttpError::UnprocessedNetworkTask {
remote: Some(remote_name.to_owned()),
task_uid: first.uid,
}
.into());
}
}
}