Skip to content

Commit c4958c7

Browse files
authored
feat: report Remote Configuration status (#108)
- Fix Remote Configuration payload. - Report configuration status. - Update Remote Configuration tests. - Strengthen Remote Configuration parsing for increased resilience. - Implement scheduling of telemetry and remote configuration requests by the system-tests server. - Fix `trim` implementation.
1 parent d57bcfe commit c4958c7

File tree

5 files changed

+95
-39
lines changed

5 files changed

+95
-39
lines changed

src/datadog/remote_config.cpp

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,18 @@ ConfigUpdate parse_dynamic_config(const nlohmann::json& j) {
5555
ConfigUpdate config_update;
5656

5757
if (auto sampling_rate_it = j.find("tracing_sampling_rate");
58-
sampling_rate_it != j.cend()) {
58+
sampling_rate_it != j.cend() && sampling_rate_it->is_number()) {
5959
config_update.trace_sampling_rate = *sampling_rate_it;
6060
}
6161

62-
if (auto tags_it = j.find("tracing_tags"); tags_it != j.cend()) {
62+
if (auto tags_it = j.find("tracing_tags");
63+
tags_it != j.cend() && tags_it->is_array()) {
6364
config_update.tags = *tags_it;
6465
}
6566

6667
if (auto tracing_enabled_it = j.find("tracing_enabled");
67-
tracing_enabled_it != j.cend()) {
68-
if (tracing_enabled_it->is_boolean()) {
69-
config_update.report_traces = tracing_enabled_it->get<bool>();
70-
}
68+
tracing_enabled_it != j.cend() && tracing_enabled_it->is_boolean()) {
69+
config_update.report_traces = tracing_enabled_it->get<bool>();
7170
}
7271

7372
return config_update;
@@ -120,26 +119,35 @@ nlohmann::json RemoteConfigurationManager::make_request_payload() {
120119
if (!applied_config_.empty()) {
121120
auto config_states = nlohmann::json::array();
122121
for (const auto& [_, config] : applied_config_) {
123-
config_states.emplace_back(nlohmann::json{{"id", config.id},
124-
{"version", config.version},
125-
{"product", k_apm_product}});
122+
nlohmann::json config_state = {
123+
{"id", config.id},
124+
{"version", config.version},
125+
{"product", k_apm_product},
126+
{"apply_state", config.state},
127+
};
128+
129+
if (config.error_message) {
130+
config_state["apply_error"] = *config.error_message;
131+
}
132+
133+
config_states.emplace_back(std::move(config_state));
126134
}
127135

128-
j["config_states"] = config_states;
136+
j["client"]["state"]["config_states"] = config_states;
129137
}
130138

131139
if (state_.error_message) {
132-
j["has_error"] = true;
133-
j["error"] = *state_.error_message;
140+
j["client"]["state"]["has_error"] = true;
141+
j["client"]["state"]["error"] = *state_.error_message;
134142
}
135143

136144
return j;
137145
}
138146

139147
std::vector<ConfigMetadata> RemoteConfigurationManager::process_response(
140148
const nlohmann::json& json) {
141-
std::vector<ConfigMetadata> config_update;
142-
config_update.reserve(8);
149+
std::vector<ConfigMetadata> config_updates;
150+
config_updates.reserve(8);
143151

144152
state_.error_message = nullopt;
145153

@@ -158,12 +166,14 @@ std::vector<ConfigMetadata> RemoteConfigurationManager::process_response(
158166
if (client_configs_it == json.cend()) {
159167
if (!applied_config_.empty()) {
160168
std::for_each(applied_config_.cbegin(), applied_config_.cend(),
161-
[this, &config_update](const auto it) {
162-
config_update = revert_config(it.second);
169+
[this, &config_updates](const auto it) {
170+
auto updated = revert_config(it.second);
171+
config_updates.insert(config_updates.end(),
172+
updated.begin(), updated.end());
163173
});
164174
applied_config_.clear();
165175
}
166-
return config_update;
176+
return config_updates;
167177
}
168178

169179
// Keep track of config path received to know which ones to revert.
@@ -194,34 +204,42 @@ std::vector<ConfigMetadata> RemoteConfigurationManager::process_response(
194204
"target file having path \"";
195205
append(*state_.error_message, config_path);
196206
*state_.error_message += '\"';
197-
return config_update;
207+
return config_updates;
198208
}
199209

200210
const auto config_json = nlohmann::json::parse(
201211
base64_decode(target_it.value().at("raw").get<StringView>()));
202212

213+
Configuration new_config;
214+
new_config.id = config_json.at("id");
215+
new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer);
216+
new_config.version = config_json.at("revision");
217+
203218
const auto& targeted_service = config_json.at("service_target");
204219
if (targeted_service.at("service").get<StringView>() !=
205220
tracer_signature_.default_service ||
206221
targeted_service.at("env").get<StringView>() !=
207222
tracer_signature_.default_environment) {
208-
continue;
209-
}
223+
new_config.state = Configuration::State::error;
224+
new_config.error_message = "Wrong service targeted";
225+
} else {
226+
new_config.state = Configuration::State::acknowledged;
227+
new_config.content = parse_dynamic_config(config_json.at("lib_config"));
210228

211-
Configuration new_config;
212-
new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer);
213-
new_config.id = config_json.at("id");
214-
new_config.version = config_json.at("revision");
215-
new_config.content = parse_dynamic_config(config_json.at("lib_config"));
229+
auto updated = apply_config(new_config);
230+
config_updates.insert(config_updates.end(), updated.begin(),
231+
updated.end());
232+
}
216233

217-
config_update = apply_config(new_config);
218234
applied_config_[std::string{config_path}] = new_config;
219235
}
220236

221237
// Applied configuration not present must be reverted.
222238
for (auto it = applied_config_.cbegin(); it != applied_config_.cend();) {
223239
if (!visited_config.count(it->first)) {
224-
config_update = revert_config(it->second);
240+
auto updated = revert_config(it->second);
241+
config_updates.insert(config_updates.end(), updated.begin(),
242+
updated.end());
225243
it = applied_config_.erase(it);
226244
} else {
227245
it++;
@@ -232,10 +250,10 @@ std::vector<ConfigMetadata> RemoteConfigurationManager::process_response(
232250
error_message += e.what();
233251

234252
state_.error_message = std::move(error_message);
235-
return config_update;
253+
return config_updates;
236254
}
237255

238-
return config_update;
256+
return config_updates;
239257
}
240258

241259
std::vector<ConfigMetadata> RemoteConfigurationManager::apply_config(

src/datadog/remote_config.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ class RemoteConfigurationManager {
4242
std::string hash;
4343
std::size_t version;
4444
ConfigUpdate content;
45+
46+
enum State : char {
47+
unacknowledged = 1,
48+
acknowledged = 2,
49+
error = 3
50+
} state = State::unacknowledged;
51+
52+
Optional<std::string> error_message;
4553
};
4654

4755
TracerSignature tracer_signature_;

src/datadog/string_util.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ namespace datadog {
88
namespace tracing {
99
namespace {
1010

11+
constexpr StringView k_spaces_characters = " \f\n\r\t\v";
12+
1113
template <typename Sequence, typename Func>
1214
std::string join(const Sequence& elements, StringView separator,
1315
Func&& append_element) {
@@ -86,8 +88,9 @@ bool starts_with(StringView subject, StringView prefix) {
8688
}
8789

8890
StringView trim(StringView str) {
89-
str.remove_prefix(std::min(str.find_first_not_of(' '), str.size()));
90-
const auto pos = str.find_last_not_of(' ');
91+
str.remove_prefix(
92+
std::min(str.find_first_not_of(k_spaces_characters), str.size()));
93+
const auto pos = str.find_last_not_of(k_spaces_characters);
9194
if (pos != str.npos) str.remove_suffix(str.size() - pos - 1);
9295
return str;
9396
}

test/system-tests/manual_scheduler.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
#pragma once
22

33
#include <datadog/event_scheduler.h>
4+
#include <datadog/threaded_event_scheduler.h>
45

56
#include <cassert>
67
#include <datadog/json.hpp>
78

89
struct ManualScheduler : public datadog::tracing::EventScheduler {
910
std::function<void()> flush_traces = nullptr;
1011
std::function<void()> flush_telemetry = nullptr;
12+
datadog::tracing::ThreadedEventScheduler scheduler_;
1113

12-
Cancel schedule_recurring_event(
13-
std::chrono::steady_clock::duration /* interval */,
14-
std::function<void()> callback) override {
14+
Cancel schedule_recurring_event(std::chrono::steady_clock::duration interval,
15+
std::function<void()> callback) override {
1516
assert(callback != nullptr);
1617

1718
// NOTE: This depends on the precise order that dd-trace-cpp sets up the
1819
// `schedule_recurring_event`s for traces and telemetry.
1920
if (flush_traces == nullptr) {
2021
flush_traces = callback;
21-
return {};
22+
return []() {};
2223
}
24+
2325
if (flush_telemetry == nullptr) {
2426
flush_telemetry = callback;
25-
return {};
2627
}
27-
return []() {};
28+
29+
return scheduler_.schedule_recurring_event(interval, callback);
2830
}
2931

3032
nlohmann::json config_json() const override {

test/test_remote_config.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ REMOTE_CONFIG_TEST("response processing") {
156156

157157
// Next payload should contains an error.
158158
const auto payload = rc.make_request_payload();
159-
CHECK(payload.contains("error") == true);
160-
CHECK(payload.contains("has_error") == true);
159+
CHECK(payload.contains("/client/state/has_error"_json_pointer) == true);
160+
CHECK(payload.contains("/client/state/error"_json_pointer) == true);
161161
}
162162

163163
SECTION("valid remote configuration") {
@@ -212,6 +212,19 @@ REMOTE_CONFIG_TEST("response processing") {
212212
CHECK(new_span_defaults != old_span_defaults);
213213
CHECK(new_report_traces != old_report_traces);
214214

215+
SECTION("config status is correctly applied") {
216+
const auto payload = rc.make_request_payload();
217+
const auto s = payload.dump(2);
218+
REQUIRE(payload.contains("/client/state/config_states"_json_pointer) ==
219+
true);
220+
221+
const auto& config_states =
222+
payload.at("/client/state/config_states"_json_pointer);
223+
REQUIRE(config_states.size() == 1);
224+
CHECK(config_states[0]["product"] == "APM_TRACING");
225+
CHECK(config_states[0]["apply_state"] == 2);
226+
}
227+
215228
SECTION("reset configuration") {
216229
SECTION(
217230
"missing from client_configs -> all configurations should be reset") {
@@ -315,5 +328,17 @@ REMOTE_CONFIG_TEST("response processing") {
315328

316329
CHECK(config_updated.empty());
317330
CHECK(new_sampling_rate == old_sampling_rate);
331+
332+
// Verify next request set the config status
333+
const auto payload = rc.make_request_payload();
334+
REQUIRE(payload.contains("/client/state/config_states"_json_pointer) ==
335+
true);
336+
337+
const auto& config_states =
338+
payload.at("/client/state/config_states"_json_pointer);
339+
REQUIRE(config_states.size() == 1);
340+
CHECK(config_states[0]["product"] == "APM_TRACING");
341+
CHECK(config_states[0]["apply_state"] == 3);
342+
CHECK(config_states[0].contains("apply_state"));
318343
}
319344
}

0 commit comments

Comments
 (0)