diff --git a/BUILD.bazel b/BUILD.bazel index 85010c9a..90ab5157 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,10 @@ 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/product.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 +120,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 fbd57b4e..2fd739bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -204,12 +205,11 @@ target_sources(dd_trace_cpp-objects PUBLIC # or installing the library. target_include_directories(dd_trace_cpp-objects PUBLIC - $ + $ $ + $ ) -add_dependencies(dd_trace_cpp-objects Threads::Threads) - target_link_libraries(dd_trace_cpp-objects PUBLIC Threads::Threads diff --git a/src/datadog/config_manager.cpp b/src/datadog/config_manager.cpp index 8a453f36..2609739b 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..6c59fa74 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 `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 + // 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..ad15ec78 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, logger), 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/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.cpp b/src/datadog/remote_config.cpp deleted file mode 100644 index a2a4489f..00000000 --- a/src/datadog/remote_config.cpp +++ /dev/null @@ -1,277 +0,0 @@ -#include "remote_config.h" - -#include -#include -#include -#include - -#include "base64.h" -#include "json.hpp" -#include "random.h" -#include "string_view.h" -#include "version.h" - -using namespace nlohmann::literals; - -namespace datadog { -namespace tracing { -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; - std::array res{}; - for (std::size_t i = 0; i < sizeof(in); ++i) { - res[j--] = static_cast(in >> (i * 8)); - } - - 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/"; - -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(); - } - - 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 - -RemoteConfigurationManager::RemoteConfigurationManager( - const TracerSignature& tracer_signature, - const std::shared_ptr& config_manager) - : tracer_signature_(tracer_signature), - config_manager_(config_manager), - client_id_(uuid()) { - assert(config_manager_); -} - -bool RemoteConfigurationManager::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; - - return it->second.hash != - config_meta.at("/hashes/sha256"_json_pointer).get(); -} - -nlohmann::json RemoteConfigurationManager::make_request_payload() { - // clang-format off - auto j = nlohmann::json{ - {"client", { - {"id", client_id_}, - {"products", nlohmann::json::array({k_apm_product})}, - {"is_tracer", true}, - {"capabilities", k_apm_capabilities}, - {"client_tracer", { - {"runtime_id", tracer_signature_.runtime_id.string()}, - {"language", tracer_signature_.library_language}, - {"tracer_version", tracer_signature_.library_version}, - {"service", tracer_signature_.default_service}, - {"env", tracer_signature_.default_environment} - }}, - {"state", { - {"root_version", 1}, - {"targets_version", state_.targets_version}, - {"backend_client_state", state_.opaque_backend_state} - }} - }} - }; - // clang-format on - - if (!applied_config_.empty()) { - auto config_states = nlohmann::json::array(); - for (const auto& [_, config] : applied_config_) { - nlohmann::json config_state = { - {"id", config.id}, - {"version", config.version}, - {"product", k_apm_product}, - {"apply_state", config.state}, - }; - - if (config.error_message) { - config_state["apply_error"] = *config.error_message; - } - - config_states.emplace_back(std::move(config_state)); - } - - 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; -} - -std::vector RemoteConfigurationManager::process_response( - const nlohmann::json& json) { - std::vector config_updates; - config_updates.reserve(8); - - state_.error_message = nullopt; - - try { - const auto targets = nlohmann::json::parse( - base64_decode(json.at("targets").get())); - - state_.targets_version = targets.at("/signed/version"_json_pointer); - state_.opaque_backend_state = - targets.at("/signed/custom/opaque_backend_state"_json_pointer); - - const auto client_configs_it = json.find("client_configs"); - - // `client_configs` is absent => remove previously applied configuration if - // any applied. - 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()); - }); - applied_config_.clear(); - } - return config_updates; - } - - // 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& 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)) { - continue; - } - - const auto& target_files = json.at("/target_files"_json_pointer); - auto target_it = std::find_if( - target_files.cbegin(), target_files.cend(), - [&config_path](const nlohmann::json& j) { - return j.at("/path"_json_pointer).get() == config_path; - }); - - 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; - } - - const auto config_json = nlohmann::json::parse( - base64_decode(target_it.value().at("raw").get())); - - Configuration new_config; - new_config.id = config_json.at("id"); - new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer); - new_config.version = config_json.at("revision"); - - const auto& targeted_service = config_json.at("service_target"); - if (targeted_service.at("service").get() != - tracer_signature_.default_service || - targeted_service.at("env").get() != - tracer_signature_.default_environment) { - 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()); - } - - applied_config_[std::string{config_path}] = new_config; - } - - // Applied configuration not present must be reverted. - 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()); - it = applied_config_.erase(it); - } else { - it++; - } - } - } catch (const nlohmann::json::exception& e) { - std::string error_message = "Ill-formatted 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 datadog diff --git a/src/datadog/remote_config/capability.h b/src/datadog/remote_config/capability.h new file mode 100644 index 00000000..221eeeb6 --- /dev/null +++ b/src/datadog/remote_config/capability.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +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; + +namespace capability { + +enum Flag : Capabilities { + 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 + +} // 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..2a29e26b --- /dev/null +++ b/src/datadog/remote_config/listener.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include "capability.h" +#include "optional.h" +#include "product.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 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/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 new file mode 100644 index 00000000..368679f4 --- /dev/null +++ b/src/datadog/remote_config/remote_config.cpp @@ -0,0 +1,298 @@ +#include "remote_config.h" + +#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" + +using namespace datadog::tracing; +using namespace nlohmann::literals; + +namespace datadog { +namespace remote_config { +namespace { + +constexpr std::array capabilities_byte_array( + uint64_t in) { + std::size_t j = sizeof(in) - 1; + std::array res{}; + for (std::size_t i = 0; i < sizeof(in); ++i) { + res[j--] = static_cast(in >> (i * 8)); + } + + return res; +} + +Optional parse_config_path(StringView config_path) { + static const std::regex path_reg( + "^(datadog/\\d+|employee)/([^/]+)/[^/]+/[^/]+$"); + + std::cmatch match; + if (!std::regex_match(config_path.data(), + config_path.data() + config_path.size(), match, + path_reg)) { + return nullopt; + } + + assert(match.ready()); + assert(match.size() == 3); + + StringView product_sv(config_path.data() + match.position(2), + std::size_t(match.length(2))); + + return parse_product(product_sv); +} + +} // namespace + +Manager::Manager(const TracerSignature& tracer_signature, + const std::vector>& listeners, + const std::shared_ptr& logger) + : tracer_signature_(tracer_signature), + listeners_(listeners), + logger_(logger), + client_id_(uuid()) { + 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 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; + + return it->second.hash != + 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{ + {"client", { + {"id", client_id_}, + {"products", products_}, + {"is_tracer", true}, + {"capabilities", capabilities_}, + {"client_tracer", { + {"runtime_id", tracer_signature_.runtime_id.string()}, + {"language", tracer_signature_.library_language}, + {"tracer_version", tracer_signature_.library_version}, + {"service", tracer_signature_.default_service}, + {"env", tracer_signature_.default_environment} + }}, + {"state", { + {"root_version", 1}, + {"targets_version", state_.targets_version}, + {"backend_client_state", state_.opaque_backend_state} + }} + }} + }; + // 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}, + {"version", config.version}, + {"product", to_string_view(config.product)}, + {"apply_state", config.state}, + }; + + if (config.error_message) { + config_state["apply_error"] = *config.error_message; + } + + 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; + } + + return j; +} + +void Manager::process_response(const nlohmann::json& json) { + state_.error_message = nullopt; + + try { + const auto targets = nlohmann::json::parse( + base64_decode(json.at("targets").get())); + + state_.targets_version = targets.at("/signed/version"_json_pointer); + state_.opaque_backend_state = + targets.at("/signed/custom/opaque_backend_state"_json_pointer); + + const auto client_configs_it = json.find("client_configs"); + + // `client_configs` is absent => remove previously applied configuration if + // any applied. + if (client_configs_it == json.cend()) { + if (!applied_config_.empty()) { + std::for_each(applied_config_.cbegin(), applied_config_.cend(), + [this](const auto& it) { + for (const auto& listener : + listeners_per_product_[it.second.product]) { + listener->on_revert(it.second); + } + }); + applied_config_.clear(); + } + + 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; + + 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 reason{config_path}; + reason += " is an invalid configuration path"; + + error(reason); + return; + } + + const auto& config_metadata = + targets.at("/signed/targets"_json_pointer).at(config_path); + + if (!is_new_config(config_path, config_metadata)) { + continue; + } + + const auto& target_files = json.at("/target_files"_json_pointer); + auto target_it = std::find_if( + target_files.cbegin(), target_files.cend(), + [&config_path](const nlohmann::json& j) { + return j.at("/path"_json_pointer).get() == config_path; + }); + + if (target_it == target_files.cend()) { + std::string reason{"Target \""}; + append(reason, config_path); + reason += "\" missing from the list of targets"; + + error(reason); + return; + } + + auto decoded_config = + base64_decode(target_it.value().at("raw").get()); + + 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() != + tracer_signature_.default_service || + targeted_service.at("env").get() != + tracer_signature_.default_environment) { + new_config.state = Configuration::State::error; + new_config.error_message = "Wrong service targeted"; + } else { + 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; + } + + // Revert applied configurations not present + for (auto it = applied_config_.cbegin(); it != applied_config_.cend();) { + if (!visited_config.count(it->first)) { + 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& json_exception) { + std::string reason = "Failed to parse the response: "; + reason += json_exception.what(); + + error(reason); + } catch (const std::exception& e) { + error(e.what()); + } +} + +} // namespace remote_config +} // namespace datadog diff --git a/src/datadog/remote_config.h b/src/datadog/remote_config/remote_config.h similarity index 62% rename from src/datadog/remote_config.h rename to src/datadog/remote_config/remote_config.h index d822fe58..cf8b9d20 100644 --- a/src/datadog/remote_config.h +++ b/src/datadog/remote_config/remote_config.h @@ -12,57 +12,61 @@ // 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; + std::string hash; + tracing::Optional error_message; }; - TracerSignature tracer_signature_; - std::shared_ptr config_manager_; + tracing::TracerSignature tracer_signature_; + std::vector> listeners_; + std::shared_ptr logger_; + 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, + const std::shared_ptr& logger); // Construct a JSON object representing the payload to be sent in a remote // configuration request. @@ -70,18 +74,15 @@ 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); + bool is_new_config(tracing::StringView config_path, + const nlohmann::json& config_meta); - // Revert a remote configuration. - std::vector revert_config(Configuration config); + void error(std::string message); }; -} // 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 ebc37bf6..c18f35e2 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 47adbaf1..e1e6c49e 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..48d90c80 --- /dev/null +++ b/test/remote_config/test_remote_config.cpp @@ -0,0 +1,457 @@ +#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; + +#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; } +}; + +auto logger = std::make_shared(); + +} // 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}, logger); + + 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, {}, logger); + + 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}, logger); + 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}, + logger); + 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..aa2f9f5c --- /dev/null +++ b/test/test_config_manager.cpp @@ -0,0 +1,187 @@ +#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 = */ "", + /* 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"] ==