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
|
CORSAllowedOrigin string
|
||||||
GlobalHTTPProxy string
|
GlobalHTTPProxy string
|
||||||
GlobalHTTPProxySource string
|
GlobalHTTPProxySource string
|
||||||
|
PlatformProxyBypassIDs string
|
||||||
LogLevel slog.Level
|
LogLevel slog.Level
|
||||||
BillingEngineMode string
|
BillingEngineMode string
|
||||||
ProcessRole string
|
ProcessRole string
|
||||||
@@ -155,6 +156,7 @@ func Load() Config {
|
|||||||
CORSAllowedOrigin: env("CORS_ALLOWED_ORIGIN", "http://localhost:5178,http://127.0.0.1:5178"),
|
CORSAllowedOrigin: env("CORS_ALLOWED_ORIGIN", "http://localhost:5178,http://127.0.0.1:5178"),
|
||||||
GlobalHTTPProxy: globalProxy.HTTPProxy,
|
GlobalHTTPProxy: globalProxy.HTTPProxy,
|
||||||
GlobalHTTPProxySource: globalProxy.Source,
|
GlobalHTTPProxySource: globalProxy.Source,
|
||||||
|
PlatformProxyBypassIDs: env("AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS", ""),
|
||||||
LogLevel: logLevel(env("LOG_LEVEL", "info")),
|
LogLevel: logLevel(env("LOG_LEVEL", "info")),
|
||||||
BillingEngineMode: strings.ToLower(env("BILLING_ENGINE_MODE", "observe")),
|
BillingEngineMode: strings.ToLower(env("BILLING_ENGINE_MODE", "observe")),
|
||||||
ProcessRole: strings.ToLower(strings.TrimSpace(env("AI_GATEWAY_PROCESS_ROLE", "all"))),
|
ProcessRole: strings.ToLower(strings.TrimSpace(env("AI_GATEWAY_PROCESS_ROLE", "all"))),
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ func (s *Service) httpClientForCandidate(candidate store.RuntimeModelCandidate,
|
|||||||
if simulated {
|
if simulated {
|
||||||
return s.httpClients.none, nil
|
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))
|
config, err := netproxy.Normalize(netproxy.FromPlatformConfig(candidate.PlatformConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &clients.ClientError{Code: "invalid_proxy", Message: err.Error(), Retryable: false}
|
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) {
|
func (c *httpClientCache) customClient(rawProxy string) (*http.Client, error) {
|
||||||
normalized, proxyURL, err := netproxy.ParseHTTPProxy(rawProxy)
|
normalized, proxyURL, err := netproxy.ParseHTTPProxy(rawProxy)
|
||||||
if err != nil {
|
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) {
|
func TestPlatformProxyModeGlobalUsesConfiguredGlobalHTTPProxy(t *testing.T) {
|
||||||
var targetHits int
|
var targetHits int
|
||||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -715,6 +715,8 @@ spec:
|
|||||||
fieldPath: metadata.uid
|
fieldPath: metadata.uid
|
||||||
- name: EASYAI_SITE
|
- name: EASYAI_SITE
|
||||||
value: hongkong
|
value: hongkong
|
||||||
|
- name: AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS
|
||||||
|
value: 99372d7c-f2a4-472a-987f-30cb76c7962c
|
||||||
ports:
|
ports:
|
||||||
- name: health
|
- name: health
|
||||||
containerPort: 8088
|
containerPort: 8088
|
||||||
|
|||||||
@@ -116,5 +116,7 @@ fi
|
|||||||
hongkong_strategy=$(deployment_strategy easyai-worker-hongkong)
|
hongkong_strategy=$(deployment_strategy easyai-worker-hongkong)
|
||||||
grep -Fq ' replicas: 1' <<<"$hongkong_strategy"
|
grep -Fq ' replicas: 1' <<<"$hongkong_strategy"
|
||||||
grep -Fq 'easyai.io/worker-only' <<<"$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'
|
echo 'cluster_release_helper_tests=PASS'
|
||||||
|
|||||||
Reference in New Issue
Block a user