mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 08:49:26 +00:00
feat(table-catalog): add REST catalog route surface (#3211)
* feat(table-catalog): add REST catalog route surface * fix(table-catalog): avoid test-only router import * fix(table-catalog): align unsupported REST responses --------- Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
This commit is contained in:
@@ -45,6 +45,7 @@ pub mod service_account;
|
|||||||
pub mod site_replication;
|
pub mod site_replication;
|
||||||
pub mod sts;
|
pub mod sts;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
|
pub mod table_catalog;
|
||||||
mod target_descriptor;
|
mod target_descriptor;
|
||||||
pub mod tier;
|
pub mod tier;
|
||||||
pub mod tls_debug;
|
pub mod tls_debug;
|
||||||
@@ -95,6 +96,7 @@ mod tests {
|
|||||||
let _site_replication_add_handler = site_replication::SiteReplicationAddHandler {};
|
let _site_replication_add_handler = site_replication::SiteReplicationAddHandler {};
|
||||||
let _site_replication_info_handler = site_replication::SiteReplicationInfoHandler {};
|
let _site_replication_info_handler = site_replication::SiteReplicationInfoHandler {};
|
||||||
let _site_replication_status_handler = site_replication::SiteReplicationStatusHandler {};
|
let _site_replication_status_handler = site_replication::SiteReplicationStatusHandler {};
|
||||||
|
let _table_catalog_config_handler = table_catalog::GetCatalogConfigHandler {};
|
||||||
|
|
||||||
// Just verify they can be created without panicking
|
// Just verify they can be created without panicking
|
||||||
// Test passes if we reach this point without panicking
|
// Test passes if we reach this point without panicking
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
// Copyright 2024 RustFS Team
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use crate::admin::{
|
||||||
|
auth::validate_admin_request,
|
||||||
|
router::{AdminOperation, Operation, S3Router},
|
||||||
|
};
|
||||||
|
use crate::auth::{check_key_valid, get_session_token};
|
||||||
|
use crate::server::{RemoteAddr, TABLE_CATALOG_PREFIX};
|
||||||
|
use crate::table_catalog::DEFAULT_WAREHOUSE_ID;
|
||||||
|
use http::{HeaderMap, HeaderValue, StatusCode};
|
||||||
|
use hyper::Method;
|
||||||
|
use matchit::Params;
|
||||||
|
use rustfs_policy::policy::action::{Action, AdminAction};
|
||||||
|
use s3s::{Body, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
const JSON_CONTENT_TYPE: &str = "application/json";
|
||||||
|
const WAREHOUSE_PROPERTY: &str = "warehouse";
|
||||||
|
const UNSUPPORTED_OPERATION_TYPE: &str = "UnsupportedOperationException";
|
||||||
|
const UNSUPPORTED_OPERATION_STATUS: StatusCode = StatusCode::NOT_ACCEPTABLE;
|
||||||
|
|
||||||
|
static GET_CONFIG_HANDLER: GetCatalogConfigHandler = GetCatalogConfigHandler {};
|
||||||
|
static LIST_NAMESPACES_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::GetTableNamespaceAction,
|
||||||
|
operation: "list namespaces",
|
||||||
|
};
|
||||||
|
static CREATE_NAMESPACE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::SetTableNamespaceAction,
|
||||||
|
operation: "create namespace",
|
||||||
|
};
|
||||||
|
static GET_NAMESPACE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::GetTableNamespaceAction,
|
||||||
|
operation: "get namespace",
|
||||||
|
};
|
||||||
|
static DROP_NAMESPACE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::DeleteTableNamespaceAction,
|
||||||
|
operation: "drop namespace",
|
||||||
|
};
|
||||||
|
static LIST_TABLES_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::GetTableAction,
|
||||||
|
operation: "list tables",
|
||||||
|
};
|
||||||
|
static CREATE_TABLE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::CreateTableAction,
|
||||||
|
operation: "create table",
|
||||||
|
};
|
||||||
|
static REGISTER_TABLE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::RegisterTableAction,
|
||||||
|
operation: "register table",
|
||||||
|
};
|
||||||
|
static LOAD_TABLE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::GetTableAction,
|
||||||
|
operation: "load table",
|
||||||
|
};
|
||||||
|
static COMMIT_TABLE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::CommitTableAction,
|
||||||
|
operation: "commit table",
|
||||||
|
};
|
||||||
|
static DROP_TABLE_HANDLER: UnsupportedCatalogOperationHandler = UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction::DeleteTableAction,
|
||||||
|
operation: "drop table",
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct CatalogConfigResponse {
|
||||||
|
defaults: BTreeMap<&'static str, &'static str>,
|
||||||
|
overrides: BTreeMap<&'static str, &'static str>,
|
||||||
|
endpoints: Vec<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ErrorResponse {
|
||||||
|
error: RestCatalogError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct RestCatalogError {
|
||||||
|
message: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
error_type: &'static str,
|
||||||
|
code: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_table_catalog_route(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
||||||
|
r.insert(
|
||||||
|
Method::GET,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/config").as_str(),
|
||||||
|
AdminOperation(&GET_CONFIG_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::GET,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces").as_str(),
|
||||||
|
AdminOperation(&LIST_NAMESPACES_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::POST,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces").as_str(),
|
||||||
|
AdminOperation(&CREATE_NAMESPACE_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::GET,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}").as_str(),
|
||||||
|
AdminOperation(&GET_NAMESPACE_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::DELETE,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}").as_str(),
|
||||||
|
AdminOperation(&DROP_NAMESPACE_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::GET,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}/tables").as_str(),
|
||||||
|
AdminOperation(&LIST_TABLES_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::POST,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}/tables").as_str(),
|
||||||
|
AdminOperation(&CREATE_TABLE_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::POST,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}/register").as_str(),
|
||||||
|
AdminOperation(®ISTER_TABLE_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::GET,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
||||||
|
AdminOperation(&LOAD_TABLE_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::POST,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
||||||
|
AdminOperation(&COMMIT_TABLE_HANDLER),
|
||||||
|
)?;
|
||||||
|
r.insert(
|
||||||
|
Method::DELETE,
|
||||||
|
format!("{TABLE_CATALOG_PREFIX}/{{warehouse}}/namespaces/{{namespace}}/tables/{{table}}").as_str(),
|
||||||
|
AdminOperation(&DROP_TABLE_HANDLER),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn catalog_config_response() -> CatalogConfigResponse {
|
||||||
|
CatalogConfigResponse {
|
||||||
|
defaults: BTreeMap::from([(WAREHOUSE_PROPERTY, DEFAULT_WAREHOUSE_ID)]),
|
||||||
|
overrides: BTreeMap::new(),
|
||||||
|
endpoints: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_json_response<T: Serialize>(status: StatusCode, body: &T) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
let data = serde_json::to_vec(body).map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(CONTENT_TYPE, HeaderValue::from_static(JSON_CONTENT_TYPE));
|
||||||
|
Ok(S3Response::with_headers((status, Body::from(data)), headers))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authorize_table_catalog_request(req: &S3Request<Body>, action: AdminAction) -> S3Result<()> {
|
||||||
|
let Some(input_cred) = &req.credentials else {
|
||||||
|
return Err(s3_error!(InvalidRequest, "authentication required"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cred, owner) =
|
||||||
|
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
|
||||||
|
|
||||||
|
validate_admin_request(
|
||||||
|
&req.headers,
|
||||||
|
&cred,
|
||||||
|
owner,
|
||||||
|
false,
|
||||||
|
vec![Action::AdminAction(action)],
|
||||||
|
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsupported_response(operation: &str) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
build_json_response(
|
||||||
|
UNSUPPORTED_OPERATION_STATUS,
|
||||||
|
&ErrorResponse {
|
||||||
|
error: RestCatalogError {
|
||||||
|
message: format!("Iceberg REST Catalog {operation} is not implemented yet"),
|
||||||
|
error_type: UNSUPPORTED_OPERATION_TYPE,
|
||||||
|
code: UNSUPPORTED_OPERATION_STATUS.as_u16(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GetCatalogConfigHandler {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for GetCatalogConfigHandler {
|
||||||
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
authorize_table_catalog_request(&req, AdminAction::GetTableCatalogAction).await?;
|
||||||
|
build_json_response(StatusCode::OK, &catalog_config_response())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UnsupportedCatalogOperationHandler {
|
||||||
|
action: AdminAction,
|
||||||
|
operation: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for UnsupportedCatalogOperationHandler {
|
||||||
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
authorize_table_catalog_request(&req, self.action).await?;
|
||||||
|
unsupported_response(self.operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn catalog_config_response_uses_default_warehouse_without_unsupported_endpoints() {
|
||||||
|
let response = catalog_config_response();
|
||||||
|
|
||||||
|
assert_eq!(response.defaults.get(WAREHOUSE_PROPERTY), Some(&DEFAULT_WAREHOUSE_ID));
|
||||||
|
assert!(response.overrides.is_empty());
|
||||||
|
assert!(response.endpoints.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsupported_response_uses_rest_catalog_unsupported_status() {
|
||||||
|
let response = unsupported_response("create table").expect("unsupported response should build");
|
||||||
|
|
||||||
|
assert_eq!(response.output.0, UNSUPPORTED_OPERATION_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_catalog_handlers_require_table_admin_actions() {
|
||||||
|
let src = include_str!("table_catalog.rs");
|
||||||
|
|
||||||
|
assert!(src.contains("AdminAction::GetTableCatalogAction"));
|
||||||
|
assert!(src.contains("AdminAction::GetTableNamespaceAction"));
|
||||||
|
assert!(src.contains("AdminAction::SetTableNamespaceAction"));
|
||||||
|
assert!(src.contains("AdminAction::DeleteTableNamespaceAction"));
|
||||||
|
assert!(src.contains("AdminAction::CreateTableAction"));
|
||||||
|
assert!(src.contains("AdminAction::RegisterTableAction"));
|
||||||
|
assert!(src.contains("AdminAction::CommitTableAction"));
|
||||||
|
assert!(src.contains("AdminAction::DeleteTableAction"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ mod route_registration_test;
|
|||||||
|
|
||||||
use handlers::{
|
use handlers::{
|
||||||
audit, bucket_meta, config_admin, heal, health, kms, module_switch, oidc, plugins_catalog, plugins_instances, pools,
|
audit, bucket_meta, config_admin, heal, health, kms, module_switch, oidc, plugins_catalog, plugins_instances, pools,
|
||||||
profile_admin, quota, rebalance, replication, scanner, site_replication, sts, system, tier, tls_debug, user,
|
profile_admin, quota, rebalance, replication, scanner, site_replication, sts, system, table_catalog, tier, tls_debug, user,
|
||||||
};
|
};
|
||||||
use router::{AdminOperation, S3Router};
|
use router::{AdminOperation, S3Router};
|
||||||
use s3s::route::S3Route;
|
use s3s::route::S3Route;
|
||||||
@@ -70,6 +70,7 @@ pub fn make_admin_route(console_enabled: bool) -> std::io::Result<impl S3Route>
|
|||||||
tls_debug::register_tls_debug_route(&mut r)?;
|
tls_debug::register_tls_debug_route(&mut r)?;
|
||||||
kms::register_kms_route(&mut r)?;
|
kms::register_kms_route(&mut r)?;
|
||||||
oidc::register_oidc_route(&mut r)?;
|
oidc::register_oidc_route(&mut r)?;
|
||||||
|
table_catalog::register_table_catalog_route(&mut r)?;
|
||||||
|
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,15 @@
|
|||||||
use crate::admin::{
|
use crate::admin::{
|
||||||
handlers::{
|
handlers::{
|
||||||
audit, bucket_meta, config_admin, heal, health, kms, module_switch, oidc, plugins_catalog, plugins_instances, pools,
|
audit, bucket_meta, config_admin, heal, health, kms, module_switch, oidc, plugins_catalog, plugins_instances, pools,
|
||||||
profile_admin, quota, rebalance, replication, scanner, site_replication, sts, system, tier, tls_debug, user,
|
profile_admin, quota, rebalance, replication, scanner, site_replication, sts, system, table_catalog, tier, tls_debug,
|
||||||
|
user,
|
||||||
},
|
},
|
||||||
router::{AdminOperation, S3Router},
|
router::{AdminOperation, S3Router},
|
||||||
};
|
};
|
||||||
use crate::server::{ADMIN_PREFIX, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH};
|
use crate::server::{
|
||||||
|
ADMIN_PREFIX, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH,
|
||||||
|
TABLE_CATALOG_PREFIX,
|
||||||
|
};
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use temp_env::with_var;
|
use temp_env::with_var;
|
||||||
@@ -32,6 +36,10 @@ fn compat_admin_alias_path(path: &str) -> String {
|
|||||||
format!("{}{}", MINIO_ADMIN_PREFIX, path)
|
format!("{}{}", MINIO_ADMIN_PREFIX, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn table_catalog_path(path: &str) -> String {
|
||||||
|
format!("{}{}", TABLE_CATALOG_PREFIX, path)
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_route(router: &S3Router<AdminOperation>, method: Method, path: &str) {
|
fn assert_route(router: &S3Router<AdminOperation>, method: Method, path: &str) {
|
||||||
assert!(
|
assert!(
|
||||||
router.contains_route(method.clone(), path),
|
router.contains_route(method.clone(), path),
|
||||||
@@ -64,6 +72,7 @@ fn register_admin_routes(router: &mut S3Router<AdminOperation>) {
|
|||||||
tls_debug::register_tls_debug_route(router).expect("register tls debug route");
|
tls_debug::register_tls_debug_route(router).expect("register tls debug route");
|
||||||
kms::register_kms_route(router).expect("register kms route");
|
kms::register_kms_route(router).expect("register kms route");
|
||||||
oidc::register_oidc_route(router).expect("register oidc route");
|
oidc::register_oidc_route(router).expect("register oidc route");
|
||||||
|
table_catalog::register_table_catalog_route(router).expect("register table catalog route");
|
||||||
}
|
}
|
||||||
|
|
||||||
// register_admin_routes reads ENV_HEALTH_ENDPOINT_ENABLE to decide whether
|
// register_admin_routes reads ENV_HEALTH_ENDPOINT_ENABLE to decide whether
|
||||||
@@ -126,6 +135,18 @@ fn test_register_routes_cover_representative_admin_paths() {
|
|||||||
assert_route(&router, Method::PUT, &admin_path("/v3/config"));
|
assert_route(&router, Method::PUT, &admin_path("/v3/config"));
|
||||||
assert_route(&router, Method::GET, &admin_path("/v3/scanner/status"));
|
assert_route(&router, Method::GET, &admin_path("/v3/scanner/status"));
|
||||||
|
|
||||||
|
assert_route(&router, Method::GET, &table_catalog_path("/config"));
|
||||||
|
assert_route(&router, Method::GET, &table_catalog_path("/analytics/namespaces"));
|
||||||
|
assert_route(&router, Method::POST, &table_catalog_path("/analytics/namespaces"));
|
||||||
|
assert_route(&router, Method::GET, &table_catalog_path("/analytics/namespaces/sales"));
|
||||||
|
assert_route(&router, Method::DELETE, &table_catalog_path("/analytics/namespaces/sales"));
|
||||||
|
assert_route(&router, Method::GET, &table_catalog_path("/analytics/namespaces/sales/tables"));
|
||||||
|
assert_route(&router, Method::POST, &table_catalog_path("/analytics/namespaces/sales/tables"));
|
||||||
|
assert_route(&router, Method::POST, &table_catalog_path("/analytics/namespaces/sales/register"));
|
||||||
|
assert_route(&router, Method::GET, &table_catalog_path("/analytics/namespaces/sales/tables/orders"));
|
||||||
|
assert_route(&router, Method::POST, &table_catalog_path("/analytics/namespaces/sales/tables/orders"));
|
||||||
|
assert_route(&router, Method::DELETE, &table_catalog_path("/analytics/namespaces/sales/tables/orders"));
|
||||||
|
|
||||||
assert_route(&router, Method::POST, &admin_path("/v3/service"));
|
assert_route(&router, Method::POST, &admin_path("/v3/service"));
|
||||||
assert_route(&router, Method::GET, &admin_path("/v3/info"));
|
assert_route(&router, Method::GET, &admin_path("/v3/info"));
|
||||||
assert_route(&router, Method::GET, &admin_path("/v3/storageinfo"));
|
assert_route(&router, Method::GET, &admin_path("/v3/storageinfo"));
|
||||||
|
|||||||
@@ -2458,6 +2458,7 @@ mod tests {
|
|||||||
fn is_admin_path_accepts_rustfs_and_compat_prefixes() {
|
fn is_admin_path_accepts_rustfs_and_compat_prefixes() {
|
||||||
assert!(is_admin_path("/rustfs/admin/v3/info"));
|
assert!(is_admin_path("/rustfs/admin/v3/info"));
|
||||||
assert!(is_admin_path("/minio/admin/v3/info"));
|
assert!(is_admin_path("/minio/admin/v3/info"));
|
||||||
|
assert!(is_admin_path(&format!("{}/config", crate::server::TABLE_CATALOG_PREFIX)));
|
||||||
assert!(!is_admin_path("/bucket/object"));
|
assert!(!is_admin_path("/bucket/object"));
|
||||||
assert!(!is_admin_path("/rustfs/administrator/object"));
|
assert!(!is_admin_path("/rustfs/administrator/object"));
|
||||||
assert!(!is_admin_path("/minio/administrator/object"));
|
assert!(!is_admin_path("/minio/administrator/object"));
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ use crate::server::cors;
|
|||||||
use crate::server::hybrid::HybridBody;
|
use crate::server::hybrid::HybridBody;
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
ADMIN_PREFIX, CONSOLE_PREFIX, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX,
|
ADMIN_PREFIX, CONSOLE_PREFIX, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX,
|
||||||
MINIO_ADMIN_V3_PREFIX, RPC_PREFIX, RUSTFS_ADMIN_PREFIX, active_http_requests, collect_dependency_readiness_report,
|
MINIO_ADMIN_V3_PREFIX, RPC_PREFIX, RUSTFS_ADMIN_PREFIX, TABLE_CATALOG_PREFIX, active_http_requests,
|
||||||
has_path_prefix, is_admin_path,
|
collect_dependency_readiness_report, has_path_prefix, is_admin_path,
|
||||||
};
|
};
|
||||||
use crate::storage::apply_cors_headers;
|
use crate::storage::apply_cors_headers;
|
||||||
use crate::storage::request_context::{RequestContext, extract_request_id_from_headers};
|
use crate::storage::request_context::{RequestContext, extract_request_id_from_headers};
|
||||||
@@ -864,6 +864,7 @@ fn is_object_attributes_request(req: &HttpRequest<Incoming>) -> bool {
|
|||||||
|| has_path_prefix(path, MINIO_ADMIN_PREFIX)
|
|| has_path_prefix(path, MINIO_ADMIN_PREFIX)
|
||||||
|| has_path_prefix(path, RUSTFS_ADMIN_PREFIX)
|
|| has_path_prefix(path, RUSTFS_ADMIN_PREFIX)
|
||||||
|| has_path_prefix(path, MINIO_ADMIN_V3_PREFIX)
|
|| has_path_prefix(path, MINIO_ADMIN_V3_PREFIX)
|
||||||
|
|| has_path_prefix(path, TABLE_CATALOG_PREFIX)
|
||||||
|| has_path_prefix(path, CONSOLE_PREFIX)
|
|| has_path_prefix(path, CONSOLE_PREFIX)
|
||||||
|| has_path_prefix(path, RPC_PREFIX)
|
|| has_path_prefix(path, RPC_PREFIX)
|
||||||
{
|
{
|
||||||
@@ -908,6 +909,7 @@ impl ConditionalCorsLayer {
|
|||||||
// Exclude Admin, Console, RPC, and configured special paths
|
// Exclude Admin, Console, RPC, and configured special paths
|
||||||
!has_path_prefix(path, ADMIN_PREFIX)
|
!has_path_prefix(path, ADMIN_PREFIX)
|
||||||
&& !has_path_prefix(path, MINIO_ADMIN_PREFIX)
|
&& !has_path_prefix(path, MINIO_ADMIN_PREFIX)
|
||||||
|
&& !has_path_prefix(path, TABLE_CATALOG_PREFIX)
|
||||||
&& !has_path_prefix(path, RPC_PREFIX)
|
&& !has_path_prefix(path, RPC_PREFIX)
|
||||||
&& !is_console_path(path)
|
&& !is_console_path(path)
|
||||||
&& !Self::EXCLUDED_EXACT_PATHS.contains(&path)
|
&& !Self::EXCLUDED_EXACT_PATHS.contains(&path)
|
||||||
@@ -1958,6 +1960,7 @@ mod tests {
|
|||||||
assert!(ConditionalCorsLayer::is_s3_path("/"));
|
assert!(ConditionalCorsLayer::is_s3_path("/"));
|
||||||
assert!(!ConditionalCorsLayer::is_s3_path("/rustfs/admin/v3/info"));
|
assert!(!ConditionalCorsLayer::is_s3_path("/rustfs/admin/v3/info"));
|
||||||
assert!(!ConditionalCorsLayer::is_s3_path("/minio/admin/v3/info"));
|
assert!(!ConditionalCorsLayer::is_s3_path("/minio/admin/v3/info"));
|
||||||
|
assert!(!ConditionalCorsLayer::is_s3_path(&format!("{TABLE_CATALOG_PREFIX}/config")));
|
||||||
assert!(ConditionalCorsLayer::is_s3_path("/minio/adminx/object"));
|
assert!(ConditionalCorsLayer::is_s3_path("/minio/adminx/object"));
|
||||||
assert!(!ConditionalCorsLayer::is_s3_path("/health"));
|
assert!(!ConditionalCorsLayer::is_s3_path("/health"));
|
||||||
assert!(!ConditionalCorsLayer::is_s3_path("/health/ready"));
|
assert!(!ConditionalCorsLayer::is_s3_path("/health/ready"));
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub(crate) use module_switch::{
|
|||||||
pub(crate) use prefix::{
|
pub(crate) use prefix::{
|
||||||
ADMIN_PREFIX, CONSOLE_PREFIX, FAVICON_PATH, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, LICENSE,
|
ADMIN_PREFIX, CONSOLE_PREFIX, FAVICON_PATH, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, LICENSE,
|
||||||
MINIO_ADMIN_PREFIX, MINIO_ADMIN_V3_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH, RPC_PREFIX, RUSTFS_ADMIN_PREFIX,
|
MINIO_ADMIN_PREFIX, MINIO_ADMIN_V3_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH, RPC_PREFIX, RUSTFS_ADMIN_PREFIX,
|
||||||
TONIC_PREFIX, VERSION, has_path_prefix, is_admin_path,
|
TABLE_CATALOG_PREFIX, TONIC_PREFIX, VERSION, has_path_prefix, is_admin_path,
|
||||||
};
|
};
|
||||||
pub(crate) use readiness::DependencyReadiness;
|
pub(crate) use readiness::DependencyReadiness;
|
||||||
pub(crate) use readiness::DependencyReadinessReport;
|
pub(crate) use readiness::DependencyReadinessReport;
|
||||||
|
|||||||
@@ -43,9 +43,14 @@ pub(crate) const ADMIN_PREFIX: &str = "/rustfs/admin";
|
|||||||
/// This alias allows stock MinIO admin tooling to reach RustFS handlers.
|
/// This alias allows stock MinIO admin tooling to reach RustFS handlers.
|
||||||
pub(crate) const MINIO_ADMIN_PREFIX: &str = "/minio/admin";
|
pub(crate) const MINIO_ADMIN_PREFIX: &str = "/minio/admin";
|
||||||
|
|
||||||
|
/// Iceberg REST Catalog prefix for RustFS S3 Tables control-plane routes.
|
||||||
|
pub(crate) const TABLE_CATALOG_PREFIX: &str = "/iceberg/v1";
|
||||||
|
|
||||||
/// Returns true for the admin prefix itself or slash-delimited children.
|
/// Returns true for the admin prefix itself or slash-delimited children.
|
||||||
pub(crate) fn is_admin_path(path: &str) -> bool {
|
pub(crate) fn is_admin_path(path: &str) -> bool {
|
||||||
has_path_prefix(path, ADMIN_PREFIX) || has_path_prefix(path, MINIO_ADMIN_PREFIX)
|
has_path_prefix(path, ADMIN_PREFIX)
|
||||||
|
|| has_path_prefix(path, MINIO_ADMIN_PREFIX)
|
||||||
|
|| has_path_prefix(path, TABLE_CATALOG_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_path_prefix(path: &str, prefix: &str) -> bool {
|
pub(crate) fn has_path_prefix(path: &str, prefix: &str) -> bool {
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ fn is_probe_path(path: &str) -> bool {
|
|||||||
|
|
||||||
let is_prefix_probe = has_path_prefix(path, crate::server::RUSTFS_ADMIN_PREFIX)
|
let is_prefix_probe = has_path_prefix(path, crate::server::RUSTFS_ADMIN_PREFIX)
|
||||||
|| has_path_prefix(path, crate::server::MINIO_ADMIN_V3_PREFIX)
|
|| has_path_prefix(path, crate::server::MINIO_ADMIN_V3_PREFIX)
|
||||||
|
|| has_path_prefix(path, crate::server::TABLE_CATALOG_PREFIX)
|
||||||
|| has_path_prefix(path, crate::server::CONSOLE_PREFIX)
|
|| has_path_prefix(path, crate::server::CONSOLE_PREFIX)
|
||||||
|| has_path_prefix(path, crate::server::RPC_PREFIX)
|
|| has_path_prefix(path, crate::server::RPC_PREFIX)
|
||||||
|| has_path_prefix(path, crate::server::ADMIN_PREFIX)
|
|| has_path_prefix(path, crate::server::ADMIN_PREFIX)
|
||||||
@@ -719,6 +720,7 @@ mod tests {
|
|||||||
fn probe_path_checks_admin_boundaries() {
|
fn probe_path_checks_admin_boundaries() {
|
||||||
assert!(is_probe_path("/minio/admin/v3/info"));
|
assert!(is_probe_path("/minio/admin/v3/info"));
|
||||||
assert!(is_probe_path("/rustfs/admin/v3/info"));
|
assert!(is_probe_path("/rustfs/admin/v3/info"));
|
||||||
|
assert!(is_probe_path(&format!("{}/config", crate::server::TABLE_CATALOG_PREFIX)));
|
||||||
assert!(is_probe_path("/rustfs/console/"));
|
assert!(is_probe_path("/rustfs/console/"));
|
||||||
assert!(!is_probe_path("/minio/adminx/object"));
|
assert!(!is_probe_path("/minio/adminx/object"));
|
||||||
assert!(!is_probe_path("/rustfs/adminx/object"));
|
assert!(!is_probe_path("/rustfs/adminx/object"));
|
||||||
|
|||||||
Reference in New Issue
Block a user