feat(adapter): externalize platform worker runtime tuning
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user