Skip to content

Commit ce3b3a7

Browse files
authored
[FSSDK-8838] feat(odp-cache): Adds support for odp cache. (#365)
1 parent d8251bb commit ce3b3a7

30 files changed

+1294
-101
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ Below is a comprehensive list of available configuration properties.
115115
| client.queueSize | OPTIMIZELY_CLIENT_QUEUESIZE | The max number of events pending dispatch. Default: 1000 |
116116
| client.sdkKeyRegex | OPTIMIZELY_CLIENT_SDKKEYREGEX | Regex to validate SDK keys provided in request header. Default: ^\\w+(:\\w+)?$ |
117117
| client.userProfileService | OPTIMIZELY_CLIENT_USERPROFILESERVICE | Property used to enable and set UserProfileServices. Default: ./config.yaml |
118+
| client.odp.disable | OPTIMIZELY_CLIENT_ODP_DISABLE | Property used to disable odp. Default: false |
119+
| client.odp.eventsRequestTimeout | OPTIMIZELY_CLIENT_ODP_EVENTSREQUESTTIMEOUT | Property used to update timeout in seconds after which event requests will timeout. Default: 10s |
120+
| client.odp.eventsFlushInterval | OPTIMIZELY_CLIENT_ODP_EVENTSFLUSHINTERVAL | Property used to update flush interval in seconds for events. Default: 1s |
121+
| client.odp.segmentsRequestTimeout | OPTIMIZELY_CLIENT_ODP_SEGMENTSREQUESTTIMEOUT | Property used to update timeout in seconds after which segment requests will timeout: 10s |
122+
| client.odp.cache | OPTIMIZELY_CLIENT_ODP_SEGMENTSCACHE | Property used to enable and set cache service for odp. Default: ./config.yaml |
118123
| config.filename | OPTIMIZELY_CONFIG_FILENAME | Location of the configuration YAML file. Default: ./config.yaml |
119124
| log.level | OPTIMIZELY_LOG_LEVEL | The log [level](https://github.com/rs/zerolog#leveled-logging) for the agent. Default: info |
120125
| log.pretty | OPTIMIZELY_LOG_PRETTY | Flag used to set colorized console output as opposed to structured json logs. Default: false |
@@ -320,6 +325,10 @@ to provide a namespaced environment for custom logic. Plugins must be compiled a
320325

321326
- [UserProfileService](./plugins/userprofileservice/README.md) - Adds UserProfileService.
322327

328+
### ODPCache Plugins
329+
330+
- [ODPCache](./plugins/odpcache/README.md) - Adds ODP Cache.
331+
323332
### Authorization
324333

325334
Optimizely Agent supports authorization workflows based on OAuth and JWT standards, allowing you to protect access to its API and Admin interfaces. For details, see [Authorization Guide](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/authorization).

cmd/optimizely/main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019,2022, Optimizely, Inc. and contributors *
2+
* Copyright 2019,2022-2023 Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -41,6 +41,8 @@ import (
4141

4242
// Initiate the loading of the userprofileservice plugins
4343
_ "github.com/optimizely/agent/plugins/userprofileservice/all"
44+
// Initiate the loading of the odpCache plugins
45+
_ "github.com/optimizely/agent/plugins/odpcache/all"
4446
"github.com/optimizely/go-sdk/pkg/logging"
4547
)
4648

@@ -93,6 +95,11 @@ func loadConfig(v *viper.Viper) *config.AgentConfig {
9395
conf.Client.UserProfileService = userProfileService
9496
}
9597

98+
// Check if JSON string was set using OPTIMIZELY_CLIENT_ODP_SEGMENTSCACHE environment variable
99+
if odpSegmentsCache := v.GetStringMap("client.odp.segmentsCache"); odpSegmentsCache != nil {
100+
conf.Client.ODP.SegmentsCache = odpSegmentsCache
101+
}
102+
96103
return conf
97104
}
98105

cmd/optimizely/main_test.go

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019-2020,2022, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2020,2022-2023, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -56,38 +56,61 @@ func assertServer(t *testing.T, actual config.ServerConfig, assertPlugins bool)
5656
}
5757
}
5858

