fix: tune sub2api codex pool concurrency
This commit is contained in:
@@ -56,9 +56,11 @@ bun scripts/cli.ts platform-infra sub2api codex-pool validate
|
||||
- `pool.groupName`: Sub2API group 名称。
|
||||
- `pool.apiKeySecretName` / `pool.apiKeySecretKey`: 统一消费 API key 的 k3s Secret 位置,默认 `platform-infra/sub2api-codex-pool-api-key.API_KEY`。
|
||||
- `pool.minOwnerBalanceUsd`: pool key owner 最低余额,sync/validate 会补齐。
|
||||
- `pool.minOwnerConcurrency`: 统一消费 API key owner 的最低并发,sync/validate 会补齐;用于避免共享 key 在用户并发层触发 WS 1013,不要用提高某个 provider capacity 来掩盖。
|
||||
- `pool.defaultTempUnschedulable`: 默认账号级临时下线规则;用于在上游返回容量、限流、overload 或认证状态异常时,让 Sub2API 冷却该账号并切换到同组其他账号。
|
||||
- `profiles.entries`: 从 master `~/.codex/` 选择上游 profile 并映射到 Sub2API account。
|
||||
- `profiles.entries[].capacity`: 可选 per-account concurrency override;不写则使用 `pool.defaultAccountCapacity`。具体数值只以 `config/platform-infra/sub2api-codex-pool.yaml` 为准,skill 和长期参考只描述规则,不重复写当前值。
|
||||
- 除非用户明确要求修改配置,不要仅凭推断改账号 membership、priority、capacity、WebSocket mode 或其他调度策略;先保留 YAML,完成 provenance/runtime evidence 溯源,并把结论写回相关 issue 或 runbook 后再提出变更。
|
||||
- `profiles.entries[].tempUnschedulable`: 可选 per-account 临时下线规则覆盖;字段语义以 `docs/reference/platform-infra.md` 为权威。
|
||||
- `profiles.entries[].openaiResponsesWebSocketsV2Mode`: 需要 Responses WebSocket v2 的上游才设置,值为 `off`、`ctx_pool` 或 `passthrough`。
|
||||
- `profiles.entries[].upstreamUserAgent`: 少数要求 Codex CLI User-Agent 的上游才设置,不能含换行。
|
||||
@@ -125,7 +127,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm
|
||||
|
||||
- `sub2api status`:Deployment/StatefulSet/Service/Secret 可见,运行镜像与 YAML 一致。
|
||||
- `sub2api validate`:app、PostgreSQL、Redis 和 service proxy 基础检查通过。
|
||||
- `codex-pool validate`:统一 key 的 `GET /v1/models` 成功,owner balance 已满足 YAML 最小值,capacity、WebSocket v2 和 temporary-unschedulable 运行时状态与 YAML 对齐。
|
||||
- `codex-pool validate`:统一 key 的 `GET /v1/models` 成功,owner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2 和 temporary-unschedulable 运行时状态与 YAML 对齐。
|
||||
- 若 `publicExposure.enabled=true`,确认 FRP path 可用;`expose --confirm` 会用未带 key 的 public `/v1/models` 401 作为网关可达性探针。
|
||||
|
||||
如果要证明真实模型请求可用,使用最小 `/v1/responses` 或等价 Codex smoke。不要把 group-level `/v1/models` 成功解释成每个上游 account 都健康。
|
||||
|
||||
@@ -4,6 +4,7 @@ pool:
|
||||
apiKeySecretName: sub2api-codex-pool-api-key
|
||||
apiKeySecretKey: API_KEY
|
||||
minOwnerBalanceUsd: 1000
|
||||
minOwnerConcurrency: 50
|
||||
defaultAccountCapacity: 5
|
||||
defaultTempUnschedulable:
|
||||
enabled: true
|
||||
@@ -51,7 +52,7 @@ profiles:
|
||||
configFile: config.toml.HY
|
||||
authFile: auth.json.HY
|
||||
openaiResponsesWebSocketsV2Mode: passthrough
|
||||
capacity: 20
|
||||
capacity: 10
|
||||
priority: 1
|
||||
- profile: gptclub
|
||||
accountName: unidesk-codex-gptclub
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
|
||||
- `pool.groupName` names the Sub2API group that represents the pool.
|
||||
- `pool.apiKeySecretName` and `pool.apiKeySecretKey` name the k3s Secret that stores the single consumer API key.
|
||||
- `pool.minOwnerConcurrency` declares the minimum concurrency for the Sub2API user that owns the unified consumer API key. Keep it high enough to cover the declared account capacity set, so the shared key does not fail WS sessions at the user-concurrency layer. Do not compensate for owner-concurrency 1013 errors by pinning capacity to one provider.
|
||||
- `pool.defaultTempUnschedulable` declares Sub2API account-level temporary unschedulable rules. Keep 429/overload/capacity failures in this YAML policy so the scheduler can cool down a failing account and choose another candidate instead of hard-pinning one provider.
|
||||
- `profiles.entries` selects local Codex profile files from `~/.codex/` and maps them to Sub2API account names.
|
||||
- `profiles.entries[].capacity` optionally overrides `pool.defaultAccountCapacity` for one account. Capacity is a YAML-controlled routing input; concrete current values belong only in `config/platform-infra/sub2api-codex-pool.yaml` and runtime validation output, not in long-term reference prose. Code constants, Secrets, ad-hoc runtime patches, or stale tests must not override YAML source of truth.
|
||||
- Do not change account membership, priority, capacity, WebSocket mode, or other routing policy from inference alone. Unless the user explicitly asks for a configuration change, first preserve the current YAML, collect provenance and runtime evidence, and write the finding to the relevant issue or runbook before proposing a change.
|
||||
- `profiles.entries[].tempUnschedulable` may override the pool default for one account. The CLI renders it into Sub2API credentials as `temp_unschedulable_enabled` and `temp_unschedulable_rules`; rules match HTTP status plus response-body keywords and place only that account into a temporary unschedulable cooldown.
|
||||
- `profiles.entries[].openaiResponsesWebSocketsV2Mode` is the account-level Responses WebSocket v2 switch for OpenAI-compatible upstreams that require WebSocket transport. Allowed values are `off`, `ctx_pool`, and `passthrough`; omit the field unless that upstream needs it.
|
||||
- `profiles.entries[].upstreamUserAgent` is an optional account-level upstream request User-Agent override. Use it only for upstreams that require a Codex CLI compatible User-Agent; keep the value YAML-controlled and newline-free.
|
||||
|
||||
@@ -9,6 +9,7 @@ const configPath = rootPath("config", "platform-infra", "sub2api-codex-pool.yaml
|
||||
const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as {
|
||||
pool?: {
|
||||
defaultAccountCapacity?: number;
|
||||
minOwnerConcurrency?: number;
|
||||
defaultTempUnschedulable?: {
|
||||
enabled?: boolean;
|
||||
rules?: Array<{ statusCode?: number; keywords?: string[]; durationMinutes?: number }>;
|
||||
@@ -18,25 +19,35 @@ const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as {
|
||||
};
|
||||
|
||||
const entries = parsed.profiles?.entries ?? [];
|
||||
const hy = entries.find((entry) => entry.profile === "HY");
|
||||
const wsEnabled = entries.filter((entry) => entry.openaiResponsesWebSocketsV2Mode && entry.openaiResponsesWebSocketsV2Mode !== "off");
|
||||
const rules = parsed.pool?.defaultTempUnschedulable?.rules ?? [];
|
||||
const overload429 = rules.find((rule) => rule.statusCode === 429);
|
||||
const defaultCapacity = parsed.pool?.defaultAccountCapacity ?? 0;
|
||||
const desiredCapacity = entries.reduce((total, entry) => total + (entry.capacity ?? defaultCapacity), 0);
|
||||
const allowedWebSocketModes = new Set(["off", "ctx_pool", "passthrough"]);
|
||||
|
||||
assertCondition(parsed.pool?.defaultAccountCapacity === 5, "Codex pool default capacity should remain explicit YAML", parsed.pool);
|
||||
assertCondition(hy !== undefined, "HY profile should remain declared as a YAML-managed pool candidate", entries);
|
||||
assertCondition(hy?.capacity === 20, "HY capacity must remain explicitly YAML-controlled at the current configured value", hy);
|
||||
assertCondition(wsEnabled.length >= 2, "Responses WSv2 should be a capability set with multiple candidates", wsEnabled);
|
||||
assertCondition(parsed.pool?.defaultTempUnschedulable?.enabled === true, "temporary unschedulable policy must be enabled from YAML", parsed.pool?.defaultTempUnschedulable);
|
||||
assertCondition(overload429 !== undefined, "temporary unschedulable policy must include 429 handling", rules);
|
||||
assertCondition(overload429?.keywords?.includes("too many requests"), "429 handling must catch provider concurrency/rate-limit text", overload429);
|
||||
assertCondition((overload429?.durationMinutes ?? 0) > 0, "429 handling must cool down for a positive duration", overload429);
|
||||
assertCondition(entries.length > 0, "Codex pool must declare YAML-managed profile entries", parsed.profiles);
|
||||
assertCondition(Number.isInteger(defaultCapacity) && defaultCapacity > 0, "defaultAccountCapacity must be a positive integer", parsed.pool);
|
||||
assertCondition(entries.every((entry) => typeof entry.profile === "string" && entry.profile.length > 0), "profile entries must declare profile names", entries);
|
||||
assertCondition(entries.every((entry) => typeof entry.accountName === "string" && entry.accountName.length > 0), "profile entries must declare account names", entries);
|
||||
assertCondition(entries.every((entry) => entry.capacity === undefined || (Number.isInteger(entry.capacity) && entry.capacity > 0)), "profile capacity overrides must be positive integers when declared", entries);
|
||||
assertCondition(
|
||||
entries.every((entry) => entry.openaiResponsesWebSocketsV2Mode === undefined || entry.openaiResponsesWebSocketsV2Mode === null || allowedWebSocketModes.has(entry.openaiResponsesWebSocketsV2Mode)),
|
||||
"profile WebSocket mode overrides must use supported values when declared",
|
||||
entries,
|
||||
);
|
||||
assertCondition((parsed.pool?.minOwnerConcurrency ?? 0) >= desiredCapacity, "pool owner concurrency must not bottleneck the declared account capacity set", { minOwnerConcurrency: parsed.pool?.minOwnerConcurrency, desiredCapacity });
|
||||
if (parsed.pool?.defaultTempUnschedulable?.enabled === true) {
|
||||
assertCondition(rules.length > 0, "enabled temporary unschedulable policy must declare rules", parsed.pool?.defaultTempUnschedulable);
|
||||
assertCondition(rules.every((rule) => Number.isInteger(rule.statusCode) && (rule.statusCode ?? 0) >= 100 && (rule.statusCode ?? 0) <= 599), "temporary unschedulable rules must declare valid HTTP status codes", rules);
|
||||
assertCondition(rules.every((rule) => Array.isArray(rule.keywords) && rule.keywords.length > 0), "temporary unschedulable rules must declare non-empty keywords", rules);
|
||||
assertCondition(rules.every((rule) => Number.isInteger(rule.durationMinutes) && (rule.durationMinutes ?? 0) > 0), "temporary unschedulable rules must declare positive cooldown durations", rules);
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
checks: [
|
||||
"HY capacity is YAML-controlled",
|
||||
"WSv2 routing is represented as a multi-account capability set",
|
||||
"temporary unschedulable rules are YAML-controlled",
|
||||
"routing config is schema-valid without profile-specific test gates",
|
||||
"pool owner concurrency covers the YAML account capacity set",
|
||||
"optional WebSocket mode overrides use supported values",
|
||||
"temporary unschedulable rules are structurally valid when enabled",
|
||||
],
|
||||
}));
|
||||
|
||||
@@ -17,20 +17,14 @@ const credentials = renderSub2ApiTempUnschedulableCredentials(policy) as {
|
||||
};
|
||||
|
||||
const rules = credentials.temp_unschedulable_rules ?? [];
|
||||
const overload429 = rules.find((rule) => rule.error_code === 429);
|
||||
const overloaded529 = rules.find((rule) => rule.error_code === 529);
|
||||
|
||||
assertCondition(credentials.temp_unschedulable_enabled === true, "default policy must enable Sub2API temporary unschedulable mode", credentials);
|
||||
assertCondition(Array.isArray(credentials.temp_unschedulable_rules), "Sub2API rules must be rendered as an array", credentials);
|
||||
assertCondition(rules.length === policy.rules.length, "rendered rule count must match the UniDesk policy", { policy, credentials });
|
||||
assertCondition(overload429 !== undefined, "rendered policy must include 429 handling", rules);
|
||||
assertCondition(overload429?.keywords?.includes("capacity"), "429 handling must catch capacity text", overload429);
|
||||
assertCondition(overload429?.keywords?.includes("too many requests"), "429 handling must catch rate-limit text", overload429);
|
||||
assertCondition(overload429?.duration_minutes === 10, "429 cooldown must match the default policy", overload429);
|
||||
assertCondition(overloaded529 !== undefined, "rendered policy must include provider overloaded status 529", rules);
|
||||
assertCondition(rules.every((rule) => typeof rule.error_code === "number"), "rules must use Sub2API error_code field names", rules);
|
||||
assertCondition(rules.every((rule) => typeof rule.duration_minutes === "number"), "rules must use Sub2API duration_minutes field names", rules);
|
||||
assertCondition(rules.every((rule) => Array.isArray(rule.keywords) && rule.keywords.length > 0), "rules must include non-empty keyword lists", rules);
|
||||
assertCondition(rules.every((rule, index) => rule.error_code === policy.rules[index]?.statusCode), "rules must map statusCode to Sub2API error_code", { policy, credentials });
|
||||
assertCondition(rules.every((rule, index) => rule.duration_minutes === policy.rules[index]?.durationMinutes), "rules must map durationMinutes to Sub2API duration_minutes", { policy, credentials });
|
||||
assertCondition(rules.every((rule, index) => JSON.stringify(rule.keywords ?? []) === JSON.stringify(policy.rules[index]?.keywords ?? [])), "rules must preserve policy keywords", { policy, credentials });
|
||||
assertCondition(rules.every((rule, index) => rule.description === policy.rules[index]?.description), "rules must preserve policy descriptions", { policy, credentials });
|
||||
assertCondition(!("pool_mode" in credentials), "pool_mode must not be enabled because it retries the same account instead of cooling it down", credentials);
|
||||
assertCondition(!("api_key" in credentials) && !("base_url" in credentials), "temporary-unschedulable rendering must not include secrets or endpoints", credentials);
|
||||
|
||||
@@ -46,7 +40,7 @@ console.log(JSON.stringify({
|
||||
ok: true,
|
||||
checks: [
|
||||
"temporary unschedulable policy renders to Sub2API credential field names",
|
||||
"capacity/rate-limit errors cool down an account without enabling pool_mode",
|
||||
"temporary unschedulable rendering follows the input policy without hard-coded policy gates",
|
||||
"disabled policies clear runtime rules",
|
||||
],
|
||||
}));
|
||||
|
||||
@@ -74,6 +74,7 @@ interface CodexPoolConfig {
|
||||
apiKeySecretName: string;
|
||||
apiKeySecretKey: string;
|
||||
minOwnerBalanceUsd: number;
|
||||
minOwnerConcurrency: number;
|
||||
defaultAccountCapacity: number;
|
||||
defaultTempUnschedulable: CodexTempUnschedulablePolicy;
|
||||
profiles: CodexPoolProfileConfig[];
|
||||
@@ -262,6 +263,7 @@ async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promi
|
||||
apiKeySecretName: pool.apiKeySecretName,
|
||||
apiKeySecretKey: pool.apiKeySecretKey,
|
||||
minOwnerBalanceUsd: pool.minOwnerBalanceUsd,
|
||||
minOwnerConcurrency: pool.minOwnerConcurrency,
|
||||
defaultAccountCapacity: pool.defaultAccountCapacity,
|
||||
},
|
||||
profiles: profiles.map((profile) => ({
|
||||
@@ -567,6 +569,7 @@ function readCodexPoolConfig(): CodexPoolConfig {
|
||||
apiKeySecretName: stringValue(pool.apiKeySecretName) ?? defaults.apiKeySecretName,
|
||||
apiKeySecretKey: stringValue(pool.apiKeySecretKey) ?? defaults.apiKeySecretKey,
|
||||
minOwnerBalanceUsd: numberValue(pool.minOwnerBalanceUsd) ?? defaults.minOwnerBalanceUsd,
|
||||
minOwnerConcurrency: readAccountCapacity(pool.minOwnerConcurrency, "pool.minOwnerConcurrency"),
|
||||
defaultAccountCapacity: readAccountCapacity(pool.defaultAccountCapacity, "pool.defaultAccountCapacity"),
|
||||
defaultTempUnschedulable,
|
||||
profiles: readProfileConfig(parsed.profiles, defaults.profiles, defaultTempUnschedulable),
|
||||
@@ -579,6 +582,10 @@ function readCodexPoolConfig(): CodexPoolConfig {
|
||||
throw new Error(`${codexPoolConfigPath}.pool.apiKeySecretKey must be a valid secret key name`);
|
||||
}
|
||||
if (config.minOwnerBalanceUsd <= 0) throw new Error(`${codexPoolConfigPath}.pool.minOwnerBalanceUsd must be > 0`);
|
||||
const declaredAccountCapacity = config.profiles.reduce((total, profile) => total + (profile.capacity ?? config.defaultAccountCapacity), 0);
|
||||
if (declaredAccountCapacity > 0 && config.minOwnerConcurrency < declaredAccountCapacity) {
|
||||
throw new Error(`${codexPoolConfigPath}.pool.minOwnerConcurrency must be >= the sum of declared account capacities (${declaredAccountCapacity})`);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -589,6 +596,8 @@ function defaultCodexPoolConfig(): CodexPoolConfig {
|
||||
apiKeySecretName: defaultPoolApiKeySecretName,
|
||||
apiKeySecretKey: defaultPoolApiKeySecretKey,
|
||||
minOwnerBalanceUsd: defaultMinOwnerBalanceUsd,
|
||||
// Only used for the no-YAML fallback path; real pool configs must declare pool.minOwnerConcurrency.
|
||||
minOwnerConcurrency: 1,
|
||||
defaultAccountCapacity: 5,
|
||||
defaultTempUnschedulable: defaultCodexTempUnschedulablePolicy(),
|
||||
profiles: [],
|
||||
@@ -986,6 +995,7 @@ function poolTarget(pool = readCodexPoolConfig()): Record<string, unknown> {
|
||||
groupName: pool.groupName,
|
||||
apiKeyName: pool.apiKeyName,
|
||||
apiKeySecret: `${namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}`,
|
||||
minOwnerConcurrency: pool.minOwnerConcurrency,
|
||||
defaultAccountCapacity: pool.defaultAccountCapacity,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
@@ -1698,6 +1708,7 @@ POOL_API_KEY_NAME = "${pool.apiKeyName}"
|
||||
POOL_API_KEY_SECRET_NAME = "${pool.apiKeySecretName}"
|
||||
POOL_API_KEY_SECRET_KEY = "${pool.apiKeySecretKey}"
|
||||
MIN_OWNER_BALANCE_USD = ${JSON.stringify(pool.minOwnerBalanceUsd)}
|
||||
MIN_OWNER_CONCURRENCY = ${JSON.stringify(pool.minOwnerConcurrency)}
|
||||
POOL_DEFAULT_ACCOUNT_CAPACITY = ${JSON.stringify(pool.defaultAccountCapacity)}
|
||||
EXPECTED_ACCOUNT_CAPACITIES = ${JSON.stringify(desiredAccountCapacityMap(pool))}
|
||||
EXPECTED_ACCOUNT_WS_MODES = json.loads(${JSON.stringify(JSON.stringify(desiredAccountWebSocketsV2ModeMap(pool)))})
|
||||
@@ -2105,6 +2116,37 @@ def ensure_pool_owner_balance(token, user_id):
|
||||
"minimumBalanceUsd": MIN_OWNER_BALANCE_USD,
|
||||
}
|
||||
|
||||
def ensure_pool_owner_concurrency(token, user_id):
|
||||
user = get_admin_user(token, user_id)
|
||||
try:
|
||||
current = int(user.get("concurrency") or 0)
|
||||
except Exception:
|
||||
current = 0
|
||||
if current >= MIN_OWNER_CONCURRENCY:
|
||||
return {
|
||||
"ok": True,
|
||||
"action": "kept-existing",
|
||||
"userId": user_id,
|
||||
"concurrencyBefore": current,
|
||||
"concurrencyAfter": current,
|
||||
"minimumConcurrency": MIN_OWNER_CONCURRENCY,
|
||||
}
|
||||
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/users/{user_id}", bearer=token, payload={
|
||||
"concurrency": MIN_OWNER_CONCURRENCY,
|
||||
}), "set API key owner concurrency")
|
||||
try:
|
||||
after = int(updated.get("concurrency") or MIN_OWNER_CONCURRENCY) if isinstance(updated, dict) else MIN_OWNER_CONCURRENCY
|
||||
except Exception:
|
||||
after = MIN_OWNER_CONCURRENCY
|
||||
return {
|
||||
"ok": after >= MIN_OWNER_CONCURRENCY,
|
||||
"action": "set",
|
||||
"userId": user_id,
|
||||
"concurrencyBefore": current,
|
||||
"concurrencyAfter": after,
|
||||
"minimumConcurrency": MIN_OWNER_CONCURRENCY,
|
||||
}
|
||||
|
||||
def validate_gateway(api_key):
|
||||
resp = curl_api("GET", "/v1/models", bearer=api_key)
|
||||
parsed = resp.get("json")
|
||||
@@ -2369,9 +2411,10 @@ def run_sync():
|
||||
api_key, secret_action, secret_apply_stdout = ensure_api_key_secret(group_id)
|
||||
api_key_result = ensure_sub2api_api_key(token, api_key, group_id)
|
||||
owner_balance = ensure_pool_owner_balance(token, api_key_result["userId"])
|
||||
owner_concurrency = ensure_pool_owner_concurrency(token, api_key_result["userId"])
|
||||
gateway = validate_gateway(api_key)
|
||||
return {
|
||||
"ok": gateway["ok"] is True and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"ok": gateway["ok"] is True and owner_concurrency["ok"] is True and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"mode": "sync",
|
||||
"namespace": NAMESPACE,
|
||||
"serviceDns": SERVICE_DNS,
|
||||
@@ -2403,6 +2446,7 @@ def run_sync():
|
||||
"valuesPrinted": False,
|
||||
},
|
||||
"ownerBalance": owner_balance,
|
||||
"ownerConcurrency": owner_concurrency,
|
||||
"validation": {"gatewayModels": gateway},
|
||||
}
|
||||
|
||||
@@ -2413,14 +2457,16 @@ def run_validate():
|
||||
admin_email, token = login()
|
||||
key_item = next((item for item in list_user_keys(token) if item.get("key") == api_key), None)
|
||||
owner_balance = None
|
||||
owner_concurrency = None
|
||||
if key_item is not None and key_item.get("user_id") is not None:
|
||||
owner_balance = ensure_pool_owner_balance(token, key_item["user_id"])
|
||||
owner_concurrency = ensure_pool_owner_concurrency(token, key_item["user_id"])
|
||||
capacity_status = account_capacity_status(token)
|
||||
ws_v2_status = account_ws_v2_status(token)
|
||||
temp_unschedulable_status = account_temp_unschedulable_status(token)
|
||||
gateway = validate_gateway(api_key)
|
||||
return {
|
||||
"ok": gateway["ok"] is True and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"ok": gateway["ok"] is True and (owner_concurrency is None or owner_concurrency["ok"] is True) and capacity_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True,
|
||||
"mode": "validate",
|
||||
"namespace": NAMESPACE,
|
||||
"serviceDns": SERVICE_DNS,
|
||||
@@ -2434,6 +2480,7 @@ def run_validate():
|
||||
"valuesPrinted": False,
|
||||
},
|
||||
"ownerBalance": owner_balance,
|
||||
"ownerConcurrency": owner_concurrency,
|
||||
"capacity": capacity_status,
|
||||
"webSocketsV2": ws_v2_status,
|
||||
"tempUnschedulable": temp_unschedulable_status,
|
||||
|
||||
Reference in New Issue
Block a user