diff --git a/apps/aether-gateway/src/control/route/admin/model_provider_families.rs b/apps/aether-gateway/src/control/route/admin/model_provider_families.rs index 10c378896..b6a89a4b4 100644 --- a/apps/aether-gateway/src/control/route/admin/model_provider_families.rs +++ b/apps/aether-gateway/src/control/route/admin/model_provider_families.rs @@ -22,6 +22,24 @@ pub(super) fn classify_admin_model_provider_family_route( "admin:models", false, )) + } else if method == http::Method::GET && normalized_path == "/api/admin/models/external/config" + { + Some(classified( + "admin_proxy", + "model_external_manage", + "external_config_get", + "admin:models", + false, + )) + } else if method == http::Method::PUT && normalized_path == "/api/admin/models/external/config" + { + Some(classified( + "admin_proxy", + "model_external_manage", + "external_config_set", + "admin:models", + false, + )) } else if method == http::Method::DELETE && normalized_path == "/api/admin/models/external/cache" { diff --git a/apps/aether-gateway/src/control/tests/admin_providers_models.rs b/apps/aether-gateway/src/control/tests/admin_providers_models.rs index 1dba3e340..4f24ef424 100644 --- a/apps/aether-gateway/src/control/tests/admin_providers_models.rs +++ b/apps/aether-gateway/src/control/tests/admin_providers_models.rs @@ -1,5 +1,8 @@ use http::Uri; +use crate::control::GatewayPublicRequestContext; +use crate::handlers::shared::local_proxy_route_requires_buffered_body; + use super::{classify_control_route, headers}; #[test] @@ -488,6 +491,53 @@ fn classifies_admin_external_models_as_admin_proxy_route() { assert!(!decision.is_execution_runtime_candidate()); } +#[test] +fn classifies_admin_external_models_config_routes_as_admin_proxy_routes() { + let headers = headers(&[]); + let uri: Uri = "/api/admin/models/external/config" + .parse() + .expect("uri should parse"); + + for (method, route_kind) in [ + (http::Method::GET, "external_config_get"), + (http::Method::PUT, "external_config_set"), + ] { + let decision = classify_control_route(&method, &uri, &headers) + .unwrap_or_else(|| panic!("{method} route should classify")); + + assert_eq!(decision.route_class.as_deref(), Some("admin_proxy")); + assert_eq!( + decision.route_family.as_deref(), + Some("model_external_manage") + ); + assert_eq!(decision.route_kind.as_deref(), Some(route_kind)); + assert_eq!( + decision.auth_endpoint_signature.as_deref(), + Some("admin:models") + ); + assert!(!decision.is_execution_runtime_candidate()); + } +} + +#[test] +fn admin_external_models_config_update_buffers_request_body() { + let headers = headers(&[]); + let uri: Uri = "/api/admin/models/external/config" + .parse() + .expect("uri should parse"); + let decision = + classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify"); + let context = GatewayPublicRequestContext::from_request_parts( + "trace-admin-external-models-config", + &http::Method::PUT, + &uri, + &headers, + Some(decision), + ); + + assert!(local_proxy_route_requires_buffered_body(&context)); +} + #[test] fn classifies_admin_clear_external_models_cache_as_admin_proxy_route() { let headers = headers(&[]); diff --git a/apps/aether-gateway/src/handlers/admin/mod.rs b/apps/aether-gateway/src/handlers/admin/mod.rs index a085d1ace..0626429e8 100644 --- a/apps/aether-gateway/src/handlers/admin/mod.rs +++ b/apps/aether-gateway/src/handlers/admin/mod.rs @@ -18,6 +18,10 @@ mod shared; pub(crate) use self::auth::maybe_build_local_admin_security_response; pub(crate) use self::endpoint::build_admin_endpoint_health_status_payload; pub(crate) use self::features::maybe_build_local_admin_video_tasks_response; +#[cfg(test)] +pub(crate) use self::model::{ + set_admin_external_models_source_url_for_tests, ADMIN_EXTERNAL_MODELS_CONFIG_MUTATION_LOCK_KEY, +}; pub(crate) use self::observability::{ admin_stats_bad_request_response, maybe_build_local_admin_usage_response, parse_bounded_u32, round_to, AdminStatsTimeRange, AdminStatsUsageFilter, @@ -53,4 +57,7 @@ pub(crate) use self::request::{ }; pub(crate) use self::routes::maybe_build_local_admin_response; #[cfg(test)] -pub(crate) use self::system::override_proxy_connectivity_probe_url_for_tests; +pub(crate) use self::system::{ + clear_proxy_node_references_with_cache_failure_for_tests, + override_proxy_connectivity_probe_url_for_tests, +}; diff --git a/apps/aether-gateway/src/handlers/admin/model/catalog_routes.rs b/apps/aether-gateway/src/handlers/admin/model/catalog_routes.rs index 4b0c9a5f9..2a5b37cbc 100644 --- a/apps/aether-gateway/src/handlers/admin/model/catalog_routes.rs +++ b/apps/aether-gateway/src/handlers/admin/model/catalog_routes.rs @@ -1,8 +1,9 @@ use crate::handlers::admin::model::build_admin_model_catalog_payload; use crate::handlers::admin::request::{AdminAppState, AdminRequestContext}; +use crate::handlers::admin::shared::attach_admin_audit_response; use crate::GatewayError; use axum::{ - body::Body, + body::{Body, Bytes}, http, response::{IntoResponse, Response}, Json, @@ -22,6 +23,7 @@ fn build_admin_model_catalog_data_unavailable_response() -> Response
{ pub(crate) async fn maybe_build_local_admin_model_catalog_response( state: &AdminAppState<'_>, request_context: &AdminRequestContext<'_>, + request_body: Option<&Bytes>, ) -> Result