@cryptotaxi247 / netdata-1 / commits / 256f22ff0

Dynamic Config MVP0 (#15486)

* work on dynamic configuration interface which should allow adding/modifying monitoring endpoints during runtime

Timotej S committed Aug 2, 2023 at 13:38 UTC 256f22ff0955cbdccdc55af3efb3667153bd2c66
19 files changed +1631 -157
Makefile.am
+2
@@ -200,6 +200,8 @@ LIBNETDATA_FILES = \
200 libnetdata/worker_utilization/worker_utilization.c \
201 libnetdata/worker_utilization/worker_utilization.h \
202 libnetdata/http/http_defs.h \
203 + libnetdata/dyn_conf/dyn_conf.c \
204 + libnetdata/dyn_conf/dyn_conf.h \
205 $(NULL)
206
207 if ENABLE_PLUGIN_EBPF
collectors/plugins.d/gperf-config.txt
+32 -29
@@ -12,41 +12,44 @@ PARSER_KEYWORD;
12 #
13 # Plugins Only Keywords
14 #
15 -FLUSH, 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1
16 -DISABLE, 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2
17 -EXIT, 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3
18 -HOST, 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4
19 -HOST_DEFINE, 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5
20 -HOST_DEFINE_END, 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6
21 -HOST_LABEL, 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7
15 +FLUSH, 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1
16 +DISABLE, 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2
17 +EXIT, 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3
18 +HOST, 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4
19 +HOST_DEFINE, 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5
20 +HOST_DEFINE_END, 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6
21 +HOST_LABEL, 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7
22 #
23 # Common keywords
24 #
25 -BEGIN, 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8
26 -CHART, 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9
27 -CLABEL, 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10
28 -CLABEL_COMMIT, 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11
29 -DIMENSION, 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12
30 -END, 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13
31 -FUNCTION, 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14
32 -FUNCTION_RESULT_BEGIN, 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15
33 -LABEL, 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16
34 -OVERWRITE, 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17
35 -SET, 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18
36 -VARIABLE, 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19
25 +BEGIN, 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8
26 +CHART, 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9
27 +CLABEL, 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10
28 +CLABEL_COMMIT, 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11
29 +DIMENSION, 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12
30 +END, 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13
31 +FUNCTION, 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14
32 +FUNCTION_RESULT_BEGIN, 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15
33 +LABEL, 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16
34 +OVERWRITE, 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17
35 +SET, 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18
36 +VARIABLE, 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19
37 +DYNCFG_ENABLE, 101, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20
38 +DYNCFG_REGISTER_MODULE, 102, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21
39 +REPORT_JOB_STATUS, 110, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22
40 #
41 # Streaming only keywords
42 #
40 -CLAIMED_ID, 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20
41 -BEGIN2, 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21
42 -SET2, 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22
43 -END2, 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23
43 +CLAIMED_ID, 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23
44 +BEGIN2, 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24
45 +SET2, 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25
46 +END2, 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26
47 #
48 # Streaming Replication keywords
49 #
47 -CHART_DEFINITION_END, 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24
48 -RBEGIN, 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25
49 -RDSTATE, 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26
50 -REND, 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27
51 -RSET, 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28
52 -RSSTATE, 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29
50 +CHART_DEFINITION_END, 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27
51 +RBEGIN, 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28
52 +RDSTATE, 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29
53 +REND, 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 30
54 +RSET, 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 31
55 +RSSTATE, 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 32
collectors/plugins.d/gperf-hashtable.h
+85 -79
@@ -30,12 +30,12 @@
30 #endif
31
32
33 -#define GPERF_PARSER_TOTAL_KEYWORDS 29
33 +#define GPERF_PARSER_TOTAL_KEYWORDS 32
34 #define GPERF_PARSER_MIN_WORD_LENGTH 3
35 -#define GPERF_PARSER_MAX_WORD_LENGTH 21
36 -#define GPERF_PARSER_MIN_HASH_VALUE 4
37 -#define GPERF_PARSER_MAX_HASH_VALUE 36
38 -/* maximum key range = 33, duplicates = 0 */
35 +#define GPERF_PARSER_MAX_WORD_LENGTH 22
36 +#define GPERF_PARSER_MIN_HASH_VALUE 3
37 +#define GPERF_PARSER_MAX_HASH_VALUE 41
38 +/* maximum key range = 39, duplicates = 0 */
39
40 #ifdef __GNUC__
41 __inline
@@ -49,99 +49,105 @@ gperf_keyword_hash_function (register const char *str, register size_t len)
49 {
50 static unsigned char asso_values[] =
51 {
52 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
53 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
54 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
55 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
56 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
57 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
58 - 37, 37, 37, 37, 37, 15, 10, 1, 1, 9,
59 - 4, 37, 0, 20, 37, 37, 9, 37, 14, 0,
60 - 37, 37, 1, 0, 37, 7, 13, 37, 18, 37,
61 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
62 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
63 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
64 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
65 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
66 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
67 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
68 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
69 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
70 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
71 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
72 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
73 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
74 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
75 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
76 - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
77 - 37, 37, 37, 37, 37, 37
52 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
53 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
54 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
55 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
56 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
57 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
58 + 42, 42, 42, 42, 42, 16, 7, 2, 11, 0,
59 + 8, 42, 3, 9, 42, 42, 9, 42, 0, 2,
60 + 42, 42, 1, 3, 42, 7, 17, 42, 27, 2,
61 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
62 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
63 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
64 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
65 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
66 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
67 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
68 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
69 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
70 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
71 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
72 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
73 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
74 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
75 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
76 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
77 + 42, 42, 42, 42, 42, 42
78 };
79 return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]];
80 }
81
82 static PARSER_KEYWORD gperf_keywords[] =
83 {
84 - {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0},
84 + {(char*)0}, {(char*)0}, {(char*)0},
85 +#line 30 "gperf-config.txt"
86 + {"END", 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13},
87 +#line 46 "gperf-config.txt"
88 + {"END2", 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26},
89 +#line 53 "gperf-config.txt"
90 + {"REND", 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 30},
91 +#line 35 "gperf-config.txt"
92 + {"SET", 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18},
93 +#line 45 "gperf-config.txt"
94 + {"SET2", 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25},
95 +#line 54 "gperf-config.txt"
96 + {"RSET", 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 31},
97 #line 18 "gperf-config.txt"
86 - {"HOST", 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4},
87 -#line 51 "gperf-config.txt"
88 - {"RSET", 21, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28},
98 + {"HOST", 71, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 4},
99 #line 26 "gperf-config.txt"
90 - {"CHART", 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9},
91 - {(char*)0},
92 -#line 52 "gperf-config.txt"
93 - {"RSSTATE", 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29},
94 -#line 49 "gperf-config.txt"
95 - {"RDSTATE", 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 26},
100 + {"CHART", 32, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9},
101 +#line 55 "gperf-config.txt"
102 + {"RSSTATE", 24, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 32},
103 +#line 25 "gperf-config.txt"
104 + {"BEGIN", 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8},
105 +#line 44 "gperf-config.txt"
106 + {"BEGIN2", 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24},
107 +#line 51 "gperf-config.txt"
108 + {"RBEGIN", 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 28},
109 #line 21 "gperf-config.txt"
97 - {"HOST_LABEL", 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7},
110 + {"HOST_LABEL", 74, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 7},
111 #line 19 "gperf-config.txt"
99 - {"HOST_DEFINE", 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5},
100 -#line 35 "gperf-config.txt"
101 - {"SET", 11, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18},
102 -#line 42 "gperf-config.txt"
103 - {"SET2", 1, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22},
104 -#line 50 "gperf-config.txt"
105 - {"REND", 25, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27},
106 -#line 20 "gperf-config.txt"
107 - {"HOST_DEFINE_END", 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6},
112 + {"HOST_DEFINE", 72, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 5},
113 #line 27 "gperf-config.txt"
109 - {"CLABEL", 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10},
110 -#line 48 "gperf-config.txt"
111 - {"RBEGIN", 22, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 25},
114 + {"CLABEL", 34, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10},
115 +#line 39 "gperf-config.txt"
116 + {"REPORT_JOB_STATUS", 110, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 22},
117 +#line 52 "gperf-config.txt"
118 + {"RDSTATE", 23, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 29},
119 +#line 20 "gperf-config.txt"
120 + {"HOST_DEFINE_END", 73, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 6},
121 +#line 43 "gperf-config.txt"
122 + {"CLAIMED_ID", 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23},
123 #line 15 "gperf-config.txt"
113 - {"FLUSH", 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1},
124 + {"FLUSH", 97, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 1},
125 #line 31 "gperf-config.txt"
115 - {"FUNCTION", 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14},
116 -#line 40 "gperf-config.txt"
117 - {"CLAIMED_ID", 61, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20},
118 -#line 47 "gperf-config.txt"
119 - {"CHART_DEFINITION_END", 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 24},
120 -#line 34 "gperf-config.txt"
121 - {"OVERWRITE", 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17},
126 + {"FUNCTION", 41, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14},
127 #line 28 "gperf-config.txt"
123 - {"CLABEL_COMMIT", 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11},
124 -#line 25 "gperf-config.txt"
125 - {"BEGIN", 12, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8},
126 -#line 41 "gperf-config.txt"
127 - {"BEGIN2", 2, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21},
128 -#line 30 "gperf-config.txt"
129 - {"END", 13, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13},
130 -#line 43 "gperf-config.txt"
131 - {"END2", 3, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 23},
128 + {"CLABEL_COMMIT", 35, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11},
129 +#line 50 "gperf-config.txt"
130 + {"CHART_DEFINITION_END", 33, PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 27},
131 +#line 37 "gperf-config.txt"
132 + {"DYNCFG_ENABLE", 101, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 20},
133 #line 16 "gperf-config.txt"
133 - {"DISABLE", 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2},
134 -#line 33 "gperf-config.txt"
135 - {"LABEL", 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16},
134 + {"DISABLE", 98, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 2},
135 +#line 34 "gperf-config.txt"
136 + {"OVERWRITE", 52, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17},
137 #line 29 "gperf-config.txt"
137 - {"DIMENSION", 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12},
138 + {"DIMENSION", 31, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12},
139 +#line 33 "gperf-config.txt"
140 + {"LABEL", 51, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16},
141 #line 17 "gperf-config.txt"
139 - {"EXIT", 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3},
140 -#line 32 "gperf-config.txt"
141 - {"FUNCTION_RESULT_BEGIN", 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15},
142 + {"EXIT", 99, PARSER_INIT_PLUGINSD, WORKER_PARSER_FIRST_JOB + 3},
143 {(char*)0}, {(char*)0}, {(char*)0},
144 +#line 38 "gperf-config.txt"
145 + {"DYNCFG_REGISTER_MODULE", 102, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 21},
146 +#line 32 "gperf-config.txt"
147 + {"FUNCTION_RESULT_BEGIN", 42, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15},
148 + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0},
149 #line 36 "gperf-config.txt"
144 - {"VARIABLE", 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19}
150 + {"VARIABLE", 53, PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19}
151 };
152
153 PARSER_KEYWORD *
collectors/plugins.d/plugins_d.h
+8
@@ -43,6 +43,11 @@
43 #define PLUGINSD_KEYWORD_HOST_LABEL "HOST_LABEL"
44 #define PLUGINSD_KEYWORD_HOST "HOST"
45
46 +#define PLUGINSD_KEYWORD_DYNCFG_ENABLE "DYNCFG_ENABLE"
47 +#define PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE "DYNCFG_REGISTER_MODULE"
48 +
49 +#define PLUGINSD_KEYWORD_REPORT_JOB_STATUS "REPORT_JOB_STATUS"
50 +
51 #define PLUGINSD_KEYWORD_EXIT "EXIT"
52
53 #define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
@@ -80,6 +85,9 @@ struct plugind {
85
86 time_t started_t;
87
88 + const DICTIONARY_ITEM *cfg_dict_item;
89 + struct configurable_plugin *configuration;
90 +
91 struct plugind *prev;
92 struct plugind *next;
93 };
collectors/plugins.d/pluginsd_parser.c
+292 -1
@@ -726,6 +726,7 @@ struct inflight_function {
726 usec_t timeout_ut;
727 usec_t started_ut;
728 usec_t sent_ut;
729 + const char *payload;
730 };
731
732 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
738 pf->code = HTTP_RESP_GATEWAY_TIMEOUT;
739
740 char buffer[2048 + 1];
740 - snprintfz(buffer, 2048, "FUNCTION %s %d \"%s\"\n",
741 + snprintfz(buffer, 2048, "%s %s %d \"%s\"\n",
742 + pf->payload ? "FUNCTION_PAYLOAD" : "FUNCTION",
743 dictionary_acquired_item_name(item),
744 pf->timeout,
745 string2str(pf->function));
@@ -757,6 +759,25 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
759 string2str(pf->function), dictionary_acquired_item_name(item), ret,
760 pf->sent_ut - pf->started_ut);
761 }
762 +
763 + if (!pf->payload)
764 + return;
765 +
766 + // send the payload to the plugin
767 + ret = send_to_plugin(pf->payload, parser);
768 +
769 + if(ret < 0) {
770 + netdata_log_error("FUNCTION_PAYLOAD: failed to send function to plugin, error %d", ret);
771 + rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_BACKEND_FETCH_FAILED);
772 + }
773 + else {
774 + internal_error(LOG_FUNCTIONS,
775 + "FUNCTION_PAYLOAD '%s' with transaction '%s' sent to collector (%d bytes, in %llu usec)",
776 + string2str(pf->function), dictionary_acquired_item_name(item), ret,
777 + pf->sent_ut - pf->started_ut);
778 + }
779 +
780 + send_to_plugin("\nFUNCTION_PAYLOAD_END\n", parser);
781 }
782
783 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
848 .function = string_strdupz(function),
849 .callback = callback,
850 .callback_data = callback_data,
851 + .payload = NULL
852 };
853
854 uuid_t uuid;
@@ -1834,6 +1856,264 @@ static inline PARSER_RC pluginsd_exit(char **words __maybe_unused, size_t num_wo
1856 return PARSER_RC_STOP;
1857 }
1858
1859 +struct mutex_cond {
1860 + pthread_mutex_t lock;
1861 + pthread_cond_t cond;
1862 + int rc;
1863 +};
1864 +
1865 +static void virt_fnc_got_data_cb(BUFFER *wb, int code, void *callback_data)
1866 +{
1867 + struct mutex_cond *ctx = callback_data;
1868 + pthread_mutex_lock(&ctx->lock);
1869 + ctx->rc = code;
1870 + pthread_cond_broadcast(&ctx->cond);
1871 + pthread_mutex_unlock(&ctx->lock);
1872 +}
1873 +
1874 +#define VIRT_FNC_TIMEOUT 1
1875 +dyncfg_config_t call_virtual_function_blocking(PARSER *parser, const char *name, int *rc, const char *payload) {
1876 + usec_t now = now_realtime_usec();
1877 + BUFFER *wb = buffer_create(4096, NULL);
1878 +
1879 + struct mutex_cond cond = {
1880 + .lock = PTHREAD_MUTEX_INITIALIZER,
1881 + .cond = PTHREAD_COND_INITIALIZER
1882 + };
1883 +
1884 + struct inflight_function tmp = {
1885 + .started_ut = now,
1886 + .timeout_ut = now + VIRT_FNC_TIMEOUT + USEC_PER_SEC,
1887 + .destination_wb = wb,
1888 + .timeout = VIRT_FNC_TIMEOUT,
1889 + .function = string_strdupz(name),
1890 + .callback = virt_fnc_got_data_cb,
1891 + .callback_data = &cond,
1892 + .payload = payload,
1893 + };
1894 +
1895 + uuid_t uuid;
1896 + uuid_generate_time(uuid);
1897 +
1898 + char key[UUID_STR_LEN];
1899 + uuid_unparse_lower(uuid, key);
1900 +
1901 + dictionary_write_lock(parser->inflight.functions);
1902 +
1903 + // if there is any error, our dictionary callbacks will call the caller callback to notify
1904 + // the caller about the error - no need for error handling here.
1905 + dictionary_set(parser->inflight.functions, key, &tmp, sizeof(struct inflight_function));
1906 +
1907 + if(!parser->inflight.smaller_timeout || tmp.timeout_ut < parser->inflight.smaller_timeout)
1908 + parser->inflight.smaller_timeout = tmp.timeout_ut;
1909 +
1910 + // garbage collect stale inflight functions
1911 + if(parser->inflight.smaller_timeout < now)
1912 + inflight_functions_garbage_collect(parser, now);
1913 +
1914 + dictionary_write_unlock(parser->inflight.functions);
1915 +
1916 + struct timespec tp;
1917 + clock_gettime(CLOCK_REALTIME, &tp);
1918 + tp.tv_sec += (time_t)VIRT_FNC_TIMEOUT;
1919 +
1920 + pthread_mutex_lock(&cond.lock);
1921 +
1922 + int ret = pthread_cond_timedwait(&cond.cond, &cond.lock, &tp);
1923 + if (ret == ETIMEDOUT)
1924 + netdata_log_error("PLUGINSD: DYNCFG virtual function %s timed out", name);
1925 +
1926 + pthread_mutex_unlock(&cond.lock);
1927 +
1928 + dyncfg_config_t cfg;
1929 + cfg.data = strdupz(buffer_tostring(wb));
1930 + cfg.data_size = buffer_strlen(wb);
1931 +
1932 + if (rc != NULL)
1933 + *rc = cond.rc;
1934 +
1935 + buffer_free(wb);
1936 + return cfg;
1937 +}
1938 +
1939 +static dyncfg_config_t get_plugin_config_cb(void *usr_ctx)
1940 +{
1941 + PARSER *parser = usr_ctx;
1942 + return call_virtual_function_blocking(parser, "get_plugin_config", NULL, NULL);
1943 +}
1944 +
1945 +static dyncfg_config_t get_plugin_config_schema_cb(void *usr_ctx)
1946 +{
1947 + PARSER *parser = usr_ctx;
1948 + return call_virtual_function_blocking(parser, "get_plugin_config_schema", NULL, NULL);
1949 +}
1950 +
1951 +static dyncfg_config_t get_module_config_cb(void *usr_ctx, const char *module_name)
1952 +{
1953 + PARSER *parser = usr_ctx;
1954 + char buf[1024];
1955 + snprintfz(buf, sizeof(buf), "get_module_config %s", module_name);
1956 + return call_virtual_function_blocking(parser, buf, NULL, NULL);
1957 +}
1958 +
1959 +static dyncfg_config_t get_module_config_schema_cb(void *usr_ctx, const char *module_name)
1960 +{
1961 + PARSER *parser = usr_ctx;
1962 + char buf[1024];
1963 + snprintfz(buf, sizeof(buf), "get_module_config_schema %s", module_name);
1964 + return call_virtual_function_blocking(parser, buf, NULL, NULL);
1965 +}
1966 +
1967 +static dyncfg_config_t get_job_config_schema_cb(void *usr_ctx, const char *module_name)
1968 +{
1969 + PARSER *parser = usr_ctx;
1970 + char buf[1024];
1971 + snprintfz(buf, sizeof(buf), "get_job_config_schema %s", module_name);
1972 + return call_virtual_function_blocking(parser, buf, NULL, NULL);
1973 +}
1974 +
1975 +static dyncfg_config_t get_job_config_cb(void *usr_ctx, const char *module_name, const char* job_name)
1976 +{
1977 + PARSER *parser = usr_ctx;
1978 + char buf[1024];
1979 + snprintfz(buf, sizeof(buf), "get_job_config %s %s", module_name, job_name);
1980 + return call_virtual_function_blocking(parser, buf, NULL, NULL);
1981 +}
1982 +
1983 +enum set_config_result set_plugin_config_cb(void *usr_ctx, dyncfg_config_t *cfg)
1984 +{
1985 + PARSER *parser = usr_ctx;
1986 + int rc;
1987 + call_virtual_function_blocking(parser, "set_plugin_config", &rc, cfg->data);
1988 + if(rc != 1)
1989 + return SET_CONFIG_REJECTED;
1990 + return SET_CONFIG_ACCEPTED;
1991 +}
1992 +
1993 +enum set_config_result set_module_config_cb(void *usr_ctx, const char *module_name, dyncfg_config_t *cfg)
1994 +{
1995 + PARSER *parser = usr_ctx;
1996 + int rc;
1997 +
1998 + char buf[1024];
1999 + snprintfz(buf, sizeof(buf), "set_module_config %s", module_name);
2000 + call_virtual_function_blocking(parser, buf, &rc, cfg->data);
2001 +
2002 + if(rc != 1)
2003 + return SET_CONFIG_REJECTED;
2004 + return SET_CONFIG_ACCEPTED;
2005 +}
2006 +
2007 +enum set_config_result set_job_config_cb(void *usr_ctx, const char *module_name, const char *job_name, dyncfg_config_t *cfg)
2008 +{
2009 + PARSER *parser = usr_ctx;
2010 + int rc;
2011 +
2012 + char buf[1024];
2013 + snprintfz(buf, sizeof(buf), "set_job_config %s %s", module_name, job_name);
2014 + call_virtual_function_blocking(parser, buf, &rc, cfg->data);
2015 +
2016 + if(rc != 1)
2017 + return SET_CONFIG_REJECTED;
2018 + return SET_CONFIG_ACCEPTED;
2019 +}
2020 +
2021 +enum set_config_result delete_job_cb(void *usr_ctx, const char *module_name, const char *job_name)
2022 +{
2023 + PARSER *parser = usr_ctx;
2024 + int rc;
2025 +
2026 + char buf[1024];
2027 + snprintfz(buf, sizeof(buf), "delete_job %s %s", module_name, job_name);
2028 + call_virtual_function_blocking(parser, buf, &rc, NULL);
2029 +
2030 + if(rc != 1)
2031 + return SET_CONFIG_REJECTED;
2032 + return SET_CONFIG_ACCEPTED;
2033 +}
2034 +
2035 +
2036 +static inline PARSER_RC pluginsd_register_plugin(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser __maybe_unused) {
2037 + netdata_log_info("PLUGINSD: DYNCFG_ENABLE");
2038 +
2039 + if (unlikely (num_words != 2))
2040 + return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_ENABLE, "missing name parameter");
2041 +
2042 + struct configurable_plugin *cfg = callocz(1, sizeof(struct configurable_plugin));
2043 +
2044 + cfg->name = strdupz(words[1]);
2045 + cfg->set_config_cb = set_plugin_config_cb;
2046 + cfg->get_config_cb = get_plugin_config_cb;
2047 + cfg->get_config_schema_cb = get_plugin_config_schema_cb;
2048 + cfg->cb_usr_ctx = parser;
2049 +
2050 + parser->user.cd->cfg_dict_item = register_plugin(cfg);
2051 +
2052 + if (unlikely(parser->user.cd->cfg_dict_item == NULL)) {
2053 + freez(cfg->name);
2054 + freez(cfg);
2055 + return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_ENABLE, "error registering plugin");
2056 + }
2057 +
2058 + parser->user.cd->configuration = cfg;
2059 + return PARSER_RC_OK;
2060 +}
2061 +
2062 +static inline PARSER_RC pluginsd_register_module(char **words __maybe_unused, size_t num_words __maybe_unused, PARSER *parser __maybe_unused) {
2063 + netdata_log_info("PLUGINSD: DYNCFG_REG_MODULE");
2064 +
2065 + struct configurable_plugin *plug_cfg = parser->user.cd->configuration;
2066 + if (unlikely(plug_cfg == NULL))
2067 + return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE, "you have to enable dynamic configuration first using " PLUGINSD_KEYWORD_DYNCFG_ENABLE);
2068 +
2069 + if (unlikely(num_words != 3))
2070 + return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE, "expected 2 parameters module_name followed by module_type");
2071 +
2072 + struct module *mod = callocz(1, sizeof(struct module));
2073 +
2074 + mod->type = str2_module_type(words[2]);
2075 + if (unlikely(mod->type == MOD_TYPE_UNKNOWN)) {
2076 + freez(mod);
2077 + return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE, "unknown module type (allowed: job_array, single)");
2078 + }
2079 +
2080 + mod->name = strdupz(words[1]);
2081 +
2082 + mod->set_config_cb = set_module_config_cb;
2083 + mod->get_config_cb = get_module_config_cb;
2084 + mod->get_config_schema_cb = get_module_config_schema_cb;
2085 + mod->config_cb_usr_ctx = parser;
2086 +
2087 + mod->get_job_config_cb = get_job_config_cb;
2088 + mod->get_job_config_schema_cb = get_job_config_schema_cb;
2089 + mod->set_job_config_cb = set_job_config_cb;
2090 + mod->delete_job_cb = delete_job_cb;
2091 + mod->job_config_cb_usr_ctx = parser;
2092 +
2093 + register_module(plug_cfg, mod);
2094 + return PARSER_RC_OK;
2095 +}
2096 +
2097 +// job_status <module_name> <job_name> <status_code> <state> <message>
2098 +static inline PARSER_RC pluginsd_job_status(char **words, size_t num_words, PARSER *parser)
2099 +{
2100 + if (unlikely(num_words != 6 && num_words != 5))
2101 + return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_REPORT_JOB_STATUS, "expected 4 or 5 parameters: module_name, job_name, status_code, state, [optional: message]");
2102 +
2103 + int state = atoi(words[4]);
2104 +
2105 + enum job_status job_status = str2job_state(words[3]);
2106 + if (unlikely(job_status == JOB_STATUS_UNKNOWN))
2107 + return PLUGINSD_DISABLE_PLUGIN(parser, PLUGINSD_KEYWORD_REPORT_JOB_STATUS, "unknown job state");
2108 +
2109 + char *message = NULL;
2110 + if (num_words == 6)
2111 + message = strdupz(words[5]);
2112 +
2113 + report_job_status(parser->user.cd->configuration, words[1], words[2], job_status, state, message);
2114 + return PARSER_RC_OK;
2115 +}
2116 +
2117 static inline PARSER_RC streaming_claimed_id(char **words, size_t num_words, PARSER *parser)
2118 {
2119 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,
2418 case 99:
2419 return pluginsd_exit(words, num_words, parser);
2420
2421 + case 101:
2422 + return pluginsd_register_plugin(words, num_words, parser);
2423 +
2424 + case 102:
2425 + return pluginsd_register_module(words, num_words, parser);
2426 +
2427 default:
2428 fatal("Unknown keyword '%s' with id %zu", keyword->keyword, keyword->id);
2429 }
@@ -2158,6 +2444,11 @@ void parser_destroy(PARSER *parser) {
2444 if (unlikely(!parser))
2445 return;
2446
2447 + if (parser->user.cd != NULL && parser->user.cd->configuration != NULL) {
2448 + unregister_plugin(parser->user.cd->cfg_dict_item);
2449 + parser->user.cd->configuration = NULL;
2450 + }
2451 +
2452 dictionary_destroy(parser->inflight.functions);
2453 freez(parser);
2454 }
daemon/main.c
+2
@@ -2073,6 +2073,8 @@ int main(int argc, char **argv) {
2073 signals_block();
2074 signals_init(); // setup the signals we want to use
2075
2076 + dyn_conf_init();
2077 +
2078 // --------------------------------------------------------------------
2079 // check which threads are enabled and initialize them
2080
daemon/static_threads.c
+9
@@ -195,6 +195,15 @@ const struct netdata_static_thread static_threads_common[] = {
195 .init_routine = NULL,
196 .start_routine = profile_main
197 },
198 + {
199 + .name = "DYNCFG",
200 + .config_section = NULL,
201 + .config_name = NULL,
202 + .enabled = 1,
203 + .thread = NULL,
204 + .init_routine = NULL,
205 + .start_routine = dyncfg_main
206 + },
207
208 // terminator
209 {
libnetdata/dyn_conf/dyn_conf.c new
+906
@@ -0,0 +1,906 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "dyn_conf.h"
4 +
5 +#define DYN_CONF_PATH_MAX (4096)
6 +#define DYN_CONF_DIR VARLIB_DIR "/etc"
7 +
8 +#define DYN_CONF_JOB_SCHEMA "job_schema"
9 +#define DYN_CONF_SCHEMA "schema"
10 +#define DYN_CONF_MODULE_LIST "modules"
11 +#define DYN_CONF_JOB_LIST "jobs"
12 +#define DYN_CONF_CFG_EXT ".cfg"
13 +
14 +DICTIONARY *plugins_dict = NULL;
15 +
16 +struct deferred_cfg_send {
17 + char *plugin_name;
18 + char *module_name;
19 + char *job_name;
20 + struct deferred_cfg_send *next;
21 +};
22 +
23 +struct deferred_cfg_send *deferred_configs = NULL;
24 +pthread_mutex_t deferred_configs_lock = PTHREAD_MUTEX_INITIALIZER;
25 +pthread_cond_t deferred_configs_cond = PTHREAD_COND_INITIALIZER;
26 +
27 +static void deferred_config_push_back(const char *plugin_name, const char *module_name, const char *job_name)
28 +{
29 + struct deferred_cfg_send *deferred = callocz(1, sizeof(struct deferred_cfg_send));
30 + deferred->plugin_name = strdupz(plugin_name);
31 + if (module_name != NULL) {
32 + deferred->module_name = strdupz(module_name);
33 + if (job_name != NULL)
34 + deferred->job_name = strdupz(job_name);
35 + }
36 + pthread_mutex_lock(&deferred_configs_lock);
37 + struct deferred_cfg_send *last = deferred_configs;
38 + if (last == NULL)
39 + deferred_configs = deferred;
40 + else {
41 + while (last->next != NULL)
42 + last = last->next;
43 + last->next = deferred;
44 + }
45 + pthread_cond_signal(&deferred_configs_cond);
46 + pthread_mutex_unlock(&deferred_configs_lock);
47 +}
48 +
49 +static struct deferred_cfg_send *deferred_config_pop()
50 +{
51 + pthread_mutex_lock(&deferred_configs_lock);
52 + while (deferred_configs == NULL)
53 + pthread_cond_wait(&deferred_configs_cond, &deferred_configs_lock);
54 + struct deferred_cfg_send *deferred = deferred_configs;
55 + deferred_configs = deferred_configs->next;
56 + pthread_mutex_unlock(&deferred_configs_lock);
57 + return deferred;
58 +}
59 +
60 +static void deferred_config_free(struct deferred_cfg_send *dcs)
61 +{
62 + freez(dcs->plugin_name);
63 + freez(dcs->module_name);
64 + freez(dcs->job_name);
65 + freez(dcs);
66 +}
67 +
68 +static int _get_list_of_plugins_json_cb(const DICTIONARY_ITEM *item, void *entry, void *data)
69 +{
70 + UNUSED(item);
71 + json_object *obj = (json_object *)data;
72 + struct configurable_plugin *plugin = (struct configurable_plugin *)entry;
73 +
74 + json_object *plugin_name = json_object_new_string(plugin->name);
75 + json_object_array_add(obj, plugin_name);
76 +
77 + return 0;
78 +}
79 +
80 +json_object *get_list_of_plugins_json()
81 +{
82 + json_object *obj = json_object_new_array();
83 +
84 + dictionary_walkthrough_read(plugins_dict, _get_list_of_plugins_json_cb, obj);
85 +
86 + return obj;
87 +}
88 +
89 +static int _get_list_of_modules_json_cb(const DICTIONARY_ITEM *item, void *entry, void *data)
90 +{
91 + UNUSED(item);
92 + json_object *obj = (json_object *)data;
93 + struct module *module = (struct module *)entry;
94 +
95 + json_object *json_module = json_object_new_object();
96 +
97 + json_object *json_item = json_object_new_string(module->name);
98 + json_object_object_add(json_module, "name", json_item);
99 + const char *module_type;
100 + switch (module->type) {
101 + case MOD_TYPE_SINGLE:
102 + module_type = "single";
103 + break;
104 + case MOD_TYPE_ARRAY:
105 + module_type = "job_array";
106 + break;
107 + default:
108 + module_type = "unknown";
109 + break;
110 + }
111 + json_item = json_object_new_string(module_type);
112 + json_object_object_add(json_module, "type", json_item);
113 +
114 + json_object_array_add(obj, json_module);
115 +
116 + return 0;
117 +}
118 +
119 +json_object *get_list_of_modules_json(struct configurable_plugin *plugin)
120 +{
121 + json_object *obj = json_object_new_array();
122 +
123 + pthread_mutex_lock(&plugin->lock);
124 +
125 + dictionary_walkthrough_read(plugin->modules, _get_list_of_modules_json_cb, obj);
126 +
127 + pthread_mutex_unlock(&plugin->lock);
128 +
129 + return obj;
130 +}
131 +
132 +const char *job_status2str(enum job_status status)
133 +{
134 + switch (status) {
135 + case JOB_STATUS_UNKNOWN:
136 + return "unknown";
137 + case JOB_STATUS_STOPPED:
138 + return "stopped";
139 + case JOB_STATUS_RUNNING:
140 + return "running";
141 + case JOB_STATUS_ERROR:
142 + return "error";
143 + default:
144 + return "unknown";
145 + }
146 +}
147 +
148 +static int _get_list_of_jobs_json_cb(const DICTIONARY_ITEM *item, void *entry, void *data)
149 +{
150 + UNUSED(item);
151 + json_object *obj = (json_object *)data;
152 + struct job *job = (struct job *)entry;
153 +
154 + json_object *json_job = json_object_new_object();
155 + json_object *json_item = json_object_new_string(job->name);
156 + json_object_object_add(json_job, "name", json_item);
157 + json_item = json_object_new_string(job_status2str(job->status));
158 + json_object_object_add(json_job, "state", json_item);
159 + int64_t last_state_update_s = job->last_state_update / USEC_PER_SEC;
160 + int64_t last_state_update_us = job->last_state_update % USEC_PER_SEC;
161 +
162 + json_item = json_object_new_int64(last_state_update_s);
163 + json_object_object_add(json_job, "last_state_update_s", json_item);
164 +
165 + json_item = json_object_new_int64(last_state_update_us);
166 + json_object_object_add(json_job, "last_state_update_us", json_item);
167 +
168 + json_object_array_add(obj, json_job);
169 +
170 + return 0;
171 +}
172 +
173 +json_object *get_list_of_jobs_json(struct module *module)
174 +{
175 + json_object *obj = json_object_new_array();
176 +
177 + pthread_mutex_lock(&module->lock);
178 +
179 + dictionary_walkthrough_read(module->jobs, _get_list_of_jobs_json_cb, obj);
180 +
181 + pthread_mutex_unlock(&module->lock);
182 +
183 + return obj;
184 +}
185 +
186 +struct job *get_job_by_name(struct module *module, const char *job_name)
187 +{
188 + return dictionary_get(module->jobs, job_name);
189 +}
190 +
191 +int remove_job(struct module *module, struct job *job)
192 +{
193 + // as we are going to do unlink here we better make sure we have all to build proper path
194 + if (unlikely(job->name == NULL || module == NULL || module->name == NULL || module->plugin == NULL || module->plugin->name == NULL))
195 + return 0;
196 +
197 + enum set_config_result rc = module->delete_job_cb(module->job_config_cb_usr_ctx, module->name, job->name);
198 +
199 + if (rc != SET_CONFIG_ACCEPTED) {
200 + error_report("DYNCFG module \"%s\" rejected delete job for \"%s\"", module->name, job->name);
201 + return 0;
202 + }
203 +
204 + BUFFER *buffer = buffer_create(DYN_CONF_PATH_MAX, NULL);
205 + buffer_sprintf(buffer, DYN_CONF_DIR "/%s/%s/%s" DYN_CONF_CFG_EXT, module->plugin->name, module->name, job->name);
206 + unlink(buffer_tostring(buffer));
207 + buffer_free(buffer);
208 + return dictionary_del(module->jobs, job->name);
209 +}
210 +
211 +struct module *get_module_by_name(struct configurable_plugin *plugin, const char *module_name)
212 +{
213 + return dictionary_get(plugin->modules, module_name);
214 +}
215 +
216 +inline struct configurable_plugin *get_plugin_by_name(const char *name)
217 +{
218 + return dictionary_get(plugins_dict, name);
219 +}
220 +
221 +static int store_config(const char *module_name, const char *submodule_name, const char *cfg_idx, dyncfg_config_t cfg)
222 +{
223 + BUFFER *filename = buffer_create(DYN_CONF_PATH_MAX, NULL);
224 + buffer_sprintf(filename, DYN_CONF_DIR "/%s", module_name);
225 + if (mkdir(buffer_tostring(filename), 0755) == -1) {
226 + if (errno != EEXIST) {
227 + netdata_log_error("DYNCFG store_config: failed to create module directory %s", buffer_tostring(filename));
228 + buffer_free(filename);
229 + return 1;
230 + }
231 + }
232 +
233 + if (submodule_name != NULL) {
234 + buffer_sprintf(filename, "/%s", submodule_name);
235 + if (mkdir(buffer_tostring(filename), 0755) == -1) {
236 + if (errno != EEXIST) {
237 + netdata_log_error("DYNCFG store_config: failed to create submodule directory %s", buffer_tostring(filename));
238 + buffer_free(filename);
239 + return 1;
240 + }
241 + }
242 + }
243 +
244 + if (cfg_idx != NULL)
245 + buffer_sprintf(filename, "/%s", cfg_idx);
246 +
247 + buffer_strcat(filename, DYN_CONF_CFG_EXT);
248 +
249 +
250 + error_report("DYNCFG store_config: %s", buffer_tostring(filename));
251 +
252 + //write to file
253 + FILE *f = fopen(buffer_tostring(filename), "w");
254 + if (f == NULL) {
255 + error_report("DYNCFG store_config: failed to open %s for writing", buffer_tostring(filename));
256 + buffer_free(filename);
257 + return 1;
258 + }
259 +
260 + fwrite(cfg.data, cfg.data_size, 1, f);
261 + fclose(f);
262 +
263 + buffer_free(filename);
264 + return 0;
265 +}
266 +
267 +dyncfg_config_t load_config(const char *plugin_name, const char *module_name, const char *job_id)
268 +{
269 + BUFFER *filename = buffer_create(DYN_CONF_PATH_MAX, NULL);
270 + buffer_sprintf(filename, DYN_CONF_DIR "/%s", plugin_name);
271 + if (module_name != NULL)
272 + buffer_sprintf(filename, "/%s", module_name);
273 +
274 + if (job_id != NULL)
275 + buffer_sprintf(filename, "/%s", job_id);
276 +
277 + buffer_strcat(filename, DYN_CONF_CFG_EXT);
278 +
279 + dyncfg_config_t config;
280 + long bytes;
281 + config.data = read_by_filename(buffer_tostring(filename), &bytes);
282 +
283 + if (config.data == NULL)
284 + error_report("DYNCFG load_config: failed to load config from %s", buffer_tostring(filename));
285 +
286 + config.data_size = bytes;
287 +
288 + buffer_free(filename);
289 +
290 + return config;
291 +}
292 +
293 +char *set_plugin_config(struct configurable_plugin *plugin, dyncfg_config_t cfg)
294 +{
295 + enum set_config_result rc = plugin->set_config_cb(plugin->cb_usr_ctx, &cfg);
296 + if (rc != SET_CONFIG_ACCEPTED) {
297 + error_report("DYNCFG plugin \"%s\" rejected config", plugin->name);
298 + return "plugin rejected config";
299 + }
300 +
301 + if (store_config(plugin->name, NULL, NULL, cfg)) {
302 + error_report("DYNCFG could not store config for module \"%s\"", plugin->name);
303 + return "could not store config on disk";
304 + }
305 + return NULL;
306 +}
307 +
308 +static char *set_module_config(struct module *mod, dyncfg_config_t cfg)
309 +{
310 + struct configurable_plugin *plugin = mod->plugin;
311 +
312 + enum set_config_result rc = mod->set_config_cb(mod->config_cb_usr_ctx, mod->name, &cfg);
313 + if (rc != SET_CONFIG_ACCEPTED) {
314 + error_report("DYNCFG module \"%s\" rejected config", plugin->name);
315 + return "module rejected config";
316 + }
317 +
318 + if (store_config(plugin->name, mod->name, NULL, cfg)) {
319 + error_report("DYNCFG could not store config for module \"%s\"", mod->name);
320 + return "could not store config on disk";
321 + }
322 +
323 + return NULL;
324 +}
325 +
326 +struct job *job_new()
327 +{
328 + struct job *job = callocz(1, sizeof(struct job));
329 + job->state = JOB_STATUS_UNKNOWN;
330 + job->last_state_update = now_realtime_usec();
331 + return job;
332 +}
333 +
334 +static int set_job_config(struct job *job, dyncfg_config_t cfg)
335 +{
336 + struct module *mod = job->module;
337 + enum set_config_result rt = mod->set_job_config_cb(mod->job_config_cb_usr_ctx, mod->name, job->name, &cfg);
338 +
339 + if (rt != SET_CONFIG_ACCEPTED) {
340 + error_report("DYNCFG module \"%s\" rejected config for job \"%s\"", mod->name, job->name);
341 + return 1;
342 + }
343 +
344 + if (store_config(mod->plugin->name, mod->name, job->name, cfg)) {
345 + error_report("DYNCFG could not store config for module \"%s\"", mod->name);
346 + return 1;
347 + }
348 +
349 + return 0;
350 +}
351 +
352 +struct job *add_job(struct module *mod, const char *job_id, dyncfg_config_t cfg)
353 +{
354 + struct job *job = job_new();
355 + job->name = strdupz(job_id);
356 + job->module = mod;
357 +
358 + if (set_job_config(job, cfg)) {
359 + freez(job->name);
360 + freez(job);
361 + return NULL;
362 + }
363 +
364 + dictionary_set(mod->jobs, job->name, job, sizeof(job));
365 +
366 + return job;
367 +
368 +}
369 +
370 +void module_del_cb(const DICTIONARY_ITEM *item, void *value, void *data)
371 +{
372 + UNUSED(item);
373 + UNUSED(data);
374 + struct module *mod = (struct module *)value;
375 + dictionary_destroy(mod->jobs);
376 + freez(mod->name);
377 + freez(mod);
378 +}
379 +
380 +
381 +const DICTIONARY_ITEM *register_plugin(struct configurable_plugin *plugin)
382 +{
383 + if (get_plugin_by_name(plugin->name) != NULL) {
384 + error_report("DYNCFG plugin \"%s\" already registered", plugin->name);
385 + return NULL;
386 + }
387 +
388 + if (plugin->set_config_cb == NULL) {
389 + error_report("DYNCFG plugin \"%s\" has no set_config_cb", plugin->name);
390 + return NULL;
391 + }
392 +
393 + pthread_mutex_init(&plugin->lock, NULL);
394 +
395 + plugin->modules = dictionary_create(DICT_OPTION_VALUE_LINK_DONT_CLONE);
396 + dictionary_register_delete_callback(plugin->modules, module_del_cb, NULL);
397 +
398 + deferred_config_push_back(plugin->name, NULL, NULL);
399 +
400 + dictionary_set(plugins_dict, plugin->name, plugin, sizeof(plugin));
401 +
402 + // the plugin keeps the pointer to the dictionary item, so we need to acquire it
403 + return dictionary_get_and_acquire_item(plugins_dict, plugin->name);
404 +}
405 +
406 +void unregister_plugin(const DICTIONARY_ITEM *plugin)
407 +{
408 + struct configurable_plugin *plug = dictionary_acquired_item_value(plugin);
409 + dictionary_acquired_item_release(plugins_dict, plugin);
410 + dictionary_del(plugins_dict, plug->name);
411 +}
412 +
413 +void job_del_cb(const DICTIONARY_ITEM *item, void *value, void *data)
414 +{
415 + UNUSED(item);
416 + UNUSED(data);
417 + struct job *job = (struct job *)value;
418 + freez(job->reason);
419 + freez(job->name);
420 + freez(job);
421 +}
422 +
423 +int register_module(struct configurable_plugin *plugin, struct module *module)
424 +{
425 + if (get_module_by_name(plugin, module->name) != NULL) {
426 + error_report("DYNCFG module \"%s\" already registered", module->name);
427 + return 1;
428 + }
429 +
430 + pthread_mutex_init(&module->lock, NULL);
431 +
432 + deferred_config_push_back(plugin->name, module->name, NULL);
433 +
434 + module->plugin = plugin;
435 +
436 + if (module->type == MOD_TYPE_ARRAY) {
437 + module->jobs = dictionary_create(DICT_OPTION_VALUE_LINK_DONT_CLONE);
438 + dictionary_register_delete_callback(module->jobs, job_del_cb, NULL);
439 +
440 + // load all jobs from disk
441 + BUFFER *path = buffer_create(DYN_CONF_PATH_MAX, NULL);
442 + buffer_sprintf(path, "%s/%s/%s", DYN_CONF_DIR, plugin->name, module->name);
443 + DIR *dir = opendir(buffer_tostring(path));
444 + if (dir != NULL) {
445 + struct dirent *ent;
446 + while ((ent = readdir(dir)) != NULL) {
447 + if (ent->d_name[0] == '.')
448 + continue;
449 + if (ent->d_type != DT_REG)
450 + continue;
451 + size_t len = strnlen(ent->d_name, NAME_MAX);
452 + if (len <= strlen(DYN_CONF_CFG_EXT))
453 + continue;
454 + if (strcmp(ent->d_name + len - strlen(DYN_CONF_CFG_EXT), DYN_CONF_CFG_EXT) != 0)
455 + continue;
456 + ent->d_name[len - strlen(DYN_CONF_CFG_EXT)] = '\0';
457 +
458 + struct job *job = job_new();
459 + job->name = strdupz(ent->d_name);
460 + job->module = module;
461 + dictionary_set(module->jobs, job->name, job, sizeof(job));
462 +
463 + deferred_config_push_back(plugin->name, module->name, job->name);
464 + }
465 + closedir(dir);
466 + }
467 + buffer_free(path);
468 + }
469 +
470 + dictionary_set(plugin->modules, module->name, module, sizeof(module));
471 +
472 + return 0;
473 +}
474 +
475 +
476 +void handle_dyncfg_root(struct uni_http_response *resp, int method)
477 +{
478 + if (method != HTTP_METHOD_GET) {
479 + resp->content = "method not allowed";
480 + resp->content_length = strlen(resp->content);
481 + resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
482 + return;
483 + }
484 + json_object *obj = get_list_of_plugins_json();
485 + json_object *wrapper = json_object_new_object();
486 + json_object_object_add(wrapper, "configurable_plugins", obj);
487 + resp->content = strdupz(json_object_to_json_string_ext(wrapper, JSON_C_TO_STRING_PRETTY));
488 + json_object_put(wrapper);
489 + resp->status = HTTP_RESP_OK;
490 + resp->content_type = CT_APPLICATION_JSON;
491 + resp->content_free = freez;
492 + resp->content_length = strlen(resp->content);
493 +}
494 +
495 +void handle_plugin_root(struct uni_http_response *resp, int method, struct configurable_plugin *plugin, void *post_payload, size_t post_payload_size)
496 +{
497 + switch(method) {
498 + case HTTP_METHOD_GET:
499 + {
500 + dyncfg_config_t cfg = plugin->get_config_cb(plugin->cb_usr_ctx);
501 + resp->content = mallocz(cfg.data_size);
502 + memcpy(resp->content, cfg.data, cfg.data_size);
503 + resp->status = HTTP_RESP_OK;
504 + resp->content_free = free;
505 + resp->content_length = cfg.data_size;
506 + return;
507 + }
508 + case HTTP_METHOD_PUT:
509 + {
510 + char *response;
511 + if (post_payload == NULL) {
512 + resp->content = "no payload";
513 + resp->content_length = strlen(resp->content);
514 + resp->status = HTTP_RESP_BAD_REQUEST;
515 + return;
516 + }
517 + dyncfg_config_t cont = {
518 + .data = post_payload,
519 + .data_size = post_payload_size
520 + };
521 + response = set_plugin_config(plugin, cont);
522 + if (response == NULL) {
523 + resp->status = HTTP_RESP_OK;
524 + resp->content = "OK";
525 + resp->content_length = strlen(resp->content);
526 + } else {
527 + resp->status = HTTP_RESP_BAD_REQUEST;
528 + resp->content = response;
529 + resp->content_length = strlen(resp->content);
530 + }
531 + return;
532 + }
533 + default:
534 + resp->content = "method not allowed";
535 + resp->content_length = strlen(resp->content);
536 + resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
537 + return;
538 + }
539 +}
540 +
541 +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)
542 +{
543 + if (strncmp(module, DYN_CONF_SCHEMA, strlen(DYN_CONF_SCHEMA)) == 0) {
544 + dyncfg_config_t cfg = plugin->get_config_schema_cb(plugin->cb_usr_ctx);
545 + resp->content = mallocz(cfg.data_size);
546 + memcpy(resp->content, cfg.data, cfg.data_size);
547 + resp->status = HTTP_RESP_OK;
548 + resp->content_free = freez;
549 + resp->content_length = cfg.data_size;
550 + return;
551 + }
552 + if (strncmp(module, DYN_CONF_MODULE_LIST, strlen(DYN_CONF_MODULE_LIST)) == 0) {
553 + if (method != HTTP_METHOD_GET) {
554 + resp->content = "method not allowed (only GET)";
555 + resp->content_length = strlen(resp->content);
556 + resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
557 + return;
558 + }
559 + json_object *obj = get_list_of_modules_json(plugin);
560 + json_object *wrapper = json_object_new_object();
561 + json_object_object_add(wrapper, "modules", obj);
562 + resp->content = strdupz(json_object_to_json_string_ext(wrapper, JSON_C_TO_STRING_PRETTY));
563 + json_object_put(wrapper);
564 + resp->status = HTTP_RESP_OK;
565 + resp->content_type = CT_APPLICATION_JSON;
566 + resp->content_free = freez;
567 + resp->content_length = strlen(resp->content);
568 + return;
569 + }
570 + struct module *mod = get_module_by_name(plugin, module);
571 + if (mod == NULL) {
572 + resp->content = "module not found";
573 + resp->content_length = strlen(resp->content);
574 + resp->status = HTTP_RESP_NOT_FOUND;
575 + return;
576 + }
577 + if (method == HTTP_METHOD_GET) {
578 + dyncfg_config_t cfg = mod->get_config_cb(mod->config_cb_usr_ctx, mod->name);
579 + resp->content = mallocz(cfg.data_size);
580 + memcpy(resp->content, cfg.data, cfg.data_size);
581 + resp->status = HTTP_RESP_OK;
582 + resp->content_free = free;
583 + resp->content_length = cfg.data_size;
584 + return;
585 + } else if (method == HTTP_METHOD_PUT) {
586 + char *response;
587 + if (post_payload == NULL) {
588 + resp->content = "no payload";
589 + resp->content_length = strlen(resp->content);
590 + resp->status = HTTP_RESP_BAD_REQUEST;
591 + return;
592 + }
593 + dyncfg_config_t cont = {
594 + .data = post_payload,
595 + .data_size = post_payload_size
596 + };
597 + response = set_module_config(mod, cont);
598 + if (response == NULL) {
599 + resp->status = HTTP_RESP_OK;
600 + resp->content = "OK";
601 + resp->content_length = strlen(resp->content);
602 + } else {
603 + resp->status = HTTP_RESP_BAD_REQUEST;
604 + resp->content = response;
605 + resp->content_length = strlen(resp->content);
606 + }
607 + return;
608 + }
609 + resp->content = "method not allowed";
610 + resp->content_length = strlen(resp->content);
611 + resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
612 +}
613 +
614 +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)
615 +{
616 + if (method == HTTP_METHOD_POST) {
617 + if (job != NULL) {
618 + resp->content = "can't POST, job already exists (use PUT to update?)";
619 + resp->content_length = strlen(resp->content);
620 + resp->status = HTTP_RESP_BAD_REQUEST;
621 + return;
622 + }
623 + if (post_payload == NULL) {
624 + resp->content = "no payload";
625 + resp->content_length = strlen(resp->content);
626 + resp->status = HTTP_RESP_BAD_REQUEST;
627 + return;
628 + }
629 + dyncfg_config_t cont = {
630 + .data = post_payload,
631 + .data_size = post_payload_size
632 + };
633 + job = add_job(mod, job_id, cont);
634 + if (job == NULL) {
635 + resp->content = "failed to add job";
636 + resp->content_length = strlen(resp->content);
637 + resp->status = HTTP_RESP_INTERNAL_SERVER_ERROR;
638 + return;
639 + }
640 + resp->status = HTTP_RESP_OK;
641 + resp->content = "OK";
642 + resp->content_length = strlen(resp->content);
643 + return;
644 + }
645 + if (job == NULL) {
646 + resp->content = "job not found";
647 + resp->content_length = strlen(resp->content);
648 + resp->status = HTTP_RESP_NOT_FOUND;
649 + return;
650 + }
651 + switch (method) {
652 + case HTTP_METHOD_GET:
653 + {
654 + dyncfg_config_t cfg = mod->get_job_config_cb(mod->job_config_cb_usr_ctx, mod->name, job->name);
655 + resp->content = mallocz(cfg.data_size);
656 + memcpy(resp->content, cfg.data, cfg.data_size);
657 + resp->status = HTTP_RESP_OK;
658 + resp->content_free = freez;
659 + resp->content_length = cfg.data_size;
660 + return;
661 + }
662 + case HTTP_METHOD_PUT:
663 + {
664 + if (post_payload == NULL) {
665 + resp->content = "missing payload";
666 + resp->content_length = strlen(resp->content);
667 + resp->status = HTTP_RESP_BAD_REQUEST;
668 + return;
669 + }
670 + dyncfg_config_t cont = {
671 + .data = post_payload,
672 + .data_size = post_payload_size
673 + };
674 + if(set_job_config(job, cont)) {
675 + resp->status = HTTP_RESP_BAD_REQUEST;
676 + resp->content = "failed to set job config";
677 + resp->content_length = strlen(resp->content);
678 + return;
679 + }
680 + resp->status = HTTP_RESP_OK;
681 + resp->content = "OK";
682 + resp->content_length = strlen(resp->content);
683 + return;
684 + }
685 + case HTTP_METHOD_DELETE:
686 + {
687 + if (!remove_job(mod, job)) {
688 + resp->content = "failed to remove job";
689 + resp->content_length = strlen(resp->content);
690 + resp->status = HTTP_RESP_INTERNAL_SERVER_ERROR;
691 + return;
692 + }
693 + resp->status = HTTP_RESP_OK;
694 + resp->content = "OK";
695 + resp->content_length = strlen(resp->content);
696 + return;
697 + }
698 + default:
699 + resp->content = "method not allowed (only GET, PUT, DELETE)";
700 + resp->content_length = strlen(resp->content);
701 + resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
702 + return;
703 + }
704 +}
705 +
706 +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)
707 +{
708 + if (strncmp(job_id, DYN_CONF_SCHEMA, strlen(DYN_CONF_SCHEMA)) == 0) {
709 + dyncfg_config_t cfg = mod->get_config_schema_cb(mod->config_cb_usr_ctx, mod->name);
710 + resp->content = mallocz(cfg.data_size);
711 + memcpy(resp->content, cfg.data, cfg.data_size);
712 + resp->status = HTTP_RESP_OK;
713 + resp->content_free = freez;
714 + resp->content_length = cfg.data_size;
715 + return;
716 + }
717 + if (strncmp(job_id, DYN_CONF_JOB_SCHEMA, strlen(DYN_CONF_JOB_SCHEMA)) == 0) {
718 + dyncfg_config_t cfg = mod->get_job_config_schema_cb(mod->job_config_cb_usr_ctx, mod->name);
719 + resp->content = mallocz(cfg.data_size);
720 + memcpy(resp->content, cfg.data, cfg.data_size);
721 + resp->status = HTTP_RESP_OK;
722 + resp->content_free = freez;
723 + resp->content_length = cfg.data_size;
724 + return;
725 + }
726 + if (strncmp(job_id, DYN_CONF_JOB_LIST, strlen(DYN_CONF_JOB_LIST)) == 0) {
727 + if (mod->type != MOD_TYPE_ARRAY) {
728 + resp->content = "module type is not job_array (can't get the list of jobs)";
729 + resp->content_length = strlen(resp->content);
730 + resp->status = HTTP_RESP_NOT_FOUND;
731 + return;
732 + }
733 + if (method != HTTP_METHOD_GET) {
734 + resp->content = "method not allowed (only GET)";
735 + resp->content_length = strlen(resp->content);
736 + resp->status = HTTP_RESP_METHOD_NOT_ALLOWED;
737 + return;
738 + }
739 + json_object *obj = get_list_of_jobs_json(mod);
740 + json_object *wrapper = json_object_new_object();
741 + json_object_object_add(wrapper, "jobs", obj);
742 + resp->content = strdupz(json_object_to_json_string_ext(wrapper, JSON_C_TO_STRING_PRETTY));
743 + json_object_put(wrapper);
744 + resp->status = HTTP_RESP_OK;
745 + resp->content_type = CT_APPLICATION_JSON;
746 + resp->content_free = freez;
747 + resp->content_length = strlen(resp->content);
748 + return;
749 + }
750 + const DICTIONARY_ITEM *job_item = dictionary_get_and_acquire_item(mod->jobs, job_id);
751 + struct job *job = dictionary_acquired_item_value(job_item);
752 +
753 + _handle_job_root(resp, method, mod, job_id, post_payload, post_payload_size, job);
754 +
755 + dictionary_acquired_item_release(mod->jobs, job_item);
756 +}
757 +
758 +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)
759 +{
760 + struct uni_http_response resp = {
761 + .status = HTTP_RESP_INTERNAL_SERVER_ERROR,
762 + .content_type = CT_TEXT_PLAIN,
763 + .content = HTTP_RESP_INTERNAL_SERVER_ERROR_STR,
764 + .content_free = NULL,
765 + .content_length = 0
766 + };
767 + if (plugin == NULL) {
768 + handle_dyncfg_root(&resp, method);
769 + return resp;
770 + }
771 + const DICTIONARY_ITEM *plugin_item = dictionary_get_and_acquire_item(plugins_dict, plugin);
772 + if (plugin_item == NULL) {
773 + resp.content = "plugin not found";
774 + resp.content_length = strlen(resp.content);
775 + resp.status = HTTP_RESP_NOT_FOUND;
776 + return resp;
777 + }
778 + struct configurable_plugin *plug = dictionary_acquired_item_value(plugin_item);
779 + if (module == NULL) {
780 + handle_plugin_root(&resp, method, plug, post_payload, post_payload_size);
781 + goto EXIT_PLUGIN;
782 + }
783 + if (job_id == NULL) {
784 + handle_module_root(&resp, method, plug, module, post_payload, post_payload_size);
785 + goto EXIT_PLUGIN;
786 + }
787 + // for modules we do not do get_and_acquire as modules are never removed (only together with the plugin)
788 + struct module *mod = get_module_by_name(plug, module);
789 + if (mod == NULL) {
790 + resp.content = "module not found";
791 + resp.content_length = strlen(resp.content);
792 + resp.status = HTTP_RESP_NOT_FOUND;
793 + goto EXIT_PLUGIN;
794 + }
795 + if (mod->type != MOD_TYPE_ARRAY) {
796 + resp.content = "module is not array";
797 + resp.content_length = strlen(resp.content);
798 + resp.status = HTTP_RESP_NOT_FOUND;
799 + goto EXIT_PLUGIN;
800 + }
801 + handle_job_root(&resp, method, mod, job_id, post_payload, post_payload_size);
802 +
803 +EXIT_PLUGIN:
804 + dictionary_acquired_item_release(plugins_dict, plugin_item);
805 + return resp;
806 +}
807 +
808 +void plugin_del_cb(const DICTIONARY_ITEM *item, void *value, void *data)
809 +{
810 + UNUSED(item);
811 + UNUSED(data);
812 + struct configurable_plugin *plugin = (struct configurable_plugin *)value;
813 + dictionary_destroy(plugin->modules);
814 + freez(plugin->name);
815 + freez(plugin);
816 +}
817 +
818 +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)
819 +{
820 + const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(plugins_dict, plugin->name);
821 + if (item == NULL) {
822 + netdata_log_error("plugin %s not found", plugin->name);
823 + return;
824 + }
825 + struct configurable_plugin *plug = dictionary_acquired_item_value(item);
826 + struct module *mod = get_module_by_name(plug, module_name);
827 + if (mod == NULL) {
828 + netdata_log_error("module %s not found", module_name);
829 + goto EXIT_PLUGIN;
830 + }
831 + if (mod->type != MOD_TYPE_ARRAY) {
832 + netdata_log_error("module %s is not array", module_name);
833 + goto EXIT_PLUGIN;
834 + }
835 + const DICTIONARY_ITEM *job_item = dictionary_get_and_acquire_item(mod->jobs, job_name);
836 + if (job_item == NULL) {
837 + netdata_log_error("job %s not found", job_name);
838 + goto EXIT_PLUGIN;
839 + }
840 + struct job *job = dictionary_acquired_item_value(job_item);
841 + job->status = status;
842 + job->state = status_code;
843 + if (job->reason != NULL) {
844 + freez(job->reason);
845 + }
846 + job->reason = reason;
847 + job->last_state_update = now_realtime_usec();
848 +
849 + dictionary_acquired_item_release(mod->jobs, job_item);
850 +
851 +EXIT_PLUGIN:
852 + dictionary_acquired_item_release(plugins_dict, item);
853 +}
854 +
855 +int dyn_conf_init(void)
856 +{
857 + if (mkdir(DYN_CONF_DIR, 0755) == -1) {
858 + if (errno != EEXIST) {
859 + netdata_log_error("failed to create directory for dynamic configuration");
860 + return 1;
861 + }
862 + }
863 +
864 + plugins_dict = dictionary_create(DICT_OPTION_VALUE_LINK_DONT_CLONE);
865 + dictionary_register_delete_callback(plugins_dict, plugin_del_cb, NULL);
866 +
867 + return 0;
868 +}
869 +
870 +void *dyncfg_main(void *in)
871 +{
872 + while (!netdata_exit) {
873 + struct deferred_cfg_send *dcs = deferred_config_pop();
874 + const DICTIONARY_ITEM *plugin_item = dictionary_get_and_acquire_item(plugins_dict, dcs->plugin_name);
875 + if (plugin_item == NULL) {
876 + error_report("DYNCFG, plugin %s not found", dcs->plugin_name);
877 + deferred_config_free(dcs);
878 + continue;
879 + }
880 + struct configurable_plugin *plugin = dictionary_acquired_item_value(plugin_item);
881 + if (dcs->module_name == NULL) {
882 + dyncfg_config_t cfg = load_config(dcs->plugin_name, NULL, NULL);
883 + if (cfg.data != NULL) {
884 + plugin->set_config_cb(plugin->cb_usr_ctx, &cfg);
885 + freez(cfg.data);
886 + }
887 + } else if (dcs->job_name == NULL) {
888 + dyncfg_config_t cfg = load_config(dcs->plugin_name, dcs->module_name, NULL);
889 + if (cfg.data != NULL) {
890 + struct module *mod = get_module_by_name(plugin, dcs->module_name);
891 + mod->set_config_cb(mod->config_cb_usr_ctx, mod->name, &cfg);
892 + freez(cfg.data);
893 + }
894 + } else {
895 + dyncfg_config_t cfg = load_config(dcs->plugin_name, dcs->module_name, dcs->job_name);
896 + if (cfg.data != NULL) {
897 + struct module *mod = get_module_by_name(plugin, dcs->module_name);
898 + mod->set_job_config_cb(mod->job_config_cb_usr_ctx, mod->name, dcs->job_name, &cfg);
899 + freez(cfg.data);
900 + }
901 + }
902 + deferred_config_free(dcs);
903 + dictionary_acquired_item_release(plugins_dict, plugin_item);
904 + }
905 + return NULL;
906 +}
libnetdata/dyn_conf/dyn_conf.h new
+136
@@ -0,0 +1,136 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef DYN_CONF_H
4 +#define DYN_CONF_H
5 +
6 +#include "../libnetdata.h"
7 +
8 +enum module_type {
9 + MOD_TYPE_UNKNOWN = 0,
10 + MOD_TYPE_ARRAY,
11 + MOD_TYPE_SINGLE
12 +};
13 +
14 +static inline enum module_type str2_module_type(const char *type_name)
15 +{
16 + if (strcmp(type_name, "job_array") == 0)
17 + return MOD_TYPE_ARRAY;
18 + else if (strcmp(type_name, "single") == 0)
19 + return MOD_TYPE_SINGLE;
20 + return MOD_TYPE_UNKNOWN;
21 +}
22 +
23 +struct dyncfg_config {
24 + void *data;
25 + size_t data_size;
26 +};
27 +
28 +typedef struct dyncfg_config dyncfg_config_t;
29 +
30 +struct configurable_plugin;
31 +struct module;
32 +
33 +enum job_status {
34 + JOB_STATUS_UNKNOWN = 0, // State used until plugin reports first status
35 + JOB_STATUS_STOPPED,
36 + JOB_STATUS_RUNNING,
37 + JOB_STATUS_ERROR
38 +};
39 +
40 +inline enum job_status str2job_state(const char *state_name) {
41 + if (strcmp(state_name, "stopped") == 0)
42 + return JOB_STATUS_STOPPED;
43 + else if (strcmp(state_name, "running") == 0)
44 + return JOB_STATUS_RUNNING;
45 + else if (strcmp(state_name, "error") == 0)
46 + return JOB_STATUS_ERROR;
47 + return JOB_STATUS_UNKNOWN;
48 +}
49 +
50 +enum set_config_result {
51 + SET_CONFIG_ACCEPTED = 0,
52 + SET_CONFIG_REJECTED,
53 + SET_CONFIG_DEFFER
54 +};
55 +
56 +struct job
57 +{
58 + char *name;
59 +
60 + //state reported by config
61 + enum job_status status; // reported by plugin, enum as this has to be interpreted by UI
62 + int state; // code reported by plugin which can mean anything plugin wants
63 + char *reason; // reported by plugin, can be NULL (optional)
64 +
65 + usec_t last_state_update;
66 +
67 + struct module *module;
68 +};
69 +
70 +struct module
71 +{
72 + pthread_mutex_t lock;
73 + char *name;
74 + enum module_type type;
75 +
76 + struct configurable_plugin *plugin;
77 +
78 + // module config
79 + enum set_config_result (*set_config_cb)(void *usr_ctx, const char *module_name, dyncfg_config_t *cfg);
80 + dyncfg_config_t (*get_config_cb)(void *usr_ctx, const char *name);
81 + dyncfg_config_t (*get_config_schema_cb)(void *usr_ctx, const char *name);
82 + void *config_cb_usr_ctx;
83 +
84 + DICTIONARY *jobs;
85 +
86 + // jobs config
87 + dyncfg_config_t (*get_job_config_cb)(void *usr_ctx, const char *module_name, const char *job_name);
88 + dyncfg_config_t (*get_job_config_schema_cb)(void *usr_ctx, const char *module_name);
89 + enum set_config_result (*set_job_config_cb)(void *usr_ctx, const char *module_name, const char *job_name, dyncfg_config_t *cfg);
90 + enum set_config_result (*delete_job_cb)(void *usr_ctx, const char *module_name, const char *job_name);
91 + void *job_config_cb_usr_ctx;
92 +};
93 +
94 +struct configurable_plugin {
95 + pthread_mutex_t lock;
96 + char *name;
97 + DICTIONARY *modules;
98 + const char *schema;
99 +
100 + dyncfg_config_t (*get_config_cb)(void *usr_ctx);
101 + dyncfg_config_t (*get_config_schema_cb)(void *usr_ctx);
102 + enum set_config_result (*set_config_cb)(void *usr_ctx, dyncfg_config_t *cfg);
103 + void *cb_usr_ctx; // context for all callbacks (split if needed in future)
104 +};
105 +
106 +// API to be used by plugins
107 +const DICTIONARY_ITEM *register_plugin(struct configurable_plugin *plugin);
108 +void unregister_plugin(const DICTIONARY_ITEM *plugin);
109 +int register_module(struct configurable_plugin *plugin, struct module *module);
110 +
111 +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);
112 +
113 +// API to be used by the web server(s)
114 +json_object *get_list_of_plugins_json();
115 +struct configurable_plugin *get_plugin_by_name(const char *name);
116 +
117 +json_object *get_list_of_modules_json(struct configurable_plugin *plugin);
118 +struct module *get_module_by_name(struct configurable_plugin *plugin, const char *module_name);
119 +
120 +// helper struct to make interface between internal webserver and h2o same
121 +struct uni_http_response {
122 + int status;
123 + char *content;
124 + size_t content_length;
125 + HTTP_CONTENT_TYPE content_type;
126 + void (*content_free)(void *);
127 +};
128 +
129 +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);
130 +
131 +// API to be used by main netdata process, initialization and destruction etc.
132 +int dyn_conf_init(void);
133 +
134 +void *dyncfg_main(void *in);
135 +
136 +#endif //DYN_CONF_H
libnetdata/http/http_defs.h
+8
@@ -17,15 +17,23 @@
17 #define HTTP_RESP_UNAUTHORIZED 401
18 #define HTTP_RESP_FORBIDDEN 403
19 #define HTTP_RESP_NOT_FOUND 404
20 +#define HTTP_RESP_METHOD_NOT_ALLOWED 405
21 +#define HTTP_RESP_METHOD_NOT_ALLOWED_STR "Method Not Allowed"
22 #define HTTP_RESP_CONFLICT 409
23 #define HTTP_RESP_PRECOND_FAIL 412
24 #define HTTP_RESP_CONTENT_TOO_LONG 413
25
26 // HTTP_CODES 5XX Server Errors
27 #define HTTP_RESP_INTERNAL_SERVER_ERROR 500
28 +#define HTTP_RESP_INTERNAL_SERVER_ERROR_STR "Internal Server Error"
29 #define HTTP_RESP_BACKEND_FETCH_FAILED 503 // 503 is right
30 #define HTTP_RESP_SERVICE_UNAVAILABLE 503 // 503 is right
31 #define HTTP_RESP_GATEWAY_TIMEOUT 504
32 #define HTTP_RESP_BACKEND_RESPONSE_INVALID 591
33
34 +#define HTTP_METHOD_GET (1)
35 +#define HTTP_METHOD_POST (2)
36 +#define HTTP_METHOD_PUT (3)
37 +#define HTTP_METHOD_DELETE (4)
38 +
39 #endif /* NETDATA_HTTP_DEFS_H */
libnetdata/libnetdata.c
+1 -1
@@ -1628,7 +1628,7 @@ void recursive_config_double_dir_load(const char *user_path, const char *stock_p
1628
1629 // Returns the number of bytes read from the file if file_size is not NULL.
1630 // The actual buffer has an extra byte set to zero (not included in the count).
1631 -char *read_by_filename(char *filename, long *file_size)
1631 +char *read_by_filename(const char *filename, long *file_size)
1632 {
1633 FILE *f = fopen(filename, "r");
1634 if (!f)
libnetdata/libnetdata.h
+2 -1
@@ -579,7 +579,7 @@ void recursive_config_double_dir_load(
579 , void *data
580 , size_t depth
581 );
582 -char *read_by_filename(char *filename, long *file_size);
582 +char *read_by_filename(const char *filename, long *file_size);
583 char *find_and_replace(const char *src, const char *find, const char *replace, const char *where);
584
585 /* fix for alpine linux */
@@ -837,6 +837,7 @@ extern char *netdata_configured_host_prefix;
837 #include "yaml.h"
838 #include "http/http_defs.h"
839 #include "gorilla/gorilla.h"
840 +#include "dyn_conf/dyn_conf.h"
841
842 // BEWARE: this exists in alarm-notify.sh
843 #define DEFAULT_CLOUD_BASE_URL "https://app.netdata.cloud"
libnetdata/url/url.c
+1 -1
@@ -243,7 +243,7 @@ inline bool url_is_request_complete(char *begin, char *end, size_t length, char
243 if(likely(strncmp(begin, "GET ", 4)) == 0) {
244 return strstr(end - 4, "\r\n\r\n");
245 }
246 - else if(unlikely(strncmp(begin, "POST ", 5) == 0)) {
246 + else if(unlikely(strncmp(begin, "POST ", 5) == 0 || strncmp(begin, "PUT ", 4) == 0)) {
247 char *cl = strstr(begin, "Content-Length: ");
248 if(!cl) return false;
249 cl = &cl[16];
web/api/web_api.c
+23 -4
@@ -40,12 +40,28 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
40 return HTTP_RESP_BAD_REQUEST;
41 }
42
43 - uint32_t hash = simple_hash(url_path_endpoint);
43 + char *api_command = strchr(url_path_endpoint, '/');
44 + if (likely(api_command == NULL)) // only config command supports subpaths for now
45 + api_command = url_path_endpoint;
46 + else {
47 + size_t api_command_len = api_command - url_path_endpoint;
48 + api_command = callocz(1, api_command_len + 1);
49 + memcpy(api_command, url_path_endpoint, api_command_len);
50 + }
51 +
52 + uint32_t hash = simple_hash(api_command);
53
54 for(int i = 0; api_commands[i].command ; i++) {
46 - if(unlikely(hash == api_commands[i].hash && !strcmp(url_path_endpoint, api_commands[i].command))) {
47 - if(unlikely(!web_client_check_acl_and_bearer(w, api_commands[i].acl)))
48 - return web_client_bearer_required(w);
55 + if(unlikely(hash == api_commands[i].hash && !strcmp(api_command, api_commands[i].command))) {
56 + if(unlikely(!api_commands[i].allow_subpaths && api_command != url_path_endpoint)) {
57 + buffer_flush(w->response.data);
58 + buffer_sprintf(w->response.data, "API command '%s' does not support subpaths.", api_command);
59 + freez(api_command);
60 + return HTTP_RESP_BAD_REQUEST;
61 + }
62 +
63 + if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl))
64 + return web_client_permission_denied(w);
65
66 char *query_string = (char *)buffer_tostring(w->url_query_string_decoded);
67
@@ -56,6 +72,9 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
72 }
73 }
74
75 + if (api_command != url_path_endpoint)
76 + freez(api_command);
77 +
78 buffer_flush(w->response.data);
79 buffer_strcat(w->response.data, "Unsupported API command: ");
80 buffer_strcat_htmlescape(w->response.data, url_path_endpoint);
web/api/web_api.h
+1
@@ -20,6 +20,7 @@ struct web_api_command {
20 uint32_t hash;
21 WEB_CLIENT_ACL acl;
22 int (*callback)(RRDHOST *host, struct web_client *w, char *url);
23 + unsigned int allow_subpaths;
24 };
25
26 struct web_client;
web/api/web_api_v1.c
+23 -23
@@ -1463,43 +1463,43 @@ int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struc
1463 #endif
1464
1465 static struct web_api_command api_commands_v1[] = {
1466 - { "info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_info },
1467 - { "data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_data },
1468 - { "chart", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_chart },
1469 - { "charts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_charts },
1470 - { "context", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_context },
1471 - { "contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_contexts },
1466 + { "info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_info, 0 },
1467 + { "data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_data, 0 },
1468 + { "chart", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_chart, 0 },
1469 + { "charts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_charts, 0 },
1470 + { "context", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_context, 0 },
1471 + { "contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_contexts, 0 },
1472
1473 // registry checks the ACL by itself, so we allow everything
1474 - { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry },
1474 + { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry, 0 },
1475
1476 // badges can be fetched with both dashboard and badge permissions
1477 - { "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC | WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge },
1477 + { "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC | WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge, 0 },
1478
1479 - { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms },
1480 - { "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms_values },
1481 - { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_log },
1482 - { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_variables },
1483 - { "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_count },
1484 - { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_allmetrics },
1479 + { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms, 0 },
1480 + { "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms_values, 0 },
1481 + { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_log, 0 },
1482 + { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_variables, 0 },
1483 + { "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_count, 0 },
1484 + { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_allmetrics, 0 },
1485
1486 #if defined(ENABLE_ML)
1487 - { "ml_info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_ml_info },
1487 + { "ml_info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_ml_info, 0 },
1488 // { "ml_models", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_ml_models },
1489 #endif
1490
1491 - {"manage/health", 0, WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_mgmt_health },
1492 - { "aclk", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_aclk_state },
1493 - { "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_metric_correlations },
1494 - { "weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_weights },
1491 + {"manage/health", 0, WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_mgmt_health, 0 },
1492 + { "aclk", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_aclk_state, 0 },
1493 + { "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_metric_correlations, 0 },
1494 + { "weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_weights, 0 },
1495
1496 - {"function", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function },
1497 - {"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions },
1496 + {"function", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function, 0 },
1497 + {"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions, 0 },
1498
1499 - { "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_dbengine_stats },
1499 + { "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_dbengine_stats, 0 },
1500
1501 // terminator
1502 - { NULL, 0, WEB_CLIENT_ACL_NONE, NULL },
1502 + { NULL, 0, WEB_CLIENT_ACL_NONE, NULL, 0 },
1503 };
1504
1505 inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
web/api/web_api_v2.c
+83 -17
@@ -644,31 +644,97 @@ static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct
644 return webrtc_new_connection(w->post_payload, w->response.data);
645 }
646
647 +#define CONFIG_API_V2_URL "/api/v2/config"
648 +static int web_client_api_request_v2_config(RRDHOST *host __maybe_unused, struct web_client *w, char *query) {
649 +
650 + char *url = strdupz(buffer_tostring(w->url_as_received));
651 + char *url_full = url;
652 +
653 + if (strncmp(url, CONFIG_API_V2_URL, strlen(CONFIG_API_V2_URL)) != 0) {
654 + buffer_sprintf(w->response.data, "Invalid URL");
655 + return HTTP_RESP_BAD_REQUEST;
656 + }
657 + url += strlen(CONFIG_API_V2_URL);
658 +
659 + char *save_ptr = NULL;
660 + char *plugin = strtok_r(url, "/", &save_ptr);
661 + char *module = strtok_r(NULL, "/", &save_ptr);
662 + char *job_id = strtok_r(NULL, "/", &save_ptr);
663 + char *extra = strtok_r(NULL, "/", &save_ptr);
664 +
665 + buffer_flush(w->response.data);
666 + if (extra != NULL) {
667 + buffer_sprintf(w->response.data, "Invalid URL");
668 + freez(url_full);
669 + return HTTP_RESP_BAD_REQUEST;
670 + }
671 +
672 + int http_method;
673 + switch (w->mode)
674 + {
675 + case WEB_CLIENT_MODE_GET:
676 + http_method = HTTP_METHOD_GET;
677 + break;
678 + case WEB_CLIENT_MODE_POST:
679 + http_method = HTTP_METHOD_POST;
680 + break;
681 + case WEB_CLIENT_MODE_PUT:
682 + http_method = HTTP_METHOD_PUT;
683 + break;
684 + case WEB_CLIENT_MODE_DELETE:
685 + http_method = HTTP_METHOD_DELETE;
686 + break;
687 + default:
688 + buffer_sprintf(w->response.data, "Invalid HTTP method");
689 + freez(url_full);
690 + return HTTP_RESP_BAD_REQUEST;
691 + }
692 +
693 + struct uni_http_response resp = dyn_conf_process_http_request(http_method, plugin, module, job_id, w->post_payload, w->post_payload_size);
694 + if (resp.content[resp.content_length - 1] != '\0') {
695 + char *con = mallocz(resp.content_length + 1);
696 + memcpy(con, resp.content, resp.content_length);
697 + con[resp.content_length] = '\0';
698 + if (resp.content_free)
699 + resp.content_free(resp.content);
700 + resp.content = con;
701 + resp.content_free = freez;
702 + }
703 + buffer_strcat(w->response.data, resp.content);
704 + if (resp.content_free)
705 + resp.content_free(resp.content);
706 + w->response.data->content_type = resp.content_type;
707 + freez(url_full);
708 + return resp.status;
709 +}
710 +
711 static struct web_api_command api_commands_v2[] = {
648 - {"info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_info},
712 + {"info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_info, 0},
713 +
714 + {"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data, 0},
715 + {"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights, 0},
716
650 - {"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data},
651 - {"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights},
717 + {"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts, 0},
718 + {"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes, 0},
719 + {"node_instances", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_node_instances, 0},
720 + {"versions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_versions, 0},
721 + {"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_functions, 0},
722 + {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q, 0},
723 + {"alerts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts, 0},
724
653 - {"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts},
654 - {"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes},
655 - {"node_instances", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_node_instances},
656 - {"versions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_versions},
657 - {"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_functions},
658 - {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q},
659 - {"alerts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts},
725 + {"alert_transitions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_transitions, 0},
726 + {"alert_config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_config, 0},
727
661 - {"alert_transitions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_transitions},
662 - {"alert_config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_config},
728 + {"claim", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v2_claim, 0},
729
664 - {"claim", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v2_claim},
730 + {"rtc_offer", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_webrtc, 0},
731 + {"bearer_protection", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_protection, 0},
732 + {"bearer_get_token", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_token, 0},
733
666 - {"rtc_offer", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_webrtc},
667 - {"bearer_protection", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_protection},
668 - {"bearer_get_token", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_token},
734 + {"config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_config, 1},
735
736 // terminator
671 - {NULL, 0, WEB_CLIENT_ACL_NONE, NULL},
737 + {NULL, 0, WEB_CLIENT_ACL_NONE, NULL, 0},
738 };
739
740 inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
web/server/web_client.c
+15 -1
@@ -204,7 +204,9 @@ void web_client_request_done(struct web_client *w) {
204 break;
205
206 case WEB_CLIENT_MODE_POST:
207 + case WEB_CLIENT_MODE_PUT:
208 case WEB_CLIENT_MODE_GET:
209 + case WEB_CLIENT_MODE_DELETE:
210 mode = "DATA";
211 break;
212
@@ -1075,6 +1077,14 @@ static inline char *web_client_valid_method(struct web_client *w, char *s) {
1077 s = &s[5];
1078 w->mode = WEB_CLIENT_MODE_POST;
1079 }
1080 + else if(!strncmp(s, "PUT ", 4)) {
1081 + s = &s[4];
1082 + w->mode = WEB_CLIENT_MODE_PUT;
1083 + }
1084 + else if(!strncmp(s, "DELETE ", 7)) {
1085 + s = &s[7];
1086 + w->mode = WEB_CLIENT_MODE_DELETE;
1087 + }
1088 else if(!strncmp(s, "STREAM ", 7)) {
1089 s = &s[7];
1090
@@ -1747,6 +1757,8 @@ void web_client_process_request(struct web_client *w) {
1757 case WEB_CLIENT_MODE_FILECOPY:
1758 case WEB_CLIENT_MODE_POST:
1759 case WEB_CLIENT_MODE_GET:
1760 + case WEB_CLIENT_MODE_PUT:
1761 + case WEB_CLIENT_MODE_DELETE:
1762 if(unlikely(
1763 !web_client_can_access_dashboard(w) &&
1764 !web_client_can_access_registry(w) &&
@@ -1879,6 +1891,8 @@ void web_client_process_request(struct web_client *w) {
1891
1892 case WEB_CLIENT_MODE_POST:
1893 case WEB_CLIENT_MODE_GET:
1894 + case WEB_CLIENT_MODE_PUT:
1895 + case WEB_CLIENT_MODE_DELETE:
1896 netdata_log_debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%zu bytes) to client.", w->id, w->response.data->len);
1897 break;
1898
@@ -2042,7 +2056,7 @@ ssize_t web_client_send_deflate(struct web_client *w)
2056
2057 // ask for FINISH if we have all the input
2058 int flush = Z_SYNC_FLUSH;
2045 - if((w->mode == WEB_CLIENT_MODE_GET || w->mode == WEB_CLIENT_MODE_POST)
2059 + 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)
2060 || (w->mode == WEB_CLIENT_MODE_FILECOPY && !web_client_has_wait_receive(w) && w->response.data->len == w->response.rlen)) {
2061 flush = Z_FINISH;
2062 netdata_log_debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id);
web/server/web_client.h
+2
@@ -18,6 +18,8 @@ typedef enum web_client_mode {
18 WEB_CLIENT_MODE_FILECOPY = 2,
19 WEB_CLIENT_MODE_OPTIONS = 3,
20 WEB_CLIENT_MODE_STREAM = 4,
21 + WEB_CLIENT_MODE_PUT = 5,
22 + WEB_CLIENT_MODE_DELETE = 6,
23 } WEB_CLIENT_MODE;
24
25 typedef enum {