From 2edfc8d93e44e9851438e6c13cb356ea50ea13ec Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:53:34 -0800 Subject: [PATCH 1/4] chore: Add support for persistent store contract tests. --- .github/actions/ci/action.yml | 8 ++- .github/workflows/server.yml | 4 ++ .../include/data_model/data_model.hpp | 32 ++++++++++- .../server-contract-tests/CMakeLists.txt | 5 ++ .../src/entity_manager.cpp | 54 +++++++++++++++++++ .../server-contract-tests/src/main.cpp | 3 ++ scripts/build.sh | 8 +++ 7 files changed, 111 insertions(+), 3 deletions(-) diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index 35c1ca23b..a68d659de 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -37,6 +37,10 @@ inputs: description: 'Whether to install CURL development libraries. Required for OpenTelemetry builds (server-sdk-otel), but does not enable CURL networking for the SDK itself.' required: false default: 'false' + use_redis: + description: 'Whether to enable Redis support (LD_BUILD_REDIS_SUPPORT=ON)' + required: false + default: 'false' runs: using: composite @@ -58,7 +62,7 @@ runs: id: install-curl - name: Build Library shell: bash - run: ./scripts/build.sh ${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} + run: ./scripts/build.sh ${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} ${{ inputs.use_redis }} env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} Boost_DIR: ${{ steps.install-boost.outputs.Boost_DIR }} @@ -69,7 +73,7 @@ runs: id: build-tests if: inputs.run_tests == 'true' shell: bash - run: ./scripts/build.sh gtest_${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} + run: ./scripts/build.sh gtest_${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} ${{ inputs.use_redis }} env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} Boost_DIR: ${{ steps.install-boost.outputs.Boost_DIR }} diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 3542c8b50..88e879eab 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -26,6 +26,7 @@ jobs: with: cmake_target: server-tests run_tests: false + use_redis: true - name: 'Launch test service as background task' run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 & - uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.1.0 @@ -33,6 +34,7 @@ jobs: # Inform the test harness of test service's port. test_service_port: ${{ env.TEST_SERVICE_PORT }} token: ${{ secrets.GITHUB_TOKEN }} + enable_persistence_tests: true contract-tests-curl: runs-on: ubuntu-22.04 @@ -47,6 +49,7 @@ jobs: cmake_target: server-tests run_tests: false use_curl: true + use_redis: true - name: 'Launch test service as background task' run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 & - uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.1.0 @@ -54,6 +57,7 @@ jobs: # Inform the test harness of test service's port. test_service_port: ${{ env.TEST_SERVICE_PORT }} token: ${{ secrets.GITHUB_TOKEN }} + enable_persistence_tests: true build-test-server: runs-on: ubuntu-22.04 diff --git a/contract-tests/data-model/include/data_model/data_model.hpp b/contract-tests/data-model/include/data_model/data_model.hpp index 679fd0015..ef3aab779 100644 --- a/contract-tests/data-model/include/data_model/data_model.hpp +++ b/contract-tests/data-model/include/data_model/data_model.hpp @@ -150,6 +150,34 @@ struct ConfigWrapper { NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigWrapper, name, version); +struct ConfigPersistentCache { + std::string mode; // "off", "ttl", "infinite" + std::optional ttlMs; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentCache, + mode, + ttlMs); + +struct ConfigPersistentStore { + std::string type; // "redis", "consul", "dynamodb" + std::string dsn; + std::optional prefix; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentStore, + type, + dsn, + prefix); + +struct ConfigPersistentDataStore { + ConfigPersistentStore store; + ConfigPersistentCache cache; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentDataStore, + store, + cache); struct ConfigParams { std::string credential; std::optional startWaitTimeMs; @@ -164,6 +192,7 @@ struct ConfigParams { std::optional proxy; std::optional hooks; std::optional wrapper; + std::optional persistentDataStore; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams, @@ -179,7 +208,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams, tls, proxy, hooks, - wrapper); + wrapper, + persistentDataStore); struct ContextSingleParams { std::optional kind; diff --git a/contract-tests/server-contract-tests/CMakeLists.txt b/contract-tests/server-contract-tests/CMakeLists.txt index 9455cd27e..faeca51eb 100644 --- a/contract-tests/server-contract-tests/CMakeLists.txt +++ b/contract-tests/server-contract-tests/CMakeLists.txt @@ -28,4 +28,9 @@ target_link_libraries(server-tests PRIVATE contract-test-data-model ) +if (LD_BUILD_REDIS_SUPPORT) + target_link_libraries(server-tests PRIVATE launchdarkly::server_redis_source) + target_compile_definitions(server-tests PRIVATE LD_REDIS_SUPPORT_ENABLED) +endif () + target_include_directories(server-tests PUBLIC include) diff --git a/contract-tests/server-contract-tests/src/entity_manager.cpp b/contract-tests/server-contract-tests/src/entity_manager.cpp index 085df1c2d..b337d5598 100644 --- a/contract-tests/server-contract-tests/src/entity_manager.cpp +++ b/contract-tests/server-contract-tests/src/entity_manager.cpp @@ -7,6 +7,10 @@ #include +#ifdef LD_REDIS_SUPPORT_ENABLED +#include +#endif + using launchdarkly::LogLevel; using namespace launchdarkly::server_side; @@ -154,6 +158,56 @@ std::optional EntityManager::create(ConfigParams const& in) { } } +#ifdef LD_REDIS_SUPPORT_ENABLED + if (in.persistentDataStore) { + if (in.persistentDataStore->store.type == "redis") { + std::string prefix = + in.persistentDataStore->store.prefix.value_or("launchdarkly"); + + auto redis_result = launchdarkly::server_side::integrations:: + RedisDataSource::Create(in.persistentDataStore->store.dsn, + prefix); + + if (!redis_result) { + LD_LOG(logger_, LogLevel::kWarn) + << "entity_manager: couldn't create Redis data source: " + << redis_result.error(); + return std::nullopt; + } + + auto lazy_load = config::builders::LazyLoadBuilder(); + lazy_load.Source(std::move(*redis_result)); + + // Configure cache mode + // Default is 5 minutes, but contract tests may specify: + // - "off": disable caching (fetch from DB every time) + // - "ttl": custom TTL in milliseconds + // - "infinite": never expire cached items + if (in.persistentDataStore->cache.mode == "off") { + lazy_load.CacheRefresh(std::chrono::seconds(0)); + } else if (in.persistentDataStore->cache.mode == "ttl") { + if (in.persistentDataStore->cache.ttlMs) { + lazy_load.CacheRefresh(std::chrono::milliseconds( + *in.persistentDataStore->cache.ttlMs)); + } + } else if (in.persistentDataStore->cache.mode == "infinite") { + // Use a very large TTL to effectively never expire + lazy_load.CacheRefresh(std::chrono::hours(24 * 365)); + } + // If no mode specified, the default 5-minute TTL is used + + config_builder.DataSystem().Method( + config::builders::DataSystemBuilder::LazyLoad( + std::move(lazy_load))); + } else { + LD_LOG(logger_, LogLevel::kWarn) + << "entity_manager: unsupported persistent store type: " + << in.persistentDataStore->store.type; + return std::nullopt; + } + } +#endif + auto config = config_builder.Build(); if (!config) { LD_LOG(logger_, LogLevel::kWarn) diff --git a/contract-tests/server-contract-tests/src/main.cpp b/contract-tests/server-contract-tests/src/main.cpp index e017a8634..481997961 100644 --- a/contract-tests/server-contract-tests/src/main.cpp +++ b/contract-tests/server-contract-tests/src/main.cpp @@ -51,6 +51,9 @@ int main(int argc, char* argv[]) { srv.add_capability("evaluation-hooks"); srv.add_capability("track-hooks"); srv.add_capability("wrapper"); +#ifdef LD_REDIS_SUPPORT_ENABLED + srv.add_capability("persistent-data-store-redis"); +#endif net::signal_set signals{ioc, SIGINT, SIGTERM}; diff --git a/scripts/build.sh b/scripts/build.sh index 9ca6d21a2..1be4f9bc5 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -7,6 +7,7 @@ # $1 the name of the target. For example "launchdarkly-cpp-common". # $2 ON/OFF which enables/disables building in a test configuration (unit tests + contract tests.) # $3 (optional) true/false to enable/disable CURL networking (LD_CURL_NETWORKING) +# $4 (optional) true/false to enable/disable Redis support (LD_BUILD_REDIS_SUPPORT) function cleanup { cd .. @@ -30,6 +31,13 @@ build_curl="OFF" if [ "$3" == "true" ]; then build_curl="ON" fi + +# Check for Redis support option (override the automatic detection if explicitly passed) +if [ "$4" == "true" ]; then + build_redis="ON" +elif [ "$4" == "false" ]; then + build_redis="OFF" +fi # Special case: OpenTelemetry support requires additional dependencies. # Enable OTEL support and fetch deps when building OTEL targets. build_otel="OFF" From eb918dda13a3ddb8de4bd31730447bcb4947c277 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:31:39 +0000 Subject: [PATCH 2/4] fix: add use_redis flag to server-redis.yml workflow steps Co-Authored-By: rlamb@launchdarkly.com --- .github/workflows/server-redis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/server-redis.yml b/.github/workflows/server-redis.yml index 82f31ca4b..255c9c14b 100644 --- a/.github/workflows/server-redis.yml +++ b/.github/workflows/server-redis.yml @@ -28,6 +28,7 @@ jobs: with: cmake_target: launchdarkly-cpp-server-redis-source simulate_release: true + use_redis: true build-redis-mac: runs-on: macos-13 steps: @@ -39,6 +40,7 @@ jobs: platform_version: 12 run_tests: false # TODO: figure out how to run Redis service on Mac simulate_release: true + use_redis: true build-test-redis-windows: runs-on: windows-2022 steps: @@ -57,3 +59,4 @@ jobs: toolset: msvc run_tests: false # TODO: figure out how to run Redis service on Windows simulate_windows_release: true + use_redis: true From d960e6f748298da3844cc61dc13b92d0607e39bf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:08:36 +0000 Subject: [PATCH 3/4] fix: update OpenSSL chocolatey version from 3.5.4 to 3.6.1 for Windows CI Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/install-openssl/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/install-openssl/action.yml b/.github/actions/install-openssl/action.yml index 6d10d813f..3674a8b0d 100644 --- a/.github/actions/install-openssl/action.yml +++ b/.github/actions/install-openssl/action.yml @@ -32,7 +32,7 @@ runs: if: runner.os == 'Windows' shell: bash run: | - choco install openssl --version 3.5.4 -y --no-progress + choco install openssl --version 3.6.1 -y --no-progress if [ -d "C:\Program Files\OpenSSL-Win64" ]; then echo "OPENSSL_ROOT_DIR=C:\Program Files\OpenSSL-Win64" >> $GITHUB_OUTPUT else From 14b2e22918e297f0860dbfbb65141edf9ffed212 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:48:07 +0000 Subject: [PATCH 4/4] Revert "fix: update OpenSSL chocolatey version from 3.5.4 to 3.6.1 for Windows CI" This reverts commit d960e6f748298da3844cc61dc13b92d0607e39bf. --- .github/actions/install-openssl/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/install-openssl/action.yml b/.github/actions/install-openssl/action.yml index 3674a8b0d..6d10d813f 100644 --- a/.github/actions/install-openssl/action.yml +++ b/.github/actions/install-openssl/action.yml @@ -32,7 +32,7 @@ runs: if: runner.os == 'Windows' shell: bash run: | - choco install openssl --version 3.6.1 -y --no-progress + choco install openssl --version 3.5.4 -y --no-progress if [ -d "C:\Program Files\OpenSSL-Win64" ]; then echo "OPENSSL_ROOT_DIR=C:\Program Files\OpenSSL-Win64" >> $GITHUB_OUTPUT else