mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
test: add admin route matrix guard (#3268)
This commit is contained in:
@@ -5,15 +5,15 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
||||
## Current Context
|
||||
|
||||
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
|
||||
- Branch: `overtrue/arch-migration-ci-rules`
|
||||
- Baseline: `upstream/main` at `6f4d0b54a171ff1560b5d892378d2ed407411fb9`
|
||||
- PR type for this branch: `ci-gate`
|
||||
- Branch: `overtrue/arch-admin-route-matrix-guard`
|
||||
- Baseline: `upstream/main` at `dee550a831cbfc24aa5cecd60f6f3a4cfe1a2e30`
|
||||
- PR type for this branch: `test-only`
|
||||
- Runtime behavior changes: none
|
||||
- Rust code changes: none
|
||||
- CI/script changes: add a migration rule check for PR type vocabulary and
|
||||
temporary compatibility marker/register consistency, plus a lightweight
|
||||
docs-only workflow so architecture documentation PRs still run the same guard.
|
||||
- Docs changes: record the new guardrail in the migration handoff.
|
||||
- Rust code changes: add test-only admin route matrix coverage, test-only
|
||||
registered route tracking, and a private shared admin route registration helper
|
||||
so tests exercise the production registration sequence.
|
||||
- CI/script changes: none
|
||||
- Docs changes: record the route matrix guard handoff.
|
||||
|
||||
## Phase 0 Tasks
|
||||
|
||||
@@ -31,12 +31,11 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
||||
- Acceptance: `./scripts/check_layer_dependencies.sh` passes on current
|
||||
`upstream/main` while still rejecting new unaccepted layer dependencies.
|
||||
- [~] `G-006` Create migration loss-prevention checks.
|
||||
- Current branch: add a migration rule check for PR type vocabulary and
|
||||
temporary compatibility marker/register consistency, with a dedicated
|
||||
architecture-doc trigger that covers `ARCHITECTURE.md` and
|
||||
`docs/architecture/**` docs-only PRs.
|
||||
- Remaining follow-up: add checks for public re-export, route matrix, and
|
||||
storage trait coverage before pure moves.
|
||||
- Current branch: add a mechanical admin route matrix guard from
|
||||
[`admin-route-action-snapshot.md`](admin-route-action-snapshot.md) and
|
||||
`rustfs/src/admin/route_registration_test.rs`.
|
||||
- Remaining follow-up: add checks for public re-export and storage trait
|
||||
coverage before pure moves.
|
||||
- [x] `G-007` Create startup timeline table.
|
||||
- Acceptance: [`startup-timeline.md`](startup-timeline.md) records current
|
||||
binary startup order, side effects, fatal boundaries, and readiness stages.
|
||||
@@ -66,42 +65,38 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
||||
|
||||
## Next PRs
|
||||
|
||||
1. `test-only`: add a mechanical admin route matrix guard from the current
|
||||
snapshot and `route_registration_test.rs`.
|
||||
2. `contract`: define the config-model contract surface while preserving the
|
||||
1. `contract`: define the config-model contract surface while preserving the
|
||||
existing `Config`, `KV`, and `KVS` behavior.
|
||||
3. `ci-gate`: add focused checks for public re-export and storage trait coverage
|
||||
2. `ci-gate`: add focused checks for public re-export and storage trait coverage
|
||||
before pure moves.
|
||||
|
||||
## Pre-Push Review Log
|
||||
|
||||
| Expert | Status | Notes |
|
||||
|---|---|---|
|
||||
| Quality/architecture | pass | Confirmed the guard script is narrow/readable, fixes single-token PR type detection, and reuses one script across CI/make/docs triggers rather than adding a parallel rule system |
|
||||
| Migration preservation | pass | Confirmed this is a `ci-gate` PR with only CI/config/docs/script changes and no runtime logic, storage hot-path, global-state, compatibility implementation, or crate-split changes |
|
||||
| Testing/verification | pass | Confirmed positive checks, staged diff check, and temporary negative checks cover unknown PR type and missing cleanup-register entry failure modes |
|
||||
| Quality/architecture | pass | Confirmed the matrix helpers keep the snapshot explicit, `S3Router` instrumentation is fully `cfg(test)`, the production registration order is shared through one private helper, and the non-test insertion path keeps existing route construction/insert behavior |
|
||||
| Migration preservation | pass | Confirmed this remains `test-only` in effect: no route handler/auth/alias semantics changed, no storage hot-path/global-state/crate-split changes, and MinIO admin alias coverage is expanded across all admin-prefix matrix entries |
|
||||
| Testing/verification | pass | Confirmed focused route tests pin `ENV_HEALTH_ENDPOINT_ENABLE=true`, use the production registration helper, and pass with formatting, non-test cargo check, migration guard scripts, diff check, full `make pre-commit`, and temporary unaccounted-route negative coverage |
|
||||
|
||||
## Verification Notes
|
||||
|
||||
Passed:
|
||||
- `cargo fmt --all --check`
|
||||
- `cargo check -p rustfs --lib`
|
||||
- `cargo test -p rustfs admin::route_registration_test -- --nocapture`
|
||||
- `./scripts/check_architecture_migration_rules.sh`
|
||||
- `./scripts/check_layer_dependencies.sh`
|
||||
- `./scripts/check_metrics_migration_refs.sh`
|
||||
- `git diff --check`
|
||||
- `git diff --cached --check`
|
||||
- `bash -n scripts/check_architecture_migration_rules.sh`
|
||||
- `make architecture-migration-check`
|
||||
- `ruby -e "require 'yaml'; YAML.load_file('.github/workflows/architecture-migration-rules.yml')"`
|
||||
- temporary negative check for unknown single-token PR type
|
||||
- temporary negative check for unknown PR type in `ARCHITECTURE.md`
|
||||
- temporary negative check for unknown PR type in nested `docs/architecture/**`
|
||||
- temporary negative check for source compatibility marker without a register entry
|
||||
- temporary negative check for an unaccounted admin route registration
|
||||
- `make pre-commit`
|
||||
|
||||
## Handoff Notes
|
||||
|
||||
- Keep Phase 0 PRs small. Do not move Config, Storage API, Runtime, or ECStore
|
||||
code inside this `ci-gate` branch.
|
||||
- Keep CI checks in a separate `ci-gate` PR so the PR type rule remains enforceable.
|
||||
code inside this `test-only` branch.
|
||||
- Route matrix instrumentation must remain test-only and must not alter dispatch
|
||||
or auth behavior.
|
||||
- Do not add temporary compatibility code without a matching
|
||||
`RUSTFS_COMPAT_TODO(<task-id>)` marker and cleanup-register entry.
|
||||
- The next config-model PR must preserve the current tuple-struct shapes and
|
||||
|
||||
+34
-30
@@ -42,35 +42,39 @@ use s3s::route::S3Route;
|
||||
/// An instance of S3Route for admin operations
|
||||
pub fn make_admin_route(console_enabled: bool) -> std::io::Result<impl S3Route> {
|
||||
let mut r: S3Router<AdminOperation> = S3Router::new(console_enabled);
|
||||
|
||||
health::register_health_route(&mut r)?;
|
||||
sts::register_admin_auth_route(&mut r)?;
|
||||
|
||||
user::register_user_route(&mut r)?;
|
||||
system::register_system_route(&mut r)?;
|
||||
pools::register_pool_route(&mut r)?;
|
||||
rebalance::register_rebalance_route(&mut r)?;
|
||||
|
||||
heal::register_heal_route(&mut r)?;
|
||||
|
||||
tier::register_tier_route(&mut r)?;
|
||||
|
||||
quota::register_quota_route(&mut r)?;
|
||||
bucket_meta::register_bucket_meta_route(&mut r)?;
|
||||
config_admin::register_config_route(&mut r)?;
|
||||
scanner::register_scanner_route(&mut r)?;
|
||||
audit::register_audit_target_route(&mut r)?;
|
||||
module_switch::register_module_switch_route(&mut r)?;
|
||||
plugins_catalog::register_plugin_catalog_route(&mut r)?;
|
||||
plugins_instances::register_plugin_instance_route(&mut r)?;
|
||||
|
||||
replication::register_replication_route(&mut r)?;
|
||||
site_replication::register_site_replication_route(&mut r)?;
|
||||
profile_admin::register_profiling_route(&mut r)?;
|
||||
tls_debug::register_tls_debug_route(&mut r)?;
|
||||
kms::register_kms_route(&mut r)?;
|
||||
oidc::register_oidc_route(&mut r)?;
|
||||
table_catalog::register_table_catalog_route(&mut r)?;
|
||||
|
||||
register_admin_routes(&mut r)?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn register_admin_routes(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
||||
health::register_health_route(r)?;
|
||||
sts::register_admin_auth_route(r)?;
|
||||
|
||||
user::register_user_route(r)?;
|
||||
system::register_system_route(r)?;
|
||||
pools::register_pool_route(r)?;
|
||||
rebalance::register_rebalance_route(r)?;
|
||||
|
||||
heal::register_heal_route(r)?;
|
||||
|
||||
tier::register_tier_route(r)?;
|
||||
|
||||
quota::register_quota_route(r)?;
|
||||
bucket_meta::register_bucket_meta_route(r)?;
|
||||
config_admin::register_config_route(r)?;
|
||||
scanner::register_scanner_route(r)?;
|
||||
audit::register_audit_target_route(r)?;
|
||||
module_switch::register_module_switch_route(r)?;
|
||||
plugins_catalog::register_plugin_catalog_route(r)?;
|
||||
plugins_instances::register_plugin_instance_route(r)?;
|
||||
|
||||
replication::register_replication_route(r)?;
|
||||
site_replication::register_site_replication_route(r)?;
|
||||
profile_admin::register_profiling_route(r)?;
|
||||
tls_debug::register_tls_debug_route(r)?;
|
||||
kms::register_kms_route(r)?;
|
||||
oidc::register_oidc_route(r)?;
|
||||
table_catalog::register_table_catalog_route(r)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,11 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::admin::{
|
||||
handlers::{
|
||||
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, table_catalog, tier, tls_debug,
|
||||
user,
|
||||
},
|
||||
handlers::health,
|
||||
router::{AdminOperation, S3Router},
|
||||
};
|
||||
use crate::server::{
|
||||
@@ -26,6 +22,7 @@ use crate::server::{
|
||||
};
|
||||
use hyper::Method;
|
||||
use serial_test::serial;
|
||||
use std::collections::BTreeSet;
|
||||
use temp_env::with_var;
|
||||
|
||||
fn admin_path(path: &str) -> String {
|
||||
@@ -40,6 +37,54 @@ fn table_catalog_path(path: &str) -> String {
|
||||
format!("{}{}", TABLE_CATALOG_PREFIX, path)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RouteMatrixEntry {
|
||||
method: Method,
|
||||
pattern: String,
|
||||
sample: String,
|
||||
}
|
||||
|
||||
fn route(method: Method, pattern: impl Into<String>) -> RouteMatrixEntry {
|
||||
let pattern = pattern.into();
|
||||
RouteMatrixEntry {
|
||||
method,
|
||||
sample: pattern.clone(),
|
||||
pattern,
|
||||
}
|
||||
}
|
||||
|
||||
fn route_sample(method: Method, pattern: impl Into<String>, sample: impl Into<String>) -> RouteMatrixEntry {
|
||||
RouteMatrixEntry {
|
||||
method,
|
||||
pattern: pattern.into(),
|
||||
sample: sample.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_route(method: Method, pattern: &str) -> RouteMatrixEntry {
|
||||
route(method, admin_path(pattern))
|
||||
}
|
||||
|
||||
fn admin_route_sample(method: Method, pattern: &str, sample: &str) -> RouteMatrixEntry {
|
||||
route_sample(method, admin_path(pattern), admin_path(sample))
|
||||
}
|
||||
|
||||
fn table_route(method: Method, pattern: &str) -> RouteMatrixEntry {
|
||||
route(method, table_catalog_path(pattern))
|
||||
}
|
||||
|
||||
fn table_route_sample(method: Method, pattern: &str, sample: &str) -> RouteMatrixEntry {
|
||||
route_sample(method, table_catalog_path(pattern), table_catalog_path(sample))
|
||||
}
|
||||
|
||||
fn route_key(method: &Method, path: &str) -> String {
|
||||
format!("{}|{}", method.as_str(), path)
|
||||
}
|
||||
|
||||
fn route_key_set(routes: &[RouteMatrixEntry]) -> BTreeSet<String> {
|
||||
routes.iter().map(|route| route_key(&route.method, &route.pattern)).collect()
|
||||
}
|
||||
|
||||
fn assert_route(router: &S3Router<AdminOperation>, method: Method, path: &str) {
|
||||
assert!(
|
||||
router.contains_route(method.clone(), path),
|
||||
@@ -49,40 +94,269 @@ fn assert_route(router: &S3Router<AdminOperation>, method: Method, path: &str) {
|
||||
);
|
||||
}
|
||||
|
||||
fn register_admin_routes(router: &mut S3Router<AdminOperation>) {
|
||||
health::register_health_route(router).expect("register health route");
|
||||
sts::register_admin_auth_route(router).expect("register sts route");
|
||||
user::register_user_route(router).expect("register user route");
|
||||
system::register_system_route(router).expect("register system route");
|
||||
pools::register_pool_route(router).expect("register pool route");
|
||||
rebalance::register_rebalance_route(router).expect("register rebalance route");
|
||||
heal::register_heal_route(router).expect("register heal route");
|
||||
tier::register_tier_route(router).expect("register tier route");
|
||||
quota::register_quota_route(router).expect("register quota route");
|
||||
bucket_meta::register_bucket_meta_route(router).expect("register bucket meta route");
|
||||
config_admin::register_config_route(router).expect("register config admin route");
|
||||
scanner::register_scanner_route(router).expect("register scanner route");
|
||||
audit::register_audit_target_route(router).expect("register audit target route");
|
||||
module_switch::register_module_switch_route(router).expect("register module switch route");
|
||||
plugins_catalog::register_plugin_catalog_route(router).expect("register plugin catalog route");
|
||||
plugins_instances::register_plugin_instance_route(router).expect("register plugin instances route");
|
||||
replication::register_replication_route(router).expect("register replication route");
|
||||
site_replication::register_site_replication_route(router).expect("register site replication route");
|
||||
profile_admin::register_profiling_route(router).expect("register profile route");
|
||||
tls_debug::register_tls_debug_route(router).expect("register tls debug route");
|
||||
kms::register_kms_route(router).expect("register kms route");
|
||||
oidc::register_oidc_route(router).expect("register oidc route");
|
||||
table_catalog::register_table_catalog_route(router).expect("register table catalog route");
|
||||
fn registered_admin_router() -> S3Router<AdminOperation> {
|
||||
with_var(rustfs_config::ENV_HEALTH_ENDPOINT_ENABLE, Some("true"), || {
|
||||
let mut router = S3Router::new(false);
|
||||
super::register_admin_routes(&mut router).expect("register production admin routes");
|
||||
router
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_admin_route_matrix() -> Vec<RouteMatrixEntry> {
|
||||
vec![
|
||||
route(Method::GET, HEALTH_PREFIX),
|
||||
route(Method::HEAD, HEALTH_PREFIX),
|
||||
route(Method::GET, HEALTH_READY_PATH),
|
||||
route(Method::HEAD, HEALTH_READY_PATH),
|
||||
route(Method::GET, PROFILE_CPU_PATH),
|
||||
route(Method::GET, PROFILE_MEMORY_PATH),
|
||||
route(Method::POST, "/"),
|
||||
admin_route(Method::GET, "/v3/is-admin"),
|
||||
admin_route(Method::GET, "/v3/accountinfo"),
|
||||
admin_route(Method::GET, "/v3/list-users"),
|
||||
admin_route(Method::GET, "/v3/user-info"),
|
||||
admin_route(Method::DELETE, "/v3/remove-user"),
|
||||
admin_route(Method::PUT, "/v3/add-user"),
|
||||
admin_route(Method::PUT, "/v3/set-user-status"),
|
||||
admin_route(Method::GET, "/v3/groups"),
|
||||
admin_route(Method::GET, "/v3/group"),
|
||||
admin_route_sample(Method::DELETE, "/v3/group/{group}", "/v3/group/test-group"),
|
||||
admin_route(Method::PUT, "/v3/set-group-status"),
|
||||
admin_route(Method::PUT, "/v3/update-group-members"),
|
||||
admin_route(Method::POST, "/v3/update-service-account"),
|
||||
admin_route(Method::GET, "/v3/info-service-account"),
|
||||
admin_route(Method::GET, "/v3/temporary-account-info"),
|
||||
admin_route(Method::GET, "/v3/info-access-key"),
|
||||
admin_route(Method::GET, "/v3/list-service-accounts"),
|
||||
admin_route(Method::GET, "/v3/list-access-keys-bulk"),
|
||||
admin_route(Method::DELETE, "/v3/delete-service-accounts"),
|
||||
admin_route(Method::DELETE, "/v3/delete-service-account"),
|
||||
admin_route(Method::PUT, "/v3/add-service-accounts"),
|
||||
admin_route(Method::PUT, "/v3/add-service-account"),
|
||||
admin_route(Method::GET, "/v3/export-iam"),
|
||||
admin_route(Method::PUT, "/v3/import-iam"),
|
||||
admin_route(Method::GET, "/v3/list-canned-policies"),
|
||||
admin_route(Method::GET, "/v3/info-canned-policy"),
|
||||
admin_route(Method::PUT, "/v3/add-canned-policy"),
|
||||
admin_route(Method::DELETE, "/v3/remove-canned-policy"),
|
||||
admin_route(Method::PUT, "/v3/set-user-or-group-policy"),
|
||||
admin_route(Method::PUT, "/v3/set-policy"),
|
||||
admin_route(Method::POST, "/v3/idp/builtin/policy/attach"),
|
||||
admin_route(Method::POST, "/v3/idp/builtin/policy/detach"),
|
||||
admin_route(Method::GET, "/v3/idp/builtin/policy-entities"),
|
||||
admin_route(Method::GET, "/v3/target/list"),
|
||||
admin_route_sample(Method::PUT, "/v3/target/{target_type}/{target_name}", "/v3/target/webhook/test-target"),
|
||||
admin_route_sample(
|
||||
Method::DELETE,
|
||||
"/v3/target/{target_type}/{target_name}/reset",
|
||||
"/v3/target/webhook/test-target/reset",
|
||||
),
|
||||
admin_route(Method::GET, "/v3/target/arns"),
|
||||
admin_route(Method::POST, "/v3/service"),
|
||||
admin_route(Method::GET, "/v3/info"),
|
||||
admin_route(Method::GET, "/v3/inspect-data"),
|
||||
admin_route(Method::POST, "/v3/inspect-data"),
|
||||
admin_route(Method::GET, "/v3/storageinfo"),
|
||||
admin_route(Method::GET, "/v3/datausageinfo"),
|
||||
admin_route(Method::GET, "/v3/metrics"),
|
||||
admin_route(Method::GET, "/v3/pools/list"),
|
||||
admin_route(Method::GET, "/v3/pools/status"),
|
||||
admin_route(Method::POST, "/v3/pools/decommission"),
|
||||
admin_route(Method::POST, "/v3/pools/cancel"),
|
||||
admin_route(Method::POST, "/v3/rebalance/start"),
|
||||
admin_route(Method::GET, "/v3/rebalance/status"),
|
||||
admin_route(Method::POST, "/v3/rebalance/stop"),
|
||||
admin_route(Method::POST, "/v3/heal/"),
|
||||
admin_route_sample(Method::POST, "/v3/heal/{bucket}", "/v3/heal/test-bucket"),
|
||||
admin_route_sample(Method::POST, "/v3/heal/{bucket}/{prefix}", "/v3/heal/test-bucket/prefix"),
|
||||
admin_route(Method::POST, "/v3/background-heal/status"),
|
||||
admin_route(Method::GET, "/v3/tier"),
|
||||
admin_route(Method::GET, "/v3/tier-stats"),
|
||||
admin_route_sample(Method::GET, "/v3/tier/{tier}", "/v3/tier/HOT"),
|
||||
admin_route_sample(Method::DELETE, "/v3/tier/{tiername}", "/v3/tier/HOT"),
|
||||
admin_route(Method::PUT, "/v3/tier"),
|
||||
admin_route_sample(Method::POST, "/v3/tier/{tiername}", "/v3/tier/HOT"),
|
||||
admin_route(Method::POST, "/v3/tier/clear"),
|
||||
admin_route(Method::PUT, "/v3/set-bucket-quota"),
|
||||
admin_route(Method::GET, "/v3/get-bucket-quota"),
|
||||
admin_route_sample(Method::PUT, "/v3/quota/{bucket}", "/v3/quota/test-bucket"),
|
||||
admin_route_sample(Method::GET, "/v3/quota/{bucket}", "/v3/quota/test-bucket"),
|
||||
admin_route_sample(Method::DELETE, "/v3/quota/{bucket}", "/v3/quota/test-bucket"),
|
||||
admin_route_sample(Method::GET, "/v3/quota-stats/{bucket}", "/v3/quota-stats/test-bucket"),
|
||||
admin_route_sample(Method::POST, "/v3/quota-check/{bucket}", "/v3/quota-check/test-bucket"),
|
||||
admin_route(Method::GET, "/export-bucket-metadata"),
|
||||
admin_route(Method::GET, "/v3/export-bucket-metadata"),
|
||||
admin_route(Method::PUT, "/import-bucket-metadata"),
|
||||
admin_route(Method::PUT, "/v3/import-bucket-metadata"),
|
||||
admin_route(Method::GET, "/v3/get-config-kv"),
|
||||
admin_route(Method::PUT, "/v3/set-config-kv"),
|
||||
admin_route(Method::DELETE, "/v3/del-config-kv"),
|
||||
admin_route(Method::GET, "/v3/help-config-kv"),
|
||||
admin_route(Method::GET, "/v3/list-config-history-kv"),
|
||||
admin_route(Method::DELETE, "/v3/clear-config-history-kv"),
|
||||
admin_route(Method::PUT, "/v3/restore-config-history-kv"),
|
||||
admin_route(Method::GET, "/v3/config"),
|
||||
admin_route(Method::PUT, "/v3/config"),
|
||||
admin_route(Method::GET, "/v3/scanner/status"),
|
||||
admin_route(Method::GET, "/v3/audit/target/list"),
|
||||
admin_route_sample(
|
||||
Method::PUT,
|
||||
"/v3/audit/target/{target_type}/{target_name}",
|
||||
"/v3/audit/target/audit_webhook/test-audit",
|
||||
),
|
||||
admin_route_sample(
|
||||
Method::DELETE,
|
||||
"/v3/audit/target/{target_type}/{target_name}/reset",
|
||||
"/v3/audit/target/audit_webhook/test-audit/reset",
|
||||
),
|
||||
admin_route(Method::GET, "/v3/module-switches"),
|
||||
admin_route(Method::PUT, "/v3/module-switches"),
|
||||
admin_route(Method::GET, "/v4/plugins/catalog"),
|
||||
admin_route(Method::GET, "/v4/plugins/instances"),
|
||||
admin_route_sample(Method::GET, "/v4/plugins/instances/{id}", "/v4/plugins/instances/example-id"),
|
||||
admin_route_sample(Method::PUT, "/v4/plugins/instances/{id}", "/v4/plugins/instances/example-id"),
|
||||
admin_route_sample(Method::DELETE, "/v4/plugins/instances/{id}", "/v4/plugins/instances/example-id"),
|
||||
admin_route(Method::GET, "/v3/list-remote-targets"),
|
||||
admin_route(Method::GET, "/v3/replicationmetrics"),
|
||||
admin_route(Method::PUT, "/v3/set-remote-target"),
|
||||
admin_route(Method::DELETE, "/v3/remove-remote-target"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/add"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/remove"),
|
||||
admin_route(Method::GET, "/v3/site-replication/info"),
|
||||
admin_route(Method::GET, "/v3/site-replication/metainfo"),
|
||||
admin_route(Method::GET, "/v3/site-replication/status"),
|
||||
admin_route(Method::POST, "/v3/site-replication/devnull"),
|
||||
admin_route(Method::POST, "/v3/site-replication/netperf"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/peer/join"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/peer/bucket-ops"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/peer/iam-item"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/peer/bucket-meta"),
|
||||
admin_route(Method::GET, "/v3/site-replication/peer/idp-settings"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/edit"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/peer/edit"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/peer/remove"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/resync/op"),
|
||||
admin_route(Method::PUT, "/v3/site-replication/state/edit"),
|
||||
admin_route(Method::GET, "/debug/pprof/profile"),
|
||||
admin_route(Method::GET, "/debug/pprof/status"),
|
||||
admin_route(Method::GET, "/debug/tls/status"),
|
||||
admin_route(Method::POST, "/v3/kms/create-key"),
|
||||
admin_route(Method::POST, "/v3/kms/key/create"),
|
||||
admin_route(Method::GET, "/v3/kms/describe-key"),
|
||||
admin_route(Method::GET, "/v3/kms/key/status"),
|
||||
admin_route(Method::GET, "/v3/kms/list-keys"),
|
||||
admin_route(Method::POST, "/v3/kms/generate-data-key"),
|
||||
admin_route(Method::GET, "/v3/kms/status"),
|
||||
admin_route(Method::POST, "/v3/kms/status"),
|
||||
admin_route(Method::GET, "/v3/kms/config"),
|
||||
admin_route(Method::POST, "/v3/kms/clear-cache"),
|
||||
admin_route(Method::POST, "/v3/kms/configure"),
|
||||
admin_route(Method::POST, "/v3/kms/start"),
|
||||
admin_route(Method::POST, "/v3/kms/stop"),
|
||||
admin_route(Method::GET, "/v3/kms/service-status"),
|
||||
admin_route(Method::POST, "/v3/kms/reconfigure"),
|
||||
admin_route(Method::POST, "/v3/kms/keys"),
|
||||
admin_route(Method::DELETE, "/v3/kms/keys/delete"),
|
||||
admin_route(Method::POST, "/v3/kms/keys/cancel-deletion"),
|
||||
admin_route(Method::GET, "/v3/kms/keys"),
|
||||
admin_route_sample(Method::GET, "/v3/kms/keys/{key_id}", "/v3/kms/keys/test-key"),
|
||||
admin_route(Method::GET, "/v3/oidc/providers"),
|
||||
admin_route_sample(Method::GET, "/v3/oidc/authorize/{provider_id}", "/v3/oidc/authorize/default"),
|
||||
admin_route_sample(Method::GET, "/v3/oidc/callback/{provider_id}", "/v3/oidc/callback/default"),
|
||||
admin_route(Method::GET, "/v3/oidc/logout"),
|
||||
admin_route(Method::GET, "/v3/oidc/config"),
|
||||
admin_route_sample(Method::PUT, "/v3/oidc/config/{provider_id}", "/v3/oidc/config/default"),
|
||||
admin_route_sample(Method::DELETE, "/v3/oidc/config/{provider_id}", "/v3/oidc/config/default"),
|
||||
admin_route(Method::POST, "/v3/oidc/validate"),
|
||||
table_route(Method::GET, "/config"),
|
||||
table_route_sample(Method::GET, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
||||
table_route_sample(Method::POST, "/{warehouse}/namespaces", "/analytics/namespaces"),
|
||||
table_route_sample(Method::GET, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||
table_route_sample(Method::DELETE, "/{warehouse}/namespaces/{namespace}", "/analytics/namespaces/sales"),
|
||||
table_route_sample(
|
||||
Method::GET,
|
||||
"/{warehouse}/namespaces/{namespace}/tables",
|
||||
"/analytics/namespaces/sales/tables",
|
||||
),
|
||||
table_route_sample(
|
||||
Method::POST,
|
||||
"/{warehouse}/namespaces/{namespace}/tables",
|
||||
"/analytics/namespaces/sales/tables",
|
||||
),
|
||||
table_route_sample(
|
||||
Method::POST,
|
||||
"/{warehouse}/namespaces/{namespace}/register",
|
||||
"/analytics/namespaces/sales/register",
|
||||
),
|
||||
table_route_sample(
|
||||
Method::GET,
|
||||
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||
"/analytics/namespaces/sales/tables/orders",
|
||||
),
|
||||
table_route_sample(
|
||||
Method::POST,
|
||||
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||
"/analytics/namespaces/sales/tables/orders",
|
||||
),
|
||||
table_route_sample(
|
||||
Method::DELETE,
|
||||
"/{warehouse}/namespaces/{namespace}/tables/{table}",
|
||||
"/analytics/namespaces/sales/tables/orders",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
// registered_admin_router pins ENV_HEALTH_ENDPOINT_ENABLE because the
|
||||
// production registration helper intentionally honors that environment switch.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_admin_route_matrix_matches_registered_routes() {
|
||||
let router = registered_admin_router();
|
||||
|
||||
let matrix = expected_admin_route_matrix();
|
||||
let actual_routes = router.registered_routes();
|
||||
assert_eq!(
|
||||
actual_routes.len(),
|
||||
matrix.len(),
|
||||
"admin route matrix must account for every registered route"
|
||||
);
|
||||
|
||||
let actual = actual_routes.iter().cloned().collect::<BTreeSet<_>>();
|
||||
let expected = route_key_set(&matrix);
|
||||
assert_eq!(actual, expected, "admin route registration changed without updating the matrix");
|
||||
|
||||
for route in matrix {
|
||||
assert_route(&router, route.method, &route.sample);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_admin_route_matrix_preserves_minio_admin_aliases() {
|
||||
let router = registered_admin_router();
|
||||
|
||||
for route in expected_admin_route_matrix()
|
||||
.into_iter()
|
||||
.filter(|route| route.sample.starts_with(ADMIN_PREFIX))
|
||||
{
|
||||
let suffix = route
|
||||
.sample
|
||||
.strip_prefix(ADMIN_PREFIX)
|
||||
.expect("matrix route is already filtered by admin prefix");
|
||||
let alias = compat_admin_alias_path(suffix);
|
||||
assert!(
|
||||
router.contains_compatible_route(route.method.clone(), &alias),
|
||||
"expected MinIO admin alias path to match: {} {}",
|
||||
route.method.as_str(),
|
||||
alias
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// register_admin_routes reads ENV_HEALTH_ENDPOINT_ENABLE to decide whether
|
||||
// to register /health; serialise with the env-mutating test below to avoid
|
||||
// cross-thread leakage of that override.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_register_routes_cover_representative_admin_paths() {
|
||||
let mut router: S3Router<AdminOperation> = S3Router::new(false);
|
||||
register_admin_routes(&mut router);
|
||||
let router = registered_admin_router();
|
||||
assert_route(&router, Method::GET, HEALTH_PREFIX);
|
||||
assert_route(&router, Method::HEAD, HEALTH_PREFIX);
|
||||
assert_route(&router, Method::GET, HEALTH_READY_PATH);
|
||||
@@ -221,8 +495,7 @@ fn test_register_routes_cover_representative_admin_paths() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_admin_alias_paths_match_existing_admin_routes() {
|
||||
let mut router: S3Router<AdminOperation> = S3Router::new(false);
|
||||
register_admin_routes(&mut router);
|
||||
let router = registered_admin_router();
|
||||
|
||||
for (method, path) in [
|
||||
(Method::GET, compat_admin_alias_path("/v3/is-admin")),
|
||||
|
||||
@@ -2212,6 +2212,8 @@ pub struct S3Router<T> {
|
||||
router: Router<T>,
|
||||
console_enabled: bool,
|
||||
console_router: Option<axum::routing::RouterIntoService<Body>>,
|
||||
#[cfg(test)]
|
||||
registered_routes: Vec<String>,
|
||||
}
|
||||
|
||||
fn is_public_health_path(path: &str) -> bool {
|
||||
@@ -2242,6 +2244,8 @@ impl<T: Operation> S3Router<T> {
|
||||
router,
|
||||
console_enabled,
|
||||
console_router,
|
||||
#[cfg(test)]
|
||||
registered_routes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2250,6 +2254,13 @@ impl<T: Operation> S3Router<T> {
|
||||
|
||||
// warn!("set uri {}", &path);
|
||||
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.router.insert(path.clone(), operation).map_err(std::io::Error::other)?;
|
||||
self.registered_routes.push(path);
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
self.router.insert(path, operation).map_err(std::io::Error::other)?;
|
||||
|
||||
Ok(())
|
||||
@@ -2272,6 +2283,11 @@ impl<T: Operation> S3Router<T> {
|
||||
let route = Self::make_route_str(method, canonical_path.as_ref());
|
||||
self.router.at(&route).is_ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn registered_routes(&self) -> &[String] {
|
||||
&self.registered_routes
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Operation> Default for S3Router<T> {
|
||||
|
||||
Reference in New Issue
Block a user