Skip to content

Commit 2edfc8d

Browse files
committed
chore: Add support for persistent store contract tests.
1 parent 245dd97 commit 2edfc8d

7 files changed

Lines changed: 111 additions & 3 deletions

File tree

.github/actions/ci/action.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ inputs:
3737
description: 'Whether to install CURL development libraries. Required for OpenTelemetry builds (server-sdk-otel), but does not enable CURL networking for the SDK itself.'
3838
required: false
3939
default: 'false'
40+
use_redis:
41+
description: 'Whether to enable Redis support (LD_BUILD_REDIS_SUPPORT=ON)'
42+
required: false
43+
default: 'false'
4044

4145
runs:
4246
using: composite
@@ -58,7 +62,7 @@ runs:
5862
id: install-curl
5963
- name: Build Library
6064
shell: bash
61-
run: ./scripts/build.sh ${{ inputs.cmake_target }} ON ${{ inputs.use_curl }}
65+
run: ./scripts/build.sh ${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} ${{ inputs.use_redis }}
6266
env:
6367
BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }}
6468
Boost_DIR: ${{ steps.install-boost.outputs.Boost_DIR }}
@@ -69,7 +73,7 @@ runs:
6973
id: build-tests
7074
if: inputs.run_tests == 'true'
7175
shell: bash
72-
run: ./scripts/build.sh gtest_${{ inputs.cmake_target }} ON ${{ inputs.use_curl }}
76+
run: ./scripts/build.sh gtest_${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} ${{ inputs.use_redis }}
7377
env:
7478
BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }}
7579
Boost_DIR: ${{ steps.install-boost.outputs.Boost_DIR }}

.github/workflows/server.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ jobs:
2626
with:
2727
cmake_target: server-tests
2828
run_tests: false
29+
use_redis: true
2930
- name: 'Launch test service as background task'
3031
run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 &
3132
- uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.1.0
3233
with:
3334
# Inform the test harness of test service's port.
3435
test_service_port: ${{ env.TEST_SERVICE_PORT }}
3536
token: ${{ secrets.GITHUB_TOKEN }}
37+
enable_persistence_tests: true
3638

3739
contract-tests-curl:
3840
runs-on: ubuntu-22.04
@@ -47,13 +49,15 @@ jobs:
4749
cmake_target: server-tests
4850
run_tests: false
4951
use_curl: true
52+
use_redis: true
5053
- name: 'Launch test service as background task'
5154
run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 &
5255
- uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.1.0
5356
with:
5457
# Inform the test harness of test service's port.
5558
test_service_port: ${{ env.TEST_SERVICE_PORT }}
5659
token: ${{ secrets.GITHUB_TOKEN }}
60+
enable_persistence_tests: true
5761

5862
build-test-server:
5963
runs-on: ubuntu-22.04

contract-tests/data-model/include/data_model/data_model.hpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,34 @@ struct ConfigWrapper {
150150

151151
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigWrapper, name, version);
152152

153+
struct ConfigPersistentCache {
154+
std::string mode; // "off", "ttl", "infinite"
155+
std::optional<int> ttlMs;
156+
};
157+
158+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentCache,
159+
mode,
160+
ttlMs);
161+
162+
struct ConfigPersistentStore {
163+
std::string type; // "redis", "consul", "dynamodb"
164+
std::string dsn;
165+
std::optional<std::string> prefix;
166+
};
167+
168+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentStore,
169+
type,
170+
dsn,
171+
prefix);
172+
173+
struct ConfigPersistentDataStore {
174+
ConfigPersistentStore store;
175+
ConfigPersistentCache cache;
176+
};
177+
178+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentDataStore,
179+
store,
180+
cache);
153181
struct ConfigParams {
154182
std::string credential;
155183
std::optional<uint32_t> startWaitTimeMs;
@@ -164,6 +192,7 @@ struct ConfigParams {
164192
std::optional<ConfigProxyParams> proxy;
165193
std::optional<ConfigHooksParams> hooks;
166194
std::optional<ConfigWrapper> wrapper;
195+
std::optional<ConfigPersistentDataStore> persistentDataStore;
167196
};
168197

169198
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams,
@@ -179,7 +208,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams,
179208
tls,
180209
proxy,
181210
hooks,
182-
wrapper);
211+
wrapper,
212+
persistentDataStore);
183213

