master
c 466 lines 18.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "pluginsd_functions.h"
4
5 #define LOG_FUNCTIONS false
6
7 // ----------------------------------------------------------------------------
8 // execution of functions
9
10 static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void *func, void *parser_ptr) {
11 struct inflight_function *pf = func;
12
13 PARSER *parser = parser_ptr;
14
15 // leave this code as default, so that when the dictionary is destroyed this will be sent back to the caller
16 pf->code = HTTP_RESP_SERVICE_UNAVAILABLE;
17
18 const char *transaction = dictionary_acquired_item_name(item);
19
20 int rc = uuid_parse_flexi(transaction, pf->transaction);
21 if(rc != 0)
22 netdata_log_error("FUNCTION: '%s': cannot parse transaction UUID", string2str(pf->function));
23
24 CLEAN_BUFFER *buffer = buffer_create(1024, NULL);
25 if(pf->payload && buffer_strlen(pf->payload)) {
26 buffer_sprintf(
27 buffer,
28 PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN " %s %d \"%s\" \""HTTP_ACCESS_FORMAT"\" \"%s\" \"%s\"\n",
29 transaction,
30 pf->timeout_s,
31 string2str(pf->function),
32 (HTTP_ACCESS_FORMAT_CAST)pf->access,
33 pf->source ? pf->source : "",
34 content_type_id2string(pf->payload->content_type)
35 );
36
37 buffer_fast_strcat(buffer, buffer_tostring(pf->payload), buffer_strlen(pf->payload));
38 buffer_strcat(buffer, "\nFUNCTION_PAYLOAD_END\n");
39 }
40 else {
41 buffer_sprintf(
42 buffer,
43 PLUGINSD_CALL_FUNCTION " %s %d \"%s\" \""HTTP_ACCESS_FORMAT"\" \"%s\"\n",
44 transaction,
45 pf->timeout_s,
46 string2str(pf->function),
47 (HTTP_ACCESS_FORMAT_CAST)pf->access,
48 pf->source ? pf->source : ""
49 );
50 }
51
52 // send the command to the plugin
53 // IMPORTANT: make sure all commands are sent in 1 call, because in streaming they may interfere with others
54 ssize_t ret = send_to_plugin(buffer_tostring(buffer), parser, STREAM_TRAFFIC_TYPE_FUNCTIONS);
55 pf->sent_monotonic_ut = now_monotonic_usec();
56
57 if(ret < 0) {
58 pf->sent_successfully = false;
59
60 pf->code = HTTP_RESP_SERVICE_UNAVAILABLE;
61 netdata_log_error("FUNCTION '%s': failed to send it to the plugin, error %zd", string2str(pf->function), ret);
62 rrd_call_function_error(pf->result_body_wb, "Failed to send this request to the plugin that offered it.", pf->code);
63 }
64 else {
65 pf->sent_successfully = true;
66
67 internal_error(LOG_FUNCTIONS,
68 "FUNCTION '%s' with transaction '%s' sent to collector (%zd bytes, in %"PRIu64" usec)",
69 string2str(pf->function), dictionary_acquired_item_name(item), ret,
70 pf->sent_monotonic_ut - pf->started_monotonic_ut);
71 }
72 }
73
74 static bool inflight_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func __maybe_unused, void *new_func, void *parser_ptr __maybe_unused) {
75 struct inflight_function *pf = new_func;
76
77 netdata_log_error("PLUGINSD_PARSER: duplicate UUID on pending function '%s' detected. Ignoring the second one.", string2str(pf->function));
78 pf->code = rrd_call_function_error(pf->result_body_wb, "This transaction is already in progress.", HTTP_RESP_BAD_REQUEST);
79 pf->result.cb(pf->result_body_wb, pf->code, pf->result.data);
80 string_freez(pf->function);
81
82 return false;
83 }
84
85 static void inflight_functions_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func, void *parser_ptr) {
86 struct inflight_function *pf = func;
87 struct parser *parser = (struct parser *)parser_ptr; (void)parser;
88
89 internal_error(LOG_FUNCTIONS,
90 "FUNCTION '%s' result of transaction '%s' received from collector "
91 "(%zu bytes, request %"PRIu64" usec, response %"PRIu64" usec)",
92 string2str(pf->function), dictionary_acquired_item_name(item),
93 buffer_strlen(pf->result_body_wb),
94 pf->sent_monotonic_ut - pf->started_monotonic_ut, now_realtime_usec() - pf->sent_monotonic_ut);
95
96 if(pf->code == HTTP_RESP_SERVICE_UNAVAILABLE && !buffer_strlen(pf->result_body_wb))
97 rrd_call_function_error(pf->result_body_wb, "The plugin that was servicing this request, exited before responding.", pf->code);
98
99 pf->result.cb(pf->result_body_wb, pf->code, pf->result.data);
100
101 string_freez(pf->function);
102 buffer_free((void *)pf->payload);
103 freez((void *)pf->source);
104 }
105
106 void pluginsd_inflight_functions_init(PARSER *parser) {
107 parser->inflight.functions = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE, &dictionary_stats_category_functions, 0);
108 dictionary_register_insert_callback(parser->inflight.functions, inflight_functions_insert_callback, parser);
109 dictionary_register_delete_callback(parser->inflight.functions, inflight_functions_delete_callback, parser);
110 dictionary_register_conflict_callback(parser->inflight.functions, inflight_functions_conflict_callback, parser);
111 }
112
113 void pluginsd_inflight_functions_cleanup(PARSER *parser) {
114 dictionary_destroy(parser->inflight.functions);
115 }
116
117 // ----------------------------------------------------------------------------
118
119 void pluginsd_inflight_functions_garbage_collect(PARSER *parser, usec_t now_ut) {
120 parser->inflight.smaller_monotonic_timeout_ut = 0;
121 struct inflight_function *pf;
122 dfe_start_write(parser->inflight.functions, pf) {
123 if (*pf->stop_monotonic_ut + RRDFUNCTIONS_TIMEOUT_EXTENSION_UT < now_ut) {
124 internal_error(true,
125 "FUNCTION '%s' removing expired transaction '%s', after %"PRIu64" usec.",
126 string2str(pf->function), pf_dfe.name, now_ut - pf->started_monotonic_ut);
127
128 if(!buffer_strlen(pf->result_body_wb) || pf->code == HTTP_RESP_OK)
129 pf->code = rrd_call_function_error(pf->result_body_wb,
130 "Timeout waiting for a response.",
131 HTTP_RESP_GATEWAY_TIMEOUT);
132
133 // Notify the plugin that the transaction has been cancelled due to timeout,
134 // so it can stop any in-progress work for this transaction.
135 char buffer[2048];
136 snprintfz(buffer, sizeof(buffer), PLUGINSD_CALL_FUNCTION_CANCEL " %s\n", pf_dfe.name);
137 send_to_plugin(buffer, pf->parser, STREAM_TRAFFIC_TYPE_FUNCTIONS);
138
139 dictionary_del(parser->inflight.functions, pf_dfe.name);
140 }
141
142 else if(!parser->inflight.smaller_monotonic_timeout_ut || *pf->stop_monotonic_ut + RRDFUNCTIONS_TIMEOUT_EXTENSION_UT < parser->inflight.smaller_monotonic_timeout_ut)
143 parser->inflight.smaller_monotonic_timeout_ut = *pf->stop_monotonic_ut + RRDFUNCTIONS_TIMEOUT_EXTENSION_UT;
144 }
145 dfe_done(pf);
146 }
147
148 // ----------------------------------------------------------------------------
149
150 static void pluginsd_function_cancel(void *data) {
151 struct inflight_function *look_for = data, *t;
152
153 bool sent = false;
154 dfe_start_read(look_for->parser->inflight.functions, t) {
155 if(look_for == t) {
156 const char *transaction = t_dfe.name;
157
158 internal_error(true, "PLUGINSD: sending function cancellation to plugin for transaction '%s'", transaction);
159
160 char buffer[2048];
161 snprintfz(buffer, sizeof(buffer), PLUGINSD_CALL_FUNCTION_CANCEL " %s\n", transaction);
162
163 // send the command to the plugin
164 ssize_t ret = send_to_plugin(buffer, t->parser, STREAM_TRAFFIC_TYPE_FUNCTIONS);
165 if(ret < 0)
166 sent = true;
167
168 break;
169 }
170 }
171 dfe_done(t);
172
173 if(sent <= 0)
174 nd_log(NDLS_DAEMON, NDLP_DEBUG,
175 "PLUGINSD: FUNCTION_CANCEL request didn't match any pending function requests in pluginsd.d.");
176 }
177
178 static void pluginsd_function_progress_to_plugin(const char *transaction, void *data) {
179 PARSER *parser = data;
180
181 if(!transaction || !*transaction) {
182 nd_log(NDLS_DAEMON, NDLP_ERR,
183 "PLUGINSD: FUNCTION_PROGRESS request without transaction!");
184 return;
185 }
186
187 if(!parser) {
188 nd_log(NDLS_DAEMON, NDLP_ERR,
189 "PLUGINSD: FUNCTION_PROGRESS request without parser!");
190 return;
191 }
192
193 DICTIONARY *dict = parser->inflight.functions;
194
195 if(!dict) {
196 nd_log(NDLS_DAEMON, NDLP_ERR,
197 "PLUGINSD: FUNCTION_PROGRESS request without inflight functions dictionary!");
198 return;
199 }
200
201 const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(dict, transaction);
202 if(!item) {
203 nd_log(NDLS_DAEMON, NDLP_DEBUG,
204 "PLUGINSD: FUNCTION_PROGRESS request for transaction '%s' that is not in progress!", transaction);
205 return;
206 }
207
208 struct inflight_function *t = dictionary_acquired_item_value(item);
209 if(t->parser != parser) {
210 nd_log(NDLS_DAEMON, NDLP_ERR,
211 "PLUGINSD: FUNCTION_PROGRESS request for transaction '%s' parser mismatch!", transaction);
212 dictionary_acquired_item_release(dict, item);
213 return;
214 }
215
216 internal_error(true, "PLUGINSD: sending function progress to plugin for transaction '%s'", transaction);
217
218 char buffer[512];
219 snprintfz(buffer, sizeof(buffer), PLUGINSD_CALL_FUNCTION_PROGRESS " %s\n", transaction);
220
221 // send the command to the plugin
222 ssize_t ret = send_to_plugin(buffer, t->parser, STREAM_TRAFFIC_TYPE_FUNCTIONS);
223 if(ret != (ssize_t)strlen(buffer)) {
224 nd_log(NDLS_DAEMON, NDLP_ERR,
225 "PLUGINSD: FUNCTION_PROGRESS request failed to send to plugin for transaction '%s'", transaction);
226 }
227
228 dictionary_acquired_item_release(dict, item);
229 }
230
231 // this is the function called from
232 // rrd_call_function_and_wait() and rrd_call_function_async()
233 int pluginsd_function_execute_cb(struct rrd_function_execute *rfe, void *data) {
234
235 // IMPORTANT: this function MUST call the result_cb even on failures
236
237 PARSER *parser = data;
238
239 usec_t now_ut = now_monotonic_usec();
240
241 int timeout_s = (int)((*rfe->stop_monotonic_ut - now_ut + USEC_PER_SEC / 2) / USEC_PER_SEC);
242
243 struct inflight_function tmp = {
244 .started_monotonic_ut = now_ut,
245 .stop_monotonic_ut = rfe->stop_monotonic_ut,
246 .result_body_wb = rfe->result.wb,
247 .timeout_s = timeout_s,
248 .function = string_strdupz(rfe->function),
249 .payload = buffer_dup(rfe->payload),
250 .access = rfe->user_access,
251 .source = rfe->source ? strdupz(rfe->source) : NULL,
252 .parser = parser,
253
254 .result = {
255 .cb = rfe->result.cb,
256 .data = rfe->result.data,
257 },
258 .progress = {
259 .cb = rfe->progress.cb,
260 .data = rfe->progress.data,
261 },
262 };
263 uuid_copy(tmp.transaction, *rfe->transaction);
264
265 char transaction_str[UUID_COMPACT_STR_LEN];
266 uuid_unparse_lower_compact(tmp.transaction, transaction_str);
267
268 dictionary_write_lock(parser->inflight.functions);
269
270 // if there is any error, our dictionary callbacks will call the caller callback to notify
271 // the caller about the error - no need for error handling here.
272 struct inflight_function *t = dictionary_set(parser->inflight.functions, transaction_str, &tmp, sizeof(struct inflight_function));
273 if(!t) {
274 // dictionary_set() returns NULL when the dictionary is destroyed
275 // (e.g., the plugin has exited). Clean up and notify the caller.
276 dictionary_write_unlock(parser->inflight.functions);
277
278 int code = HTTP_RESP_SERVICE_UNAVAILABLE;
279 rrd_call_function_error(rfe->result.wb, "The plugin is not available.", code);
280 rfe->result.cb(rfe->result.wb, code, rfe->result.data);
281
282 string_freez(tmp.function);
283 buffer_free(tmp.payload);
284 freez((void *)tmp.source);
285
286 return code;
287 }
288
289 if(!t->sent_successfully) {
290 int code = t->code;
291 dictionary_write_unlock(parser->inflight.functions);
292 dictionary_del(parser->inflight.functions, transaction_str);
293 pluginsd_inflight_functions_garbage_collect(parser, now_ut);
294 return code;
295 }
296 else {
297 if (rfe->register_canceller.cb)
298 rfe->register_canceller.cb(rfe->register_canceller.data, pluginsd_function_cancel, t);
299
300 if (rfe->register_progresser.cb &&
301 (parser->repertoire == PARSER_INIT_PLUGINSD || (parser->repertoire == PARSER_INIT_STREAMING &&
302 stream_has_capability(&parser->user, STREAM_CAP_PROGRESS))))
303 rfe->register_progresser.cb(rfe->register_progresser.data, pluginsd_function_progress_to_plugin, t->parser);
304
305 if (!parser->inflight.smaller_monotonic_timeout_ut ||
306 *tmp.stop_monotonic_ut + RRDFUNCTIONS_TIMEOUT_EXTENSION_UT < parser->inflight.smaller_monotonic_timeout_ut)
307 parser->inflight.smaller_monotonic_timeout_ut = *tmp.stop_monotonic_ut + RRDFUNCTIONS_TIMEOUT_EXTENSION_UT;
308
309 // garbage collect stale inflight functions
310 if (parser->inflight.smaller_monotonic_timeout_ut < now_ut)
311 pluginsd_inflight_functions_garbage_collect(parser, now_ut);
312
313 dictionary_write_unlock(parser->inflight.functions);
314
315 return HTTP_RESP_OK;
316 }
317 }
318
319 PARSER_RC pluginsd_function(char **words, size_t num_words, PARSER *parser) {
320 // a plugin or a child is registering a function
321
322 bool global = false;
323 size_t i = 1;
324 if(num_words >= 2 && strcmp(get_word(words, num_words, 1), "GLOBAL") == 0) {
325 i++;
326 global = true;
327 }
328
329 char *name = get_word(words, num_words, i++);
330 char *timeout_str = get_word(words, num_words, i++);
331 char *help = get_word(words, num_words, i++);
332 char *tags = get_word(words, num_words, i++);
333 char *access_str = get_word(words, num_words, i++);
334 char *priority_str = get_word(words, num_words, i++);
335 char *version_str = get_word(words, num_words, i++);
336
337 RRDHOST *host = pluginsd_require_scope_host(parser, PLUGINSD_KEYWORD_FUNCTION);
338 if(!host) return PARSER_RC_ERROR;
339
340 RRDSET *st = (global)? NULL: pluginsd_require_scope_chart(parser, PLUGINSD_KEYWORD_FUNCTION, PLUGINSD_KEYWORD_CHART);
341 if(!st) global = true;
342
343 if (unlikely(!timeout_str || !name || !help || (!global && !st))) {
344 netdata_log_error("PLUGINSD: 'host:%s/chart:%s' got a FUNCTION, without providing the required data (global = '%s', name = '%s', timeout = '%s', priority = '%s', version = '%s', help = '%s'). Ignoring it.",
345 rrdhost_hostname(host),
346 st?rrdset_id(st):"(unset)",
347 global?"yes":"no",
348 name?name:"(unset)",
349 timeout_str ? timeout_str : "(unset)",
350 priority_str ? priority_str : "(unset)",
351 version_str ? version_str : "(unset)",
352 help?help:"(unset)"
353 );
354 return PARSER_RC_ERROR;
355 }
356
357 int timeout_s = PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT;
358 if (timeout_str && *timeout_str) {
359 timeout_s = str2i(timeout_str);
360 if (unlikely(timeout_s <= 0))
361 timeout_s = PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT;
362 }
363
364 int priority = RRDFUNCTIONS_PRIORITY_DEFAULT;
365 if(priority_str && *priority_str) {
366 priority = str2i(priority_str);
367 if(priority <= 0)
368 priority = RRDFUNCTIONS_PRIORITY_DEFAULT;
369 }
370
371 uint32_t version = RRDFUNCTIONS_VERSION_DEFAULT;
372 if(version_str && *version_str)
373 version = str2u(version_str);
374
375 rrd_function_add(host, st, name, timeout_s, priority, version, help, tags,
376 http_access_from_hex_mapping_old_roles(access_str), false,
377 pluginsd_function_execute_cb, parser);
378
379 parser->user.data_collections_count++;
380
381 return PARSER_RC_OK;
382 }
383
384 static void pluginsd_function_result_end(struct parser *parser, void *action_data) {
385 STRING *key = action_data;
386 if(key)
387 dictionary_del(parser->inflight.functions, string2str(key));
388 string_freez(key);
389
390 parser->user.data_collections_count++;
391 }
392
393 static inline struct inflight_function *inflight_function_find(PARSER *parser, const char *transaction) {
394 struct inflight_function *pf = NULL;
395
396 if(transaction && *transaction)
397 pf = (struct inflight_function *)dictionary_get(parser->inflight.functions, transaction);
398
399 if(!pf)
400 netdata_log_error("got a " PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN " for transaction '%s', but the transaction is not found.", transaction ? transaction : "(unset)");
401
402 return pf;
403 }
404
405 PARSER_RC pluginsd_function_result_begin(char **words, size_t num_words, PARSER *parser) {
406 char *transaction = get_word(words, num_words, 1);
407 char *status = get_word(words, num_words, 2);
408 char *format = get_word(words, num_words, 3);
409 char *expires = get_word(words, num_words, 4);
410
411 if (unlikely(!transaction || !*transaction || !status || !*status || !format || !*format || !expires || !*expires)) {
412 netdata_log_error("got a " PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN " without providing the required data (key = '%s', status = '%s', format = '%s', expires = '%s')."
413 , transaction ? transaction : "(unset)"
414 , status ? status : "(unset)"
415 , format ? format : "(unset)"
416 , expires ? expires : "(unset)"
417 );
418 }
419
420 int code = (status && *status) ? str2i(status) : 0;
421 if (code <= 0)
422 code = HTTP_RESP_BACKEND_RESPONSE_INVALID;
423
424 time_t expiration = (expires && *expires) ? str2l(expires) : 0;
425
426 struct inflight_function *pf = inflight_function_find(parser, transaction);
427 if(pf) {
428 if(format && *format)
429 pf->result_body_wb->content_type = content_type_string2id(format);
430
431 pf->code = code;
432
433 pf->result_body_wb->expires = expiration;
434 if(expiration <= now_realtime_sec())
435 buffer_no_cacheable(pf->result_body_wb);
436 else
437 buffer_cacheable(pf->result_body_wb);
438 }
439
440 parser->defer.response = (pf) ? pf->result_body_wb : NULL;
441 parser->defer.end_keyword = PLUGINSD_KEYWORD_FUNCTION_RESULT_END;
442 parser->defer.action = pluginsd_function_result_end;
443 parser->defer.action_data = string_strdupz(transaction); // it is ok is key is NULL
444 parser->flags |= PARSER_DEFER_UNTIL_KEYWORD;
445
446 return PARSER_RC_OK;
447 }
448
449 PARSER_RC pluginsd_function_progress(char **words, size_t num_words, PARSER *parser) {
450 size_t i = 1;
451
452 char *transaction = get_word(words, num_words, i++);
453 char *done_str = get_word(words, num_words, i++);
454 char *all_str = get_word(words, num_words, i++);
455
456 struct inflight_function *pf = inflight_function_find(parser, transaction);
457 if(pf) {
458 size_t done = done_str && *done_str ? str2u(done_str) : 0;
459 size_t all = all_str && *all_str ? str2u(all_str) : 0;
460
461 if(pf->progress.cb)
462 pf->progress.cb(&pf->transaction, pf->progress.data, done, all);
463 }
464
465 return PARSER_RC_OK;
466 }