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 {