fix(acceptance): 隔离高并发验收用户组
为生产同构验收创建并绑定专属用户组,避免验收账号继承默认并发 5 的限流。\n\n验收脚本通过管理 API 幂等维护用户组,并在数据库侧校验唯一用户、唯一活跃 Key、并发策略和无外部引用。\n\n验证:bash -n;ShellCheck;git diff --check。
This commit is contained in:
@@ -219,6 +219,147 @@ wait_for_existing_tasks_to_drain() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensure_acceptance_user_group() {
|
||||||
|
local groups_response=$temporary_root/user-groups.json
|
||||||
|
local group_response=$temporary_root/acceptance-user-group.json
|
||||||
|
local users_response=$temporary_root/users.json
|
||||||
|
local user_response=$temporary_root/acceptance-user.json
|
||||||
|
local group_id group_body user_body isolation_state
|
||||||
|
admin_request GET /api/admin/user-groups '' "$groups_response"
|
||||||
|
group_id=$(jq -r '
|
||||||
|
[.items[] | select(.groupKey == "production-acceptance") | .id]
|
||||||
|
| if length == 1 then .[0] else empty end
|
||||||
|
' "$groups_response")
|
||||||
|
group_body=$(jq -cn '{
|
||||||
|
groupKey:"production-acceptance",
|
||||||
|
name:"Production Acceptance",
|
||||||
|
description:"Dedicated production-isomorphic acceptance traffic",
|
||||||
|
source:"gateway",
|
||||||
|
priority:1,
|
||||||
|
rechargeDiscountPolicy:{},
|
||||||
|
billingDiscountPolicy:{},
|
||||||
|
rateLimitPolicy:{
|
||||||
|
rules:[{
|
||||||
|
metric:"concurrent",
|
||||||
|
limit:1000,
|
||||||
|
leaseTtlSeconds:600
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
quotaPolicy:{},
|
||||||
|
metadata:{purpose:"production_acceptance",isolated:true},
|
||||||
|
status:"active"
|
||||||
|
}')
|
||||||
|
if [[ -z $group_id ]]; then
|
||||||
|
[[ $(jq '[.items[] | select(.groupKey == "production-acceptance")] | length' \
|
||||||
|
"$groups_response") == 0 ]] || {
|
||||||
|
echo 'acceptance user group lookup was ambiguous' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
admin_request POST /api/admin/user-groups "$group_body" "$group_response"
|
||||||
|
group_id=$(jq -r '.id // empty' "$group_response")
|
||||||
|
else
|
||||||
|
[[ $group_id =~ ^[0-9a-f-]{36}$ ]] || {
|
||||||
|
echo 'acceptance user group API returned an invalid group ID' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
isolation_state=$(database_query "
|
||||||
|
SELECT
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_users
|
||||||
|
WHERE default_user_group_id='$group_id'::uuid
|
||||||
|
AND id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid)
|
||||||
|
||':'||
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_api_keys
|
||||||
|
WHERE user_group_id='$group_id'::uuid
|
||||||
|
AND gateway_user_id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid);")
|
||||||
|
[[ $isolation_state == 0:0 ]] || {
|
||||||
|
echo 'acceptance user group is referenced by non-acceptance identities' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
admin_request PATCH "/api/admin/user-groups/$group_id" \
|
||||||
|
"$group_body" "$group_response"
|
||||||
|
fi
|
||||||
|
[[ $group_id =~ ^[0-9a-f-]{36}$ ]] || {
|
||||||
|
echo 'acceptance user group API returned an invalid group ID' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
admin_request GET /api/admin/users '' "$users_response"
|
||||||
|
user_body=$(jq -c \
|
||||||
|
--arg userID "$AI_GATEWAY_ACCEPTANCE_USER_ID" \
|
||||||
|
--arg groupID "$group_id" '
|
||||||
|
[.items[] | select(.id == $userID)]
|
||||||
|
| if length != 1 then empty else .[0] end
|
||||||
|
| {
|
||||||
|
userKey:(.userKey // ""),
|
||||||
|
source:(.source // "gateway"),
|
||||||
|
externalUserId:(.externalUserId // ""),
|
||||||
|
username:(.username // ""),
|
||||||
|
displayName:(.displayName // ""),
|
||||||
|
email:(.email // ""),
|
||||||
|
phone:(.phone // ""),
|
||||||
|
avatarUrl:(.avatarUrl // ""),
|
||||||
|
gatewayTenantId:(.gatewayTenantId // ""),
|
||||||
|
tenantId:(.tenantId // ""),
|
||||||
|
tenantKey:(.tenantKey // ""),
|
||||||
|
defaultUserGroupId:$groupID,
|
||||||
|
roles:(.roles // []),
|
||||||
|
authProfile:(.authProfile // {}),
|
||||||
|
metadata:(.metadata // {}),
|
||||||
|
status:(.status // "active")
|
||||||
|
}
|
||||||
|
' "$users_response")
|
||||||
|
[[ -n $user_body && $(jq -r '.username // empty' <<<"$user_body") != '' ]] || {
|
||||||
|
echo 'dedicated acceptance user was not found through the control API' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
admin_request PATCH "/api/admin/users/$AI_GATEWAY_ACCEPTANCE_USER_ID" \
|
||||||
|
"$user_body" "$user_response"
|
||||||
|
|
||||||
|
isolation_state=$(database_query "
|
||||||
|
SELECT
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_users
|
||||||
|
WHERE id='$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid
|
||||||
|
AND default_user_group_id='$group_id'::uuid)
|
||||||
|
||':'||
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_api_keys
|
||||||
|
WHERE id='$AI_GATEWAY_ACCEPTANCE_API_KEY_ID'::uuid
|
||||||
|
AND gateway_user_id='$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid
|
||||||
|
AND COALESCE(user_group_id,'$group_id'::uuid)='$group_id'::uuid
|
||||||
|
AND status='active'
|
||||||
|
AND deleted_at IS NULL)
|
||||||
|
||':'||
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_api_keys
|
||||||
|
WHERE gateway_user_id='$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid
|
||||||
|
AND status='active'
|
||||||
|
AND deleted_at IS NULL)
|
||||||
|
||':'||
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_user_groups
|
||||||
|
WHERE id='$group_id'::uuid
|
||||||
|
AND status='active'
|
||||||
|
AND rate_limit_policy @> '{\"rules\":[{\"metric\":\"concurrent\",\"limit\":1000}]}'::jsonb)
|
||||||
|
||':'||
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_users
|
||||||
|
WHERE default_user_group_id='$group_id'::uuid
|
||||||
|
AND id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid)
|
||||||
|
||':'||
|
||||||
|
(SELECT count(*)
|
||||||
|
FROM gateway_api_keys
|
||||||
|
WHERE user_group_id='$group_id'::uuid
|
||||||
|
AND gateway_user_id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid);")
|
||||||
|
[[ $isolation_state == 1:1:1:1:0:0 ]] || {
|
||||||
|
echo 'dedicated acceptance identity isolation verification failed' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
echo 'acceptance_user_group=PASS concurrent_limit=1000 active_keys=1 isolated=true'
|
||||||
|
}
|
||||||
|
|
||||||
select_acceptance_models() {
|
select_acceptance_models() {
|
||||||
if [[ -z $AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL ]]; then
|
if [[ -z $AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL ]]; then
|
||||||
AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL=$(database_query "
|
AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL=$(database_query "
|
||||||
@@ -1069,6 +1210,7 @@ finish_and_promote() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
verify_release_cas
|
verify_release_cas
|
||||||
|
ensure_acceptance_user_group
|
||||||
select_acceptance_models
|
select_acceptance_models
|
||||||
ensure_acceptance_model_access
|
ensure_acceptance_model_access
|
||||||
deploy_protocol_emulator
|
deploy_protocol_emulator
|
||||||
|
|||||||
Reference in New Issue
Block a user