master
c 1,317 lines 48.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "sqlite_functions.h"
4 #include "sqlite_aclk.h"
5
6 void sanity_check(void) {
7 // make sure the compiler will stop on misconfigurations
8 BUILD_BUG_ON(WORKER_UTILIZATION_MAX_JOB_TYPES < ACLK_MAX_ENUMERATIONS_DEFINED);
9 }
10
11 #include "sqlite_aclk_node.h"
12 #include "aclk/aclk_query_queue.h"
13 #include "aclk/aclk_query.h"
14
15 static void create_node_instance_result_job(const char *machine_guid, const char *node_id)
16 {
17 nd_uuid_t host_uuid, node_uuid;
18
19 if (uuid_parse(machine_guid, host_uuid)) {
20 netdata_log_error("Error parsing machine_guid provided by CreateNodeInstanceResult");
21 return;
22 }
23
24 if (uuid_parse(node_id, node_uuid)) {
25 netdata_log_error("Error parsing node_id provided by CreateNodeInstanceResult");
26 return;
27 }
28
29 RRDHOST *host = rrdhost_find_by_guid(machine_guid);
30 if (unlikely(!host)) {
31 netdata_log_error("Cannot find machine_guid provided by CreateNodeInstanceResult");
32 return;
33 }
34 sql_update_node_id(&host_uuid, &node_uuid);
35 schedule_node_state_update(host, 1000);
36 }
37
38 struct aclk_sync_config_s {
39 ND_THREAD *thread;
40 uv_loop_t loop;
41 uv_timer_t timer_req;
42 uv_async_t async;
43 bool initialized;
44 bool shutdown_requested;
45 mqtt_wss_client client;
46 int aclk_queries_running;
47 bool run_query_batch;
48 bool alert_push_running;
49 bool aclk_batch_job_is_running;
50 uint32_t aclk_jobs_pending;
51 struct completion start_stop_complete;
52 CmdPool cmd_pool;
53 WorkerPool worker_pool;
54 } aclk_sync_config = { 0 };
55
56 static cmd_data_t aclk_database_deq_cmd(void)
57 {
58 cmd_data_t ret = { 0 };
59 ret.opcode = ACLK_DATABASE_NOOP;
60 (void) pop_cmd(&aclk_sync_config.cmd_pool, (cmd_data_t *) &ret);
61 return ret;
62 }
63
64 static bool aclk_database_enq_cmd(cmd_data_t *cmd, bool wait_on_full)
65 {
66 if(unlikely(!__atomic_load_n(&aclk_sync_config.initialized, __ATOMIC_RELAXED)))
67 return false;
68
69 bool added = push_cmd(&aclk_sync_config.cmd_pool, (void *)cmd, wait_on_full);
70 if (added)
71 (void) uv_async_send(&aclk_sync_config.async);
72 return added;
73 }
74
75 struct children {
76 int vnodes;
77 int normal;
78 };
79
80 // Column indices for SQL_FETCH_ALL_HOSTS — keep in lock-step with the SELECT list.
81 enum {
82 COL_FETCH_HOST_ID = 0,
83 COL_FETCH_HOSTNAME,
84 COL_FETCH_REGISTRY,
85 COL_FETCH_UPDATE_EVERY,
86 COL_FETCH_OS,
87 COL_FETCH_TIMEZONE,
88 COL_FETCH_HOPS,
89 COL_FETCH_ABBREV_TIMEZONE,
90 COL_FETCH_UTC_OFFSET,
91 COL_FETCH_PROGRAM_NAME,
92 COL_FETCH_PROGRAM_VERSION,
93 COL_FETCH_ENTRIES,
94 COL_FETCH_LAST_CONNECTED,
95 COL_FETCH_IS_EPHEMERAL,
96 COL_FETCH_IS_REGISTERED,
97 };
98
99 // Materialise one archived host row from SQL_FETCH_ALL_HOSTS into rrdhost_root_index.
100 // Returns the host (or NULL if creation skipped/failed) so the caller can update counters.
101 static RRDHOST *load_archived_host_from_row(sqlite3_stmt *res)
102 {
103 // The COL_FETCH_* enum is in lock-step with SQL_FETCH_ALL_HOSTS' SELECT list.
104 // Catch drift early in debug builds; release builds compile this out.
105 internal_fatal(sqlite3_column_count(res) != COL_FETCH_IS_REGISTERED + 1,
106 "SQL_FETCH_ALL_HOSTS column count (%d) does not match COL_FETCH_* enum (%d)",
107 sqlite3_column_count(res), COL_FETCH_IS_REGISTERED + 1);
108
109 nd_uuid_t host_uuid;
110 if (!sqlite3_column_uuid_copy(res, COL_FETCH_HOST_ID, host_uuid)) {
111 nd_log_daemon(
112 NDLP_ERR,
113 "Skipping archived host: host_id column is not a valid 16-byte UUID blob (type=%d, bytes=%d). Possible DB corruption.",
114 sqlite3_column_type(res, COL_FETCH_HOST_ID),
115 sqlite3_column_bytes(res, COL_FETCH_HOST_ID));
116 return NULL;
117 }
118
119 char guid[UUID_STR_LEN];
120 uuid_unparse_lower(host_uuid, guid);
121
122 const char *hostname = (const char *)sqlite3_column_text(res, COL_FETCH_HOSTNAME);
123 const char *registry = (const char *)sqlite3_column_text(res, COL_FETCH_REGISTRY);
124 const char *os = (const char *)sqlite3_column_text(res, COL_FETCH_OS);
125 const char *host_tz = (const char *)sqlite3_column_text(res, COL_FETCH_TIMEZONE);
126 const char *abbrev_tz = (const char *)sqlite3_column_text(res, COL_FETCH_ABBREV_TIMEZONE);
127 const char *prog_name = (const char *)sqlite3_column_text(res, COL_FETCH_PROGRAM_NAME);
128 const char *prog_version = (const char *)sqlite3_column_text(res, COL_FETCH_PROGRAM_VERSION);
129 int hops = sqlite3_column_int(res, COL_FETCH_HOPS);
130 int utc_offset = sqlite3_column_int(res, COL_FETCH_UTC_OFFSET);
131 int entries = sqlite3_column_int(res, COL_FETCH_ENTRIES);
132 // update_every defaults to 1 only when the column is SQL NULL — preserves
133 // the pre-refactor `argv[i] ? str2i(argv[i]) : 1` fallback exactly. A
134 // stored 0 stays 0 (matches the original str2i path).
135 int update_every = (sqlite3_column_type(res, COL_FETCH_UPDATE_EVERY) == SQLITE_NULL)
136 ? 1
137 : sqlite3_column_int(res, COL_FETCH_UPDATE_EVERY);
138 int64_t last_connected_db = sqlite3_column_int64(res, COL_FETCH_LAST_CONNECTED);
139 int is_ephemeral = sqlite3_column_int(res, COL_FETCH_IS_EPHEMERAL);
140 int is_registered = sqlite3_column_int(res, COL_FETCH_IS_REGISTERED);
141
142 time_t last_connected = (time_t)last_connected_db;
143 if (!last_connected)
144 last_connected = now_realtime_sec();
145
146 time_t age = now_realtime_sec() - last_connected;
147
148 if (is_ephemeral && ((!is_registered && last_connected == 1) ||
149 (rrdhost_free_ephemeral_time_s && age > rrdhost_free_ephemeral_time_s))) {
150 netdata_log_info(
151 "%s ephemeral hostname \"%s\" with GUID \"%s\", age = %ld seconds (limit %ld seconds)",
152 is_registered ? "Loading registered" : "Skipping unregistered",
153 hostname,
154 guid,
155 age,
156 rrdhost_free_ephemeral_time_s);
157
158 if (!is_registered)
159 return NULL;
160 }
161
162 struct rrdhost_system_info *system_info = rrdhost_system_info_create();
163 rrdhost_system_info_hops_set(system_info, (int16_t)hops);
164 sql_build_host_system_info(&host_uuid, system_info);
165
166 RRDHOST *host = rrdhost_find_or_create(
167 hostname,
168 registry,
169 guid,
170 os,
171 host_tz,
172 abbrev_tz,
173 (int32_t)utc_offset,
174 prog_name ? prog_name : "unknown",
175 prog_version ? prog_version : "unknown",
176 update_every,
177 entries,
178 default_rrd_memory_mode,
179 0, // health
180 0, // rrdpush enabled
181 NULL, // destination
182 NULL, // api key
183 NULL, // send charts matching
184 false, // rrdpush_enable_replication
185 0, // rrdpush_seconds_to_replicate
186 0, // rrdpush_replication_step
187 system_info,
188 1);
189
190 rrdhost_system_info_free(system_info);
191
192 if (unlikely(!host))
193 return NULL;
194
195 if (is_ephemeral) {
196 rrdhost_option_set(host, RRDHOST_OPTION_EPHEMERAL_HOST);
197 host->stream.rcv.status.last_disconnected = now_realtime_sec();
198 }
199
200 host->rrdlabels = sql_load_host_labels(&host_uuid);
201 host->stream.snd.status.last_connected = last_connected;
202
203 pulse_host_status(host, 0, 0); // this will detect the receiver status
204
205 #ifdef NETDATA_INTERNAL_CHECKS
206 char node_str[UUID_STR_LEN] = "<none>";
207 if (likely(!UUIDiszero(host->node_id)))
208 uuid_unparse_lower(host->node_id.uuid, node_str);
209 internal_error(true, "Adding archived host \"%s\" with GUID \"%s\" node id = \"%s\" ephemeral=%d",
210 rrdhost_hostname(host), host->machine_guid, node_str, is_ephemeral);
211 #endif
212
213 return host;
214 }
215
216
217 #define SQL_SELECT_ACLK_ALERT_TABLES \
218 "SELECT 'DROP '||type||' IF EXISTS '||name||';' FROM sqlite_schema WHERE name LIKE 'aclk_alert_%' AND type IN ('table', 'trigger', 'index')"
219
220 static void sql_delete_aclk_table_list(void)
221 {
222 sqlite3_stmt *res = NULL;
223
224 BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE, NULL);
225
226 if (!PREPARE_STATEMENT(db_meta, SQL_SELECT_ACLK_ALERT_TABLES, &res))
227 goto fail;
228
229 while (sqlite3_step_monitored(res) == SQLITE_ROW)
230 buffer_strcat(sql, (char *) sqlite3_column_text(res, 0));
231
232 SQLITE_FINALIZE(res);
233
234 int rc = db_execute(db_meta, buffer_tostring(sql), NULL);
235 if (unlikely(rc))
236 netdata_log_error("Failed to drop unused ACLK tables");
237
238 fail:
239 buffer_free(sql);
240 }
241
242 #define SQL_INVALIDATE_HOST_LAST_CONNECTED "UPDATE host SET last_connected = 1 WHERE host_id = @host_id"
243
244 static void invalidate_host_last_connected(nd_uuid_t *host_uuid)
245 {
246 sqlite3_stmt *res = NULL;
247
248 if (!PREPARE_STATEMENT(db_meta, SQL_INVALIDATE_HOST_LAST_CONNECTED, &res))
249 return;
250
251 int param = 0;
252 SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_blob(res, ++param, host_uuid, sizeof(*host_uuid), SQLITE_STATIC));
253
254 param = 0;
255 int rc = sqlite3_step_monitored(res);
256 if (unlikely(rc != SQLITE_DONE)) {
257 char wstr[UUID_STR_LEN];
258 uuid_unparse_lower(*host_uuid, wstr);
259 error_report("Failed invalidate last_connected time for host with GUID %s, rc = %d", wstr, rc);
260 }
261
262 bind_fail:
263 REPORT_BIND_FAIL(res, param);
264 SQLITE_FINALIZE(res);
265 }
266
267
268 // OPCODE: ACLK_DATABASE_NODE_UNREGISTER
269 static void sql_unregister_node(char *machine_guid)
270 {
271 int rc;
272 nd_uuid_t host_uuid;
273
274 if (unlikely(!machine_guid))
275 return;
276
277 rc = uuid_parse(machine_guid, host_uuid);
278 if (rc)
279 goto skip;
280
281 sqlite3_stmt *res = NULL;
282
283 if (!PREPARE_STATEMENT(db_meta, "UPDATE node_instance SET node_id = NULL WHERE host_id = @host_id", &res))
284 goto skip;
285
286 int param = 0;
287 SQLITE_BIND_FAIL(done, sqlite3_bind_blob(res, ++param, &host_uuid, sizeof(host_uuid), SQLITE_STATIC));
288 param = 0;
289
290 rc = sqlite3_step_monitored(res);
291 if (unlikely(rc != SQLITE_DONE))
292 error_report("Failed to execute command to remove host node id");
293 else
294 invalidate_host_last_connected(&host_uuid);
295
296 done:
297 REPORT_BIND_FAIL(res, param);
298 SQLITE_FINALIZE(res);
299 skip:
300 freez(machine_guid);
301 }
302
303 struct judy_list_t {
304 Pvoid_t JudyL;
305 Word_t count;
306 };
307
308 static void async_cb(uv_async_t *handle __maybe_unused)
309 {
310 ;
311 }
312
313 #define TIMER_PERIOD_MS (1000)
314
315 static void after_aclk_run_query_job(uv_work_t *req, int status __maybe_unused)
316 {
317 worker_data_t *worker = req->data;
318 struct aclk_sync_config_s *config = worker->config;
319 config->aclk_queries_running--;
320 return_worker(&config->worker_pool, worker);
321 }
322
323 static void aclk_run_query(struct aclk_sync_config_s *config, aclk_query_t *query)
324 {
325 if (query->type == UNKNOWN || query->type >= ACLK_QUERY_TYPE_COUNT) {
326 error_report("Unknown query in query queue. %u", query->type);
327 aclk_query_free(query);
328 return;
329 }
330
331 bool ok_to_send = true;
332 mqtt_wss_client client = __atomic_load_n(&config->client, __ATOMIC_RELAXED);
333
334 switch (query->type) {
335
336 // Incoming : cloud -> agent
337 case HTTP_API_V2:
338 worker_is_busy(UV_EVENT_ACLK_QUERY_EXECUTE);
339 if (client)
340 http_api_v2(client, query);
341 ok_to_send = false;
342 break;
343 case CTX_CHECKPOINT:
344 worker_is_busy(UV_EVENT_CTX_CHECKPOINT);
345 rrdcontext_hub_checkpoint_command(query->data.payload);
346 ok_to_send = false;
347 break;
348 case CTX_STOP_STREAMING:
349 worker_is_busy(UV_EVENT_CTX_STOP_STREAMING);
350 rrdcontext_hub_stop_streaming_command(query->data.payload);
351 ok_to_send = false;
352 break;
353 case SEND_NODE_INSTANCES:
354 worker_is_busy(UV_EVENT_SEND_NODE_INSTANCES);
355 aclk_send_node_instances();
356 ok_to_send = false;
357 break;
358 case ALERT_START_STREAMING:
359 worker_is_busy(UV_EVENT_ALERT_START_STREAMING);
360 aclk_start_alert_streaming(query->data.node_id, query->version);
361 ok_to_send = false;
362 break;
363 case ALERT_CHECKPOINT:
364 worker_is_busy(UV_EVENT_ALERT_CHECKPOINT);
365 aclk_alert_version_check(query->data.node_id, query->claim_id, query->version);
366 ok_to_send = false;
367 break;
368 case CREATE_NODE_INSTANCE:
369 worker_is_busy(UV_EVENT_CREATE_NODE_INSTANCE);
370 create_node_instance_result_job(query->machine_guid, query->data.node_id);
371 ok_to_send = false;
372 break;
373
374 // Outgoing: agent -> cloud
375 case ALARM_PROVIDE_CFG:
376 worker_is_busy(UV_EVENT_ALARM_PROVIDE_CFG);
377 break;
378 case ALARM_SNAPSHOT:
379 worker_is_busy(UV_EVENT_ALARM_SNAPSHOT);
380 break;
381 case REGISTER_NODE:
382 worker_is_busy(UV_EVENT_REGISTER_NODE);
383 break;
384 case UPDATE_NODE_COLLECTORS:
385 worker_is_busy(UV_EVENT_UPDATE_NODE_COLLECTORS);
386 break;
387 case UPDATE_NODE_INFO:
388 worker_is_busy(UV_EVENT_UPDATE_NODE_INFO);
389 break;
390 case CTX_SEND_SNAPSHOT:
391 worker_is_busy(UV_EVENT_CTX_SEND_SNAPSHOT);
392 break;
393 case CTX_SEND_SNAPSHOT_UPD:
394 worker_is_busy(UV_EVENT_CTX_SEND_SNAPSHOT_UPD);
395 break;
396 case NODE_STATE_UPDATE:
397 worker_is_busy(UV_EVENT_NODE_STATE_UPDATE);
398 break;
399 default:
400 nd_log_daemon(NDLP_ERR, "Unknown msg type %u; ignoring", query->type);
401 ok_to_send = false;
402 break;
403 }
404
405 if (ok_to_send) {
406 if (client)
407 send_bin_msg(client, query);
408 else {
409 freez(query->data.bin_payload.payload);
410 nd_log_daemon(NDLP_ERR, "No client to send message %u", query->type);
411 }
412 }
413
414 aclk_query_free(query);
415 }
416
417 static void aclk_run_query_job(uv_work_t *req)
418 {
419 register_libuv_worker_jobs();
420
421 worker_data_t *worker = req->data;
422 struct aclk_sync_config_s *config = worker->config;
423 aclk_query_t *query = (aclk_query_t *)worker->payload;
424
425 // aclk_run_query() frees the query; if we're shutting down we must still free it here
426 if (unlikely(__atomic_load_n(&config->shutdown_requested, __ATOMIC_RELAXED)))
427 aclk_query_free(query);
428 else
429 aclk_run_query(config, query);
430 worker_is_idle();
431 }
432
433 static void after_aclk_execute_batch(uv_work_t *req, int status __maybe_unused)
434 {
435 worker_data_t *worker = req->data;
436 struct aclk_sync_config_s *config = worker->config;
437 config->aclk_batch_job_is_running = false;
438 return_worker(&config->worker_pool, worker);
439 }
440
441 static void aclk_execute_batch(uv_work_t *req)
442 {
443 register_libuv_worker_jobs();
444
445 worker_data_t *worker = req->data;
446 struct aclk_sync_config_s *config = worker->config;
447 struct judy_list_t *aclk_query_batch = worker->payload;
448
449 if (!aclk_query_batch)
450 return;
451
452 Word_t Index = 0;
453 bool first = true;
454 Pvoid_t *Pvalue;
455 while ((Pvalue = JudyLFirstThenNext(aclk_query_batch->JudyL, &Index, &first))) {
456 if (!*Pvalue)
457 continue;
458
459 aclk_query_t *query = *Pvalue;
460 // Shutdown may be requested while this batch is already running, so
461 // re-check before each query instead of relying on a stale snapshot.
462 if (unlikely(__atomic_load_n(&config->shutdown_requested, __ATOMIC_RELAXED)))
463 aclk_query_free(query);
464 else
465 aclk_run_query(config, query);
466 }
467
468 (void) JudyLFreeArray(&aclk_query_batch->JudyL, PJE0);
469 freez(aclk_query_batch);
470
471 worker_is_idle();
472 }
473
474 struct notify_timer_cb_data {
475 void *payload;
476 struct completion *completion;
477 };
478
479 static void after_do_unregister_node(uv_work_t *req, int status __maybe_unused)
480 {
481 worker_data_t *worker = req->data;
482 struct aclk_sync_config_s *config = worker->config;
483 return_worker(&config->worker_pool, worker);
484 }
485
486 static void do_unregister_node(uv_work_t *req)
487 {
488 register_libuv_worker_jobs();
489
490 worker_data_t *worker = req->data;
491
492 worker_is_busy(UV_EVENT_UNREGISTER_NODE);
493
494 sql_unregister_node(worker->payload);
495
496 worker_is_idle();
497 }
498
499 static void notify_timer_close_callback(uv_handle_t *handle)
500 {
501 struct notify_timer_cb_data *data = handle->data;
502 if (data->completion) {
503 completion_mark_complete(data->completion);
504 }
505 freez(data);
506 }
507
508 static void node_update_timer_cb(uv_timer_t *handle)
509 {
510 struct aclk_sync_cfg_t *aclk_host_config = handle->data;
511 if (unlikely(!aclk_host_config))
512 return;
513
514 RRDHOST *host = aclk_host_config->host;
515
516 if(!host || aclk_host_state_update_auto(host))
517 uv_timer_stop(&aclk_host_config->timer);
518 }
519
520 static void after_start_alert_push(uv_work_t *req, int status __maybe_unused)
521 {
522 struct worker_data *worker = req->data;
523 struct aclk_sync_config_s *config = worker->config;
524
525 config->alert_push_running = false;
526 return_worker(&config->worker_pool, worker);
527 }
528
529 // Worker thread to scan hosts for pending metadata to store
530 static void start_alert_push(uv_work_t *req)
531 {
532 register_libuv_worker_jobs();
533
534 struct worker_data *worker = req->data;
535 struct aclk_sync_config_s *config = worker->config;
536
537 if (unlikely(__atomic_load_n(&config->shutdown_requested, __ATOMIC_RELAXED)))
538 return;
539
540 worker_is_busy(UV_EVENT_ACLK_NODE_INFO);
541 aclk_check_node_info_and_collectors();
542 worker_is_idle();
543
544 worker_is_busy(UV_EVENT_ACLK_ALERT_PUSH);
545 aclk_push_alert_events_for_all_hosts();
546 worker_is_idle();
547 }
548
549 #define MAX_ACLK_BATCH_JOBS_IN_QUEUE (20)
550
551 #define MAX_BATCH_SIZE (64)
552
553 // Take a query, and try to schedule it in a worker
554 // Update config->aclk_queries_running if success
555 // config->aclk_queries_running is only accessed from the vent loop
556 // On failure: free the payload
557
558 int schedule_query_in_worker(uv_loop_t *loop, struct aclk_sync_config_s *config, aclk_query_t *query) {
559
560 worker_data_t *worker = get_worker(&config->worker_pool);
561 worker->payload = query;
562 worker->config = config;
563
564 config->aclk_queries_running++;
565 int rc = uv_queue_work(loop, &worker->request, aclk_run_query_job, after_aclk_run_query_job);
566 if (rc) {
567 config->aclk_queries_running--;
568 return_worker(&config->worker_pool, worker);
569 }
570 return rc;
571 }
572
573 static void free_query_list(Pvoid_t JudyL)
574 {
575 bool first = true;
576 Pvoid_t *Pvalue;
577 Word_t Index = 0;
578 aclk_query_t *query;
579 while ((Pvalue = JudyLFirstThenNext(JudyL, &Index, &first))) {
580 if (!*Pvalue)
581 continue;
582 query = *Pvalue;
583 aclk_query_free(query);
584 }
585 }
586
587 static void timer_cb(uv_timer_t *handle)
588 {
589 struct aclk_sync_config_s *config = handle->data;
590
591 if (aclk_online_for_alerts()) {
592 worker_data_t *worker;
593 if (!config->alert_push_running) {
594 worker = get_worker(&config->worker_pool);
595 worker->config = config;
596 config->alert_push_running = true;
597 if (uv_queue_work(handle->loop, &worker->request, start_alert_push, after_start_alert_push)) {
598 config->alert_push_running = false;
599 return_worker(&config->worker_pool, worker);
600 }
601 }
602 }
603
604 if (config->aclk_jobs_pending > 0)
605 config->run_query_batch = true;
606 }
607
608 #define SHUTDOWN_SLEEP_INTERVAL_MS (100)
609 #define ACLK_SHUTDOWN_WATCHDOG_TIMEOUT_SECONDS (15)
610 #define CMD_POOL_SIZE (2048)
611
612 #define ACLK_JOBS_ARE_RUNNING \
613 (config->aclk_queries_running || config->alert_push_running || config->aclk_batch_job_is_running)
614
615 static void aclk_synchronization_event_loop(void *arg)
616 {
617 struct aclk_sync_config_s *config = arg;
618 uv_thread_set_name_np("ACLKSYNC");
619 init_cmd_pool(&config->cmd_pool, CMD_POOL_SIZE);
620
621 worker_register("ACLKSYNC");
622
623 service_register(NULL, NULL, NULL);
624
625 worker_register_job_name(ACLK_DATABASE_NOOP, "noop");
626 worker_register_job_name(ACLK_DATABASE_NODE_STATE, "node state");
627 worker_register_job_name(ACLK_DATABASE_PUSH_ALERT_CONFIG, "alert conf push");
628 worker_register_job_name(ACLK_QUERY_BATCH_EXECUTE, "aclk batch execute");
629 worker_register_job_name(ACLK_QUERY_BATCH_ADD, "aclk batch add");
630 worker_register_job_name(ACLK_MQTT_WSS_CLIENT_SET, "config mqtt client");
631 worker_register_job_name(ACLK_MQTT_WSS_CLIENT_RESET, "reset mqtt client");
632 worker_register_job_name(ACLK_DATABASE_NODE_UNREGISTER, "unregister node");
633 worker_register_job_name(ACLK_CANCEL_NODE_UPDATE_TIMER, "cancel node update timer");
634 worker_register_job_name(ACLK_QUEUE_NODE_INFO, "queue node info");
635
636 uv_loop_t *loop = &config->loop;
637 fatal_assert(0 == uv_loop_init(loop));
638 fatal_assert(0 == uv_async_init(loop, &config->async, async_cb));
639
640 fatal_assert(0 == uv_timer_init(loop, &config->timer_req));
641 config->timer_req.data = config;
642 fatal_assert(0 == uv_timer_start(&config->timer_req, timer_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS));
643
644 netdata_log_info("Starting ACLK synchronization thread");
645
646 sql_delete_aclk_table_list();
647
648 int query_thread_count = (int) netdata_conf_cloud_query_threads();
649 netdata_log_info("Starting ACLK synchronization thread with %d parallel query threads", query_thread_count);
650
651 struct notify_timer_cb_data *timer_cb_data;
652
653 // This holds queries that need to be executed one by one
654 struct judy_list_t *aclk_query_batch = NULL;
655
656 // This holds queries that can be dispatched in parallel in ACLK QUERY worker threads
657 struct judy_list_t *aclk_query_execute = callocz(1, sizeof(*aclk_query_execute));
658 size_t pending_queries = 0;
659
660 Pvoid_t *Pvalue;
661 worker_data_t *worker;
662
663 __atomic_store_n(&config->shutdown_requested, false, __ATOMIC_RELAXED);
664 config->initialized = true;
665 completion_mark_complete(&config->start_stop_complete);
666
667 while (likely(!__atomic_load_n(&config->shutdown_requested, __ATOMIC_RELAXED))) {
668 enum aclk_database_opcode opcode;
669 RRDHOST *host;
670 struct aclk_sync_cfg_t *aclk_host_config;
671 aclk_query_t *query;
672 worker_is_idle();
673 uv_run(loop, UV_RUN_ONCE);
674
675 do {
676 cmd_data_t cmd;
677
678 if (config->run_query_batch) {
679 opcode = ACLK_QUERY_BATCH_EXECUTE;
680 config->run_query_batch = false;
681 }
682 else
683 {
684 cmd = aclk_database_deq_cmd();
685 opcode = cmd.opcode;
686 }
687
688 if(likely(opcode != ACLK_DATABASE_NOOP && opcode != ACLK_QUERY_EXECUTE))
689 worker_is_busy(opcode);
690
691 // Check if we have pending commands to execute
692 if (opcode == ACLK_DATABASE_NOOP && pending_queries && config->aclk_queries_running < query_thread_count) {
693 opcode = ACLK_QUERY_EXECUTE;
694 cmd.param[0] = NULL;
695 }
696
697 switch (opcode) {
698 case ACLK_DATABASE_NOOP:
699 /* the command queue was empty, do nothing */
700 break;
701 // NODE STATE
702 case ACLK_DATABASE_NODE_STATE:
703 host = cmd.param[0];
704 aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
705 if (unlikely(!aclk_host_config)) {
706 create_aclk_config(host, &host->host_id.uuid, &host->node_id.uuid);
707 aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
708 }
709
710 if (aclk_host_config) {
711 uint64_t schedule_time = (uint64_t)(uintptr_t)cmd.param[1];
712 if (!aclk_host_config->timer_initialized) {
713 int rc = uv_timer_init(loop, &aclk_host_config->timer);
714 if (!rc) {
715 aclk_host_config->timer_initialized = true;
716 aclk_host_config->timer.data = aclk_host_config;
717 }
718 }
719
720 if (aclk_host_config->timer_initialized) {
721 if (uv_is_active((uv_handle_t *)&aclk_host_config->timer))
722 uv_timer_stop(&aclk_host_config->timer);
723
724 aclk_host_config->timer.data = aclk_host_config;
725 int rc = uv_timer_start(&aclk_host_config->timer, node_update_timer_cb, schedule_time, 5000);
726 if (!rc)
727 break; // Timer started, exit
728 }
729 }
730
731 // This is fallback if timer fails
732 aclk_host_state_update_auto(host);
733 break;
734 case ACLK_QUEUE_NODE_INFO:
735 host = cmd.param[0];
736 bool immediate = (bool)(uintptr_t)cmd.param[1];
737 aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
738 if (unlikely(!aclk_host_config)) {
739 create_aclk_config(host, &host->host_id.uuid, &host->node_id.uuid);
740 aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
741 }
742 aclk_host_config->node_info_send_time = (host == localhost || immediate) ? 1 : now_realtime_sec();
743 break;
744 case ACLK_CANCEL_NODE_UPDATE_TIMER:
745 host = cmd.param[0];
746 struct completion *compl = cmd.param[1];
747 aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
748 if (!aclk_host_config || !aclk_host_config->timer_initialized) {
749 completion_mark_complete(compl);
750 break;
751 }
752 if (uv_is_active((uv_handle_t *)&aclk_host_config->timer))
753 uv_timer_stop(&aclk_host_config->timer);
754
755 aclk_host_config->timer_initialized = false;
756 timer_cb_data = mallocz(sizeof(*timer_cb_data));
757 timer_cb_data->payload = host;
758 timer_cb_data->completion = compl;
759 aclk_host_config->timer.data = timer_cb_data;
760 uv_close((uv_handle_t *)&aclk_host_config->timer, notify_timer_close_callback);
761 break;
762
763 case ACLK_DATABASE_NODE_UNREGISTER:
764 worker = get_worker(&config->worker_pool);
765 worker->config = config;
766 worker->payload = cmd.param[0];
767
768 if (uv_queue_work(loop, &worker->request, do_unregister_node, after_do_unregister_node)) {
769 freez(cmd.param[0]);
770 return_worker(&config->worker_pool, worker);
771 }
772 break;
773 case ACLK_DATABASE_PUSH_ALERT_CONFIG:
774 aclk_push_alert_config_event(cmd.param[0], cmd.param[1]);
775 break;
776 case ACLK_MQTT_WSS_CLIENT_SET:
777 config->client = (mqtt_wss_client)cmd.param[0];
778 break;
779 case ACLK_MQTT_WSS_CLIENT_RESET:
780 __atomic_store_n(&config->client, NULL, __ATOMIC_RELEASE);
781 struct completion *comp = cmd.param[0];
782 completion_mark_complete(comp);
783 break;
784 case ACLK_QUERY_EXECUTE:
785 query = (aclk_query_t *) cmd.param[0];
786
787 bool too_busy = (config->aclk_queries_running >= query_thread_count);
788
789 // If we are busy and it's just a ping to run, leave
790 if (too_busy && !query)
791 break;
792
793 // if we are busy (we have a query) store it and leave
794 if (too_busy) {
795 Pvalue = JudyLIns(&aclk_query_execute->JudyL, ++aclk_query_execute->count, PJE0);
796 if (Pvalue != PJERR) {
797 *Pvalue = query;
798 pending_queries++;
799 } else {
800 nd_log_daemon(NDLP_ERR, "Failed to add ACLK command to the pending commands Judy");
801 aclk_query_free(query);
802 }
803 break;
804 }
805
806 // Here: we are not busy
807 // If we have query it was a normal incoming command
808 // if we dont, it was a ping from the callback
809
810 // Lets try to queue as many of the pending commands
811 while(!too_busy && pending_queries && config->aclk_queries_running < query_thread_count) {
812
813 Word_t Index = 0;
814 Pvalue = JudyLFirst(aclk_query_execute->JudyL, &Index, PJE0);
815
816 // We have nothing, leave
817 if (Pvalue == NULL)
818 break;
819 aclk_query_t *query_in_queue = *Pvalue;
820
821 // Schedule it and increase running
822 too_busy = schedule_query_in_worker(loop, config, query_in_queue);
823
824 // It was scheduled in worker, remove it from pending
825 if (!too_busy) {
826 pending_queries--;
827 (void)JudyLDel(&aclk_query_execute->JudyL, Index, PJE0);
828 }
829 }
830
831 // Was it just a ping to run? leave
832 if (!query)
833 break;
834
835 // We have a query, if not busy lets run it
836 if (!too_busy)
837 too_busy = schedule_query_in_worker(loop, config, query);
838
839 // We were either busy, or failed to start worker, schedule for later
840 if (too_busy) {
841 Pvalue = JudyLIns(&aclk_query_execute->JudyL, ++aclk_query_execute->count, PJE0);
842 if (Pvalue != PJERR) {
843 *Pvalue = query;
844 pending_queries++;
845 }
846 else {
847 nd_log_daemon(NDLP_ERR, "Failed to add ACLK command to the pending commands Judy");
848 aclk_query_free(query);
849 }
850 }
851 break;
852
853 // Note: The following two opcodes must be in this order
854 case ACLK_QUERY_BATCH_ADD:
855 query = (aclk_query_t *)cmd.param[0];
856 if (!query)
857 break;
858
859 if (!aclk_query_batch)
860 aclk_query_batch = callocz(1, sizeof(*aclk_query_batch));
861
862 Pvalue = JudyLIns(&aclk_query_batch->JudyL, ++aclk_query_batch->count, PJE0);
863 if (Pvalue != PJERR)
864 *Pvalue = query;
865 else {
866 aclk_query_free(query);
867 aclk_query_batch->count--;
868
869 // Clean up the batch structure if this was the first entry that failed
870 if (aclk_query_batch->count == 0) {
871 freez(aclk_query_batch);
872 aclk_query_batch = NULL;
873 }
874 break;
875 }
876
877 config->aclk_jobs_pending++;
878 if (aclk_query_batch->count < MAX_ACLK_BATCH_JOBS_IN_QUEUE || config->aclk_batch_job_is_running)
879 break;
880 // fall through
881 case ACLK_QUERY_BATCH_EXECUTE:
882 if (!aclk_query_batch || config->aclk_batch_job_is_running)
883 break;
884
885 worker = get_worker(&config->worker_pool);
886 worker->config = config;
887 worker->payload = aclk_query_batch;
888
889 config->aclk_batch_job_is_running = true;
890 config->aclk_jobs_pending -= aclk_query_batch->count;
891 aclk_query_batch = NULL;
892
893 if (uv_queue_work(loop, &worker->request, aclk_execute_batch, after_aclk_execute_batch)) {
894 aclk_query_batch = worker->payload;
895 config->aclk_jobs_pending += aclk_query_batch->count;
896 return_worker(&config->worker_pool, worker);
897 config->aclk_batch_job_is_running = false;
898 }
899 break;
900 case ACLK_SYNC_SHUTDOWN:
901 __atomic_store_n(&config->shutdown_requested, true, __ATOMIC_RELAXED);
902 mark_pending_req_cancel_all();
903 break;
904 default:
905 break;
906 }
907
908 if (opcode != ACLK_DATABASE_NOOP)
909 uv_run(loop, UV_RUN_NOWAIT);
910
911 } while (opcode != ACLK_DATABASE_NOOP);
912 }
913 config->initialized = false;
914
915 if (!uv_timer_stop(&config->timer_req))
916 uv_close((uv_handle_t *)&config->timer_req, NULL);
917
918 uv_close((uv_handle_t *)&config->async, NULL);
919 uv_walk(loop, libuv_close_callback, NULL);
920
921 size_t shutdown_wait_iterations = 0;
922 const size_t log_every_iterations = (10 * MSEC_PER_SEC) / SHUTDOWN_SLEEP_INTERVAL_MS;
923 const size_t watchdog_iterations = (ACLK_SHUTDOWN_WATCHDOG_TIMEOUT_SECONDS * MSEC_PER_SEC) / SHUTDOWN_SLEEP_INTERVAL_MS;
924
925 while (ACLK_JOBS_ARE_RUNNING || uv_loop_alive(loop)) {
926 (void)uv_run(loop, UV_RUN_NOWAIT);
927
928 shutdown_wait_iterations++;
929
930 if (shutdown_wait_iterations >= watchdog_iterations) {
931 nd_log_daemon(
932 NDLP_ERR,
933 "ACLK: shutdown watchdog timeout (%d seconds) exceeded, abandoning outstanding libuv jobs "
934 "(queries_running=%d, alert_push_running=%d, batch_job_running=%d)",
935 ACLK_SHUTDOWN_WATCHDOG_TIMEOUT_SECONDS,
936 config->aclk_queries_running,
937 config->alert_push_running,
938 config->aclk_batch_job_is_running);
939 break;
940 }
941
942 if ((shutdown_wait_iterations % log_every_iterations) == 0) {
943 nd_log_daemon(
944 NDLP_WARNING,
945 "ACLK: waiting for outstanding libuv jobs during shutdown "
946 "(queries_running=%d, alert_push_running=%d, batch_job_running=%d)",
947 config->aclk_queries_running,
948 config->alert_push_running,
949 config->aclk_batch_job_is_running);
950 }
951
952 sleep_usec(SHUTDOWN_SLEEP_INTERVAL_MS * USEC_PER_MS);
953 }
954
955 (void) uv_loop_close(loop);
956
957 // Free execute commands / queries
958 free_query_list(aclk_query_execute->JudyL);
959 (void)JudyLFreeArray(&aclk_query_execute->JudyL, PJE0);
960 freez(aclk_query_execute);
961
962 // Free batch commands
963 if (aclk_query_batch) {
964 free_query_list(aclk_query_batch->JudyL);
965 (void)JudyLFreeArray(&aclk_query_batch->JudyL, PJE0);
966 freez(aclk_query_batch);
967 }
968
969 release_cmd_pool(&config->cmd_pool);
970 worker_unregister();
971 service_exits();
972 completion_mark_complete(&config->start_stop_complete);
973 }
974
975 static void aclk_initialize_event_loop(void)
976 {
977 memset(&aclk_sync_config, 0, sizeof(aclk_sync_config));
978 completion_init(&aclk_sync_config.start_stop_complete);
979
980 init_worker_pool(&aclk_sync_config.worker_pool);
981
982 aclk_sync_config.thread = nd_thread_create("ACLKSYNC", NETDATA_THREAD_OPTION_DEFAULT, aclk_synchronization_event_loop, &aclk_sync_config);
983 fatal_assert(NULL != aclk_sync_config.thread);
984
985 completion_wait_for(&aclk_sync_config.start_stop_complete);
986 // Keep completion, just reset it for next use during shutdown
987 completion_reset(&aclk_sync_config.start_stop_complete);
988 }
989
990 // -------------------------------------------------------------
991
992 void create_aclk_config(RRDHOST *host, nd_uuid_t *host_uuid __maybe_unused, nd_uuid_t *node_id __maybe_unused)
993 {
994
995 if (!host || __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE))
996 return;
997
998 struct aclk_sync_cfg_t *aclk_host_config = callocz(1, sizeof(struct aclk_sync_cfg_t));
999 spinlock_init(&aclk_host_config->pending_ctx_spinlock);
1000 if (node_id && !uuid_is_null(*node_id))
1001 uuid_unparse_lower(*node_id, aclk_host_config->node_id);
1002
1003 // Initialize every field BEFORE publishing the pointer via CAS; the RELEASE on
1004 // the CAS pairs with ACQUIRE loads of host->aclk_host_config in readers so they
1005 // cannot observe the pointer with zero-initialized fields (host == NULL etc.).
1006 aclk_host_config->host = host;
1007 aclk_host_config->stream_alerts = false;
1008 time_t now = now_realtime_sec();
1009 aclk_host_config->node_info_send_time = (host == localhost || NULL == localhost) ? now - 25 : now;
1010
1011 struct aclk_sync_cfg_t *expected = NULL;
1012 if (__atomic_compare_exchange_n(&host->aclk_host_config, &expected, aclk_host_config, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED)) {
1013 if (node_id && UUIDiszero(host->node_id))
1014 uuid_copy(host->node_id.uuid, *node_id);
1015 }
1016 else {
1017 freez(aclk_host_config);
1018 return;
1019 }
1020 }
1021
1022 // Replaces two correlated subqueries (host_label, node_instance) with LEFT JOINs
1023 // so the planner does the lookup once per row instead of 2N extra index probes.
1024 // memory_mode and health_enabled are intentionally omitted — they were SELECTed
1025 // in the previous shape but never read by the consumer.
1026 //
1027 // Both LEFT JOINs are guaranteed to match at most one row per host by the
1028 // schema (sqlite_metadata.c database_config[]): host_label has
1029 // PRIMARY KEY (host_id, label_key), and node_instance has host_id PRIMARY KEY.
1030 // Row multiplication is therefore impossible here without a SQLite invariant
1031 // violation; no DISTINCT / GROUP BY / EXISTS wrapper needed.
1032 #define SQL_FETCH_ALL_HOSTS \
1033 "SELECT h.host_id, h.hostname, h.registry_hostname, h.update_every, h.os, " \
1034 "h.timezone, h.hops, h.abbrev_timezone, h.utc_offset, h.program_name, " \
1035 "h.program_version, h.entries, h.last_connected, " \
1036 "CASE WHEN hl.label_value = 'true' THEN 1 ELSE 0 END, " \
1037 "CASE WHEN ni.node_id IS NULL THEN 0 ELSE 1 END " \
1038 "FROM host h " \
1039 "LEFT JOIN host_label hl ON hl.host_id = h.host_id AND hl.label_key = '_is_ephemeral' " \
1040 "LEFT JOIN node_instance ni ON ni.host_id = h.host_id " \
1041 "WHERE h.hops > 0"
1042
1043 #define SQL_FETCH_ALL_INSTANCES \
1044 "SELECT ni.host_id, ni.node_id FROM host h, node_instance ni " \
1045 "WHERE h.host_id = ni.host_id AND ni.node_id IS NOT NULL"
1046
1047
1048 uv_sem_t ctx_sem;
1049
1050 void aclk_synchronization_init(void)
1051 {
1052 nd_log_daemon(NDLP_INFO, "Creating archived hosts");
1053 struct children node_data = { 0, 0 };
1054
1055 sqlite3_stmt *res = NULL;
1056 if (PREPARE_STATEMENT(db_meta, SQL_FETCH_ALL_HOSTS, &res)) {
1057 int step_rc;
1058 while ((step_rc = sqlite3_step_monitored(res)) == SQLITE_ROW) {
1059 RRDHOST *host = load_archived_host_from_row(res);
1060 if (!host)
1061 continue;
1062 if (IS_VIRTUAL_HOST_OS(host))
1063 node_data.vnodes++;
1064 else
1065 node_data.normal++;
1066 }
1067 if (step_rc != SQLITE_DONE)
1068 nd_log_daemon(
1069 NDLP_ERR,
1070 "SQLite error while loading archived hosts, rc = %d (%s); load may be partial",
1071 step_rc,
1072 sqlite3_errmsg(db_meta));
1073 SQLITE_FINALIZE(res);
1074 }
1075 else
1076 nd_log_daemon(NDLP_ERR,
1077 "SQLite error when preparing statement to load archived hosts: %s",
1078 sqlite3_errmsg(db_meta));
1079
1080 nd_log_daemon(
1081 NDLP_INFO,
1082 "Created %d archived hosts (%d children and %d vnodes)",
1083 node_data.normal + node_data.vnodes,
1084 node_data.normal,
1085 node_data.vnodes);
1086
1087 bool sem_init = true;
1088 uv_sem_init(&ctx_sem, 0);
1089
1090 // Trigger host context load for hosts that have been created
1091 if (unlikely(!metadata_queue_load_host_context())) {
1092 nd_log_daemon(NDLP_WARNING, "Failed to queue command to load contexts for archived hosts");
1093 // Reset context load flag so that contexts will be loaded on demand
1094 reset_host_context_load_flag();
1095 uv_sem_destroy(&ctx_sem);
1096 sem_init = false;
1097 }
1098
1099 sqlite3_stmt *res_inst = NULL;
1100 if (PREPARE_STATEMENT(db_meta, SQL_FETCH_ALL_INSTANCES, &res_inst)) {
1101 int step_rc;
1102 while ((step_rc = sqlite3_step_monitored(res_inst)) == SQLITE_ROW) {
1103 nd_uuid_t host_uuid, node_uuid;
1104 if (!sqlite3_column_uuid_copy(res_inst, 0, host_uuid)) {
1105 nd_log_daemon(
1106 NDLP_ERR,
1107 "Skipping node_instance row: host_id (col 0) is not a valid 16-byte UUID blob (type=%d, bytes=%d). ACLK config not configured for this host.",
1108 sqlite3_column_type(res_inst, 0),
1109 sqlite3_column_bytes(res_inst, 0));
1110 continue;
1111 }
1112 if (!sqlite3_column_uuid_copy(res_inst, 1, node_uuid)) {
1113 nd_log_daemon(
1114 NDLP_ERR,
1115 "Skipping node_instance row: node_id (col 1) is not a valid 16-byte UUID blob (type=%d, bytes=%d). ACLK config not configured for this host.",
1116 sqlite3_column_type(res_inst, 1),
1117 sqlite3_column_bytes(res_inst, 1));
1118 continue;
1119 }
1120
1121 char uuid_str[UUID_STR_LEN];
1122 uuid_unparse_lower(host_uuid, uuid_str);
1123 RRDHOST *host = rrdhost_find_by_guid(uuid_str);
1124 // create_aclk_config() already null-checks `host`, but the explicit
1125 // guard makes the intent clear and skips the call for unknown GUIDs.
1126 if (host && host != localhost)
1127 create_aclk_config(host, &host_uuid, &node_uuid);
1128 }
1129 if (step_rc != SQLITE_DONE)
1130 nd_log_daemon(
1131 NDLP_ERR,
1132 "SQLite error while configuring host ACLK synchronization parameters, rc = %d (%s); some configs may be missing",
1133 step_rc,
1134 sqlite3_errmsg(db_meta));
1135 SQLITE_FINALIZE(res_inst);
1136 }
1137 else
1138 nd_log_daemon(NDLP_ERR,
1139 "SQLite error when preparing statement to configure host ACLK synchronization parameters: %s",
1140 sqlite3_errmsg(db_meta));
1141
1142 aclk_initialize_event_loop();
1143
1144 if (!(node_data.normal + node_data.vnodes))
1145 aclk_queue_node_info(localhost, true);
1146
1147 if (sem_init) {
1148 int finished_vnodes = 0;
1149 time_t deadline = now_realtime_sec() + 60; // hard timeput to avoid infinite block
1150 while (finished_vnodes < node_data.vnodes) {
1151 if (uv_sem_trywait(&ctx_sem) == 0) {
1152 finished_vnodes++;
1153 continue;
1154 }
1155
1156 if (now_realtime_sec() >= deadline) {
1157 nd_log_daemon(NDLP_WARNING, "Vnodes context load still in progress, continue with agent start");
1158 break;
1159 }
1160 sleep_usec(100 * USEC_PER_MS);
1161 }
1162 if (finished_vnodes == node_data.vnodes) {
1163 uv_sem_destroy(&ctx_sem);
1164 }
1165 }
1166 nd_log_daemon(NDLP_INFO, "ACLK sync initialization completed");
1167 }
1168
1169 static inline bool queue_aclk_sync_cmd(enum aclk_database_opcode opcode, const void *param0, const void *param1)
1170 {
1171 cmd_data_t cmd;
1172 cmd.opcode = opcode;
1173 cmd.param[0] = (void *) param0;
1174 cmd.param[1] = (void *) param1;
1175 return aclk_database_enq_cmd(&cmd, true);
1176 }
1177
1178 void aclk_synchronization_shutdown(void)
1179 {
1180 if (!aclk_sync_config.thread)
1181 return;
1182
1183 // Send shutdown command, note that the completion is initialized
1184 // on init and still valid
1185 aclk_mqtt_client_reset();
1186
1187 if (queue_aclk_sync_cmd(ACLK_SYNC_SHUTDOWN, NULL, NULL))
1188 completion_wait_for(&aclk_sync_config.start_stop_complete);
1189
1190 completion_destroy(&aclk_sync_config.start_stop_complete);
1191 int rc = nd_thread_join(aclk_sync_config.thread);
1192 if (rc)
1193 nd_log_daemon(NDLP_ERR, "ACLK: Failed to join synchronization thread");
1194 else
1195 nd_log_daemon(NDLP_INFO, "ACLK: synchronization thread shutdown completed");
1196 }
1197
1198 // Public
1199 void aclk_push_alert_config(const char *node_id, const char *config_hash)
1200 {
1201 if (unlikely(!node_id || !config_hash))
1202 return;
1203
1204 char *node_id_dup = strdupz(node_id);
1205 char *config_hash_dup = strdupz(config_hash);
1206 bool queued = queue_aclk_sync_cmd(ACLK_DATABASE_PUSH_ALERT_CONFIG, node_id_dup, config_hash_dup);
1207 if (unlikely(!queued)) {
1208 nd_log_daemon(NDLP_WARNING, "ACLK: Failed to queue alert config push for node %s (config hash %s)", node_id, config_hash);
1209 freez(node_id_dup);
1210 freez(config_hash_dup);
1211 }
1212 }
1213
1214 void aclk_execute_query(aclk_query_t *query)
1215 {
1216 if (unlikely(!query))
1217 return;
1218
1219 bool queued = queue_aclk_sync_cmd(ACLK_QUERY_EXECUTE, query, NULL);
1220 if (unlikely(!queued)) {
1221 nd_log_daemon(NDLP_WARNING, "ACLK: Failed to queue query execution");
1222 aclk_query_free(query);
1223 }
1224 }
1225
1226 void aclk_add_job(aclk_query_t *query)
1227 {
1228 if (unlikely(!query))
1229 return;
1230
1231 bool queued = queue_aclk_sync_cmd(ACLK_QUERY_BATCH_ADD, query, NULL);
1232 if (unlikely(!queued)) {
1233 nd_log_daemon(NDLP_WARNING, "ACLK: Failed to queue query job");
1234 aclk_query_free(query);
1235 }
1236 }
1237
1238 void aclk_mqtt_client_set(mqtt_wss_client client)
1239 {
1240 (void) queue_aclk_sync_cmd(ACLK_MQTT_WSS_CLIENT_SET, client, NULL);
1241 }
1242
1243 void aclk_mqtt_client_reset()
1244 {
1245 if (!__atomic_load_n(&aclk_sync_config.client, __ATOMIC_RELAXED))
1246 return;
1247
1248 struct completion compl;
1249 completion_init(&compl);
1250 if (queue_aclk_sync_cmd(ACLK_MQTT_WSS_CLIENT_RESET, &compl, NULL))
1251 completion_wait_for(&compl);
1252 completion_destroy(&compl);
1253 }
1254
1255 void schedule_node_state_update(RRDHOST *host, uint64_t delay)
1256 {
1257 if (unlikely(!host))
1258 return;
1259
1260 (void) queue_aclk_sync_cmd(ACLK_DATABASE_NODE_STATE, host, (void *)(uintptr_t)delay);
1261 }
1262
1263 void unregister_node(const char *machine_guid)
1264 {
1265 if (unlikely(!machine_guid))
1266 return;
1267
1268 char *machine_guid_dup = strdupz(machine_guid);
1269 bool queued = queue_aclk_sync_cmd(ACLK_DATABASE_NODE_UNREGISTER, machine_guid_dup, NULL);
1270 if (unlikely(!queued)) {
1271 nd_log_daemon(NDLP_WARNING, "ACLK: Failed to queue unregister node command for %s", machine_guid);
1272 freez(machine_guid_dup);
1273 }
1274 }
1275
1276 void destroy_aclk_config(RRDHOST *host)
1277 {
1278 if (!host)
1279 return;
1280
1281 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
1282 if (!aclk_host_config)
1283 return;
1284
1285 if(likely(__atomic_load_n(&aclk_sync_config.initialized, __ATOMIC_RELAXED))) {
1286 struct completion compl;
1287 completion_init(&compl);
1288
1289 if (queue_aclk_sync_cmd(ACLK_CANCEL_NODE_UPDATE_TIMER, (void *)host, (void *)&compl))
1290 completion_wait_for(&compl);
1291 completion_destroy(&compl);
1292 }
1293
1294 struct aclk_sync_cfg_t *old_aclk_host_config = __atomic_exchange_n(&host->aclk_host_config, NULL, __ATOMIC_ACQUIRE);
1295 if (!old_aclk_host_config)
1296 return;
1297
1298 // detach pending checkpoint strings under lock, to avoid racing with save/replay
1299 spinlock_lock(&old_aclk_host_config->pending_ctx_spinlock);
1300 char *pending_claim_id = old_aclk_host_config->pending_ctx_claim_id;
1301 char *pending_node_id = old_aclk_host_config->pending_ctx_node_id;
1302 old_aclk_host_config->pending_ctx_claim_id = NULL;
1303 old_aclk_host_config->pending_ctx_node_id = NULL;
1304 old_aclk_host_config->pending_ctx_version_hash = 0;
1305 old_aclk_host_config->pending_ctx_saved_monotonic_s = 0;
1306 __atomic_store_n(&old_aclk_host_config->pending_ctx_checkpoint, false, __ATOMIC_RELEASE);
1307 spinlock_unlock(&old_aclk_host_config->pending_ctx_spinlock);
1308
1309 freez(pending_claim_id);
1310 freez(pending_node_id);
1311 freez(old_aclk_host_config);
1312 }
1313
1314 void aclk_queue_node_info(RRDHOST *host, bool immediate)
1315 {
1316 (void) queue_aclk_sync_cmd(ACLK_QUEUE_NODE_INFO, (void *)host, (void *)(uintptr_t)immediate);
1317 }