From 968b9aa6085cdb6e28d2d82568df61da896c3bdb Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Fri, 7 Jun 2024 11:20:53 +0200 Subject: [PATCH 1/6] refactor: Remote Configuration Refactor the Remote Configuration (RC) as a standalone module, enabling its use independently of the tracer. Additionally, ensure that external listeners can register and receive configuration updates when RC is embedded in the tracer. Changes: - Move APM_TRACING updates handling to the configuration manager. - Expose the `remote_configuration_listeners` field in datadog agent config to allow registration of external listeners. - Improve the robustness of config path parsing and ensure an error is reported if parsing fails. - Add `to_upper` utility function. - Increase test coverage for RC and the new configuration manager behaviour. --- BUILD.bazel | 8 +- CMakeLists.txt | 19 +- src/datadog/config_manager.cpp | 64 ++- src/datadog/config_manager.h | 42 +- src/datadog/config_update.h | 24 - src/datadog/datadog_agent.cpp | 29 +- src/datadog/datadog_agent.h | 9 +- src/datadog/datadog_agent_config.cpp | 3 + src/datadog/datadog_agent_config.h | 6 + src/datadog/remote_config/capability.h | 130 +++++ src/datadog/remote_config/listener.h | 49 ++ .../{ => remote_config}/remote_config.cpp | 213 +++++---- .../{ => remote_config}/remote_config.h | 48 +- src/datadog/string_util.cpp | 9 + src/datadog/string_util.h | 2 + src/datadog/tracer.cpp | 13 +- src/datadog/tracer.h | 4 +- src/datadog/tracer_telemetry.cpp | 26 +- src/datadog/tracer_telemetry.h | 7 +- test/CMakeLists.txt | 9 +- test/remote_config/test_remote_config.cpp | 452 ++++++++++++++++++ test/test_config_manager.cpp | 188 ++++++++ test/test_datadog_agent.cpp | 6 +- test/test_remote_config.cpp | 349 -------------- test/test_tracer_telemetry.cpp | 6 +- 25 files changed, 1144 insertions(+), 571 deletions(-) delete mode 100644 src/datadog/config_update.h create mode 100644 src/datadog/remote_config/capability.h create mode 100644 src/datadog/remote_config/listener.h rename src/datadog/{ => remote_config}/remote_config.cpp (51%) rename src/datadog/{ => remote_config}/remote_config.h (64%) create mode 100644 test/remote_config/test_remote_config.cpp create mode 100644 test/test_config_manager.cpp delete mode 100644 test/test_remote_config.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 85010c9a..3408acbf 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -27,7 +27,7 @@ cc_library( "src/datadog/propagation_style.cpp", "src/datadog/random.cpp", "src/datadog/rate.cpp", - "src/datadog/remote_config.cpp", + "src/datadog/remote_config/remote_config.cpp", "src/datadog/runtime_id.cpp", "src/datadog/span.cpp", "src/datadog/span_data.cpp", @@ -55,7 +55,6 @@ cc_library( "src/datadog/config.h", "src/datadog/clock.h", "src/datadog/config_manager.h", - "src/datadog/config_update.h", "src/datadog/collector.h", "src/datadog/collector_response.h", # "src/datadog/curl.h", no libcurl @@ -88,7 +87,9 @@ cc_library( "src/datadog/propagation_style.h", "src/datadog/random.h", "src/datadog/rate.h", - "src/datadog/remote_config.h", + "src/datadog/remote_config/capability.h", + "src/datadog/remote_config/listener.h", + "src/datadog/remote_config/remote_config.h", "src/datadog/runtime_id.h", "src/datadog/sampling_decision.h", "src/datadog/sampling_mechanism.h", @@ -118,6 +119,7 @@ cc_library( "src/datadog/w3c_propagation.h", ], strip_include_prefix = "src/", + copts = ["-Isrc/datadog"], visibility = ["//visibility:public"], deps = [ "@com_google_absl//absl/strings", diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f33e850..d046aacd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ endif() if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "RelWithDebInfo") elseif (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(DD_TRACE_ENABLE_SANITIZE ON) + # set(DD_TRACE_ENABLE_SANITIZE ON) endif () # Linking this library requires libcurl and threads. @@ -103,7 +103,7 @@ target_sources(dd_trace_cpp-objects src/datadog/propagation_style.cpp src/datadog/random.cpp src/datadog/rate.cpp - src/datadog/remote_config.cpp + src/datadog/remote_config/remote_config.cpp src/datadog/runtime_id.cpp src/datadog/span.cpp src/datadog/span_data.cpp @@ -137,7 +137,6 @@ target_sources(dd_trace_cpp-objects PUBLIC src/datadog/cerr_logger.h src/datadog/clock.h src/datadog/config_manager.h - src/datadog/config_update.h src/datadog/collector.h src/datadog/collector_response.h # src/datadog/curl.h except for curl.h @@ -170,7 +169,9 @@ target_sources(dd_trace_cpp-objects PUBLIC src/datadog/propagation_style.h src/datadog/random.h src/datadog/rate.h - src/datadog/remote_config.h + src/datadog/remote_config/remote_config.h + src/datadog/remote_config/capability.h + src/datadog/remote_config/listener.h src/datadog/runtime_id.h src/datadog/sampling_decision.h src/datadog/sampling_mechanism.h @@ -203,11 +204,11 @@ target_sources(dd_trace_cpp-objects PUBLIC # TODO: define public/private # Headers location are different depending of whether we are building # or installing the library. -# target_include_directories(dd_trace_cpp-objects -# PUBLIC -# $ -# $ -# ) +target_include_directories(dd_trace_cpp-objects + PUBLIC + $ + $ +) target_link_libraries(dd_trace_cpp-objects PRIVATE diff --git a/src/datadog/config_manager.cpp b/src/datadog/config_manager.cpp index 8a453f36..557917f5 100644 --- a/src/datadog/config_manager.cpp +++ b/src/datadog/config_manager.cpp @@ -77,16 +77,71 @@ Expected parse_trace_sampling_rules(const nlohmann::json& json_rules) { return parsed_rules; } +ConfigManager::Update parse_dynamic_config(const nlohmann::json& j) { + ConfigManager::Update config_update; + + if (auto sampling_rate_it = j.find("tracing_sampling_rate"); + sampling_rate_it != j.cend() && sampling_rate_it->is_number()) { + config_update.trace_sampling_rate = sampling_rate_it->get(); + } + + if (auto tags_it = j.find("tracing_tags"); + tags_it != j.cend() && tags_it->is_array()) { + config_update.tags = tags_it->get>(); + } + + if (auto tracing_enabled_it = j.find("tracing_enabled"); + tracing_enabled_it != j.cend() && tracing_enabled_it->is_boolean()) { + config_update.report_traces = tracing_enabled_it->get(); + } + + if (auto tracing_sampling_rules_it = j.find("tracing_sampling_rules"); + tracing_sampling_rules_it != j.cend() && + tracing_sampling_rules_it->is_array()) { + config_update.trace_sampling_rules = &(*tracing_sampling_rules_it); + } + + return config_update; +} + } // namespace -ConfigManager::ConfigManager(const FinalizedTracerConfig& config) +namespace rc = datadog::remote_config; + +ConfigManager::ConfigManager(const FinalizedTracerConfig& config, + const std::shared_ptr& telemetry) : clock_(config.clock), default_metadata_(config.metadata), trace_sampler_( std::make_shared(config.trace_sampler, clock_)), rules_(config.trace_sampler.rules), span_defaults_(std::make_shared(config.defaults)), - report_traces_(config.report_traces) {} + report_traces_(config.report_traces), + telemetry_(telemetry) {} + +rc::Products ConfigManager::get_products() { return rc::product::APM_TRACING; } + +rc::Capabilities ConfigManager::get_capabilities() { + using namespace rc::capability; + return APM_TRACING_SAMPLE_RATE | APM_TRACING_TAGS | APM_TRACING_ENABLED | + APM_TRACING_SAMPLE_RULES; +}; + +Optional ConfigManager::on_update(const Configuration& config) { + const auto config_json = nlohmann::json::parse(config.content); + auto config_update = parse_dynamic_config(config_json.at("lib_config")); + + auto config_metadata = apply_update(config_update); + telemetry_->capture_configuration_change(config_metadata); + + // TODO: + return nullopt; +} + +void ConfigManager::on_revert(const Configuration&) { + auto config_metadata = apply_update({}); + telemetry_->capture_configuration_change(config_metadata); +} std::shared_ptr ConfigManager::trace_sampler() { std::lock_guard lock(mutex_); @@ -103,7 +158,8 @@ bool ConfigManager::report_traces() { return report_traces_.value(); } -std::vector ConfigManager::update(const ConfigUpdate& conf) { +std::vector ConfigManager::apply_update( + const ConfigManager::Update& conf) { std::vector metadata; std::lock_guard lock(mutex_); @@ -210,8 +266,6 @@ void ConfigManager::reset_config(ConfigName name, T& conf, metadata.emplace_back(default_metadata_[name]); } -std::vector ConfigManager::reset() { return update({}); } - nlohmann::json ConfigManager::config_json() const { std::lock_guard lock(mutex_); return nlohmann::json{{"defaults", to_json(*span_defaults_.value())}, diff --git a/src/datadog/config_manager.h b/src/datadog/config_manager.h index ce43f2ab..1451cd15 100644 --- a/src/datadog/config_manager.h +++ b/src/datadog/config_manager.h @@ -8,16 +8,31 @@ #include #include "clock.h" -#include "config_update.h" #include "json.hpp" #include "optional.h" +#include "remote_config/listener.h" #include "span_defaults.h" #include "tracer_config.h" +#include "tracer_telemetry.h" namespace datadog { namespace tracing { -class ConfigManager { +class ConfigManager : public remote_config::Listener { + public: + // The `ConfigUpdate` struct serves as a container for configuration that can + // exclusively be changed remotely. + // + // Configurations can be `nullopt` to signal the absence of a value from the + // remote configuration value. + struct Update { + Optional report_traces; + Optional trace_sampling_rate; + Optional> tags; + const nlohmann::json* trace_sampling_rules = nullptr; + }; + + private: // A class template for managing dynamic configuration values. // // This class allows storing and managing dynamic configuration values. It @@ -60,13 +75,25 @@ class ConfigManager { DynamicConfig> span_defaults_; DynamicConfig report_traces_; + std::shared_ptr telemetry_; + private: template void reset_config(ConfigName name, T& conf, std::vector& metadata); public: - ConfigManager(const FinalizedTracerConfig& config); + ConfigManager(const FinalizedTracerConfig& config, + const std::shared_ptr& telemetry); + ~ConfigManager() override{}; + + remote_config::Products get_products() override; + remote_config::Capabilities get_capabilities() override; + + Optional on_update( + const Listener::Configuration& config) override; + void on_revert(const Listener::Configuration& config) override; + void on_post_process() override{}; // Return the `TraceSampler` consistent with the most recent configuration. std::shared_ptr trace_sampler(); @@ -77,16 +104,11 @@ class ConfigManager { // Return whether traces should be sent to the collector. bool report_traces(); - // Apply the specified `conf` update. - std::vector update(const ConfigUpdate& conf); - - // Restore the configuration that was passed to this object's constructor, - // overriding any previous calls to `update`. - std::vector reset(); - // Return a JSON representation of the current configuration managed by this // object. nlohmann::json config_json() const; + + std::vector apply_update(const ConfigManager::Update& conf); }; } // namespace tracing diff --git a/src/datadog/config_update.h b/src/datadog/config_update.h deleted file mode 100644 index d16fe92b..00000000 --- a/src/datadog/config_update.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -#include "optional" -#include "trace_sampler_config.h" - -namespace datadog { -namespace tracing { - -// The `ConfigUpdate` struct serves as a container for configuration that can -// exclusively be changed remotely. -// -// Configurations can be `nullopt` to signal the absence of a value from the -// remote configuration value. -struct ConfigUpdate { - Optional report_traces; - Optional trace_sampling_rate; - Optional> tags; - const nlohmann::json* trace_sampling_rules = nullptr; -}; - -} // namespace tracing -} // namespace datadog diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index 507f69dd..707bf707 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -145,12 +145,14 @@ std::variant parse_agent_traces_response( } // namespace +namespace rc = datadog::remote_config; + DatadogAgent::DatadogAgent( const FinalizedDatadogAgentConfig& config, const std::shared_ptr& tracer_telemetry, const std::shared_ptr& logger, const TracerSignature& tracer_signature, - const std::shared_ptr& config_manager) + const std::vector>& rc_listeners) : tracer_telemetry_(tracer_telemetry), clock_(config.clock), logger_(logger), @@ -162,7 +164,7 @@ DatadogAgent::DatadogAgent( flush_interval_(config.flush_interval), request_timeout_(config.request_timeout), shutdown_timeout_(config.shutdown_timeout), - remote_config_(tracer_signature, config_manager), + remote_config_(tracer_signature, rc_listeners), tracer_signature_(tracer_signature) { assert(logger_); assert(tracer_telemetry_); @@ -410,14 +412,13 @@ void DatadogAgent::send_heartbeat_and_telemetry() { send_telemetry("app-heartbeat", tracer_telemetry_->heartbeat_and_telemetry()); } -void DatadogAgent::send_app_closing() { - send_telemetry("app-closing", tracer_telemetry_->app_closing()); +void DatadogAgent::send_configuration_change() { + send_telemetry("app-client-configuration-change", + tracer_telemetry_->configuration_change()); } -void DatadogAgent::send_configuration_change( - const std::vector& config) { - send_telemetry("app-client-configuration-change", - tracer_telemetry_->configuration_change(config)); +void DatadogAgent::send_app_closing() { + send_telemetry("app-closing", tracer_telemetry_->app_closing()); } void DatadogAgent::get_and_apply_remote_configuration_updates() { @@ -457,11 +458,13 @@ void DatadogAgent::get_and_apply_remote_configuration_updates() { } if (!response_json.empty()) { - auto updated_configuration = - remote_config_.process_response(response_json); - if (!updated_configuration.empty()) { - send_configuration_change(updated_configuration); - } + remote_config_.process_response(response_json); + // NOTE(@dmehala): Not ideal but it mimics the old behavior. + // In the future, I would prefer telemetry pushing to the agent + // and not the agent pulling from telemetry. That way telemetry will + // be more flexible and could support env var to customize how often + // it captures metrics. + send_configuration_change(); } }; diff --git a/src/datadog/datadog_agent.h b/src/datadog/datadog_agent.h index 592dc71b..6c4d1ded 100644 --- a/src/datadog/datadog_agent.h +++ b/src/datadog/datadog_agent.h @@ -16,7 +16,7 @@ #include "event_scheduler.h" #include "http_client.h" #include "metrics.h" -#include "remote_config.h" +#include "remote_config/remote_config.h" #include "tracer_signature.h" #include "tracer_telemetry.h" @@ -55,7 +55,7 @@ class DatadogAgent : public Collector { std::chrono::steady_clock::duration request_timeout_; std::chrono::steady_clock::duration shutdown_timeout_; - RemoteConfigurationManager remote_config_; + remote_config::Manager remote_config_; TracerSignature tracer_signature_; void flush(); @@ -67,7 +67,8 @@ class DatadogAgent : public Collector { DatadogAgent(const FinalizedDatadogAgentConfig&, const std::shared_ptr&, const std::shared_ptr&, const TracerSignature& id, - const std::shared_ptr& config_manager); + const std::vector>& + rc_listeners); ~DatadogAgent(); Expected send( @@ -77,7 +78,7 @@ class DatadogAgent : public Collector { void send_app_started( const std::unordered_map& config_metadata); - void send_configuration_change(const std::vector& config); + void send_configuration_change(); void get_and_apply_remote_configuration_updates(); diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index 6049a823..f1d382f9 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -78,6 +78,9 @@ Expected finalize_config( result.event_scheduler = user_config.event_scheduler; } + result.remote_configuration_listeners = + user_config.remote_configuration_listeners; + if (auto flush_interval_milliseconds = value_or(env_config->flush_interval_milliseconds, user_config.flush_interval_milliseconds, 2000); diff --git a/src/datadog/datadog_agent_config.h b/src/datadog/datadog_agent_config.h index 67559aee..39c2a313 100644 --- a/src/datadog/datadog_agent_config.h +++ b/src/datadog/datadog_agent_config.h @@ -21,6 +21,7 @@ #include "config.h" #include "expected.h" #include "http_client.h" +#include "remote_config/listener.h" #include "string_view.h" namespace datadog { @@ -40,6 +41,9 @@ struct DatadogAgentConfig { // Datadog Agent. If `event_scheduler` is null, then a // `ThreadedEventScheduler` instance will be used instead. std::shared_ptr event_scheduler = nullptr; + // A list of Remote Configuration listeners. + std::vector> + remote_configuration_listeners; // A URL at which the Datadog Agent can be contacted. // The following formats are supported: // @@ -77,6 +81,8 @@ class FinalizedDatadogAgentConfig { bool remote_configuration_enabled; std::shared_ptr http_client; std::shared_ptr event_scheduler; + std::vector> + remote_configuration_listeners; HTTPClient::URL url; std::chrono::steady_clock::duration flush_interval; std::chrono::steady_clock::duration request_timeout; diff --git a/src/datadog/remote_config/capability.h b/src/datadog/remote_config/capability.h new file mode 100644 index 00000000..0b6f5cce --- /dev/null +++ b/src/datadog/remote_config/capability.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include + +#include "string_util.h" +#include "string_view.h" + +namespace datadog { +namespace remote_config { + +// Type alias for capabilities flags. +// +// Usage: +// +// using namespace datadog::remote_config::capability; +// Capabilities c = APM_TRACING_SAMPLE_RATE | APM_TRACING_ENABLED; +using Capabilities = uint64_t; + +// Type alias for product flags. +// +// Usage: +// +// using namespace datadog::remote_config::product; +// Products p = AGENT_CONFIG | APM_TRACING; +using Products = uint64_t; + +namespace capability { + +enum Flag : Capabilities { + // DEFAULT is a special value. It is the default of the product capability. + DEFAULT = 0, + ASM_ACTIVATION = 1 << 1, + ASM_IP_BLOCKING = 1 << 2, + ASM_DD_RULES = 1 << 3, + ASM_EXCLUSIONS = 1 << 4, + ASM_REQUEST_BLOCKING = 1 << 5, + ASM_RESPONSE_BLOCKING = 1 << 6, + ASM_USER_BLOCKING = 1 << 7, + ASM_CUSTOM_RULES = 1 << 8, + ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9, + ASM_TRUSTED_IPS = 1 << 10, + ASM_API_SECURITY_SAMPLE_RATE = 1 << 11, + APM_TRACING_SAMPLE_RATE = 1 << 12, + APM_TRACING_LOGS_INJECTION = 1 << 13, + APM_TRACING_HTTP_HEADER_TAGS = 1 << 14, + APM_TRACING_TAGS = 1 << 15, + ASM_PREPROCESSOR_OVERRIDES = 1 << 16, + ASM_CUSTOM_DATA_SCANNERS = 1 << 17, + ASM_EXCLUSION_DATA = 1 << 18, + APM_TRACING_ENABLED = 1 << 19, + APM_TRACING_DATA_STREAMS_ENABLED = 1 << 20, + ASM_RASP_SQLI = 1 << 21, + ASM_RASP_LFI = 1 << 22, + ASM_RASP_SSRF = 1 << 23, + ASM_RASP_SHI = 1 << 24, + ASM_RASP_XXE = 1 << 25, + ASM_RASP_RCE = 1 << 26, + ASM_RASP_NOSQLI = 1 << 27, + ASM_RASP_XSS = 1 << 28, + APM_TRACING_SAMPLE_RULES = 1 << 29, +}; + +} // namespace capability + +#define DD_QUOTED_IMPL(ARG) #ARG +#define DD_QUOTED(ARG) DD_QUOTED_IMPL(ARG) + +// List of remote configuration product built to support flag arithmetic +#define DD_LIST_REMOTE_CONFIG_PRODUCTS \ + X(AGENT_CONFIG, 1) \ + X(AGENT_TASK, 2) \ + X(APM_TRACING, 3) \ + X(LIVE_DEBUGGING, 4) \ + X(LIVE_DEBUGGING_SYMBOL_DB, 5) \ + X(ASM, 6) \ + X(ASM_DD, 7) \ + X(ASM_DATA, 8) \ + X(ASM_FEATURES, 9) + +namespace product { + +enum Flag : Products { +#define X(NAME, ID) NAME = 1 << ID, + DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef X +}; +} // namespace product + +inline tracing::StringView to_string_view(product::Flag product) { +#define X(NAME, ID) \ + case product::Flag::NAME: { \ + return DD_QUOTED(NAME); \ + } + switch (product) { DD_LIST_REMOTE_CONFIG_PRODUCTS } +#undef X + + std::abort(); +} + +inline product::Flag parse_product(tracing::StringView sv) { + const auto upcase_product = tracing::to_upper(sv); + +#define X(NAME, ID) \ + if (upcase_product == DD_QUOTED(NAME)) { \ + return product::Flag::NAME; \ + } + DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef X + + std::abort(); +} + +inline void visit_products(Products products, + std::function on_product) { +#define X(NAME, ID) \ + if (products & product::Flag::NAME) { \ + on_product(product::Flag::NAME); \ + } + + DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef X +} + +#undef DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef DD_QUOTED_IMPL +#undef DD_QUOTED + +} // namespace remote_config +} // namespace datadog diff --git a/src/datadog/remote_config/listener.h b/src/datadog/remote_config/listener.h new file mode 100644 index 00000000..2d502853 --- /dev/null +++ b/src/datadog/remote_config/listener.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include "capability.h" +#include "optional.h" + +namespace datadog { +namespace remote_config { + +// Interface for handling remote configuration notifications +// +// The `Listener` class provides an interface for handling configuration +// updates and reverts for the set of products and capabilities it subscribes +// to. +class Listener { + public: + struct Configuration { + std::string id; + std::string path; + std::string hash; + std::string content; + std::size_t version; + product::Flag product; + }; + + virtual ~Listener() = default; + + // Retrieves the list of products the listener wants to subscribe. + virtual Products get_products() = 0; + + // Retrieve the list of capabilities the listener wants to subscribe. + virtual Capabilities get_capabilities() = 0; + + // Pure virtual function called when a configuration needs to be reverted. + virtual void on_revert(const Configuration&) = 0; + + // Pure virtual function called when a configuration is updated. + // Returns an error message if the configuration could not be applied, or + // nothing. + virtual tracing::Optional on_update(const Configuration&) = 0; + + // TODO: Find a better name and a better solution! Mainly here for ASM + // Called once the last remote configuration response is completed. + virtual void on_post_process() = 0; +}; + +} // namespace remote_config +} // namespace datadog diff --git a/src/datadog/remote_config.cpp b/src/datadog/remote_config/remote_config.cpp similarity index 51% rename from src/datadog/remote_config.cpp rename to src/datadog/remote_config/remote_config.cpp index a2a4489f..141967da 100644 --- a/src/datadog/remote_config.cpp +++ b/src/datadog/remote_config/remote_config.cpp @@ -1,39 +1,23 @@ #include "remote_config.h" #include -#include -#include +#include #include #include "base64.h" #include "json.hpp" #include "random.h" +#include "remote_config/capability.h" +#include "remote_config/listener.h" #include "string_view.h" -#include "version.h" +using namespace datadog::tracing; using namespace nlohmann::literals; namespace datadog { -namespace tracing { +namespace remote_config { namespace { -// The ".client.capabilities" field of the remote config request payload -// describes which parts of the library's configuration are supported for remote -// configuration. -// -// It's a bitset, 64 bits wide, where each bit indicates whether the library -// supports a particular feature for remote configuration. -// -// The bitset is encoded in the request as a JSON array of 8 integers, where -// each integer is one byte from the 64 bits. The bytes are in big-endian order -// within the array. -enum CapabilitiesFlag : uint64_t { - APM_TRACING_SAMPLE_RATE = 1 << 12, - APM_TRACING_TAGS = 1 << 15, - APM_TRACING_ENABLED = 1 << 19, - APM_TRACING_SAMPLE_RULES = 1 << 29, -}; - constexpr std::array capabilities_byte_array( uint64_t in) { std::size_t j = sizeof(in) - 1; @@ -45,53 +29,59 @@ constexpr std::array capabilities_byte_array( return res; } -constexpr std::array k_apm_capabilities = - capabilities_byte_array(APM_TRACING_SAMPLE_RATE | APM_TRACING_TAGS | - APM_TRACING_ENABLED | APM_TRACING_SAMPLE_RULES); - -constexpr StringView k_apm_product = "APM_TRACING"; -constexpr StringView k_apm_product_path_substring = "/APM_TRACING/"; +Optional parse_config_path(StringView config_path) { + static const std::regex path_reg( + "^(datadog/\\d+|employee)/([^/]+)/[^/]+/[^/]+$"); -ConfigUpdate parse_dynamic_config(const nlohmann::json& j) { - ConfigUpdate config_update; - - if (auto sampling_rate_it = j.find("tracing_sampling_rate"); - sampling_rate_it != j.cend() && sampling_rate_it->is_number()) { - config_update.trace_sampling_rate = sampling_rate_it->get(); + std::cmatch match; + if (!std::regex_match(config_path.data(), + config_path.data() + config_path.size(), match, + path_reg)) { + return nullopt; } - if (auto tags_it = j.find("tracing_tags"); - tags_it != j.cend() && tags_it->is_array()) { - config_update.tags = tags_it->get>(); - } + assert(match.ready()); + assert(match.size() == 3); - if (auto tracing_enabled_it = j.find("tracing_enabled"); - tracing_enabled_it != j.cend() && tracing_enabled_it->is_boolean()) { - config_update.report_traces = tracing_enabled_it->get(); - } - - if (auto tracing_sampling_rules_it = j.find("tracing_sampling_rules"); - tracing_sampling_rules_it != j.cend() && - tracing_sampling_rules_it->is_array()) { - config_update.trace_sampling_rules = &(*tracing_sampling_rules_it); - } + StringView product_sv(config_path.data() + match.position(2), + std::size_t(match.length(2))); - return config_update; + return parse_product(product_sv); } } // namespace -RemoteConfigurationManager::RemoteConfigurationManager( - const TracerSignature& tracer_signature, - const std::shared_ptr& config_manager) +Manager::Manager(const TracerSignature& tracer_signature, + const std::vector>& listeners) : tracer_signature_(tracer_signature), - config_manager_(config_manager), + listeners_(listeners), client_id_(uuid()) { - assert(config_manager_); + Capabilities capabilities = 0; + + for (const auto& listener : listeners_) { + visit_products( + listener->get_products(), [this, &listener](product::Flag product) { + products_.emplace(to_string_view(product)); + listeners_per_product_[product].emplace_back(listener.get()); + }); + capabilities |= listener->get_capabilities(); + } + + // The ".client.capabilities" field of the remote config request payload + // describes which parts of the library's configuration are supported for + // remote configuration. + // + // It's a bitset, 64 bits wide, where each bit indicates whether the library + // supports a particular feature for remote configuration. + // + // The bitset is encoded in the request as a JSON array of 8 integers, where + // each integer is one byte from the 64 bits. The bytes are in big-endian + // order within the array. + capabilities_ = capabilities_byte_array(capabilities); } -bool RemoteConfigurationManager::is_new_config( - StringView config_path, const nlohmann::json& config_meta) { +bool Manager::is_new_config(StringView config_path, + const nlohmann::json& config_meta) { auto it = applied_config_.find(std::string{config_path}); if (it == applied_config_.cend()) return true; @@ -99,14 +89,14 @@ bool RemoteConfigurationManager::is_new_config( config_meta.at("/hashes/sha256"_json_pointer).get(); } -nlohmann::json RemoteConfigurationManager::make_request_payload() { +nlohmann::json Manager::make_request_payload() { // clang-format off auto j = nlohmann::json{ {"client", { {"id", client_id_}, - {"products", nlohmann::json::array({k_apm_product})}, + {"products", products_}, {"is_tracer", true}, - {"capabilities", k_apm_capabilities}, + {"capabilities", capabilities_}, {"client_tracer", { {"runtime_id", tracer_signature_.runtime_id.string()}, {"language", tracer_signature_.library_language}, @@ -129,7 +119,7 @@ nlohmann::json RemoteConfigurationManager::make_request_payload() { nlohmann::json config_state = { {"id", config.id}, {"version", config.version}, - {"product", k_apm_product}, + {"product", to_string_view(config.product)}, {"apply_state", config.state}, }; @@ -151,11 +141,7 @@ nlohmann::json RemoteConfigurationManager::make_request_payload() { return j; } -std::vector RemoteConfigurationManager::process_response( - const nlohmann::json& json) { - std::vector config_updates; - config_updates.reserve(8); - +void Manager::process_response(const nlohmann::json& json) { state_.error_message = nullopt; try { @@ -173,28 +159,41 @@ std::vector RemoteConfigurationManager::process_response( if (client_configs_it == json.cend()) { if (!applied_config_.empty()) { std::for_each(applied_config_.cbegin(), applied_config_.cend(), - [this, &config_updates](const auto it) { - auto updated = revert_config(it.second); - config_updates.insert(config_updates.end(), - updated.begin(), updated.end()); + [this](const auto& it) { + for (const auto& listener : + listeners_per_product_[it.second.product]) { + listener->on_revert(it.second); + } }); applied_config_.clear(); } - return config_updates; + + for (const auto& listener : listeners_) { + listener->on_post_process(); + } + return; } // Keep track of config path received to know which ones to revert. std::unordered_set visited_config; - visited_config.reserve(client_configs_it->size()); for (const auto& client_config : *client_configs_it) { auto config_path = client_config.get(); visited_config.emplace(config_path); + const auto product = parse_config_path(config_path); + if (!product) { + std::string error_message{config_path}; + error_message += " is an invalid configuration path"; + + state_.error_message = std::move(error_message); + return; + } + const auto& config_metadata = targets.at("/signed/targets"_json_pointer).at(config_path); - if (!contains(config_path, k_apm_product_path_substring) || - !is_new_config(config_path, config_metadata)) { + + if (!is_new_config(config_path, config_metadata)) { continue; } @@ -206,21 +205,27 @@ std::vector RemoteConfigurationManager::process_response( }); if (target_it == target_files.cend()) { - state_.error_message = - "Missing configuration from Remote Configuration response: No " - "target file having path \""; - append(*state_.error_message, config_path); - *state_.error_message += '\"'; - return config_updates; + std::string error_message{"Missing configuration \""}; + append(error_message, config_path); + error_message += "\" from Remote Configuration response"; + + state_.error_message = std::move(error_message); + return; } - const auto config_json = nlohmann::json::parse( - base64_decode(target_it.value().at("raw").get())); + auto decoded_config = + base64_decode(target_it.value().at("raw").get()); + + // TODO: try to remove the following line + const auto config_json = nlohmann::json::parse(decoded_config); Configuration new_config; new_config.id = config_json.at("id"); + new_config.path = std::string{config_path}; new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer); + new_config.content = std::move(decoded_config); new_config.version = config_json.at("revision"); + new_config.product = *product; const auto& targeted_service = config_json.at("service_target"); if (targeted_service.at("service").get() != @@ -230,48 +235,46 @@ std::vector RemoteConfigurationManager::process_response( new_config.state = Configuration::State::error; new_config.error_message = "Wrong service targeted"; } else { - new_config.state = Configuration::State::acknowledged; - new_config.content = parse_dynamic_config(config_json.at("lib_config")); - - auto updated = apply_config(new_config); - config_updates.insert(config_updates.end(), updated.begin(), - updated.end()); + for (const auto& listener : listeners_per_product_[*product]) { + // Q: Two listeners on the same product. What should be the behaviour + // if one of the listeners report an error? + // R(@dmehala): Unspecified. For now, the config is marked with an + // error. + if (auto error_msg = listener->on_update(new_config)) { + new_config.state = Configuration::State::error; + new_config.error_message = std::move(*error_msg); + } else { + new_config.state = Configuration::State::acknowledged; + } + } } applied_config_[std::string{config_path}] = new_config; } - // Applied configuration not present must be reverted. + // Revert applied configurations not present for (auto it = applied_config_.cbegin(); it != applied_config_.cend();) { if (!visited_config.count(it->first)) { - auto updated = revert_config(it->second); - config_updates.insert(config_updates.end(), updated.begin(), - updated.end()); + for (const auto& listener : + listeners_per_product_[it->second.product]) { + listener->on_revert(it->second); + } it = applied_config_.erase(it); } else { it++; } } + + for (const auto& listener : listeners_) { + listener->on_post_process(); + } } catch (const nlohmann::json::exception& e) { - std::string error_message = "Ill-formatted Remote Configuration response: "; + std::string error_message = "Invalid Remote Configuration response: "; error_message += e.what(); state_.error_message = std::move(error_message); - return config_updates; } - - return config_updates; -} - -std::vector RemoteConfigurationManager::apply_config( - Configuration config) { - return config_manager_->update(config.content); -} - -std::vector RemoteConfigurationManager::revert_config( - Configuration) { - return config_manager_->reset(); } -} // namespace tracing +} // namespace remote_config } // namespace datadog diff --git a/src/datadog/remote_config.h b/src/datadog/remote_config/remote_config.h similarity index 64% rename from src/datadog/remote_config.h rename to src/datadog/remote_config/remote_config.h index d822fe58..d7190073 100644 --- a/src/datadog/remote_config.h +++ b/src/datadog/remote_config/remote_config.h @@ -12,57 +12,58 @@ // It interacts with the `ConfigManager` to seamlessly apply or revert // configurations based on responses received from the remote source. +#include +#include #include +#include #include -#include "config_manager.h" #include "logger.h" #include "optional.h" +#include "remote_config/listener.h" #include "runtime_id.h" #include "string_view.h" #include "trace_sampler_config.h" #include "tracer_signature.h" namespace datadog { -namespace tracing { +namespace remote_config { -class RemoteConfigurationManager { - // Represents the *current* state of the RemoteConfigurationManager. +class Manager { + // Represents the *current* state of the Manager. // It is also used to report errors to the remote source. struct State { uint64_t targets_version = 0; std::string opaque_backend_state; - Optional error_message; + tracing::Optional error_message; }; // Holds information about a specific configuration update, // including its identifier, hash value, version number and the content. - struct Configuration { - std::string id; - std::string hash; - std::size_t version; - ConfigUpdate content; - + struct Configuration final : public Listener::Configuration { enum State : char { unacknowledged = 1, acknowledged = 2, error = 3 } state = State::unacknowledged; - Optional error_message; + tracing::Optional error_message; }; - TracerSignature tracer_signature_; - std::shared_ptr config_manager_; + tracing::TracerSignature tracer_signature_; + std::vector> listeners_; + std::set products_; + std::unordered_map> + listeners_per_product_; + std::array capabilities_; std::string client_id_; State state_; std::unordered_map applied_config_; public: - RemoteConfigurationManager( - const TracerSignature& tracer_signature, - const std::shared_ptr& config_manager); + Manager(const tracing::TracerSignature& tracer_signature, + const std::vector>& listeners); // Construct a JSON object representing the payload to be sent in a remote // configuration request. @@ -70,18 +71,13 @@ class RemoteConfigurationManager { // Handles the response received from a remote source and udates the internal // state accordingly. - std::vector process_response(const nlohmann::json& json); + void process_response(const nlohmann::json& json); private: // Tell if a `config_path` is a new configuration update. - bool is_new_config(StringView config_path, const nlohmann::json& config_meta); - - // Apply a remote configuration. - std::vector apply_config(Configuration config); - - // Revert a remote configuration. - std::vector revert_config(Configuration config); + bool is_new_config(tracing::StringView config_path, + const nlohmann::json& config_meta); }; -} // namespace tracing +} // namespace remote_config } // namespace datadog diff --git a/src/datadog/string_util.cpp b/src/datadog/string_util.cpp index 0455a24f..49607b13 100644 --- a/src/datadog/string_util.cpp +++ b/src/datadog/string_util.cpp @@ -43,6 +43,15 @@ std::string to_lower(StringView sv) { return s; } +std::string to_upper(StringView sv) { + std::string s; + s.reserve(sv.size()); + std::transform(sv.begin(), sv.end(), std::back_inserter(s), + [](char c) { return std::toupper(c); }); + + return s; +} + std::string to_string(bool b) { return b ? "true" : "false"; } std::string to_string(double d, size_t precision) { diff --git a/src/datadog/string_util.h b/src/datadog/string_util.h index 090b1018..9775df5e 100644 --- a/src/datadog/string_util.h +++ b/src/datadog/string_util.h @@ -13,6 +13,8 @@ namespace tracing { void to_lower(std::string& text); std::string to_lower(StringView sv); +std::string to_upper(StringView sv); + // Return a string representation of the specified boolean `value`. // The result is "true" for `true` and "false" for `false`. std::string to_string(bool b); diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 76bb19a5..26cfb5fc 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -36,8 +36,6 @@ Tracer::Tracer(const FinalizedTracerConfig& config) Tracer::Tracer(const FinalizedTracerConfig& config, const std::shared_ptr& generator) : logger_(config.logger), - config_manager_(std::make_shared(config)), - collector_(/* see constructor body */), runtime_id_(config.runtime_id ? *config.runtime_id : RuntimeID::generate()), signature_{runtime_id_, config.defaults.service, @@ -45,6 +43,9 @@ Tracer::Tracer(const FinalizedTracerConfig& config, tracer_telemetry_(std::make_shared( config.report_telemetry, config.clock, logger_, signature_, config.integration_name, config.integration_version)), + config_manager_( + std::make_shared(config, tracer_telemetry_)), + collector_(/* see constructor body */), span_sampler_( std::make_shared(config.span_sampler, config.clock)), generator_(generator), @@ -63,9 +64,11 @@ Tracer::Tracer(const FinalizedTracerConfig& config, auto& agent_config = std::get(config.collector); - auto agent = std::make_shared(agent_config, tracer_telemetry_, - config.logger, signature_, - config_manager_); + auto rc_listeners = agent_config.remote_configuration_listeners; + rc_listeners.emplace_back(config_manager_); + auto agent = + std::make_shared(agent_config, tracer_telemetry_, + config.logger, signature_, rc_listeners); collector_ = agent; if (tracer_telemetry_->enabled()) { diff --git a/src/datadog/tracer.h b/src/datadog/tracer.h index 7fc5cb24..a6147f7c 100644 --- a/src/datadog/tracer.h +++ b/src/datadog/tracer.h @@ -35,11 +35,11 @@ class SpanSampler; class Tracer { std::shared_ptr logger_; - std::shared_ptr config_manager_; - std::shared_ptr collector_; RuntimeID runtime_id_; TracerSignature signature_; std::shared_ptr tracer_telemetry_; + std::shared_ptr config_manager_; + std::shared_ptr collector_; std::shared_ptr span_sampler_; std::shared_ptr generator_; Clock clock_; diff --git a/src/datadog/tracer_telemetry.cpp b/src/datadog/tracer_telemetry.cpp index 2e3adaad..57bdd1e2 100644 --- a/src/datadog/tracer_telemetry.cpp +++ b/src/datadog/tracer_telemetry.cpp @@ -230,6 +230,13 @@ void TracerTelemetry::capture_metrics() { } } +void TracerTelemetry::capture_configuration_change( + const std::vector& new_configuration) { + configuration_snapshot_.insert(configuration_snapshot_.begin(), + new_configuration.begin(), + new_configuration.end()); +} + std::string TracerTelemetry::heartbeat_and_telemetry() { auto batch_payloads = nlohmann::json::array(); @@ -281,6 +288,7 @@ std::string TracerTelemetry::heartbeat_and_telemetry() { auto telemetry_body = generate_telemetry_body("message-batch"); telemetry_body["payload"] = batch_payloads; auto message_batch_payload = telemetry_body.dump(); + return message_batch_payload; } @@ -335,22 +343,26 @@ std::string TracerTelemetry::app_closing() { auto telemetry_body = generate_telemetry_body("message-batch"); telemetry_body["payload"] = batch_payloads; auto message_batch_payload = telemetry_body.dump(); + return message_batch_payload; } -std::string TracerTelemetry::configuration_change( - const std::vector& new_configuration) { +std::string TracerTelemetry::configuration_change() { auto configuration_json = nlohmann::json::array(); - for (const auto& config_metadata : new_configuration) { + for (const auto& config_metadata : configuration_snapshot_) { // if (config_metadata.value.empty()) continue; configuration_json.emplace_back( generate_configuration_field(config_metadata)); } - auto configuration_change = - generate_telemetry_body("app-client-configuration-change"); - configuration_change["payload"] = - nlohmann::json{{"configuration", configuration_json}}; + // clang-format off + auto configuration_change = nlohmann::json({ + {"request_type", "app-client-configuration-change"}, + {"payload", nlohmann::json{ + {"configuration", configuration_json} + }} + }); + // clang-format on return configuration_change.dump(); } diff --git a/src/datadog/tracer_telemetry.h b/src/datadog/tracer_telemetry.h index 3911ba5d..f129ea84 100644 --- a/src/datadog/tracer_telemetry.h +++ b/src/datadog/tracer_telemetry.h @@ -105,6 +105,8 @@ class TracerTelemetry { std::vector, MetricSnapshot>> metrics_snapshots_; + std::vector configuration_snapshot_; + nlohmann::json generate_telemetry_body(std::string request_type); nlohmann::json generate_configuration_field( @@ -129,6 +131,8 @@ class TracerTelemetry { // collect timestamped "points" of values. These values are later submitted // in `generate-metrics` messages. void capture_metrics(); + void capture_configuration_change( + const std::vector& new_configuration); // Constructs a messsage-batch containing `app-heartbeat`, and if metrics // have been modified, a `generate-metrics` message. std::string heartbeat_and_telemetry(); @@ -136,8 +140,7 @@ class TracerTelemetry { // been modified, a `generate-metrics` message. std::string app_closing(); // Construct an `app-client-configuration-change` message. - std::string configuration_change( - const std::vector& new_configuration); + std::string configuration_change(); }; } // namespace tracing diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c4ae657e..5907a046 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,13 +20,13 @@ add_executable(tests test_base64.cpp test_cerr_logger.cpp test_curl.cpp + test_config_manager.cpp test_datadog_agent.cpp test_glob.cpp test_limiter.cpp test_metrics.cpp test_msgpack.cpp test_parse_util.cpp - test_remote_config.cpp test_smoke.cpp test_span.cpp test_span_sampler.cpp @@ -36,6 +36,13 @@ add_executable(tests test_tracer_telemetry.cpp test_tracer.cpp test_trace_sampler.cpp + + remote_config/test_remote_config.cpp +) + +target_include_directories(tests + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} ) target_link_libraries(tests diff --git a/test/remote_config/test_remote_config.cpp b/test/remote_config/test_remote_config.cpp new file mode 100644 index 00000000..a5e0f6d9 --- /dev/null +++ b/test/remote_config/test_remote_config.cpp @@ -0,0 +1,452 @@ +#include "catch.hpp" +#include "datadog/json.hpp" +#include "datadog/remote_config/remote_config.h" + +namespace rc = datadog::remote_config; +using namespace datadog::tracing; + +#define REMOTE_CONFIG_TEST(x) TEST_CASE(x, "[remote_config]") + +namespace { +struct FakeListener : public rc::Listener { + rc::Products products{0}; + rc::Capabilities capabilities{0}; + size_t count_on_update{0}; + size_t count_on_revert{0}; + size_t count_on_post_process{0}; + std::function(const Configuration&)> update_callback{ + nullptr}; + + FakeListener() {} + ~FakeListener() = default; + + rc::Products get_products() override { return products; } + + rc::Capabilities get_capabilities() override { return capabilities; } + + void on_revert(const Configuration&) override { ++count_on_revert; } + + Optional on_update(const Configuration& conf) override { + ++count_on_update; + if (update_callback) { + return update_callback(conf); + } + + return nullopt; + } + + void on_post_process() override { ++count_on_post_process; } +}; +} // namespace + +REMOTE_CONFIG_TEST("initial state payload") { + // Verify the initial payload structure for a remote configuration instance. + const TracerSignature tracer_signature{ + /* runtime_id = */ RuntimeID::generate(), + /* service = */ "testsvc", + /* environment = */ "test"}; + + auto tracing_listener = std::make_shared(); + tracing_listener->products = rc::product::APM_TRACING; + tracing_listener->capabilities = rc::capability::APM_TRACING_SAMPLE_RATE | + rc::capability::APM_TRACING_TAGS; + + auto asm_listener = std::make_shared(); + asm_listener->products = rc::product::ASM | rc::product::ASM_DD | + rc::product::ASM_DATA | rc::product::ASM_FEATURES; + asm_listener->capabilities = + rc::capability::ASM_ACTIVATION | rc::capability::ASM_CUSTOM_RULES; + + const std::vector expected_products{ + "APM_TRACING", "ASM", "ASM_DATA", "ASM_DD", "ASM_FEATURES"}; + const std::vector expected_capabilities{0, 0, 0, 0, 0, 0, 145, 2}; + + rc::Manager rc(tracer_signature, {tracing_listener, asm_listener}); + + const auto payload = rc.make_request_payload(); + + CHECK(payload["client"]["is_tracer"] == true); + CHECK(payload["client"]["products"] == expected_products); + CHECK(payload["client"]["capabilities"] == expected_capabilities); + CHECK(payload["client"]["client_tracer"]["language"] == "cpp"); + CHECK(payload["client"]["client_tracer"]["service"] == "testsvc"); + CHECK(payload["client"]["client_tracer"]["env"] == "test"); + CHECK(payload["client"]["client_tracer"]["runtime_id"] == + tracer_signature.runtime_id.string()); + CHECK(payload["client"]["client_tracer"]["tracer_version"] == + tracer_signature.library_version); + CHECK(payload["client"]["state"]["root_version"] == 1); + CHECK(payload["client"]["state"]["targets_version"] == 0); + CHECK(payload["client"]["state"]["backend_client_state"] == ""); + + CHECK(payload.contains("error") == false); + CHECK(payload["client"]["state"].contains("config_states") == false); +} + +// TODO: test all combination of product and capabilities generation + +REMOTE_CONFIG_TEST("response processing") { + const TracerSignature tracer_signature{ + /* runtime_id = */ RuntimeID::generate(), + /* service = */ "testsvc", + /* environment = */ "test"}; + + SECTION("ill formatted input", + "inputs not following the Remote Configuration JSON schema should " + "generate an error") { + // clang-format off + auto test_case = GENERATE(values({ + // Missing all fields + "{}", + // `targets` field is empty + R"({ "targets": "" })", + // `targets` field is not base64 encoded + R"({ "targets": "Hello, Mars!" })", + // `targets` field is not a JSON base64 encoded + // decode("bm90IGpzb24=") == "not json" + R"({ "targets": "bm90IGpzb24=" })", + // `targets` field JSON base64 encoded do not follow the expected + // schema + // decode("eyJmb28iOiAiYmFyIn0=") == "{"foo": "bar"}" + R"({ "targets": "eyJmb28iOiAiYmFyIn0=" })", + // `targets` is missing the `targets` field. + // + // decode("eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=") + // == "{"signed": {"version": 2, "custom": {"opaque_backend_state": "15"}}}" + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", + "client_configs": ["employee/APM_TRACING/missing_target/conf"] + })", + // `/targets/targets` have no `datadog` entry + // {"signed": {"version": 2, "targets": {"foo": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["employee/APM_TRACING/missing_client_entry/conf"] + })", + // `targets` OK but no `target_files` field. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZW1wbG95ZWUvQVBNX1RSQUNJTkcvdmFsaWRfY29uZl9wYXRoL2NvbmZpZyI6IHt9LCAiYmFyIjoge319LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["employee/APM_TRACING/valid_conf_path/config"] + })", + // `targets` OK. `target_files` field is empty. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZW1wbG95ZWUvQVBNX1RSQUNJTkcvdmFsaWRfY29uZl9wYXRoL2NvbmZpZyI6IHt9LCAiYmFyIjoge319LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["employee/APM_TRACING/valid_conf_path/config"], + "target_files": [] + })", + // `targets` OK. `target_files` field is not an array. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZW1wbG95ZWUvQVBNX1RSQUNJTkcvdmFsaWRfY29uZl9wYXRoL2NvbmZpZyI6IHt9LCAiYmFyIjoge319LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["employee/APM_TRACING/valid_conf_path/config"], + "target_files": 15 + })", + // `targets` OK. `target_files` field content is not base64 encoded. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZW1wbG95ZWUvQVBNX1RSQUNJTkcvdmFsaWRfY29uZl9wYXRoL2NvbmZpZyI6IHt9LCAiYmFyIjoge319LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["employee/APM_TRACING/valid_conf_path/config"], + "target_files": [{"path": "employee/APM_TRACING/valid_conf_path/config", "raw": "Hello, Uranus!"}] + })", + // `targets` OK. `target_files` field content is not a JSON base64 encoded. + // decode("bm90IGpzb24=") == "not json" + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZW1wbG95ZWUvQVBNX1RSQUNJTkcvdmFsaWRfY29uZl9wYXRoL2NvbmZpZyI6IHt9LCAiYmFyIjoge319LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["employee/APM_TRACING/valid_conf_path/config"], + "target_files": [{"path": "employee/APM_TRACING/valid_conf_path/config", "raw": "bm90IGpzb24="}] + })", + // `targets` OK. `target_files` field JSON base64 content do not follow the expected schema. + // decode("eyJmb28iOiAiYmFyIn0=") == "{"foo": "bar"}" + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZW1wbG95ZWUvQVBNX1RSQUNJTkcvdmFsaWRfY29uZl9wYXRoL2NvbmZpZyI6IHt9LCAiYmFyIjoge319LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["employee/APM_TRACING/valid_conf_path/config"], + "target_files": [{"path": "employee/APM_TRACING/valid_conf_path/config", "raw": "eyJmb28iOiAiYmFyIn0="}] + })", + })); + // clang-format on + + CAPTURE(test_case); + const auto response_json = + nlohmann::json::parse(/* input = */ test_case, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + REQUIRE(!response_json.is_discarded()); + + rc::Manager rc(tracer_signature, {}); + + rc.process_response(response_json); + + // Next payload should contains an error. + const auto payload = rc.make_request_payload(); + CHECK(payload.contains("/client/state/has_error"_json_pointer) == true); + CHECK(payload.contains("/client/state/error"_json_pointer) == true); + } + + SECTION( + "configuration updates targetting the wrong tracer reports an error") { + // clang-format off + auto test_case = GENERATE(values({ + // "service_target": { "service": "not-testsvc", "env": "test" } + R"({ + "targets": + "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImVtcGxveWVlL0FQTV9UUkFDSU5HL3Rlc3RfcmNfd3JvbmdfdGFyZ2V0L3NlcnZpY2VfbmFtZSI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "client_configs": ["employee/APM_TRACING/test_rc_wrong_target/service_name"], + "target_files": [ + { + "path": "employee/APM_TRACING/test_rc_wrong_target/service_name", + "raw": + "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAibm90LXRlc3RzdmMiLCAiZW52IjogInRlc3QiIH0gfQ==" + } + ] + })", + // "service_target": { "service": "testsvc", "env": "dev" } + R"({ + "targets": + "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImVtcGxveWVlL0FQTV9UUkFDSU5HL3Rlc3RfcmNfd3JvbmdfdGFyZ2V0L2Vudl9uYW1lIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImExNzc3NjhiMjBiN2M3Zjg0NDkzNWNhZTY5YzVjNWVkODhlYWFlMjM0ZTAxODJhNzgzNTk5NzMzOWU1NTI0YmMiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDM3NAogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0=", + "client_configs": ["employee/APM_TRACING/test_rc_wrong_target/env_name"], + "target_files": [ + { + "path": "employee/APM_TRACING/test_rc_wrong_target/env_name", + "raw": + "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAiZGV2IiB9IH0=" + } + ] + })" + })); + // clang-format on + + CAPTURE(test_case); + + const auto response_json = + nlohmann::json::parse(/* input = */ test_case, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + auto tracing_listener = std::make_shared(); + tracing_listener->products = rc::product::APM_TRACING; + + rc::Manager rc(tracer_signature, {tracing_listener}); + rc.process_response(response_json); + + CHECK(tracing_listener->count_on_update == 0); + CHECK(tracing_listener->count_on_revert == 0); + CHECK(tracing_listener->count_on_post_process == 1); + + // Verify next request set the config status + const auto payload = rc.make_request_payload(); + REQUIRE(payload.contains("/client/state/config_states"_json_pointer) == + true); + + constexpr auto error_state = 3; + const auto& config_states = + payload.at("/client/state/config_states"_json_pointer); + REQUIRE(config_states.size() == 1); + CHECK(config_states[0]["product"] == "APM_TRACING"); + CHECK(config_states[0]["apply_state"] == error_state); + } + + SECTION("update dispatch") { + // Verify configuration updates are dispatched to the correct listener + std::string_view rc_response = R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImVtcGxveWVlL0FQTV9UUkFDSU5HL3Rlc3RfcmNfdXBkYXRlL2xpYl91cGRhdGUiOiB7CiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiYTE3Nzc2OGIyMGI3YzdmODQ0OTM1Y2FlNjljNWM1ZWQ4OGVhYWUyMzRlMDE4MmE3ODM1OTk3MzM5ZTU1MjRiYyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMzc0CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJlbXBsb3llZS9BR0VOVF9UQVNLL3Rlc3RfcmNfdXBkYXRlL2ZsYXJlX3Rhc2siOiB7CiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiNDE5NGNlNmY3MTEzOTU5NDZiZTgzN2JmNWViYTk0ODkxYjdiZGU3OTg5MTFlZDVlZmZmNjU5OWQyMWFiOTY5NiIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMzc0CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJlbXBsb3llZS9BR0VOVF9DT05GSUcvdGVzdF9yY191cGRhdGUvZmxhcmVfY29uZiI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICIyZDc4YWU3MzZhM2ZjNDU1YjcyMzFkYWY5OTQ1ZjhkZjcwNGYxNzI1YjUwZGRlNDZkMGNiY2RjM2YwZTExZDQxIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgIAogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "client_configs": [ + "employee/APM_TRACING/test_rc_update/lib_update", + "employee/AGENT_TASK/test_rc_update/flare_task", + "employee/AGENT_CONFIG/test_rc_update/flare_conf" + ], + "target_files": [ + { + "path": "employee/AGENT_CONFIG/test_rc_update/flare_conf", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + }, + { + "path": "employee/APM_TRACING/test_rc_update/lib_update", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + }, + { + "path": "employee/AGENT_TASK/test_rc_update/flare_task", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + } + ] + })"; + + const auto response_json = + nlohmann::json::parse(/* input = */ rc_response, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + auto tracing_listener = std::make_shared(); + tracing_listener->products = rc::product::APM_TRACING; + + auto agent_listener = std::make_shared(); + agent_listener->products = + rc::product::AGENT_TASK | rc::product::AGENT_CONFIG; + + tracing_listener->update_callback = + ([](const rc::Listener::Configuration& conf) { + CHECK(conf.path == "employee/APM_TRACING/test_rc_update/lib_update"); + return "test error message"; + }); + + rc::Manager rc(tracer_signature, {tracing_listener, agent_listener}); + rc.process_response(response_json); + + CHECK(tracing_listener->count_on_update == 1); + CHECK(tracing_listener->count_on_revert == 0); + CHECK(tracing_listener->count_on_post_process == 1); + + CHECK(agent_listener->count_on_update == 2); + CHECK(agent_listener->count_on_revert == 0); + CHECK(agent_listener->count_on_post_process == 1); + + SECTION("config states are reported on next payload") { + const auto payload = rc.make_request_payload(); + REQUIRE(payload.contains("/client/state/config_states"_json_pointer) == + true); + + constexpr auto error_state = 3; + constexpr auto acknowledged_state = 2; + + const auto& config_states = + payload.at("/client/state/config_states"_json_pointer); + REQUIRE(config_states.size() == 3); + + for (const auto& config_state : config_states) { + if (config_state["product"] == "APM_TRACING") { + CHECK(config_state["apply_state"] == error_state); + CHECK(config_state["apply_error"] == "test error message"); + } else { + CHECK(config_state["apply_state"] == acknowledged_state); + CHECK(!config_state.contains("apply_error")); + } + } + } + + SECTION("same config update should not trigger listeners") { + rc.process_response(response_json); + CHECK(tracing_listener->count_on_update == 1); + CHECK(tracing_listener->count_on_revert == 0); + CHECK(tracing_listener->count_on_post_process == 2); + + CHECK(agent_listener->count_on_update == 2); + CHECK(agent_listener->count_on_revert == 0); + CHECK(agent_listener->count_on_post_process == 2); + } + + SECTION("new version of a config calls listeners") { + std::string_view new_rc_response = R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImVtcGxveWVlL0FQTV9UUkFDSU5HL3Rlc3RfcmNfdXBkYXRlL2xpYl91cGRhdGUiOiB7CiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiM2I5NDIxY2FhYTVkNzUzMTg0NWY3YzMwN2FkN2M2MTU1ZDgxOTVkMjcwOTEzMzY0OTI2YzlmNjQxZTkyNDE0NyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMzc0CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJlbXBsb3llZS9BR0VOVF9UQVNLL3Rlc3RfcmNfdXBkYXRlL2ZsYXJlX3Rhc2siOiB7CiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiNTY3NzQ4MWE4YzIxZDZjNzQwODI5ZmQxMDYxMDBmNDZmN2M1MWY1MjY1YjBiYTU0MGJjMTk4YmQ4MzM5Zjg3MiIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMzc0CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJlbXBsb3llZS9BR0VOVF9DT05GSUcvdGVzdF9yY191cGRhdGUvZmxhcmVfY29uZiI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJlNjhlYzhkOWIxMWE4Y2Q1OGM4Y2E1ZTE0MjVkNjE2M2RiOTQ3ZWFhMzVmNzM4NTYxYzQ4NmUxNDRlOTRmYzUyIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "client_configs": [ + "employee/APM_TRACING/test_rc_update/lib_update", + "employee/AGENT_TASK/test_rc_update/flare_task", + "employee/AGENT_CONFIG/test_rc_update/flare_conf" + ], + "target_files": [ + { + "path": "employee/AGENT_CONFIG/test_rc_update/flare_conf", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + }, + { + "path": "employee/APM_TRACING/test_rc_update/lib_update", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + }, + { + "path": "employee/AGENT_TASK/test_rc_update/flare_task", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + } + ] + })"; + + const auto response_json = + nlohmann::json::parse(/* input = */ new_rc_response, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + rc.process_response(response_json); + + CHECK(tracing_listener->count_on_update == 2); + CHECK(tracing_listener->count_on_revert == 0); + CHECK(tracing_listener->count_on_post_process == 2); + + CHECK(agent_listener->count_on_update == 4); + CHECK(agent_listener->count_on_revert == 0); + CHECK(agent_listener->count_on_post_process == 2); + } + + SECTION("revert configuration update") { + SECTION("partial revert") { + // Removed `employee/APM_TRACING/test_rc_update/lib_update` + // configuration update This should trigger a revert on `APM_TRACING` + // listeners. + std::string_view rc_partial_revert_response = R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImVtcGxveWVlL0FHRU5UX1RBU0svdGVzdF9yY191cGRhdGUvZmxhcmVfdGFzayI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI0MTk0Y2U2ZjcxMTM5NTk0NmJlODM3YmY1ZWJhOTQ4OTFiN2JkZTc5ODkxMWVkNWVmZmY2NTk5ZDIxYWI5Njk2IgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImVtcGxveWVlL0FHRU5UX0NPTkZJRy90ZXN0X3JjX3VwZGF0ZS9mbGFyZV9jb25mIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjJkNzhhZTczNmEzZmM0NTViNzIzMWRhZjk5NDVmOGRmNzA0ZjE3MjViNTBkZGU0NmQwY2JjZGMzZjBlMTFkNDEiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDM3NAogICAgICAgICAgICB9CiAgICAgCiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0=", + "client_configs": [ + "employee/AGENT_TASK/test_rc_update/flare_task", + "employee/AGENT_CONFIG/test_rc_update/flare_conf" + ], + "target_files": [ + { + "path": "employee/AGENT_CONFIG/test_rc_update/flare_conf", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + }, + { + "path": "employee/AGENT_TASK/test_rc_update/flare_task", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + } + ] + })"; + + const auto response_json = + nlohmann::json::parse(/* input = */ rc_partial_revert_response, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + rc.process_response(response_json); + + CHECK(tracing_listener->count_on_update == 1); + CHECK(tracing_listener->count_on_revert == 1); + CHECK(tracing_listener->count_on_post_process == 2); + + CHECK(agent_listener->count_on_update == 2); + CHECK(agent_listener->count_on_revert == 0); + CHECK(agent_listener->count_on_post_process == 2); + } + + SECTION("missing client_configs field triggers a full revert") { + std::string_view rc_revert_response = R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImVtcGxveWVlL0FQTV9UUkFDSU5HL3Rlc3RfcmNfdXBkYXRlL2xpYl91cGRhdGUiOiB7CiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiYTE3Nzc2OGIyMGI3YzdmODQ0OTM1Y2FlNjljNWM1ZWQ4OGVhYWUyMzRlMDE4MmE3ODM1OTk3MzM5ZTU1MjRiYyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMzc0CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJlbXBsb3llZS9BR0VOVF9UQVNLL3Rlc3RfcmNfdXBkYXRlL2ZsYXJlX3Rhc2siOiB7CiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiNDE5NGNlNmY3MTEzOTU5NDZiZTgzN2JmNWViYTk0ODkxYjdiZGU3OTg5MTFlZDVlZmZmNjU5OWQyMWFiOTY5NiIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMzc0CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJlbXBsb3llZS9BR0VOVF9DT05GSUcvdGVzdF9yY191cGRhdGUvZmxhcmVfY29uZiI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICIyZDc4YWU3MzZhM2ZjNDU1YjcyMzFkYWY5OTQ1ZjhkZjcwNGYxNzI1YjUwZGRlNDZkMGNiY2RjM2YwZTExZDQxIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgIAogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "target_files": [{}] + })"; + + const auto response_json = + nlohmann::json::parse(/* input = */ rc_revert_response, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + rc.process_response(response_json); + + CHECK(tracing_listener->count_on_update == 1); + CHECK(tracing_listener->count_on_revert == 1); + CHECK(tracing_listener->count_on_post_process == 2); + + CHECK(agent_listener->count_on_update == 2); + CHECK(agent_listener->count_on_revert == 2); + CHECK(agent_listener->count_on_post_process == 2); + } + } + } +} diff --git a/test/test_config_manager.cpp b/test/test_config_manager.cpp new file mode 100644 index 00000000..a286108e --- /dev/null +++ b/test/test_config_manager.cpp @@ -0,0 +1,188 @@ +#include "catch.hpp" +#include "datadog/config_manager.h" +#include "datadog/remote_config/listener.h" +#include "datadog/trace_sampler.h" + +namespace rc = datadog::remote_config; +using namespace datadog::tracing; + +#define CONFIG_MANAGER_TEST(x) TEST_CASE(x, "[config_manager]") + +nlohmann::json load_json(std::string_view sv) { + auto j = nlohmann::json::parse(/* input = */ sv, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + REQUIRE(!j.is_discarded()); + return j; +} + +CONFIG_MANAGER_TEST("remote configuration handling") { + const TracerSignature tracer_signature{ + /* runtime_id = */ RuntimeID::generate(), + /* service = */ "testsvc", + /* environment = */ "test"}; + + TracerConfig config; + config.service = "testsvc"; + config.environment = "test"; + + auto tracer_telemetry = std::make_shared( + false, default_clock, nullptr, tracer_signature, "", ""); + + ConfigManager config_manager(*finalize_config(config), tracer_telemetry); + + rc::Listener::Configuration config_update{/* id = */ "id", + /* path = */ "", + /* hash = */ "", + /* content = */ "", + /* version = */ 1, + rc::product::Flag::APM_TRACING}; + + SECTION("handling of `tracing_sampling_rate`") { + // SECTION("invalid value") { + // config_update.content = R"({ + // "lib_config": { + // "library_language": "all", + // "library_version": "latest", + // "service_name": "testsvc", + // "env": "test", + // "tracing_enabled": false, + // "tracing_sampling_rate": 100.0, + // "tracing_tags": [ + // "hello:world", + // "foo:bar" + // ] + // }, + // "service_target": { + // "service": "testsvc", + // "env": "test" + // } + // })"; + // + // const auto old_trace_sampler_config = + // config_manager.trace_sampler()->config_json(); + // + // const auto err = config_manager.on_update(config_update); + // CHECK(!err); + // + // const auto new_trace_sampler_config = + // config_manager.trace_sampler()->config_json(); + // + // CHECK(old_trace_sampler_config == new_trace_sampler_config); + // } + SECTION("valid value") { + config_update.content = R"({ + "lib_config": { + "library_language": "all", + "library_version": "latest", + "service_name": "testsvc", + "env": "test", + "tracing_enabled": false, + "tracing_sampling_rate": 0.6, + "tracing_tags": [ + "hello:world", + "foo:bar" + ] + }, + "service_target": { + "service": "testsvc", + "env": "test" + } + })"; + + const auto old_trace_sampler_config = + config_manager.trace_sampler()->config_json(); + + const auto err = config_manager.on_update(config_update); + CHECK(!err); + + const auto new_trace_sampler_config = + config_manager.trace_sampler()->config_json(); + + CHECK(old_trace_sampler_config != new_trace_sampler_config); + + config_manager.on_revert(config_update); + + const auto revert_trace_sampler_config = + config_manager.trace_sampler()->config_json(); + + CHECK(old_trace_sampler_config == revert_trace_sampler_config); + } + } + + SECTION("handling of `tracing_tags`") { + config_update.content = R"({ + "lib_config": { + "library_language": "all", + "library_version": "latest", + "service_name": "testsvc", + "env": "test", + "tracing_enabled": false, + "tracing_sampling_rate": 0.6, + "tracing_tags": [ + "hello:world", + "foo:bar" + ] + }, + "service_target": { + "service": "testsvc", + "env": "test" + } + })"; + + const std::unordered_map expected_tags{ + {"hello", "world"}, {"foo", "bar"}}; + + const auto old_tags = config_manager.span_defaults()->tags; + + const auto err = config_manager.on_update(config_update); + CHECK(!err); + + const auto new_tags = config_manager.span_defaults()->tags; + + CHECK(old_tags != new_tags); + CHECK(new_tags == expected_tags); + + config_manager.on_revert(config_update); + + const auto reverted_tags = config_manager.span_defaults()->tags; + + CHECK(old_tags == reverted_tags); + } + + SECTION("handling of `tracing_enabled`") { + config_update.content = R"({ + "lib_config": { + "library_language": "all", + "library_version": "latest", + "service_name": "testsvc", + "env": "test", + "tracing_enabled": false, + "tracing_sampling_rate": 0.6, + "tracing_tags": [ + "hello:world", + "foo:bar" + ] + }, + "service_target": { + "service": "testsvc", + "env": "test" + } + })"; + + const auto old_tracing_status = config_manager.report_traces(); + + const auto err = config_manager.on_update(config_update); + CHECK(!err); + + const auto new_tracing_status = config_manager.report_traces(); + + CHECK(old_tracing_status != new_tracing_status); + CHECK(new_tracing_status == false); + + config_manager.on_revert(config_update); + + const auto reverted_tracing_status = config_manager.report_traces(); + CHECK(old_tracing_status == reverted_tracing_status); + } +} diff --git a/test/test_datadog_agent.cpp b/test/test_datadog_agent.cpp index cc97acb3..c1bd4d7a 100644 --- a/test/test_datadog_agent.cpp +++ b/test/test_datadog_agent.cpp @@ -195,16 +195,16 @@ TEST_CASE("Remote Configuration", "[datadog_agent]") { REQUIRE(finalized); const TracerSignature signature(RuntimeID::generate(), "testsvc", "test"); - auto config_manager = std::make_shared(*finalized); auto telemetry = std::make_shared( finalized->report_telemetry, finalized->clock, finalized->logger, signature, "", ""); + auto config_manager = std::make_shared(*finalized, telemetry); + const auto& agent_config = std::get(finalized->collector); - DatadogAgent agent(agent_config, telemetry, config.logger, signature, - config_manager); + DatadogAgent agent(agent_config, telemetry, config.logger, signature, {}); SECTION("404 do not log an error") { http_client->response_status = 404; diff --git a/test/test_remote_config.cpp b/test/test_remote_config.cpp deleted file mode 100644 index e3bd9d98..00000000 --- a/test/test_remote_config.cpp +++ /dev/null @@ -1,349 +0,0 @@ -#include - -#include "catch.hpp" -#include "datadog/json_fwd.hpp" -#include "datadog/remote_config.h" -#include "datadog/trace_sampler.h" -#include "mocks/loggers.h" -#include "test.h" - -using namespace datadog::tracing; - -#define REMOTE_CONFIG_TEST(x) TEST_CASE(x, "[remote_config]") - -REMOTE_CONFIG_TEST("first payload") { - const TracerSignature tracer_signature{ - /* runtime_id = */ RuntimeID::generate(), - /* service = */ "testsvc", - /* environment = */ "test"}; - - TracerConfig tracer_cfg; - - const std::time_t mock_time = 1672484400; - const Clock clock = [mock_time]() { - TimePoint result; - result.wall = std::chrono::system_clock::from_time_t(mock_time); - return result; - }; - - TracerConfig config; - config.service = "testsvc"; - config.environment = "test"; - const auto config_manager = - std::make_shared(*finalize_config(config)); - - RemoteConfigurationManager rc(tracer_signature, config_manager); - - const auto payload = rc.make_request_payload(); - - CHECK(payload.contains("error") == false); - CHECK(payload["client"]["is_tracer"] == true); - CHECK(payload["client"]["client_tracer"]["language"] == "cpp"); - CHECK(payload["client"]["client_tracer"]["service"] == "testsvc"); - CHECK(payload["client"]["client_tracer"]["env"] == "test"); - CHECK(payload["client"]["state"]["root_version"] == 1); - CHECK(payload["client"]["state"]["targets_version"] == 0); -} - -REMOTE_CONFIG_TEST("response processing") { - const TracerSignature tracer_signature{ - /* runtime_id = */ RuntimeID::generate(), - /* service = */ "testsvc", - /* environment = */ "test"}; - - TracerConfig tracer_cfg; - - const std::time_t mock_time = 1672484400; - const Clock clock = [mock_time]() { - TimePoint result; - result.wall = std::chrono::system_clock::from_time_t(mock_time); - return result; - }; - - TracerConfig config; - config.service = "testsvc"; - config.environment = "test"; - config.trace_sampler.sample_rate = 1.0; - config.report_traces = true; - const auto config_manager = - std::make_shared(*finalize_config(config)); - - RemoteConfigurationManager rc(tracer_signature, config_manager); - - SECTION("ill formatted input", - "inputs not following the Remote Configuration JSON schema should " - "generate an error") { - // clang-format off - auto test_case = GENERATE(values({ - // Missing all fields - "{}", - // `targets` field is empty - R"({ "targets": "" })", - // `targets` field is not base64 encoded - R"({ "targets": "Hello, Mars!" })", - // `targets` field is not a JSON base64 encoded - // decode("bm90IGpzb24=") == "not json" - R"({ "targets": "bm90IGpzb24=" })", - // `targets` field JSON base64 encoded do not follow the expected schema - // decode("eyJmb28iOiAiYmFyIn0=") == "{"foo": "bar"}" - R"({ "targets": "eyJmb28iOiAiYmFyIn0=" })", - // `targets` is missing the `targets` field. - // decode("eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=") == "{"signed": {"version": 2, "custom": {"opaque_backend_state": "15"}}}" - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", - "client_configs": ["datadog"] - })", - // `/targets/targets` have no `datadog` entry - // {"signed": {"version": 2, "targets": {"foo": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["datadog"] - })", - // `targets` OK but no `target_files` field. - // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["foo/APM_TRACING/30"] - })", - // `targets` OK. `target_files` field is empty. - // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [] - })", - // `targets` OK. `target_files` field is not an array. - // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": 15 - })", - // `targets` OK. `target_files` field content is not base64 encoded. - // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [{"path": "foo/APM_TRACING/30", "raw": "Hello, Uranus!"}] - })", - // `targets` OK. `target_files` field content is not a JSON base64 encoded. - // decode("bm90IGpzb24=") == "not json" - // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [{"path": "foo/APM_TRACING/30", "raw": "bm90IGpzb24="}] - })", - // `targets` OK. `target_files` field JSON base64 content do not follow the expected schema. - // decode("eyJmb28iOiAiYmFyIn0=") == "{"foo": "bar"}" - // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ - "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [{"path": "foo/APM_TRACING/30", "raw": "eyJmb28iOiAiYmFyIn0="}] - })", - })); - // clang-format on - - CAPTURE(test_case); - const auto response_json = - nlohmann::json::parse(/* input = */ test_case, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); - - REQUIRE(!response_json.is_discarded()); - const auto config_updated = rc.process_response(response_json); - CHECK(config_updated.empty()); - - // Next payload should contains an error. - const auto payload = rc.make_request_payload(); - CHECK(payload.contains("/client/state/has_error"_json_pointer) == true); - CHECK(payload.contains("/client/state/error"_json_pointer) == true); - } - - SECTION("valid remote configuration") { - // clang-format off - // { - // "lib_config": { - // "library_language": "all", - // "library_version": "latest", - // "service_name": "testsvc", - // "env": "test", - // "tracing_enabled": false, - // "tracing_sampling_rate": 0.6, - // "tracing_tags": [ - // "hello:world", - // "foo:bar" - // ] - // }, - // "service_target": { - // "service": "testsvc", - // "env": "test" - // } - // } - const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [ - { - "path": "foo/APM_TRACING/30", - "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiBmYWxzZSwgInRyYWNpbmdfc2FtcGxpbmdfcmF0ZSI6IDAuNiwgInRyYWNpbmdfdGFncyI6IFsiaGVsbG86d29ybGQiLCAiZm9vOmJhciJdIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" - } - ] - })"; - // clang-format on - - const auto response_json = - nlohmann::json::parse(/* input = */ json_input, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); - - REQUIRE(!response_json.is_discarded()); - - const auto old_trace_sampler_config = - config_manager->trace_sampler()->config_json(); - const auto old_span_defaults = config_manager->span_defaults(); - const auto old_report_traces = config_manager->report_traces(); - const auto config_updated = rc.process_response(response_json); - REQUIRE(config_updated.size() == 3); - const auto new_trace_sampler_config = - config_manager->trace_sampler()->config_json(); - const auto new_span_defaults = config_manager->span_defaults(); - const auto new_report_traces = config_manager->report_traces(); - - CHECK(new_trace_sampler_config != old_trace_sampler_config); - CHECK(new_span_defaults != old_span_defaults); - CHECK(new_report_traces != old_report_traces); - - SECTION("config status is correctly applied") { - const auto payload = rc.make_request_payload(); - const auto s = payload.dump(2); - REQUIRE(payload.contains("/client/state/config_states"_json_pointer) == - true); - - const auto& config_states = - payload.at("/client/state/config_states"_json_pointer); - REQUIRE(config_states.size() == 1); - CHECK(config_states[0]["product"] == "APM_TRACING"); - CHECK(config_states[0]["apply_state"] == 2); - } - - SECTION("reset configuration") { - SECTION( - "missing from client_configs -> all configurations should be reset") { - // clang-format off - const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "target_files": [] - })"; - // clang-format on - - const auto response_json = - nlohmann::json::parse(/* input = */ json_input, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); - - REQUIRE(!response_json.is_discarded()); - - const auto config_updated = rc.process_response(response_json); - REQUIRE(config_updated.size() == 3); - - const auto current_trace_sampler_config = - config_manager->trace_sampler()->config_json(); - const auto current_span_defaults = config_manager->span_defaults(); - const auto current_report_traces = config_manager->report_traces(); - - CHECK(old_trace_sampler_config == current_trace_sampler_config); - CHECK(old_span_defaults == current_span_defaults); - CHECK(old_report_traces == current_report_traces); - } - - SECTION( - "missing the trace_sampling_rate field -> only this field should be " - "reset") { - // clang-format off - const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI2OWUzNDZiNWZmY2U4NDVlMjk5ODRlNzU5YjcxZDdiMDdjNTYxOTc5ZmFlOWU4MmVlZDA4MmMwMzhkODZlNmIwIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [ - { - "path": "foo/APM_TRACING/30", - "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiBmYWxzZSwgInRyYWNpbmdfdGFncyI6IFsiaGVsbG86d29ybGQiLCAiZm9vOmJhciJdIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" - } - ] - })"; - // clang-format on - - const auto response_json = - nlohmann::json::parse(/* input = */ json_input, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); - - REQUIRE(!response_json.is_discarded()); - - const auto config_updated = rc.process_response(response_json); - REQUIRE(config_updated.size() == 1); - const auto current_trace_sampler_config = - config_manager->trace_sampler()->config_json(); - CHECK(old_trace_sampler_config == current_trace_sampler_config); - } - } - } - - SECTION("update received not for us") { - // clang-format off - auto test_case = GENERATE(values({ - // "service_target": { "service": "not-testsvc", "env": "test" } - R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [ - { - "path": "foo/APM_TRACING/30", - "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAibm90LXRlc3RzdmMiLCAiZW52IjogInRlc3QiIH0gfQ==" - } - ] - })", - // "service_target": { "service": "testsvc", "env": "dev" } - R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], - "target_files": [ - { - "path": "foo/APM_TRACING/30", - "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAiZGV2IiB9IH0=" - } - ] - })" - })); - // clang-format on - - CAPTURE(test_case); - - const auto response_json = - nlohmann::json::parse(/* input = */ test_case, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); - - REQUIRE(!response_json.is_discarded()); - - const auto old_sampling_rate = config_manager->trace_sampler(); - const auto config_updated = rc.process_response(response_json); - const auto new_sampling_rate = config_manager->trace_sampler(); - - CHECK(config_updated.empty()); - CHECK(new_sampling_rate == old_sampling_rate); - - // Verify next request set the config status - const auto payload = rc.make_request_payload(); - REQUIRE(payload.contains("/client/state/config_states"_json_pointer) == - true); - - const auto& config_states = - payload.at("/client/state/config_states"_json_pointer); - REQUIRE(config_states.size() == 1); - CHECK(config_states[0]["product"] == "APM_TRACING"); - CHECK(config_states[0]["apply_state"] == 3); - CHECK(config_states[0].contains("apply_state")); - } -} diff --git a/test/test_tracer_telemetry.cpp b/test/test_tracer_telemetry.cpp index 2b0a7b21..47062144 100644 --- a/test/test_tracer_telemetry.cpp +++ b/test/test_tracer_telemetry.cpp @@ -95,7 +95,7 @@ TEST_CASE("Tracer telemetry", "[telemetry]") { SECTION("generates a configuration change event") { SECTION("empty configuration generate a valid payload") { auto config_change_message = nlohmann::json::parse( - tracer_telemetry.configuration_change({}), nullptr, false); + tracer_telemetry.configuration_change(), nullptr, false); REQUIRE(config_change_message.is_discarded() == false); CHECK(config_change_message["request_type"] == @@ -111,9 +111,9 @@ TEST_CASE("Tracer telemetry", "[telemetry]") { {ConfigName::REPORT_TRACES, "", ConfigMetadata::Origin::DEFAULT, Error{Error::Code::OTHER, "empty field"}}}; + tracer_telemetry.capture_configuration_change(new_config); auto config_change_message = nlohmann::json::parse( - tracer_telemetry.configuration_change(new_config), nullptr, - false); + tracer_telemetry.configuration_change(), nullptr, false); REQUIRE(config_change_message.is_discarded() == false); CHECK(config_change_message["request_type"] == From a32272c1539e812f597285b763d8d4f5fe93df94 Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 20 Jun 2024 09:30:10 +0200 Subject: [PATCH 2/6] code review: split capability and product --- BUILD.bazel | 1 + src/datadog/remote_config/capability.h | 75 --------------------- src/datadog/remote_config/listener.h | 1 + src/datadog/remote_config/remote_config.cpp | 2 +- 4 files changed, 3 insertions(+), 76 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 3408acbf..90ab5157 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -89,6 +89,7 @@ cc_library( "src/datadog/rate.h", "src/datadog/remote_config/capability.h", "src/datadog/remote_config/listener.h", + "src/datadog/remote_config/product.h", "src/datadog/remote_config/remote_config.h", "src/datadog/runtime_id.h", "src/datadog/sampling_decision.h", diff --git a/src/datadog/remote_config/capability.h b/src/datadog/remote_config/capability.h index 0b6f5cce..691c872e 100644 --- a/src/datadog/remote_config/capability.h +++ b/src/datadog/remote_config/capability.h @@ -1,10 +1,6 @@ #pragma once #include -#include - -#include "string_util.h" -#include "string_view.h" namespace datadog { namespace remote_config { @@ -17,14 +13,6 @@ namespace remote_config { // Capabilities c = APM_TRACING_SAMPLE_RATE | APM_TRACING_ENABLED; using Capabilities = uint64_t; -// Type alias for product flags. -// -// Usage: -// -// using namespace datadog::remote_config::product; -// Products p = AGENT_CONFIG | APM_TRACING; -using Products = uint64_t; - namespace capability { enum Flag : Capabilities { @@ -63,68 +51,5 @@ enum Flag : Capabilities { } // namespace capability -#define DD_QUOTED_IMPL(ARG) #ARG -#define DD_QUOTED(ARG) DD_QUOTED_IMPL(ARG) - -// List of remote configuration product built to support flag arithmetic -#define DD_LIST_REMOTE_CONFIG_PRODUCTS \ - X(AGENT_CONFIG, 1) \ - X(AGENT_TASK, 2) \ - X(APM_TRACING, 3) \ - X(LIVE_DEBUGGING, 4) \ - X(LIVE_DEBUGGING_SYMBOL_DB, 5) \ - X(ASM, 6) \ - X(ASM_DD, 7) \ - X(ASM_DATA, 8) \ - X(ASM_FEATURES, 9) - -namespace product { - -enum Flag : Products { -#define X(NAME, ID) NAME = 1 << ID, - DD_LIST_REMOTE_CONFIG_PRODUCTS -#undef X -}; -} // namespace product - -inline tracing::StringView to_string_view(product::Flag product) { -#define X(NAME, ID) \ - case product::Flag::NAME: { \ - return DD_QUOTED(NAME); \ - } - switch (product) { DD_LIST_REMOTE_CONFIG_PRODUCTS } -#undef X - - std::abort(); -} - -inline product::Flag parse_product(tracing::StringView sv) { - const auto upcase_product = tracing::to_upper(sv); - -#define X(NAME, ID) \ - if (upcase_product == DD_QUOTED(NAME)) { \ - return product::Flag::NAME; \ - } - DD_LIST_REMOTE_CONFIG_PRODUCTS -#undef X - - std::abort(); -} - -inline void visit_products(Products products, - std::function on_product) { -#define X(NAME, ID) \ - if (products & product::Flag::NAME) { \ - on_product(product::Flag::NAME); \ - } - - DD_LIST_REMOTE_CONFIG_PRODUCTS -#undef X -} - -#undef DD_LIST_REMOTE_CONFIG_PRODUCTS -#undef DD_QUOTED_IMPL -#undef DD_QUOTED - } // namespace remote_config } // namespace datadog diff --git a/src/datadog/remote_config/listener.h b/src/datadog/remote_config/listener.h index 2d502853..3a3496bd 100644 --- a/src/datadog/remote_config/listener.h +++ b/src/datadog/remote_config/listener.h @@ -4,6 +4,7 @@ #include "capability.h" #include "optional.h" +#include "product.h" namespace datadog { namespace remote_config { diff --git a/src/datadog/remote_config/remote_config.cpp b/src/datadog/remote_config/remote_config.cpp index 141967da..bcf8bf4e 100644 --- a/src/datadog/remote_config/remote_config.cpp +++ b/src/datadog/remote_config/remote_config.cpp @@ -175,7 +175,7 @@ void Manager::process_response(const nlohmann::json& json) { } // Keep track of config path received to know which ones to revert. - std::unordered_set visited_config; + std::unordered_set visited_config; for (const auto& client_config : *client_configs_it) { auto config_path = client_config.get(); From 423024471029e6be02f281ea7e2f134658d7f63d Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 20 Jun 2024 09:31:12 +0200 Subject: [PATCH 3/6] code review: remove DEFAULT value --- src/datadog/remote_config/capability.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/datadog/remote_config/capability.h b/src/datadog/remote_config/capability.h index 691c872e..221eeeb6 100644 --- a/src/datadog/remote_config/capability.h +++ b/src/datadog/remote_config/capability.h @@ -16,8 +16,6 @@ using Capabilities = uint64_t; namespace capability { enum Flag : Capabilities { - // DEFAULT is a special value. It is the default of the product capability. - DEFAULT = 0, ASM_ACTIVATION = 1 << 1, ASM_IP_BLOCKING = 1 << 2, ASM_DD_RULES = 1 << 3, From d547482a2d932db2a50043836caf8353aa4ae12b Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 20 Jun 2024 11:42:18 +0200 Subject: [PATCH 4/6] code review --- src/datadog/config_manager.cpp | 2 +- src/datadog/config_manager.h | 2 +- src/datadog/datadog_agent.cpp | 2 +- src/datadog/error.h | 1 + src/datadog/remote_config/listener.h | 1 - src/datadog/remote_config/product.h | 88 +++++++++++++++++++++ src/datadog/remote_config/remote_config.cpp | 50 ++++++++---- src/datadog/remote_config/remote_config.h | 7 +- test/remote_config/test_remote_config.cpp | 13 ++- test/test_config_manager.cpp | 1 - 10 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 src/datadog/remote_config/product.h diff --git a/src/datadog/config_manager.cpp b/src/datadog/config_manager.cpp index 557917f5..2609739b 100644 --- a/src/datadog/config_manager.cpp +++ b/src/datadog/config_manager.cpp @@ -125,7 +125,7 @@ rc::Capabilities ConfigManager::get_capabilities() { using namespace rc::capability; return APM_TRACING_SAMPLE_RATE | APM_TRACING_TAGS | APM_TRACING_ENABLED | APM_TRACING_SAMPLE_RULES; -}; +} Optional ConfigManager::on_update(const Configuration& config) { const auto config_json = nlohmann::json::parse(config.content); diff --git a/src/datadog/config_manager.h b/src/datadog/config_manager.h index 1451cd15..6c59fa74 100644 --- a/src/datadog/config_manager.h +++ b/src/datadog/config_manager.h @@ -20,7 +20,7 @@ namespace tracing { class ConfigManager : public remote_config::Listener { public: - // The `ConfigUpdate` struct serves as a container for configuration that can + // The `Update` struct serves as a container for configuration that can // exclusively be changed remotely. // // Configurations can be `nullopt` to signal the absence of a value from the diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index 707bf707..ad15ec78 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -164,7 +164,7 @@ DatadogAgent::DatadogAgent( flush_interval_(config.flush_interval), request_timeout_(config.request_timeout), shutdown_timeout_(config.shutdown_timeout), - remote_config_(tracer_signature, rc_listeners), + remote_config_(tracer_signature, rc_listeners, logger), tracer_signature_(tracer_signature) { assert(logger_); assert(tracer_telemetry_); diff --git a/src/datadog/error.h b/src/datadog/error.h index 16bfa63c..fdc0dc33 100644 --- a/src/datadog/error.h +++ b/src/datadog/error.h @@ -75,6 +75,7 @@ struct Error { DATADOG_AGENT_INVALID_SHUTDOWN_TIMEOUT = 50, DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL = 51, SAMPLING_DELEGATION_RESPONSE_INVALID_JSON = 52, + REMOTE_CONFIGURATION_INVALID_INPUT = 53, }; Code code; diff --git a/src/datadog/remote_config/listener.h b/src/datadog/remote_config/listener.h index 3a3496bd..2a29e26b 100644 --- a/src/datadog/remote_config/listener.h +++ b/src/datadog/remote_config/listener.h @@ -19,7 +19,6 @@ class Listener { struct Configuration { std::string id; std::string path; - std::string hash; std::string content; std::size_t version; product::Flag product; diff --git a/src/datadog/remote_config/product.h b/src/datadog/remote_config/product.h new file mode 100644 index 00000000..c9489fb1 --- /dev/null +++ b/src/datadog/remote_config/product.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include "string_util.h" +#include "string_view.h" + +namespace datadog { +namespace remote_config { + +// Type alias for product flags. +// +// Usage: +// +// using namespace datadog::remote_config::product; +// Products p = AGENT_CONFIG | APM_TRACING; +using Products = uint64_t; + +#define DD_QUOTED_IMPL(ARG) #ARG +#define DD_QUOTED(ARG) DD_QUOTED_IMPL(ARG) + +// List of remote configuration product built to support flag arithmetic +#define DD_LIST_REMOTE_CONFIG_PRODUCTS \ + X(UNKNOWN, 0) \ + X(AGENT_CONFIG, 1) \ + X(AGENT_TASK, 2) \ + X(APM_TRACING, 3) \ + X(LIVE_DEBUGGING, 4) \ + X(LIVE_DEBUGGING_SYMBOL_DB, 5) \ + X(ASM, 6) \ + X(ASM_DD, 7) \ + X(ASM_DATA, 8) \ + X(ASM_FEATURES, 9) + +namespace product { + +enum Flag : Products { +#define X(NAME, ID) NAME = 1 << ID, + DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef X +}; +} // namespace product + +inline tracing::StringView to_string_view(product::Flag product) { +#define X(NAME, ID) \ + case product::Flag::NAME: { \ + return DD_QUOTED(NAME); \ + } + switch (product) { DD_LIST_REMOTE_CONFIG_PRODUCTS } +#undef X + + assert(true); + return "UNKNOWN"; +} + +inline product::Flag parse_product(tracing::StringView sv) { + const auto upcase_product = tracing::to_upper(sv); + +#define X(NAME, ID) \ + if (upcase_product == DD_QUOTED(NAME)) { \ + return product::Flag::NAME; \ + } + DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef X + + assert(true); + return product::Flag::UNKNOWN; +} + +inline void visit_products(Products products, + std::function on_product) { +#define X(NAME, ID) \ + if (products & product::Flag::NAME) { \ + on_product(product::Flag::NAME); \ + } + + DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef X +} + +#undef DD_LIST_REMOTE_CONFIG_PRODUCTS +#undef DD_QUOTED_IMPL +#undef DD_QUOTED + +} // namespace remote_config +} // namespace datadog diff --git a/src/datadog/remote_config/remote_config.cpp b/src/datadog/remote_config/remote_config.cpp index bcf8bf4e..d983656b 100644 --- a/src/datadog/remote_config/remote_config.cpp +++ b/src/datadog/remote_config/remote_config.cpp @@ -52,9 +52,11 @@ Optional parse_config_path(StringView config_path) { } // namespace Manager::Manager(const TracerSignature& tracer_signature, - const std::vector>& listeners) + const std::vector>& listeners, + const std::shared_ptr& logger) : tracer_signature_(tracer_signature), listeners_(listeners), + logger_(logger), client_id_(uuid()) { Capabilities capabilities = 0; @@ -89,6 +91,11 @@ bool Manager::is_new_config(StringView config_path, config_meta.at("/hashes/sha256"_json_pointer).get(); } +void Manager::error(std::string message) { + logger_->log_error(Error{Error::REMOTE_CONFIGURATION_INVALID_INPUT, message}); + state_.error_message = std::move(message); +} + nlohmann::json Manager::make_request_payload() { // clang-format off auto j = nlohmann::json{ @@ -113,8 +120,15 @@ nlohmann::json Manager::make_request_payload() { }; // clang-format on + if (state_.error_message) { + j["client"]["state"]["has_error"] = true; + j["client"]["state"]["error"] = *state_.error_message; + } + if (!applied_config_.empty()) { auto config_states = nlohmann::json::array(); + auto cached_target_files = nlohmann::json::array(); + for (const auto& [_, config] : applied_config_) { nlohmann::json config_state = { {"id", config.id}, @@ -128,16 +142,19 @@ nlohmann::json Manager::make_request_payload() { } config_states.emplace_back(std::move(config_state)); + + nlohmann::json cached_file = { + {"path", config.path}, + {"length", config.content.size()}, + {"hashes", {{"algorithm", "sha256"}, {"hash", config.hash}}}}; + + cached_target_files.emplace_back(std::move(cached_file)); } + j["cached_target"] = cached_target_files; j["client"]["state"]["config_states"] = config_states; } - if (state_.error_message) { - j["client"]["state"]["has_error"] = true; - j["client"]["state"]["error"] = *state_.error_message; - } - return j; } @@ -183,10 +200,10 @@ void Manager::process_response(const nlohmann::json& json) { const auto product = parse_config_path(config_path); if (!product) { - std::string error_message{config_path}; - error_message += " is an invalid configuration path"; + std::string reason{config_path}; + reason += " is an invalid configuration path"; - state_.error_message = std::move(error_message); + error(reason); return; } @@ -205,18 +222,17 @@ void Manager::process_response(const nlohmann::json& json) { }); if (target_it == target_files.cend()) { - std::string error_message{"Missing configuration \""}; - append(error_message, config_path); - error_message += "\" from Remote Configuration response"; + std::string reason{"Target \""}; + append(reason, config_path); + reason += "\" missing from the list of targets"; - state_.error_message = std::move(error_message); + error(reason); return; } auto decoded_config = base64_decode(target_it.value().at("raw").get()); - // TODO: try to remove the following line const auto config_json = nlohmann::json::parse(decoded_config); Configuration new_config; @@ -269,10 +285,10 @@ void Manager::process_response(const nlohmann::json& json) { listener->on_post_process(); } } catch (const nlohmann::json::exception& e) { - std::string error_message = "Invalid Remote Configuration response: "; - error_message += e.what(); + std::string reason = "Failed to parse the response: "; + reason += e.what(); - state_.error_message = std::move(error_message); + error(reason); } } diff --git a/src/datadog/remote_config/remote_config.h b/src/datadog/remote_config/remote_config.h index d7190073..cf8b9d20 100644 --- a/src/datadog/remote_config/remote_config.h +++ b/src/datadog/remote_config/remote_config.h @@ -47,11 +47,13 @@ class Manager { error = 3 } state = State::unacknowledged; + std::string hash; tracing::Optional error_message; }; tracing::TracerSignature tracer_signature_; std::vector> listeners_; + std::shared_ptr logger_; std::set products_; std::unordered_map> listeners_per_product_; @@ -63,7 +65,8 @@ class Manager { public: Manager(const tracing::TracerSignature& tracer_signature, - const std::vector>& listeners); + const std::vector>& listeners, + const std::shared_ptr& logger); // Construct a JSON object representing the payload to be sent in a remote // configuration request. @@ -77,6 +80,8 @@ class Manager { // Tell if a `config_path` is a new configuration update. bool is_new_config(tracing::StringView config_path, const nlohmann::json& config_meta); + + void error(std::string message); }; } // namespace remote_config diff --git a/test/remote_config/test_remote_config.cpp b/test/remote_config/test_remote_config.cpp index a5e0f6d9..48d90c80 100644 --- a/test/remote_config/test_remote_config.cpp +++ b/test/remote_config/test_remote_config.cpp @@ -1,6 +1,7 @@ #include "catch.hpp" #include "datadog/json.hpp" #include "datadog/remote_config/remote_config.h" +#include "mocks/loggers.h" namespace rc = datadog::remote_config; using namespace datadog::tracing; @@ -37,6 +38,9 @@ struct FakeListener : public rc::Listener { void on_post_process() override { ++count_on_post_process; } }; + +auto logger = std::make_shared(); + } // namespace REMOTE_CONFIG_TEST("initial state payload") { @@ -61,7 +65,7 @@ REMOTE_CONFIG_TEST("initial state payload") { "APM_TRACING", "ASM", "ASM_DATA", "ASM_DD", "ASM_FEATURES"}; const std::vector expected_capabilities{0, 0, 0, 0, 0, 0, 145, 2}; - rc::Manager rc(tracer_signature, {tracing_listener, asm_listener}); + rc::Manager rc(tracer_signature, {tracing_listener, asm_listener}, logger); const auto payload = rc.make_request_payload(); @@ -176,7 +180,7 @@ REMOTE_CONFIG_TEST("response processing") { /* allow_exceptions = */ false); REQUIRE(!response_json.is_discarded()); - rc::Manager rc(tracer_signature, {}); + rc::Manager rc(tracer_signature, {}, logger); rc.process_response(response_json); @@ -231,7 +235,7 @@ REMOTE_CONFIG_TEST("response processing") { auto tracing_listener = std::make_shared(); tracing_listener->products = rc::product::APM_TRACING; - rc::Manager rc(tracer_signature, {tracing_listener}); + rc::Manager rc(tracer_signature, {tracing_listener}, logger); rc.process_response(response_json); CHECK(tracing_listener->count_on_update == 0); @@ -296,7 +300,8 @@ REMOTE_CONFIG_TEST("response processing") { return "test error message"; }); - rc::Manager rc(tracer_signature, {tracing_listener, agent_listener}); + rc::Manager rc(tracer_signature, {tracing_listener, agent_listener}, + logger); rc.process_response(response_json); CHECK(tracing_listener->count_on_update == 1); diff --git a/test/test_config_manager.cpp b/test/test_config_manager.cpp index a286108e..aa2f9f5c 100644 --- a/test/test_config_manager.cpp +++ b/test/test_config_manager.cpp @@ -33,7 +33,6 @@ CONFIG_MANAGER_TEST("remote configuration handling") { rc::Listener::Configuration config_update{/* id = */ "id", /* path = */ "", - /* hash = */ "", /* content = */ "", /* version = */ 1, rc::product::Flag::APM_TRACING}; From edd5397dc1140322035c7f9af2f4f45e3584cf2c Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 20 Jun 2024 12:13:46 +0200 Subject: [PATCH 5/6] fix compilation --- src/datadog/remote_config/remote_config.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/datadog/remote_config/remote_config.cpp b/src/datadog/remote_config/remote_config.cpp index d983656b..368679f4 100644 --- a/src/datadog/remote_config/remote_config.cpp +++ b/src/datadog/remote_config/remote_config.cpp @@ -192,7 +192,7 @@ void Manager::process_response(const nlohmann::json& json) { } // Keep track of config path received to know which ones to revert. - std::unordered_set visited_config; + std::unordered_set visited_config; for (const auto& client_config : *client_configs_it) { auto config_path = client_config.get(); @@ -284,11 +284,13 @@ void Manager::process_response(const nlohmann::json& json) { for (const auto& listener : listeners_) { listener->on_post_process(); } - } catch (const nlohmann::json::exception& e) { + } catch (const nlohmann::json::exception& json_exception) { std::string reason = "Failed to parse the response: "; - reason += e.what(); + reason += json_exception.what(); error(reason); + } catch (const std::exception& e) { + error(e.what()); } } From 2572cab19cf2d08275fdaac579fcac13935d71c3 Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Tue, 2 Jul 2024 14:14:00 +0200 Subject: [PATCH 6/6] fix compilation --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 739aa3c4..2fd739bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,8 +205,9 @@ target_sources(dd_trace_cpp-objects PUBLIC # or installing the library. target_include_directories(dd_trace_cpp-objects PUBLIC - $ + $ $ + $ ) target_link_libraries(dd_trace_cpp-objects