1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "libnetdata/libnetdata.h"
4
+#include "agent_cloud_link.h"
5
+
6
+// Read from the config file -- new section [agent_cloud_link]
7
+// Defaults are supplied
8
+int aclk_recv_maximum = 0; // default 20
9
+int aclk_send_maximum = 0; // default 20
10
+
11
+int aclk_port = 0; // default 1883
12
+char *aclk_hostname = NULL; //default localhost
13
+int aclk_subscribed = 0;
14
+
15
+int aclk_metadata_submitted = 0;
16
+int waiting_init = 1;
17
+int cmdpause = 0; // Used to pause query processing
18
+
19
+BUFFER *aclk_buffer = NULL;
20
+char *global_base_topic = NULL;
21
+
22
+int cloud_to_agent_parse(JSON_ENTRY *e)
23
+{
24
+ struct aclk_request *data = e->callback_data;
25
+
26
+ switch(e->type) {
27
+ case JSON_OBJECT:
28
+ e->callback_function = cloud_to_agent_parse;
29
+ break;
30
+ case JSON_ARRAY:
31
+ e->callback_function = cloud_to_agent_parse;
32
+ break;
33
+ case JSON_STRING:
34
+ if (!strcmp(e->name, ACLK_JSON_IN_MSGID)) {
35
+ data->msg_id = strdupz(e->data.string);
36
+ break;
37
+ }
38
+ if (!strcmp(e->name, ACLK_JSON_IN_TYPE)) {
39
+ data->type_id = strdupz(e->data.string);
40
+ break;
41
+ }
42
+ if (!strcmp(e->name, ACLK_JSON_IN_TOPIC)) {
43
+ data->topic = strdupz(e->data.string);
44
+ break;
45
+ }
46
+ if (!strcmp(e->name, ACLK_JSON_IN_URL)) {
47
+ data->url = strdupz(e->data.string);
48
+ break;
49
+ }
50
+ break;
51
+ case JSON_NUMBER:
52
+ if (!strcmp(e->name, ACLK_JSON_IN_VERSION)) {
53
+ data->version = atol(e->data.string);
54
+ break;
55
+ }
56
+ break;
57
+
58
+ case JSON_BOOLEAN:
59
+ break;
60
+
61
+ case JSON_NULL:
62
+ break;
63
+ }
64
+ return 0;
65
+}
66
+
67
+//char *send_http_request(char *host, char *port, char *url, BUFFER *b)
68
+//{
69
+// struct timeval timeout = { .tv_sec = 30, .tv_usec = 0 };
70
+//
71
+// buffer_flush(b);
72
+// buffer_sprintf(
73
+// b,
74
+// "GET %s HTTP/1.1\r\nHost: %s\r\nAccept: plain/text\r\nAccept-Language: en-us\r\nUser-Agent: Netdata/rocks\r\n\r\n",
75
+// url, host);
76
+// int sock = connect_to_this_ip46(IPPROTO_TCP, SOCK_STREAM, host, 0, "443", &timeout);
77
+//
78
+// if (unlikely(sock == -1)) {
79
+// error("Handshake failed");
80
+// return NULL;
81
+// }
82
+//
83
+// SSL_CTX *ctx = security_initialize_openssl_client();
84
+// // Certificate chain: not updating the stores - do we need private CA roots?
85
+// // Calls to SSL_CTX_load_verify_locations would go here.
86
+// SSL *ssl = SSL_new(ctx);
87
+// SSL_set_fd(ssl, sock);
88
+// int err = SSL_connect(ssl);
89
+// SSL_write(ssl, b->buffer, b->len); // Timeout options?
90
+// int bytes_read = SSL_read(ssl, b->buffer, b->len);
91
+// SSL_shutdown(ssl);
92
+// close(sock);
93
+//}
94
+
95
+// Set when we have connection up and running from the connection callback
96
+int aclk_connection_initialized = 0;
97
+
98
+static netdata_mutex_t aclk_mutex = NETDATA_MUTEX_INITIALIZER;
99
+static netdata_mutex_t query_mutex = NETDATA_MUTEX_INITIALIZER;
100
+
101
+#define ACLK_LOCK netdata_mutex_lock(&aclk_mutex)
102
+#define ACLK_UNLOCK netdata_mutex_unlock(&aclk_mutex)
103
+
104
+#define QUERY_LOCK netdata_mutex_lock(&query_mutex)
105
+#define QUERY_UNLOCK netdata_mutex_unlock(&query_mutex)
106
+
107
+pthread_cond_t query_cond_wait = PTHREAD_COND_INITIALIZER;
108
+pthread_mutex_t query_lock_wait = PTHREAD_MUTEX_INITIALIZER;
109
+
110
+#define QUERY_THREAD_LOCK pthread_mutex_lock(&query_lock_wait);
111
+#define QUERY_THREAD_UNLOCK pthread_mutex_unlock(&query_lock_wait)
112
+#define QUERY_THREAD_WAKEUP pthread_cond_signal(&query_cond_wait)
113
+
114
+
115
+struct aclk_query {
116
+ time_t created;
117
+ time_t run_after; // Delay run until after this time
118
+ char *topic; // Topic to respond to
119
+ char *data; // Internal data (NULL if request from the cloud)
120
+ char *msg_id; // msg_id generated by the cloud (NULL if internal)
121
+ char *query; // The actual query
122
+ u_char deleted; // Mark deleted for garbage collect
123
+ struct aclk_query *next;
124
+};
125
+
126
+struct aclk_query_queue {
127
+ struct aclk_query *aclk_query_head;
128
+ struct aclk_query *aclk_query_tail;
129
+ u_int64_t count;
130
+} aclk_queue = { .aclk_query_head = NULL, .aclk_query_tail = NULL, .count = 0 };
131
+
132
+/*
133
+ * Free a query structure when done
134
+ */
135
+
136
+void aclk_query_free(struct aclk_query *this_query)
137
+{
138
+ if (unlikely(!this_query))
139
+ return;
140
+
141
+ freez(this_query->topic);
142
+ freez(this_query->query);
143
+ if (this_query->data)
144
+ freez(this_query->data);
145
+ if (this_query->msg_id)
146
+ freez(this_query->msg_id);
147
+ freez(this_query);
148
+ return;
149
+}
150
+
151
+// Returns the entry after which we need to create a new entry to run at the specified time
152
+// If NULL is returned we need to add to HEAD
153
+// Called with locked entries
154
+
155
+struct aclk_query *aclk_query_find_position(time_t time_to_run)
156
+{
157
+ struct aclk_query *tmp_query, *last_query;
158
+
159
+ last_query = NULL;
160
+ tmp_query = aclk_queue.aclk_query_head;
161
+
162
+ while (tmp_query) {
163
+ if (tmp_query->run_after > time_to_run)
164
+ return last_query;
165
+ last_query = tmp_query;
166
+ tmp_query = tmp_query->next;
167
+ }
168
+ return last_query;
169
+}
170
+
171
+// Need to have a lock before calling this
172
+struct aclk_query *aclk_query_find(char *topic, char *data, char *msg_id, char *query)
173
+{
174
+ struct aclk_query *tmp_query;
175
+
176
+ tmp_query = aclk_queue.aclk_query_head;
177
+
178
+ while (tmp_query) {
179
+ if (likely(!tmp_query->deleted)) {
180
+ if (strcmp(tmp_query->topic, topic) == 0 && (strcmp(tmp_query->query, query) == 0)) {
181
+ if ((!data || (data && strcmp(data, tmp_query->data) == 0)) &&
182
+ (!msg_id || (msg_id && strcmp(msg_id, tmp_query->msg_id) == 0)))
183
+ return tmp_query;
184
+ }
185
+ }
186
+ tmp_query = tmp_query->next;
187
+ }
188
+ return NULL;
189
+}
190
+
191
+/*
192
+ * Add a query to execute, the result will be send to the specified topic
193
+ */
194
+
195
+int aclk_queue_query(char *topic, char *data, char *msg_id, char *query, int run_after, int internal)
196
+{
197
+ struct aclk_query *new_query, *tmp_query;
198
+
199
+ // Ignore all commands while we wait for the agent to initialize
200
+ if (unlikely(waiting_init))
201
+ return 0;
202
+
203
+ run_after = now_realtime_sec() + run_after;
204
+
205
+ QUERY_LOCK;
206
+ tmp_query = aclk_query_find(topic, data, msg_id, query);
207
+ if (unlikely(tmp_query)) {
208
+ if (tmp_query->run_after == run_after) {
209
+ QUERY_UNLOCK;
210
+ QUERY_THREAD_WAKEUP;
211
+ return 0;
212
+ }
213
+ tmp_query->deleted = 1;
214
+ }
215
+
216
+ new_query = callocz(1, sizeof(struct aclk_query));
217
+ if (internal) {
218
+ new_query->topic = strdupz(topic);
219
+ new_query->query = strdupz(query);
220
+ } else {
221
+ new_query->topic = topic;
222
+ new_query->query = query;
223
+ new_query->msg_id = msg_id;
224
+ }
225
+
226
+ if (data)
227
+ new_query->data = strdupz(data);
228
+
229
+ new_query->next = NULL;
230
+ new_query->created = now_realtime_sec();
231
+ new_query->run_after = run_after;
232
+
233
+ info("Added query (%s) (%s)", topic, query);
234
+
235
+ tmp_query = aclk_query_find_position(run_after);
236
+
237
+ if (tmp_query) {
238
+ new_query->next = tmp_query->next;
239
+ tmp_query->next = new_query;
240
+ if (tmp_query == aclk_queue.aclk_query_tail)
241
+ aclk_queue.aclk_query_tail = new_query;
242
+ aclk_queue.count++;
243
+ QUERY_UNLOCK;
244
+ QUERY_THREAD_WAKEUP;
245
+ return 0;
246
+ }
247
+
248
+ new_query->next = aclk_queue.aclk_query_head;
249
+ aclk_queue.aclk_query_head = new_query;
250
+ aclk_queue.count++;
251
+
252
+ QUERY_UNLOCK;
253
+ QUERY_THREAD_WAKEUP;
254
+ return 0;
255
+
256
+// if (likely(aclk_queue.aclk_query_tail)) {
257
+// aclk_queue.aclk_query_tail->next = new_query;
258
+// aclk_queue.aclk_query_tail = new_query;
259
+// aclk_queue.count++;
260
+// QUERY_UNLOCK;
261
+// return 0;
262
+// }
263
+//
264
+// if (likely(!aclk_queue.aclk_query_head)) {
265
+// aclk_queue.aclk_query_head = new_query;
266
+// aclk_queue.aclk_query_tail = new_query;
267
+// aclk_queue.count++;
268
+// QUERY_UNLOCK;
269
+// return 0;
270
+// }
271
+// QUERY_UNLOCK;
272
+// return 0;
273
+}
274
+
275
+inline int aclk_submit_request(struct aclk_request *request)
276
+{
277
+ return aclk_queue_query(request->topic, NULL, request->msg_id, request->url, 0, 0);
278
+}
279
+
280
+/*
281
+ * Get the next query to process - NULL if nothing there
282
+ * The caller needs to free memory by calling aclk_query_free()
283
+ *
284
+ * topic
285
+ * query
286
+ * The structure itself
287
+ *
288
+ */
289
+struct aclk_query *aclk_queue_pop()
290
+{
291
+ struct aclk_query *this_query;
292
+
293
+ QUERY_LOCK;
294
+
295
+ if (likely(!aclk_queue.aclk_query_head)) {
296
+ QUERY_UNLOCK;
297
+ return NULL;
298
+ }
299
+
300
+ this_query = aclk_queue.aclk_query_head;
301
+
302
+ if (this_query->run_after > now_realtime_sec()) {
303
+ info("Query %s will run in %ld seconds", this_query->query, this_query->run_after - now_realtime_sec());
304
+ QUERY_UNLOCK;
305
+ return NULL;
306
+ }
307
+
308
+ aclk_queue.count--;
309
+ aclk_queue.aclk_query_head = aclk_queue.aclk_query_head->next;
310
+
311
+ if (likely(!aclk_queue.aclk_query_head)) {
312
+ aclk_queue.aclk_query_tail = NULL;
313
+ }
314
+
315
+ QUERY_UNLOCK;
316
+ return this_query;
317
+}
318
+
319
+// This will give the base topic that the agent will publish messages.
320
+// subtopics will be sent under the base topic e.g. base_topic/subtopic
321
+// This is called by aclk_init(), to compute the base topic once and have
322
+// it stored internally.
323
+// Need to check if additional logic should be added to make sure that there
324
+// is enough information to determine the base topic at init time
325
+
326
+// TODO: Locking may be needed, depends on the calculation of the base topic and also if we need to switch
327
+// that on the fly
328
+
329
+char *get_publish_base_topic(PUBLISH_TOPIC_ACTION action)
330
+{
331
+ static char *topic = NULL;
332
+
333
+ if (unlikely(!is_agent_claimed()))
334
+ return NULL;
335
+
336
+ ACLK_LOCK;
337
+
338
+ if (unlikely(action == PUBLICH_TOPIC_FREE)) {
339
+ if (likely(topic)) {
340
+ freez(topic);
341
+ topic = NULL;
342
+ }
343
+
344
+ ACLK_UNLOCK;
345
+
346
+ return NULL;
347
+ }
348
+
349
+ if (unlikely(action == PUBLICH_TOPIC_REBUILD)) {
350
+ ACLK_UNLOCK;
351
+ get_publish_base_topic(PUBLICH_TOPIC_FREE);
352
+ return get_publish_base_topic(PUBLICH_TOPIC_GET);
353
+ }
354
+
355
+ if (unlikely(!topic)) {
356
+ char tmp_topic[ACLK_MAX_TOPIC + 1];
357
+
358
+ sprintf(tmp_topic, ACLK_TOPIC_STRUCTURE, is_agent_claimed());
359
+ topic = strdupz(tmp_topic);
360
+ }
361
+
362
+ ACLK_UNLOCK;
363
+ return topic;
364
+}
365
+
366
+char *get_topic(char *sub_topic, char *final_topic, int max_size)
367
+{
368
+ if (unlikely(!global_base_topic))
369
+ global_base_topic = GET_PUBLISH_BASE_TOPIC;
370
+
371
+ if (unlikely(!global_base_topic))
372
+ return sub_topic;
373
+
374
+ snprintfz(final_topic, max_size, "%s/%s", global_base_topic, sub_topic);
375
+
376
+ return final_topic;
377
+}
378
+
379
+// Wait for ACLK connection to be established
380
+int aclk_wait_for_initialization()
381
+{
382
+ if (unlikely(!aclk_connection_initialized)) {
383
+ time_t now = now_realtime_sec();
384
+
385
+ while (!aclk_connection_initialized && (now_realtime_sec() - now) < ACLK_INITIALIZATION_WAIT) {
386
+ sleep_usec(USEC_PER_SEC * ACLK_INITIALIZATION_SLEEP_WAIT);
387
+ _link_event_loop(0);
388
+ }
389
+
390
+ if (unlikely(!aclk_connection_initialized)) {
391
+ error("ACLK connection cannot be established");
392
+ return 1;
393
+ }
394
+ }
395
+ return 0;
396
+}
397
+
398
+/*
399
+ * This function will fetch the next pending command and process it
400
+ *
401
+ */
402
+int aclk_process_query()
403
+{
404
+ struct aclk_query *this_query;
405
+ static u_int64_t query_count = 0;
406
+ //int rc;
407
+
408
+ if (unlikely(cmdpause))
409
+ return 0;
410
+
411
+ if (!aclk_connection_initialized)
412
+ return 0;
413
+
414
+ this_query = aclk_queue_pop();
415
+ if (likely(!this_query)) {
416
+ //info("No pending queries");
417
+ return 0;
418
+ }
419
+
420
+ if (unlikely(this_query->deleted)) {
421
+ info("Garbage collect query %s:%s", this_query->topic, this_query->query);
422
+ aclk_query_free(this_query);
423
+ return 1;
424
+ }
425
+
426
+ query_count++;
427
+ info(
428
+ "Query #%d (%s) (%s) in queue %d seconds", (int) query_count, this_query->topic, this_query->query,
429
+ (int) (now_realtime_sec() - this_query->created));
430
+
431
+ if (strncmp((char *)this_query->query, "/api/v1/", 8) == 0) {
432
+ struct web_client *w = (struct web_client *)callocz(1, sizeof(struct web_client));
433
+ w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
434
+ strcpy(w->origin, "*"); // Simulate web_client_create_on_fd()
435
+ w->cookie1[0] = 0; // Simulate web_client_create_on_fd()
436
+ w->cookie2[0] = 0; // Simulate web_client_create_on_fd()
437
+ w->acl = 0x1f;
438
+
439
+ char *mysep = strchr(this_query->query, '?');
440
+ if (mysep) {
441
+ strncpyz(w->decoded_query_string, mysep, NETDATA_WEB_REQUEST_URL_SIZE);
442
+ *mysep = '\0';
443
+ } else
444
+ strncpyz(w->decoded_query_string, this_query->query, NETDATA_WEB_REQUEST_URL_SIZE);
445
+
446
+ mysep = strrchr(this_query->query, '/');
447
+
448
+ // TODO: ignore return code for now
449
+ web_client_api_request_v1(localhost, w, mysep ? mysep + 1 : "noop");
450
+
451
+ //TODO: handle bad response perhaps in a different way. For now it does to the payload
452
+ //if (rc == HTTP_RESP_OK || 1) {
453
+ buffer_flush(aclk_buffer);
454
+
455
+ aclk_create_metadata_message(aclk_buffer, mysep ? mysep + 1 : "noop", this_query->msg_id, w->response.data);
456
+ aclk_buffer->contenttype = CT_APPLICATION_JSON;
457
+ aclk_send_message(this_query->topic, aclk_buffer->buffer);
458
+ //} else
459
+ // error("Query RESP: %s", w->response.data->buffer);
460
+
461
+ buffer_free(w->response.data);
462
+ freez(w);
463
+ aclk_query_free(this_query);
464
+ return 1;
465
+ }
466
+
467
+ if (strcmp((char *)this_query->topic, "_chart") == 0) {
468
+ aclk_send_single_chart(this_query->data, this_query->query);
469
+ }
470
+
471
+ aclk_query_free(this_query);
472
+
473
+ return 1;
474
+}
475
+
476
+// Launch a query processing thread
477
+
478
+/*
479
+ * Process all pending queries
480
+ * Return 0 if no queries were processed, 1 otherwise
481
+ *
482
+ */
483
+
484
+int aclk_process_queries()
485
+{
486
+ if (unlikely(cmdpause))
487
+ return 0;
488
+
489
+ // Return if no queries pending
490
+ if (likely(!aclk_queue.count))
491
+ return 0;
492
+
493
+ info("Processing %d queries", (int ) aclk_queue.count);
494
+
495
+ while (aclk_process_query()) {
496
+ //rc = _link_event_loop(0);
497
+ };
498
+
499
+ return 1;
500
+}
501
+
502
+static void aclk_query_thread_cleanup(void *ptr)
503
+{
504
+ struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
505
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
506
+
507
+ info("cleaning up...");
508
+
509
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
510
+}
511
+
512
+/**
513
+ * MAin query processing thread
514
+ *
515
+ */
516
+void *aclk_query_main_thread(void *ptr)
517
+{
518
+ netdata_thread_cleanup_push(aclk_query_thread_cleanup, ptr);
519
+
520
+ while (!netdata_exit) {
521
+
522
+ QUERY_THREAD_LOCK;
523
+
524
+ if (unlikely(!aclk_metadata_submitted)) {
525
+ aclk_send_metadata();
526
+ aclk_metadata_submitted = 1;
527
+ }
528
+
529
+ if (unlikely(pthread_cond_wait(&query_cond_wait, &query_lock_wait)))
530
+ sleep_usec(USEC_PER_SEC * 1);
531
+
532
+ if (likely(aclk_connection_initialized && !netdata_exit)) {
533
+ while (aclk_process_queries()) {
534
+ // Sleep for a few ms and retry maybe we have something to process
535
+ // before going to sleep
536
+ // TODO: This needs improvement to avoid missed queries
537
+ sleep_usec(USEC_PER_MS * 100);
538
+ }
539
+ }
540
+
541
+ QUERY_THREAD_UNLOCK;
542
+
543
+ } // forever
544
+ info("Shutting down query processing thread");
545
+ netdata_thread_cleanup_pop(1);
546
+ return NULL;
547
+}
548
+
549
+// Thread cleanup
550
+static void aclk_main_cleanup(void *ptr)
551
+{
552
+ struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
553
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
554
+
555
+ info("cleaning up...");
556
+
557
+ QUERY_THREAD_WAKEUP;
558
+
559
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
560
+}
561
+
562
+/**
563
+ * Main agent cloud link thread
564
+ *
565
+ * This thread will simply call the main event loop that handles
566
+ * pending requests - both inbound and outbound
567
+ *
568
+ * @param ptr is a pointer to the netdata_static_thread structure.
569
+ *
570
+ * @return It always returns NULL
571
+ */
572
+void *aclk_main(void *ptr)
573
+{
574
+ //netdata_thread_t *query_thread;
575
+ struct netdata_static_thread query_thread;
576
+
577
+ memset(&query_thread, 0, sizeof(query_thread));
578
+
579
+ netdata_thread_cleanup_push(aclk_main_cleanup, ptr);
580
+
581
+ if (unlikely(!aclk_buffer))
582
+ aclk_buffer = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
583
+
584
+ assert(aclk_buffer != NULL);
585
+
586
+ //netdata_thread_cleanup_push(aclk_query_thread_cleanup, ptr);
587
+ //netdata_thread_create(&query_thread.thread , "ACLKQ", NETDATA_THREAD_OPTION_DEFAULT, aclk_query_main_thread, &query_thread);
588
+ info("Waiting for netdata to be ready");
589
+ while (!netdata_ready) {
590
+ sleep_usec(USEC_PER_MS * 300);
591
+ }
592
+ info("Waiting %d seconds for the agent to initialize", ACLK_STARTUP_WAIT);
593
+ sleep_usec(USEC_PER_SEC * ACLK_STARTUP_WAIT);
594
+
595
+ // Ok mark we are ready to accept incoming requests
596
+ waiting_init = 0;
597
+
598
+ while (!netdata_exit) {
599
+ // TODO: This may change when we have enough info from the claiming itself to avoid wasting 60 seconds
600
+ // TODO: Handle the unclaim command as well -- we may need to shutdown the connection
601
+ if (likely(!is_agent_claimed())) {
602
+ sleep_usec(USEC_PER_SEC * 60);
603
+ info("Checking agent claiming status");
604
+ continue;
605
+ }
606
+
607
+ if (unlikely(!aclk_connection_initialized)) {
608
+ static int initializing = 0;
609
+
610
+ if (likely(initializing)) {
611
+ _link_event_loop(ACLK_LOOP_TIMEOUT * 1000);
612
+ continue;
613
+ }
614
+ initializing = 1;
615
+ info("Initializing connection");
616
+ //send_http_request(aclk_hostname, "443", "/auth/challenge?id=blah", aclk_buffer);
617
+ if (unlikely(aclk_init(ACLK_INIT))) {
618
+ // TODO: TBD how to handle. We are claimed and we cant init the connection. For now keep trying.
619
+ sleep_usec(USEC_PER_SEC * 60);
620
+ continue;
621
+ } else {
622
+ sleep_usec(USEC_PER_SEC * 1);
623
+ }
624
+ _link_event_loop(ACLK_LOOP_TIMEOUT * 1000);
625
+ continue;
626
+ }
627
+
628
+ if (unlikely(!aclk_subscribed)) {
629
+ aclk_subscribed = !aclk_subscribe(ACLK_COMMAND_TOPIC, 2);
630
+ }
631
+ if (unlikely(!query_thread.thread)) {
632
+ query_thread.thread = mallocz(sizeof(netdata_thread_t));
633
+ netdata_thread_create(
634
+ query_thread.thread, "ACLKQ", NETDATA_THREAD_OPTION_DEFAULT, aclk_query_main_thread, &query_thread);
635
+ }
636
+
637
+ //TODO: Check if there is a return code
638
+ _link_event_loop(ACLK_LOOP_TIMEOUT * 1000);
639
+
640
+ } // forever
641
+ aclk_shutdown();
642
+
643
+ netdata_thread_cleanup_pop(1);
644
+ return NULL;
645
+}
646
+
647
+/*
648
+ * Send a message to the cloud, using a base topic and sib_topic
649
+ * The final topic will be in the form <base_topic>/<sub_topic>
650
+ * If base_topic is missing then the global_base_topic will be used (if available)
651
+ *
652
+ */
653
+int aclk_send_message(char *sub_topic, char *message)
654
+{
655
+ int rc;
656
+ static int skip_due_to_shutdown = 0;
657
+ char topic[ACLK_MAX_TOPIC + 1];
658
+ char *final_topic;
659
+
660
+ if (!aclk_connection_initialized)
661
+ return 0;
662
+
663
+ if (unlikely(netdata_exit)) {
664
+ if (unlikely(!aclk_connection_initialized))
665
+ return 1;
666
+
667
+ ++skip_due_to_shutdown;
668
+ if (unlikely(!(skip_due_to_shutdown % 100)))
669
+ info("%d messages not sent -- shutdown in progress", skip_due_to_shutdown);
670
+ return 1;
671
+ }
672
+
673
+ if (unlikely(!message))
674
+ return 0;
675
+
676
+ if (unlikely(aclk_wait_for_initialization()))
677
+ return 1;
678
+
679
+ final_topic = get_topic(sub_topic, topic, ACLK_MAX_TOPIC);
680
+
681
+ ACLK_LOCK;
682
+ rc = _link_send_message(final_topic, message);
683
+ ACLK_UNLOCK;
684
+
685
+ // TODO: Add better handling -- error will flood the logfile here
686
+ if (unlikely(rc))
687
+ error("Failed to send message, error code %d (%s)", rc, _link_strerror(rc));
688
+
689
+ return rc;
690
+}
691
+
692
+/*
693
+ * Subscribe to a topic in the cloud
694
+ * The final subscription will be in the form
695
+ * /agent/claim_id/<sub_topic>
696
+ */
697
+int aclk_subscribe(char *sub_topic, int qos)
698
+{
699
+ int rc;
700
+ //static char *global_base_topic = NULL;
701
+ char topic[ACLK_MAX_TOPIC + 1];
702
+ char *final_topic;
703
+
704
+ if (!aclk_connection_initialized)
705
+ return 0;
706
+
707
+ if (unlikely(netdata_exit)) {
708
+ return 1;
709
+ }
710
+
711
+ if (unlikely(aclk_wait_for_initialization()))
712
+ return 1;
713
+
714
+ final_topic = get_topic(sub_topic, topic, ACLK_MAX_TOPIC);
715
+
716
+ ACLK_LOCK;
717
+ rc = _link_subscribe(final_topic, qos);
718
+ ACLK_UNLOCK;
719
+
720
+ // TODO: Add better handling -- error will flood the logfile here
721
+ if (unlikely(rc))
722
+ error("Failed to send message, error code %d (%s)", rc, _link_strerror(rc));
723
+
724
+ return rc;
725
+}
726
+
727
+// This is called from a callback when the link goes up
728
+void aclk_connect(void *ptr)
729
+{
730
+ (void) ptr;
731
+ info("Connection detected");
732
+ return;
733
+}
734
+
735
+// This is called from a callback when the link goes down
736
+void aclk_disconnect(void *ptr)
737
+{
738
+ (void) ptr;
739
+ info("Disconnect detected");
740
+ aclk_subscribed = 0;
741
+ aclk_metadata_submitted = 0;
742
+}
743
+
744
+void aclk_shutdown()
745
+{
746
+ info("Shutdown initiated");
747
+ aclk_connection_initialized = 0;
748
+ _link_shutdown();
749
+ info("Shutdown complete");
750
+}
751
+
752
+int aclk_init(ACLK_INIT_ACTION action)
753
+{
754
+ (void) action;
755
+
756
+ static int init = 0;
757
+ int rc;
758
+
759
+ if (likely(init))
760
+ return 0;
761
+
762
+ aclk_send_maximum = config_get_number(CONFIG_SECTION_ACLK, "agent cloud link send maximum", 20);
763
+ aclk_recv_maximum = config_get_number(CONFIG_SECTION_ACLK, "agent cloud link receive maximum", 20);
764
+
765
+ aclk_hostname = config_get(CONFIG_SECTION_ACLK, "agent cloud link hostname", "localhost");
766
+ aclk_port = config_get_number(CONFIG_SECTION_ACLK, "agent cloud link port", 1883);
767
+
768
+ info("Maximum parallel outgoing messages %d", aclk_send_maximum);
769
+ info("Maximum parallel incoming messages %d", aclk_recv_maximum);
770
+
771
+ // This will setup the base publish topic internally
772
+ //get_publish_base_topic(PUBLICH_TOPIC_GET);
773
+
774
+ // initialize the low level link to the cloud
775
+ rc = _link_lib_init(aclk_hostname, aclk_port, aclk_connect, aclk_disconnect);
776
+ if (unlikely(rc)) {
777
+ error("Failed to initialize the agent cloud link library");
778
+ return 1;
779
+ }
780
+ global_base_topic = GET_PUBLISH_BASE_TOPIC;
781
+ init = 1;
782
+
783
+ return 0;
784
+}
785
+
786
+// Use this to disable encoding of quotes and newlines so that
787
+// MQTT subscriber can display more readable data on screen
788
+
789
+void aclk_create_header(BUFFER *dest, char *type, char *msg_id)
790
+{
791
+ uuid_t uuid;
792
+ char uuid_str[36 + 1];
793
+
794
+ if (unlikely(!msg_id)) {
795
+ uuid_generate(uuid);
796
+ uuid_unparse(uuid, uuid_str);
797
+ msg_id = uuid_str;
798
+ }
799
+
800
+ buffer_sprintf(
801
+ dest,
802
+ "\t{\"type\": \"%s\",\n"
803
+ "\t\"msg-id\": \"%s\",\n"
804
+ "\t\"version\": %s,\n"
805
+ "\t\"payload\": ",
806
+ type, msg_id, ACLK_VERSION);
807
+}
808
+
809
+#define EYE_FRIENDLY 1
810
+
811
+// encapsulate contents into metadata message as per ACLK documentation
812
+void aclk_create_metadata_message(BUFFER *dest, char *type, char *msg_id, BUFFER *contents)
813
+{
814
+#ifndef EYE_FRIENDLY
815
+ char *tmp_buffer = mallocz(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
816
+ char *src, *dst;
817
+#endif
818
+
819
+ buffer_sprintf(
820
+ dest,
821
+ "\t{\"type\": \"%s\",\n"
822
+ "\t\"msg-id\": \"%s\",\n"
823
+ "\t\"payload\": %s\n\t}",
824
+ type, msg_id ? msg_id : "", contents->buffer);
825
+
826
+#ifndef EYE_FRIENDLY
827
+ //TODO: this is the initial escaping, It will expanded
828
+ src = dest->buffer;
829
+ dst = tmp_buffer;
830
+ while (*src) {
831
+ switch (*src) {
832
+ case '0x0a':
833
+ case '\n':
834
+ *dst++ = '\\';
835
+ *dst++ = 'n';
836
+ break;
837
+ case '\"':
838
+ *dst++ = '\\';
839
+ *dst++ = '\"';
840
+ break;
841
+ case '\'':
842
+ *dst++ = '\\';
843
+ *dst++ = '\"';
844
+ break;
845
+ default:
846
+ *dst++ = *src;
847
+ }
848
+ src++;
849
+ }
850
+ *dst = '\0';
851
+
852
+ buffer_flush(dest);
853
+ buffer_sprintf(dest, "%s", tmp_buffer);
854
+
855
+ freez(tmp_buffer);
856
+#endif
857
+ return;
858
+}
859
+
860
+//TODO: this has been changed in the latest specs. We need to pack the data in one MQTT
861
+//message with a payload and has a list of json objects
862
+int aclk_send_alarm_metadata()
863
+{
864
+ //TODO: improve locking on the buffer -- same lock is used for the message send
865
+ //improve error handling
866
+ ACLK_LOCK;
867
+ buffer_flush(aclk_buffer);
868
+ // Alarms configuration
869
+ aclk_create_header(aclk_buffer, "alarms", NULL);
870
+ health_alarms2json(localhost, aclk_buffer, 1);
871
+ buffer_sprintf(aclk_buffer,"\n}");
872
+ ACLK_UNLOCK;
873
+ aclk_send_message(ACLK_ALARMS_TOPIC, aclk_buffer->buffer);
874
+
875
+ // Alarms log
876
+ ACLK_LOCK;
877
+ buffer_flush(aclk_buffer);
878
+ aclk_create_header(aclk_buffer, "alarms_log", NULL);
879
+ health_alarm_log2json(localhost, aclk_buffer, 0);
880
+ buffer_sprintf(aclk_buffer,"\n}");
881
+ ACLK_UNLOCK;
882
+ aclk_send_message(ACLK_ALARMS_TOPIC, aclk_buffer->buffer);
883
+
884
+ return 0;
885
+}
886
+
887
+
888
+// Send info metadata message to the cloud if the link is established
889
+// or on request
890
+int aclk_send_metadata()
891
+{
892
+ ACLK_LOCK;
893
+
894
+ buffer_flush(aclk_buffer);
895
+
896
+ aclk_create_header(aclk_buffer, "connect", NULL);
897
+ buffer_sprintf(aclk_buffer,"{\n\t \"info\" : ");
898
+ web_client_api_request_v1_info_fill_buffer(localhost, aclk_buffer);
899
+ buffer_sprintf(aclk_buffer,", \n\t \"charts\" : ");
900
+ charts2json(localhost, aclk_buffer);
901
+ buffer_sprintf(aclk_buffer,"\n}\n}");
902
+ aclk_buffer->contenttype = CT_APPLICATION_JSON;
903
+
904
+ ACLK_UNLOCK;
905
+
906
+ aclk_send_message(ACLK_METADATA_TOPIC, aclk_buffer->buffer);
907
+
908
+ aclk_send_alarm_metadata();
909
+
910
+ return 0;
911
+}
912
+
913
+//rrd_stats_api_v1_chart(RRDSET *st, BUFFER *buf)
914
+
915
+int aclk_send_single_chart(char *hostname, char *chart)
916
+{
917
+ RRDHOST *target_host;
918
+ ACLK_LOCK;
919
+
920
+ buffer_flush(aclk_buffer);
921
+
922
+ target_host = rrdhost_find_by_hostname(hostname, 0);
923
+ if (!target_host)
924
+ return 1;
925
+
926
+ RRDSET *st = rrdset_find(target_host, chart);
927
+
928
+ if (!st)
929
+ st = rrdset_find_byname(target_host, chart);
930
+
931
+ if (!st) {
932
+ info("FAILED to find chart %s", chart);
933
+ return 1;
934
+ }
935
+
936
+ aclk_buffer->contenttype = CT_APPLICATION_JSON;
937
+
938
+ buffer_flush(aclk_buffer);
939
+
940
+ aclk_create_header(aclk_buffer, "chart", NULL);
941
+
942
+ rrdset2json(st, aclk_buffer, NULL, NULL);
943
+ buffer_sprintf(aclk_buffer,"\n}\n}");
944
+
945
+
946
+ ACLK_UNLOCK;
947
+ aclk_send_message(ACLK_METADATA_TOPIC, aclk_buffer->buffer);
948
+ return 0;
949
+}
950
+
951
+int aclk_update_chart(RRDHOST *host, char *chart_name)
952
+{
953
+ (void) host;
954
+ (void) chart_name;
955
+#ifndef ENABLE_ACLK
956
+ return 0;
957
+#else
958
+ if (host != localhost)
959
+ return 0;
960
+
961
+ aclk_queue_query("_chart", host->hostname, NULL, chart_name, 2, 1);
962
+ return 0;
963
+#endif
964
+}
965
+
966
+int aclk_update_alarm(RRDHOST *host, char *alarm_name)
967
+{
968
+ if (host != localhost)
969
+ return 0;
970
+
971
+ aclk_queue_query("_alarm", host->hostname, NULL, alarm_name, 2, 1);
972
+ return 0;
973
+}
974
+
975
+
976
+//TODO: add and check the incoming type e.g http
977
+int aclk_handle_cloud_request(char *payload)
978
+{
979
+ struct aclk_request cloud_to_agent = { .msg_id = NULL, .topic = NULL, .url = NULL, .version = 1};
980
+
981
+ int rc = json_parse(payload, &cloud_to_agent, cloud_to_agent_parse);
982
+
983
+ if (unlikely(JSON_OK != rc)) {
984
+ error("Malformed json request (%s)", payload);
985
+ return 1;
986
+ }
987
+
988
+ if (unlikely(!cloud_to_agent.url || !cloud_to_agent.topic)) {
989
+ return 1;
990
+ }
991
+
992
+ aclk_submit_request(&cloud_to_agent);
993
+
994
+ return 0;
995
+}