184214
struct ContextSingleParams {
185215
std::optional<std::string> kind;

contract-tests/server-contract-tests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,9 @@ target_link_libraries(server-tests PRIVATE
2828
contract-test-data-model
2929
)
3030

31+
if (LD_BUILD_REDIS_SUPPORT)
32+
target_link_libraries(server-tests PRIVATE launchdarkly::server_redis_source)
33+
target_compile_definitions(server-tests PRIVATE LD_REDIS_SUPPORT_ENABLED)
34+
endif ()
35+
3136
target_include_directories(server-tests PUBLIC include)

contract-tests/server-contract-tests/src/entity_manager.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
#include <boost/json.hpp>
99

10+
#ifdef LD_REDIS_SUPPORT_ENABLED
11+
#include <launchdarkly/server_side/integrations/redis/redis_source.hpp>
12+
#endif
13+
1014
using launchdarkly::LogLevel;
1115
using namespace launchdarkly::server_side;
1216

@@ -154,6 +158,56 @@ std::optional<std::string> EntityManager::create(ConfigParams const& in) {
154158
}
155159
}
156160

161+
#ifdef LD_REDIS_SUPPORT_ENABLED
162+
if (in.persistentDataStore) {
163+
if (in.persistentDataStore->store.type == "redis") {
164+
std::string prefix =
165+
in.persistentDataStore->store.prefix.value_or("launchdarkly");
166+
167+
auto redis_result = launchdarkly::server_side::integrations::
168+
RedisDataSource::Create(in.persistentDataStore->store.dsn,
169+
prefix);
170+
171+
if (!redis_result) {
172+
LD_LOG(logger_, LogLevel::kWarn)
173+
<< "entity_manager: couldn't create Redis data source: "
174+
<< redis_result.error();
175+
return std::nullopt;
176+
}
177+
178+
auto lazy_load = config::builders::LazyLoadBuilder();
179+
lazy_load.Source(std::move(*redis_result));
180+
181+
// Configure cache mode
182+
// Default is 5 minutes, but contract tests may specify:
183+
// - "off": disable caching (fetch from DB every time)
184+
// - "ttl": custom TTL in milliseconds
185+
// - "infinite": never expire cached items
186+
if (in.persistentDataStore->cache.mode == "off") {
187+
lazy_load.CacheRefresh(std::chrono::seconds(0));
188+
} else if (in.persistentDataStore->cache.mode == "ttl") {
189+
if (in.persistentDataStore->cache.ttlMs) {
190+
lazy_load.CacheRefresh(std::chrono::milliseconds(
191+
*in.persistentDataStore->cache.ttlMs));
192+
}
193+
} else if (in.persistentDataStore->cache.mode == "infinite") {
194+
// Use a very large TTL to effectively never expire
195+
lazy_load.CacheRefresh(std::chrono::hours(24 * 365));
196+
}
197+
// If no mode specified, the default 5-minute TTL is used
198+
199+
config_builder.DataSystem().Method(
200+
config::builders::DataSystemBuilder::LazyLoad(
201+
std::move(lazy_load)));
202+
} else {
203+
LD_LOG(logger_, LogLevel::kWarn)
204+
<< "entity_manager: unsupported persistent store type: "
205+
<< in.persistentDataStore->store.type;
206+
return std::nullopt;
207+
}
208+
}
209+
#endif
210+
157211
auto config = config_builder.Build();
158212
if (!config) {
159213
LD_LOG(logger_, LogLevel::kWarn)

contract-tests/server-contract-tests/src/main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ int main(int argc, char* argv[]) {
5151
srv.add_capability("evaluation-hooks");
5252
srv.add_capability("track-hooks");
5353
srv.add_capability("wrapper");
54+
#ifdef LD_REDIS_SUPPORT_ENABLED
55+
srv.add_capability("persistent-data-store-redis");
56+
#endif
5457

5558
net::signal_set signals{ioc, SIGINT, SIGTERM};
5659

scripts/build.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# $1 the name of the target. For example "launchdarkly-cpp-common".
88
# $2 ON/OFF which enables/disables building in a test configuration (unit tests + contract tests.)
99
# $3 (optional) true/false to enable/disable CURL networking (LD_CURL_NETWORKING)
10+
# $4 (optional) true/false to enable/disable Redis support (LD_BUILD_REDIS_SUPPORT)
1011

1112
function cleanup {
1213
cd ..
@@ -30,6 +31,13 @@ build_curl="OFF"
3031
if [ "$3" == "true" ]; then
3132
build_curl="ON"
3233
fi
34+
35+
# Check for Redis support option (override the automatic detection if explicitly passed)
36+
if [ "$4" == "true" ]; then
37+
build_redis="ON"
38+
elif [ "$4" == "false" ]; then
39+
build_redis="OFF"
40+
fi
3341
# Special case: OpenTelemetry support requires additional dependencies.
3442
# Enable OTEL support and fetch deps when building OTEL targets.
3543
build_otel="OFF"

0 commit comments

Comments
 (0)