From 44dffd014500890f7908b19da88072dcc6ddd605 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Wed, 13 Mar 2024 17:16:50 +0000 Subject: [PATCH 1/4] Generalize remote config --- src/datadog/datadog_agent.cpp | 17 +- src/datadog/datadog_agent.h | 2 +- src/datadog/datadog_agent_config.cpp | 5 +- src/datadog/datadog_agent_config.h | 16 +- src/datadog/logger.h | 6 + src/datadog/remote_config.cpp | 776 ++++++++++++++++++++++----- src/datadog/remote_config.h | 451 +++++++++++++++- src/datadog/tracer.cpp | 4 +- src/datadog/tracer.h | 4 +- src/datadog/tracer_config.cpp | 6 +- src/datadog/tracer_config.h | 6 +- test/test_curl.cpp | 2 +- test/test_remote_config.cpp | 333 +++++++++--- test/test_span.cpp | 16 +- test/test_trace_segment.cpp | 2 +- test/test_tracer.cpp | 6 +- 16 files changed, 1379 insertions(+), 273 deletions(-) diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index 457101c2..6fa8e962 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -147,7 +147,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, @@ -165,7 +165,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) { assert(logger_); assert(tracer_telemetry_); if (tracer_telemetry_->enabled()) { @@ -204,6 +204,15 @@ DatadogAgent::DatadogAgent( }); } + 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(); + cancel_remote_configuration_task_ = event_scheduler_->schedule_recurring_event( config.remote_configuration_poll_interval, @@ -421,7 +430,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); @@ -436,7 +445,7 @@ void DatadogAgent::get_and_apply_remote_configuration_updates() { // TODO (during Active Configuration): `process_response` should // return a list of configuration update and should be consumed by // telemetry. - remote_config_.process_response(response_json); + remote_config_.process_response(std::move(response_json)); } }; diff --git a/src/datadog/datadog_agent.h b/src/datadog/datadog_agent.h index 1b7f6787..6cec3635 100644 --- a/src/datadog/datadog_agent.h +++ b/src/datadog/datadog_agent.h @@ -63,7 +63,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 fd98d81d..a6ed3dc6 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -85,7 +85,7 @@ Expected DatadogAgentConfig::parse(StringView input) { } Expected finalize_config( - const DatadogAgentConfig& config, const std::shared_ptr& logger, + DatadogAgentConfig& config, const std::shared_ptr& logger, const Clock& clock) { FinalizedDatadogAgentConfig result; @@ -159,6 +159,9 @@ Expected finalize_config( result.remote_configuration_poll_interval = std::chrono::seconds(rc_poll_interval_seconds); + result.rem_cfg_listeners = std::move(config.rem_cfg_listeners); + result.rem_cfg_end_listeners = std::move(config.rem_cfg_end_listeners); + auto env_host = lookup(environment::DD_AGENT_HOST); auto env_port = lookup(environment::DD_TRACE_AGENT_PORT); diff --git a/src/datadog/datadog_agent_config.h b/src/datadog/datadog_agent_config.h index 59d9f10b..b600e445 100644 --- a/src/datadog/datadog_agent_config.h +++ b/src/datadog/datadog_agent_config.h @@ -19,6 +19,7 @@ #include "clock.h" #include "expected.h" #include "http_client.h" +#include "remote_config.h" #include "string_view.h" namespace datadog { @@ -58,12 +59,20 @@ struct DatadogAgentConfig { // updates. int remote_configuration_poll_interval_seconds = 5; + // 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; @@ -76,10 +85,13 @@ class FinalizedDatadogAgentConfig { std::chrono::steady_clock::duration request_timeout; std::chrono::steady_clock::duration shutdown_timeout; std::chrono::steady_clock::duration remote_configuration_poll_interval; + 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/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/remote_config.cpp b/src/datadog/remote_config.cpp index 89982a42..561aa8f2 100644 --- a/src/datadog/remote_config.cpp +++ b/src/datadog/remote_config.cpp @@ -1,40 +1,44 @@ #include "remote_config.h" #include -#include +#include +#include +#include +#include +#include #include #include #include "base64.h" -#include "datadog/string_view.h" +#include "config_manager.h" +#include "config_update.h" #include "json.hpp" #include "random.h" +#include "string_view.h" #include "version.h" using namespace nlohmann::literals; +using namespace std::literals; -namespace datadog { -namespace tracing { +namespace datadog::tracing { namespace { -// The ".client.capabilities" field of the remote config request payload -// describes which parts of the library's configuration are supported for remote -// configuration. -// -// It's a bitset, 64 bits wide, where each bit indicates whether the library -// supports a particular feature for remote configuration. -// -// The bitset is encoded in the request as a JSON array of 8 integers, where -// each integer is one byte from the 64 bits. The bytes are in big-endian order -// within the array. -enum CapabilitiesFlag : uint64_t { - APM_TRACING_SAMPLE_RATE = 1 << 12, -}; +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; +} -constexpr std::array capabilities_byte_array( - uint64_t in) { +// Big-endian serialization of the capabilities set +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--] = in >> (i * 8); } @@ -42,44 +46,101 @@ constexpr std::array capabilities_byte_array( return res; } -constexpr std::array k_apm_capabilities = - capabilities_byte_array(APM_TRACING_SAMPLE_RATE); +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()); +} + +// 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) override { + const auto config_json = nlohmann::json::parse(file_contents); + + if (!service_env_match(config_json)) { + return; + } + + ConfigUpdate const dyn_config = + parse_dynamic_config(config_json.at("lib_config")); -constexpr StringView k_apm_product = "APM_TRACING"; -constexpr StringView k_apm_product_path_substring = "/APM_TRACING/"; + config_manager_->update(dyn_config); + applied_configs_.emplace(key); + } -ConfigUpdate parse_dynamic_config(const nlohmann::json& j) { - ConfigUpdate config_update; + void on_config_remove(const remote_config::ParsedConfigKey& key) override { + if (applied_configs_.erase(key) > 0) { + config_manager_->reset(); + } + } - if (auto sampling_rate_it = j.find("tracing_sampling_rate"); - sampling_rate_it != j.cend()) { - TraceSamplerConfig trace_sampler_cfg; - trace_sampler_cfg.sample_rate = *sampling_rate_it; + remote_config::CapabilitiesSet capabilities() const override { + return remote_config::Capability::APM_TRACING_SAMPLE_RATE; + } - config_update.trace_sampler = trace_sampler_cfg; + private: + bool service_env_match(const nlohmann::json& config_json) { + const auto& targeted_service = config_json.find("service_target"); + if (targeted_service == config_json.cend() || + !targeted_service->is_object()) { + return false; + } + return targeted_service->at("service").get() == + tracer_signature_.default_service && + targeted_service->at("env").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"); + sampling_rate_it != j.cend()) { + TraceSamplerConfig trace_sampler_cfg; + trace_sampler_cfg.sample_rate = *sampling_rate_it; + config_update.trace_sampler = trace_sampler_cfg; + } + + 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() { @@ -87,144 +148,583 @@ 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_) { - config_states.emplace_back(nlohmann::json{{"id", config.id}, - {"version", config.version}, - {"product", k_apm_product}}); + 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()); + + 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["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["has_error"] = true; - j["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; } -void RemoteConfigurationManager::process_response(const nlohmann::json& json) { - state_.error_message = nullopt; +void RemoteConfigurationManager::process_response(nlohmann::json&& json) { + std::optional resp; + std::vector errors; try { - const auto targets = nlohmann::json::parse( - base64_decode(json.at("targets").get())); - - state_.targets_version = targets.at("/signed/version"_json_pointer); - state_.opaque_backend_state = - targets.at("/signed/custom/opaque_backend_state"_json_pointer); - - const auto client_configs_it = json.find("client_configs"); - - // `client_configs` is absent => remove previously applied configuration if - // any applied. - if (client_configs_it == json.cend()) { - if (!applied_config_.empty()) { - std::for_each(applied_config_.cbegin(), applied_config_.cend(), - [this](const auto it) { revert_config(it.second); }); - applied_config_.clear(); - } + auto maybe_resp = + remote_config::RemoteConfigResponse::from_json(std::move(json)); + if (!maybe_resp) { + logger_->log_debug( + "Remote Configuration response is empty (no change)"sv); 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"sv); + resp.emplace(std::move(*maybe_resp)); + resp->validate(); + + // check if the backend returned configuration for unscribed 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); + 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) || 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 (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; - } + if (resp) { + update_next_state({std::ref(*resp)}, build_error_message(errors)); + } else { + update_next_state({}, build_error_message(errors)); + } +} + +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)); +} - const auto config_json = nlohmann::json::parse( - base64_decode(target_it.value().at("raw").get())); +void RemoteConfigurationManager::add_config_end_listener( + std::function listener) { + config_end_listeners_.emplace_back(std::move(listener)); +} - 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) { - continue; - } +Optional RemoteConfigurationManager::build_error_message( + std::vector& errors) { + if (errors.empty()) { + return std::nullopt; + } + if (errors.size() == 1) { + return {std::move(errors.front())}; + } - Configuration new_config; - new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer); - new_config.id = config_json.at("id"); - new_config.version = config_json.at("revision"); - new_config.content = parse_dynamic_config(config_json.at("lib_config")); + std::string msg{"Failed to apply configuration due to multiple errors: "}; + for (auto&& e : errors) { + msg += e; + msg += "; "; + } + return {std::move(msg)}; +} - apply_config(new_config); - applied_config_[std::string{config_path}] = new_config; +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"sv; + 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"sv; + org_id_ = 0; + } + + StringView const product_sv{submatch_to_sv(smatch[2])}; + product_ = &Product::KnownProducts::for_name(product_sv); - // Applied configuration not present must be reverted. - for (auto it = applied_config_.cbegin(); it != applied_config_.cend();) { - if (!visited_config.count(it->first)) { - revert_config(it->second); - it = applied_config_.erase(it); - } else { - it++; + 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)}, + targets_{std::move(targets)}, + targets_signed_{targets_.at("signed"sv)} {} + +std::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 std::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 std::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 {}; + } + 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 {}; + } + + 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 {{}}; + } + + 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 {}; +} + +bool ProductState::apply(const RemoteConfigResponse& response) { + 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, 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(); } - } catch (const nlohmann::json::exception& e) { - std::string error_message = "Ill-formatted Remote Configuration response: "; - error_message += e.what(); + } + + // 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(it); + } else { + it++; + } + } + + return changes_detected; +} + +void ProductState::call_listeners_apply(const RemoteConfigResponse& resp, + const ParsedConfigKey& key, + const std::string& file_contents) { + try { + for (auto&& l : listeners_) { + l->on_config_update(key, file_contents); + } + + 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( + ProductState::per_key_state_citerator_t it) { + const ParsedConfigKey& key = it->first; + try { + for (auto&& l : listeners_) { + l->on_config_remove(key); + } + } 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(); + } + + return per_key_state_.erase(it); +} - state_.error_message = std::move(error_message); +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; } -void RemoteConfigurationManager::apply_config(Configuration config) { - config_manager_->update(config.content); +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); } -void RemoteConfigurationManager::revert_config(Configuration) { - config_manager_->reset(); +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()); +} + +CachedTargetFile ConfigTarget::to_cached_target_file( + const ParsedConfigKey& key) const { + auto length = json_.at(std::string_view{"length"}).get(); + std::vector hashes; + + auto&& hashes_json = json_.at(std::string_view{"hashes"}); + 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"sv) { + 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 a4521a75..a3901ab6 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,38 +26,426 @@ #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 = 1; - 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_LOGS_INJECTION = 1 << 13, + APM_HTTP_HEADER_TAGS = 1 << 14, + APM_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, +}; + +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 { + return std::hash()(p.name_); + } + }; + + 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_); + } }; - // Holds information about a specific configuration update, - // including its identifier, hash value, version number and the content. - struct Configuration { - std::string id; + // 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; }; + 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}; + } +}; + +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 std::optional from_json(nlohmann::json &&json); + + void validate() { + verify_targets_presence(); + verify_client_configs(); + } + + uint64_t targets_version() const { + return targets_signed_.at("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) = 0; + virtual void on_config_remove(const ParsedConfigKey &key) = 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); + + void call_listeners_apply(const RemoteConfigResponse &resp, + 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(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; + std::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. @@ -62,18 +453,20 @@ class RemoteConfigurationManager { // Handles the response received from a remote source and udates the internal // state accordingly. - void process_response(const nlohmann::json& json); + void 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. - void apply_config(Configuration config); + nlohmann::json::array_t serialize_config_states() const; + nlohmann::json serialize_cached_target_files() const; - // Revert a remote configuration. - void revert_config(Configuration config); + void update_next_state( + Optional< + std::reference_wrapper> + rcr, + std::optional error); }; -} // namespace tracing -} // namespace datadog +} // namespace datadog::tracing diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 9ca6a35a..80f27d7e 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.trace_id_128_bit)) {} -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 57942909..947dae7e 100644 --- a/src/datadog/tracer.h +++ b/src/datadog/tracer.h @@ -54,8 +54,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 4c517af6..361b5aad 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -264,11 +264,11 @@ Expected finalize_propagation_styles(FinalizedTracerConfig &result, } // namespace -Expected finalize_config(const TracerConfig &config) { +Expected finalize_config(TracerConfig &config) { return finalize_config(config, default_clock); } -Expected finalize_config(const TracerConfig &config, +Expected finalize_config(TracerConfig &config, const Clock &clock) { FinalizedTracerConfig result; @@ -324,7 +324,7 @@ Expected finalize_config(const TracerConfig &config, if (auto *error = finalized.if_error()) { return std::move(*error); } - result.collector = *finalized; + result.collector = std::move(*finalized); } else { result.collector = config.collector; } diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index 93546337..6e2a00a9 100644 --- a/src/datadog/tracer_config.h +++ b/src/datadog/tracer_config.h @@ -139,7 +139,7 @@ struct TracerConfig { // `FinalizedTracerConfig` must be obtained by calling `finalize_config`. class FinalizedTracerConfig { friend Expected finalize_config( - const TracerConfig& config, const Clock& clock); + TracerConfig& config, const Clock& clock); FinalizedTracerConfig() = default; public: @@ -174,8 +174,8 @@ class FinalizedTracerConfig { // 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 1c9873e4..9fcad265 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_remote_config.cpp b/test/test_remote_config.cpp index 3bf06bfe..87e519be 100644 --- a/test/test_remote_config.cpp +++ b/test/test_remote_config.cpp @@ -1,8 +1,11 @@ #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/tracer_config.h" #include "mocks/loggers.h" #include "test.h" @@ -31,17 +34,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, 0, 0, 16, 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); - CHECK(payload["client"]["state"]["targets_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") { @@ -66,113 +79,275 @@ 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, + auto response_json = + nlohmann::json::parse(/* input = */ test_case.first, /* parser_callback = */ nullptr, /* allow_exceptions = */ false); REQUIRE(!response_json.is_discarded()); - rc.process_response(response_json); + rc.process_response(std::move(response_json)); - // Next payload should contains an error. + // Next payload should contain an error. const auto payload = rc.make_request_payload(); - CHECK(payload.contains("error") == true); - CHECK(payload.contains("has_error") == true); + CHECK(payload["client"]["state"]["has_error"] == true); + CHECK(payload["client"]["state"]["error"] == 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); + + rc.process_response(std::move(response_json)); + + // Next payload should not contain global error. + const auto payload = rc.make_request_payload(); + 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"] == + 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") { // clang-format off const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fVFJBQ0lORy8zMC9uYW1lIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImExNzc3NjhiMjBiN2M3Zjg0NDkzNWNhZTY5YzVjNWVkODhlYWFlMjM0ZTAxODJhNzgzNTk5NzMzOWU1NTI0YmMiCiAgICAgICAgICAgICAgICB9LAoJCQkJImN1c3RvbSI6IHsgInYiOiA0MiB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDM4MQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0K", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": [ { - "path": "foo/APM_TRACING/30", + "path": "datadog/2/APM_TRACING/30/name", "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" } ] })"; // clang-format on - const auto response_json = + auto response_json = nlohmann::json::parse(/* input = */ json_input, /* parser_callback = */ nullptr, /* allow_exceptions = */ false); @@ -180,29 +355,30 @@ REMOTE_CONFIG_TEST("response processing") { REQUIRE(!response_json.is_discarded()); const auto old_trace_sampler = config_manager->get_trace_sampler(); - rc.process_response(response_json); + rc.process_response(std::move(response_json)); const auto new_trace_sampler = config_manager->get_trace_sampler(); CHECK(new_trace_sampler != old_trace_sampler); - SECTION("reset confguration") { + SECTION("reset configuration") { 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()); - rc.process_response(response_json); + rc.process_response(std::move(response_json)); const auto current_trace_sampler = config_manager->get_trace_sampler(); CHECK(old_trace_sampler == current_trace_sampler); } @@ -210,25 +386,25 @@ REMOTE_CONFIG_TEST("response processing") { SECTION("missing configuration field -> field should be reset") { // clang-format off const std::string json_input = R"({ - "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI2OWUzNDZiNWZmY2U4NDVlMjk5ODRlNzU5YjcxZDdiMDdjNTYxOTc5ZmFlOWU4MmVlZDA4MmMwMzhkODZlNmIwIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", - "client_configs": ["foo/APM_TRACING/30"], + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fVFJBQ0lORy8zMC9uYW1lIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjY5ZTM0NmI1ZmZjZTg0NWUyOTk4NGU3NTliNzFkN2IwN2M1NjE5NzlmYWU5ZTgyZWVkMDgyYzAzOGQ4NmU2YjAiCiAgICAgICAgICAgICAgICB9LAoJCQkJImN1c3RvbSI6IHsgInYiOiA0MiB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDM1MQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDY2MjA0MzIwCiAgICB9Cn0K", + "client_configs": ["datadog/2/APM_TRACING/30/name"], "target_files": [ { - "path": "foo/APM_TRACING/30", + "path": "datadog/2/APM_TRACING/30/name", "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" } ] })"; // 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()); - rc.process_response(response_json); + rc.process_response(std::move(response_json)); const auto current_trace_sampler = config_manager->get_trace_sampler(); CHECK(old_trace_sampler == current_trace_sampler); } @@ -240,22 +416,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=" } ] @@ -265,7 +441,7 @@ REMOTE_CONFIG_TEST("response processing") { CAPTURE(test_case); - const auto response_json = + auto response_json = nlohmann::json::parse(/* input = */ test_case, /* parser_callback = */ nullptr, /* allow_exceptions = */ false); @@ -273,9 +449,16 @@ REMOTE_CONFIG_TEST("response processing") { REQUIRE(!response_json.is_discarded()); const auto old_sampling_rate = config_manager->get_trace_sampler(); - rc.process_response(response_json); + rc.process_response(std::move(response_json)); const auto new_sampling_rate = config_manager->get_trace_sampler(); CHECK(new_sampling_rate == old_sampling_rate); + + 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]["apply_state"] == + datadog::tracing::remote_config::ConfigState::ApplyState::Acknowledged); } } diff --git a/test/test_span.cpp b/test/test_span.cpp index 671cea10..df70a754 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -454,7 +454,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}; @@ -474,7 +474,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`. @@ -511,7 +511,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. @@ -584,7 +584,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}; @@ -711,7 +711,7 @@ TEST_CASE("128-bit trace ID injection") { config.injection_styles.push_back(PropagationStyle::DATADOG); config.injection_styles.push_back(PropagationStyle::B3); - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); class MockIDGenerator : public IDGenerator { @@ -762,7 +762,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}; @@ -788,7 +788,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}; @@ -806,7 +806,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 6d54ea80..e733f2c0 100644 --- a/test/test_trace_segment.cpp +++ b/test/test_trace_segment.cpp @@ -384,7 +384,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 a5ab0bbf..69bc2ac2 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{ @@ -1081,7 +1081,7 @@ TEST_CASE("128-bit trace IDs") { config.extraction_styles.push_back(PropagationStyle::W3C); config.extraction_styles.push_back(PropagationStyle::DATADOG); config.extraction_styles.push_back(PropagationStyle::B3); - 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 @@ -1199,7 +1199,7 @@ TEST_CASE( config.logger = logger; config.extraction_styles.clear(); config.extraction_styles.push_back(PropagationStyle::W3C); - const auto finalized = finalize_config(config); + auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; From fc5bc77bd026fbcbfeda3752c1694791c2f3643c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 25 Mar 2024 18:26:04 +0000 Subject: [PATCH 2/4] Fix build --- src/datadog/datadog_agent.cpp | 2 +- src/datadog/datadog_agent_config.cpp | 4 +- src/datadog/remote_config.cpp | 82 ++++++++++++++-------------- src/datadog/remote_config.h | 34 ++++++++---- src/datadog/tracer_config.h | 4 +- 5 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index f5c4aa71..14d21808 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -208,7 +208,7 @@ DatadogAgent::DatadogAgent( remote_config_.add_listener(std::move(l)); } config.rem_cfg_listeners.clear(); - for (auto &&l : config.rem_cfg_end_listeners) { + for (auto&& l : config.rem_cfg_end_listeners) { remote_config_.add_config_end_listener(std::move(l)); } config.rem_cfg_end_listeners.clear(); diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index fe930db5..6e40ca54 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -44,8 +44,8 @@ Expected load_datadog_agent_env_config() { } Expected finalize_config( - 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; diff --git a/src/datadog/remote_config.cpp b/src/datadog/remote_config.cpp index f8f9c6e7..f2ef9036 100644 --- a/src/datadog/remote_config.cpp +++ b/src/datadog/remote_config.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -18,9 +17,13 @@ #include "version.h" using namespace nlohmann::literals; -using namespace std::literals; namespace datadog::tracing { + +inline StringView operator""_sv(const char* str, size_t len) noexcept { + return StringView{str, len}; +} + namespace { template @@ -79,7 +82,7 @@ class TracingProductListener : public remote_config::ProductListener { } ConfigUpdate const dyn_config = - parse_dynamic_config(config_json.at("lib_config")); + parse_dynamic_config(config_json.at("lib_config"_sv)); std::vector metadata = config_manager_->update(dyn_config); config_update.insert(config_update.end(), metadata.begin(), metadata.end()); @@ -105,30 +108,30 @@ class TracingProductListener : public remote_config::ProductListener { private: bool service_env_match(const nlohmann::json& config_json) { - const auto& targeted_service = config_json.find("service_target"); + 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").get() == + return targeted_service->at("service"_sv).get() == tracer_signature_.default_service && - targeted_service->at("env").get() == + targeted_service->at("env"_sv).get() == tracer_signature_.default_environment; } static ConfigUpdate parse_dynamic_config(const nlohmann::json& j) { ConfigUpdate config_update; - if (auto sampling_rate_it = j.find("tracing_sampling_rate"); + if (auto sampling_rate_it = j.find("tracing_sampling_rate"_sv); sampling_rate_it != j.cend()) { config_update.trace_sampling_rate = *sampling_rate_it; } - if (auto tags_it = j.find("tracing_tags"); tags_it != j.cend()) { + if (auto tags_it = j.find("tracing_tags"_sv); tags_it != j.cend()) { config_update.tags = *tags_it; } - if (auto tracing_enabled_it = j.find("tracing_enabled"); + if (auto tracing_enabled_it = j.find("tracing_enabled"_sv); tracing_enabled_it != j.cend()) { if (tracing_enabled_it->is_boolean()) { config_update.report_traces = tracing_enabled_it->get(); @@ -250,7 +253,7 @@ nlohmann::json RemoteConfigurationManager::serialize_cached_target_files() std::vector RemoteConfigurationManager::process_response( nlohmann::json&& json) { - std::optional resp; + Optional resp; std::vector errors; std::vector config_update; @@ -258,12 +261,11 @@ std::vector RemoteConfigurationManager::process_response( auto maybe_resp = remote_config::RemoteConfigResponse::from_json(std::move(json)); if (!maybe_resp) { - logger_->log_debug( - "Remote Configuration response is empty (no change)"sv); + logger_->log_debug("Remote Configuration response is empty (no change)"); return {}; } - logger_->log_debug("Got nonempty Remote Configuration response"sv); + logger_->log_debug("Got nonempty Remote Configuration response"); resp.emplace(std::move(*maybe_resp)); resp->validate(); @@ -346,7 +348,7 @@ void RemoteConfigurationManager::add_config_end_listener( Optional RemoteConfigurationManager::build_error_message( std::vector& errors) { if (errors.empty()) { - return std::nullopt; + return nullopt; } if (errors.size() == 1) { return {std::move(errors.front())}; @@ -402,7 +404,7 @@ void ParsedConfigKey::parse_config_key() { } if (key_[0] == 'd') { - source_ = "datadog"sv; + source_ = "datadog"; auto [ptr, ec] = std::from_chars(&*smatch[1].first, &*smatch[1].second, org_id_); if (ec != std::errc{} || ptr != &*smatch[1].second) { @@ -410,7 +412,7 @@ void ParsedConfigKey::parse_config_key() { std::string{submatch_to_sv(smatch[1])}); } } else { - source_ = "employee"sv; + source_ = "employee"; org_id_ = 0; } @@ -423,16 +425,16 @@ void ParsedConfigKey::parse_config_key() { RemoteConfigResponse::RemoteConfigResponse(nlohmann::json full_response, nlohmann::json targets) - : json_{std::move(full_response)}, - targets_{std::move(targets)}, - targets_signed_{targets_.at("signed"sv)} {} + : json_(std::move(full_response)), // can't use {}-initialization + targets_(std::move(targets)), // idem + targets_signed_{targets_.at("signed"_sv)} {} -std::optional RemoteConfigResponse::from_json( +Optional RemoteConfigResponse::from_json( nlohmann::json&& json) { - const auto targets_encoded = json.find("targets"sv); + const auto targets_encoded = json.find("targets"_sv); if (targets_encoded == json.cend()) { // empty response -> no change - return std::nullopt; + return nullopt; } if (!targets_encoded->is_string()) { @@ -441,13 +443,13 @@ std::optional RemoteConfigResponse::from_json( "string"); } - if (targets_encoded->get().empty()) { + if (targets_encoded->get().empty()) { // empty response -> no change - return std::nullopt; + return nullopt; } // if targets is not empty, we need targets.signed - std::string decoded = base64_decode(targets_encoded->get()); + std::string decoded = base64_decode(targets_encoded->get()); if (decoded.empty()) { throw reportable_error( "Invalid Remote Configuration response: invalid base64 data for " @@ -455,7 +457,7 @@ std::optional RemoteConfigResponse::from_json( } auto targets = nlohmann::json::parse(decoded); - auto t_signed = targets.find("signed"sv); + auto t_signed = targets.find("signed"_sv); if (t_signed == targets.cend()) { throw reportable_error( "Invalid Remote Configuration response: missing " @@ -467,7 +469,7 @@ std::optional RemoteConfigResponse::from_json( 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); + auto target_files = json_.find("target_files"_sv); if (target_files == json_.cend()) { return; } @@ -478,14 +480,14 @@ void RemoteConfigResponse::verify_targets_presence() const { } for (auto it = target_files->begin(); it != target_files->end(); ++it) { - auto path = it->find("path"sv); + 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(); + auto path_sv = path->get(); if (!get_target(path_sv)) { throw reportable_error( "Invalid Remote Configuration response: " @@ -497,7 +499,7 @@ void RemoteConfigResponse::verify_targets_presence() const { } void RemoteConfigResponse::verify_client_configs() { - auto&& client_cfgs = json_.find("client_configs"sv); + auto&& client_cfgs = json_.find("client_configs"_sv); if (client_cfgs == json_.end()) { return; } @@ -516,19 +518,19 @@ void RemoteConfigResponse::verify_client_configs() { } Optional RemoteConfigResponse::get_target(StringView key) const { - auto&& targets = targets_signed_.at("targets"sv); + auto&& targets = targets_signed_.at("targets"_sv); auto&& target = targets.find(key); if (target == targets.cend()) { - return {}; + return nullopt; } return ConfigTarget{*target}; } Optional RemoteConfigResponse::get_file_contents( const ParsedConfigKey& key) const { - auto&& target_files = json_.find("target_files"sv); + auto&& target_files = json_.find("target_files"_sv); if (target_files == json_.cend() || !target_files->is_array()) { - return {}; + return nullopt; } Optional target = get_target(key); @@ -539,7 +541,7 @@ Optional RemoteConfigResponse::get_file_contents( } for (auto&& file : *target_files) { - auto&& path = file.at("path"sv).get(); + auto&& path = file.at("path"_sv).get(); if (path != key.full_key()) { continue; } @@ -550,7 +552,7 @@ Optional RemoteConfigResponse::get_file_contents( return {{}}; } - const auto raw = file.at("raw"sv).get(); + const auto raw = file.at("raw"_sv).get(); auto decoded = base64_decode(raw); if (decoded.empty()) { throw reportable_error( @@ -569,7 +571,7 @@ Optional RemoteConfigResponse::get_file_contents( return {std::move(decoded)}; } - return {}; + return nullopt; } bool ProductState::apply(const RemoteConfigResponse& response, @@ -727,10 +729,10 @@ void ProductState::update_config_state(const RemoteConfigResponse& response, CachedTargetFile ConfigTarget::to_cached_target_file( const ParsedConfigKey& key) const { - auto length = json_.at(std::string_view{"length"}).get(); + auto length = json_.at("length"_sv).get(); std::vector hashes; - auto&& hashes_json = json_.at(std::string_view{"hashes"}); + auto&& hashes_json = json_.at("hashes"_sv); if (!hashes_json.is_object()) { throw reportable_error( "Invalid Remote Configuration response in config_target: " @@ -739,7 +741,7 @@ CachedTargetFile ConfigTarget::to_cached_target_file( hashes.reserve(hashes_json.size()); bool found_sha256 = false; for (auto&& [algo, hash] : hashes_json.items()) { - if (algo == "sha256"sv) { + if (algo == "sha256") { found_sha256 = true; } hashes.emplace_back( diff --git a/src/datadog/remote_config.h b/src/datadog/remote_config.h index d8f5bf17..6bc14eb3 100644 --- a/src/datadog/remote_config.h +++ b/src/datadog/remote_config.h @@ -73,7 +73,8 @@ enum class Capability : uint64_t { class CapabilitiesSet { std::underlying_type_t value_{}; - public: + + public: CapabilitiesSet() = default; // NOLINTNEXTLINE CapabilitiesSet(Capability c) : value_{static_cast(c)} {} @@ -101,7 +102,21 @@ class Product { struct Hash { std::size_t operator()(const Product &p) const { - return std::hash()(p.name_); +#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 } }; @@ -173,7 +188,7 @@ class ParsedConfigKey { oth.config_id_ = {}; oth.name_ = {}; } - ParsedConfigKey& operator=(ParsedConfigKey&& oth) noexcept { + ParsedConfigKey &operator=(ParsedConfigKey &&oth) noexcept { if (&oth != this) { key_ = std::move(oth.key_); source_ = oth.source_; @@ -231,7 +246,7 @@ struct CachedTargetFile { std::uint64_t length; std::vector hashes; - bool empty() const { return hashes.empty(); } // NOLINT + 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"; @@ -283,7 +298,7 @@ class ConfigTarget { class RemoteConfigResponse { public: - static std::optional from_json(nlohmann::json &&json); + static Optional from_json(nlohmann::json &&json); void validate() { verify_targets_presence(); @@ -291,7 +306,7 @@ class RemoteConfigResponse { } uint64_t targets_version() const { - return targets_signed_.at("version").get(); + return targets_signed_.at(StringView{"version"}).get(); } std::string opaque_backend_state() const { return targets_signed_.at("/custom/opaque_backend_state"_json_pointer) @@ -394,7 +409,7 @@ class ProductState { return caps; } - template + 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()) { @@ -425,7 +440,6 @@ class ProductState { const CachedTargetFile &ctf = st->second.cached_target_file; return ctf.sha256() != new_target.sha256(); } - }; } // namespace remote_config @@ -441,7 +455,7 @@ class RemoteConfigurationManager { uint64_t root_version = 1UL; uint64_t targets_version{}; std::vector> config_states; - std::optional error; // has_error + error + Optional error; // has_error + error std::string backend_client_state; }; @@ -480,7 +494,7 @@ class RemoteConfigurationManager { Optional< std::reference_wrapper> rcr, - std::optional error); + Optional error); }; } // namespace datadog::tracing diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index 8bae37ae..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( - TracerConfig& config, const Clock& clock); + friend Expected finalize_config(TracerConfig& config, + const Clock& clock); FinalizedTracerConfig() = default; public: From a41e923b646609471702aed2da79a36d288dd98c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 28 Mar 2024 14:55:09 +0000 Subject: [PATCH 3/4] Fix compatibility with older GCC Directly converting a json node into std::vector doesn't owkr in older GCCs (tried: 8.3.0) --- CMakeLists.txt | 2 +- src/datadog/config_update.h | 2 +- src/datadog/parse_util.cpp | 14 ++++++++++++-- src/datadog/parse_util.h | 4 +++- src/datadog/remote_config.cpp | 10 +++++----- src/datadog/string_util.cpp | 9 ++++++++- src/datadog/string_util.h | 1 + 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1838193..c7e0d8bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # and so won't link to libc++. So, if any of the FUZZ_* variables are set, # keep to libstdc++ (the default on most systems). if (NOT ${BUILD_FUZZERS}) - add_compile_options(-stdlib=libc++) + add_compile_options($<$:-stdlib=libc++>) add_link_options(-stdlib=libc++) endif () endif() diff --git a/src/datadog/config_update.h b/src/datadog/config_update.h index cd7e07f4..1b8af30d 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; }; } // namespace tracing diff --git a/src/datadog/parse_util.cpp b/src/datadog/parse_util.cpp index 7632419f..6960cfa4 100644 --- a/src/datadog/parse_util.cpp +++ b/src/datadog/parse_util.cpp @@ -150,11 +150,12 @@ std::vector parse_list(StringView input) { return items; } +template Expected> parse_tags( - std::vector list) { + std::vector list) { std::unordered_map tags; - for (const StringView &token : list) { + for (const auto &token : list) { const auto separator = std::find(token.begin(), token.end(), ':'); if (separator == token.end()) { std::string message; @@ -177,5 +178,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); +} + } // namespace tracing } // namespace datadog diff --git a/src/datadog/parse_util.h b/src/datadog/parse_util.h index c2104447..f6b1d23e 100644 --- a/src/datadog/parse_util.h +++ b/src/datadog/parse_util.h @@ -47,7 +47,9 @@ void to_lower(std::string& text); std::vector parse_list(StringView input); Expected> parse_tags( - std::vector list); + const std::vector& list); +Expected> parse_tags( + const std::vector& list); inline Expected> parse_tags( StringView input) { diff --git a/src/datadog/remote_config.cpp b/src/datadog/remote_config.cpp index f2ef9036..9df5675b 100644 --- a/src/datadog/remote_config.cpp +++ b/src/datadog/remote_config.cpp @@ -128,7 +128,7 @@ class TracingProductListener : public remote_config::ProductListener { } if (auto tags_it = j.find("tracing_tags"_sv); tags_it != j.cend()) { - config_update.tags = *tags_it; + config_update.tags = tags_it->get>(); } if (auto tracing_enabled_it = j.find("tracing_enabled"_sv); @@ -322,9 +322,9 @@ std::vector RemoteConfigurationManager::process_response( } if (resp) { - update_next_state({std::ref(*resp)}, build_error_message(errors)); + update_next_state({*resp}, build_error_message(errors)); } else { - update_next_state({}, build_error_message(errors)); + update_next_state(nullopt, build_error_message(errors)); } return config_update; @@ -394,7 +394,7 @@ StringView submatch_to_sv(const SubMatch& sub_match) { return StringView{&*sub_match.first, static_cast(sub_match.length())}; } -}; // namespace +} // namespace void ParsedConfigKey::parse_config_key() { std::regex const rgx{"(?:datadog/(\\d+)|employee)/([^/]+)/([^/]+)/([^/]+)"}; @@ -549,7 +549,7 @@ Optional RemoteConfigResponse::get_file_contents( // TODO check sha256 hash auto expected_len = target->length(); if (expected_len == 0) { - return {{}}; + return {std::string{}}; } const auto raw = file.at("raw"_sv).get(); diff --git a/src/datadog/string_util.cpp b/src/datadog/string_util.cpp index 9384c81f..5a3d797d 100644 --- a/src/datadog/string_util.cpp +++ b/src/datadog/string_util.cpp @@ -34,11 +34,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 255a4b3c..72a9c522 100644 --- a/src/datadog/string_util.h +++ b/src/datadog/string_util.h @@ -17,6 +17,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&); From 0d825f0f563beb57b72ffecebc769523f947f0b9 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 4 Jun 2024 13:55:34 +0100 Subject: [PATCH 4/4] Fix build on MSVC --- test/test_remote_config.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_remote_config.cpp b/test/test_remote_config.cpp index 2029bdd2..b053127c 100644 --- a/test/test_remote_config.cpp +++ b/test/test_remote_config.cpp @@ -276,7 +276,8 @@ REMOTE_CONFIG_TEST("response processing") { // 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"] == test_case.second); + CHECK(payload["client"]["state"]["error"].get() == + test_case.second); } SECTION("error applying configuration") { @@ -322,8 +323,8 @@ REMOTE_CONFIG_TEST("response processing") { "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"] == - test_case.second); + 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);