diff --git a/cmake/compiler/clang.cmake b/cmake/compiler/clang.cmake index c726e637..7e194e45 100644 --- a/cmake/compiler/clang.cmake +++ b/cmake/compiler/clang.cmake @@ -97,7 +97,7 @@ else () # and so won't link to libc++. So, if any of the FUZZ_* variables are set, # keep to libstdc++ (the default on most systems). message(STATUS "C++ standard library: libc++") - add_compile_options(-stdlib=libc++) + add_compile_options($<$:-stdlib=libc++>) add_link_options(-stdlib=libc++) endif() diff --git a/src/datadog/config_update.h b/src/datadog/config_update.h index d16fe92b..07a8fc86 100644 --- a/src/datadog/config_update.h +++ b/src/datadog/config_update.h @@ -16,7 +16,7 @@ namespace tracing { struct ConfigUpdate { Optional report_traces; Optional trace_sampling_rate; - Optional> tags; + Optional> tags; const nlohmann::json* trace_sampling_rules = nullptr; }; diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index 507f69dd..82966720 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -146,7 +146,7 @@ std::variant parse_agent_traces_response( } // namespace DatadogAgent::DatadogAgent( - const FinalizedDatadogAgentConfig& config, + FinalizedDatadogAgentConfig& config, const std::shared_ptr& tracer_telemetry, const std::shared_ptr& logger, const TracerSignature& tracer_signature, @@ -162,7 +162,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, config_manager, logger), tracer_signature_(tracer_signature) { assert(logger_); assert(tracer_telemetry_); @@ -207,6 +207,15 @@ DatadogAgent::DatadogAgent( } if (config.remote_configuration_enabled) { + for (auto&& l : config.rem_cfg_listeners) { + remote_config_.add_listener(std::move(l)); + } + config.rem_cfg_listeners.clear(); + for (auto&& l : config.rem_cfg_end_listeners) { + remote_config_.add_config_end_listener(std::move(l)); + } + config.rem_cfg_end_listeners.clear(); + tasks_.emplace_back(event_scheduler_->schedule_recurring_event( config.remote_configuration_poll_interval, [this] { get_and_apply_remote_configuration_updates(); })); @@ -445,7 +454,7 @@ void DatadogAgent::get_and_apply_remote_configuration_updates() { return; } - const auto response_json = + auto response_json = nlohmann::json::parse(/* input = */ response_body, /* parser_callback = */ nullptr, /* allow_exceptions = */ false); @@ -458,9 +467,9 @@ void DatadogAgent::get_and_apply_remote_configuration_updates() { if (!response_json.empty()) { auto updated_configuration = - remote_config_.process_response(response_json); + remote_config_.process_response(std::move(response_json)); if (!updated_configuration.empty()) { - send_configuration_change(updated_configuration); + send_configuration_change(std::move(updated_configuration)); } } }; diff --git a/src/datadog/datadog_agent.h b/src/datadog/datadog_agent.h index 592dc71b..0f8200e9 100644 --- a/src/datadog/datadog_agent.h +++ b/src/datadog/datadog_agent.h @@ -64,7 +64,7 @@ class DatadogAgent : public Collector { void send_app_closing(); public: - DatadogAgent(const FinalizedDatadogAgentConfig&, + DatadogAgent(FinalizedDatadogAgentConfig&, const std::shared_ptr&, const std::shared_ptr&, const TracerSignature& id, const std::shared_ptr& config_manager); diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index 6049a823..c2c52138 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -48,8 +48,8 @@ Expected load_datadog_agent_env_config() { } Expected finalize_config( - const DatadogAgentConfig& user_config, - const std::shared_ptr& logger, const Clock& clock) { + DatadogAgentConfig& user_config, const std::shared_ptr& logger, + const Clock& clock) { Expected env_config = load_datadog_agent_env_config(); if (auto error = env_config.if_error()) { return *error; @@ -141,6 +141,9 @@ Expected finalize_config( result.metadata[ConfigName::AGENT_URL] = ConfigMetadata(ConfigName::AGENT_URL, url, origin); + result.rem_cfg_listeners = std::move(user_config.rem_cfg_listeners); + result.rem_cfg_end_listeners = std::move(user_config.rem_cfg_end_listeners); + return result; } diff --git a/src/datadog/datadog_agent_config.h b/src/datadog/datadog_agent_config.h index 67559aee..79b02657 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.h" #include "string_view.h" namespace datadog { @@ -63,12 +64,20 @@ struct DatadogAgentConfig { // updates. Optional remote_configuration_poll_interval_seconds; + // Remote configuration listeners to add + std::vector> + rem_cfg_listeners; + + // Callbacks invoked after all the configuration listeners for all products + // have been called, as long as at least one has. + std::vector> rem_cfg_end_listeners; + static Expected parse(StringView); }; class FinalizedDatadogAgentConfig { friend Expected finalize_config( - const DatadogAgentConfig&, const std::shared_ptr&, const Clock&); + DatadogAgentConfig&, const std::shared_ptr&, const Clock&); FinalizedDatadogAgentConfig() = default; @@ -83,10 +92,13 @@ class FinalizedDatadogAgentConfig { std::chrono::steady_clock::duration shutdown_timeout; std::chrono::steady_clock::duration remote_configuration_poll_interval; std::unordered_map metadata; + std::vector> + rem_cfg_listeners; + std::vector> rem_cfg_end_listeners; }; Expected finalize_config( - const DatadogAgentConfig& config, const std::shared_ptr& logger, + DatadogAgentConfig& config, const std::shared_ptr& logger, const Clock& clock); } // namespace tracing diff --git a/src/datadog/error.h b/src/datadog/error.h index 16bfa63c..a8288e67 100644 --- a/src/datadog/error.h +++ b/src/datadog/error.h @@ -77,7 +77,7 @@ struct Error { SAMPLING_DELEGATION_RESPONSE_INVALID_JSON = 52, }; - Code code; + Code code{}; std::string message; Error with_prefix(StringView) const; diff --git a/src/datadog/logger.h b/src/datadog/logger.h index bd5c5400..0e7fc233 100644 --- a/src/datadog/logger.h +++ b/src/datadog/logger.h @@ -60,6 +60,12 @@ class Logger { virtual void log_error(const Error&); virtual void log_error(StringView); + + // optional operations for debug logging messages + virtual void log_debug(const LogFunc&) {} + virtual void log_debug(StringView message) { + log_debug([message](auto& stream) { stream << message; }); + } }; } // namespace tracing diff --git a/src/datadog/parse_util.cpp b/src/datadog/parse_util.cpp index b444cd8d..d2d7333c 100644 --- a/src/datadog/parse_util.cpp +++ b/src/datadog/parse_util.cpp @@ -128,14 +128,15 @@ std::vector parse_list(StringView input) { return items; } +template Expected> parse_tags( - std::vector list) { + std::vector list) { std::unordered_map tags; std::string key; std::string value; - for (const StringView &token : list) { + for (const auto &token : list) { const auto separator = token.find(':'); if (separator == std::string::npos) { @@ -154,6 +155,14 @@ Expected> parse_tags( return tags; } +Expected> parse_tags( + const std::vector &list) { + return parse_tags(list); +} +Expected> parse_tags( + const std::vector &list) { + return parse_tags(list); +} // This function scans the input string to identify a separator (',' or ' '). // Then, split tags using the identified separator and call `parse_tags` with diff --git a/src/datadog/parse_util.h b/src/datadog/parse_util.h index c8e4e83c..9893c5cb 100644 --- a/src/datadog/parse_util.h +++ b/src/datadog/parse_util.h @@ -13,6 +13,12 @@ namespace datadog { namespace tracing { +// Return a `string_view` over the specified range of characters `[begin, end)`. +template +StringView range(Iterator begin, Iterator end) { + return StringView{&*begin, std::size_t(end - begin)}; +} + bool falsy(StringView input); // Return a non-negative integer parsed from the specified `input` with respect @@ -37,7 +43,9 @@ Expected parse_double(StringView input); std::vector parse_list(StringView input); Expected> parse_tags( - std::vector list); + const std::vector& list); +Expected> parse_tags( + const std::vector& list); Expected> parse_tags( StringView input); diff --git a/src/datadog/remote_config.cpp b/src/datadog/remote_config.cpp index a2a4489f..4289eaf8 100644 --- a/src/datadog/remote_config.cpp +++ b/src/datadog/remote_config.cpp @@ -1,11 +1,16 @@ #include "remote_config.h" #include -#include +#include +#include +#include +#include #include #include #include "base64.h" +#include "config_manager.h" +#include "config_update.h" #include "json.hpp" #include "random.h" #include "string_view.h" @@ -13,10 +18,25 @@ using namespace nlohmann::literals; -namespace datadog { -namespace tracing { +namespace datadog::tracing { + +inline StringView operator""_sv(const char* str, size_t len) noexcept { + return StringView{str, len}; +} + namespace { +template +nlohmann::json::array_t subscribed_products_to_json( + const std::unordered_map& products) { + nlohmann::json::array_t res; + res.reserve(products.size()); + for (auto&& [p, _] : products) { + res.emplace_back(p.name()); + } + return res; +} + // The ".client.capabilities" field of the remote config request payload // describes which parts of the library's configuration are supported for remote // configuration. @@ -27,17 +47,10 @@ namespace { // 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) { +constexpr std::array +capabilities_byte_array(uint64_t in) { std::size_t j = sizeof(in) - 1; - std::array res{}; + std::array res{}; for (std::size_t i = 0; i < sizeof(in); ++i) { res[j--] = static_cast(in >> (i * 8)); } @@ -45,58 +58,124 @@ constexpr std::array capabilities_byte_array( return res; } -constexpr std::array k_apm_capabilities = - capabilities_byte_array(APM_TRACING_SAMPLE_RATE | APM_TRACING_TAGS | - APM_TRACING_ENABLED | APM_TRACING_SAMPLE_RULES); +template +auto subscribed_capabilities( + const std::unordered_map& + product_states) { + remote_config::CapabilitiesSet cap{}; + for (auto&& [_, pstate] : product_states) { + cap |= pstate.subscribed_capabilities(); + } + return capabilities_byte_array(cap.value()); +} -constexpr StringView k_apm_product = "APM_TRACING"; -constexpr StringView k_apm_product_path_substring = "/APM_TRACING/"; +// Built-in, subscribed-by-default APM_TRACING product listener. +// It builds a ConfigUpdate from the remote config response and calls +// ConfigManager::update with it +class TracingProductListener : public remote_config::ProductListener { + public: + TracingProductListener(const TracerSignature& tracer_signature, + std::shared_ptr config_manager) + : remote_config:: + ProductListener{remote_config::Product::KnownProducts::APM_TRACING}, + tracer_signature_(tracer_signature), + config_manager_(std::move(config_manager)) {} + + void on_config_update(const remote_config::ParsedConfigKey& key, + const std::string& file_contents, + std::vector& config_update) override { + const auto config_json = nlohmann::json::parse(file_contents); + + if (!service_env_match(config_json)) { + return; + } -ConfigUpdate parse_dynamic_config(const nlohmann::json& j) { - ConfigUpdate config_update; + ConfigUpdate const dyn_config = + parse_dynamic_config(config_json.at("lib_config"_sv)); - if (auto sampling_rate_it = j.find("tracing_sampling_rate"); - sampling_rate_it != j.cend() && sampling_rate_it->is_number()) { - config_update.trace_sampling_rate = sampling_rate_it->get(); + std::vector metadata = config_manager_->update(dyn_config); + config_update.insert(config_update.end(), metadata.begin(), metadata.end()); + applied_configs_.emplace(key); } - if (auto tags_it = j.find("tracing_tags"); - tags_it != j.cend() && tags_it->is_array()) { - config_update.tags = tags_it->get>(); + void on_config_remove(const remote_config::ParsedConfigKey& key, + std::vector& config_updates) override { + if (applied_configs_.erase(key) > 0) { + std::vector metadata = config_manager_->reset(); + config_updates.insert(config_updates.end(), metadata.begin(), + metadata.end()); + } } - 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(); + remote_config::CapabilitiesSet capabilities() const override { + return { + remote_config::Capability::APM_TRACING_SAMPLE_RATE, + remote_config::Capability::APM_TRACING_CUSTOM_TAGS, + remote_config::Capability::APM_TRACING_TRACING_ENABLED, + remote_config::Capability::APM_TRACING_SAMPLE_RULES, + }; } - 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); + private: + bool service_env_match(const nlohmann::json& config_json) { + const auto& targeted_service = config_json.find("service_target"_sv); + if (targeted_service == config_json.cend() || + !targeted_service->is_object()) { + return false; + } + return targeted_service->at("service"_sv).get() == + tracer_signature_.default_service && + targeted_service->at("env"_sv).get() == + tracer_signature_.default_environment; } - return config_update; -} + static ConfigUpdate parse_dynamic_config(const nlohmann::json& j) { + ConfigUpdate config_update; + + if (auto sampling_rate_it = j.find("tracing_sampling_rate"_sv); + 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"_sv); + tags_it != j.cend() && tags_it->is_array()) { + config_update.tags = tags_it->get>(); + } + + if (auto tracing_enabled_it = j.find("tracing_enabled"_sv); + 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; + } + + const TracerSignature& tracer_signature_; // NOLINT + std::shared_ptr config_manager_; + std::unordered_set + applied_configs_; +}; } // namespace RemoteConfigurationManager::RemoteConfigurationManager( const TracerSignature& tracer_signature, - const std::shared_ptr& config_manager) + const std::shared_ptr& config_manager, + std::shared_ptr logger) : tracer_signature_(tracer_signature), config_manager_(config_manager), - client_id_(uuid()) { + client_id_(uuid()), + logger_{std::move(logger)} { 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(); + auto tracing_listener = std::make_unique( + tracer_signature, config_manager_); + add_listener(std::move(tracing_listener)); } nlohmann::json RemoteConfigurationManager::make_request_payload() { @@ -104,174 +183,594 @@ nlohmann::json RemoteConfigurationManager::make_request_payload() { auto j = nlohmann::json{ {"client", { {"id", client_id_}, - {"products", nlohmann::json::array({k_apm_product})}, + {"products", subscribed_products_to_json(product_states_)}, {"is_tracer", true}, - {"capabilities", k_apm_capabilities}, + {"capabilities", subscribed_capabilities(product_states_)}, {"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} + {"env", tracer_signature_.default_environment}, + // missing: tags, extra_services, app_version }}, {"state", { {"root_version", 1}, - {"targets_version", state_.targets_version}, - {"backend_client_state", state_.opaque_backend_state} + {"targets_version", next_client_state_.targets_version}, + {"config_states", serialize_config_states()}, + {"has_error", next_client_state_.error.has_value()}, + {"error", next_client_state_.error.value_or("")}, + {"backend_client_state", next_client_state_.backend_client_state}, }} - }} + }}, + {"cached_target_files", serialize_cached_target_files()}, }; // 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; - } + return j; +} + +nlohmann::json::array_t RemoteConfigurationManager::serialize_config_states() + const { + auto&& config_states_nonser = next_client_state_.config_states; + + nlohmann::json::array_t config_states{}; + config_states.reserve(config_states_nonser.size()); - config_states.emplace_back(std::move(config_state)); + for (const std::shared_ptr& cfg_state : + config_states_nonser) { + if (!cfg_state) { + continue; // should not happen } + const remote_config::ConfigState& cs = *cfg_state; + // clang-format off + config_states.emplace_back(nlohmann::json{ + {"id", cs.id}, + {"version", cs.version}, + {"product", cs.product.name()}, + {"apply_state", static_cast>(cs.apply_state)}, + {"apply_error", cs.apply_error}, + }); + // clang-format on + } + return config_states; +} - j["client"]["state"]["config_states"] = config_states; +nlohmann::json RemoteConfigurationManager::serialize_cached_target_files() + const { + nlohmann::json::array_t cached_target_files; + + for (auto&& [_, pstate] : product_states_) { + pstate.for_each_cached_target_file( + [&cached_target_files](const remote_config::CachedTargetFile& ctf) { + nlohmann::json::array_t hashes; + for (auto&& target_file_hash : ctf.hashes) { + hashes.emplace_back( + nlohmann::json{{"algorithm", target_file_hash.algorithm}, + {"hash", target_file_hash.hash}}); + } + cached_target_files.emplace_back(nlohmann::json{ + {"path", ctf.path}, + {"length", ctf.length}, + {"hashes", std::move(hashes)}, + }); + }); } - if (state_.error_message) { - j["client"]["state"]["has_error"] = true; - j["client"]["state"]["error"] = *state_.error_message; + if (cached_target_files.empty()) { + // system tests expect (expected?) a null in this case + // can't use {}-initialization (creates an array) + return nullptr; } - return j; + return cached_target_files; } std::vector RemoteConfigurationManager::process_response( - const nlohmann::json& json) { + nlohmann::json&& json) { + Optional resp; + std::vector errors; 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; + auto maybe_resp = + remote_config::RemoteConfigResponse::from_json(std::move(json)); + if (!maybe_resp) { + logger_->log_debug("Remote Configuration response is empty (no change)"); + return {}; } - // Keep track of config path received to know which ones to revert. - std::unordered_set visited_config; - visited_config.reserve(client_configs_it->size()); + logger_->log_debug("Got nonempty Remote Configuration response"); + resp.emplace(std::move(*maybe_resp)); + resp->validate(); + + // check if the backend returned configuration for unsubscribed products + for (const remote_config::ParsedConfigKey& key : resp->client_configs()) { + if (product_states_.find(key.product()) == product_states_.cend()) { + throw remote_config::reportable_error( + "Remote Configuration response contains unknown/unsubscribed " + "product: " + + std::string{key.product().name()}); + } + } - for (const auto& client_config : *client_configs_it) { - auto config_path = client_config.get(); - visited_config.emplace(config_path); + config_updates.reserve(8); + + bool applied_any = false; + // Apply the configuration is applied product-by-product. + // ProductState::apply will inspect the returned client_configs and process + // those that pertain to the product in question. + for (auto&& [product, pstate] : product_states_) { + try { + applied_any = pstate.apply(*resp, config_updates) || applied_any; + } catch (const remote_config::reportable_error& e) { + logger_->log_error(std::string{"Failed to apply configuration for "} + + std::string{product.name()} + ": " + e.what()); + errors.emplace_back(e.what()); + } catch (...) { + std::terminate(); + } + } - 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; + if (applied_any) { + // Now call the "end listeners", which are necessary because + // AppSec has its configuration split across different products + for (auto&& listener : config_end_listeners_) { + try { + listener(); + } catch (const std::exception& e) { + logger_->log_error( + std::string{ + "Failed to call Remote Configuration end listener: "} + + e.what()); + errors.emplace_back(e.what()); + } } + } - 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; - }); + } catch (const nlohmann::json::exception& e) { + std::string error_message = "Ill-formatted Remote Configuration response: "; + error_message += e.what(); + errors.emplace_back(std::move(error_message)); + } catch (const std::exception& e) { + errors.emplace_back(e.what()); + } + + if (resp) { + update_next_state({*resp}, build_error_message(errors)); + } else { + update_next_state(nullopt, build_error_message(errors)); + } + + return config_updates; +} + +void RemoteConfigurationManager::add_listener( + std::unique_ptr listener) { + auto product = listener->product(); + auto ps_it = product_states_.find(product); + if (ps_it == product_states_.cend()) { + ps_it = product_states_.emplace(product, product).first; + } + ps_it->second.add_listener(std::move(listener)); +} + +void RemoteConfigurationManager::add_config_end_listener( + std::function listener) { + config_end_listeners_.emplace_back(std::move(listener)); +} + +Optional RemoteConfigurationManager::build_error_message( + std::vector& errors) { + if (errors.empty()) { + return nullopt; + } + if (errors.size() == 1) { + return {std::move(errors.front())}; + } + + std::string msg{"Failed to apply configuration due to multiple errors: "}; + for (auto&& e : errors) { + msg += e; + msg += "; "; + } + return {std::move(msg)}; +} + +void RemoteConfigurationManager::update_next_state( + Optional> + rcr, + Optional error) { + uint64_t const new_targets_version = + rcr ? rcr->get().targets_version() : next_client_state_.targets_version; + + std::vector> config_states; + for (auto&& [p, pstate] : product_states_) { + pstate.add_config_states_to(config_states); + } + + next_client_state_ = ClientState{ + // if there was a global error, we did not apply the configurations fully + // the system tests expect here the targets version not to be updated + next_client_state_.root_version, + error ? next_client_state_.targets_version : new_targets_version, + config_states, + std::move(error), + rcr ? rcr->get().opaque_backend_state() + : next_client_state_.backend_client_state, + }; +} + +namespace remote_config { + +namespace { +template +StringView submatch_to_sv(const SubMatch& sub_match) { + return StringView{&*sub_match.first, + static_cast(sub_match.length())}; +} +} // namespace + +void ParsedConfigKey::parse_config_key() { + std::regex const rgx{"(?:datadog/(\\d+)|employee)/([^/]+)/([^/]+)/([^/]+)"}; + std::smatch smatch; + if (!std::regex_match(key_, smatch, rgx)) { + throw reportable_error("Invalid config key: " + key_); + } + + if (key_[0] == 'd') { + source_ = "datadog"; + auto [ptr, ec] = + std::from_chars(&*smatch[1].first, &*smatch[1].second, org_id_); + if (ec != std::errc{} || ptr != &*smatch[1].second) { + throw reportable_error("Invalid org_id in config key " + key_ + ": " + + std::string{submatch_to_sv(smatch[1])}); + } + } else { + source_ = "employee"; + org_id_ = 0; + } - 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; + StringView const product_sv{submatch_to_sv(smatch[2])}; + product_ = &Product::KnownProducts::for_name(product_sv); + + config_id_ = submatch_to_sv(smatch[3]); + name_ = submatch_to_sv(smatch[4]); +} + +RemoteConfigResponse::RemoteConfigResponse(nlohmann::json full_response, + nlohmann::json targets) + : json_(std::move(full_response)), // can't use {}-initialization + targets_(std::move(targets)), // idem + targets_signed_{targets_.at("signed"_sv)} {} + +Optional RemoteConfigResponse::from_json( + nlohmann::json&& json) { + const auto targets_encoded = json.find("targets"_sv); + if (targets_encoded == json.cend()) { + // empty response -> no change + return nullopt; + } + + if (!targets_encoded->is_string()) { + throw reportable_error( + "Invalid Remote Configuration response: targets (encoded) is not a " + "string"); + } + + if (targets_encoded->get().empty()) { + // empty response -> no change + return nullopt; + } + + // if targets is not empty, we need targets.signed + std::string decoded = base64_decode(targets_encoded->get()); + if (decoded.empty()) { + throw reportable_error( + "Invalid Remote Configuration response: invalid base64 data for " + "targets"); + } + auto targets = nlohmann::json::parse(decoded); + + auto t_signed = targets.find("signed"_sv); + if (t_signed == targets.cend()) { + throw reportable_error( + "Invalid Remote Configuration response: missing " + "signed targets with nonempty \"targets\""); + } + + return RemoteConfigResponse{std::move(json), std::move(targets)}; +} + +void RemoteConfigResponse::verify_targets_presence() const { + // files referred to in target_files need to exist in targets.signed.targets + auto target_files = json_.find("target_files"_sv); + if (target_files == json_.cend()) { + return; + } + + if (!target_files->is_array()) { + throw reportable_error( + "Invalid Remote Configuration response: target_files is not an array"); + } + + for (auto it = target_files->begin(); it != target_files->end(); ++it) { + auto path = it->find("path"_sv); + if (path == it->cend() || !path->is_string()) { + throw reportable_error( + "Invalid Remote Configuration response: missing " + "path in element of target_files"); + } + + auto path_sv = path->get(); + if (!get_target(path_sv)) { + throw reportable_error( + "Invalid Remote Configuration response: " + "target_files[...].path (" + + std::string{path_sv} + + ") is a key not present in targets.signed.targets"); + } + } +} + +void RemoteConfigResponse::verify_client_configs() { + auto&& client_cfgs = json_.find("client_configs"_sv); + if (client_cfgs == json_.end()) { + return; + } + if (!client_cfgs->is_array()) { + throw reportable_error( + "Invalid Remote Configuration response: client_configs is no array"); + } + for (auto&& [_, cc] : client_cfgs->items()) { + if (!cc.is_string()) { + throw reportable_error( + "Invalid Remote Configuration response: client_configs " + "should be an array of strings"); + } + client_configs_.emplace_back(cc.get()); + } +} + +Optional RemoteConfigResponse::get_target(StringView key) const { + auto&& targets = targets_signed_.at("targets"_sv); + auto&& target = targets.find(key); + if (target == targets.cend()) { + return nullopt; + } + return ConfigTarget{*target}; +} + +Optional RemoteConfigResponse::get_file_contents( + const ParsedConfigKey& key) const { + auto&& target_files = json_.find("target_files"_sv); + if (target_files == json_.cend() || !target_files->is_array()) { + return nullopt; + } + + Optional target = get_target(key); + if (!target) { + throw std::runtime_error("targets.signed.targets[" + + std::string{key.full_key()} + + "] was expected to exist at this point"); + } + + for (auto&& file : *target_files) { + auto&& path = file.at("path"_sv).get(); + if (path != key.full_key()) { + continue; + } + + // TODO check sha256 hash + auto expected_len = target->length(); + if (expected_len == 0) { + return {std::string{}}; + } + + const auto raw = file.at("raw"_sv).get(); + auto decoded = base64_decode(raw); + if (decoded.empty()) { + throw reportable_error( + "Invalid Remote Configuration response: target_files[...].raw " + "is not a valid base64 string"); + } + if (decoded.length() != expected_len) { + throw reportable_error( + "Invalid Remote Configuration response: target_files[...].raw " + "length (after decoding) does not match the length in " + "targets.signed.targets. " + "Expected " + + std::to_string(expected_len) + ", got " + + std::to_string(decoded.length())); + } + return {std::move(decoded)}; + } + + return nullopt; +} + +bool ProductState::apply(const RemoteConfigResponse& response, + std::vector& config_update) { + const std::vector errors; + std::unordered_set processed_keys; + + bool changes_detected = false; + for (auto&& key : response.client_configs()) { + if (key.product() != product_) { + continue; + } + + try { + const ConfigTarget cfg_target = get_target_or_throw(response, key); + processed_keys.emplace(&key); + + if (is_target_changed(key, cfg_target)) { + changes_detected = true; + const std::string content = get_file_contents_or_throw(response, key); + call_listeners_apply(response, config_update, key, content); } + } catch (const reportable_error&) { + throw; + } catch (const nlohmann::json::exception& e) { + // if these happen, the response is prob malformed, so it's a global error + throw reportable_error("JSON error processing key " + + std::string{key.full_key()} + ": " + e.what()); + } catch (const std::exception& e) { + // should not happen; errors are caught in call_listeners_apply + throw reportable_error("Failed to apply configuration for " + + std::string{key.full_key()} + ": " + e.what()); + } catch (...) { + std::terminate(); + } + } - 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()); + // Process removed configuration + // per_key_state_ is a map whose keys are the ones we know about + // processed_keys are the ones we saw in the response + // So find the ones we know about but didn't see in the response + for (auto it{per_key_state_.cbegin()}; it != per_key_state_.cend();) { + bool found{}; + const ParsedConfigKey& key = it->first; + for (auto&& pk : processed_keys) { + if (*pk == key) { + found = true; + break; } + } + if (!found) { + changes_detected = true; + it = call_listeners_remove(config_update, it); + } else { + it++; + } + } - applied_config_[std::string{config_path}] = new_config; + return changes_detected; +} + +void ProductState::call_listeners_apply( + const RemoteConfigResponse& resp, + std::vector& config_update, const ParsedConfigKey& key, + const std::string& file_contents) { + try { + for (auto&& l : listeners_) { + l->on_config_update(key, file_contents, config_update); } - // 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++; - } + update_config_state(resp, key, {}); + } catch (const reportable_error&) { + throw; + } catch (const std::exception& e) { + update_config_state(resp, key, {{e.what()}}); + } catch (...) { + std::terminate(); + } +} + +ProductState::per_key_state_citerator_t ProductState::call_listeners_remove( + std::vector& config_update, + ProductState::per_key_state_citerator_t it) { + const ParsedConfigKey& key = it->first; + try { + for (auto&& l : listeners_) { + l->on_config_remove(key, config_update); } - } catch (const nlohmann::json::exception& e) { - std::string error_message = "Ill-formatted Remote Configuration response: "; - error_message += e.what(); + } catch (const reportable_error&) { + throw; + } catch (const std::exception& e) { + // no way to report errors removing a config, as its config_state entry will + // not be included in the next request. Report it globally + throw reportable_error("Failed to remove configuration for " + + std::string{key.full_key()} + ": " + e.what()); + } catch (...) { + std::terminate(); + } - state_.error_message = std::move(error_message); - return config_updates; + return per_key_state_.erase(it); +} + +ConfigTarget ProductState::get_target_or_throw( + const RemoteConfigResponse& response, const ParsedConfigKey& key) { + auto&& target = response.get_target(key); + if (!target) { + throw reportable_error("Told to apply config for " + + std::string{key.full_key()} + + ", but no corresponding entry exists in " + "targets.targets_signed.targets"); } + return *target; +} - return config_updates; +std::string ProductState::get_file_contents_or_throw( + const RemoteConfigResponse& response, const ParsedConfigKey& key) { + Optional maybe_content = response.get_file_contents(key); + if (!maybe_content) { + throw reportable_error( + "Told to apply config for " + std::string{key.full_key()} + + ", but content not present when it was expected to be (because the new " + "hash differs from the one last seen, if any)"); + } + return std::move(*maybe_content); } -std::vector RemoteConfigurationManager::apply_config( - Configuration config) { - return config_manager_->update(config.content); +void ProductState::update_config_state(const RemoteConfigResponse& response, + const ParsedConfigKey& key, + Optional error) try { + Optional config_target = response.get_target(key); + assert(config_target.has_value()); + + ConfigState new_config_state{ + std::string{key.config_id()}, + config_target->version(), + key.product(), + error ? ConfigState::ApplyState::Error + : ConfigState::ApplyState::Acknowledged, + error ? std::move(*error) : std::string{}, + }; + CachedTargetFile new_ctf = config_target->to_cached_target_file(key); + + auto&& state = per_key_state_[key]; + if (!state.config_state) { + state.config_state = + std::make_shared(std::move(new_config_state)); + } else { + *state.config_state = std::move(new_config_state); + } + state.cached_target_file = std::move(new_ctf); +} catch (const std::exception& e) { + throw reportable_error("Failed to update config state from for " + + std::string{key.full_key()} + ": " + e.what()); } -std::vector RemoteConfigurationManager::revert_config( - Configuration) { - return config_manager_->reset(); +CachedTargetFile ConfigTarget::to_cached_target_file( + const ParsedConfigKey& key) const { + auto length = json_.at("length"_sv).get(); + std::vector hashes; + + auto&& hashes_json = json_.at("hashes"_sv); + if (!hashes_json.is_object()) { + throw reportable_error( + "Invalid Remote Configuration response in config_target: " + "hashes is not an object"); + } + hashes.reserve(hashes_json.size()); + bool found_sha256 = false; + for (auto&& [algo, hash] : hashes_json.items()) { + if (algo == "sha256") { + found_sha256 = true; + } + hashes.emplace_back( + CachedTargetFile::TargetFileHash{algo, hash.get()}); + } + if (!found_sha256) { + throw reportable_error( + "Invalid Remote Configuration response in config_target: " + "missing sha256 hash for " + + std::string{key.full_key()}); + } + + return {std::string{key.full_key()}, length, std::move(hashes)}; } +} // namespace remote_config -} // namespace tracing -} // namespace datadog +} // namespace datadog::tracing diff --git a/src/datadog/remote_config.h b/src/datadog/remote_config.h index d822fe58..88c071c0 100644 --- a/src/datadog/remote_config.h +++ b/src/datadog/remote_config.h @@ -12,10 +12,13 @@ // 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 "json.hpp" #include "logger.h" #include "optional.h" #include "runtime_id.h" @@ -23,46 +26,457 @@ #include "trace_sampler_config.h" #include "tracer_signature.h" -namespace datadog { -namespace tracing { +namespace datadog::tracing { -class RemoteConfigurationManager { - // Represents the *current* state of the RemoteConfigurationManager. - // 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; +namespace remote_config { +// 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 class Capability : uint64_t { + 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_CUSTOM_TAGS = 1 << 15, + ASM_PREPROCESSOR_OVERRIDES = 1 << 16, + ASM_CUSTOM_DATA_SCANNERS = 1 << 17, + ASM_EXCLUSION_DATA = 1 << 18, + APM_TRACING_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, +}; + +class CapabilitiesSet { + std::underlying_type_t value_{}; + + public: + CapabilitiesSet() = default; + // NOLINTNEXTLINE + CapabilitiesSet(Capability c) : value_{static_cast(c)} {} + CapabilitiesSet(std::initializer_list l) { + for (auto &&c : l) { + value_ |= static_cast(c); + } + } + + CapabilitiesSet &operator|=(CapabilitiesSet other) { + value_ |= other.value_; + return *this; + } + + decltype(value_) value() const { return value_; } +}; + +class Product { + public: + struct KnownProducts; + + StringView name() const { return name_; } + bool operator==(const Product &p) const { return name_ == p.name_; } + bool operator!=(const Product &p) const { return !(p == *this); } + + struct Hash { + std::size_t operator()(const Product &p) const { +#ifdef DD_USE_ABSEIL_FOR_ENVOY + // 64-bit FNV-1 hash + auto sv = p.name_; + static constexpr std::uint64_t offset_basis = 0xcbf29ce484222325; + static constexpr std::uint64_t prime = 0x100000001b3; + + std::uint64_t hash = offset_basis; + for (char c : sv) { + hash ^= static_cast(c); + hash *= prime; + } + return hash; +#else + return std::hash()(p.name_); +#endif + } }; - // Holds information about a specific configuration update, - // including its identifier, hash value, version number and the content. - struct Configuration { - std::string id; + private: + constexpr explicit Product(StringView name) : name_{name} {} + StringView name_; + friend struct KnownProducts; +}; + +struct Product::KnownProducts { + static inline constexpr Product AGENT_CONFIG{"AGENT_CONFIG"}; + static inline constexpr Product AGENT_TASK{"AGENT_TASK"}; + static inline constexpr Product APM_TRACING{"APM_TRACING"}; + static inline constexpr Product LIVE_DEBUGGING{"LIVE_DEBUGGING"}; + static inline constexpr Product LIVE_DEBUGGING_SYMBOL_DB{ + "LIVE_DEBUGGING_SYMBOL_DB"}; + static inline constexpr Product ASM{"ASM"}; + static inline constexpr Product ASM_DD{"ASM_DD"}; + static inline constexpr Product ASM_DATA{"ASM_DATA"}; + static inline constexpr Product ASM_FEATURES{"ASM_FEATURES"}; + static inline constexpr Product UNKNOWN{"_UNKNOWN"}; + static inline constexpr auto ALL = {AGENT_CONFIG, + AGENT_TASK, + APM_TRACING, + LIVE_DEBUGGING, + LIVE_DEBUGGING_SYMBOL_DB, + ASM, + ASM_DD, + ASM_DATA, + ASM_FEATURES}; + + static const Product &for_name(StringView name) { + for (auto &&p : KnownProducts::ALL) { + if (p.name() == name) { + return p; + } + } + return KnownProducts::UNKNOWN; + } +}; + +// A configuration key has the form: +// (datadog/ | employee)///" +class ParsedConfigKey { + public: + explicit ParsedConfigKey(std::string key) : key_{std::move(key)} { + parse_config_key(); + } + ParsedConfigKey(const ParsedConfigKey &oth) : ParsedConfigKey(oth.key_) { + parse_config_key(); + } + ParsedConfigKey &operator=(const ParsedConfigKey &oth) { + if (&oth != this) { + key_ = oth.key_; + parse_config_key(); + } + return *this; + } + ParsedConfigKey(ParsedConfigKey &&oth) noexcept + : key_{std::move(oth.key_)}, + source_{oth.source()}, + org_id_{oth.org_id_}, + product_{oth.product_}, + config_id_{oth.config_id_}, + name_{oth.name_} { + oth.source_ = {}; + oth.org_id_ = 0; + oth.product_ = &Product::KnownProducts::UNKNOWN; + oth.config_id_ = {}; + oth.name_ = {}; + } + ParsedConfigKey &operator=(ParsedConfigKey &&oth) noexcept { + if (&oth != this) { + key_ = std::move(oth.key_); + source_ = oth.source_; + org_id_ = oth.org_id_; + product_ = oth.product_; + config_id_ = oth.config_id_; + name_ = oth.name_; + oth.source_ = {}; + oth.org_id_ = 0; + oth.product_ = &Product::KnownProducts::UNKNOWN; + oth.config_id_ = {}; + oth.name_ = {}; + } + return *this; + } + ~ParsedConfigKey() = default; + + bool operator==(const ParsedConfigKey &other) const { + return key_ == other.key_; + } + + struct Hash { + std::size_t operator()(const ParsedConfigKey &k) const { + return std::hash()(k.key_); + } + }; + + // lifetime of return values is that of the data pointer in key_ + StringView full_key() const { return {key_}; } + StringView source() const { return source_; } + std::uint64_t org_id() const { return org_id_; } + Product product() const { return *product_; } + StringView config_id() const { return config_id_; } + StringView name() const { return name_; } + + private: + void parse_config_key(); + + std::string key_; + StringView source_; + std::uint64_t org_id_{}; + const Product *product_{}; + StringView config_id_; + StringView name_; +}; + +// A subset of the information in config_target +struct CachedTargetFile { + struct TargetFileHash { + std::string algorithm; std::string hash; - std::size_t version; - ConfigUpdate content; + }; - enum State : char { - unacknowledged = 1, - acknowledged = 2, - error = 3 - } state = State::unacknowledged; + std::string path; + std::uint64_t length; + std::vector hashes; + + bool empty() const { return hashes.empty(); } // NOLINT + StringView sha256() const { + auto h = std::find_if(hashes.cbegin(), hashes.cend(), [](const auto &h) { + return h.algorithm == "sha256"; + }); + if (h == hashes.cend()) { + return {}; + } + return {h->hash}; + } +}; - Optional error_message; +struct ConfigState { + enum class ApplyState { + Unknown = 0, // for bc; not to be used + Unacknowledged = + 1, // default state. set until the component consuming the + // configuration has acknowledged and applied the config + Acknowledged = 2, // the configuration has been successfully applied + Error = 3, // error applying the configuration }; + std::string id; + std::size_t version; + Product product; + ApplyState apply_state{ApplyState::Unacknowledged}; + std::string apply_error; // not present => empty string +}; + +class ConfigTarget { + public: + explicit ConfigTarget(const nlohmann::json &json) : json_{json} {} + + std::string sha256() const { + return json_.at("/hashes/sha256"_json_pointer).get(); + } + std::size_t version() const { + return json_.at("/custom/v"_json_pointer).get(); + } + + std::size_t length() const { + return json_.at("/length"_json_pointer).get(); + } + + CachedTargetFile to_cached_target_file(const ParsedConfigKey &key) const; + + private: + const nlohmann::json &json_; // NOLINT +}; + +class RemoteConfigResponse { + public: + static Optional from_json(nlohmann::json &&json); + + void validate() { + verify_targets_presence(); + verify_client_configs(); + } + + uint64_t targets_version() const { + return targets_signed_.at(StringView{"version"}).get(); + } + std::string opaque_backend_state() const { + return targets_signed_.at("/custom/opaque_backend_state"_json_pointer) + .get(); + } + const std::vector &client_configs() const { + return client_configs_; + } + + Optional get_target(StringView key) const; + Optional get_target(const ParsedConfigKey &key) const { + return get_target(key.full_key()); + } + + Optional get_file_contents(const ParsedConfigKey &key) const; + + private: + RemoteConfigResponse(nlohmann::json full_response, nlohmann::json targets); + + void verify_targets_presence() const; + void verify_client_configs(); + + nlohmann::json json_; + nlohmann::json targets_; // decoded, parsed + const nlohmann::json &targets_signed_; // NOLINT + std::vector client_configs_; +}; + +class ProductListener { + Product product_; + + public: + explicit ProductListener(Product p) : product_{p} {}; + virtual ~ProductListener() = default; + ProductListener(const ProductListener &) = delete; + ProductListener &operator=(const ProductListener &) = delete; + ProductListener(ProductListener &&) = delete; + ProductListener &operator=(ProductListener &&) = delete; + + virtual void on_config_update( + const ParsedConfigKey &key, const std::string &content, + std::vector &config_updates) = 0; + + virtual void on_config_remove( + const ParsedConfigKey &key, + std::vector &config_updates) = 0; + + [[nodiscard]] virtual CapabilitiesSet capabilities() const = 0; + + Product product() const { return product_; } +}; + +// errors that should be reported in the ClientState (so not errors applying +// configurations) +class reportable_error : public std::runtime_error { + public: + using std::runtime_error::runtime_error; +}; + +class ProductState { + struct PerKeyState { + CachedTargetFile cached_target_file; + std::shared_ptr config_state; + }; + Product product_; + std::unordered_map + per_key_state_{}; + std::vector> listeners_; + std::vector errors_; + + public: + explicit ProductState(Product product) : product_{product} {} + void add_listener(std::unique_ptr listener) { + listeners_.push_back(std::move(listener)); + } + + // throws reportable_error for global errors. All other errors are considered + // configuration apply errors + bool apply(const RemoteConfigResponse &response, + std::vector &config_update); + + void call_listeners_apply(const RemoteConfigResponse &resp, + std::vector &config_update, + const ParsedConfigKey &key, + const std::string &file_contents); + + using per_key_state_citerator_t = decltype(per_key_state_)::const_iterator; + per_key_state_citerator_t call_listeners_remove( + std::vector &config_update, per_key_state_citerator_t it); + + void add_config_states_to( + std::vector> &config_states) const { + for (auto &&[key, state] : per_key_state_) { + config_states.emplace_back(state.config_state); + } + }; + + CapabilitiesSet subscribed_capabilities() const noexcept { + CapabilitiesSet caps{}; + for (auto &&listener : listeners_) { + caps |= listener->capabilities(); + } + return caps; + } + + template + void for_each_cached_target_file(Func &&f, Args &&...args) const { + for (auto &&[key, state] : per_key_state_) { + if (state.cached_target_file.empty()) { + continue; + } + std::invoke(std::forward(f), state.cached_target_file, + std::forward(args)...); + } + } + + private: + static ConfigTarget get_target_or_throw(const RemoteConfigResponse &response, + const ParsedConfigKey &key); + + static std::string get_file_contents_or_throw( + const RemoteConfigResponse &response, const ParsedConfigKey &key); + + void update_config_state(const RemoteConfigResponse &response, + const ParsedConfigKey &key, + Optional error); + + bool is_target_changed(const ParsedConfigKey &key, + const ConfigTarget &new_target) const { + auto &&st = per_key_state_.find(key); + if (st == per_key_state_.cend()) { + return true; // no previous state + } + const CachedTargetFile &ctf = st->second.cached_target_file; + return ctf.sha256() != new_target.sha256(); + } +}; +} // namespace remote_config + +class ConfigManager; + +class RemoteConfigurationManager { TracerSignature tracer_signature_; std::shared_ptr config_manager_; std::string client_id_; + std::shared_ptr logger_; + + struct ClientState { + uint64_t root_version = 1UL; + uint64_t targets_version{}; + std::vector> config_states; + Optional error; // has_error + error + std::string backend_client_state; + }; - State state_; - std::unordered_map applied_config_; + ClientState next_client_state_; + std::unordered_map + product_states_; + std::vector> config_end_listeners_; public: RemoteConfigurationManager( - const TracerSignature& tracer_signature, - const std::shared_ptr& config_manager); + const TracerSignature &tracer_signature, + const std::shared_ptr &config_manager, + std::shared_ptr logger); + + void add_listener(std::unique_ptr listener); + + void add_config_end_listener(std::function listener); // Construct a JSON object representing the payload to be sent in a remote // configuration request. @@ -70,18 +484,20 @@ class RemoteConfigurationManager { // Handles the response received from a remote source and udates the internal // state accordingly. - std::vector process_response(const nlohmann::json& json); + std::vector process_response(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); + static Optional build_error_message( + std::vector &errors); - // Apply a remote configuration. - std::vector apply_config(Configuration config); + nlohmann::json::array_t serialize_config_states() const; + nlohmann::json serialize_cached_target_files() const; - // Revert a remote configuration. - std::vector revert_config(Configuration config); + void update_next_state( + Optional< + std::reference_wrapper> + rcr, + Optional error); }; -} // namespace tracing -} // namespace datadog +} // namespace datadog::tracing diff --git a/src/datadog/string_util.cpp b/src/datadog/string_util.cpp index 0455a24f..3f45a1a2 100644 --- a/src/datadog/string_util.cpp +++ b/src/datadog/string_util.cpp @@ -51,11 +51,18 @@ std::string to_string(double d, size_t precision) { return stream.str(); } -std::string join(const std::vector& values, StringView separator) { +template +std::string join(const std::vector& values, StringView separator) { return join(values, separator, [](std::string& result, StringView value) { append(result, value); }); } +std::string join(const std::vector& values, StringView separator) { + return join(values, separator); +} +std::string join(const std::vector& values, StringView separator) { + return join(values, separator); +} std::string join_propagation_styles( const std::vector& values) { diff --git a/src/datadog/string_util.h b/src/datadog/string_util.h index 090b1018..06460945 100644 --- a/src/datadog/string_util.h +++ b/src/datadog/string_util.h @@ -22,6 +22,7 @@ std::string to_string(double d, size_t precision); // Joins elements of a vector into a single string with a specified separator std::string join(const std::vector& values, StringView separator); +std::string join(const std::vector& values, StringView separator); // Joins propagation styles into a single comma-separated string std::string join_propagation_styles(const std::vector&); diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 76bb19a5..80b07e71 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -30,10 +30,10 @@ namespace datadog { namespace tracing { -Tracer::Tracer(const FinalizedTracerConfig& config) +Tracer::Tracer(FinalizedTracerConfig& config) : Tracer(config, default_id_generator(config.generate_128bit_trace_ids)) {} -Tracer::Tracer(const FinalizedTracerConfig& config, +Tracer::Tracer(FinalizedTracerConfig& config, const std::shared_ptr& generator) : logger_(config.logger), config_manager_(std::make_shared(config)), diff --git a/src/datadog/tracer.h b/src/datadog/tracer.h index 7fc5cb24..841ed31a 100644 --- a/src/datadog/tracer.h +++ b/src/datadog/tracer.h @@ -53,8 +53,8 @@ class Tracer { // Create a tracer configured using the specified `config`, and optionally: // - using the specified `generator` to create trace IDs and span IDs // - using the specified `clock` to get the current time. - explicit Tracer(const FinalizedTracerConfig& config); - Tracer(const FinalizedTracerConfig& config, + explicit Tracer(FinalizedTracerConfig& config); + Tracer(FinalizedTracerConfig& config, const std::shared_ptr& generator); // Create a new trace and return the root span of the trace. Optionally diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index 95e52a27..92ba3b53 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -239,11 +239,11 @@ Expected load_tracer_env_config(Logger &logger) { } // namespace -Expected finalize_config(const TracerConfig &config) { +Expected finalize_config(TracerConfig &config) { return finalize_config(config, default_clock); } -Expected finalize_config(const TracerConfig &user_config, +Expected finalize_config(TracerConfig &user_config, const Clock &clock) { auto logger = user_config.logger ? user_config.logger : std::make_shared(); @@ -379,7 +379,7 @@ Expected finalize_config(const TracerConfig &user_config, if (auto *error = finalized.if_error()) { return std::move(*error); } - final_config.collector = *finalized; + final_config.collector = std::move(*finalized); final_config.metadata.merge(finalized->metadata); } else { final_config.collector = user_config.collector; diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index 49cf6ab2..480620cf 100644 --- a/src/datadog/tracer_config.h +++ b/src/datadog/tracer_config.h @@ -169,8 +169,8 @@ struct TracerConfig { // a valid `TracerConfig` and accompanying environment. // `FinalizedTracerConfig` must be obtained by calling `finalize_config`. class FinalizedTracerConfig final { - friend Expected finalize_config( - const TracerConfig& config, const Clock& clock); + friend Expected finalize_config(TracerConfig& config, + const Clock& clock); FinalizedTracerConfig() = default; public: @@ -207,8 +207,8 @@ class FinalizedTracerConfig final { // Optionally specify a `clock` used to calculate span start times, span // durations, and timeouts. If `clock` is not specified, then `default_clock` // is used. -Expected finalize_config(const TracerConfig& config); -Expected finalize_config(const TracerConfig& config, +Expected finalize_config(TracerConfig& config); +Expected finalize_config(TracerConfig& config, const Clock& clock); } // namespace tracing diff --git a/test/test_curl.cpp b/test/test_curl.cpp index e19a1db0..85c19e7b 100644 --- a/test/test_curl.cpp +++ b/test/test_curl.cpp @@ -153,7 +153,7 @@ TEST_CASE("parse response headers and body", "[curl]") { // force only tracing to be sent and exclude telemetry. config.report_telemetry = false; - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; diff --git a/test/test_datadog_agent.cpp b/test/test_datadog_agent.cpp index cc97acb3..08d1ab32 100644 --- a/test/test_datadog_agent.cpp +++ b/test/test_datadog_agent.cpp @@ -201,7 +201,7 @@ TEST_CASE("Remote Configuration", "[datadog_agent]") { finalized->report_telemetry, finalized->clock, finalized->logger, signature, "", ""); - const auto& agent_config = + auto& agent_config = std::get(finalized->collector); DatadogAgent agent(agent_config, telemetry, config.logger, signature, config_manager); diff --git a/test/test_remote_config.cpp b/test/test_remote_config.cpp index e3bd9d98..b053127c 100644 --- a/test/test_remote_config.cpp +++ b/test/test_remote_config.cpp @@ -1,9 +1,12 @@ #include #include "catch.hpp" +#include "datadog/config_manager.h" #include "datadog/json_fwd.hpp" +#include "datadog/logger.h" #include "datadog/remote_config.h" #include "datadog/trace_sampler.h" +#include "datadog/tracer_config.h" #include "mocks/loggers.h" #include "test.h" @@ -32,17 +35,27 @@ REMOTE_CONFIG_TEST("first payload") { const auto config_manager = std::make_shared(*finalize_config(config)); - RemoteConfigurationManager rc(tracer_signature, config_manager); + const auto logger = + std::make_shared(std::cerr, MockLogger::ERRORS_ONLY); + + RemoteConfigurationManager rc(tracer_signature, config_manager, logger); const auto payload = rc.make_request_payload(); CHECK(payload.contains("error") == false); CHECK(payload["client"]["is_tracer"] == true); + CHECK(payload["client"]["capabilities"] == + std::vector{0, 0, 0, 0, 32, 8, 144, 0}); + CHECK(payload["client"]["products"] == + std::vector{"APM_TRACING"}); 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); + // [RFC] Integrating with Remote Config in a Tracer indicates default is 0 CHECK(payload["client"]["state"]["targets_version"] == 0); + + CHECK(payload["cached_target_files"] == nlohmann::json(nullptr)); } REMOTE_CONFIG_TEST("response processing") { @@ -68,97 +81,257 @@ REMOTE_CONFIG_TEST("response processing") { const auto config_manager = std::make_shared(*finalize_config(config)); - RemoteConfigurationManager rc(tracer_signature, config_manager); + const auto logger = + std::make_shared(std::cerr, MockLogger::ERRORS_ONLY); + + RemoteConfigurationManager rc(tracer_signature, config_manager, logger); + + SECTION("empty response") { + auto test_case = + GENERATE(values({"{}", R"({ "targets": "" })"})); + + CAPTURE(test_case); + auto response_json = nlohmann::json::parse(test_case); + rc.process_response(std::move(response_json)); + auto next_payload = rc.make_request_payload(); + + // no error; targets_version unchanged + CHECK(next_payload["client"]["state"]["has_error"] == false); + CHECK(next_payload["client"]["state"]["error"] == ""); + CHECK(next_payload["client"]["state"]["targets_version"] == 0); + } 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": "" })", + auto test_case = GENERATE(values>({ // `targets` field is not base64 encoded - R"({ "targets": "Hello, Mars!" })", + {R"({ "targets": "Hello, Mars!" })", + "Invalid Remote Configuration response: invalid base64 data for targets"}, // `targets` field is not a JSON base64 encoded // decode("bm90IGpzb24=") == "not json" - R"({ "targets": "bm90IGpzb24=" })", + {R"({ "targets": "bm90IGpzb24=" })", + "Ill-formatted Remote Configuration response: [json.exception.parse_error." + "101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 'no'"}, // `targets` field JSON base64 encoded do not follow the expected schema // decode("eyJmb28iOiAiYmFyIn0=") == "{"foo": "bar"}" - R"({ "targets": "eyJmb28iOiAiYmFyIn0=" })", + {R"({ "targets": "eyJmb28iOiAiYmFyIn0=" })", + "Invalid Remote Configuration response: missing signed targets with nonempty \"targets\""}, // `targets` is missing the `targets` field. // decode("eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=") == "{"signed": {"version": 2, "custom": {"opaque_backend_state": "15"}}}" - R"({ + {R"({ "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", - "client_configs": ["datadog"] + "client_configs": ["datadog/2/APM_TRACING/config_id/name"] })", - // `/targets/targets` have no `datadog` entry + "JSON error processing key datadog/2/APM_TRACING/config_id/name: [json." + "exception.out_of_range.403] key 'targets' not found"}, + // `/targets/targets` have no `datadog/APM_TRACING/config_id/name` entry // {"signed": {"version": 2, "targets": {"foo": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} - R"({ + {R"({ "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", - "client_configs": ["datadog"] + "client_configs": ["datadog/2/APM_TRACING/config_id/name"] })", + "Told to apply config for datadog/2/APM_TRACING/config_id/name, but no " + "corresponding entry exists in targets.targets_signed.targets", + }, // `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"] + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 42, "custom": {"v": 43}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDQyLCAiY3VzdG9tIjogeyJ2IjogNDN9LCAiaGFzaGVzIjogeyJzaGEyNTYiOiAiIn19fSwiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0K", + "client_configs": ["datadog/2/APM_TRACING/30/name"] })", + "Told to apply config for datadog/2/APM_TRACING/30/name, but content not present " + "when it was expected to be (because the new hash differs from the one last " + "seen, if any)"}, // `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"], + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 42, "custom": {"v": 43}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDQyLCAiY3VzdG9tIjogeyJ2IjogNDN9LCAiaGFzaGVzIjogeyJzaGEyNTYiOiAiIn19fSwiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": [] })", + "Told to apply config for datadog/2/APM_TRACING/30/name, but content not present " + "when it was expected to be (because the new hash differs from the one last " + "seen, if any)"}, // `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"], + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 42, "custom": {"v": 43}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDQyLCAiY3VzdG9tIjogeyJ2IjogNDN9LCAiaGFzaGVzIjogeyJzaGEyNTYiOiAiIn19fSwiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": 15 })", + "Invalid Remote Configuration response: target_files is not an array"}, // `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!"}] + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 42, "custom": {"v": 43}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDQyLCAiY3VzdG9tIjogeyJ2IjogNDN9LCAiaGFzaGVzIjogeyJzaGEyNTYiOiAiIn19fSwiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "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="}] + "Invalid Remote Configuration response: target_files[...].raw is not a valid " + "base64 string"}, + // `targets` has no length provided + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"custom": {"v": 43}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7ImN1c3RvbSI6IHsidiI6IDQzfSwgImhhc2hlcyI6IHsic2hhMjU2IjogIiJ9fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": ""}] })", - // `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="}] + "JSON error processing key datadog/2/APM_TRACING/30/name: [json.exception." + "out_of_range.403] key 'length' not found"}, + // `targets` has non-integer length + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": "foo", "custom": {"v": 43}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6ICJmb28iLCAiY3VzdG9tIjogeyJ2IjogNDN9LCAiaGFzaGVzIjogeyJzaGEyNTYiOiAiIn19fSwiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": ""}] + })", + "JSON error processing key datadog/2/APM_TRACING/30/name: [json.exception." + "type_error.302] type must be number, but is string"}, + // `targets` has no custom field + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 2, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDIsICJoYXNoZXMiOiB7InNoYTI1NiI6ICIifX19LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "YQo="}] + })", + "Failed to update config state from for datadog/2/APM_TRACING/30/name: [json." + "exception.out_of_range.403] key 'custom' not found"}, + // `targets` has an empty "custom" + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 2, "custom": {}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDIsICJjdXN0b20iOiB7fSwgImhhc2hlcyI6IHsic2hhMjU2IjogIiJ9fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "YQo="}] })", + "Failed to update config state from for datadog/2/APM_TRACING/30/name: [json." + "exception.out_of_range.403] key 'v' not found"}, + // `targets` "custom"/"v" is not a number + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 2, "custom": {"v": []}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDIsICJjdXN0b20iOiB7InYiOiBbXX0sICJoYXNoZXMiOiB7InNoYTI1NiI6ICIifX19LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "YQo="}] + })", + "Failed to update config state from for datadog/2/APM_TRACING/30/name: [json." + "exception.type_error.302] type must be number, but is array"}, + // `targets` has no "hashes" + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 2, "custom": {"v": 1}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDIsICJjdXN0b20iOiB7InYiOiAxfX19LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "YQo="}] + })", + "Failed to update config state from for datadog/2/APM_TRACING/30/name: [json." + "exception.out_of_range.403] key 'hashes' not found"}, + // `targets` has "hashes" that's no object + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 2, "custom": {"v": 1}, "hashes": []}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDIsICJjdXN0b20iOiB7InYiOiAxfSwgImhhc2hlcyI6IFtdfX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "YQo="}] + })", + "Failed to update config state from for datadog/2/APM_TRACING/30/name: Invalid " + "Remote Configuration response in config_target: hashes is not an object"}, + // `targets` has no sha256 hash + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 2, "custom": {"v": 1}, "hashes": {}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDIsICJjdXN0b20iOiB7InYiOiAxfSwgImhhc2hlcyI6IHt9fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "YQo="}] + })", + "Failed to update config state from for datadog/2/APM_TRACING/30/name: Invalid " + "Remote Configuration response in config_target: missing sha256 hash for datadog/" + "2/APM_TRACING/30/name"}, + // `targets` OK. Length mismatch + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 20, "custom": {"v": 1}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDIwLCAiY3VzdG9tIjogeyJ2IjogMX0sICJoYXNoZXMiOiB7InNoYTI1NiI6ICIifX19LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQ==", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "YQo="}] + })", + "Invalid Remote Configuration response: target_files[...].raw length (after " + "decoding) does not match the length in targets.signed.targets. Expected 20, " + "got 2"}, + // `targets` OK, but product not subscribed + // {"signed": {"version": 2, "targets": {"datadog/2/ASM_DD/30/name": {"length": 2, "custom": {"v": 1}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FTTV9ERC8zMC9uYW1lIjogeyJsZW5ndGgiOiAyLCAiY3VzdG9tIjogeyJ2IjogMX0sICJoYXNoZXMiOiB7InNoYTI1NiI6ICIifX19LCJjdXN0b20iOiB7Im9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogIjE1In19fQo=", + "client_configs": ["datadog/2/ASM_DD/30/name"], + "target_files": [{"path": "datadog/2/ASM_DD/30/name", "raw": "YQo="}] + })", + "Remote Configuration response contains unknown/unsubscribed product: ASM_DD"}, })); // clang-format on CAPTURE(test_case); - const auto response_json = - nlohmann::json::parse(/* input = */ test_case, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); + auto response_json = nlohmann::json::parse(/* input = */ test_case.first, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); REQUIRE(!response_json.is_discarded()); - const auto config_updated = rc.process_response(response_json); + rc.process_response(std::move(response_json)); + + // Next payload should contain an error. + const auto payload = rc.make_request_payload(); + CHECK(payload["client"]["state"]["has_error"] == true); + CHECK(payload["client"]["state"]["error"].get() == + test_case.second); + } + + SECTION("error applying configuration") { + // clang-format off + auto test_case = GENERATE(values>({ + // content is not a JSON after base64 decoding + // decode("bm90IGpzb24=") == "not json" + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 8, "custom": {"v": 1}, "hashes": {"sha256": ""}}},"custom": {"opaque_backend_state": "15"}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDgsICJjdXN0b20iOiB7InYiOiAxfSwgImhhc2hlcyI6IHsic2hhMjU2IjogIiJ9fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "bm90IGpzb24="}] + })", + "[json.exception.parse_error.101] parse error at line 1, column 2: syntax " + "error while parsing value - invalid literal; last read: 'no'"}, + // `targets` OK. `target_files` field JSON base64 content do not follow the expected schema. + // {"signed": {"version": 2, "targets": {"datadog/2/APM_TRACING/30/name": {"length": 34, "custom": {"v": 1}, "hashes": {"sha256": ""}}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + // {"service_target": {"sevice": {}}} + {R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZGF0YWRvZy8yL0FQTV9UUkFDSU5HLzMwL25hbWUiOiB7Imxlbmd0aCI6IDM0LCAiY3VzdG9tIjogeyJ2IjogMX0sICJoYXNoZXMiOiB7InNoYTI1NiI6ICIifX0sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["datadog/2/APM_TRACING/30/name"], + "target_files": [{"path": "datadog/2/APM_TRACING/30/name", "raw": "eyJzZXJ2aWNlX3RhcmdldCI6IHsic2V2aWNlIjoge319fQ=="}] + })", "[json.exception.out_of_range.403] key 'service' not found"}, + })); + // clang-format on + + CAPTURE(test_case); + auto response_json = nlohmann::json::parse(test_case.first); + + const auto config_updated = rc.process_response(std::move(response_json)); CHECK(config_updated.empty()); - // Next payload should contains an error. + // Next payload should not contain global 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); + CHECK(payload["client"]["state"]["has_error"] == false); + CHECK(payload["client"]["state"]["error"] == ""); + + // However config_states should + CHECK(payload["client"]["state"]["config_states"].size() == 1); + CHECK(payload["client"]["state"]["config_states"][0]["id"] == "30"); + CHECK(payload["client"]["state"]["config_states"][0]["version"] == 1); + CHECK(payload["client"]["state"]["config_states"][0]["product"] == + "APM_TRACING"); + CHECK(payload["client"]["state"]["config_states"][0]["apply_state"] == + datadog::tracing::remote_config::ConfigState::ApplyState::Error); + CHECK(payload["client"]["state"]["config_states"][0]["apply_error"] + .get() == test_case.second); + + CHECK(payload["client"]["state"]["targets_version"] == 2); + CHECK(payload["cached_target_files"].size() == 1); + CHECK(payload["cached_target_files"][0]["hashes"].dump() == + R"([{"algorithm":"sha256","hash":""}])"); + CHECK(payload["cached_target_files"][0]["path"] == + "datadog/2/APM_TRACING/30/name"); } SECTION("valid remote configuration") { @@ -182,21 +355,20 @@ REMOTE_CONFIG_TEST("response processing") { // } // } const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fVFJBQ0lORy8zMC9uYW1lIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImExNzc3NjhiMjBiN2M3Zjg0NDkzNWNhZTY5YzVjNWVkODhlYWFlMjM0ZTAxODJhNzgzNTk5NzMzOWU1NTI0YmMiCiAgICAgICAgICAgICAgICB9LAoJCQkJImN1c3RvbSI6IHsgInYiOiA0MiB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDQyNgogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0K", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": [ { - "path": "foo/APM_TRACING/30", + "path": "datadog/2/APM_TRACING/30/name", "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiBmYWxzZSwgInRyYWNpbmdfc2FtcGxpbmdfcmF0ZSI6IDAuNiwgInRyYWNpbmdfdGFncyI6IFsiaGVsbG86d29ybGQiLCAiZm9vOmJhciJdIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" } ] })"; // clang-format on - const auto response_json = - nlohmann::json::parse(/* input = */ json_input, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); + auto response_json = nlohmann::json::parse(/* input = */ json_input, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); REQUIRE(!response_json.is_discarded()); @@ -204,7 +376,7 @@ REMOTE_CONFIG_TEST("response processing") { 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); + const auto config_updated = rc.process_response(std::move(response_json)); REQUIRE(config_updated.size() == 3); const auto new_trace_sampler_config = config_manager->trace_sampler()->config_json(); @@ -232,20 +404,22 @@ REMOTE_CONFIG_TEST("response processing") { SECTION( "missing from client_configs -> all configurations should be reset") { // clang-format off + // targets.signed.targets == {} const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHt9LAogICAgICAgICJ2ZXJzaW9uIjogNjYyMDQzMjAKICAgIH0KfQo=", "target_files": [] })"; // clang-format on - const auto response_json = + 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); + const auto config_updated = + rc.process_response(std::move(response_json)); REQUIRE(config_updated.size() == 3); const auto current_trace_sampler_config = @@ -263,25 +437,26 @@ REMOTE_CONFIG_TEST("response processing") { "reset") { // clang-format off const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI2OWUzNDZiNWZmY2U4NDVlMjk5ODRlNzU5YjcxZDdiMDdjNTYxOTc5ZmFlOWU4MmVlZDA4MmMwMzhkODZlNmIwIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fVFJBQ0lORy8zMC9uYW1lIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjY5ZTM0NmI1ZmZjZTg0NWUyOTk4NGU3NTliNzFkN2IwN2M1NjE5NzlmYWU5ZTgyZWVkMDgyYzAzOGQ4NmU2YjAiCiAgICAgICAgICAgICAgICB9LAoJCQkJImN1c3RvbSI6IHsgInYiOiA0MiB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDM5NgogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0K", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": [ { - "path": "foo/APM_TRACING/30", + "path": "datadog/2/APM_TRACING/30/name", "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiBmYWxzZSwgInRyYWNpbmdfdGFncyI6IFsiaGVsbG86d29ybGQiLCAiZm9vOmJhciJdIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" } ] })"; // clang-format on - const auto response_json = + 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); + const auto config_updated = + rc.process_response(std::move(response_json)); REQUIRE(config_updated.size() == 1); const auto current_trace_sampler_config = config_manager->trace_sampler()->config_json(); @@ -295,22 +470,22 @@ REMOTE_CONFIG_TEST("response processing") { auto test_case = GENERATE(values({ // "service_target": { "service": "not-testsvc", "env": "test" } R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fVFJBQ0lORy8zMC9uYW1lIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImExNzc3NjhiMjBiN2M3Zjg0NDkzNWNhZTY5YzVjNWVkODhlYWFlMjM0ZTAxODJhNzgzNTk5NzMzOWU1NTI0YmMiCiAgICAgICAgICAgICAgICB9LAoJCQkJImN1c3RvbSI6IHsgInYiOiA0MiB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDM4NQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0K", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": [ { - "path": "foo/APM_TRACING/30", + "path": "datadog/2/APM_TRACING/30/name", "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAibm90LXRlc3RzdmMiLCAiZW52IjogInRlc3QiIH0gfQ==" } ] })", // "service_target": { "service": "testsvc", "env": "dev" } R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fVFJBQ0lORy8zMC9uYW1lIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImExNzc3NjhiMjBiN2M3Zjg0NDkzNWNhZTY5YzVjNWVkODhlYWFlMjM0ZTAxODJhNzgzNTk5NzMzOWU1NTI0YmMiCiAgICAgICAgICAgICAgICB9LAoJCQkJImN1c3RvbSI6IHsgInYiOiA0MiB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDM4MAogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0K", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": [ { - "path": "foo/APM_TRACING/30", + "path": "datadog/2/APM_TRACING/30/name", "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAiZGV2IiB9IH0=" } ] @@ -320,30 +495,26 @@ REMOTE_CONFIG_TEST("response processing") { CAPTURE(test_case); - const auto response_json = - nlohmann::json::parse(/* input = */ test_case, - /* parser_callback = */ nullptr, - /* allow_exceptions = */ false); + 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 config_updated = rc.process_response(std::move(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")); + auto subseq_payload = rc.make_request_payload(); + CHECK(subseq_payload["client"]["state"]["error"] == ""); + CHECK(subseq_payload["client"]["state"]["config_states"].size() == 1); + CHECK(subseq_payload["client"]["state"]["config_states"][0]["product"] == + "APM_TRACING"); + CHECK( + subseq_payload["client"]["state"]["config_states"][0]["apply_state"] == + datadog::tracing::remote_config::ConfigState::ApplyState::Acknowledged); } } diff --git a/test/test_span.cpp b/test/test_span.cpp index 1fb3eb00..6f47ffa8 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -555,7 +555,7 @@ TEST_CASE("injection can be disabled using the \"none\" style") { config.logger = std::make_shared(); config.injection_styles = {PropagationStyle::NONE}; - const auto finalized_config = finalize_config(config); + auto finalized_config = finalize_config(config); REQUIRE(finalized_config); Tracer tracer{*finalized_config}; @@ -575,7 +575,7 @@ TEST_CASE("injecting W3C traceparent header") { SECTION("extracted from W3C traceparent") { config.extraction_styles = {PropagationStyle::W3C}; - const auto finalized_config = finalize_config(config); + auto finalized_config = finalize_config(config); REQUIRE(finalized_config); // Override the tracer's ID generator to always return `expected_parent_id`. @@ -612,7 +612,7 @@ TEST_CASE("injecting W3C traceparent header") { } SECTION("not extracted from W3C traceparent") { - const auto finalized_config = finalize_config(config); + auto finalized_config = finalize_config(config); REQUIRE(finalized_config); // Override the tracer's ID generator to always return a fixed value. @@ -686,7 +686,7 @@ TEST_CASE("injecting W3C tracestate header") { config.logger = logger; config.collector = std::make_shared(); - const auto finalized_config = finalize_config(config); + auto finalized_config = finalize_config(config); REQUIRE(finalized_config); Tracer tracer{*finalized_config}; @@ -816,7 +816,7 @@ TEST_CASE("128-bit trace ID injection") { PropagationStyle::W3C, PropagationStyle::DATADOG, PropagationStyle::B3}; config.injection_styles = injection_styles; - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); class MockIDGenerator : public IDGenerator { @@ -867,7 +867,7 @@ TEST_CASE("sampling delegation injection") { SECTION("configuration") { config.delegate_trace_sampling = true; - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; @@ -893,7 +893,7 @@ TEST_CASE("sampling delegation injection") { } SECTION("injection options") { - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; @@ -911,7 +911,7 @@ TEST_CASE("sampling delegation injection") { SECTION("end-to-end") { config.delegate_trace_sampling = true; - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; diff --git a/test/test_trace_segment.cpp b/test/test_trace_segment.cpp index eddaffd9..e129ee3c 100644 --- a/test/test_trace_segment.cpp +++ b/test/test_trace_segment.cpp @@ -395,7 +395,7 @@ TEST_CASE("TraceSegment finalization of spans") { SECTION( "every span tagged with: _dd.origin, process_id, language, resource-id") { - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; diff --git a/test/test_tracer.cpp b/test/test_tracer.cpp index 428cca00..1ef8de42 100644 --- a/test/test_tracer.cpp +++ b/test/test_tracer.cpp @@ -554,7 +554,7 @@ TEST_CASE("span extraction") { SECTION("extraction can be disabled using the \"none\" style") { config.extraction_styles = {PropagationStyle::NONE}; - const auto finalized_config = finalize_config(config); + auto finalized_config = finalize_config(config); REQUIRE(finalized_config); Tracer tracer{*finalized_config}; const std::unordered_map headers{ @@ -1260,7 +1260,7 @@ TEST_CASE("128-bit trace IDs") { std::vector extraction_styles{ PropagationStyle::W3C, PropagationStyle::DATADOG, PropagationStyle::B3}; config.extraction_styles = extraction_styles; - const auto finalized = finalize_config(config, clock); + auto finalized = finalize_config(config, clock); REQUIRE(finalized); Tracer tracer{*finalized}; TraceID trace_id; // used below the following SECTIONs @@ -1378,7 +1378,7 @@ TEST_CASE( config.logger = logger; std::vector extraction_styles{PropagationStyle::W3C}; config.extraction_styles = extraction_styles; - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized};