0
0
Fork 0
mirror of https://github.com/netdata/netdata.git synced 2025-04-10 16:17:36 +00:00

Dynamic Config MVP0 ()

* work on dynamic configuration interface which should allow adding/modifying monitoring endpoints during runtime
This commit is contained in:
Timotej S 2023-08-02 13:38:30 +02:00 committed by GitHub
parent 4739bbc9a4
commit 256f22ff09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1642 additions and 168 deletions

View file

@ -200,6 +200,8 @@ LIBNETDATA_FILES = \
libnetdata/worker_utilization/worker_utilization.c \
libnetdata/worker_utilization/worker_utilization.h \
libnetdata/http/http_defs.h \
libnetdata/dyn_conf/dyn_conf.c \
libnetdata/dyn_conf/dyn_conf.h \
$(NULL)
if ENABLE_PLUGIN_EBPF

View file

@ -12,41 +12,44 @@ PARSER_KEYWORD;
#
# Plugins Only Keywords
#
FLUSH, 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1
DISABLE, 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2
EXIT, 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3
HOST, 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4
HOST_DEFINE, 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5
HOST_DEFINE_END, 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6
HOST_LABEL, 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7
FLUSH, 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1
DISABLE, 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2
EXIT, 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3
HOST, 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4
HOST_DEFINE, 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5
HOST_DEFINE_END, 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6
HOST_LABEL, 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7
#
# Common keywords
#
BEGIN, 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8
CHART, 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9
CLABEL, 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10
CLABEL_COMMIT, 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11
DIMENSION, 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12
END, 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13
FUNCTION, 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14
FUNCTION_RESULT_BEGIN, 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15
LABEL, 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16
OVERWRITE, 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17
SET, 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18
VARIABLE, 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19
BEGIN, 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8
CHART, 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9
CLABEL, 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10
CLABEL_COMMIT, 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11
DIMENSION, 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12
END, 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13
FUNCTION, 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14
FUNCTION_RESULT_BEGIN, 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15
LABEL, 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16
OVERWRITE, 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17
SET, 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18
VARIABLE, 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19
DYNCFG_ENABLE, 101, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20
DYNCFG_REGISTER_MODULE, 102, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21
REPORT_JOB_STATUS, 110, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22
#
# Streaming only keywords
#
CLAIMED_ID, 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20
BEGIN2, 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21
SET2, 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22
END2, 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23
CLAIMED_ID, 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23
BEGIN2, 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24
SET2, 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25
END2, 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26
#
# Streaming Replication keywords
#
CHART_DEFINITION_END, 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24
RBEGIN, 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25
RDSTATE, 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26
REND, 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27
RSET, 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28
RSSTATE, 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29
CHART_DEFINITION_END, 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27
RBEGIN, 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28
RDSTATE, 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29
REND, 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 30
RSET, 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 31
RSSTATE, 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 32

View file

@ -30,12 +30,12 @@
#endif
#define GPERF_PARSER_TOTAL_KEYWORDS 29
#define GPERF_PARSER_TOTAL_KEYWORDS 32
#define GPERF_PARSER_MIN_WORD_LENGTH 3
#define GPERF_PARSER_MAX_WORD_LENGTH 21
#define GPERF_PARSER_MIN_HASH_VALUE 4
#define GPERF_PARSER_MAX_HASH_VALUE 36
/* maximum key range = 33, duplicates = 0 */
#define GPERF_PARSER_MAX_WORD_LENGTH 22
#define GPERF_PARSER_MIN_HASH_VALUE 3
#define GPERF_PARSER_MAX_HASH_VALUE 41
/* maximum key range = 39, duplicates = 0 */
#ifdef __GNUC__
__inline
@ -49,99 +49,105 @@ gperf_keyword_hash_function (register const char *str, register size_t len)
{
static unsigned char asso_values[] =
{
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 15, 10, 1, 1, 9,
4, 37, 0, 20, 37, 37, 9, 37, 14, 0,
37, 37, 1, 0, 37, 7, 13, 37, 18, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 16, 7, 2, 11, 0,
8, 42, 3, 9, 42, 42, 9, 42, 0, 2,
42, 42, 1, 3, 42, 7, 17, 42, 27, 2,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42
};
return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]];
}
static PARSER_KEYWORD gperf_keywords[] =
{
{(char*)0}, {(char*)0}, {(char*)0}, {(char*)0},
#line 18 "gperf-config.txt"
{"HOST", 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4},
#line 51 "gperf-config.txt"
{"RSET", 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28},
#line 26 "gperf-config.txt"
{"CHART", 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9},
{(char*)0},
#line 52 "gperf-config.txt"
{"RSSTATE", 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29},
#line 49 "gperf-config.txt"
{"RDSTATE", 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26},
#line 21 "gperf-config.txt"
{"HOST_LABEL", 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7},
#line 19 "gperf-config.txt"
{"HOST_DEFINE", 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5},
#line 35 "gperf-config.txt"
{"SET", 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18},
#line 42 "gperf-config.txt"
{"SET2", 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22},
#line 50 "gperf-config.txt"
{"REND", 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27},
#line 20 "gperf-config.txt"
{"HOST_DEFINE_END", 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6},
#line 27 "gperf-config.txt"
{"CLABEL", 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10},
#line 48 "gperf-config.txt"
{"RBEGIN", 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25},
#line 15 "gperf-config.txt"
{"FLUSH", 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1},
#line 31 "gperf-config.txt"
{"FUNCTION", 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14},
#line 40 "gperf-config.txt"
{"CLAIMED_ID", 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20},
#line 47 "gperf-config.txt"
{"CHART_DEFINITION_END", 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24},
#line 34 "gperf-config.txt"
{"OVERWRITE", 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17},
#line 28 "gperf-config.txt"
{"CLABEL_COMMIT", 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11},
#line 25 "gperf-config.txt"
{"BEGIN", 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8},
#line 41 "gperf-config.txt"
{"BEGIN2", 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21},
#line 30 "gperf-config.txt"
{"END", 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13},
#line 43 "gperf-config.txt"
{"END2", 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23},
#line 16 "gperf-config.txt"
{"DISABLE", 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2},
#line 33 "gperf-config.txt"
{"LABEL", 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16},
#line 29 "gperf-config.txt"
{"DIMENSION", 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12},
#line 17 "gperf-config.txt"
{"EXIT", 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3},
#line 32 "gperf-config.txt"
{"FUNCTION_RESULT_BEGIN", 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15},
{(char*)0}, {(char*)0}, {(char*)0},
#line 30 "gperf-config.txt"
{"END", 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13},
#line 46 "gperf-config.txt"
{"END2", 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26},
#line 53 "gperf-config.txt"
{"REND", 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 30},
#line 35 "gperf-config.txt"
{"SET", 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18},
#line 45 "gperf-config.txt"
{"SET2", 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25},
#line 54 "gperf-config.txt"
{"RSET", 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 31},
#line 18 "gperf-config.txt"
{"HOST", 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4},
#line 26 "gperf-config.txt"
{"CHART", 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9},
#line 55 "gperf-config.txt"
{"RSSTATE", 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 32},
#line 25 "gperf-config.txt"
{"BEGIN", 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8},
#line 44 "gperf-config.txt"
{"BEGIN2", 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24},
#line 51 "gperf-config.txt"
{"RBEGIN", 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28},
#line 21 "gperf-config.txt"
{"HOST_LABEL", 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7},
#line 19 "gperf-config.txt"
{"HOST_DEFINE", 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5},
#line 27 "gperf-config.txt"
{"CLABEL", 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10},
#line 39 "gperf-config.txt"
{"REPORT_JOB_STATUS", 110, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22},
#line 52 "gperf-config.txt"
{"RDSTATE", 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29},
#line 20 "gperf-config.txt"
{"HOST_DEFINE_END", 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6},
#line 43 "gperf-config.txt"
{"CLAIMED_ID", 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23},
#line 15 "gperf-config.txt"
{"FLUSH", 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1},
#line 31 "gperf-config.txt"
{"FUNCTION", 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14},
#line 28 "gperf-config.txt"
{"CLABEL_COMMIT", 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11},
#line 50 "gperf-config.txt"
{"CHART_DEFINITION_END", 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27},
#line 37 "gperf-config.txt"
{"DYNCFG_ENABLE", 101, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20},
#line 16 "gperf-config.txt"
{"DISABLE", 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2},
#line 34 "gperf-config.txt"
{"OVERWRITE", 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17},
#line 29 "gperf-config.txt"
{"DIMENSION", 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12},
#line 33 "gperf-config.txt"
{"LABEL", 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16},
#line 17 "gperf-config.txt"
{"EXIT", 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3},
{(char*)0}, {(char*)0}, {(char*)0},
#line 38 "gperf-config.txt"
{"DYNCFG_REGISTER_MODULE", 102, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21},
#line 32 "gperf-config.txt"
{"FUNCTION_RESULT_BEGIN", 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15},
{(char*)0}, {(char*)0}, {(char*)0}, {(char*)0},
#line 36 "gperf-config.txt"
{"VARIABLE", 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19}
{"VARIABLE", 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19}
};
PARSER_KEYWORD *

