Improve synchronization of dyncfg generated alert configurations to cloud (#21706)
* Track alert configs that we have sent to the cloud either directly or implicitly. On creating an alert via dyncfg check and submit the alert config if needed * Lower alert log level from INFO to DEBUG in SQLite ACLK configuration logging * Add defensive node_id null check USe compiled statements when running from health thread * Explicit init of flag Cleanup old entries on startup
Stelios Fragkakis committed
Feb 11, 2026 at 10:19 UTC
39a7a68db1f223983bcb7faa98657db34c2f3ec5
6 files changed
+94
src/database/sqlite/sqlite_aclk_alert.c
+76
@@ -508,6 +508,11 @@ static void aclk_push_alert_event(RRDHOST *host, sqlite3_stmt **res, sqlite3_stm
508
while (sqlite3_step_monitored(*res) == SQLITE_ROW) {
509
health_alarm_log_populate(&alarm_log, *res, host, &status);
510
aclk_send_alarm_log_entry(&alarm_log);
511
+
512
+ nd_uuid_t hash_id;
513
+ if (alarm_log.config_hash && !uuid_parse(alarm_log.config_hash, hash_id))
514
+ alert_hash_mark_sent(&hash_id);
515
+
516
aclk_host_config->alert_count++;
517
518
last_id = alarm_log.sequence_id;
@@ -716,6 +721,76 @@ void aclk_push_alert_events_for_all_hosts(void)
721
SQLITE_FINALIZE(res_version);
722
}
723
724
+#define SQL_SELECT_ALERT_HASH_CLOUD "SELECT 1 FROM alert_hash_cloud WHERE hash_id = @hash_id"
725
+#define SQL_INSERT_ALERT_HASH_CLOUD "INSERT OR IGNORE INTO alert_hash_cloud (hash_id) VALUES (@hash_id)"
726
+
727
+void alert_hash_mark_sent(nd_uuid_t *hash_id)
728
+{
729
+ if (!hash_id)
730
+ return;
731
+
732
+ static __thread sqlite3_stmt *compiled_res = NULL;
733
+ sqlite3_stmt *res = NULL;
734
+
735
+ if (is_health_thread) {
736
+ if (!compiled_res) {
737
+ if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_INSERT_ALERT_HASH_CLOUD, &compiled_res))
738
+ return;
739
+ }
740
+ res = compiled_res;
741
+ } else {
742
+ if (!PREPARE_STATEMENT(db_meta, SQL_INSERT_ALERT_HASH_CLOUD, &res))
743
+ return;
744
+ }
745
+
746
+ int param = 0;
747
+ SQLITE_BIND_FAIL(done, sqlite3_bind_blob(res, ++param, hash_id, sizeof(*hash_id), SQLITE_STATIC));
748
+
749
+ param = 0;
750
+ (void)sqlite3_step_monitored(res);
751
+
752
+done:
753
+ REPORT_BIND_FAIL(res, param);
754
+ if (is_health_thread)
755
+ SQLITE_RESET(res);
756
+ else
757
+ SQLITE_FINALIZE(res);
758
+}
759
+
760
+bool alert_hash_has_transitioned(nd_uuid_t *hash_id)
761
+{
762
+ if (!hash_id)
763
+ return false;
764
+
765
+ static __thread sqlite3_stmt *compiled_res = NULL;
766
+ sqlite3_stmt *res = NULL;
767
+
768
+ if (is_health_thread) {
769
+ if (!compiled_res) {
770
+ if (!PREPARE_COMPILED_STATEMENT(db_meta, SQL_SELECT_ALERT_HASH_CLOUD, &compiled_res))
771
+ return false;
772
+ }
773
+ res = compiled_res;
774
+ } else {
775
+ if (!PREPARE_STATEMENT(db_meta, SQL_SELECT_ALERT_HASH_CLOUD, &res))
776
+ return false;
777
+ }
778
+
779
+ int param = 0;
780
+ SQLITE_BIND_FAIL(done, sqlite3_bind_blob(res, ++param, hash_id, sizeof(*hash_id), SQLITE_STATIC));
781
+
782
+ param = 0;
783
+ bool found = (sqlite3_step_monitored(res) == SQLITE_ROW);
784
+
785
+done:
786
+ REPORT_BIND_FAIL(res, param);
787
+ if (is_health_thread)
788
+ SQLITE_RESET(res);
789
+ else
790
+ SQLITE_FINALIZE(res);
791
+ return found;
792
+}
793
+
794
void aclk_send_alert_configuration(char *config_hash)
795
{
796
if (unlikely(!config_hash))
@@ -841,6 +916,7 @@ void aclk_push_alert_config_event(char *node_id __maybe_unused, char *config_has
916
aclk_host_config->node_id,
917
aclk_host_config->host ? rrdhost_hostname(aclk_host_config->host) : "N/A", config_hash);
918
aclk_send_provide_alarm_cfg(&p_alarm_config);
919
+ alert_hash_mark_sent(&hash_uuid);
920
freez(p_alarm_config.cfg_hash);
921
destroy_aclk_alarm_configuration(&alarm_config);
922
}
src/database/sqlite/sqlite_aclk_alert.h
+2
@@ -7,6 +7,8 @@ extern sqlite3 *db_meta;
7
8
void aclk_send_alert_configuration(char *config_hash);
9
void aclk_push_alert_config_event(char *node_id, char *config_hash);
10
+bool alert_hash_has_transitioned(nd_uuid_t *hash_id);
11
+void alert_hash_mark_sent(nd_uuid_t *hash_id);
12
void aclk_start_alert_streaming(char *node_id, uint64_t cloud_version);
13
void aclk_alert_version_check(char *node_id, char *claim_id, uint64_t cloud_version);
14
src/database/sqlite/sqlite_metadata.c
+3
@@ -104,6 +104,8 @@ const char *database_config[] = {
104
"CREATE TABLE IF NOT EXISTS aclk_queue (sequence_id INTEGER PRIMARY KEY, host_id blob, health_log_id INT, "
105
"unique_id INT, date_created INT, UNIQUE(host_id, health_log_id))",
106
107
+ "CREATE TABLE IF NOT EXISTS alert_hash_cloud (hash_id BLOB PRIMARY KEY)",
108
+
109
"CREATE TABLE IF NOT EXISTS ctx_metadata_cleanup (id INTEGER PRIMARY KEY, host_id BLOB, context TEXT NOT NULL, date_created INT NOT NULL, "
110
"UNIQUE (host_id, context))",
111
@@ -124,6 +126,7 @@ const char *database_cleanup[] = {
126
"DROP INDEX IF EXISTS health_log_d_ind_4",
127
"DROP INDEX IF EXISTS health_log_d_ind_1",
128
"DROP INDEX IF EXISTS health_log_d_ind_5",
129
+ "DELETE FROM alert_hash_cloud WHERE hash_id NOT IN (SELECT hash_id FROM alert_hash)",
130
NULL
131
};
132
src/health/health.c
+1
@@ -24,6 +24,7 @@ struct health_plugin_globals health_globals = {
24
},
25
.prototypes = {
26
.dict = NULL,
27
+ .registering = false,
28
}
29
};
30
src/health/health_internals.h
+1
@@ -92,6 +92,7 @@ struct health_plugin_globals {
92
93
struct {
94
DICTIONARY *dict;
95
+ bool registering;
96
} prototypes;
97
};
98
src/health/health_prototypes.c
+11
@@ -2,6 +2,7 @@
2
3
#include "health_internals.h"
4
#include "health-alert-entry.h"
5
+#include "database/sqlite/sqlite_aclk_alert.h"
6
7
// ---------------------------------------------------------------------------------------------------------------------
8
@@ -388,6 +389,14 @@ void health_prototype_hash_id(RRD_ALERT_PROTOTYPE *ap) {
389
uuid_copy(ap->config.hash_id, uuid.uuid);
390
391
sql_alert_store_config(ap);
392
+
393
+ if (ap->config.source_type == DYNCFG_SOURCE_TYPE_DYNCFG &&
394
+ !__atomic_load_n(&health_globals.prototypes.registering, __ATOMIC_RELAXED) &&
395
+ !alert_hash_has_transitioned(&ap->config.hash_id)) {
396
+ char hash_id_str[UUID_STR_LEN];
397
+ uuid_unparse_lower(ap->config.hash_id, hash_id_str);
398
+ aclk_send_alert_configuration(hash_id_str);
399
+ }
400
}
401
402
bool health_prototype_add(RRD_ALERT_PROTOTYPE *ap, char **msg) {
@@ -490,7 +499,9 @@ void health_reload_prototypes(void) {
499
NULL, 0);
500
501
// register all loaded prototypes
502
+ __atomic_store_n(&health_globals.prototypes.registering, true, __ATOMIC_RELAXED);
503
health_dyncfg_register_all_prototypes();
504
+ __atomic_store_n(&health_globals.prototypes.registering, false, __ATOMIC_RELAXED);
505
}
506
507
// ---------------------------------------------------------------------------------------------------------------------