Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 3 additions & 24 deletions cmd/optimizely/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"github.com/optimizely/agent/pkg/optimizely"
"github.com/optimizely/agent/pkg/routers"
"github.com/optimizely/agent/pkg/server"
_ "github.com/optimizely/agent/plugins/cmabcache/all" // Initiate the loading of the cmabCache plugins
_ "github.com/optimizely/agent/plugins/interceptors/all" // Initiate the loading of the userprofileservice plugins
_ "github.com/optimizely/agent/plugins/odpcache/all" // Initiate the loading of the odpCache plugins
_ "github.com/optimizely/agent/plugins/userprofileservice/all" // Initiate the loading of the interceptor plugins
Expand Down Expand Up @@ -117,17 +118,7 @@ func loadConfig(v *viper.Viper) *config.AgentConfig {
}
}
if cache, ok := cmab["cache"].(map[string]interface{}); ok {
if cacheType, ok := cache["type"].(string); ok {
conf.CMAB.Cache.Type = cacheType
}
if cacheSize, ok := cache["size"].(float64); ok {
conf.CMAB.Cache.Size = int(cacheSize)
}
if cacheTTL, ok := cache["ttl"].(string); ok {
if duration, err := time.ParseDuration(cacheTTL); err == nil {
conf.CMAB.Cache.TTL = duration
}
}
conf.CMAB.Cache = cache
}
if retryConfig, ok := cmab["retryConfig"].(map[string]interface{}); ok {
if maxRetries, ok := retryConfig["maxRetries"].(float64); ok {
Expand All @@ -151,19 +142,7 @@ func loadConfig(v *viper.Viper) *config.AgentConfig {

// Check for individual map sections
if cmabCache := v.GetStringMap("cmab.cache"); len(cmabCache) > 0 {
if cacheType, ok := cmabCache["type"].(string); ok {
conf.CMAB.Cache.Type = cacheType
}
if cacheSize, ok := cmabCache["size"].(int); ok {
conf.CMAB.Cache.Size = cacheSize
} else if cacheSize, ok := cmabCache["size"].(float64); ok {
conf.CMAB.Cache.Size = int(cacheSize)
}
if cacheTTL, ok := cmabCache["ttl"].(string); ok {
if duration, err := time.ParseDuration(cacheTTL); err == nil {
conf.CMAB.Cache.TTL = duration
}
}
conf.CMAB.Cache = cmabCache
}

if cmabRetryConfig := v.GetStringMap("cmab.retryConfig"); len(cmabRetryConfig) > 0 {
Expand Down
110 changes: 90 additions & 20 deletions cmd/optimizely/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,20 @@ func assertCMAB(t *testing.T, cmab config.CMABConfig) {
// Base assertions
assert.Equal(t, 15*time.Second, cmab.RequestTimeout)

// Check cache configuration
// Check cache configuration (now a map[string]interface{})
cache := cmab.Cache
assert.Equal(t, "redis", cache.Type)
assert.Equal(t, 2000, cache.Size)
assert.Equal(t, 45*time.Minute, cache.TTL)
assert.NotNil(t, cache)
assert.Equal(t, "redis", cache["default"])

// Check services configuration
if services, ok := cache["services"].(map[string]interface{}); ok {
if redisConfig, ok := services["redis"].(map[string]interface{}); ok {
// Redis config should have host, database, and timeout fields
assert.NotNil(t, redisConfig["host"])
assert.NotNil(t, redisConfig["database"])
assert.NotNil(t, redisConfig["timeout"])
}
}

// Check retry configuration
retry := cmab.RetryConfig
Expand All @@ -207,9 +216,14 @@ func TestCMABEnvDebug(t *testing.T) {
_ = os.Setenv("OPTIMIZELY_CMAB", `{
"requestTimeout": "15s",
"cache": {
"type": "redis",
"size": 2000,
"ttl": "45m"
"default": "redis",
"services": {
"redis": {
"host": "localhost:6379",
"database": 0,
"timeout": "45m"
}
}
},
"retryConfig": {
"maxRetries": 5,
Expand Down Expand Up @@ -246,17 +260,20 @@ func TestCMABPartialConfig(t *testing.T) {
os.Unsetenv("OPTIMIZELY_CMAB_RETRYCONFIG")

// Set partial configuration through CMAB_CACHE and CMAB_RETRYCONFIG
_ = os.Setenv("OPTIMIZELY_CMAB_CACHE", `{"type": "redis", "size": 3000}`)
// Note: Cache is now a service-based map config
_ = os.Setenv("OPTIMIZELY_CMAB_CACHE", `{"default": "redis", "services": {"redis": {"host": "localhost:6379", "database": 0}}}`)
_ = os.Setenv("OPTIMIZELY_CMAB_RETRYCONFIG", `{"maxRetries": 10}`)

// Load config
v := viper.New()
assert.NoError(t, initConfig(v))
conf := loadConfig(v)

// Cache assertions
assert.Equal(t, "redis", conf.CMAB.Cache.Type)
assert.Equal(t, 3000, conf.CMAB.Cache.Size)
// Cache assertions (cache is now map[string]interface{})
assert.NotNil(t, conf.CMAB.Cache)
if defaultCache, ok := conf.CMAB.Cache["default"].(string); ok {
assert.Equal(t, "redis", defaultCache)
}

// RetryConfig assertions
assert.Equal(t, 10, conf.CMAB.RetryConfig.MaxRetries)
Expand All @@ -267,6 +284,50 @@ func TestCMABPartialConfig(t *testing.T) {
os.Unsetenv("OPTIMIZELY_CMAB_RETRYCONFIG")
}

func TestCMABRetryConfigAllFields(t *testing.T) {
// Clean any existing environment variables
os.Unsetenv("OPTIMIZELY_CMAB")
os.Unsetenv("OPTIMIZELY_CMAB_CACHE")
os.Unsetenv("OPTIMIZELY_CMAB_RETRYCONFIG")

// Set all retry config fields via CMAB_RETRYCONFIG to cover lines 154-165
_ = os.Setenv("OPTIMIZELY_CMAB_RETRYCONFIG", `{
"maxRetries": 5,
"initialBackoff": "500ms",
"maxBackoff": "45s",
"backoffMultiplier": 2.5
}`)

defer func() {
os.Unsetenv("OPTIMIZELY_CMAB_RETRYCONFIG")
}()

v := viper.New()
assert.NoError(t, initConfig(v))
conf := loadConfig(v)

// Verify all retry config fields were parsed correctly
assert.Equal(t, 5, conf.CMAB.RetryConfig.MaxRetries)
assert.Equal(t, 500*time.Millisecond, conf.CMAB.RetryConfig.InitialBackoff)
assert.Equal(t, 45*time.Second, conf.CMAB.RetryConfig.MaxBackoff)
assert.Equal(t, 2.5, conf.CMAB.RetryConfig.BackoffMultiplier)
}

func TestCMABRetryConfigIntMaxRetries(t *testing.T) {
// Test the int type path for maxRetries (line 150) by using viper's Set method
// which will preserve the int type instead of converting to float64
v := viper.New()
assert.NoError(t, initConfig(v))

// Set via viper directly to ensure it's an int, not float64
v.Set("cmab.retryConfig.maxRetries", 7)

conf := loadConfig(v)

// Verify maxRetries was parsed as int
assert.Equal(t, 7, conf.CMAB.RetryConfig.MaxRetries)
}

func TestViperYaml(t *testing.T) {
v := viper.New()
v.Set("config.filename", "./testdata/default.yaml")
Expand Down Expand Up @@ -484,9 +545,14 @@ func TestViperEnv(t *testing.T) {
_ = os.Setenv("OPTIMIZELY_CMAB", `{
"requestTimeout": "15s",
"cache": {
"type": "redis",
"size": 2000,
"ttl": "45m"
"default": "redis",
"services": {
"redis": {
"host": "localhost:6379",
"database": 0,
"timeout": "45m"
}
}
},
"retryConfig": {
"maxRetries": 5,
Expand Down Expand Up @@ -622,8 +688,8 @@ func TestCMABComplexJSON(t *testing.T) {
os.Unsetenv("OPTIMIZELY_CMAB_CACHE_REDIS_PASSWORD")
os.Unsetenv("OPTIMIZELY_CMAB_CACHE_REDIS_DATABASE")

// Set complex JSON environment variable for CMAB cache
_ = os.Setenv("OPTIMIZELY_CMAB_CACHE", `{"type":"redis","size":5000,"ttl":"3h"}`)
// Set complex JSON environment variable for CMAB cache (using new service-based format)
_ = os.Setenv("OPTIMIZELY_CMAB_CACHE", `{"default":"redis","services":{"redis":{"host":"localhost:6379","database":0,"timeout":"3h"}}}`)

defer func() {
// Clean up
Expand All @@ -634,9 +700,13 @@ func TestCMABComplexJSON(t *testing.T) {
assert.NoError(t, initConfig(v))
actual := loadConfig(v)

// Test cache settings from JSON environment variable
// Test cache settings from JSON environment variable (cache is now map[string]interface{})
cache := actual.CMAB.Cache
assert.Equal(t, "redis", cache.Type)
assert.Equal(t, 5000, cache.Size)
assert.Equal(t, 3*time.Hour, cache.TTL)
assert.NotNil(t, cache)
if defaultCache, ok := cache["default"].(string); ok {
assert.Equal(t, "redis", defaultCache)
}
if services, ok := cache["services"].(map[string]interface{}); ok {
assert.NotNil(t, services["redis"])
}
}
23 changes: 17 additions & 6 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,24 @@ cmab:
## timeout for CMAB API requests
requestTimeout: 10s
## CMAB cache configuration
## Supports both in-memory (single instance) and Redis (multi-instance) caching
cache:
## cache type (memory or redis)
type: "memory"
## maximum number of entries for in-memory cache
size: 1000
## time-to-live for cached decisions
ttl: 30m
## default cache service to use
default: "in-memory"
services:
## in-memory cache (fast, isolated per Agent instance)
in-memory:
## maximum number of entries for in-memory cache
size: 10000
## time-to-live for cached decisions
timeout: 30m
## Redis cache (shared across multiple Agent instances)
## Uncomment and configure for multi-instance deployments
# redis:
# host: "localhost:6379"
# password: ""
# database: 0
# timeout: 30m
## retry configuration for CMAB API requests
retryConfig:
## maximum number of retry attempts
Expand Down
21 changes: 9 additions & 12 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,13 @@ func NewDefaultConfig() *AgentConfig {
CMAB: CMABConfig{
RequestTimeout: 10 * time.Second,
Cache: CMABCacheConfig{
Type: "memory",
Size: 1000,
TTL: 30 * time.Minute,
"default": "in-memory",
"services": map[string]interface{}{
"in-memory": map[string]interface{}{
"size": 10000,
"timeout": "30m",
},
},
},
RetryConfig: CMABRetryConfig{
MaxRetries: 3,
Expand Down Expand Up @@ -415,15 +419,8 @@ type CMABConfig struct {
RetryConfig CMABRetryConfig `json:"retryConfig"`
}

// CMABCacheConfig holds the CMAB cache configuration
type CMABCacheConfig struct {
// Type of cache (currently only "memory" is supported)
Type string `json:"type"`
// Size is the maximum number of entries for in-memory cache
Size int `json:"size"`
// TTL is the time-to-live for cached decisions
TTL time.Duration `json:"ttl"`
}
// CMABCacheConfig holds the CMAB cache configuration (service-based)
type CMABCacheConfig map[string]interface{}

// CMABRetryConfig holds the CMAB retry configuration
type CMABRetryConfig struct {
Expand Down
26 changes: 16 additions & 10 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,14 @@ func TestDefaultConfig(t *testing.T) {
// CMAB configuration
assert.Equal(t, 10*time.Second, conf.CMAB.RequestTimeout)

// Test cache settings
cache := conf.CMAB.Cache
assert.Equal(t, "memory", cache.Type)
assert.Equal(t, 1000, cache.Size)
assert.Equal(t, 30*time.Minute, cache.TTL)
// Test cache settings (cache is now map[string]interface{})
assert.Equal(t, "in-memory", conf.CMAB.Cache["default"])
assert.Equal(t, map[string]interface{}{
"in-memory": map[string]interface{}{
"size": 10000,
"timeout": "30m",
},
}, conf.CMAB.Cache["services"])

// Test retry settings
retry := conf.CMAB.RetryConfig
Expand Down Expand Up @@ -256,11 +259,14 @@ func TestDefaultCMABConfig(t *testing.T) {
// Test default values
assert.Equal(t, 10*time.Second, conf.CMAB.RequestTimeout)

// Test default cache settings
cache := conf.CMAB.Cache
assert.Equal(t, "memory", cache.Type)
assert.Equal(t, 1000, cache.Size)
assert.Equal(t, 30*time.Minute, cache.TTL)
// Test default cache settings (cache is now map[string]interface{})
assert.Equal(t, "in-memory", conf.CMAB.Cache["default"])
assert.Equal(t, map[string]interface{}{
"in-memory": map[string]interface{}{
"size": 10000,
"timeout": "30m",
},
}, conf.CMAB.Cache["services"])

// Test default retry settings
retry := conf.CMAB.RetryConfig
Expand Down
Loading