master
c 460 lines 16.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "stream-path.h"
4 #include "stream.h"
5 #include "stream-receiver-internals.h"
6 #include "stream-sender-internals.h"
7 #include "plugins.d/pluginsd_internals.h"
8
9 typedef enum __attribute__((packed)) {
10 STREAM_PATH_FLAG_NONE = 0,
11 STREAM_PATH_FLAG_ACLK = (1 << 0),
12 STREAM_PATH_FLAG_HEALTH = (1 << 1),
13 STREAM_PATH_FLAG_ML = (1 << 2),
14 STREAM_PATH_FLAG_EPHEMERAL = (1 << 3),
15 STREAM_PATH_FLAG_VIRTUAL = (1 << 4),
16 } STREAM_PATH_FLAGS;
17
18 typedef struct stream_path {
19 STRING *hostname; // the hostname of the agent
20 ND_UUID host_id; // the machine guid of the agent
21 ND_UUID node_id; // the cloud node id of the agent
22 ND_UUID claim_id; // the cloud claim id of the agent
23 time_t since; // the timestamp of the last update
24 time_t first_time_t; // the oldest timestamp in the db
25 int16_t hops; // -1 = stale node, 0 = localhost, >0 the hops count
26 STREAM_PATH_FLAGS flags; // ACLK or NONE for the moment
27 STREAM_CAPABILITIES capabilities; // streaming connection capabilities
28 uint32_t start_time_ms; // median time in ms the agent needs to start
29 uint32_t shutdown_time_ms; // median time in ms the agent needs to shutdown
30 } STREAM_PATH;
31
32 ENUM_STR_MAP_DEFINE(STREAM_PATH_FLAGS) = {
33 { .id = STREAM_PATH_FLAG_ACLK, .name = "aclk" },
34 { .id = STREAM_PATH_FLAG_HEALTH, .name = "health" },
35 { .id = STREAM_PATH_FLAG_ML, .name = "ml" },
36 { .id = STREAM_PATH_FLAG_EPHEMERAL, .name = "ephemeral" },
37 { .id = STREAM_PATH_FLAG_VIRTUAL, .name = "virtual" },
38
39 // terminator
40 { . id = 0, .name = NULL }
41 };
42
43 BITMAP_STR_DEFINE_FUNCTIONS(STREAM_PATH_FLAGS, STREAM_PATH_FLAG_NONE, "");
44
45 static void stream_path_cleanup(STREAM_PATH *p) {
46 string_freez(p->hostname);
47 p->hostname = NULL;
48 p->host_id = UUID_ZERO;
49 p->node_id = UUID_ZERO;
50 p->claim_id = UUID_ZERO;
51 p->hops = 0;
52 p->since = 0;
53 p->first_time_t = 0;
54 p->capabilities = 0;
55 p->flags = STREAM_PATH_FLAG_NONE;
56 p->start_time_ms = 0;
57 p->shutdown_time_ms = 0;
58 }
59
60 static void rrdhost_stream_path_clear_unsafe(RRDHOST *host, bool destroy) {
61 for(size_t i = 0; i < host->stream.path.used ; i++)
62 stream_path_cleanup(&host->stream.path.array[i]);
63
64 host->stream.path.used = 0;
65
66 if(destroy) {
67 freez(host->stream.path.array);
68 host->stream.path.array = NULL;
69 host->stream.path.size = 0;
70 }
71 }
72
73 void rrdhost_stream_path_clear(RRDHOST *host, bool destroy) {
74 rw_spinlock_write_lock(&host->stream.path.spinlock);
75 rrdhost_stream_path_clear_unsafe(host, destroy);
76 rw_spinlock_write_unlock(&host->stream.path.spinlock);
77 }
78
79 static void stream_path_to_json_object(BUFFER *wb, STREAM_PATH *p) {
80 buffer_json_add_array_item_object(wb);
81 {
82 buffer_json_member_add_uint64(wb, "version", 1);
83 buffer_json_member_add_string(wb, "hostname", string2str(p->hostname));
84 buffer_json_member_add_uuid(wb, "host_id", p->host_id.uuid);
85 buffer_json_member_add_uuid(wb, "node_id", p->node_id.uuid);
86 buffer_json_member_add_uuid(wb, "claim_id", p->claim_id.uuid);
87 buffer_json_member_add_int64(wb, "hops", p->hops);
88 buffer_json_member_add_uint64(wb, "since", p->since);
89 buffer_json_member_add_uint64(wb, "first_time_t", p->first_time_t);
90 buffer_json_member_add_uint64(wb, "start_time", p->start_time_ms);
91 buffer_json_member_add_uint64(wb, "shutdown_time", p->shutdown_time_ms);
92 stream_capabilities_to_json_array(wb, p->capabilities, "capabilities");
93 STREAM_PATH_FLAGS_2json(wb, "flags", p->flags);
94 }
95 buffer_json_object_close(wb);
96 }
97
98 static STREAM_PATH rrdhost_stream_path_self(RRDHOST *host) {
99 STREAM_PATH p = { 0 };
100
101 bool is_localhost = host == localhost || rrdhost_option_check(host, RRDHOST_OPTION_VIRTUAL_HOST);
102
103 p.hostname = string_dup(localhost->hostname);
104 p.host_id = localhost->host_id;
105 p.node_id = localhost->node_id;
106 p.claim_id = claim_id_get_uuid();
107 p.start_time_ms = get_agent_event_time_median(EVENT_AGENT_START_TIME) / USEC_PER_MS;
108 p.shutdown_time_ms = get_agent_event_time_median(EVENT_AGENT_SHUTDOWN_TIME) / USEC_PER_MS;
109
110 p.flags = STREAM_PATH_FLAG_NONE;
111 if(!UUIDiszero(p.claim_id))
112 p.flags |= STREAM_PATH_FLAG_ACLK;
113
114 if(rrdhost_option_check(host, RRDHOST_OPTION_EPHEMERAL_HOST))
115 p.flags |= STREAM_PATH_FLAG_EPHEMERAL;
116
117 if(rrdhost_option_check(host, RRDHOST_OPTION_VIRTUAL_HOST))
118 p.flags |= STREAM_PATH_FLAG_VIRTUAL;
119
120 if(host->health.enabled)
121 p.flags |= STREAM_PATH_FLAG_HEALTH;
122
123 if(ml_enabled(host))
124 p.flags |= STREAM_PATH_FLAG_ML;
125
126 rrdhost_receiver_lock(host);
127 if(host->receiver) {
128 p.hops = (int16_t)host->receiver->hops;
129 p.since = host->receiver->connected_since_s;
130 }
131 else {
132 p.hops = (is_localhost) ? 0 : -1; // -1 for stale nodes
133 p.since = netdata_start_time;
134 }
135 rrdhost_receiver_unlock(host);
136
137 // the following may get the receiver lock again!
138 p.capabilities = stream_our_capabilities(host, true);
139
140 rrdhost_retention(host, 0, false, &p.first_time_t, NULL);
141
142 return p;
143 }
144
145 uint64_t rrdhost_stream_path_total_reboot_time_ms(RRDHOST *host) {
146 uint64_t total_ms = 0;
147
148 rw_spinlock_read_lock(&host->stream.path.spinlock);
149 for (size_t i = 0; i < host->stream.path.used; i++) {
150 STREAM_PATH *tmp_path = &host->stream.path.array[i];
151 if(UUIDeq(host->host_id, tmp_path->host_id)) {
152 total_ms = tmp_path->start_time_ms + tmp_path->shutdown_time_ms;
153 break;
154 }
155 }
156 rw_spinlock_read_unlock(&host->stream.path.spinlock);
157 return total_ms;
158 }
159
160 bool rrdhost_is_host_in_stream_path_before_us(struct rrdhost *host, ND_UUID remote_agent_host_id, int16_t our_hops) {
161 if(UUIDiszero(remote_agent_host_id)) return false;
162 if(UUIDeq(localhost->host_id, remote_agent_host_id)) return true;
163
164 bool rc = false;
165 rw_spinlock_read_lock(&host->stream.path.spinlock);
166 for (size_t i = 0; i < host->stream.path.used; i++) {
167 STREAM_PATH *p = &host->stream.path.array[i];
168 if(UUIDeq(remote_agent_host_id, p->host_id) && p->hops < our_hops) {
169 rc = true;
170 break;
171 }
172 }
173 rw_spinlock_read_unlock(&host->stream.path.spinlock);
174 return rc;
175 }
176
177 void rrdhost_stream_path_to_json(BUFFER *wb, struct rrdhost *host, const char *key, bool add_version) {
178 if(add_version)
179 buffer_json_member_add_uint64(wb, "version", 1);
180
181 STREAM_PATH tmp = rrdhost_stream_path_self(host);
182
183 rw_spinlock_read_lock(&host->stream.path.spinlock);
184 buffer_json_member_add_array(wb, key);
185 {
186 {
187 bool found_self = false;
188 for (size_t i = 0; i < host->stream.path.used; i++) {
189 STREAM_PATH *p = &host->stream.path.array[i];
190 if(UUIDeq(localhost->host_id, p->host_id)) {
191 // this is us, use the current data
192 p = &tmp;
193 found_self = true;
194 }
195 stream_path_to_json_object(wb, p);
196 }
197
198 if(!found_self) {
199 // we didn't find ourselves in the list.
200 // append us.
201 stream_path_to_json_object(wb, &tmp);
202 }
203 }
204 }
205 buffer_json_array_close(wb); // key
206 rw_spinlock_read_unlock(&host->stream.path.spinlock);
207
208 stream_path_cleanup(&tmp);
209 }
210
211 static BUFFER *stream_path_payload(RRDHOST *host) {
212 BUFFER *wb = buffer_create(0, NULL);
213 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
214 rrdhost_stream_path_to_json(wb, host, STREAM_PATH_JSON_MEMBER, true);
215 buffer_json_finalize(wb);
216 return wb;
217 }
218
219 void stream_path_send_to_parent(RRDHOST *host) {
220 struct sender_state *s = host->sender;
221 if(!s ||
222 !stream_has_capability(s, STREAM_CAP_PATHS) ||
223 !rrdhost_can_stream_metadata_to_parent(host))
224 return;
225
226 CLEAN_BUFFER *payload = stream_path_payload(host);
227 CLEAN_BUFFER *wb = buffer_create(0, NULL);
228 buffer_sprintf(wb, PLUGINSD_KEYWORD_JSON " " PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH "\n%s\n" PLUGINSD_KEYWORD_JSON_END "\n", buffer_tostring(payload));
229 sender_commit_clean_buffer(s, wb, STREAM_TRAFFIC_TYPE_METADATA);
230 }
231
232 void stream_path_send_to_child(RRDHOST *host) {
233 if(host == localhost)
234 return;
235
236 CLEAN_BUFFER *payload = stream_path_payload(host);
237
238 rrdhost_receiver_lock(host);
239 if(stream_has_capability(host->receiver, STREAM_CAP_PATHS) &&
240 rrdhost_flag_check(host, RRDHOST_FLAG_COLLECTOR_ONLINE)) {
241
242 CLEAN_BUFFER *wb = buffer_create(0, NULL);
243 buffer_sprintf(wb, PLUGINSD_KEYWORD_JSON " " PLUGINSD_KEYWORD_JSON_CMD_STREAM_PATH "\n%s\n" PLUGINSD_KEYWORD_JSON_END "\n", buffer_tostring(payload));
244 send_to_plugin(buffer_tostring(wb), __atomic_load_n(&host->receiver->thread.parser, __ATOMIC_RELAXED), STREAM_TRAFFIC_TYPE_METADATA);
245 }
246 rrdhost_receiver_unlock(host);
247 }
248
249 uint16_t rrdhost_stream_path_get_host_ids(struct rrdhost *host, uint16_t from, ND_UUID *host_ids, uint16_t max) {
250 if(!host || !host_ids || !max)
251 return 0;
252
253 uint16_t count = 0;
254 rw_spinlock_read_lock(&host->stream.path.spinlock);
255 for(uint16_t i = from; i < host->stream.path.used && count < max; i++)
256 host_ids[count++] = host->stream.path.array[i].host_id;
257 rw_spinlock_read_unlock(&host->stream.path.spinlock);
258 return count;
259 }
260
261 uint16_t rrdhost_stream_path_visit(struct rrdhost *host, uint16_t from,
262 stream_path_visit_cb cb, void *userdata) {
263 if(!host || !cb)
264 return 0;
265
266 uint16_t count = 0;
267 rw_spinlock_read_lock(&host->stream.path.spinlock);
268 for(uint16_t i = from; i < host->stream.path.used; i++) {
269 STREAM_PATH *p = &host->stream.path.array[i];
270 bool keep_going = cb(userdata, i,
271 p->hostname,
272 p->host_id, p->node_id, p->claim_id,
273 p->hops,
274 p->since, p->first_time_t,
275 p->start_time_ms, p->shutdown_time_ms,
276 p->capabilities,
277 (uint32_t)p->flags);
278 count++;
279 if(!keep_going) break;
280 }
281 rw_spinlock_read_unlock(&host->stream.path.spinlock);
282 return count;
283 }
284
285 void stream_path_child_disconnected(RRDHOST *host) {
286 rrdhost_stream_path_clear(host, true);
287 }
288
289 void stream_path_parent_disconnected(RRDHOST *host) {
290 rw_spinlock_write_lock(&host->stream.path.spinlock);
291
292 size_t cleared = 0;
293 size_t used = host->stream.path.used;
294 for (size_t i = 0; i < used; i++) {
295 STREAM_PATH *p = &host->stream.path.array[i];
296 if(UUIDeq(localhost->host_id, p->host_id)) {
297 host->stream.path.used = i + 1;
298
299 for(size_t j = i + 1; j < used ;j++) {
300 stream_path_cleanup(&host->stream.path.array[j]);
301 cleared++;
302 }
303
304 break;
305 }
306 }
307
308 rw_spinlock_write_unlock(&host->stream.path.spinlock);
309
310 if(cleared)
311 stream_path_send_to_child(host);
312 }
313
314 void stream_path_retention_updated(RRDHOST *host) {
315 if(!host || !localhost) return;
316 stream_path_send_to_parent(host);
317 stream_path_send_to_child(host);
318 }
319
320 void stream_path_node_id_updated(RRDHOST *host) {
321 if(!host || !localhost) return;
322 stream_path_send_to_parent(host);
323 stream_path_send_to_child(host);
324 }
325
326 // --------------------------------------------------------------------------------------------------------------------
327
328
329 static bool parse_single_path(json_object *jobj, const char *path, STREAM_PATH *p, BUFFER *error) {
330 uint32_t version = 0;
331 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "version", version, error, JSONC_OPTIONAL);
332
333 JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "hostname", p->hostname, error, JSONC_REQUIRED);
334 JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "host_id", p->host_id.uuid, error, JSONC_REQUIRED);
335 JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "node_id", p->node_id.uuid, error, JSONC_REQUIRED);
336 JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "claim_id", p->claim_id.uuid, error, JSONC_REQUIRED);
337 JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "hops", p->hops, error, JSONC_REQUIRED);
338 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "since", p->since, error, JSONC_REQUIRED);
339 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "first_time_t", p->first_time_t, error, JSONC_REQUIRED);
340 JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "start_time", p->start_time_ms, error, JSONC_REQUIRED);
341 JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "shutdown_time", p->shutdown_time_ms, error, JSONC_REQUIRED);
342 JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "flags", STREAM_PATH_FLAGS_2id_one, p->flags, error, JSONC_OPTIONAL);
343 JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "capabilities", stream_capabilities_parse_one, p->capabilities, error, JSONC_OPTIONAL);
344
345 if(!p->hostname) {
346 buffer_strcat(error, "hostname cannot be empty");
347 return false;
348 }
349
350 if(UUIDiszero(p->host_id)) {
351 buffer_strcat(error, "host_id cannot be zero");
352 return false;
353 }
354
355 if(p->hops < 0) {
356 buffer_strcat(error, "hops cannot be negative (probably the child disconnected from the Netdata before us");
357 return false;
358 }
359
360 if(p->capabilities == STREAM_CAP_NONE) {
361 buffer_strcat(error, "capabilities cannot be empty");
362 return false;
363 }
364
365 if(p->since <= 0) {
366 buffer_strcat(error, "since cannot be <= 0");
367 return false;
368 }
369
370 return true;
371 }
372
373 static XXH128_hash_t stream_path_hash_unsafe(RRDHOST *host) {
374 if(!host->stream.path.used)
375 return (XXH128_hash_t){ 0 };
376
377 return XXH3_128bits(host->stream.path.array, sizeof(*host->stream.path.array) * host->stream.path.used);
378 }
379
380 static int compare_by_hops(const void *a, const void *b) {
381 const STREAM_PATH *path1 = a;
382 const STREAM_PATH *path2 = b;
383
384 if (path1->hops < path2->hops)
385 return -1;
386 else if (path1->hops > path2->hops)
387 return 1;
388
389 return 0;
390 }
391
392 bool stream_path_set_from_json(RRDHOST *host, const char *json, bool from_parent) {
393 if(!json || !*json)
394 return false;
395
396 CLEAN_JSON_OBJECT *jobj = json_tokener_parse(json);
397 if(!jobj) {
398 nd_log(NDLS_DAEMON, NDLP_ERR,
399 "STREAM PATH '%s': Cannot parse json: %s",
400 rrdhost_hostname(host), json);
401 return false;
402 }
403
404 rw_spinlock_write_lock(&host->stream.path.spinlock);
405 XXH128_hash_t old_hash = stream_path_hash_unsafe(host);
406 rrdhost_stream_path_clear_unsafe(host, true);
407
408 CLEAN_BUFFER *error = buffer_create(0, NULL);
409
410 json_object *_jarray;
411 if (json_object_object_get_ex(jobj, STREAM_PATH_JSON_MEMBER, &_jarray) &&
412 json_object_is_type(_jarray, json_type_array)) {
413 size_t items = json_object_array_length(_jarray);
414 host->stream.path.array = callocz(items, sizeof(*host->stream.path.array));
415 host->stream.path.size = items;
416
417 for (size_t i = 0; i < items; ++i) {
418 json_object *joption = json_object_array_get_idx(_jarray, i);
419 if (!json_object_is_type(joption, json_type_object)) {
420 nd_log(NDLS_DAEMON, NDLP_ERR,
421 "STREAM PATH '%s': Array item No %zu is not an object: %s",
422 rrdhost_hostname(host), i, json);
423 continue;
424 }
425
426 if(!parse_single_path(joption, "", &host->stream.path.array[host->stream.path.used], error)) {
427 stream_path_cleanup(&host->stream.path.array[host->stream.path.used]);
428 nd_log(NDLS_DAEMON, NDLP_ERR,
429 "STREAM PATH '%s': Array item No %zu cannot be parsed: %s: %s",
430 rrdhost_hostname(host), i, buffer_tostring(error), json);
431 }
432 else
433 host->stream.path.used++;
434 }
435 }
436
437 if(host->stream.path.used > 1) {
438 // sorting is required in order to support stream_path_parent_disconnected()
439 qsort(host->stream.path.array, host->stream.path.used,
440 sizeof(*host->stream.path.array), compare_by_hops);
441 }
442
443 XXH128_hash_t new_hash = stream_path_hash_unsafe(host);
444 rw_spinlock_write_unlock(&host->stream.path.spinlock);
445
446 if(!XXH128_isEqual(old_hash, new_hash)) {
447 if(!from_parent)
448 stream_path_send_to_parent(host);
449
450 // when it comes from the child, we still need to send it back to the child
451 // including our own entry in it.
452 stream_path_send_to_child(host);
453 }
454
455 return host->stream.path.used > 0;
456 }
457
458 void rrdhost_stream_path_init(RRDHOST *host) {
459 rw_spinlock_init(&host->stream.path.spinlock);
460 }