mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
feat: add routing profile scheduling policies
This commit is contained in:
@@ -137,6 +137,11 @@ const PERMISSION_GROUPS: &[PermissionGroup] = &[
|
||||
label: "代理节点",
|
||||
assignable: true,
|
||||
},
|
||||
PermissionGroup {
|
||||
scope: "routing_profiles",
|
||||
label: "调度分组",
|
||||
assignable: true,
|
||||
},
|
||||
PermissionGroup {
|
||||
scope: "security",
|
||||
label: "安全",
|
||||
@@ -446,6 +451,9 @@ fn permission_key(scope: &str, access: &str) -> &'static str {
|
||||
("proxy_nodes", "read") => "admin:proxy_nodes:read",
|
||||
("proxy_nodes", "write") => "admin:proxy_nodes:write",
|
||||
("proxy_nodes", "admin") => "admin:proxy_nodes:admin",
|
||||
("routing_profiles", "read") => "admin:routing_profiles:read",
|
||||
("routing_profiles", "write") => "admin:routing_profiles:write",
|
||||
("routing_profiles", "admin") => "admin:routing_profiles:admin",
|
||||
("security", "read") => "admin:security:read",
|
||||
("security", "write") => "admin:security:write",
|
||||
("security", "admin") => "admin:security:admin",
|
||||
|
||||
@@ -14,6 +14,8 @@ mod observability_families;
|
||||
mod operations_families;
|
||||
#[path = "admin/provider_ops_routes.rs"]
|
||||
mod provider_ops_routes;
|
||||
#[path = "admin/routing_families.rs"]
|
||||
mod routing_families;
|
||||
#[path = "admin/system_families.rs"]
|
||||
mod system_families;
|
||||
|
||||
@@ -23,6 +25,7 @@ use model_provider_families::classify_admin_model_provider_family_route;
|
||||
use observability_families::classify_admin_observability_family_route;
|
||||
use operations_families::classify_admin_operations_family_route;
|
||||
use provider_ops_routes::classify_admin_provider_ops_routes;
|
||||
use routing_families::classify_admin_routing_family_route;
|
||||
use system_families::classify_admin_system_family_route;
|
||||
|
||||
pub(super) fn classify_admin_route(
|
||||
@@ -67,6 +70,10 @@ pub(super) fn classify_admin_route(
|
||||
classify_admin_system_family_route(method, normalized_path, normalized_path_no_trailing)
|
||||
{
|
||||
Some(route)
|
||||
} else if let Some(route) =
|
||||
classify_admin_routing_family_route(method, normalized_path_no_trailing)
|
||||
{
|
||||
Some(route)
|
||||
} else if let Some(route) = classify_admin_provider_ops_routes(method, normalized_path) {
|
||||
Some(route)
|
||||
} else if let Some(route) = classify_admin_model_provider_family_route(method, normalized_path)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_routing_family_route(
|
||||
method: &http::Method,
|
||||
normalized_path_no_trailing: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
let path = normalized_path_no_trailing;
|
||||
if method == http::Method::GET && path == "/api/admin/routing/groups" {
|
||||
Some(routing_route("list_groups"))
|
||||
} else if method == http::Method::POST && path == "/api/admin/routing/groups" {
|
||||
Some(routing_route("create_group"))
|
||||
} else if method == http::Method::GET
|
||||
&& path.starts_with("/api/admin/routing/groups/")
|
||||
&& path.ends_with("/versions")
|
||||
&& path.matches('/').count() == 6
|
||||
{
|
||||
Some(routing_route("list_group_versions"))
|
||||
} else if method == http::Method::POST
|
||||
&& path.starts_with("/api/admin/routing/groups/")
|
||||
&& path.ends_with("/publish")
|
||||
&& path.matches('/').count() == 6
|
||||
{
|
||||
Some(routing_route("publish_group"))
|
||||
} else if method == http::Method::POST
|
||||
&& path.starts_with("/api/admin/routing/groups/")
|
||||
&& path.ends_with("/dry-run")
|
||||
&& path.matches('/').count() == 6
|
||||
{
|
||||
Some(routing_route("dry_run_group"))
|
||||
} else if method == http::Method::GET
|
||||
&& path.starts_with("/api/admin/routing/groups/")
|
||||
&& path.matches('/').count() == 5
|
||||
{
|
||||
Some(routing_route("get_group"))
|
||||
} else if method == http::Method::PATCH
|
||||
&& path.starts_with("/api/admin/routing/groups/")
|
||||
&& path.matches('/').count() == 5
|
||||
{
|
||||
Some(routing_route("update_group"))
|
||||
} else if method == http::Method::DELETE
|
||||
&& path.starts_with("/api/admin/routing/groups/")
|
||||
&& path.matches('/').count() == 5
|
||||
{
|
||||
Some(routing_route("delete_group"))
|
||||
} else if method == http::Method::GET && path == "/api/admin/routing/bindings" {
|
||||
Some(routing_route("list_bindings"))
|
||||
} else if method == http::Method::POST && path == "/api/admin/routing/bindings" {
|
||||
Some(routing_route("create_binding"))
|
||||
} else if method == http::Method::PATCH
|
||||
&& path.starts_with("/api/admin/routing/bindings/")
|
||||
&& path.matches('/').count() == 5
|
||||
{
|
||||
Some(routing_route("update_binding"))
|
||||
} else if method == http::Method::DELETE
|
||||
&& path.starts_with("/api/admin/routing/bindings/")
|
||||
&& path.matches('/').count() == 5
|
||||
{
|
||||
Some(routing_route("delete_binding"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn routing_route(route_kind: &'static str) -> ClassifiedRoute {
|
||||
classified(
|
||||
"admin_proxy",
|
||||
"routing_profiles_manage",
|
||||
route_kind,
|
||||
"admin:routing_profiles",
|
||||
false,
|
||||
)
|
||||
}
|
||||
92
apps/aether-gateway/src/control/tests/admin_routing.rs
Normal file
92
apps/aether-gateway/src/control/tests/admin_routing.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use http::Uri;
|
||||
|
||||
use crate::handlers::shared::local_proxy_route_requires_buffered_body;
|
||||
|
||||
use super::{classify_control_route, headers, GatewayPublicRequestContext};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_routing_group_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/routing/groups"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
list.route_family.as_deref(),
|
||||
Some("routing_profiles_manage")
|
||||
);
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_groups"));
|
||||
assert_eq!(
|
||||
list.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:routing_profiles")
|
||||
);
|
||||
|
||||
let create_uri: Uri = "/api/admin/routing/groups"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let create = classify_control_route(&http::Method::POST, &create_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(
|
||||
create.route_family.as_deref(),
|
||||
Some("routing_profiles_manage")
|
||||
);
|
||||
assert_eq!(create.route_kind.as_deref(), Some("create_group"));
|
||||
|
||||
let update_uri: Uri = "/api/admin/routing/groups/group-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let update = classify_control_route(&http::Method::PATCH, &update_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(
|
||||
update.route_family.as_deref(),
|
||||
Some("routing_profiles_manage")
|
||||
);
|
||||
assert_eq!(update.route_kind.as_deref(), Some("update_group"));
|
||||
|
||||
let dry_run_uri: Uri = "/api/admin/routing/groups/group-1/dry-run"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let dry_run = classify_control_route(&http::Method::POST, &dry_run_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(
|
||||
dry_run.route_family.as_deref(),
|
||||
Some("routing_profiles_manage")
|
||||
);
|
||||
assert_eq!(dry_run.route_kind.as_deref(), Some("dry_run_group"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_routing_write_routes_buffer_request_body() {
|
||||
let headers = headers(&[]);
|
||||
let routes = [
|
||||
(http::Method::POST, "/api/admin/routing/groups"),
|
||||
(http::Method::PATCH, "/api/admin/routing/groups/group-1"),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/routing/groups/group-1/dry-run",
|
||||
),
|
||||
(http::Method::POST, "/api/admin/routing/bindings"),
|
||||
(http::Method::PATCH, "/api/admin/routing/bindings/binding-1"),
|
||||
];
|
||||
|
||||
for (method, path) in routes {
|
||||
let uri: Uri = path.parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
let context = GatewayPublicRequestContext::from_request_parts(
|
||||
"trace-routing-write",
|
||||
&method,
|
||||
&uri,
|
||||
&headers,
|
||||
Some(decision),
|
||||
);
|
||||
|
||||
assert!(
|
||||
local_proxy_route_requires_buffered_body(&context),
|
||||
"{method} {path} should buffer request body"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@ mod admin_provider_query;
|
||||
mod admin_provider_strategy;
|
||||
mod admin_providers_models;
|
||||
mod admin_proxy_nodes;
|
||||
mod admin_routing;
|
||||
mod admin_security;
|
||||
mod admin_stats;
|
||||
mod admin_usage;
|
||||
|
||||
Reference in New Issue
Block a user