59-
func assertClient(t *testing.T, actual config.ClientConfig, assertUserProfileService bool) {
59+
func assertClient(t *testing.T, actual config.ClientConfig) {
6060
assert.Equal(t, 10*time.Second, actual.PollingInterval)
6161
assert.Equal(t, 1, actual.BatchSize)
6262
assert.Equal(t, 10, actual.QueueSize)
6363
assert.Equal(t, 1*time.Minute, actual.FlushInterval)
6464
assert.Equal(t, "https://localhost/v1/%s.json", actual.DatafileURLTemplate)
6565
assert.Equal(t, "https://logx.localhost.com/v1", actual.EventURL)
6666
assert.Equal(t, "custom-regex", actual.SdkKeyRegex)
67-
if assertUserProfileService {
68-
assert.Equal(t, "in-memory", actual.UserProfileService["default"])
69-
userProfileServices := map[string]interface{}{
70-
"in-memory": map[string]interface{}{
71-
// Viper.set is case in-sensitive
72-
"storagestrategy": "fifo",
73-
},
74-
"redis": map[string]interface{}{
75-
"host": "localhost:6379",
76-
"password": "",
77-
},
78-
"rest": map[string]interface{}{
79-
"host": "http://localhost",
80-
"lookuppath": "/ups/lookup",
81-
"savepath": "/ups/save",
82-
"headers": map[string]interface{}{"content-type": "application/json"},
83-
"async": true,
84-
},
85-
"custom": map[string]interface{}{
86-
"path": "http://test2.com",
87-
},
88-
}
89-
assert.Equal(t, userProfileServices, actual.UserProfileService["services"])
67+
assert.True(t, actual.ODP.Disable)
68+
assert.Equal(t, 5*time.Second, actual.ODP.EventsFlushInterval)
69+
assert.Equal(t, 5*time.Second, actual.ODP.EventsRequestTimeout)
70+
assert.Equal(t, 5*time.Second, actual.ODP.SegmentsRequestTimeout)
71+
72+
assert.Equal(t, "in-memory", actual.UserProfileService["default"])
73+
userProfileServices := map[string]interface{}{
74+
"in-memory": map[string]interface{}{
75+
// Viper.set is case in-sensitive
76+
"storagestrategy": "fifo",
77+
},
78+
"redis": map[string]interface{}{
79+
"host": "localhost:6379",
80+
"password": "",
81+
},
82+
"rest": map[string]interface{}{
83+
"host": "http://localhost",
84+
"lookuppath": "/ups/lookup",
85+
"savepath": "/ups/save",
86+
"headers": map[string]interface{}{"content-type": "application/json"},
87+
"async": true,
88+
},
89+
"custom": map[string]interface{}{
90+
"path": "http://test2.com",
91+
},
92+
}
93+
assert.Equal(t, userProfileServices, actual.UserProfileService["services"])
94+
95+
assert.Equal(t, "in-memory", actual.ODP.SegmentsCache["default"])
96+
odpCacheServices := map[string]interface{}{
97+
"custom": map[string]interface{}{
98+
"path": "http://test2.com",
99+
},
90100
}
101+
actualCacheServices := actual.ODP.SegmentsCache["services"].(map[string]interface{})
102+
103+
assert.Equal(t, odpCacheServices["custom"], actualCacheServices["custom"])
104+
105+
redisCacheService := actualCacheServices["redis"].(map[string]interface{})
106+
assert.EqualValues(t, "localhost:6379", redisCacheService["host"])
107+
assert.EqualValues(t, "", redisCacheService["password"])
108+
assert.EqualValues(t, "5s", redisCacheService["timeout"])
109+
assert.EqualValues(t, "123", redisCacheService["database"])
110+
111+
actualInMemoryService := actualCacheServices["in-memory"].(map[string]interface{})
112+
assert.EqualValues(t, 100, actualInMemoryService["size"])
113+
assert.EqualValues(t, "5s", actualInMemoryService["timeout"])
91114
}
92115

93116
func assertLog(t *testing.T, actual config.LogConfig) {
@@ -165,7 +188,7 @@ func TestViperYaml(t *testing.T) {
165188

166189
assertRoot(t, actual)
167190
assertServer(t, actual.Server, true)
168-
assertClient(t, actual.Client, true)
191+
assertClient(t, actual.Client)
169192
assertLog(t, actual.Log)
170193
assertAdmin(t, actual.Admin)
171194
assertAdminAuth(t, actual.Admin.Auth)
@@ -202,7 +225,7 @@ func TestViperProps(t *testing.T) {
202225
v.Set("client.datafileURLTemplate", "https://localhost/v1/%s.json")
203226
v.Set("client.eventURL", "https://logx.localhost.com/v1")
204227
v.Set("client.sdkKeyRegex", "custom-regex")
205-
services := map[string]interface{}{
228+
upsServices := map[string]interface{}{
206229
"in-memory": map[string]interface{}{
207230
"storageStrategy": "fifo",
208231
},
@@ -223,10 +246,37 @@ func TestViperProps(t *testing.T) {
223246
}
224247
userProfileServices := map[string]interface{}{
225248
"default": "in-memory",
226-
"services": services,
249+
"services": upsServices,
227250
}
228251
v.Set("client.userProfileService", userProfileServices)
229252

253+
odpCacheServices := map[string]interface{}{
254+
"in-memory": map[string]interface{}{
255+
"size": 100,
256+
"timeout": "5s",
257+
},
258+
"redis": map[string]interface{}{
259+
"host": "localhost:6379",
260+
"password": "",
261+
"timeout": "5s",
262+
"database": "123",
263+
},
264+
"custom": map[string]interface{}{
265+
"path": "http://test2.com",
266+
},
267+
}
268+
odpCache := map[string]interface{}{
269+
"default": "in-memory",
270+
"services": odpCacheServices,
271+
}
272+
odpConfig := map[string]interface{}{
273+
"disable": true,
274+
"eventsRequestTimeout": 5 * time.Second,
275+
"eventsFlushInterval": 5 * time.Second,
276+
"segmentsRequestTimeout": 5 * time.Second,
277+
"segmentsCache": odpCache,
278+
}
279+
v.Set("client.odp", odpConfig)
230280
v.Set("log.pretty", true)
231281
v.Set("log.includeSdkKey", false)
232282
v.Set("log.level", "debug")
@@ -279,7 +329,7 @@ func TestViperProps(t *testing.T) {
279329

280330
assertRoot(t, actual)
281331
assertServer(t, actual.Server, true)
282-
assertClient(t, actual.Client, true)
332+
assertClient(t, actual.Client)
283333
assertLog(t, actual.Log)
284334
assertAdmin(t, actual.Admin)
285335
assertAdminAuth(t, actual.Admin.Auth)
@@ -312,7 +362,13 @@ func TestViperEnv(t *testing.T) {
312362
_ = os.Setenv("OPTIMIZELY_CLIENT_DATAFILEURLTEMPLATE", "https://localhost/v1/%s.json")
313363
_ = os.Setenv("OPTIMIZELY_CLIENT_EVENTURL", "https://logx.localhost.com/v1")
314364
_ = os.Setenv("OPTIMIZELY_CLIENT_SDKKEYREGEX", "custom-regex")
365+
315366
_ = os.Setenv("OPTIMIZELY_CLIENT_USERPROFILESERVICE", `{"default":"in-memory","services":{"in-memory":{"storagestrategy":"fifo"},"redis":{"host":"localhost:6379","password":""},"rest":{"host":"http://localhost","lookuppath":"/ups/lookup","savepath":"/ups/save","headers":{"content-type":"application/json"},"async":true},"custom":{"path":"http://test2.com"}}}`)
367+
_ = os.Setenv("OPTIMIZELY_CLIENT_ODP_SEGMENTSCACHE", `{"default":"in-memory","services":{"in-memory":{"size":100,"timeout":"5s"},"redis":{"host":"localhost:6379","password":"","timeout":"5s","database": "123"},"custom":{"path":"http://test2.com"}}}`)
368+
_ = os.Setenv("OPTIMIZELY_CLIENT_ODP_DISABLE", `true`)
369+
_ = os.Setenv("OPTIMIZELY_CLIENT_ODP_EVENTSREQUESTTIMEOUT", `5s`)
370+
_ = os.Setenv("OPTIMIZELY_CLIENT_ODP_EVENTSFLUSHINTERVAL", `5s`)
371+
_ = os.Setenv("OPTIMIZELY_CLIENT_ODP_SEGMENTSREQUESTTIMEOUT", `5s`)
316372

317373
_ = os.Setenv("OPTIMIZELY_LOG_PRETTY", "true")
318374
_ = os.Setenv("OPTIMIZELY_LOG_INCLUDESDKKEY", "false")
@@ -342,7 +398,7 @@ func TestViperEnv(t *testing.T) {
342398

343399
assertRoot(t, actual)
344400
assertServer(t, actual.Server, false)
345-
assertClient(t, actual.Client, true)
401+
assertClient(t, actual.Client)
346402
assertLog(t, actual.Log)
347403
assertAdmin(t, actual.Admin)
348404
assertAPI(t, actual.API)

cmd/optimizely/testdata/default.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ client:
4949
async: true
5050
custom:
5151
path: "http://test2.com"
52+
odp:
53+
disable: true
54+
eventsRequestTimeout: 5s
55+
eventsFlushInterval: 5s
56+
segmentsRequestTimeout: 5s
57+
segmentsCache:
58+
default: "in-memory"
59+
services:
60+
in-memory:
61+
size: 100
62+
timeout: 5s
63+
redis:
64+
host: "localhost:6379"
65+
password: ""
66+
timeout: 5s
67+
database: "123"
68+
custom:
69+
path: "http://test2.com"
5270

5371
admin:
5472
port: "3002"

config.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,28 @@ client:
157157
# headers:
158158
# Content-Type: "application/json"
159159
# Auth-Token: "12345"
160+
odp:
161+
## Disable odp
162+
disable: false
163+
## Timeout in seconds after which event requests will timeout.
164+
eventsRequestTimeout: 10s
165+
## Flush interval in seconds for odp events
166+
eventsFlushInterval: 1s
167+
## Timeout in seconds after which segment requests will timeout.
168+
segmentsRequestTimeout: 10s
169+
## If no segmentsCache is defined (or no default is defined), we will use the default in-memory with default size and timeout
170+
segmentsCache:
171+
default: "in-memory"
172+
services:
173+
in-memory:
174+
size: 10000
175+
timeout: 600s
176+
# redis:
177+
# host: "localhost:6379"
178+
# password: ""
179+
# database: 0
180+
# timeout: 0s
181+
160182

161183
##
162184
## optimizely runtime configuration can be used for debugging and profiling the go runtime.

0 commit comments

Comments
 (0)