master
c 990 lines 41.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "stream-sender-internals.h"
4 #include "stream-replication-sender.h"
5
6 #define TIME_TO_CONSIDER_PARENTS_SIMILAR 120
7
8 struct stream_parent {
9 STRING *destination; // the parent destination
10 bool ssl; // the parent uses SSL
11
12 bool banned_permanently; // when the parent is the origin of this host
13 bool banned_for_this_session; // when the parent is before us in the streaming path
14 bool banned_temporarily_erroneous; // when the parent is blocked by another node we host
15 STREAM_HANDSHAKE reason;
16 uint32_t attempts; // how many times we have tried to connect to this parent
17 usec_t since_ut; // the last time we tried to connect to it
18 usec_t postpone_until_ut; // based on the reason, a randomized time to wait for reconnection
19
20 struct {
21 ND_UUID host_id; // the machine_guid of the agent
22 int status; // the response code of the stream_info call
23 uint32_t nonce; // a random 32-bit number
24 size_t nodes; // how many nodes the parent has
25 size_t receivers; // how many receivers the parent has
26
27 // these are from RRDHOST_STATUS and can only be used when status == 200
28 RRDHOST_DB_STATUS db_status;
29 RRDHOST_DB_LIVENESS db_liveness;
30 RRDHOST_INGEST_TYPE ingest_type;
31 RRDHOST_INGEST_STATUS ingest_status;
32 time_t db_first_time_s; // the oldest timestamp for us in the parent's database
33 time_t db_last_time_s; // the latest timestamp for us in the parent's database
34 } remote;
35
36 struct {
37 size_t batch; // the batch priority (>= 1, 0 == excluded)
38 size_t order; // the final order of the parent (>= 1, 0 == excluded)
39 bool random; // this batch has more than 1 parents, so we flipped coins to select order
40 bool info; // we go stream info from the parent
41 bool skipped; // we skipped this parent for some reason
42 } selection;
43
44 STREAM_PARENT *prev;
45 STREAM_PARENT *next;
46 };
47
48 static const char *stream_parent_effective_service(const char *definition, int default_port, char *service, size_t service_size) {
49 if(!service || !service_size)
50 return "";
51
52 if(!connect_to_definition_get_service(definition, default_port, service, service_size))
53 snprintfz(service, service_size, "%d", default_port);
54
55 return service;
56 }
57
58 // --------------------------------------------------------------------------------------------------------------------
59 // block unresponsive parents for some time, to allow speeding up the connection of the rest
60
61 struct blocked_parent {
62 STRING *destination;
63 usec_t until;
64 };
65
66 DEFINE_JUDYL_TYPED(BLOCKED_PARENTS, struct blocked_parent *);
67 static BLOCKED_PARENTS_JudyLSet blocked_parents_set = { 0 };
68 static RW_SPINLOCK blocked_parents_spinlock = RW_SPINLOCK_INITIALIZER;
69
70 static void block_parent_for_all_nodes(STREAM_PARENT *d, time_t duration_s) {
71 rw_spinlock_write_lock(&blocked_parents_spinlock);
72
73 struct blocked_parent *p = BLOCKED_PARENTS_GET(&blocked_parents_set, (Word_t)d->destination);
74 if(!p) {
75 p = callocz(1, sizeof(*p));
76 p->destination = string_dup(d->destination);
77 BLOCKED_PARENTS_SET(&blocked_parents_set, (Word_t)p->destination, p);
78 }
79 p->until = now_monotonic_usec() + duration_s * USEC_PER_SEC;
80
81 rw_spinlock_write_unlock(&blocked_parents_spinlock);
82 }
83
84 static bool is_a_blocked_parent(STREAM_PARENT *d) {
85 rw_spinlock_read_lock(&blocked_parents_spinlock);
86
87 struct blocked_parent *p = BLOCKED_PARENTS_GET(&blocked_parents_set, (Word_t)d->destination);
88 bool ret = p && p->until > now_monotonic_usec();
89
90 rw_spinlock_read_unlock(&blocked_parents_spinlock);
91 return ret;
92 }
93
94 // --------------------------------------------------------------------------------------------------------------------
95
96 STREAM_HANDSHAKE stream_parent_get_disconnect_reason(STREAM_PARENT *d) {
97 if(!d) return STREAM_HANDSHAKE_PARENT_INTERNAL_ERROR;
98 return d->reason;
99 }
100
101 void stream_parent_set_host_disconnect_reason(RRDHOST *host, STREAM_HANDSHAKE reason, time_t since) {
102 host->stream.snd.status.reason = reason;
103 struct stream_parent *d = host->stream.snd.parents.current;
104 if(!d) return;
105 d->since_ut = since * USEC_PER_SEC;
106 d->reason = reason;
107 }
108
109 static inline usec_t randomize_wait_ut(time_t min, time_t max) {
110 min = (min < SENDER_MIN_RECONNECT_DELAY ? SENDER_MIN_RECONNECT_DELAY : min);
111 if(max < min) max = min;
112
113 usec_t min_ut = min * USEC_PER_SEC;
114 usec_t max_ut = max * USEC_PER_SEC;
115 usec_t wait_ut = min_ut + os_random(max_ut - min_ut);
116 return now_realtime_usec() + wait_ut;
117 }
118
119 void stream_parents_host_reset(RRDHOST *host, STREAM_HANDSHAKE reason) {
120 usec_t until_ut = randomize_wait_ut(stream_send.parents.reconnect_delay_s / 2, stream_send.parents.reconnect_delay_s + 5);
121 rw_spinlock_write_lock(&host->stream.snd.parents.spinlock);
122 for (STREAM_PARENT *d = host->stream.snd.parents.all; d; d = d->next) {
123 d->postpone_until_ut = until_ut;
124 d->banned_for_this_session = false;
125 d->reason = reason;
126 }
127 rw_spinlock_write_unlock(&host->stream.snd.parents.spinlock);
128 }
129
130 static void stream_parent_set_reconnect_delay(STREAM_PARENT *d, STREAM_HANDSHAKE reason, time_t secs) {
131 if(!d) return;
132 d->reason = reason;
133 d->postpone_until_ut = randomize_wait_ut(5, secs);
134 }
135
136 void stream_parent_set_host_reconnect_delay(RRDHOST *host, STREAM_HANDSHAKE reason, time_t secs) {
137 stream_parent_set_reconnect_delay(host->stream.snd.parents.current, reason, secs);
138 }
139
140 static void stream_parent_set_connect_failure_reason(RRDHOST *host, STREAM_PARENT *d, STREAM_HANDSHAKE reason, time_t secs) {
141 host->stream.snd.status.reason = reason;
142 pulse_host_status(host, PULSE_HOST_STATUS_SND_NO_DST_FAILED, reason);
143 pulse_sender_connection_failed(d ? string2str(d->destination) : NULL, reason);
144 stream_parent_set_reconnect_delay(d, reason, secs);
145 }
146
147 void stream_parent_set_host_connect_failure_reason(RRDHOST *host, STREAM_HANDSHAKE reason, time_t secs) {
148 stream_parent_set_connect_failure_reason(host, host->stream.snd.parents.current, reason, secs);
149 }
150
151 usec_t stream_parent_get_reconnection_ut(STREAM_PARENT *d) {
152 return d ? d->postpone_until_ut : 0;
153 }
154
155 bool stream_parent_is_ssl(STREAM_PARENT *d) {
156 return d ? d->ssl : false;
157 }
158
159 usec_t stream_parent_handshake_error_to_json(BUFFER *wb, RRDHOST *host) {
160 usec_t last_attempt = 0;
161 rw_spinlock_read_lock(&host->stream.snd.parents.spinlock);
162 for(STREAM_PARENT *d = host->stream.snd.parents.all; d ; d = d->next) {
163 if(d->since_ut > last_attempt)
164 last_attempt = d->since_ut;
165
166 buffer_json_add_array_item_string(wb, stream_handshake_error_to_string(d->reason));
167 }
168 rw_spinlock_read_unlock(&host->stream.snd.parents.spinlock);
169 return last_attempt;
170 }
171
172 void rrdhost_stream_parents_to_json(BUFFER *wb, RRDHOST_STATUS *s) {
173 char buf[1024];
174
175 rw_spinlock_read_lock(&s->host->stream.snd.parents.spinlock);
176
177 usec_t now_ut = now_realtime_usec();
178 STREAM_PARENT *d;
179 for (d = s->host->stream.snd.parents.all; d; d = d->next) {
180 buffer_json_add_array_item_object(wb);
181 buffer_json_member_add_uint64(wb, "attempts", d->attempts + 1);
182 {
183 if (d->ssl) {
184 snprintfz(buf, sizeof(buf) - 1, "%s:SSL", string2str(d->destination));
185 buffer_json_member_add_string(wb, "destination", buf);
186 }
187 else
188 buffer_json_member_add_string(wb, "destination", string2str(d->destination));
189
190 buffer_json_member_add_datetime_rfc3339(wb, "since", d->since_ut, false);
191 buffer_json_member_add_duration_ut(wb, "age", d->since_ut < now_ut ? (int64_t)(now_ut - d->since_ut) : 0);
192
193 if(!d->banned_for_this_session && !d->banned_permanently && !d->banned_temporarily_erroneous) {
194 buffer_json_member_add_string(wb, "last_handshake", stream_handshake_error_to_string(d->reason));
195
196 if (d->postpone_until_ut > now_ut) {
197 buffer_json_member_add_datetime_rfc3339(wb, "next_check", d->postpone_until_ut, false);
198 buffer_json_member_add_duration_ut(wb, "next_in", (int64_t)(d->postpone_until_ut - now_ut));
199 }
200
201 if(d->selection.batch) {
202 buffer_json_member_add_uint64(wb, "batch", d->selection.batch);
203 buffer_json_member_add_uint64(wb, "order", d->selection.order);
204 buffer_json_member_add_boolean(wb, "random", d->selection.random);
205 }
206
207 buffer_json_member_add_boolean(wb, "info", d->selection.info);
208 buffer_json_member_add_boolean(wb, "skipped", d->selection.skipped);
209 }
210 else {
211 if(d->banned_permanently)
212 buffer_json_member_add_string(wb, "ban", "it is the localhost");
213 else if(d->banned_for_this_session)
214 buffer_json_member_add_string(wb, "ban", "it is our parent");
215 else if(d->banned_temporarily_erroneous)
216 buffer_json_member_add_string(wb, "ban", "it is erroneous");
217 }
218 }
219 buffer_json_object_close(wb); // each candidate
220 }
221
222 rw_spinlock_read_unlock(&s->host->stream.snd.parents.spinlock);
223 }
224
225 void rrdhost_stream_parent_ssl_init(struct sender_state *s) {
226 static SPINLOCK sp = SPINLOCK_INITIALIZER;
227 spinlock_lock(&sp);
228
229 if(netdata_ssl_streaming_sender_ctx || !s->host) {
230 spinlock_unlock(&sp);
231 goto cleanup;
232 }
233
234 rw_spinlock_read_lock(&s->host->stream.snd.parents.spinlock);
235
236 for(STREAM_PARENT *d = s->host->stream.snd.parents.all; d ; d = d->next) {
237 if (d->ssl) {
238 // we need to initialize SSL
239
240 netdata_ssl_initialize_ctx(NETDATA_SSL_STREAMING_SENDER_CTX);
241
242 ssl_security_location_for_context(
243 netdata_ssl_streaming_sender_ctx,
244 string2str(stream_send.parents.ssl_ca_file),
245 string2str(stream_send.parents.ssl_ca_path));
246
247 // stop the loop
248 break;
249 }
250 }
251
252 rw_spinlock_read_unlock(&s->host->stream.snd.parents.spinlock);
253 spinlock_unlock(&sp);
254
255 cleanup:
256 s->sock.ctx = netdata_ssl_streaming_sender_ctx;
257 s->sock.verify_certificate = netdata_ssl_validate_certificate_sender;
258 }
259
260 static void stream_parent_nd_sock_error_to_reason(STREAM_PARENT *d, ND_SOCK *sock) {
261 switch (sock->error) {
262 case ND_SOCK_ERR_CONNECTION_REFUSED:
263 d->reason = STREAM_HANDSHAKE_SP_CONNECTION_REFUSED;
264 d->postpone_until_ut = randomize_wait_ut(30, 60);
265 block_parent_for_all_nodes(d, 30);
266 break;
267
268 case ND_SOCK_ERR_CANNOT_RESOLVE_HOSTNAME:
269 d->reason = STREAM_HANDSHAKE_SP_CANT_RESOLVE_HOSTNAME;
270 d->postpone_until_ut = randomize_wait_ut(30, 60);
271 block_parent_for_all_nodes(d, 30);
272 break;
273
274 case ND_SOCK_ERR_NO_HOST_IN_DEFINITION:
275 d->reason = STREAM_HANDSHAKE_SP_NO_HOST_IN_DESTINATION;
276 d->banned_for_this_session = true;
277 d->postpone_until_ut = randomize_wait_ut(30, 60);
278 block_parent_for_all_nodes(d, 30);
279 break;
280
281 case ND_SOCK_ERR_TIMEOUT:
282 d->reason = STREAM_HANDSHAKE_SP_CONNECT_TIMEOUT;
283 d->postpone_until_ut = randomize_wait_ut(300, d->remote.nodes < 10 ? 600 : 900);
284 block_parent_for_all_nodes(d, 300);
285 break;
286
287 case ND_SOCK_ERR_SSL_INVALID_CERTIFICATE:
288 d->reason = STREAM_HANDSHAKE_CONNECT_INVALID_CERTIFICATE;
289 d->postpone_until_ut = randomize_wait_ut(300, 600);
290 block_parent_for_all_nodes(d, 300);
291 break;
292
293 case ND_SOCK_ERR_SSL_CANT_ESTABLISH_SSL_CONNECTION:
294 case ND_SOCK_ERR_SSL_FAILED_TO_OPEN:
295 d->reason = STREAM_HANDSHAKE_CONNECT_SSL_ERROR;
296 d->postpone_until_ut = randomize_wait_ut(60, 180);
297 block_parent_for_all_nodes(d, 60);
298 break;
299
300 default:
301 case ND_SOCK_ERR_POLL_ERROR:
302 case ND_SOCK_ERR_FAILED_TO_CREATE_SOCKET:
303 case ND_SOCK_ERR_UNKNOWN_ERROR:
304 d->reason = STREAM_HANDSHAKE_PARENT_INTERNAL_ERROR;
305 d->postpone_until_ut = randomize_wait_ut(30, 60);
306 break;
307
308 case ND_SOCK_ERR_THREAD_CANCELLED:
309 case ND_SOCK_ERR_NO_DESTINATION_AVAILABLE:
310 d->reason = STREAM_HANDSHAKE_PARENT_INTERNAL_ERROR;
311 d->postpone_until_ut = randomize_wait_ut(30, 60);
312 break;
313 }
314 }
315
316 int stream_info_to_json_v1(BUFFER *wb, const char *machine_guid) {
317 pulse_parent_stream_info_received_request();
318
319 buffer_reset(wb);
320 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
321
322 RRDHOST_STATUS status = { 0 };
323 int ret = HTTP_RESP_OK;
324 RRDHOST *host;
325 if(!machine_guid || !*machine_guid || !(host = rrdhost_find_by_guid(machine_guid)))
326 ret = HTTP_RESP_NOT_FOUND;
327 else
328 rrdhost_status(host, now_realtime_sec(), &status, RRDHOST_STATUS_BASIC);
329
330 buffer_json_member_add_uint64(wb, "version", 1);
331 buffer_json_member_add_uint64(wb, "status", ret);
332 buffer_json_member_add_uuid(wb, "host_id", localhost->host_id.uuid);
333 buffer_json_member_add_uint64(wb, "nodes", dictionary_entries(rrdhost_root_index));
334 buffer_json_member_add_uint64(wb, "receivers", stream_receivers_currently_connected());
335 buffer_json_member_add_uint64(wb, "nonce", os_random32());
336
337 if(ret == HTTP_RESP_OK) {
338 if((status.ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED || status.ingest.status == RRDHOST_INGEST_STATUS_OFFLINE) &&
339 !stream_control_children_should_be_accepted())
340 status.ingest.status = RRDHOST_INGEST_STATUS_INITIALIZING;
341
342 buffer_json_member_add_string(wb, "db_status", rrdhost_db_status_to_string(status.db.status));
343 buffer_json_member_add_string(wb, "db_liveness", rrdhost_db_liveness_to_string(status.db.liveness));
344 buffer_json_member_add_string(wb, "ingest_type", rrdhost_ingest_type_to_string(status.ingest.type));
345 buffer_json_member_add_string(wb, "ingest_status", rrdhost_ingest_status_to_string(status.ingest.status));
346 buffer_json_member_add_uint64(wb, "first_time_s", status.db.first_time_s);
347 buffer_json_member_add_uint64(wb, "last_time_s", status.db.last_time_s);
348 }
349
350 buffer_json_finalize(wb);
351 return ret;
352 }
353
354 static bool stream_info_json_parse_v1(struct json_object *jobj, const char *path, STREAM_PARENT *d, BUFFER *error) {
355 uint32_t version = 0; (void)version;
356 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "version", version, error, JSONC_REQUIRED);
357
358 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "status", d->remote.status, error, JSONC_REQUIRED);
359 JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "host_id", d->remote.host_id.uuid, error, JSONC_REQUIRED);
360 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "nodes", d->remote.nodes, error, JSONC_REQUIRED);
361 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "receivers", d->remote.receivers, error, JSONC_REQUIRED);
362 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "nonce", d->remote.nonce, error, JSONC_REQUIRED);
363
364 if(d->remote.status == HTTP_RESP_OK) {
365 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "first_time_s", d->remote.db_first_time_s, error, JSONC_REQUIRED);
366 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "last_time_s", d->remote.db_last_time_s, error, JSONC_REQUIRED);
367 JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "db_status", RRDHOST_DB_STATUS_2id, d->remote.db_status, error, JSONC_REQUIRED);
368 JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "db_liveness", RRDHOST_DB_LIVENESS_2id, d->remote.db_liveness, error, JSONC_REQUIRED);
369 JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "ingest_type", RRDHOST_INGEST_TYPE_2id, d->remote.ingest_type, error, JSONC_REQUIRED);
370 JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "ingest_status", RRDHOST_INGEST_STATUS_2id, d->remote.ingest_status, error, JSONC_REQUIRED);
371 return true;
372 }
373
374 buffer_sprintf(error, "status reported (%d) is not OK (%d)", d->remote.status, HTTP_RESP_OK);
375
376 d->remote.db_first_time_s = 0;
377 d->remote.db_last_time_s = 0;
378 d->remote.db_status = 0;
379 d->remote.db_liveness = 0;
380 d->remote.ingest_type = 0;
381 d->remote.ingest_status = 0;
382
383 return false;
384 }
385
386 static bool stream_info_fetch(STREAM_PARENT *d, const char *uuid, int default_port, ND_SOCK *sender_sock, bool ssl, const char *hostname) {
387 char effective_service[NI_MAXSERV + 1];
388 stream_parent_effective_service(string2str(d->destination), default_port, effective_service, sizeof(effective_service));
389
390 ND_LOG_STACK lgs[] = {
391 ND_LOG_FIELD_STR(NDF_DST_IP, d->destination),
392 ND_LOG_FIELD_TXT(NDF_DST_PORT, effective_service),
393 ND_LOG_FIELD_TXT(NDF_REQUEST_METHOD, "GET"),
394 ND_LOG_FIELD_END(),
395 };
396 ND_LOG_STACK_PUSH(lgs);
397
398 char buf[HTTP_HEADER_SIZE];
399 CLEAN_ND_SOCK sock = ND_SOCK_INIT(sender_sock->ctx, sender_sock->verify_certificate);
400
401 // Build HTTP request
402 snprintf(buf, sizeof(buf),
403 "GET /api/v3/stream_info?machine_guid=%s" HTTP_1_1 HTTP_ENDL
404 "Host: %s" HTTP_ENDL
405 "User-Agent: %s/%s" HTTP_ENDL
406 "Accept: */*" HTTP_ENDL
407 "Accept-Encoding: identity" HTTP_ENDL // disable chunked encoding
408 "TE: identity" HTTP_ENDL // disable chunked encoding
409 "Pragma: no-cache" HTTP_ENDL
410 "Cache-Control: no-cache" HTTP_ENDL
411 "Connection: close" HTTP_HDR_END,
412 uuid,
413 string2str(d->destination),
414 rrdhost_program_name(localhost),
415 rrdhost_program_version(localhost));
416
417 nd_log(NDLS_DAEMON, NDLP_DEBUG,
418 "STREAM PARENTS '%s': fetching stream info from '%s'...",
419 hostname, string2str(d->destination));
420
421 pulse_stream_info_sent_request();
422
423 // Establish connection
424 d->reason = STREAM_HANDSHAKE_SP_CONNECTING;
425 if (!nd_sock_connect_to_this(&sock, string2str(d->destination), default_port, 5, ssl)) {
426 d->selection.info = false;
427 stream_parent_nd_sock_error_to_reason(d, &sock);
428 nd_log(NDLS_DAEMON, NDLP_WARNING,
429 "STREAM PARENTS '%s': failed to connect for stream info to '%s': %s",
430 hostname, string2str(d->destination),
431 ND_SOCK_ERROR_2str(sock.error));
432 return false;
433 }
434
435 // Send HTTP request
436 ssize_t sent = nd_sock_send_timeout(&sock, buf, strlen(buf), 0, 5);
437 if (sent <= 0) {
438 d->selection.info = false;
439 stream_parent_nd_sock_error_to_reason(d, &sock);
440 nd_log(NDLS_DAEMON, NDLP_WARNING,
441 "STREAM PARENTS '%s': failed to send stream info request to '%s': %s",
442 hostname, string2str(d->destination),
443 ND_SOCK_ERROR_2str(sock.error));
444 return false;
445 }
446
447 // Receive HTTP response
448 size_t total_received = 0;
449 size_t payload_received = 0;
450 size_t content_length = 0;
451 char *payload_start = NULL;
452
453 while (!payload_received || content_length < payload_received) {
454 size_t remaining = sizeof(buf) - total_received;
455
456 if (remaining <= 1) {
457 nd_log(NDLS_DAEMON, NDLP_WARNING,
458 "STREAM PARENTS '%s': stream info receive buffer is full while receiving response from '%s'",
459 hostname, string2str(d->destination));
460 d->selection.info = false;
461 d->reason = STREAM_HANDSHAKE_PARENT_INTERNAL_ERROR;
462 return false;
463 }
464
465 ssize_t received = nd_sock_recv_timeout(&sock, buf + total_received, remaining - 1, 0, 5);
466 if (received <= 0) {
467 nd_log(NDLS_DAEMON, NDLP_WARNING,
468 "STREAM PARENTS '%s': socket receive error while querying stream info on '%s' "
469 "(total received %zu, payload received %zu, content length %zu): %s",
470 hostname, string2str(d->destination),
471 total_received, payload_received, content_length,
472 ND_SOCK_ERROR_2str(sock.error));
473
474 d->selection.info = false;
475 stream_parent_nd_sock_error_to_reason(d, &sock);
476 return false;
477 }
478
479 total_received += received;
480 buf[total_received] = '\0';
481
482 if(!payload_start) {
483 char *headers_end = strstr(buf, HTTP_HDR_END);
484 if (!headers_end)
485 // we have not received the whole header yet
486 continue;
487
488 payload_start = headers_end + sizeof(HTTP_HDR_END) - 1;
489 }
490
491 // the payload size so far
492 payload_received = total_received - (payload_start - buf);
493
494 if(!content_length) {
495 char *content_length_ptr = strstr(buf, "Content-Length: ");
496 if (!content_length_ptr) {
497 nd_log(NDLS_DAEMON, NDLP_WARNING,
498 "STREAM PARENTS '%s': stream info response from '%s' does not have a Content-Length",
499 hostname, string2str(d->destination));
500
501 d->selection.info = false;
502 d->reason = STREAM_HANDSHAKE_PARENT_INTERNAL_ERROR;
503 return false;
504 }
505 content_length = strtoul(content_length_ptr + strlen("Content-Length: "), NULL, 10);
506 if (!content_length) {
507 nd_log(NDLS_DAEMON, NDLP_WARNING,
508 "STREAM PARENTS '%s': stream info response from '%s' has invalid Content-Length",
509 hostname, string2str(d->destination));
510
511 d->selection.info = false;
512 d->reason = STREAM_HANDSHAKE_PARENT_INTERNAL_ERROR;
513 return false;
514 }
515 }
516 }
517
518 // Parse HTTP response and extract JSON
519 CLEAN_JSON_OBJECT *jobj = json_tokener_parse(payload_start);
520 if (!jobj) {
521 d->selection.info = false;
522 d->reason = STREAM_HANDSHAKE_SP_NO_STREAM_INFO;
523 nd_log(NDLS_DAEMON, NDLP_WARNING,
524 "STREAM PARENTS '%s': failed to parse stream info response from '%s', JSON data: %s",
525 hostname, string2str(d->destination), payload_start);
526 return false;
527 }
528
529 CLEAN_BUFFER *error = buffer_create(0, NULL);
530
531 if(!stream_info_json_parse_v1(jobj, "", d, error)) {
532 d->selection.info = false;
533 d->reason = STREAM_HANDSHAKE_SP_NO_STREAM_INFO;
534 nd_log(NDLS_DAEMON, NDLP_WARNING,
535 "STREAM PARENTS '%s': failed to extract fields from JSON stream info response from '%s': %s"
536 " - JSON data: %s",
537 hostname, string2str(d->destination),
538 buffer_tostring(error),
539 payload_start);
540 return false;
541 }
542
543 nd_log(NDLS_DAEMON, NDLP_DEBUG,
544 "STREAM PARENTS '%s': received stream_info data from '%s': "
545 "status: %d, nodes: %zu, receivers: %zu, first_time_s: %ld, last_time_s: %ld, "
546 "db status: %s, db liveness: %s, ingest type: %s, ingest status: %s",
547 hostname, string2str(d->destination),
548 d->remote.status, d->remote.nodes, d->remote.receivers,
549 d->remote.db_first_time_s, d->remote.db_last_time_s,
550 RRDHOST_DB_STATUS_2str(d->remote.db_status),
551 RRDHOST_DB_LIVENESS_2str(d->remote.db_liveness),
552 RRDHOST_INGEST_TYPE_2str(d->remote.ingest_type),
553 RRDHOST_INGEST_STATUS_2str(d->remote.ingest_status));
554
555 d->selection.info = true;
556 d->reason = STREAM_HANDSHAKE_NEVER;
557 return true;
558 }
559
560 static int compare_last_time(const void *a, const void *b) {
561 STREAM_PARENT *parent_a = *(STREAM_PARENT **)a;
562 STREAM_PARENT *parent_b = *(STREAM_PARENT **)b;
563
564 if (parent_a->remote.db_last_time_s < parent_b->remote.db_last_time_s) return 1;
565 else if (parent_a->remote.db_last_time_s > parent_b->remote.db_last_time_s) return -1;
566 else {
567 if(parent_a->since_ut < parent_b->since_ut) return -1;
568 else if(parent_a->since_ut > parent_b->since_ut) return 1;
569 else {
570 if(parent_a->attempts < parent_b->attempts) return -1;
571 else if(parent_a->attempts > parent_b->attempts) return 1;
572 else return 0;
573 }
574 }
575 }
576
577 bool stream_parent_connect_to_one_unsafe(
578 ND_SOCK *sender_sock,
579 RRDHOST *host,
580 int default_port,
581 time_t timeout,
582 char *connected_to,
583 size_t connected_to_size,
584 STREAM_PARENT **destination)
585 {
586 sender_sock->error = ND_SOCK_ERR_NO_DESTINATION_AVAILABLE;
587
588 // count the parents
589 size_t size = 0;
590 for (STREAM_PARENT *d = host->stream.snd.parents.all; d; d = d->next) {
591 d->selection.order = 0;
592 d->selection.batch = 0;
593 d->selection.random = false;
594 d->selection.info = false;
595 d->selection.skipped = true;
596 size++;
597 }
598
599 // do we have any parents?
600 if(!size) {
601 nd_log(NDLS_DAEMON, NDLP_DEBUG, "STREAM PARENTS '%s': no parents configured", rrdhost_hostname(host));
602 return false;
603 }
604
605 STREAM_PARENT **array = callocz(size, sizeof(*array));
606 usec_t now_ut = now_realtime_usec();
607 bool rc = false;
608
609 // fetch stream info for all of them and put them in the array
610 size_t count = 0, skipped_but_useful = 0, skipped_not_useful = 0, potential = 0;
611 for (STREAM_PARENT *d = host->stream.snd.parents.all; d && count < size ; d = d->next) {
612 if (nd_thread_signaled_to_cancel()) {
613 sender_sock->error = ND_SOCK_ERR_THREAD_CANCELLED;
614 goto cleanup;
615 }
616
617 // make sure they all have a random number
618 // this is taken from the parent, but if the stream_info call fails,
619 // we generate a random number for every parent here
620 d->remote.nonce = os_random32();
621 d->banned_temporarily_erroneous = is_a_blocked_parent(d);
622
623 if (d->banned_permanently || d->banned_for_this_session)
624 continue;
625
626 if (d->banned_temporarily_erroneous) {
627 potential++;
628 host->stream.snd.status.reason = d->reason;
629 continue;
630 }
631
632 if (d->postpone_until_ut > now_ut) {
633 skipped_but_useful++;
634 potential++;
635 host->stream.snd.status.reason = d->reason;
636 nd_log(NDLS_DAEMON, NDLP_DEBUG,
637 "STREAM PARENTS '%s': skipping useful parent '%s': POSTPONED FOR %ld SECS MORE: %s",
638 rrdhost_hostname(host),
639 string2str(d->destination),
640 (time_t)((d->postpone_until_ut - now_ut) / USEC_PER_SEC),
641 stream_handshake_error_to_string(d->reason));
642 continue;
643 }
644
645 if(stream_info_fetch(d, host->machine_guid, default_port,
646 sender_sock, stream_parent_is_ssl(d), rrdhost_hostname(host))) {
647 switch(d->remote.ingest_type) {
648 case RRDHOST_INGEST_TYPE_VIRTUAL:
649 case RRDHOST_INGEST_TYPE_LOCALHOST:
650 d->reason = STREAM_HANDSHAKE_PARENT_IS_LOCALHOST;
651 d->since_ut = now_ut;
652 d->postpone_until_ut = randomize_wait_ut(3600, 7200);
653 d->banned_permanently = true;
654 skipped_not_useful++;
655
656 if(rrdhost_is_host_in_stream_path_before_us(host, d->remote.host_id, 1)) {
657 // we passed hops == 1, to make sure this succeeds only when the parent
658 // is the origin child of this node
659 nd_log(NDLS_DAEMON, NDLP_INFO,
660 "STREAM PARENTS '%s': destination '%s' is banned permanently because it is the origin server",
661 rrdhost_hostname(host), string2str(d->destination));
662 }
663 else {
664 nd_log(NDLS_DAEMON, NDLP_WARNING,
665 "STREAM PARENTS '%s': destination '%s' is banned permanently because it is the origin server, "
666 "but it is not in the stream path before us!",
667 rrdhost_hostname(host), string2str(d->destination));
668 }
669 continue;
670
671 default:
672 case RRDHOST_INGEST_TYPE_CHILD:
673 case RRDHOST_INGEST_TYPE_ARCHIVED:
674 break;
675 }
676
677 switch(d->remote.ingest_status) {
678 case RRDHOST_INGEST_STATUS_INITIALIZING:
679 d->reason = STREAM_HANDSHAKE_PARENT_IS_INITIALIZING;
680 d->since_ut = now_ut;
681 d->postpone_until_ut = randomize_wait_ut(30, 60);
682 pulse_sender_stream_info_failed(string2str(d->destination), d->reason);
683 skipped_but_useful++;
684 potential++;
685 host->stream.snd.status.reason = d->reason;
686 nd_log(NDLS_DAEMON, NDLP_DEBUG,
687 "STREAM PARENTS '%s': skipping useful parent '%s': %s",
688 rrdhost_hostname(host), string2str(d->destination),
689 stream_handshake_error_to_string(d->reason));
690 continue;
691
692 case RRDHOST_INGEST_STATUS_REPLICATING:
693 case RRDHOST_INGEST_STATUS_ONLINE:
694 if(rrdhost_is_host_in_stream_path_before_us(host, d->remote.host_id, host->sender->hops)) {
695 d->reason = STREAM_HANDSHAKE_PARENT_NODE_ALREADY_CONNECTED;
696 d->since_ut = now_ut;
697 d->postpone_until_ut = randomize_wait_ut(3600, 7200);
698 d->banned_for_this_session = true;
699 skipped_not_useful++;
700 nd_log(NDLS_DAEMON, NDLP_INFO,
701 "STREAM PARENTS '%s': destination '%s' is banned for this session, because it is in our path before us.",
702 rrdhost_hostname(host), string2str(d->destination));
703 pulse_sender_stream_info_failed(string2str(d->destination), d->reason);
704 continue;
705 }
706 // else {
707 // skip = true;
708 // if(!netdata_conf_is_parent()) {
709 // nd_log(NDLS_DAEMON, NDLP_INFO,
710 // "STREAM PARENTS '%s': destination '%s' reports I am already connected.",
711 // rrdhost_hostname(host), string2str(d->destination));
712 // }
713 // }
714 break;
715
716 default:
717 case RRDHOST_INGEST_STATUS_OFFLINE:
718 break;
719 }
720 }
721 else
722 pulse_sender_stream_info_failed(string2str(d->destination), d->reason);
723
724 d->selection.skipped = false;
725 d->selection.batch = count + 1;
726 d->selection.order = count + 1;
727 array[count++] = d;
728 }
729
730 // can we use any parent?
731 if(!count) {
732 nd_log(NDLS_DAEMON, NDLP_DEBUG,
733 "STREAM PARENTS '%s': no parents available (%zu skipped but useful, %zu skipped not useful, %zu potential)",
734 rrdhost_hostname(host), skipped_but_useful, skipped_not_useful, potential);
735
736 if(!potential) {
737 if(host->stream.snd.status.reason != STREAM_HANDSHAKE_SP_NO_DESTINATION) {
738 host->stream.snd.status.reason = STREAM_HANDSHAKE_SP_NO_DESTINATION;
739 pulse_sender_connection_failed(NULL, host->stream.snd.status.reason);
740 }
741 pulse_host_status(host, PULSE_HOST_STATUS_SND_NO_DST, 0);
742 }
743
744 goto cleanup;
745 }
746
747 // order the parents in the array the way we want to connect
748 if(count > 1) {
749 qsort(array, count, sizeof(STREAM_PARENT *), compare_last_time);
750
751 size_t base = 0, batch = 0;
752 while (base < count) {
753 // find how many have similar db_last_time_s;
754 size_t similar = 1;
755 if(!array[base]->remote.nonce) array[base]->remote.nonce = os_random32();
756 time_t tB = array[base]->remote.db_last_time_s;
757 for (size_t i = base + 1; i < count; i++) {
758 time_t tN = array[i]->remote.db_last_time_s;
759 if ((tN > tB && tN - tB <= TIME_TO_CONSIDER_PARENTS_SIMILAR) ||
760 (tB - tN <= TIME_TO_CONSIDER_PARENTS_SIMILAR))
761 similar++;
762 else
763 break;
764 }
765
766 // if we have only 1 similar, move on
767 if (similar == 1) {
768 nd_log(NDLS_DAEMON, NDLP_DEBUG,
769 "STREAM PARENTS '%s': reordering keeps parent No %zu, '%s'",
770 rrdhost_hostname(host), base, string2str(array[base]->destination));
771 array[base]->selection.order = base + 1;
772 array[base]->selection.batch = batch + 1;
773 array[base]->selection.random = false;
774 base++;
775 batch++;
776 continue;
777 }
778 else {
779 // reorder the parents who have similar db_last_time
780
781 while (similar > 1) {
782 size_t chosen = base;
783 for(size_t i = base + 1 ; i < base + similar ;i++) {
784 uint32_t i_nonce = array[i]->remote.nonce | os_random32();
785 uint32_t chosen_nonce = array[chosen]->remote.nonce | os_random32();
786 if(i_nonce > chosen_nonce) chosen = i;
787 }
788
789 if (chosen != base)
790 SWAP(array[base], array[chosen]);
791
792 nd_log(NDLS_DAEMON, NDLP_DEBUG,
793 "STREAM PARENTS '%s': random reordering of %zu similar parents (slots %zu to %zu), No %zu is '%s'",
794 rrdhost_hostname(host),
795 similar, base, base + similar,
796 base, string2str(array[base]->destination));
797
798 array[base]->selection.order = base + 1;
799 array[base]->selection.batch = batch + 1;
800 array[base]->selection.random = true;
801 base++;
802 similar--;
803 }
804
805 // the last one of the similar
806 array[base]->selection.order = base + 1;
807 array[base]->selection.batch = batch + 1;
808 array[base]->selection.random = true;
809 base++;
810 batch++;
811 }
812 }
813 }
814 else {
815 array[0]->selection.order = 1;
816 array[0]->selection.batch = 1;
817 array[0]->selection.random = false;
818
819 nd_log(NDLS_DAEMON, NDLP_DEBUG,
820 "STREAM PARENTS '%s': only 1 parent is available: '%s'",
821 rrdhost_hostname(host), string2str(array[0]->destination));
822 }
823
824 // now the parents are sorted based on preference of connection
825 for(size_t i = 0; i < count ;i++) {
826 STREAM_PARENT *d = array[i];
827
828 if(d->postpone_until_ut > now_ut)
829 continue;
830
831 if(nd_thread_signaled_to_cancel()) {
832 sender_sock->error = ND_SOCK_ERR_THREAD_CANCELLED;
833 host->stream.snd.status.reason = STREAM_HANDSHAKE_DISCONNECT_SIGNALED_TO_STOP;
834 pulse_host_status(host, PULSE_HOST_STATUS_SND_OFFLINE, host->stream.snd.status.reason);
835 goto cleanup;
836 }
837
838 nd_log(NDLS_DAEMON, NDLP_DEBUG,
839 "STREAM PARENTS '%s': connecting to '%s' (default port: %d, parent %zu of %zu)...",
840 rrdhost_hostname(host), string2str(d->destination), default_port,
841 i + 1, count);
842
843 char effective_service[NI_MAXSERV + 1];
844 stream_parent_effective_service(string2str(d->destination), default_port, effective_service, sizeof(effective_service));
845 ND_LOG_STACK lgs[] = {
846 ND_LOG_FIELD_STR(NDF_DST_IP, d->destination),
847 ND_LOG_FIELD_TXT(NDF_DST_PORT, effective_service),
848 ND_LOG_FIELD_END(),
849 };
850 ND_LOG_STACK_PUSH(lgs);
851
852 d->since_ut = now_ut;
853 d->attempts++;
854 pulse_host_status(host, PULSE_HOST_STATUS_SND_CONNECTING, 0);
855 if (nd_sock_connect_to_this(sender_sock, string2str(d->destination),
856 default_port, timeout, stream_parent_is_ssl(d))) {
857
858 if (connected_to && connected_to_size)
859 strncpyz(connected_to, string2str(d->destination), connected_to_size);
860
861 *destination = d;
862
863 // move the current item to the end of the list
864 // without this, this destination will break the loop again and again
865 // not advancing the destinations to find one that may work
866 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->stream.snd.parents.all, d, prev, next);
867 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(host->stream.snd.parents.all, d, prev, next);
868
869 nd_log(NDLS_DAEMON, NDLP_DEBUG,
870 "STREAM PARENTS '%s': connected to '%s' (default port: %d, fd %d)...",
871 rrdhost_hostname(host), string2str(d->destination), default_port,
872 sender_sock->fd);
873
874 sender_sock->error = ND_SOCK_ERR_NONE;
875 host->stream.snd.status.reason = STREAM_HANDSHAKE_SP_CONNECTED;
876 pulse_host_status(host, PULSE_HOST_STATUS_SND_CONNECTING, host->stream.snd.status.reason);
877 rc = true;
878 goto cleanup;
879 }
880 else {
881 stream_parent_nd_sock_error_to_reason(d, sender_sock);
882 host->stream.snd.status.reason = d->reason;
883 pulse_sender_connection_failed(string2str(d->destination), d->reason);
884 pulse_host_status(host, PULSE_HOST_STATUS_SND_CONNECTING, host->stream.snd.status.reason);
885 nd_log(NDLS_DAEMON, NDLP_DEBUG,
886 "STREAM PARENTS '%s': stream connection to '%s' failed (default port: %d): %s",
887 rrdhost_hostname(host),
888 string2str(d->destination), default_port,
889 ND_SOCK_ERROR_2str(sender_sock->error));
890 }
891 }
892
893 pulse_host_status(host, PULSE_HOST_STATUS_SND_OFFLINE, 0);
894
895 cleanup:
896 freez(array);
897 return rc;
898 }
899
900 bool stream_parent_connect_to_one(
901 ND_SOCK *sender_sock,
902 RRDHOST *host,
903 int default_port,
904 time_t timeout,
905 char *connected_to,
906 size_t connected_to_size,
907 STREAM_PARENT **destination) {
908
909 rw_spinlock_read_lock(&host->stream.snd.parents.spinlock);
910 bool rc = stream_parent_connect_to_one_unsafe(
911 sender_sock, host, default_port, timeout, connected_to, connected_to_size, destination);
912 rw_spinlock_read_unlock(&host->stream.snd.parents.spinlock);
913 return rc;
914 }
915
916 // --------------------------------------------------------------------------------------------------------------------
917 // create stream parents linked list
918
919 struct stream_parent_init_tmp {
920 RRDHOST *host;
921 STREAM_PARENT *list;
922 int count;
923 };
924
925 static bool stream_parent_add_one_unsafe(char *entry, void *data) {
926 struct stream_parent_init_tmp *t = data;
927
928 STREAM_PARENT *d = callocz(1, sizeof(STREAM_PARENT));
929 char *colon_ssl = strstr(entry, ":SSL");
930 if(colon_ssl) {
931 *colon_ssl = '\0';
932 d->ssl = true;
933 }
934 else
935 d->ssl = false;
936
937 d->destination = string_strdupz(entry);
938 d->since_ut = now_realtime_usec();
939
940 __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(STREAM_PARENT), __ATOMIC_RELAXED);
941
942 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(t->list, d, prev, next);
943
944 t->count++;
945 nd_log(NDLS_DAEMON, NDLP_DEBUG,
946 "STREAM PARENTS '%s': added streaming destination No %d: '%s'",
947 rrdhost_hostname(t->host), t->count, string2str(d->destination));
948
949 return false; // we return false, so that we will get all defined destinations
950 }
951
952 void rrdhost_stream_parents_update_from_destination(RRDHOST *host) {
953 rw_spinlock_write_lock(&host->stream.snd.parents.spinlock);
954 rrdhost_stream_parents_free(host, true);
955
956 if(host->stream.snd.destination) {
957 struct stream_parent_init_tmp t = {
958 .host = host,
959 .list = NULL,
960 .count = 0,
961 };
962 foreach_entry_in_connection_string(string2str(host->stream.snd.destination), stream_parent_add_one_unsafe, &t);
963 host->stream.snd.parents.all = t.list;
964 }
965
966 rw_spinlock_write_unlock(&host->stream.snd.parents.spinlock);
967 }
968
969 void rrdhost_stream_parents_free(RRDHOST *host, bool having_write_lock) {
970 if(!having_write_lock)
971 rw_spinlock_write_lock(&host->stream.snd.parents.spinlock);
972
973 while (host->stream.snd.parents.all) {
974 STREAM_PARENT *tmp = host->stream.snd.parents.all;
975 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->stream.snd.parents.all, tmp, prev, next);
976 string_freez(tmp->destination);
977 freez(tmp);
978 __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(STREAM_PARENT), __ATOMIC_RELAXED);
979 }
980
981 host->stream.snd.parents.all = NULL;
982 host->stream.snd.parents.current = NULL;
983
984 if(!having_write_lock)
985 rw_spinlock_write_unlock(&host->stream.snd.parents.spinlock);
986 }
987
988 void rrdhost_stream_parents_init(RRDHOST *host) {
989 rw_spinlock_init(&host->stream.snd.parents.spinlock);
990 }