Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 6 additions & 2 deletions .github/actions/ci/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }}
Expand All @@ -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 }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/server-redis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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-15
steps:
Expand All @@ -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:
Expand All @@ -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
4 changes: 4 additions & 0 deletions .github/workflows/server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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 &
# https://github.com/launchdarkly/gh-actions/releases/tag/contract-tests-v1.1.0
Expand All @@ -35,6 +36,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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Redis service container for persistence contract tests

High Severity

The contract-tests and contract-tests-curl jobs enable enable_persistence_tests: true and build with use_redis: true, but neither job defines a Redis services: container. Contrast with server-redis.yml, which properly includes a services: redis: block with the Redis image and port mapping. The persistence tests will attempt to connect to Redis at runtime and fail because no instance is running.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the test runs, it doesn't seem like the contract tests actually ran with the persistent tests enabled, so maybe we are missing something here?


contract-tests-curl:
runs-on: ubuntu-22.04
Expand All @@ -50,6 +52,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 &
# https://github.com/launchdarkly/gh-actions/releases/tag/contract-tests-v1.1.0
Expand All @@ -58,6 +61,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
Expand Down
32 changes: 31 additions & 1 deletion contract-tests/data-model/include/data_model/data_model.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#pragma once

#include <optional>
Expand Down Expand Up @@ -150,6 +150,34 @@

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigWrapper, name, version);

struct ConfigPersistentCache {
std::string mode; // "off", "ttl", "infinite"
std::optional<int> ttlMs;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentCache,
mode,
ttlMs);

struct ConfigPersistentStore {
std::string type; // "redis", "consul", "dynamodb"
std::string dsn;
std::optional<std::string> 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<uint32_t> startWaitTimeMs;
Expand All @@ -164,6 +192,7 @@
std::optional<ConfigProxyParams> proxy;
std::optional<ConfigHooksParams> hooks;
std::optional<ConfigWrapper> wrapper;
std::optional<ConfigPersistentDataStore> persistentDataStore;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams,
Expand All @@ -179,7 +208,8 @@
tls,
proxy,
hooks,
wrapper);
wrapper,
persistentDataStore);

struct ContextSingleParams {
std::optional<std::string> kind;
Expand Down
5 changes: 5 additions & 0 deletions contract-tests/server-contract-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
54 changes: 54 additions & 0 deletions contract-tests/server-contract-tests/src/entity_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#include "entity_manager.hpp"
#include "contract_test_hook.hpp"

Expand All @@ -7,6 +7,10 @@

#include <boost/json.hpp>

#ifdef LD_REDIS_SUPPORT_ENABLED
#include <launchdarkly/server_side/integrations/redis/redis_source.hpp>
#endif

using launchdarkly::LogLevel;
using namespace launchdarkly::server_side;

Expand Down Expand Up @@ -154,6 +158,56 @@
}
}

#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)
Expand Down
3 changes: 3 additions & 0 deletions contract-tests/server-contract-tests/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
8 changes: 8 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 ..
Expand All @@ -31,6 +32,13 @@ 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

# Set build type to Debug when testing is enabled
build_type="Release"
if [ "$2" == "ON" ]; then
Expand Down
Loading