dyncfg-intercept.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "dyncfg-internals.h"
  3. #include "dyncfg.h"
  4. // ----------------------------------------------------------------------------
  5. // we intercept the config function calls of the plugin
  6. struct dyncfg_call {
  7. BUFFER *payload;
  8. char *function;
  9. char *id;
  10. char *add_name;
  11. char *source;
  12. DYNCFG_CMDS cmd;
  13. rrd_function_result_callback_t result_cb;
  14. void *result_cb_data;
  15. bool from_dyncfg_echo;
  16. };
  17. DYNCFG_STATUS dyncfg_status_from_successful_response(int code) {
  18. DYNCFG_STATUS status;
  19. if(code == DYNCFG_RESP_RUNNING)
  20. status = DYNCFG_STATUS_RUNNING;
  21. else if(code == DYNCFG_RESP_ACCEPTED || code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
  22. status = DYNCFG_STATUS_ACCEPTED;
  23. return status;
  24. }
  25. void dyncfg_function_intercept_result_cb(BUFFER *wb, int code, void *result_cb_data) {
  26. struct dyncfg_call *dc = result_cb_data;
  27. bool called_from_dyncfg_echo = dc->from_dyncfg_echo;
  28. const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item_advanced(dyncfg_globals.nodes, dc->id, -1);
  29. if(item) {
  30. DYNCFG *df = dictionary_acquired_item_value(item);
  31. bool old_user_disabled = df->user_disabled;
  32. bool save_required = false;
  33. if (!called_from_dyncfg_echo) {
  34. // the command was sent by a user
  35. if (DYNCFG_RESP_SUCCESS(code)) {
  36. if (dc->cmd == DYNCFG_CMD_ADD) {
  37. char id[strlen(dc->id) + 1 + strlen(dc->add_name) + 1];
  38. snprintfz(id, sizeof(id), "%s:%s", dc->id, dc->add_name);
  39. const DICTIONARY_ITEM *new_item = dyncfg_add_internal(
  40. df->host,
  41. id,
  42. string2str(df->path),
  43. dyncfg_status_from_successful_response(code),
  44. DYNCFG_TYPE_JOB,
  45. DYNCFG_SOURCE_TYPE_DYNCFG,
  46. dc->source,
  47. (df->cmds & ~DYNCFG_CMD_ADD) | DYNCFG_CMD_GET | DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_REMOVE,
  48. 0,
  49. 0,
  50. df->sync,
  51. df->execute_cb, df->execute_cb_data, false);
  52. DYNCFG *new_df = dictionary_acquired_item_value(new_item);
  53. SWAP(new_df->payload, dc->payload);
  54. if(code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
  55. new_df->restart_required = true;
  56. dyncfg_file_save(id, new_df);
  57. dictionary_acquired_item_release(dyncfg_globals.nodes, new_item);
  58. } else if (dc->cmd == DYNCFG_CMD_UPDATE) {
  59. df->source_type = DYNCFG_SOURCE_TYPE_DYNCFG;
  60. string_freez(df->source);
  61. df->source = string_strdupz(dc->source);
  62. df->status = dyncfg_status_from_successful_response(code);
  63. SWAP(df->payload, dc->payload);
  64. save_required = true;
  65. } else if (dc->cmd == DYNCFG_CMD_ENABLE) {
  66. df->user_disabled = false;
  67. } else if (dc->cmd == DYNCFG_CMD_DISABLE) {
  68. df->user_disabled = true;
  69. } else if (dc->cmd == DYNCFG_CMD_REMOVE) {
  70. dyncfg_file_delete(dc->id);
  71. }
  72. if(dc->cmd != DYNCFG_CMD_ADD && code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
  73. df->restart_required = true;
  74. }
  75. else
  76. nd_log(NDLS_DAEMON, NDLP_ERR,
  77. "DYNCFG: plugin returned code %d to user initiated call: %s", code, dc->function);
  78. }
  79. else {
  80. // the command was sent by dyncfg
  81. if(DYNCFG_RESP_SUCCESS(code)) {
  82. if(dc->cmd == DYNCFG_CMD_ADD) {
  83. char id[strlen(dc->id) + 1 + strlen(dc->add_name) + 1];
  84. snprintfz(id, sizeof(id), "%s:%s", dc->id, dc->add_name);
  85. const DICTIONARY_ITEM *new_item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
  86. if(new_item) {
  87. DYNCFG *new_df = dictionary_acquired_item_value(new_item);
  88. new_df->status = dyncfg_status_from_successful_response(code);
  89. if(code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
  90. new_df->restart_required = true;
  91. dictionary_acquired_item_release(dyncfg_globals.nodes, new_item);
  92. }
  93. }
  94. else if(dc->cmd == DYNCFG_CMD_UPDATE) {
  95. df->status = dyncfg_status_from_successful_response(code);
  96. df->plugin_rejected = false;
  97. }
  98. else if(dc->cmd == DYNCFG_CMD_DISABLE)
  99. df->status = DYNCFG_STATUS_DISABLED;
  100. else if(dc->cmd == DYNCFG_CMD_ENABLE)
  101. df->status = dyncfg_status_from_successful_response(code);
  102. if(dc->cmd != DYNCFG_CMD_ADD && code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
  103. df->restart_required = true;
  104. }
  105. else {
  106. nd_log(NDLS_DAEMON, NDLP_ERR,
  107. "DYNCFG: plugin returned code %d to dyncfg initiated call: %s", code, dc->function);
  108. if(dc->cmd & (DYNCFG_CMD_UPDATE | DYNCFG_CMD_ADD))
  109. df->plugin_rejected = true;
  110. }
  111. }
  112. if (save_required || old_user_disabled != df->user_disabled)
  113. dyncfg_file_save(dc->id, df);
  114. dictionary_acquired_item_release(dyncfg_globals.nodes, item);
  115. }
  116. if(dc->result_cb)
  117. dc->result_cb(wb, code, dc->result_cb_data);
  118. buffer_free(dc->payload);
  119. freez(dc->function);
  120. freez(dc->id);
  121. freez(dc->source);
  122. freez(dc->add_name);
  123. freez(dc);
  124. }
  125. // ----------------------------------------------------------------------------
  126. static void dyncfg_apply_action_on_all_template_jobs(const char *template_id, DYNCFG_CMDS c) {
  127. STRING *template = string_strdupz(template_id);
  128. DYNCFG *df;
  129. dfe_start_reentrant(dyncfg_globals.nodes, df) {
  130. if(df->template == template && df->type == DYNCFG_TYPE_JOB) {
  131. DYNCFG_CMDS cmd_to_send_to_plugin = c;
  132. if(c == DYNCFG_CMD_ENABLE)
  133. cmd_to_send_to_plugin = df->user_disabled ? DYNCFG_CMD_DISABLE : DYNCFG_CMD_ENABLE;
  134. else if(c == DYNCFG_CMD_DISABLE)
  135. cmd_to_send_to_plugin = DYNCFG_CMD_DISABLE;
  136. dyncfg_echo(df_dfe.item, df, df_dfe.name, cmd_to_send_to_plugin);
  137. }
  138. }
  139. dfe_done(df);
  140. string_freez(template);
  141. }
  142. // ----------------------------------------------------------------------------
  143. // the callback for all config functions
  144. int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __maybe_unused) {
  145. // IMPORTANT: this function MUST call the result_cb even on failures
  146. bool called_from_dyncfg_echo = rrd_function_has_this_original_result_callback(rfe->transaction, dyncfg_echo_cb);
  147. DYNCFG_CMDS c = DYNCFG_CMD_NONE;
  148. const DICTIONARY_ITEM *item = NULL;
  149. const char *add_name = NULL;
  150. size_t add_name_len = 0;
  151. if(strncmp(rfe->function, PLUGINSD_FUNCTION_CONFIG " ", sizeof(PLUGINSD_FUNCTION_CONFIG)) == 0) {
  152. const char *id = &rfe->function[sizeof(PLUGINSD_FUNCTION_CONFIG)];
  153. while(isspace(*id)) id++;
  154. const char *space = id;
  155. while(*space && !isspace(*space)) space++;
  156. size_t id_len = space - id;
  157. const char *cmd = space;
  158. while(isspace(*cmd)) cmd++;
  159. space = cmd;
  160. while(*space && !isspace(*space)) space++;
  161. size_t cmd_len = space - cmd;
  162. char cmd_copy[cmd_len + 1];
  163. strncpyz(cmd_copy, cmd, cmd_len);
  164. c = dyncfg_cmds2id(cmd_copy);
  165. if(c == DYNCFG_CMD_ADD) {
  166. add_name = space;
  167. while(isspace(*add_name)) add_name++;
  168. space = add_name;
  169. while(*space && !isspace(*space)) space++;
  170. add_name_len = space - add_name;
  171. }
  172. item = dictionary_get_and_acquire_item_advanced(dyncfg_globals.nodes, id, (ssize_t)id_len);
  173. }
  174. int rc = HTTP_RESP_INTERNAL_SERVER_ERROR;
  175. if(!item) {
  176. rc = HTTP_RESP_NOT_FOUND;
  177. dyncfg_default_response(rfe->result.wb, rc, "dyncfg functions intercept: id is not found");
  178. if(rfe->result.cb)
  179. rfe->result.cb(rfe->result.wb, rc, rfe->result.data);
  180. return HTTP_RESP_NOT_FOUND;
  181. }
  182. DYNCFG *df = dictionary_acquired_item_value(item);
  183. const char *id = dictionary_acquired_item_name(item);
  184. bool has_payload = rfe->payload && buffer_strlen(rfe->payload) ? true : false;
  185. bool make_the_call_to_plugin = true;
  186. if((c & (DYNCFG_CMD_GET | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_REMOVE | DYNCFG_CMD_RESTART)) && has_payload)
  187. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: command has a payload, but it is not going to be used: %s", rfe->function);
  188. if(c == DYNCFG_CMD_NONE) {
  189. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: this command is unknown: %s", rfe->function);
  190. rc = HTTP_RESP_BAD_REQUEST;
  191. dyncfg_default_response(rfe->result.wb, rc,
  192. "dyncfg functions intercept: unknown command");
  193. make_the_call_to_plugin = false;
  194. }
  195. else if(!(df->cmds & c)) {
  196. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: this command is not supported by the configuration node: %s", rfe->function);
  197. rc = HTTP_RESP_BAD_REQUEST;
  198. dyncfg_default_response(rfe->result.wb, rc,
  199. "dyncfg functions intercept: this command is not supported by this configuration node");
  200. make_the_call_to_plugin = false;
  201. }
  202. else if((c & (DYNCFG_CMD_ADD | DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST)) && !has_payload) {
  203. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: command requires a payload, but no payload given: %s", rfe->function);
  204. rc = HTTP_RESP_BAD_REQUEST;
  205. dyncfg_default_response(rfe->result.wb, rc,
  206. "dyncfg functions intercept: payload is required");
  207. make_the_call_to_plugin = false;
  208. }
  209. else if(c == DYNCFG_CMD_SCHEMA) {
  210. bool loaded = false;
  211. if(df->type == DYNCFG_TYPE_JOB) {
  212. char template[strlen(id) + 1];
  213. memcpy(template, id, sizeof(template));
  214. char *colon = strrchr(template, ':');
  215. if(colon) *colon = '\0';
  216. if(template[0])
  217. loaded = dyncfg_get_schema(template, rfe->result.wb);
  218. }
  219. else
  220. loaded = dyncfg_get_schema(id, rfe->result.wb);
  221. if(loaded) {
  222. rfe->result.wb->content_type = CT_APPLICATION_JSON;
  223. rfe->result.wb->expires = now_realtime_sec();
  224. rc = HTTP_RESP_OK;
  225. make_the_call_to_plugin = false;
  226. }
  227. }
  228. else if(c & (DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_RESTART) && df->type == DYNCFG_TYPE_TEMPLATE) {
  229. if(!called_from_dyncfg_echo) {
  230. bool old_user_disabled = df->user_disabled;
  231. if (c == DYNCFG_CMD_ENABLE)
  232. df->user_disabled = false;
  233. else if (c == DYNCFG_CMD_DISABLE)
  234. df->user_disabled = true;
  235. if (df->user_disabled != old_user_disabled)
  236. dyncfg_file_save(id, df);
  237. }
  238. dyncfg_apply_action_on_all_template_jobs(id, c);
  239. rc = HTTP_RESP_OK;
  240. dyncfg_default_response(rfe->result.wb, rc, "applied");
  241. make_the_call_to_plugin = false;
  242. }
  243. else if(c == DYNCFG_CMD_ADD) {
  244. if (df->type != DYNCFG_TYPE_TEMPLATE) {
  245. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: add command can only be applied on templates, not %s: %s",
  246. dyncfg_id2type(df->type), rfe->function);
  247. rc = HTTP_RESP_BAD_REQUEST;
  248. dyncfg_default_response(rfe->result.wb, rc,
  249. "dyncfg functions intercept: add command is only allowed in templates");
  250. make_the_call_to_plugin = false;
  251. }
  252. else if (!add_name || !*add_name || !add_name_len) {
  253. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: add command does not specify a name: %s", rfe->function);
  254. rc = HTTP_RESP_BAD_REQUEST;
  255. dyncfg_default_response(rfe->result.wb, rc,
  256. "dyncfg functions intercept: command add requires a name, which is missing");
  257. make_the_call_to_plugin = false;
  258. }
  259. }
  260. else if(c == DYNCFG_CMD_ENABLE && df->type == DYNCFG_TYPE_JOB && dyncfg_is_user_disabled(string2str(df->template))) {
  261. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot enable a job of a disabled template: %s", rfe->function);
  262. rc = HTTP_RESP_BAD_REQUEST;
  263. dyncfg_default_response(rfe->result.wb, rc,
  264. "dyncfg functions intercept: this job belongs to disabled template");
  265. make_the_call_to_plugin = false;
  266. }
  267. if(make_the_call_to_plugin) {
  268. struct dyncfg_call *dc = callocz(1, sizeof(*dc));
  269. dc->function = strdupz(rfe->function);
  270. dc->id = strdupz(id);
  271. dc->source = rfe->source ? strdupz(rfe->source) : NULL;
  272. dc->add_name = (c == DYNCFG_CMD_ADD) ? strndupz(add_name, add_name_len) : NULL;
  273. dc->cmd = c;
  274. dc->result_cb = rfe->result.cb;
  275. dc->result_cb_data = rfe->result.data;
  276. dc->payload = buffer_dup(rfe->payload);
  277. dc->from_dyncfg_echo = called_from_dyncfg_echo;
  278. rfe->result.cb = dyncfg_function_intercept_result_cb;
  279. rfe->result.data = dc;
  280. rc = df->execute_cb(rfe, df->execute_cb_data);
  281. }
  282. else if(rfe->result.cb)
  283. rfe->result.cb(rfe->result.wb, rc, rfe->result.data);
  284. dictionary_acquired_item_release(dyncfg_globals.nodes, item);
  285. return rc;
  286. }