feat: add routing profile scheduling policies

This commit is contained in:
fawney19
2026-05-18 11:03:49 +08:00
parent a2f91b4108
commit 92813e6122
124 changed files with 11681 additions and 578 deletions

View File

@@ -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",

View File

@@ -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)

View File

@@ -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,
)
}

View 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"
);
}
}

View File

@@ -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;