feat(adapter): externalize platform worker runtime tuning

This commit is contained in:
Your Name
2026-05-06 10:54:08 +08:00
parent cbbb553e9f
commit 16e53361f2
7 changed files with 183 additions and 18 deletions

View File

@@ -178,6 +178,9 @@ func New(cfg *config.Config, logger *slog.Logger) (*App, error) {
profile.CallbackMaxRetries,
)
worker.Logger = logger
worker.PollInterval = time.Duration(profile.CallbackPollIntervalMS) * time.Millisecond
worker.BatchSize = profile.CallbackBatchSize
worker.RetrySchedule = toRetrySchedule(profile.CallbackRetrySchedule)
go worker.Start(workerCtx)
}
startWorker("sub2api", cfg.PlatformAdapters.Sub2API)
@@ -202,6 +205,19 @@ func New(cfg *config.Config, logger *slog.Logger) (*App, error) {
}, nil
}
func toRetrySchedule(seconds []int) []time.Duration {
if len(seconds) == 0 {
return nil
}
result := make([]time.Duration, 0, len(seconds))
for _, value := range seconds {
if value > 0 {
result = append(result, time.Duration(value)*time.Second)
}
}
return result
}
func (a *App) TicketStore() ticketLister {
return a.ticketStore
}

View File

