fix(worker): 香港站点直连 Gemini 官方平台
官方 Gemini 平台配置的共享 HTTP Proxy 会拒绝香港出口的 CONNECT 请求,导致香港 Worker 的真实 VEO 与图片任务进入 upstream_submission_unknown。 新增仅按平台 UUID 生效的代理直连白名单,并只在香港 Worker 为官方 Gemini 平台启用;宁波与其他平台继续使用原代理策略。 验证:API 全量 go test、代理路由单测、bash -n、ShellCheck、cluster release helper、manual release 与差异检查均通过。
This commit is contained in:
@@ -55,6 +55,7 @@ type Config struct {
|
||||
CORSAllowedOrigin string
|
||||
GlobalHTTPProxy string
|
||||
GlobalHTTPProxySource string
|
||||
PlatformProxyBypassIDs string
|
||||
LogLevel slog.Level
|
||||
BillingEngineMode string
|
||||
ProcessRole string
|
||||
@@ -155,6 +156,7 @@ func Load() Config {
|
||||
CORSAllowedOrigin: env("CORS_ALLOWED_ORIGIN", "http://localhost:5178,http://127.0.0.1:5178"),
|
||||
GlobalHTTPProxy: globalProxy.HTTPProxy,
|
||||
GlobalHTTPProxySource: globalProxy.Source,
|
||||
PlatformProxyBypassIDs: env("AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS", ""),
|
||||
LogLevel: logLevel(env("LOG_LEVEL", "info")),
|
||||
BillingEngineMode: strings.ToLower(env("BILLING_ENGINE_MODE", "observe")),
|
||||
ProcessRole: strings.ToLower(strings.TrimSpace(env("AI_GATEWAY_PROCESS_ROLE", "all"))),
|
||||
|
||||
@@ -38,6 +38,13 @@ func (s *Service) httpClientForCandidate(candidate store.RuntimeModelCandidate,
|
||||
if simulated {
|
||||
return s.httpClients.none, nil
|
||||
}
|
||||
// Some Worker sites have direct provider egress but are not authorized to
|
||||
// use a platform's shared proxy. Keep this override explicitly scoped to
|
||||
// platform UUIDs so one site's routing exception cannot bypass proxies for
|
||||
// unrelated providers or platforms.
|
||||
if platformIDListed(s.cfg.PlatformProxyBypassIDs, candidate.PlatformID) {
|
||||
return s.httpClients.none, nil
|
||||
}
|
||||
config, err := netproxy.Normalize(netproxy.FromPlatformConfig(candidate.PlatformConfig))
|
||||
if err != nil {
|
||||
return nil, &clients.ClientError{Code: "invalid_proxy", Message: err.Error(), Retryable: false}
|
||||
@@ -55,6 +62,19 @@ func (s *Service) httpClientForCandidate(candidate store.RuntimeModelCandidate,
|
||||
}
|
||||
}
|
||||
|
||||
func platformIDListed(raw string, platformID string) bool {
|
||||
platformID = strings.TrimSpace(platformID)
|
||||
if platformID == "" {
|
||||
return false
|
||||
}
|
||||
for value := range strings.SplitSeq(raw, ",") {
|
||||
if strings.TrimSpace(value) == platformID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *httpClientCache) customClient(rawProxy string) (*http.Client, error) {
|
||||
normalized, proxyURL, err := netproxy.ParseHTTPProxy(rawProxy)
|
||||
if err != nil {
|
||||
|
||||
@@ -101,6 +101,41 @@ func TestPlatformProxyModeCustomUsesConfiguredHTTPProxy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformProxyBypassIDUsesDirectConnection(t *testing.T) {
|
||||
var targetHits int
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
targetHits++
|
||||
_, _ = w.Write([]byte("target"))
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
var proxyHits int
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
proxyHits++
|
||||
w.WriteHeader(http.StatusProxyAuthRequired)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
client, err := testProxyService(config.Config{
|
||||
PlatformProxyBypassIDs: "other-platform, official-gemini-platform ",
|
||||
}).httpClientForCandidate(store.RuntimeModelCandidate{
|
||||
PlatformID: "official-gemini-platform",
|
||||
PlatformConfig: map[string]any{"networkProxy": map[string]any{"mode": "custom", "httpProxy": proxy.URL}},
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatalf("build bypassed http client: %v", err)
|
||||
}
|
||||
resp, err := client.Get(target.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("get target directly: %v", err)
|
||||
}
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK || targetHits != 1 || proxyHits != 0 {
|
||||
t.Fatalf("unexpected status=%d targetHits=%d proxyHits=%d", resp.StatusCode, targetHits, proxyHits)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformProxyModeGlobalUsesConfiguredGlobalHTTPProxy(t *testing.T) {
|
||||
var targetHits int
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -715,6 +715,8 @@ spec:
|
||||
fieldPath: metadata.uid
|
||||
- name: EASYAI_SITE
|
||||
value: hongkong
|
||||
- name: AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS
|
||||
value: 99372d7c-f2a4-472a-987f-30cb76c7962c
|
||||
ports:
|
||||
- name: health
|
||||
containerPort: 8088
|
||||
|
||||
@@ -116,5 +116,7 @@ fi
|
||||
hongkong_strategy=$(deployment_strategy easyai-worker-hongkong)
|
||||
grep -Fq ' replicas: 1' <<<"$hongkong_strategy"
|
||||
grep -Fq 'easyai.io/worker-only' <<<"$hongkong_strategy"
|
||||
grep -Fq 'AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS' <<<"$hongkong_strategy"
|
||||
grep -Fq '99372d7c-f2a4-472a-987f-30cb76c7962c' <<<"$hongkong_strategy"
|
||||
|
||||
echo 'cluster_release_helper_tests=PASS'
|
||||
|
||||
Reference in New Issue
Block a user