View file

@ -43,6 +43,11 @@
#define PLUGINSD_KEYWORD_HOST_LABEL "HOST_LABEL"
#define PLUGINSD_KEYWORD_HOST "HOST"
#define PLUGINSD_KEYWORD_DYNCFG_ENABLE "DYNCFG_ENABLE"
#define PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE "DYNCFG_REGISTER_MODULE"
#define PLUGINSD_KEYWORD_REPORT_JOB_STATUS "REPORT_JOB_STATUS"
#define PLUGINSD_KEYWORD_EXIT "EXIT"
#define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
@ -80,6 +85,9 @@ struct plugind {
time_t started_t;
const DICTIONARY_ITEM *cfg_dict_item;
struct configurable_plugin *configuration;
struct plugind *prev;
struct plugind *next;
};

View file

@ -726,6 +726,7 @@ struct inflight_function {
usec_t timeout_ut;
usec_t started_ut;
usec_t sent_ut;
const char *payload;
};
static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void *func, void *parser_ptr) {
@ -737,7 +738,8 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
pf->code = HTTP_RESP_GATEWAY_TIMEOUT;
char buffer[2048 + 1];
snprintfz(buffer, 2048, "FUNCTION %s %d \"%s\"\n",
snprintfz(buffer, 2048, "%s %s %d \"%s\"\n",
pf->payload ? "FUNCTION_PAYLOAD" : "FUNCTION",
dictionary_acquired_item_name(item),
pf->timeout,
string2str(pf->function));
@ -757,6 +759,25 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
string2str(pf->function), dictionary_acquired_item_name(item), ret,
pf->sent_ut - pf->started_ut);
}
if (!pf->payload)
return;
// send the payload to the plugin
ret = send_to_plugin(pf->payload, parser);
if(ret < 0) {
netdata_log_error("FUNCTION_PAYLOAD: failed to send function to plugin, error %d", ret);
rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_BACKEND_FETCH_FAILED);
}
else {
internal_error(LOG_FUNCTIONS,
"FUNCTION_PAYLOAD '%s' with transaction '%s' sent to collector (%d bytes, in %llu usec)",
string2str(pf->function), dictionary_acquired_item_name(item), ret,
pf->sent_ut - pf->started_ut);
}
send_to_plugin("\nFUNCTION_PAYLOAD_END\n", parser);
}
static bool inflight_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func __maybe_unused, void *new_func, void *parser_ptr __maybe_unused) {
@ -827,6 +848,7 @@ static int pluginsd_execute_function_callback(BUFFER *destination_wb, int timeou
.function = string_strdupz(function),
.callback = callback,
.callback_data = callback_data,
.payload = NULL
};
uuid_t uuid;
@ -1834,6 +1856,264 @@ static inline PARSER_RC pluginsd_exit(char **words __maybe_unused, size_t num_wo
return PARSER_RC_STOP;
}
struct mutex_cond {
pthread_mutex_t lock;
pthread_cond_t cond;
int rc;
};
static void virt_fnc_got_data_cb(BUFFER *wb, int code, void *callback_data)
{
struct mutex_cond *ctx = callback_data;
pthread_mutex_lock(&ctx->lock);
ctx->rc = code;
pthread_cond_broadcast(&ctx->cond);
pthread_mutex_unlock(&ctx->lock);
}
#define VIRT_FNC_TIMEOUT 1
dyncfg_config_t call_virtual_function_blocking(PARSER *parser, const char *name, int *rc, const char *payload) {
usec_t now = now_realtime_usec();
BUFFER *wb = buffer_create(4096, NULL);
struct mutex_cond cond = {
.lock = PTHREAD_MUTEX_INITIALIZER,
.cond = PTHREAD_COND_INITIALIZER
};
struct inflight_function tmp = {
.started_ut = now,
.timeout_ut = now + VIRT_FNC_TIMEOUT + USEC_PER_SEC,
.destination_wb = wb,
.timeout = VIRT_FNC_TIMEOUT,
.function = string_strdupz(name),
.callback = virt_fnc_got_data_cb,
.callback_data = &cond,
.payload = payload,
};
uuid_t uuid;
uuid_generate_time(uuid);
char key[UUID_STR_LEN];
uuid_unparse_lower(uuid, key);
dictionary_write_lock(parser->inflight.functions);
// if there is any error, our dictionary callbacks will call the caller callback to notify
// the caller about the error - no need for error handling here.
dictionary_set(parser->inflight.functions, key, &tmp, sizeof(struct inflight_function));
if(!parser->inflight.smaller_timeout || tmp.timeout_ut < parser->inflight.smaller_timeout)
parser->inflight.smaller_timeout = tmp.timeout_ut;
// garbage collect stale inflight functions
if(parser->inflight.smaller_timeout < now)
inflight_functions_garbage_collect(parser, now);
dictionary_write_unlock(parser->inflight.functions);
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
tp.tv_sec += (time_t)VIRT_FNC_TIMEOUT;
pthread_mutex_lock(&cond.lock);
int ret = pthread_cond_timedwait(&cond.cond, &cond.lock, &tp);
if (ret == ETIMEDOUT)
netdata_log_error("PLUGINSD: DYNCFG virtual function %s timed out", name);
pthread_mutex_unlock(&cond.lock);
dyncfg_config_t cfg;
cfg.data = strdupz(buffer_tostring(wb));
cfg.data_size = buffer_strlen(wb);
if (rc != NULL)
*rc = cond.rc;
buffer_free(wb);
return cfg;
}
static dyncfg_config_t get_plugin_config_cb(void *usr_ctx)
{
PARSER *parser = usr_ctx;
return call_virtual_function_blocking(parser, "get_plugin_config", NULL, NULL);
}
static dyncfg_config_t get_plugin_config_schema_cb(void *usr_ctx)
{
PARSER *parser = usr_ctx;
return call_virtual_function_blocking(parser, "get_plugin_config_schema", NULL, NULL);
}
static dyncfg_config_t get_module_config_cb(void *usr_ctx, const char *module_name)
{
PARSER *parser = usr_ctx;
char buf[1024];
snprintfz(buf, sizeof(buf), "get_module_config %s", module_name);
return call_virtual_function_blocking(parser, buf, NULL, NULL);
}
static dyncfg_config_t get_module_config_schema_cb(void *usr_ctx, const char *module_name)
{
PARSER *parser = usr_ctx;
char buf[1024];
snprintfz(buf, sizeof(buf), "get_module_config_schema %s", module_name);
return call_virtual_function_blocking(parser, buf, NULL, NULL);
}
static dyncfg_config_t get_job_config_schema_cb(void *usr_ctx, const char *module_name)
{
PARSER *parser = usr_ctx;
char buf[1024];
snprintfz(buf, sizeof(buf), "get_job_config_schema %s", module_name);
return call_virtual_function_blocking(parser, buf, NULL, NULL);
}
static dyncfg_config_t get_job_config_cb(void *usr_ctx, const char *module_name, const char* job_name)
{
PARSER *parser = usr_ctx;
char buf[1024];
snprintfz(buf, sizeof(buf), "get_job_config %s %s", module_name, job_name);
return call_virtual_function_blocking(parser, buf, NULL, NULL);
}
enum set_config_result set_plugin_config_cb(void *usr_ctx, dyncfg_config_t *cfg)
{
PARSER *parser = usr_ctx;
int rc;
call_virtual_function_blocking(parser, "set_plugin_config", &rc, cfg->data);
if(rc != 1)
return SET_CONFIG_REJECTED;
return SET_CONFIG_ACCEPTED;
}
enum set_config_result set_module_config_cb(void *usr_ctx, const char *module_name, dyncfg_config_t *cfg)
{
PARSER *parser = usr_ctx;
int rc;
char buf[1024];
snprintfz(buf, sizeof(buf), "set_module_config %s", module_name);
call_virtual_function_blocking(parser, buf, &rc, cfg->data);
if(rc != 1)
return SET_CONFIG_REJECTED;
return SET_CONFIG_ACCEPTED;
}
enum set_config_result set_job_config_cb(void *usr_ctx, const char *module_name, const char *job_name, dyncfg_config_t *cfg)
{
PARSER *parser = usr_ctx;
int rc;
char buf[1024];
snprintfz(buf, sizeof(buf), "set_job_config %s %s", module_name, job_name);
call_virtual_function_blocking(parser, buf, &rc, cfg->data);
if(rc != 1)
return SET_CONFIG_REJECTED;
return SET_CONFIG_ACCEPTED;
}
enum set_config_result delete_job_cb(void *usr_ctx, const char *module_name, const char *job_name)
{
PARSER *parser = usr_ctx;
int rc;
char buf[1024];
snprintfz(buf, sizeof(buf), "delete_job %s %s", module_name, job_name);
call_virtual_function_blocking(parser, buf, &rc, NULL);
if(rc != 1)
return SET_CONFIG_REJECTED;
return SET_CONFIG_ACCEPTED;
}
static inline PARSER_RC pluginsd_register_plugin(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser __maybe_unused) {
netdata_log_info("PLUGINSD: DYNCFG_ENABLE");
if (unlikely (num_words != 2))
return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_ENABLE, "missing name parameter");
struct configurable_plugin *cfg = callocz(1, sizeof(struct configurable_plugin));
cfg->name = strdupz(words[1]);
cfg->set_config_cb = set_plugin_config_cb;
cfg->get_config_cb = get_plugin_config_cb;
cfg->get_config_schema_cb = get_plugin_config_schema_cb;
cfg->cb_usr_ctx = parser;
parser->user.cd->cfg_dict_item = register_plugin(cfg);
if (unlikely(parser->user.cd->cfg_dict_item == NULL)) {
freez(cfg->name);
freez(cfg);
return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_ENABLE, "error registering plugin");
}
parser->user.cd->configuration = cfg;
return PARSER_RC_OK;
}
static inline PARSER_RC pluginsd_register_module(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser __maybe_unused) {
netdata_log_info("PLUGINSD: DYNCFG_REG_MODULE");
struct configurable_plugin *plug_cfg = parser->user.cd->configuration;
if (unlikely(plug_cfg == NULL))
return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE, "you have to enable dynamic configuration first using " PLUGINSD_KEYWORD_DYNCFG_ENABLE);
if (unlikely(num_words != 3))
return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE, "expected 2 parameters module_name followed by module_type");
struct module *mod = callocz(1, sizeof(struct module));
mod->type = str2_module_type(words[2]);
if (unlikely(mod->type == MOD_TYPE_UNKNOWN)) {
freez(mod);
return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE, "unknown module type (allowed: job_array, single)");
}
mod->name = strdupz(words[1]);
mod->set_config_cb = set_module_config_cb;
mod->get_config_cb = get_module_config_cb;
mod->get_config_schema_cb = get_module_config_schema_cb;
mod->config_cb_usr_ctx = parser;
mod->get_job_config_cb = get_job_config_cb;
mod->get_job_config_schema_cb = get_job_config_schema_cb;
mod->set_job_config_cb = set_job_config_cb;
mod->delete_job_cb = delete_job_cb;
mod->job_config_cb_usr_ctx = parser;
register_module(plug_cfg, mod);
return PARSER_RC_OK;
}
// job_status <module_name> <job_name> <status_code> <state> <message>
static inline PARSER_RC pluginsd_job_status(char **words, size_t num_words, PARSER *parser)
{
if (unlikely(num_words != 6 && num_words != 5))
return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_REPORT_JOB_STATUS, "expected 4 or 5 parameters: module_name, job_name, status_code, state, [optional: message]");
int state = atoi(words[4]);
enum job_status job_status = str2job_state(words[3]);
if (unlikely(job_status == JOB_STATUS_UNKNOWN))
return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_REPORT_JOB_STATUS, "unknown job state");
char *message = NULL;
if (num_words == 6)
message = strdupz(words[5]);
report_job_status(parser->user.cd->configuration, words[1], words[2], job_status, state, message);
return PARSER_RC_OK;
}
static inline PARSER_RC streaming_claimed_id(char **words, size_t num_words, PARSER *parser)
{
const char *host_uuid_str = get_word(words, num_words, 1);
@ -2138,6 +2418,12 @@ PARSER_RC parser_execute(PARSER *parser, PARSER_KEYWORD *keyword, char **words,
case 99:
return pluginsd_exit(words, num_words, parser);
case 101:
return pluginsd_register_plugin(words, num_words, parser);
case 102:
return pluginsd_register_module(words, num_words, parser);
default:
fatal("Unknown keyword '%s' with id %zu", keyword->keyword, keyword->id);
}
@ -2158,6 +2444,11 @@ void parser_destroy(PARSER *parser) {
if (unlikely(!parser))
return;
if (parser->user.cd != NULL && parser->user.cd->configuration != NULL) {
unregister_plugin(parser->user.cd->cfg_dict_item);
parser->user.cd->configuration = NULL;
}
dictionary_destroy(parser->inflight.functions);
freez(parser);
}

View file

@ -2073,6 +2073,8 @@ int main(int argc, char **argv) {
signals_block();
signals_init(); // setup the signals we want to use
dyn_conf_init();
// --------------------------------------------------------------------
// check which threads are enabled and initialize them

View file

@ -195,6 +195,15 @@ const struct netdata_static_thread static_threads_common[] = {
.init_routine = NULL,
.start_routine = profile_main
},
{
.name = "DYNCFG",
.config_section = NULL,
.config_name = NULL,
.enabled = 1,
.thread = NULL,
.init_routine = NULL,
.start_routine = dyncfg_main
},
// terminator
{

View file

@ -0,0 +1,906 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "dyn_conf.h"
#define DYN_CONF_PATH_MAX (4096)
#define DYN_CONF_DIR VARLIB_DIR "/etc"
#define DYN_CONF_JOB_SCHEMA "job_schema"
#define DYN_CONF_SCHEMA "schema"
#define DYN_CONF_MODULE_LIST "modules"
#define DYN_CONF_JOB_LIST "jobs"
#define DYN_CONF_CFG_EXT ".cfg"
DICTIONARY *plugins_dict = NULL;
struct deferred_cfg_send {
char *plugin_name;
char *module_name;
char *job_name;
struct deferred_cfg_send *next;
};
struct deferred_cfg_send *deferred_configs = NULL;
pthread_mutex_t deferred_configs_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t deferred_configs_cond = PTHREAD_COND_INITIALIZER;
static void deferred_config_push_back(const char *plugin_name, const char *module_name, const char *job_name)
{
struct deferred_cfg_send *deferred = callocz(1, sizeof(struct deferred_cfg_send));
deferred->plugin_name = strdupz(plugin_name);
if (module_name != NULL) {
deferred->module_name = strdupz(module_name);
if (job_name != NULL)
deferred->job_name = strdupz(job_name);
}
pthread_mutex_lock(&deferred_configs_lock);
struct deferred_cfg_send *last = deferred_configs;
if (last == NULL)
deferred_configs = deferred;
else {
while (last->next != NULL)
last = last->next;
last->next = deferred;
}
pthread_cond_signal(&deferred_configs_cond);
pthread_mutex_unlock(&deferred_configs_lock);
}
static struct deferred_cfg_send *deferred_config_pop()
{
pthread_mutex_lock(&deferred_configs_lock);
while (deferred_configs == NULL)
pthread_cond_wait(&deferred_configs_cond, &deferred_configs_lock);
struct deferred_cfg_send *deferred = deferred_configs;
deferred_configs = deferred_configs->next;
pthread_mutex_unlock(&deferred_configs_lock);
return deferred;
}
static void deferred_config_free(struct deferred_cfg_send *dcs)
{
freez(dcs->plugin_name);
freez(dcs->module_name);
freez(dcs->job_name);
freez(dcs);
}
static int _get_list_of_plugins_json_cb(const DICTIONARY_ITEM *item, void *entry, void *data)
{
UNUSED(item);
json_object *obj = (json_object *)data;
struct configurable_plugin *plugin = (struct configurable_plugin *)entry;
json_object *plugin_name = json_object_new_string(plugin->name);
json_object_array_add(obj, plugin_name);
return 0;
}
json_object *get_list_of_plugins_json()
{
json_object *obj = json_object_new_array();
dictionary_walkthrough_read(plugins_dict, _get_list_of_plugins_json_cb, obj);
return obj;
}
static int _get_list_of_modules_json_cb(const DICTIONARY_ITEM *item, void *entry, void *data)
{
UNUSED(item);
json_object *obj = (json_object *)data;
struct module *module = (struct module *)entry;
json_object *json_module = json_object_new_object();
json_object *json_item = json_object_new_string(module->name);
json_object_object_add(json_module, "name", json_item);
const char *module_type;
switch (module->type) {
case MOD_TYPE_SINGLE:
module_type = "single";
break;
case MOD_TYPE_ARRAY:
module_type = "job_array";
break;
default:
module_type = "unknown";
break;
}
json_item = json_object_new_string(module_type);
json_object_object_add(json_module, "type", json_item);
json_object_array_add(obj, json_module);
return 0;
}
json_object *get_list_of_modules_json(struct configurable_plugin *plugin)
{
json_object *obj = json_object_new_array();
pthread_mutex_lock(&plugin->lock);
dictionary_walkthrough_read(plugin->modules, _get_list_of_modules_json_cb, obj);
pthread_mutex_unlock(&plugin->lock);
return obj;
}
const char *job_status2str(enum job_status status)
{
switch (status) {
case JOB_STATUS_UNKNOWN:
return "unknown";
case JOB_STATUS_STOPPED:
return "stopped";
case JOB_STATUS_RUNNING:
return "running";
case JOB_STATUS_ERROR:
return "error";
default:
return "unknown";
}
}
static int _get_list_of_jobs_json_cb(const DICTIONARY_ITEM *item, void *entry, void *data)
{
UNUSED(item);
json_object *obj = (json_object *)data;
struct job *job = (struct job *)entry;
json_object *json_job = json_object_new_object();
json_object *json_item = json_object_new_string(job->name);
json_object_object_add(json_job, "name", json_item);
json_item = json_object_new_string(job_status2str(job->status));
json_object_object_add(json_job, "state", json_item);
int64_t last_state_update_s = job->last_state_update / USEC_PER_SEC;
int64_t last_state_update_us = job->last_state_update % USEC_PER_SEC;
json_item = json_object_new_int64(last_state_update_s);
json_object_object_add(json_job, "last_state_update_s", json_item);
json_item = json_object_new_int64(last_state_update_us);
json_object_object_add(json_job, "last_state_update_us", json_item);
json_object_array_add(obj, json_job);
return 0;
}
json_object *get_list_of_jobs_json(struct module *module)
{
json_object *obj = json_object_new_array();
pthread_mutex_lock(&module->lock);
dictionary_walkthrough_read(module->jobs, _get_list_of_jobs_json_cb, obj);
pthread_mutex_unlock(&module->lock);
return obj;
}
struct job *get_job_by_name(struct module *module, const char *job_name)
{
return dictionary_get(module->jobs, job_name);
}
int remove_job(struct module *module, struct job *job)
{
// as we are going to do unlink here we better make sure we have all to build proper path
if (unlikely(job->name == NULL || module == NULL || module->name == NULL || module->plugin == NULL || module->plugin->name == NULL))
return 0;
enum set_config_result rc = module->delete_job_cb(module->job_config_cb_usr_ctx, module->name, job->name);
if (rc != SET_CONFIG_ACCEPTED) {
error_report("DYNCFG module \"%s\" rejected delete job for \"%s\"", module->name, job->name);
return 0;
}
BUFFER *buffer = buffer_create(DYN_CONF_PATH_MAX, NULL);
buffer_sprintf(buffer, DYN_CONF_DIR "/%s/%s/%s" DYN_CONF_CFG_EXT, module->plugin->name, module->name, job->name);
unlink(buffer_tostring(buffer));
buffer_free(buffer);
return dictionary_del(module->jobs, job->name);
}
struct module *get_module_by_name(struct configurable_plugin *plugin, const char *module_name)
{
return dictionary_get(plugin->modules, module_name);
}
inline struct configurable_plugin *get_plugin_by_name(const char *name)
{
return dictionary_get(plugins_dict, name);
}
static int store_config(const char *module_name, const char *submodule_name, const char *cfg_idx, dyncfg_config_t cfg)
{
BUFFER *filename = buffer_create(DYN_CONF_PATH_MAX, NULL);
buffer_sprintf(filename, DYN_CONF_DIR "/%s", module_name);
if (mkdir(buffer_tostring(filename), 0755) == -1) {
if (errno != EEXIST) {
netdata_log_error("DYNCFG store_config: failed to create module directory %s", buffer_tostring(filename));
buffer_free(filename);
return 1;
}
}
if (submodule_name != NULL) {
buffer_sprintf(filename, "/%s", submodule_name);
if (mkdir(buffer_tostring(filename), 0755) == -1) {
if (errno != EEXIST) {
netdata_log_error("DYNCFG store_config: failed to create submodule directory %s", buffer_tostring(filename));
buffer_free(filename);
return 1;
}
}
}
if (cfg_idx != NULL)
buffer_sprintf(filename, "/%s", cfg_idx);
buffer_strcat(filename, DYN_CONF_CFG_EXT);
error_report("DYNCFG store_config: %s", buffer_tostring(filename));
//write to file
FILE *f = fopen(buffer_tostring(filename), "w");
if (f == NULL) {
error_report("DYNCFG store_config: failed to open %s for writing", buffer_tostring(filename));
buffer_free(filename);
return 1;
}
fwrite(cfg.data, cfg.data_size, 1, f);
fclose(f);
buffer_free(filename);
return 0;
}
dyncfg_config_t load_config(const char *plugin_name, const char *module_name, const char *job_id)
{
BUFFER *filename = buffer_create(DYN_CONF_PATH_MAX, NULL);
buffer_sprintf(filename, DYN_CONF_DIR "/%s", plugin_name);
if (module_name != NULL)
buffer_sprintf(filename, "/%s", module_name);
if (job_id != NULL)
buffer_sprintf(filename, "/%s", job_id);
buffer_strcat(filename, DYN_CONF_CFG_EXT);
dyncfg_config_t config;
long bytes;
config.data = read_by_filename(buffer_tostring(filename), &bytes);
if (config.data == NULL)
error_report("DYNCFG load_config: failed to load config from %s", buffer_tostring(filename));
config.data_size = bytes;
buffer_free(filename);
return config;
}
char *set_plugin_config(struct configurable_plugin *plugin, dyncfg_config_t cfg)
{
enum set_config_result rc = plugin->set_config_cb(plugin->cb_usr_ctx, &cfg);
if (rc != SET_CONFIG_ACCEPTED) {
error_report("DYNCFG plugin \"%s\" rejected config", plugin->name);
return "plugin rejected config";
}
if (store_config(plugin->name, NULL, NULL, cfg)) {
error_report("DYNCFG could not store config for module \"%s\"", plugin->name);
return "could not store config on disk";
}
return NULL;
}
static char *set_module_config(struct module *mod, dyncfg_config_t cfg)
{
struct configurable_plugin *plugin = mod->plugin;
enum set_config_result rc = mod->set_config_cb(mod->config_cb_usr_ctx, mod->name, &cfg);
if (rc != SET_CONFIG_ACCEPTED) {
error_report("DYNCFG module \"%s\" rejected config", plugin->name);
return "module rejected config";
}
if (store_config(plugin->name, mod->name, NULL, cfg)) {
error_report("DYNCFG could not store config for module \"%s\"", mod->name);
return "could not store config on disk";
}
return NULL;
}
struct job *job_new()
{
struct job *job = callocz(1, sizeof(struct job));
job->state = JOB_STATUS_UNKNOWN;
job->last_state_update = now_realtime_usec();
return job;
}
static int set_job_config(struct job *job, dyncfg_config_t cfg)
{
struct module *mod = job->module;
enum set_config_result rt = mod->set_job_config_cb(mod->job_config_cb_usr_ctx, mod->name, job->name, &cfg);
if (rt != SET_CONFIG_ACCEPTED) {
error_report("DYNCFG module \"%s\" rejected config for job \"%s\"", mod->name, job->name);
return 1;
}
if (store_config(mod->plugin->name, mod->name, job->name, cfg)) {
error_report("DYNCFG could not store config for module \"%s\"", mod->name);
return 1;
}
return 0;
}
struct job *add_job(struct module *mod, const char *job_id, dyncfg_config_t cfg)
{
struct job *job = job_new();
job->name = strdupz(job_id);
job->module = mod;
if (set_job_config(job, cfg)) {
freez(job->name);
freez(job);
return NULL;
}
dictionary_set(mod->jobs, job->name, job, sizeof(job));
return job;
}
void module_del_cb(const DICTIONARY_ITEM *item, void *value, void *data)
{
UNUSED(item);
UNUSED(data);
struct module *mod = (struct module *)value;
dictionary_destroy(mod->jobs);
freez(mod->name);
freez(mod);
}
const DICTIONARY_ITEM *register_plugin(struct configurable_plugin *plugin)
{
if (get_plugin_by_name(plugin->name) != NULL) {
error_report("DYNCFG plugin \"%s\" already registered", plugin->name);
return NULL;
}
if (plugin->set_config_cb == NULL) {
error_report("DYNCFG plugin \"%s\" has no set_config_cb", plugin->name);
return NULL;
}
pthread_mutex_init(&plugin->lock, NULL);
plugin->modules = dictionary_create(DICT_OPTION_VALUE_LINK_DONT_CLONE);
dictionary_register_delete_callback(plugin->modules, module_del_cb, NULL);
deferred_config_push_back(plugin->name, NULL, NULL);
dictionary_set(plugins_dict, plugin->name, plugin, sizeof(plugin));
// the plugin keeps the pointer to the dictionary item, so we need to acquire it
return dictionary_get_and_acquire_item(plugins_dict, plugin->name);
}
void unregister_plugin(const DICTIONARY_ITEM *plugin)
{
struct configurable_plugin *plug = dictionary_acquired_item_value(plugin);
dictionary_acquired_item_release(plugins_dict, plugin);
dictionary_del(plugins_dict, plug->name);
}
void job_del_cb(const DICTIONARY_ITEM *item, void *value, void *data)
{
UNUSED(item);
UNUSED(data);
struct job *job = (struct job *)value;
freez(job->reason);
freez(job->name);
freez(job);
}
int register_module(struct configurable_plugin *plugin, struct module *module)
{
if (get_module_by_name(plugin, module->name) != NULL) {
error_report("DYNCFG module \"%s\" already registered", module->name);
return 1;
}
pthread_mutex_init(&module->lock, NULL);
deferred_config_push_back(plugin->name, module->name, NULL);
module->plugin = plugin;
if (module->type == MOD_TYPE_ARRAY) {
module->jobs = dictionary_create(DICT_OPTION_VALUE_LINK_DONT_CLONE);
dictionary_register_delete_callback(module->jobs, job_del_cb, NULL);
// load all jobs from disk
BUFFER *path = buffer_create(DYN_CONF_PATH_MAX, NULL);
buffer_sprintf(path, "%s/%s/%s", DYN_CONF_DIR, plugin->name, module->name);
DIR *dir = opendir(buffer_tostring(path));
if (dir != NULL) {
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == '.')
continue;
if (ent->d_type != DT_REG)
continue;
size_t len = strnlen(ent->d_name, NAME_MAX);
if (len <= strlen(DYN_CONF_CFG_EXT))
continue;
if (strcmp(ent->d_name + len - strlen(DYN_CONF_CFG_EXT), DYN_CONF_CFG_EXT) != 0)
continue;
ent->d_name[len - strlen(DYN_CONF_CFG_EXT)] = '\0';
struct job *job = job_new();
job->name = strdupz(ent->d_name);
job->module = module;
dictionary_set(module->jobs, job->name, job, sizeof(job));
deferred_config_push_back(plugin->name, module->name, job->name);
}
closedir(dir);
}
buffer_free(path);
}
dictionary_set(plugin->modules, module->name, module, sizeof(module));
return 0;
}
void handle_dyncfg_root(struct uni_http_response *resp, int method)
{
if (method != HTTP_METHOD_GET) {
resp->content = "method not allowed";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
return;
}
json_object *obj = get_list_of_plugins_json();
json_object *wrapper = json_object_new_object();
json_object_object_add(wrapper, "configurable_plugins", obj);
resp->content = strdupz(json_object_to_json_string_ext(wrapper, JSON_C_TO_STRING_PRETTY));
json_object_put(wrapper);
resp->status = HTTP_RESP_OK;
resp->content_type = CT_APPLICATION_JSON;
resp->content_free = freez;
resp->content_length = strlen(resp->content);
}
void handle_plugin_root(struct uni_http_response *resp, int method, struct configurable_plugin *plugin, void *post_payload, size_t post_payload_size)
{
switch(method) {
case HTTP_METHOD_GET:
{
dyncfg_config_t cfg = plugin->get_config_cb(plugin->cb_usr_ctx);
resp->content = mallocz(cfg.data_size);
memcpy(resp->content, cfg.data, cfg.data_size);
resp->status = HTTP_RESP_OK;
resp->content_free = free;
resp->content_length = cfg.data_size;
return;
}
case HTTP_METHOD_PUT:
{
char *response;
if (post_payload == NULL) {
resp->content = "no payload";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_BAD_REQUEST;
return;
}
dyncfg_config_t cont = {
.data = post_payload,
.data_size = post_payload_size
};
response = set_plugin_config(plugin, cont);
if (response == NULL) {
resp->status = HTTP_RESP_OK;
resp->content = "OK";
resp->content_length = strlen(resp->content);
} else {
resp->status = HTTP_RESP_BAD_REQUEST;
resp->content = response;
resp->content_length = strlen(resp->content);
}
return;
}
default:
resp->content = "method not allowed";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
return;
}
}
void handle_module_root(struct uni_http_response *resp, int method, struct configurable_plugin *plugin, const char *module, void *post_payload, size_t post_payload_size)
{
if (strncmp(module, DYN_CONF_SCHEMA, strlen(DYN_CONF_SCHEMA)) == 0) {
dyncfg_config_t cfg = plugin->get_config_schema_cb(plugin->cb_usr_ctx);
resp->content = mallocz(cfg.data_size);
memcpy(resp->content, cfg.data, cfg.data_size);
resp->status = HTTP_RESP_OK;
resp->content_free = freez;
resp->content_length = cfg.data_size;
return;
}
if (strncmp(module, DYN_CONF_MODULE_LIST, strlen(DYN_CONF_MODULE_LIST)) == 0) {
if (method != HTTP_METHOD_GET) {
resp->content = "method not allowed (only GET)";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
return;
}
json_object *obj = get_list_of_modules_json(plugin);
json_object *wrapper = json_object_new_object();
json_object_object_add(wrapper, "modules", obj);
resp->content = strdupz(json_object_to_json_string_ext(wrapper, JSON_C_TO_STRING_PRETTY));
json_object_put(wrapper);
resp->status = HTTP_RESP_OK;
resp->content_type = CT_APPLICATION_JSON;
resp->content_free = freez;
resp->content_length = strlen(resp->content);
return;
}
struct module *mod = get_module_by_name(plugin, module);
if (mod == NULL) {
resp->content = "module not found";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_NOT_FOUND;
return;
}
if (method == HTTP_METHOD_GET) {
dyncfg_config_t cfg = mod->get_config_cb(mod->config_cb_usr_ctx, mod->name);
resp->content = mallocz(cfg.data_size);
memcpy(resp->content, cfg.data, cfg.data_size);
resp->status = HTTP_RESP_OK;
resp->content_free = free;
resp->content_length = cfg.data_size;
return;
} else if (method == HTTP_METHOD_PUT) {
char *response;
if (post_payload == NULL) {
resp->content = "no payload";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_BAD_REQUEST;
return;
}
dyncfg_config_t cont = {
.data = post_payload,
.data_size = post_payload_size
};
response = set_module_config(mod, cont);
if (response == NULL) {
resp->status = HTTP_RESP_OK;
resp->content = "OK";
resp->content_length = strlen(resp->content);
} else {
resp->status = HTTP_RESP_BAD_REQUEST;
resp->content = response;
resp->content_length = strlen(resp->content);
}
return;
}
resp->content = "method not allowed";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
}
static inline void _handle_job_root(struct uni_http_response *resp, int method, struct module *mod, const char *job_id, void *post_payload, size_t post_payload_size, struct job *job)
{
if (method == HTTP_METHOD_POST) {
if (job != NULL) {
resp->content = "can't POST, job already exists (use PUT to update?)";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_BAD_REQUEST;
return;
}
if (post_payload == NULL) {
resp->content = "no payload";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_BAD_REQUEST;
return;
}
dyncfg_config_t cont = {
.data = post_payload,
.data_size = post_payload_size
};
job = add_job(mod, job_id, cont);
if (job == NULL) {
resp->content = "failed to add job";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_INTERNAL_SERVER_ERROR;
return;
}
resp->status = HTTP_RESP_OK;
resp->content = "OK";
resp->content_length = strlen(resp->content);
return;
}
if (job == NULL) {
resp->content = "job not found";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_NOT_FOUND;
return;
}
switch (method) {
case HTTP_METHOD_GET:
{
dyncfg_config_t cfg = mod->get_job_config_cb(mod->job_config_cb_usr_ctx, mod->name, job->name);
resp->content = mallocz(cfg.data_size);
memcpy(resp->content, cfg.data, cfg.data_size);
resp->status = HTTP_RESP_OK;
resp->content_free = freez;
resp->content_length = cfg.data_size;
return;
}
case HTTP_METHOD_PUT:
{
if (post_payload == NULL) {
resp->content = "missing payload";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_BAD_REQUEST;
return;
}
dyncfg_config_t cont = {
.data = post_payload,
.data_size = post_payload_size
};
if(set_job_config(job, cont)) {
resp->status = HTTP_RESP_BAD_REQUEST;
resp->content = "failed to set job config";
resp->content_length = strlen(resp->content);
return;
}
resp->status = HTTP_RESP_OK;
resp->content = "OK";
resp->content_length = strlen(resp->content);
return;
}
case HTTP_METHOD_DELETE:
{
if (!remove_job(mod, job)) {
resp->content = "failed to remove job";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_INTERNAL_SERVER_ERROR;
return;
}
resp->status = HTTP_RESP_OK;
resp->content = "OK";
resp->content_length = strlen(resp->content);
return;
}
default:
resp->content = "method not allowed (only GET, PUT, DELETE)";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
return;
}
}
void handle_job_root(struct uni_http_response *resp, int method, struct module *mod, const char *job_id, void *post_payload, size_t post_payload_size)
{
if (strncmp(job_id, DYN_CONF_SCHEMA, strlen(DYN_CONF_SCHEMA)) == 0) {
dyncfg_config_t cfg = mod->get_config_schema_cb(mod->config_cb_usr_ctx, mod->name);
resp->content = mallocz(cfg.data_size);
memcpy(resp->content, cfg.data, cfg.data_size);
resp->status = HTTP_RESP_OK;
resp->content_free = freez;
resp->content_length = cfg.data_size;
return;
}
if (strncmp(job_id, DYN_CONF_JOB_SCHEMA, strlen(DYN_CONF_JOB_SCHEMA)) == 0) {
dyncfg_config_t cfg = mod->get_job_config_schema_cb(mod->job_config_cb_usr_ctx, mod->name);
resp->content = mallocz(cfg.data_size);
memcpy(resp->content, cfg.data, cfg.data_size);
resp->status = HTTP_RESP_OK;
resp->content_free = freez;
resp->content_length = cfg.data_size;
return;
}
if (strncmp(job_id, DYN_CONF_JOB_LIST, strlen(DYN_CONF_JOB_LIST)) == 0) {
if (mod->type != MOD_TYPE_ARRAY) {
resp->content = "module type is not job_array (can't get the list of jobs)";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_NOT_FOUND;
return;
}
if (method != HTTP_METHOD_GET) {
resp->content = "method not allowed (only GET)";
resp->content_length = strlen(resp->content);
resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
return;
}
json_object *obj = get_list_of_jobs_json(mod);
json_object *wrapper = json_object_new_object();
json_object_object_add(wrapper, "jobs", obj);
resp->content = strdupz(json_object_to_json_string_ext(wrapper, JSON_C_TO_STRING_PRETTY));
json_object_put(wrapper);
resp->status = HTTP_RESP_OK;
resp->content_type = CT_APPLICATION_JSON;
resp->content_free = freez;
resp->content_length = strlen(resp->content);
return;
}
const DICTIONARY_ITEM *job_item = dictionary_get_and_acquire_item(mod->jobs, job_id);
struct job *job = dictionary_acquired_item_value(job_item);
_handle_job_root(resp, method, mod, job_id, post_payload, post_payload_size, job);
dictionary_acquired_item_release(mod->jobs, job_item);
}
struct uni_http_response dyn_conf_process_http_request(int method, const char *plugin, const char *module, const char *job_id, void *post_payload, size_t post_payload_size)
{
struct uni_http_response resp = {
.status = HTTP_RESP_INTERNAL_SERVER_ERROR,
.content_type = CT_TEXT_PLAIN,
.content = HTTP_RESP_INTERNAL_SERVER_ERROR_STR,
.content_free = NULL,
.content_length = 0
};
if (plugin == NULL) {
handle_dyncfg_root(&resp, method);
return resp;
}
const DICTIONARY_ITEM *plugin_item = dictionary_get_and_acquire_item(plugins_dict, plugin);
if (plugin_item == NULL) {
resp.content = "plugin not found";
resp.content_length = strlen(resp.content);
resp.status = HTTP_RESP_NOT_FOUND;
return resp;
}
struct configurable_plugin *plug = dictionary_acquired_item_value(plugin_item);
if (module == NULL) {
handle_plugin_root(&resp, method, plug, post_payload, post_payload_size);
goto EXIT_PLUGIN;
}
if (job_id == NULL) {
handle_module_root(&resp, method, plug, module, post_payload, post_payload_size);
goto EXIT_PLUGIN;
}
// for modules we do not do get_and_acquire as modules are never removed (only together with the plugin)
struct module *mod = get_module_by_name(plug, module);
if (mod == NULL) {
resp.content = "module not found";
resp.content_length = strlen(resp.content);
resp.status = HTTP_RESP_NOT_FOUND;
goto EXIT_PLUGIN;
}
if (mod->type != MOD_TYPE_ARRAY) {
resp.content = "module is not array";
resp.content_length = strlen(resp.content);
resp.status = HTTP_RESP_NOT_FOUND;
goto EXIT_PLUGIN;
}
handle_job_root(&resp, method, mod, job_id, post_payload, post_payload_size);
EXIT_PLUGIN:
dictionary_acquired_item_release(plugins_dict, plugin_item);
return resp;
}
void plugin_del_cb(const DICTIONARY_ITEM *item, void *value, void *data)
{
UNUSED(item);
UNUSED(data);
struct configurable_plugin *plugin = (struct configurable_plugin *)value;
dictionary_destroy(plugin->modules);
freez(plugin->name);
freez(plugin);
}
void report_job_status(struct configurable_plugin *plugin, const char *module_name, const char *job_name, enum job_status status, int status_code, char *reason)
{
const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(plugins_dict, plugin->name);
if (item == NULL) {
netdata_log_error("plugin %s not found", plugin->name);
return;
}
struct configurable_plugin *plug = dictionary_acquired_item_value(item);
struct module *mod = get_module_by_name(plug, module_name);
if (mod == NULL) {
netdata_log_error("module %s not found", module_name);
goto EXIT_PLUGIN;
}
if (mod->type != MOD_TYPE_ARRAY) {
netdata_log_error("module %s is not array", module_name);
goto EXIT_PLUGIN;
}
const DICTIONARY_ITEM *job_item = dictionary_get_and_acquire_item(mod->jobs, job_name);
if (job_item == NULL) {
netdata_log_error("job %s not found", job_name);
goto EXIT_PLUGIN;
}
struct job *job = dictionary_acquired_item_value(job_item);
job->status = status;
job->state = status_code;
if (job->reason != NULL) {
freez(job->reason);
}
job->reason = reason;
job->last_state_update = now_realtime_usec();
dictionary_acquired_item_release(mod->jobs, job_item);
EXIT_PLUGIN:
dictionary_acquired_item_release(plugins_dict, item);
}
int dyn_conf_init(void)
{
if (mkdir(DYN_CONF_DIR, 0755) == -1) {
if (errno != EEXIST) {
netdata_log_error("failed to create directory for dynamic configuration");
return 1;
}
}
plugins_dict = dictionary_create(DICT_OPTION_VALUE_LINK_DONT_CLONE);
dictionary_register_delete_callback(plugins_dict, plugin_del_cb, NULL);
return 0;
}
void *dyncfg_main(void *in)
{
while (!netdata_exit) {
struct deferred_cfg_send *dcs = deferred_config_pop();
const DICTIONARY_ITEM *plugin_item = dictionary_get_and_acquire_item(plugins_dict, dcs->plugin_name);
if (plugin_item == NULL) {
error_report("DYNCFG, plugin %s not found", dcs->plugin_name);
deferred_config_free(dcs);
continue;
}
struct configurable_plugin *plugin = dictionary_acquired_item_value(plugin_item);
if (dcs->module_name == NULL) {
dyncfg_config_t cfg = load_config(dcs->plugin_name, NULL, NULL);
if (cfg.data != NULL) {
plugin->set_config_cb(plugin->cb_usr_ctx, &cfg);
freez(cfg.data);
}
} else if (dcs->job_name == NULL) {
dyncfg_config_t cfg = load_config(dcs->plugin_name, dcs->module_name, NULL);
if (cfg.data != NULL) {
struct module *mod = get_module_by_name(plugin, dcs->module_name);
mod->set_config_cb(mod->config_cb_usr_ctx, mod->name, &cfg);
freez(cfg.data);
}
} else {
dyncfg_config_t cfg = load_config(dcs->plugin_name, dcs->module_name, dcs->job_name);
if (cfg.data != NULL) {
struct module *mod = get_module_by_name(plugin, dcs->module_name);
mod->set_job_config_cb(mod->job_config_cb_usr_ctx, mod->name, dcs->job_name, &cfg);
freez(cfg.data);
}
}
deferred_config_free(dcs);
dictionary_acquired_item_release(plugins_dict, plugin_item);
}
return NULL;
}

View file

@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef DYN_CONF_H
#define DYN_CONF_H
#include "../libnetdata.h"
enum module_type {
MOD_TYPE_UNKNOWN = 0,
MOD_TYPE_ARRAY,
MOD_TYPE_SINGLE
};
static inline enum module_type str2_module_type(const char *type_name)
{
if (strcmp(type_name, "job_array") == 0)
return MOD_TYPE_ARRAY;
else if (strcmp(type_name, "single") == 0)
return MOD_TYPE_SINGLE;
return MOD_TYPE_UNKNOWN;
}
struct dyncfg_config {
void *data;
size_t data_size;
};
typedef struct dyncfg_config dyncfg_config_t;
struct configurable_plugin;
struct module;
enum job_status {
JOB_STATUS_UNKNOWN = 0, // State used until plugin reports first status
JOB_STATUS_STOPPED,
JOB_STATUS_RUNNING,
JOB_STATUS_ERROR
};
inline enum job_status str2job_state(const char *state_name) {
if (strcmp(state_name, "stopped") == 0)
return JOB_STATUS_STOPPED;
else if (strcmp(state_name, "running") == 0)
return JOB_STATUS_RUNNING;
else if (strcmp(state_name, "error") == 0)
return JOB_STATUS_ERROR;
return JOB_STATUS_UNKNOWN;
}
enum set_config_result {
SET_CONFIG_ACCEPTED = 0,
SET_CONFIG_REJECTED,
SET_CONFIG_DEFFER
};
struct job
{
char *name;
//state reported by config
enum job_status status; // reported by plugin, enum as this has to be interpreted by UI
int state; // code reported by plugin which can mean anything plugin wants
char *reason; // reported by plugin, can be NULL (optional)
usec_t last_state_update;
struct module *module;
};
struct module
{
pthread_mutex_t lock;
char *name;
enum module_type type;
struct configurable_plugin *plugin;
// module config
enum set_config_result (*set_config_cb)(void *usr_ctx, const char *module_name, dyncfg_config_t *cfg);
dyncfg_config_t (*get_config_cb)(void *usr_ctx, const char *name);
dyncfg_config_t (*get_config_schema_cb)(void *usr_ctx, const char *name);
void *config_cb_usr_ctx;
DICTIONARY *jobs;
// jobs config
dyncfg_config_t (*get_job_config_cb)(void *usr_ctx, const char *module_name, const char *job_name);
dyncfg_config_t (*get_job_config_schema_cb)(void *usr_ctx, const char *module_name);
enum set_config_result (*set_job_config_cb)(void *usr_ctx, const char *module_name, const char *job_name, dyncfg_config_t *cfg);
enum set_config_result (*delete_job_cb)(void *usr_ctx, const char *module_name, const char *job_name);
void *job_config_cb_usr_ctx;
};
struct configurable_plugin {
pthread_mutex_t lock;
char *name;
DICTIONARY *modules;
const char *schema;
dyncfg_config_t (*get_config_cb)(void *usr_ctx);
dyncfg_config_t (*get_config_schema_cb)(void *usr_ctx);
enum set_config_result (*set_config_cb)(void *usr_ctx, dyncfg_config_t *cfg);
void *cb_usr_ctx; // context for all callbacks (split if needed in future)
};
// API to be used by plugins
const DICTIONARY_ITEM *register_plugin(struct configurable_plugin *plugin);
void unregister_plugin(const DICTIONARY_ITEM *plugin);
int register_module(struct configurable_plugin *plugin, struct module *module);
void report_job_status(struct configurable_plugin *plugin, const char *module_name, const char *job_name, enum job_status status, int status_code, char *reason);
// API to be used by the web server(s)
json_object *get_list_of_plugins_json();
struct configurable_plugin *get_plugin_by_name(const char *name);
json_object *get_list_of_modules_json(struct configurable_plugin *plugin);
struct module *get_module_by_name(struct configurable_plugin *plugin, const char *module_name);
// helper struct to make interface between internal webserver and h2o same
struct uni_http_response {
int status;
char *content;
size_t content_length;
HTTP_CONTENT_TYPE content_type;
void (*content_free)(void *);
};
struct uni_http_response dyn_conf_process_http_request(int method, const char *plugin, const char *module, const char *job_id, void *payload, size_t payload_size);
// API to be used by main netdata process, initialization and destruction etc.
int dyn_conf_init(void);
void *dyncfg_main(void *in);
#endif //DYN_CONF_H

View file

@ -17,15 +17,23 @@
#define HTTP_RESP_UNAUTHORIZED 401
#define HTTP_RESP_FORBIDDEN 403
#define HTTP_RESP_NOT_FOUND 404
#define HTTP_RESP_METHOD_NOT_ALLOWED 405
#define HTTP_RESP_METHOD_NOT_ALLOWED_STR "Method Not Allowed"
#define HTTP_RESP_CONFLICT 409
#define HTTP_RESP_PRECOND_FAIL 412
#define HTTP_RESP_CONTENT_TOO_LONG 413
// HTTP_CODES 5XX Server Errors
#define HTTP_RESP_INTERNAL_SERVER_ERROR 500
#define HTTP_RESP_INTERNAL_SERVER_ERROR_STR "Internal Server Error"
#define HTTP_RESP_BACKEND_FETCH_FAILED 503 // 503 is right
#define HTTP_RESP_SERVICE_UNAVAILABLE 503 // 503 is right
#define HTTP_RESP_GATEWAY_TIMEOUT 504
#define HTTP_RESP_BACKEND_RESPONSE_INVALID 591
#define HTTP_METHOD_GET (1)
#define HTTP_METHOD_POST (2)
#define HTTP_METHOD_PUT (3)
#define HTTP_METHOD_DELETE (4)
#endif /* NETDATA_HTTP_DEFS_H */

View file

@ -1628,7 +1628,7 @@ void recursive_config_double_dir_load(const char *user_path, const char *stock_p
// Returns the number of bytes read from the file if file_size is not NULL.
// The actual buffer has an extra byte set to zero (not included in the count).
char *read_by_filename(char *filename, long *file_size)
char *read_by_filename(const char *filename, long *file_size)
{
FILE *f = fopen(filename, "r");
if (!f)

View file

@ -579,7 +579,7 @@ void recursive_config_double_dir_load(
, void *data
, size_t depth
);
char *read_by_filename(char *filename, long *file_size);
char *read_by_filename(const char *filename, long *file_size);
char *find_and_replace(const char *src, const char *find, const char *replace, const char *where);
/* fix for alpine linux */
@ -837,6 +837,7 @@ extern char *netdata_configured_host_prefix;
#include "yaml.h"
#include "http/http_defs.h"
#include "gorilla/gorilla.h"
#include "dyn_conf/dyn_conf.h"
// BEWARE: this exists in alarm-notify.sh
#define DEFAULT_CLOUD_BASE_URL "https://app.netdata.cloud"

View file

@ -243,7 +243,7 @@ inline bool url_is_request_complete(char *begin, char *end, size_t length, char
if(likely(strncmp(begin, "GET ", 4)) == 0) {
return strstr(end - 4, "\r\n\r\n");
}
else if(unlikely(strncmp(begin, "POST ", 5) == 0)) {
else if(unlikely(strncmp(begin, "POST ", 5) == 0 || strncmp(begin, "PUT ", 4) == 0)) {
char *cl = strstr(begin, "Content-Length: ");
if(!cl) return false;
cl = &cl[16];

View file

@ -40,12 +40,28 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
return HTTP_RESP_BAD_REQUEST;
}
uint32_t hash = simple_hash(url_path_endpoint);
char *api_command = strchr(url_path_endpoint, '/');
if (likely(api_command == NULL)) // only config command supports subpaths for now
api_command = url_path_endpoint;
else {
size_t api_command_len = api_command - url_path_endpoint;
api_command = callocz(1, api_command_len + 1);
memcpy(api_command, url_path_endpoint, api_command_len);
}
uint32_t hash = simple_hash(api_command);
for(int i = 0; api_commands[i].command ; i++) {
if(unlikely(hash == api_commands[i].hash && !strcmp(url_path_endpoint, api_commands[i].command))) {
if(unlikely(!web_client_check_acl_and_bearer(w, api_commands[i].acl)))
return web_client_bearer_required(w);
if(unlikely(hash == api_commands[i].hash && !strcmp(api_command, api_commands[i].command))) {
if(unlikely(!api_commands[i].allow_subpaths && api_command != url_path_endpoint)) {
buffer_flush(w->response.data);
buffer_sprintf(w->response.data, "API command '%s' does not support subpaths.", api_command);
freez(api_command);
return HTTP_RESP_BAD_REQUEST;
}
if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl))
return web_client_permission_denied(w);
char *query_string = (char *)buffer_tostring(w->url_query_string_decoded);
@ -56,6 +72,9 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
}
}
if (api_command != url_path_endpoint)
freez(api_command);
buffer_flush(w->response.data);
buffer_strcat(w->response.data, "Unsupported API command: ");
buffer_strcat_htmlescape(w->response.data, url_path_endpoint);

View file

@ -20,6 +20,7 @@ struct web_api_command {
uint32_t hash;
WEB_CLIENT_ACL acl;
int (*callback)(RRDHOST *host, struct web_client *w, char *url);
unsigned int allow_subpaths;
};
struct web_client;

View file

@ -1463,43 +1463,43 @@ int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struc
#endif
static struct web_api_command api_commands_v1[] = {
{ "info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_info },
{ "data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_data },
{ "chart", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_chart },
{ "charts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_charts },
{ "context", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_context },
{ "contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_contexts },
{ "info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_info, 0 },
{ "data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_data, 0 },
{ "chart", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_chart, 0 },
{ "charts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_charts, 0 },
{ "context", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_context, 0 },
{ "contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_contexts, 0 },
// registry checks the ACL by itself, so we allow everything
{ "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry },
{ "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry, 0 },
// badges can be fetched with both dashboard and badge permissions
{ "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC | WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge },
{ "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC | WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge, 0 },
{ "alarms", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms },
{ "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms_values },
{ "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_log },
{ "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_variables },
{ "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_count },
{ "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_allmetrics },
{ "alarms", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms, 0 },
{ "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms_values, 0 },
{ "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_log, 0 },
{ "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_variables, 0 },
{ "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_count, 0 },
{ "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_allmetrics, 0 },
#if defined(ENABLE_ML)
{ "ml_info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_ml_info },
{ "ml_info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_ml_info, 0 },
// { "ml_models", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_ml_models },
#endif
{"manage/health", 0, WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_mgmt_health },
{ "aclk", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_aclk_state },
{ "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_metric_correlations },
{ "weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_weights },
{"manage/health", 0, WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_mgmt_health, 0 },
{ "aclk", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_aclk_state, 0 },
{ "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_metric_correlations, 0 },
{ "weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_weights, 0 },
{"function", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function },
{"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions },
{"function", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function, 0 },
{"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions, 0 },
{ "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_dbengine_stats },
{ "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_dbengine_stats, 0 },
// terminator
{ NULL, 0, WEB_CLIENT_ACL_NONE, NULL },
{ NULL, 0, WEB_CLIENT_ACL_NONE, NULL, 0 },
};
inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {

View file

@ -644,31 +644,97 @@ static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct
return webrtc_new_connection(w->post_payload, w->response.data);
}
#define CONFIG_API_V2_URL "/api/v2/config"
static int web_client_api_request_v2_config(RRDHOST *host __maybe_unused, struct web_client *w, char *query) {
char *url = strdupz(buffer_tostring(w->url_as_received));
char *url_full = url;
if (strncmp(url, CONFIG_API_V2_URL, strlen(CONFIG_API_V2_URL)) != 0) {
buffer_sprintf(w->response.data, "Invalid URL");
return HTTP_RESP_BAD_REQUEST;
}
url += strlen(CONFIG_API_V2_URL);
char *save_ptr = NULL;
char *plugin = strtok_r(url, "/", &save_ptr);
char *module = strtok_r(NULL, "/", &save_ptr);
char *job_id = strtok_r(NULL, "/", &save_ptr);
char *extra = strtok_r(NULL, "/", &save_ptr);
buffer_flush(w->response.data);
if (extra != NULL) {
buffer_sprintf(w->response.data, "Invalid URL");
freez(url_full);
return HTTP_RESP_BAD_REQUEST;
}
int http_method;
switch (w->mode)
{
case WEB_CLIENT_MODE_GET:
http_method = HTTP_METHOD_GET;
break;
case WEB_CLIENT_MODE_POST:
http_method = HTTP_METHOD_POST;
break;
case WEB_CLIENT_MODE_PUT:
http_method = HTTP_METHOD_PUT;
break;
case WEB_CLIENT_MODE_DELETE:
http_method = HTTP_METHOD_DELETE;
break;
default:
buffer_sprintf(w->response.data, "Invalid HTTP method");
freez(url_full);
return HTTP_RESP_BAD_REQUEST;
}
struct uni_http_response resp = dyn_conf_process_http_request(http_method, plugin, module, job_id, w->post_payload, w->post_payload_size);
if (resp.content[resp.content_length - 1] != '\0') {
char *con = mallocz(resp.content_length + 1);
memcpy(con, resp.content, resp.content_length);
con[resp.content_length] = '\0';
if (resp.content_free)
resp.content_free(resp.content);
resp.content = con;
resp.content_free = freez;
}
buffer_strcat(w->response.data, resp.content);
if (resp.content_free)
resp.content_free(resp.content);
w->response.data->content_type = resp.content_type;
freez(url_full);
return resp.status;
}
static struct web_api_command api_commands_v2[] = {
{"info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_info},
{"info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_info, 0},
{"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data},
{"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights},
{"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data, 0},
{"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights, 0},
{"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts},
{"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes},
{"node_instances", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_node_instances},
{"versions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_versions},
{"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_functions},
{"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q},
{"alerts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts},
{"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts, 0},
{"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes, 0},
{"node_instances", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_node_instances, 0},
{"versions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_versions, 0},
{"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_functions, 0},
{"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q, 0},
{"alerts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts, 0},
{"alert_transitions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_transitions},
{"alert_config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_config},
{"alert_transitions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_transitions, 0},
{"alert_config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_config, 0},
{"claim", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v2_claim},
{"claim", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v2_claim, 0},
{"rtc_offer", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_webrtc},
{"bearer_protection", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_protection},
{"bearer_get_token", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_token},
{"rtc_offer", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_webrtc, 0},
{"bearer_protection", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_protection, 0},
{"bearer_get_token", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_token, 0},
{"config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_config, 1},
// terminator
{NULL, 0, WEB_CLIENT_ACL_NONE, NULL},
{NULL, 0, WEB_CLIENT_ACL_NONE, NULL, 0},
};
inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {

View file

@ -204,7 +204,9 @@ void web_client_request_done(struct web_client *w) {
break;
case WEB_CLIENT_MODE_POST:
case WEB_CLIENT_MODE_PUT:
case WEB_CLIENT_MODE_GET:
case WEB_CLIENT_MODE_DELETE:
mode = "DATA";
break;
@ -1075,6 +1077,14 @@ static inline char *web_client_valid_method(struct web_client *w, char *s) {
s = &s[5];
w->mode = WEB_CLIENT_MODE_POST;
}
else if(!strncmp(s, "PUT ", 4)) {
s = &s[4];
w->mode = WEB_CLIENT_MODE_PUT;
}
else if(!strncmp(s, "DELETE ", 7)) {
s = &s[7];
w->mode = WEB_CLIENT_MODE_DELETE;
}
else if(!strncmp(s, "STREAM ", 7)) {
s = &s[7];
@ -1747,6 +1757,8 @@ void web_client_process_request(struct web_client *w) {
case WEB_CLIENT_MODE_FILECOPY:
case WEB_CLIENT_MODE_POST:
case WEB_CLIENT_MODE_GET:
case WEB_CLIENT_MODE_PUT:
case WEB_CLIENT_MODE_DELETE:
if(unlikely(
!web_client_can_access_dashboard(w) &&
!web_client_can_access_registry(w) &&
@ -1879,6 +1891,8 @@ void web_client_process_request(struct web_client *w) {
case WEB_CLIENT_MODE_POST:
case WEB_CLIENT_MODE_GET:
case WEB_CLIENT_MODE_PUT:
case WEB_CLIENT_MODE_DELETE:
netdata_log_debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%zu bytes) to client.", w->id, w->response.data->len);
break;
@ -2042,7 +2056,7 @@ ssize_t web_client_send_deflate(struct web_client *w)
// ask for FINISH if we have all the input
int flush = Z_SYNC_FLUSH;
if((w->mode == WEB_CLIENT_MODE_GET || w->mode == WEB_CLIENT_MODE_POST)
if((w->mode == WEB_CLIENT_MODE_GET || w->mode == WEB_CLIENT_MODE_POST || w->mode == WEB_CLIENT_MODE_PUT || w->mode == WEB_CLIENT_MODE_DELETE)
|| (w->mode == WEB_CLIENT_MODE_FILECOPY && !web_client_has_wait_receive(w) && w->response.data->len == w->response.rlen)) {
flush = Z_FINISH;
netdata_log_debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id);

View file

@ -18,6 +18,8 @@ typedef enum web_client_mode {
WEB_CLIENT_MODE_FILECOPY = 2,
WEB_CLIENT_MODE_OPTIONS = 3,
WEB_CLIENT_MODE_STREAM = 4,
WEB_CLIENT_MODE_PUT = 5,
WEB_CLIENT_MODE_DELETE = 6,
} WEB_CLIENT_MODE;
typedef enum {