dyncfg-tree.c 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "dyncfg-internals.h"
  3. #include "dyncfg.h"
  4. static int dyncfg_tree_compar(const void *a, const void *b) {
  5. const DICTIONARY_ITEM *item1 = *(const DICTIONARY_ITEM **)a;
  6. const DICTIONARY_ITEM *item2 = *(const DICTIONARY_ITEM **)b;
  7. DYNCFG *df1 = dictionary_acquired_item_value(item1);
  8. DYNCFG *df2 = dictionary_acquired_item_value(item2);
  9. int rc = string_cmp(df1->path, df2->path);
  10. if(rc == 0)
  11. rc = strcmp(dictionary_acquired_item_name(item1), dictionary_acquired_item_name(item2));
  12. return rc;
  13. }
  14. static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb) {
  15. buffer_json_member_add_object(wb, id);
  16. {
  17. buffer_json_member_add_string(wb, "type", dyncfg_id2type(df->type));
  18. if(df->type == DYNCFG_TYPE_JOB)
  19. buffer_json_member_add_string(wb, "template", string2str(df->template));
  20. buffer_json_member_add_string(wb, "status", dyncfg_id2status(df->status));
  21. dyncfg_cmds2json_array(df->cmds, "cmds", wb);
  22. buffer_json_member_add_string(wb, "source_type", dyncfg_id2source_type(df->source_type));
  23. buffer_json_member_add_string(wb, "source", string2str(df->source));
  24. buffer_json_member_add_boolean(wb, "sync", df->sync);
  25. buffer_json_member_add_boolean(wb, "user_disabled", df->user_disabled);
  26. buffer_json_member_add_boolean(wb, "restart_required", df->restart_required);
  27. buffer_json_member_add_boolean(wb, "plugin_rejected", df->restart_required);
  28. buffer_json_member_add_object(wb, "payload");
  29. {
  30. if (df->payload && buffer_strlen(df->payload)) {
  31. buffer_json_member_add_boolean(wb, "available", true);
  32. buffer_json_member_add_string(wb, "content_type", content_type_id2string(df->payload->content_type));
  33. buffer_json_member_add_uint64(wb, "content_length", df->payload->len);
  34. } else
  35. buffer_json_member_add_boolean(wb, "available", false);
  36. }
  37. buffer_json_object_close(wb); // payload
  38. buffer_json_member_add_uint64(wb, "saves", df->saves);
  39. buffer_json_member_add_uint64(wb, "created_ut", df->created_ut);
  40. buffer_json_member_add_uint64(wb, "modified_ut", df->modified_ut);
  41. }
  42. buffer_json_object_close(wb);
  43. }
  44. static void dyncfg_tree_for_host(RRDHOST *host, BUFFER *wb, const char *path, const char *id) {
  45. size_t entries = dictionary_entries(dyncfg_globals.nodes);
  46. size_t used = 0;
  47. const DICTIONARY_ITEM *items[entries];
  48. size_t restart_required = 0, plugin_rejected = 0, status_incomplete = 0, status_failed = 0;
  49. STRING *template = NULL;
  50. if(id && *id)
  51. template = string_strdupz(id);
  52. size_t path_len = strlen(path);
  53. DYNCFG *df;
  54. dfe_start_read(dyncfg_globals.nodes, df) {
  55. if(!df->host) {
  56. if(uuid_memcmp(&df->host_uuid, &host->host_uuid) == 0)
  57. df->host = host;
  58. }
  59. if(df->host != host || strncmp(string2str(df->path), path, path_len) != 0)
  60. continue;
  61. if(!rrd_function_available(host, string2str(df->function)))
  62. df->status = DYNCFG_STATUS_ORPHAN;
  63. if((id && strcmp(id, df_dfe.name) != 0) || (template && df->template != template))
  64. continue;
  65. items[used++] = dictionary_acquired_item_dup(dyncfg_globals.nodes, df_dfe.item);
  66. }
  67. dfe_done(df);
  68. if(used > 1)
  69. qsort(items, used, sizeof(const DICTIONARY_ITEM *), dyncfg_tree_compar);
  70. buffer_flush(wb);
  71. buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
  72. buffer_json_member_add_uint64(wb, "version", 1);
  73. buffer_json_member_add_object(wb, "tree");
  74. {
  75. STRING *last_path = NULL;
  76. for (size_t i = 0; i < used; i++) {
  77. df = dictionary_acquired_item_value(items[i]);
  78. if (df->path != last_path) {
  79. last_path = df->path;
  80. if (i)
  81. buffer_json_object_close(wb);
  82. buffer_json_member_add_object(wb, string2str(last_path));
  83. }
  84. dyncfg_to_json(df, dictionary_acquired_item_name(items[i]), wb);
  85. if(df->status != DYNCFG_STATUS_ORPHAN) {
  86. if (df->restart_required)
  87. restart_required++;
  88. if (df->plugin_rejected)
  89. plugin_rejected++;
  90. if (df->status == DYNCFG_STATUS_FAILED)
  91. status_failed++;
  92. if (df->status == DYNCFG_STATUS_INCOMPLETE)
  93. status_incomplete++;
  94. }
  95. }
  96. if (used)
  97. buffer_json_object_close(wb);
  98. }
  99. buffer_json_object_close(wb); // tree
  100. buffer_json_member_add_object(wb, "attention");
  101. {
  102. buffer_json_member_add_boolean(wb, "degraded", restart_required + plugin_rejected + status_failed + status_incomplete > 0);
  103. buffer_json_member_add_uint64(wb, "restart_required", restart_required);
  104. buffer_json_member_add_uint64(wb, "plugin_rejected", plugin_rejected);
  105. buffer_json_member_add_uint64(wb, "status_failed", status_failed);
  106. buffer_json_member_add_uint64(wb, "status_incomplete", status_incomplete);
  107. }
  108. buffer_json_object_close(wb); // attention
  109. buffer_json_agents_v2(wb, NULL, 0, false, false);
  110. buffer_json_finalize(wb);
  111. for(size_t i = 0; i < used ;i++)
  112. dictionary_acquired_item_release(dyncfg_globals.nodes, items[i]);
  113. }
  114. static int dyncfg_config_execute_cb(struct rrd_function_execute *rfe, void *data) {
  115. RRDHOST *host = data;
  116. int code;
  117. char buf[strlen(rfe->function) + 1];
  118. memcpy(buf, rfe->function, sizeof(buf));
  119. char *words[MAX_FUNCTION_PARAMETERS]; // an array of pointers for the words in this line
  120. size_t num_words = quoted_strings_splitter_pluginsd(buf, words, MAX_FUNCTION_PARAMETERS);
  121. const char *config = get_word(words, num_words, 0);
  122. const char *action = get_word(words, num_words, 1);
  123. const char *path = get_word(words, num_words, 2);
  124. const char *id = get_word(words, num_words, 3);
  125. if(!config || !*config || strcmp(config, PLUGINSD_FUNCTION_CONFIG) != 0) {
  126. char *msg = "invalid function call, expected: config";
  127. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG TREE: function call '%s': %s", rfe->function, msg);
  128. code = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  129. goto cleanup;
  130. }
  131. if(!action || !*action) {
  132. char *msg = "invalid function call, expected: config tree";
  133. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG TREE: function call '%s': %s", rfe->function, msg);
  134. code = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  135. goto cleanup;
  136. }
  137. if(strcmp(action, "tree") == 0) {
  138. if(!path || !*path)
  139. path = "/";
  140. if(!id || !*id)
  141. id = NULL;
  142. else if(!dyncfg_is_valid_id(id)) {
  143. char *msg = "invalid id given";
  144. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG TREE: function call '%s': %s", rfe->function, msg);
  145. code = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  146. goto cleanup;
  147. }
  148. code = HTTP_RESP_OK;
  149. dyncfg_tree_for_host(host, rfe->result.wb, path, id);
  150. }
  151. else {
  152. code = HTTP_RESP_NOT_FOUND;
  153. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: unknown config id '%s' in call: '%s'. This can happen if the plugin that registered the dynamic configuration is not running now.", action, rfe->function);
  154. rrd_call_function_error(rfe->result.wb, "unknown config id given", code);
  155. }
  156. cleanup:
  157. if(rfe->result.cb)
  158. rfe->result.cb(rfe->result.wb, code, rfe->result.data);
  159. return code;
  160. }
  161. // ----------------------------------------------------------------------------
  162. // this adds a 'config' function to all leaf nodes (localhost and virtual nodes)
  163. // which is used to serve the tree and act as a catch-all for all config calls
  164. // for which there is no id overloaded.
  165. void dyncfg_host_init(RRDHOST *host) {
  166. rrd_function_add(host, NULL, PLUGINSD_FUNCTION_CONFIG, 120,
  167. 1000, "Dynamic configuration", "config",
  168. HTTP_ACCESS_ADMIN,
  169. true, dyncfg_config_execute_cb, host);
  170. }