@@ -110,6 +110,9 @@ func TestNew_RegistersPlatformWebhookRouteWhenSub2APIEnabled(t *testing.T) {
cfg.PlatformAdapters.Enabled = true
cfg.PlatformAdapters.Sub2API.Enabled = true
cfg.PlatformAdapters.Sub2API.IngressSecret = "sub2api-secret"
cfg.PlatformAdapters.Sub2API.CallbackPollIntervalMS = 2500
cfg.PlatformAdapters.Sub2API.CallbackBatchSize = 8
cfg.PlatformAdapters.Sub2API.CallbackRetrySchedule = []int{5, 15, 45}
app, err := New(cfg, logging.New())
if err != nil {
@@ -124,6 +127,13 @@ func TestNew_RegistersPlatformWebhookRouteWhenSub2APIEnabled(t *testing.T) {
}
}
func TestToRetrySchedule(t *testing.T) {
got := toRetrySchedule([]int{5, 15, 45})
if len(got) != 3 || got[0] != 5*time.Second || got[1] != 15*time.Second || got[2] != 45*time.Second {
t.Fatalf("toRetrySchedule() = %v, want [5s 15s 45s]", got)
}
}
func TestApp_TicketStore(t *testing.T) {
cfg := minimalHTTPConfig()
cfg.Webhook.Secret = "test-secret"

View File

@@ -52,12 +52,15 @@ type PlatformAdaptersConfig struct {
}
type PlatformAdapterProfileConfig struct {
Enabled bool
IngressSecret string
CallbackBaseURL string
CallbackSecret string
CallbackTimeoutMS int
CallbackMaxRetries int
Enabled bool
IngressSecret string
CallbackBaseURL string
CallbackSecret string
CallbackTimeoutMS int
CallbackMaxRetries int
CallbackPollIntervalMS int
CallbackBatchSize int
CallbackRetrySchedule []int
}
func Load() (*Config, error) {
@@ -88,20 +91,26 @@ func Load() (*Config, error) {
PlatformAdapters: PlatformAdaptersConfig{
Enabled: getEnvBool("AI_CS_PLATFORM_ADAPTERS_ENABLED", false),
Sub2API: PlatformAdapterProfileConfig{
Enabled: getEnvBool("AI_CS_PLATFORM_SUB2API_ENABLED", false),
IngressSecret: getEnv("AI_CS_PLATFORM_SUB2API_INGRESS_SECRET", ""),
CallbackBaseURL: getEnv("AI_CS_PLATFORM_SUB2API_CALLBACK_BASE_URL", ""),
CallbackSecret: getEnv("AI_CS_PLATFORM_SUB2API_CALLBACK_SECRET", ""),
CallbackTimeoutMS: getEnvInt("AI_CS_PLATFORM_SUB2API_CALLBACK_TIMEOUT_MS", 3000),
CallbackMaxRetries: getEnvInt("AI_CS_PLATFORM_SUB2API_CALLBACK_MAX_RETRIES", 5),
Enabled: getEnvBool("AI_CS_PLATFORM_SUB2API_ENABLED", false),
IngressSecret: getEnv("AI_CS_PLATFORM_SUB2API_INGRESS_SECRET", ""),
CallbackBaseURL: getEnv("AI_CS_PLATFORM_SUB2API_CALLBACK_BASE_URL", ""),
CallbackSecret: getEnv("AI_CS_PLATFORM_SUB2API_CALLBACK_SECRET", ""),
CallbackTimeoutMS: getEnvInt("AI_CS_PLATFORM_SUB2API_CALLBACK_TIMEOUT_MS", 3000),
CallbackMaxRetries: getEnvInt("AI_CS_PLATFORM_SUB2API_CALLBACK_MAX_RETRIES", 5),
CallbackPollIntervalMS: getEnvInt("AI_CS_PLATFORM_SUB2API_CALLBACK_POLL_INTERVAL_MS", 5000),
CallbackBatchSize: getEnvInt("AI_CS_PLATFORM_SUB2API_CALLBACK_BATCH_SIZE", 20),
CallbackRetrySchedule: getEnvIntList("AI_CS_PLATFORM_SUB2API_CALLBACK_RETRY_SCHEDULE_SEC", []int{10, 30, 60, 300, 900}),
},
NewAPI: PlatformAdapterProfileConfig{
Enabled: getEnvBool("AI_CS_PLATFORM_NEWAPI_ENABLED", false),
IngressSecret: getEnv("AI_CS_PLATFORM_NEWAPI_INGRESS_SECRET", ""),
CallbackBaseURL: getEnv("AI_CS_PLATFORM_NEWAPI_CALLBACK_BASE_URL", ""),
CallbackSecret: getEnv("AI_CS_PLATFORM_NEWAPI_CALLBACK_SECRET", ""),
CallbackTimeoutMS: getEnvInt("AI_CS_PLATFORM_NEWAPI_CALLBACK_TIMEOUT_MS", 3000),
CallbackMaxRetries: getEnvInt("AI_CS_PLATFORM_NEWAPI_CALLBACK_MAX_RETRIES", 5),
Enabled: getEnvBool("AI_CS_PLATFORM_NEWAPI_ENABLED", false),
IngressSecret: getEnv("AI_CS_PLATFORM_NEWAPI_INGRESS_SECRET", ""),
CallbackBaseURL: getEnv("AI_CS_PLATFORM_NEWAPI_CALLBACK_BASE_URL", ""),
CallbackSecret: getEnv("AI_CS_PLATFORM_NEWAPI_CALLBACK_SECRET", ""),
CallbackTimeoutMS: getEnvInt("AI_CS_PLATFORM_NEWAPI_CALLBACK_TIMEOUT_MS", 3000),
CallbackMaxRetries: getEnvInt("AI_CS_PLATFORM_NEWAPI_CALLBACK_MAX_RETRIES", 5),
CallbackPollIntervalMS: getEnvInt("AI_CS_PLATFORM_NEWAPI_CALLBACK_POLL_INTERVAL_MS", 5000),
CallbackBatchSize: getEnvInt("AI_CS_PLATFORM_NEWAPI_CALLBACK_BATCH_SIZE", 20),
CallbackRetrySchedule: getEnvIntList("AI_CS_PLATFORM_NEWAPI_CALLBACK_RETRY_SCHEDULE_SEC", []int{10, 30, 60, 300, 900}),
},
},
Runtime: RuntimeConfig{
@@ -152,6 +161,20 @@ func validatePlatformProfile(platform string, adaptersEnabled bool, profile Plat
if profile.CallbackMaxRetries < 0 {
return fmt.Errorf("AI_CS_PLATFORM_%s_CALLBACK_MAX_RETRIES must not be negative", upperPlatform)
}
if profile.CallbackPollIntervalMS <= 0 {
return fmt.Errorf("AI_CS_PLATFORM_%s_CALLBACK_POLL_INTERVAL_MS must be positive", upperPlatform)
}
if profile.CallbackBatchSize <= 0 {
return fmt.Errorf("AI_CS_PLATFORM_%s_CALLBACK_BATCH_SIZE must be positive", upperPlatform)
}
if len(profile.CallbackRetrySchedule) == 0 {
return fmt.Errorf("AI_CS_PLATFORM_%s_CALLBACK_RETRY_SCHEDULE_SEC must not be empty", upperPlatform)
}
for _, seconds := range profile.CallbackRetrySchedule {
if seconds <= 0 {
return fmt.Errorf("AI_CS_PLATFORM_%s_CALLBACK_RETRY_SCHEDULE_SEC must contain only positive integers", upperPlatform)
}
}
return nil
}
@@ -213,3 +236,20 @@ func getEnvBool(key string, fallback bool) bool {
return fallback
}
}
func getEnvIntList(key string, fallback []int) []int {
value := strings.TrimSpace(os.Getenv(key))
if value == "" {
return append([]int(nil), fallback...)
}
parts := strings.Split(value, ",")
result := make([]int, 0, len(parts))
for _, part := range parts {
parsed, err := strconv.Atoi(strings.TrimSpace(part))
if err != nil {
return append([]int(nil), fallback...)
}
result = append(result, parsed)
}
return result
}

View File

@@ -85,6 +85,22 @@ func TestGetEnvInt64_ValidValue(t *testing.T) {
}
}
func TestGetEnvIntList_ValidValue(t *testing.T) {
t.Setenv("TEST_INT_LIST", "10,30,60")
got := getEnvIntList("TEST_INT_LIST", []int{1})
if len(got) != 3 || got[0] != 10 || got[1] != 30 || got[2] != 60 {
t.Fatalf("getEnvIntList(TEST_INT_LIST) = %v, want [10 30 60]", got)
}
}
func TestGetEnvIntList_InvalidValueFallsBack(t *testing.T) {
t.Setenv("TEST_INT_LIST", "10,oops,60")
got := getEnvIntList("TEST_INT_LIST", []int{1, 2})
if len(got) != 2 || got[0] != 1 || got[1] != 2 {
t.Fatalf("getEnvIntList(invalid) = %v, want [1 2]", got)
}
}
func TestLoadDefaults(t *testing.T) {
t.Setenv("AI_CS_ADDR", "")
cfg, err := Load()
@@ -240,6 +256,21 @@ func TestLoad_RejectsEnabledSub2APIWithoutIngressSecret(t *testing.T) {
}
}
func TestLoad_RejectsEnabledSub2APIWithInvalidWorkerPollingConfig(t *testing.T) {
t.Setenv("AI_CS_PLATFORM_ADAPTERS_ENABLED", "true")
t.Setenv("AI_CS_PLATFORM_SUB2API_ENABLED", "true")
t.Setenv("AI_CS_PLATFORM_SUB2API_INGRESS_SECRET", "sub2api-secret")
t.Setenv("AI_CS_PLATFORM_SUB2API_CALLBACK_POLL_INTERVAL_MS", "0")
_, err := Load()
if err == nil {
t.Fatal("expected error when sub2api callback poll interval is invalid")
}
if !strings.Contains(err.Error(), "AI_CS_PLATFORM_SUB2API_CALLBACK_POLL_INTERVAL_MS") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoad_PlatformAdapterOverrides(t *testing.T) {
t.Setenv("AI_CS_PLATFORM_ADAPTERS_ENABLED", "true")
t.Setenv("AI_CS_PLATFORM_SUB2API_ENABLED", "true")
@@ -248,6 +279,9 @@ func TestLoad_PlatformAdapterOverrides(t *testing.T) {
t.Setenv("AI_CS_PLATFORM_SUB2API_CALLBACK_SECRET", "cb-secret")
t.Setenv("AI_CS_PLATFORM_SUB2API_CALLBACK_TIMEOUT_MS", "4000")
t.Setenv("AI_CS_PLATFORM_SUB2API_CALLBACK_MAX_RETRIES", "7")
t.Setenv("AI_CS_PLATFORM_SUB2API_CALLBACK_POLL_INTERVAL_MS", "2500")
t.Setenv("AI_CS_PLATFORM_SUB2API_CALLBACK_BATCH_SIZE", "12")
t.Setenv("AI_CS_PLATFORM_SUB2API_CALLBACK_RETRY_SCHEDULE_SEC", "5,15,45")
cfg, err := Load()
if err != nil {
@@ -274,4 +308,13 @@ func TestLoad_PlatformAdapterOverrides(t *testing.T) {
if cfg.PlatformAdapters.Sub2API.CallbackMaxRetries != 7 {
t.Fatalf("sub2api callback max retries = %d, want 7", cfg.PlatformAdapters.Sub2API.CallbackMaxRetries)
}
if cfg.PlatformAdapters.Sub2API.CallbackPollIntervalMS != 2500 {
t.Fatalf("sub2api callback poll interval ms = %d, want 2500", cfg.PlatformAdapters.Sub2API.CallbackPollIntervalMS)
}
if cfg.PlatformAdapters.Sub2API.CallbackBatchSize != 12 {
t.Fatalf("sub2api callback batch size = %d, want 12", cfg.PlatformAdapters.Sub2API.CallbackBatchSize)
}
if len(cfg.PlatformAdapters.Sub2API.CallbackRetrySchedule) != 3 || cfg.PlatformAdapters.Sub2API.CallbackRetrySchedule[0] != 5 || cfg.PlatformAdapters.Sub2API.CallbackRetrySchedule[1] != 15 || cfg.PlatformAdapters.Sub2API.CallbackRetrySchedule[2] != 45 {
t.Fatalf("sub2api callback retry schedule = %v, want [5 15 45]", cfg.PlatformAdapters.Sub2API.CallbackRetrySchedule)
}
}