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
|
||||
}
|
||||
|
||||
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() {
|
||||
if [[ -z $AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL ]]; then
|
||||
AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL=$(database_query "
|
||||
@@ -1069,6 +1210,7 @@ finish_and_promote() {
|
||||
}
|
||||
|
||||
verify_release_cas
|
||||
ensure_acceptance_user_group
|
||||
select_acceptance_models
|
||||
ensure_acceptance_model_access
|
||||
deploy_protocol_emulator
|
||||
|
||||
Reference in New Issue
Block a user