mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 00:47:13 +00:00
test(admin): replace a source-text guard with a behavior test (#6212)
This commit is contained in:
@@ -92,6 +92,13 @@ pub struct TestECStoreEnv {
|
|||||||
/// `init_local_disks` + `ECStore::new` on `127.0.0.1:0` (random port keeps
|
/// `init_local_disks` + `ECStore::new` on `127.0.0.1:0` (random port keeps
|
||||||
/// nextest's process-per-test parallelism safe).
|
/// nextest's process-per-test parallelism safe).
|
||||||
pub ecstore: Arc<ECStore>,
|
pub ecstore: Arc<ECStore>,
|
||||||
|
/// The single-pool, single-set topology the store was built from.
|
||||||
|
///
|
||||||
|
/// The bootstrap does **not** publish it on the instance context (server
|
||||||
|
/// startup is what calls `set_endpoints`, and that write is once-only), so
|
||||||
|
/// a test that needs `get_global_endpoints` to resolve — admin server-info
|
||||||
|
/// and other topology readers — publishes this value itself.
|
||||||
|
pub endpoint_pools: EndpointServerPools,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestECStoreEnv {
|
impl TestECStoreEnv {
|
||||||
@@ -234,7 +241,7 @@ impl TestECStoreEnvBuilder {
|
|||||||
// Port 0 keeps ECStore-backed integration binaries parallel-safe under
|
// Port 0 keeps ECStore-backed integration binaries parallel-safe under
|
||||||
// nextest: no fixed peer port is ever shared between test processes.
|
// nextest: no fixed peer port is ever shared between test processes.
|
||||||
let server_addr: std::net::SocketAddr = "127.0.0.1:0".parse().expect("parse test addr");
|
let server_addr: std::net::SocketAddr = "127.0.0.1:0".parse().expect("parse test addr");
|
||||||
let ecstore = ECStore::new(server_addr, endpoint_pools, CancellationToken::new())
|
let ecstore = ECStore::new(server_addr, endpoint_pools.clone(), CancellationToken::new())
|
||||||
.await
|
.await
|
||||||
.expect("build test ECStore");
|
.expect("build test ECStore");
|
||||||
|
|
||||||
@@ -254,6 +261,7 @@ impl TestECStoreEnvBuilder {
|
|||||||
temp_root,
|
temp_root,
|
||||||
disk_paths,
|
disk_paths,
|
||||||
ecstore,
|
ecstore,
|
||||||
|
endpoint_pools,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1541,6 +1541,84 @@ mod tests {
|
|||||||
assert_eq!(error.code(), &S3ErrorCode::InvalidRequest);
|
assert_eq!(error.code(), &S3ErrorCode::InvalidRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `ServerInfoHandler` must answer an authorized admin request with the
|
||||||
|
/// per-pool erasure-set topology (rustfs/backlog#1839). That map is only
|
||||||
|
/// filled when the server-info query is issued with pools included, so a
|
||||||
|
/// handler that stopped asking for them would still return 200 with an
|
||||||
|
/// empty `pools` object instead of failing.
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
|
async fn server_info_response_carries_pool_topology() {
|
||||||
|
use crate::admin::runtime_sources::{AppContext, publish_test_app_context};
|
||||||
|
use crate::admin::storage_api::runtime::bootstrap_ctx;
|
||||||
|
use http_body_util::BodyExt as _;
|
||||||
|
use rustfs_iam::store::{Store as _, object::IAM_CONFIG_PREFIX};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
const ROOT_ACCESS_KEY: &str = "SERVERINFOROOTACCESSKEY";
|
||||||
|
const ROOT_SECRET_KEY: &str = "serverInfoRootSecret123";
|
||||||
|
|
||||||
|
let _ = rustfs_credentials::init_global_action_credentials(
|
||||||
|
Some(ROOT_ACCESS_KEY.to_string()),
|
||||||
|
Some(ROOT_SECRET_KEY.to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let env = rustfs_test_utils::TestECStoreEnv::builder()
|
||||||
|
.prefix("admin_server_info_pools")
|
||||||
|
.disk_count(1)
|
||||||
|
.init_bucket_metadata(false)
|
||||||
|
.build()
|
||||||
|
.await;
|
||||||
|
// Server startup owns this write in production; the test bootstrap
|
||||||
|
// stops short of it, and without a topology the server-info query
|
||||||
|
// returns before it ever looks at drives.
|
||||||
|
bootstrap_ctx().set_endpoints(env.endpoint_pools.clone());
|
||||||
|
rustfs_iam::store::object::ObjectStore::new(Arc::clone(&env.ecstore))
|
||||||
|
.save_iam_config(serde_json::json!({"version": 1}), format!("{}/format.json", *IAM_CONFIG_PREFIX))
|
||||||
|
.await
|
||||||
|
.expect("seed IAM format");
|
||||||
|
let iam = rustfs_iam::build_iam_sys(Arc::clone(&env.ecstore))
|
||||||
|
.await
|
||||||
|
.expect("build test IAM");
|
||||||
|
publish_test_app_context(Arc::new(AppContext::with_default_interfaces(
|
||||||
|
Arc::clone(&env.ecstore),
|
||||||
|
iam,
|
||||||
|
Arc::new(rustfs_kms::KmsServiceManager::new()),
|
||||||
|
)));
|
||||||
|
|
||||||
|
let request = S3Request {
|
||||||
|
input: Body::empty(),
|
||||||
|
method: Method::GET,
|
||||||
|
uri: Uri::from_static("/rustfs/admin/v3/info"),
|
||||||
|
headers: HeaderMap::new(),
|
||||||
|
extensions: Extensions::new(),
|
||||||
|
credentials: Some(s3s::auth::Credentials {
|
||||||
|
access_key: ROOT_ACCESS_KEY.to_string(),
|
||||||
|
secret_key: s3s::auth::SecretKey::from(ROOT_SECRET_KEY.to_string()),
|
||||||
|
}),
|
||||||
|
region: None,
|
||||||
|
service: None,
|
||||||
|
trailing_headers: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (status, body) = super::ServerInfoHandler {}
|
||||||
|
.call(request, Params::new())
|
||||||
|
.await
|
||||||
|
.expect("root admin credentials must be served server info")
|
||||||
|
.output;
|
||||||
|
assert_eq!(status, hyper::StatusCode::OK);
|
||||||
|
|
||||||
|
let bytes = body.collect().await.expect("server info body should read").to_bytes();
|
||||||
|
let payload: serde_json::Value = serde_json::from_slice(&bytes).expect("server info must be json");
|
||||||
|
let pools = payload["info"]["pools"]
|
||||||
|
.as_object()
|
||||||
|
.expect("server info must carry a pools object");
|
||||||
|
assert!(
|
||||||
|
pools.contains_key("0"),
|
||||||
|
"server info must report the erasure-set topology of pool 0, got {pools:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Authorization denial for this exact action is pinned to AccessDenied by
|
/// Authorization denial for this exact action is pinned to AccessDenied by
|
||||||
/// `crate::admin::auth::tests::non_admin_credential_is_denied`.
|
/// `crate::admin::auth::tests::non_admin_credential_is_denied`.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1412,23 +1412,6 @@ fn test_health_routes_not_registered_when_disabled_by_env() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_phase5_admin_info_contract() {
|
|
||||||
let system_src = include_str!("handlers/system.rs");
|
|
||||||
|
|
||||||
let server_info_impl_marker = "impl Operation for ServerInfoHandler";
|
|
||||||
let server_info_impl_start = system_src
|
|
||||||
.find(server_info_impl_marker)
|
|
||||||
.expect("Expected impl Operation for ServerInfoHandler in handlers/system.rs");
|
|
||||||
let server_info_impl_block = &system_src[server_info_impl_start..];
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
server_info_impl_block.contains("default_admin_usecase()")
|
|
||||||
&& server_info_impl_block.contains("execute_query_server_info(QueryServerInfoRequest { include_pools: true })"),
|
|
||||||
"admin server info path must be served through admin runtime-source DefaultAdminUsecase::execute_query_server_info"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_block_between_markers<'a>(src: &'a str, start_marker: &str, end_marker: &str) -> &'a str {
|
fn extract_block_between_markers<'a>(src: &'a str, start_marker: &str, end_marker: &str) -> &'a str {
|
||||||
let start = src
|
let start = src
|
||||||
.find(start_marker)
|
.find(start_marker)
|
||||||
|
|||||||
@@ -948,6 +948,10 @@ pub(crate) mod runtime {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) use super::{Endpoint, Endpoints, PoolEndpoints};
|
pub(crate) use super::{Endpoint, Endpoints, PoolEndpoints};
|
||||||
|
/// Test-only: the process instance context, so a handler test can publish
|
||||||
|
/// the endpoint topology that server startup normally installs.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) use crate::storage::storage_api::ecstore_runtime::bootstrap_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod s3 {
|
pub(crate) mod s3 {
|
||||||
|
|||||||
Reference in New Issue
Block a user