mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-18 18:46:17 +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
|
||||
/// nextest's process-per-test parallelism safe).
|
||||
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 {
|
||||
@@ -234,7 +241,7 @@ impl TestECStoreEnvBuilder {
|
||||
// Port 0 keeps ECStore-backed integration binaries parallel-safe under
|
||||
// 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 ecstore = ECStore::new(server_addr, endpoint_pools, CancellationToken::new())
|
||||
let ecstore = ECStore::new(server_addr, endpoint_pools.clone(), CancellationToken::new())
|
||||
.await
|
||||
.expect("build test ECStore");
|
||||
|
||||
@@ -254,6 +261,7 @@ impl TestECStoreEnvBuilder {
|
||||
temp_root,
|
||||
disk_paths,
|
||||
ecstore,
|
||||
endpoint_pools,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1541,6 +1541,84 @@ mod tests {
|
||||
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
|
||||
/// `crate::admin::auth::tests::non_admin_credential_is_denied`.
|
||||
#[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 {
|
||||
let start = src
|
||||
.find(start_marker)
|
||||
|
||||
@@ -948,6 +948,10 @@ pub(crate) mod runtime {
|
||||
|
||||
#[cfg(test)]
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user