Port ACLs, Management API and Health commands (#4969)
##### Summary fixes #2673 fixes #2149 fixes #5017 fixes #3830 fixes #3187 fixes #5154 Implements a command API for health which will accept commands via a socket to selectively suppress health checks. Allows different ports to accept different request types (streaming, dashboard, api, registry, netdata.conf, badges, management) Removes support for multi-threaded and single-threaded web servers. ##### Component Name health, daemon
Chris Akritidis committed
Jan 15, 2019 at 12:49 UTC
08649bec373555144878b4314e87c9a8eb38c82e
51 files changed
+2078
-1105
.gitignore
+1
-1
@@ -51,7 +51,6 @@ cgroup-network
51
!cgroup-network/
52
53
# installation artifacts
54
-installer/.environment.sh
54
packaging/installer/.environment.sh
55
*.tar.*
56
*.run
@@ -140,6 +139,7 @@ tests/profile/benchmark-line-parsing
139
tests/profile/benchmark-procfile-parser
140
tests/profile/benchmark-value-pairs
141
tests/profile/statsd-stress
142
+tests/health_mgmtapi/health-cmdapi-test.sh
143
oprofile_data/
144
vgcore.*
145
callgrind.out.*
CMakeLists.txt
+6
-10
@@ -13,7 +13,7 @@ find_package(PkgConfig REQUIRED)
13
#set(CMAKE_BUILD_TYPE "Release")
14
15
# set this to see the compilation commands
16
-#set(CMAKE_VERBOSE_MAKEFILE 1)
16
+# set(CMAKE_VERBOSE_MAKEFILE 1)
17
18
19
# -----------------------------------------------------------------------------
@@ -30,8 +30,8 @@ IF("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
30
set(CXX_FORMAT_SIGNEDNESS "-Wformat-signedness")
31
set(CXX_FORMAT_SECURITY "-Werror=format-security")
32
set(CXX_STACK_PROTECTOR "-fstack-protector-all")
33
-
34
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O1 -ggdb -Wall -Wextra -DNETDATA_INTERNAL_CHECKS=1 -DNETDATA_VERIFY_LOCKS=1 ${CXX_FORMAT_SIGNEDNESS} ${CXX_FORMAT_SECURITY} ${CXX_STACK_PROTECTOR}")
33
+ set(CXX_FLAGS_DEBUG "-O0")
34
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O1 -ggdb -Wall -Wextra -DNETDATA_INTERNAL_CHECKS=1 -DNETDATA_VERIFY_LOCKS=1 ${CXX_FORMAT_SIGNEDNESS} ${CXX_FORMAT_SECURITY} ${CXX_STACK_PROTECTOR} ${CXX_FLAGS_DEBUG}")
35
ELSE()
36
message(STATUS "building for: release")
37
cmake_policy(SET CMP0069 "NEW")
@@ -221,8 +221,7 @@ set(HEALTH_PLUGIN_FILES
221
health/health.h
222
health/health_config.c
223
health/health_json.c
224
- health/health_log.c
225
- )
224
+ health/health_log.c)
225
226
set(IDLEJITTER_PLUGIN_FILES
227
collectors/idlejitter.plugin/plugin_idlejitter.c
@@ -354,10 +353,6 @@ set(WEB_PLUGIN_FILES
353
web/server/web_client.h
354
web/server/web_server.c
355
web/server/web_server.h
357
- web/server/single/single-threaded.c
358
- web/server/single/single-threaded.h
359
- web/server/multi/multi-threaded.c
360
- web/server/multi/multi-threaded.h
356
web/server/static/static-threaded.c
357
web/server/static/static-threaded.h
358
web/server/web_client_cache.c
@@ -411,6 +406,7 @@ set(API_PLUGIN_FILES
406
web/api/formatters/charts2json.h
407
web/api/formatters/rrdset2json.c
408
web/api/formatters/rrdset2json.h
409
+ web/api/health/health_cmdapi.c
410
)
411
412
set(STREAMING_PLUGIN_FILES
@@ -479,7 +475,7 @@ add_definitions(
475
-DLIBCONFIG_DIR="/usr/lib/netdata/conf.d"
476
-DLOG_DIR="/var/log/netdata"
477
-DPLUGINS_DIR="/usr/libexec/netdata"
482
- -DWEB_DIR="/usr/share/netdata"
478
+ -DWEB_DIR="/usr/share/netdata/web"
479
-DVARLIB_DIR="/var/lib/netdata"
480
)
481
Makefile.am
+2
-4
@@ -337,6 +337,8 @@ API_PLUGIN_FILES = \
337
web/api/formatters/charts2json.h \
338
web/api/formatters/rrdset2json.c \
339
web/api/formatters/rrdset2json.h \
340
+ web/api/health/health_cmdapi.c \
341
+ web/api/health/health_cmdapi.h \
342
web/api/web_api_v1.c \
343
web/api/web_api_v1.h \
344
$(NULL)
@@ -374,10 +376,6 @@ WEB_PLUGIN_FILES = \
376
web/server/web_server.h \
377
web/server/web_client_cache.c \
378
web/server/web_client_cache.h \
377
- web/server/single/single-threaded.c \
378
- web/server/single/single-threaded.h \
379
- web/server/multi/multi-threaded.c \
380
- web/server/multi/multi-threaded.h \
379
web/server/static/static-threaded.c \
380
web/server/static/static-threaded.h \
381
$(NULL)
build/subst.inc
+2
@@ -5,6 +5,8 @@
5
-e 's#[@]configdir_POST@#$(configdir)#g' \
6
-e 's#[@]libconfigdir_POST@#$(libconfigdir)#g' \
7
-e 's#[@]cachedir_POST@#$(cachedir)#g' \
8
+ -e 's#[@]registrydir_POST@#$(registrydir)#g' \
9
+ -e 's#[@]varlibdir_POST@#$(varlibdir)#g' \
10
$< > $@.tmp; then \
11
mv "$@.tmp" "$@"; \
12
else \
configure.ac
+1
-2
@@ -609,10 +609,9 @@ AC_CONFIG_FILES([
609
web/api/queries/ses/Makefile
610
web/api/queries/stddev/Makefile
611
web/api/queries/sum/Makefile
612
+ web/api/health/Makefile
613
web/gui/Makefile
614
web/server/Makefile
614
- web/server/single/Makefile
615
- web/server/multi/Makefile
615
web/server/static/Makefile
616
])
617
AC_OUTPUT
daemon/main.c
+2
-19
@@ -67,8 +67,6 @@ struct netdata_static_thread static_threads[] = {
67
68
// common plugins for all systems
69
{"BACKENDS", NULL, NULL, 1, NULL, NULL, backends_main},
70
- {"WEB_SERVER[multi]", NULL, NULL, 1, NULL, NULL, socket_listen_main_multi_threaded},
71
- {"WEB_SERVER[single]", NULL, NULL, 0, NULL, NULL, socket_listen_main_single_threaded},
70
{"WEB_SERVER[static1]", NULL, NULL, 0, NULL, NULL, socket_listen_main_static_threaded},
71
{"STREAM", NULL, NULL, 0, NULL, NULL, rrdpush_sender_thread},
72
@@ -81,18 +79,10 @@ struct netdata_static_thread static_threads[] = {
79
void web_server_threading_selection(void) {
80
web_server_mode = web_server_mode_id(config_get(CONFIG_SECTION_WEB, "mode", web_server_mode_name(web_server_mode)));
81
84
- int multi_threaded = (web_server_mode == WEB_SERVER_MODE_MULTI_THREADED);
85
- int single_threaded = (web_server_mode == WEB_SERVER_MODE_SINGLE_THREADED);
82
int static_threaded = (web_server_mode == WEB_SERVER_MODE_STATIC_THREADED);
83
84
int i;
85
for (i = 0; static_threads[i].name; i++) {
90
- if (static_threads[i].start_routine == socket_listen_main_multi_threaded)
91
- static_threads[i].enabled = multi_threaded;
92
-
93
- if (static_threads[i].start_routine == socket_listen_main_single_threaded)
94
- static_threads[i].enabled = single_threaded;
95
-
86
if (static_threads[i].start_routine == socket_listen_main_static_threaded)
87
static_threads[i].enabled = static_threaded;
88
}
@@ -113,6 +103,8 @@ void web_server_config_options(void) {
103
web_allow_registry_from = simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT);
104
web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), NULL, SIMPLE_PATTERN_EXACT);
105
web_allow_netdataconf_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow netdata.conf from", "localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.*"), NULL, SIMPLE_PATTERN_EXACT);
106
+ web_allow_mgmt_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow management from", "localhost"), NULL, SIMPLE_PATTERN_EXACT);
107
+
108
109
#ifdef NETDATA_WITH_ZLIB
110
web_enable_gzip = config_get_boolean(CONFIG_SECTION_WEB, "enable gzip compression", web_enable_gzip);
@@ -367,13 +359,6 @@ void log_init(void) {
359
}
360
361
static void backwards_compatible_config() {
370
- // allow existing configurations to work with the current version of netdata
371
-
372
- if(config_exists(CONFIG_SECTION_GLOBAL, "multi threaded web server")) {
373
- int mode = config_get_boolean(CONFIG_SECTION_GLOBAL, "multi threaded web server", 1);
374
- web_server_mode = (mode)?WEB_SERVER_MODE_MULTI_THREADED:WEB_SERVER_MODE_SINGLE_THREADED;
375
- }
376
-
362
// move [global] options to the [web] section
363
config_move(CONFIG_SECTION_GLOBAL, "http port listen backlog",
364
CONFIG_SECTION_WEB, "listen backlog");
@@ -876,7 +861,6 @@ int main(int argc, char **argv) {
861
load_netdata_conf(NULL, 0);
862
}
863
879
- backwards_compatible_config();
864
get_netdata_configured_variables();
865
866
const char *section = argv[optind];
@@ -1056,7 +1040,6 @@ int main(int argc, char **argv) {
1040
1041
rrd_init(netdata_configured_hostname);
1042
1059
-
1043
// ------------------------------------------------------------------------
1044
// enable log flood protection
1045
database/rrdcalc.h
+2
@@ -25,6 +25,8 @@
25
#define RRDCALC_FLAG_WARN_ERROR 0x00000010
26
#define RRDCALC_FLAG_CRIT_ERROR 0x00000020
27
#define RRDCALC_FLAG_RUNNABLE 0x00000040
28
+#define RRDCALC_FLAG_DISABLED 0x00000080
29
+#define RRDCALC_FLAG_SILENCED 0x00000100
30
#define RRDCALC_FLAG_NO_CLEAR_NOTIFICATION 0x80000000
31
32
struct rrdcalc {
database/rrdhost.c
+3
-2
@@ -103,7 +103,6 @@ static inline void rrdhost_init_machine_guid(RRDHOST *host, const char *machine_
103
host->hash_machine_guid = simple_hash(host->machine_guid);
104
}
105
106
-
106
// ----------------------------------------------------------------------------
107
// RRDHOST - add a host
108
@@ -149,6 +148,7 @@ RRDHOST *rrdhost_create(const char *hostname,
148
149
rrdhost_init_hostname(host, hostname);
150
rrdhost_init_machine_guid(host, guid);
151
+
152
rrdhost_init_os(host, os);
153
rrdhost_init_timezone(host, timezone);
154
rrdhost_init_tags(host, tags);
@@ -442,7 +442,7 @@ restart_after_removal:
442
void rrd_init(char *hostname) {
443
rrdset_free_obsolete_time = config_get_number(CONFIG_SECTION_GLOBAL, "cleanup obsolete charts after seconds", rrdset_free_obsolete_time);
444
gap_when_lost_iterations_above = (int)config_get_number(CONFIG_SECTION_GLOBAL, "gap when lost iterations above", gap_when_lost_iterations_above);
445
- if(gap_when_lost_iterations_above < 1)
445
+ if (gap_when_lost_iterations_above < 1)
446
gap_when_lost_iterations_above = 1;
447
448
health_init();
@@ -471,6 +471,7 @@ void rrd_init(char *hostname) {
471
, 1
472
);
473
rrd_unlock();
474
+ web_client_api_v1_management_init();
475
}
476
477
// ----------------------------------------------------------------------------
database/rrdsetvar.c
+2
-2
@@ -150,12 +150,12 @@ RRDSETVAR *rrdsetvar_custom_chart_variable_create(RRDSET *st, const char *name)
150
if(hash == rs->hash && strcmp(n, rs->variable) == 0) {
151
rrdset_unlock(st);
152
if(rs->options & RRDVAR_OPTION_CUSTOM_CHART_VAR) {
153
- free(n);
153
+ freez(n);
154
return rs;
155
}
156
else {
157
error("RRDSETVAR: custom variable '%s' on chart '%s' of host '%s', conflicts with an internal chart variable", n, st->id, host->hostname);
158
- free(n);
158
+ freez(n);
159
return NULL;
160
}
161
}
database/rrdvar.c
+1
-1
@@ -137,7 +137,7 @@ static RRDVAR *rrdvar_custom_variable_create(const char *scope, avl_tree_lock *t
137
138
RRDVAR *rv = rrdvar_create_and_index(scope, tree_lock, name, RRDVAR_TYPE_CALCULATED, RRDVAR_OPTION_CUSTOM_HOST_VAR|RRDVAR_OPTION_ALLOCATED, v);
139
if(unlikely(!rv)) {
140
- free(v);
140
+ freez(v);
141
debug(D_VARIABLES, "Requested variable '%s' already exists - possibly 2 plugins are updating it at the same time.", name);
142
143
char *variable = strdupz(name);
docs/generator/buildyaml.sh
+1
-3
@@ -94,8 +94,6 @@ markdown_extensions:
94
- pymdownx.caret
95
- pymdownx.critic
96
- pymdownx.details
97
- - pymdownx.emoji:
98
- emoji_generator: !!python/name:pymdownx.emoji.to_svg
97
- pymdownx.inlinehilite
98
- pymdownx.magiclink
99
- pymdownx.mark
@@ -234,5 +232,5 @@ echo -ne "- Hacking netdata:
232
navpart 2 makeself "" "" 4
233
navpart 2 libnetdata "" "libnetdata" 4
234
navpart 2 contrib
237
-navpart 2 tests
235
+navpart 2 tests "" "" 2
236
navpart 2 diagrams/data_structures
docs/generator/requirements.txt
-1
@@ -1,3 +1,2 @@
1
mkdocs>=1.0.1
2
mkdocs-material
3
-
health/README.md
+8
-2
@@ -159,7 +159,7 @@ The simple pattern syntax and operation is explained in [simple patterns](../lib
159
160
#### Alarm line `lookup`
161
162
-This lines makes a database lookup to find a value. This result of this lookup is available as `$this`.
162
+This line makes a database lookup to find a value. This result of this lookup is available as `$this`.
163
164
The format is:
165
@@ -465,7 +465,7 @@ Although the `alarm_variables` link shows you variables for a particular chart,
465
- `$status`, which is resolved to the current status of the alarm (the current = the last
466
status, i.e. before the current database lookup and the evaluation of the `calc` line).
467
This values can be compared with `$REMOVED`, `$UNINITIALIZED`, `$UNDEFINED`, `$CLEAR`,
468
- `$WARNING`, `$CRITICAL`. These values are incremental, ie. `$status > $CLEAL` works as
468
+ `$WARNING`, `$CRITICAL`. These values are incremental, ie. `$status > $CLEAR` works as
469
expected.
470
471
- `$now`, which is resolved to current unix timestamp.
@@ -653,5 +653,11 @@ You can find the context of charts by looking up the chart in either
653
654
You can find how netdata interpreted the expressions by examining the alarm at `http://your.netdata:19999/api/v1/alarms?all`. For each expression, netdata will return the expression as given in its config file, and the same expression with additional parentheses added to indicate the evaluation flow of the expression.
655
656
+## Disabling health checks or silencing notifications at runtime
657
+
658
+The health checks can be controlled at runtime via the [health management api](../web/api/health/#health-management-api).
659
660
[]()
661
+
662
+
663
+
health/health.c
+391
-351
@@ -2,6 +2,12 @@
2
3
#include "health.h"
4
5
+struct health_cmdapi_thread_status {
6
+ int status;
7
+ ;
8
+ struct rusage rusage;
9
+};
10
+
11
unsigned int default_health_enabled = 1;
12
13
// ----------------------------------------------------------------------------
@@ -147,6 +153,12 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
153
}
154
}
155
156
+ // Check if alarm notifications are silenced
157
+ if (ae->flags & HEALTH_ENTRY_FLAG_SILENCED) {
158
+ info("Health not sending notification for alarm '%s.%s' status %s (command API has disabled notifications)", ae->chart, ae->name, rrdcalc_status2string(ae->new_status));
159
+ goto done;
160
+ }
161
+
162
static char command_to_run[ALARM_EXEC_COMMAND_LENGTH + 1];
163
pid_t command_pid;
164
@@ -381,6 +393,67 @@ static void health_main_cleanup(void *ptr) {
393
static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
394
}
395
396
+SILENCE_TYPE check_silenced(RRDCALC *rc, char* host, SILENCERS *silencers) {
397
+ SILENCER *s;
398
+ debug(D_HEALTH, "Checking if alarm was silenced via the command API. Alarm info name:%s context:%s chart:%s host:%s family:%s",
399
+ rc->name, (rc->rrdset)?rc->rrdset->context:"", rc->chart, host, (rc->rrdset)?rc->rrdset->family:"");
400
+
401
+ for (s = silencers->silencers; s!=NULL; s=s->next){
402
+ if (
403
+ (!s->alarms_pattern || (rc->name && s->alarms_pattern && simple_pattern_matches(s->alarms_pattern,rc->name))) &&
404
+ (!s->contexts_pattern || (rc->rrdset && rc->rrdset->context && s->contexts_pattern && simple_pattern_matches(s->contexts_pattern,rc->rrdset->context))) &&
405
+ (!s->hosts_pattern || (host && s->hosts_pattern && simple_pattern_matches(s->hosts_pattern,host))) &&
406
+ (!s->charts_pattern || (rc->chart && s->charts_pattern && simple_pattern_matches(s->charts_pattern,rc->chart))) &&
407
+ (!s->families_pattern || (rc->rrdset && rc->rrdset->family && s->families_pattern && simple_pattern_matches(s->families_pattern,rc->rrdset->family)))
408
+ ) {
409
+ debug(D_HEALTH, "Alarm matches command API silence entry %s:%s:%s:%s:%s", s->alarms,s->charts, s->contexts, s->hosts, s->families);
410
+ if (unlikely(silencers->stype == STYPE_NONE)) {
411
+ debug(D_HEALTH, "Alarm %s matched a silence entry, but no SILENCE or DISABLE command was issued via the command API. The match has no effect.", rc->name);
412
+ } else {
413
+ debug(D_HEALTH, "Alarm %s via the command API - name:%s context:%s chart:%s host:%s family:%s"
414
+ , (silencers->stype==STYPE_DISABLE_ALARMS)?"Disabled":"Silenced"
415
+ , rc->name
416
+ , (rc->rrdset)?rc->rrdset->context:""
417
+ , rc->chart
418
+ , host
419
+ , (rc->rrdset)?rc->rrdset->family:""
420
+ );
421
+ }
422
+ return silencers->stype;
423
+ }
424
+ }
425
+ return STYPE_NONE;
426
+}
427
+
428
+int update_disabled_silenced(RRDHOST *host, RRDCALC *rc) {
429
+ uint32_t rrdcalc_flags_old = rc->rrdcalc_flags;
430
+ // Clear the flags
431
+ rc->rrdcalc_flags &= ~(RRDCALC_FLAG_DISABLED | RRDCALC_FLAG_SILENCED);
432
+ if (unlikely(silencers->all_alarms)) {
433
+ if (silencers->stype == STYPE_DISABLE_ALARMS) rc->rrdcalc_flags |= RRDCALC_FLAG_DISABLED;
434
+ else if (silencers->stype == STYPE_SILENCE_NOTIFICATIONS) rc->rrdcalc_flags |= RRDCALC_FLAG_SILENCED;
435
+ } else {
436
+ SILENCE_TYPE st = check_silenced(rc, host->hostname, silencers);
437
+ if (st == STYPE_DISABLE_ALARMS) rc->rrdcalc_flags |= RRDCALC_FLAG_DISABLED;
438
+ else if (st == STYPE_SILENCE_NOTIFICATIONS) rc->rrdcalc_flags |= RRDCALC_FLAG_SILENCED;
439
+ }
440
+
441
+ if (rrdcalc_flags_old != rc->rrdcalc_flags) {
442
+ info("Alarm silencing changed for host '%s' alarm '%s': Disabled %s->%s Silenced %s->%s",
443
+ host->hostname,
444
+ rc->name,
445
+ (rrdcalc_flags_old & RRDCALC_FLAG_DISABLED)?"true":"false",
446
+ (rc->rrdcalc_flags & RRDCALC_FLAG_DISABLED)?"true":"false",
447
+ (rrdcalc_flags_old & RRDCALC_FLAG_SILENCED)?"true":"false",
448
+ (rc->rrdcalc_flags & RRDCALC_FLAG_SILENCED)?"true":"false"
449
+ );
450
+ }
451
+ if (rc->rrdcalc_flags & RRDCALC_FLAG_DISABLED)
452
+ return 1;
453
+ else
454
+ return 0;
455
+}
456
+
457
void *health_main(void *ptr) {
458
netdata_thread_cleanup_push(health_main_cleanup, ptr);
459
@@ -391,371 +464,338 @@ void *health_main(void *ptr) {
464
time_t hibernation_delay = config_get_number(CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for seconds", 60);
465
466
unsigned int loop = 0;
394
- while(!netdata_exit) {
395
- loop++;
396
- debug(D_HEALTH, "Health monitoring iteration no %u started", loop);
397
-
398
- int runnable = 0, apply_hibernation_delay = 0;
399
- time_t next_run = now + min_run_every;
400
- RRDCALC *rc;
401
-
402
- if(unlikely(check_if_resumed_from_suspention())) {
403
- apply_hibernation_delay = 1;
404
-
405
- info("Postponing alarm checks for %ld seconds, because it seems that the system was just resumed from suspension."
406
- , hibernation_delay
407
- );
408
- }
467
410
- rrd_rdlock();
468
+ silencers = mallocz(sizeof(SILENCERS));
469
+ silencers->all_alarms=0;
470
+ silencers->stype=STYPE_NONE;
471
+ silencers->silencers=NULL;
472
412
- RRDHOST *host;
413
- rrdhost_foreach_read(host) {
414
- if(unlikely(!host->health_enabled))
415
- continue;
416
-
417
- if(unlikely(apply_hibernation_delay)) {
418
-
419
- info("Postponing health checks for %ld seconds, on host '%s'."
420
- , hibernation_delay
421
- , host->hostname
422
- );
423
-
424
- host->health_delay_up_to = now + hibernation_delay;
425
- }
473
+ while(!netdata_exit) {
474
+ loop++;
475
+ debug(D_HEALTH, "Health monitoring iteration no %u started", loop);
476
427
- if(unlikely(host->health_delay_up_to)) {
428
- if(unlikely(now < host->health_delay_up_to))
429
- continue;
477
+ int runnable = 0, apply_hibernation_delay = 0;
478
+ time_t next_run = now + min_run_every;
479
+ RRDCALC *rc;
480
431
- info("Resuming health checks on host '%s'.", host->hostname);
432
- host->health_delay_up_to = 0;
433
- }
481
+ if (unlikely(check_if_resumed_from_suspention())) {
482
+ apply_hibernation_delay = 1;
483
435
- rrdhost_rdlock(host);
436
-
437
- // the first loop is to lookup values from the db
438
- for(rc = host->alarms; rc; rc = rc->next) {
439
- if(unlikely(!rrdcalc_isrunnable(rc, now, &next_run))) {
440
- if(unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_RUNNABLE))
441
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_RUNNABLE;
442
- continue;
443
- }
444
-
445
- runnable++;
446
- rc->old_value = rc->value;
447
- rc->rrdcalc_flags |= RRDCALC_FLAG_RUNNABLE;
448
-
449
- // ------------------------------------------------------------
450
- // if there is database lookup, do it
451
-
452
- if(unlikely(RRDCALC_HAS_DB_LOOKUP(rc))) {
453
- /* time_t old_db_timestamp = rc->db_before; */
454
- int value_is_null = 0;
455
-
456
- int ret = rrdset2value_api_v1(rc->rrdset
457
- , NULL
458
- , &rc->value
459
- , rc->dimensions
460
- , 1
461
- , rc->after
462
- , rc->before
463
- , rc->group
464
- , 0
465
- , rc->options
466
- , &rc->db_after
467
- , &rc->db_before
468
- , &value_is_null
469
- );
470
-
471
- if(unlikely(ret != 200)) {
472
- // database lookup failed
473
- rc->value = NAN;
474
- rc->rrdcalc_flags |= RRDCALC_FLAG_DB_ERROR;
475
-
476
- debug(D_HEALTH
477
- , "Health on host '%s', alarm '%s.%s': database lookup returned error %d"
478
- , host->hostname
479
- , rc->chart ? rc->chart : "NOCHART"
480
- , rc->name
481
- , ret
482
- );
483
- }
484
- else
485
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_ERROR;
486
-
487
- /* - RRDCALC_FLAG_DB_STALE not currently used
488
- if (unlikely(old_db_timestamp == rc->db_before)) {
489
- // database is stale
490
-
491
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database is stale", host->hostname, rc->chart?rc->chart:"NOCHART", rc->name);
492
-
493
- if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE))) {
494
- rc->rrdcalc_flags |= RRDCALC_FLAG_DB_STALE;
495
- error("Health on host '%s', alarm '%s.%s': database is stale", host->hostname, rc->chart?rc->chart:"NOCHART", rc->name);
496
- }
497
- }
498
- else if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE))
499
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_STALE;
500
- */
501
-
502
- if(unlikely(value_is_null)) {
503
- // collected value is null
504
- rc->value = NAN;
505
- rc->rrdcalc_flags |= RRDCALC_FLAG_DB_NAN;
506
-
507
- debug(D_HEALTH
508
- , "Health on host '%s', alarm '%s.%s': database lookup returned empty value (possibly value is not collected yet)"
509
- , host->hostname
510
- , rc->chart ? rc->chart : "NOCHART"
511
- , rc->name
512
- );
513
- }
514
- else
515
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_NAN;
516
-
517
- debug(D_HEALTH
518
- , "Health on host '%s', alarm '%s.%s': database lookup gave value " CALCULATED_NUMBER_FORMAT
519
- , host->hostname
520
- , rc->chart ? rc->chart : "NOCHART"
521
- , rc->name
522
- , rc->value
523
- );
524
- }
525
-
526
- // ------------------------------------------------------------
527
- // if there is calculation expression, run it
528
-
529
- if(unlikely(rc->calculation)) {
530
- if(unlikely(!expression_evaluate(rc->calculation))) {
531
- // calculation failed
532
- rc->value = NAN;
533
- rc->rrdcalc_flags |= RRDCALC_FLAG_CALC_ERROR;
534
-
535
- debug(D_HEALTH
536
- , "Health on host '%s', alarm '%s.%s': expression '%s' failed: %s"
537
- , host->hostname
538
- , rc->chart ? rc->chart : "NOCHART"
539
- , rc->name
540
- , rc->calculation->parsed_as
541
- , buffer_tostring(rc->calculation->error_msg)
542
- );
543
- }
544
- else {
545
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_CALC_ERROR;
546
-
547
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': expression '%s' gave value " CALCULATED_NUMBER_FORMAT ": %s (source: %s)"
548
- , host->hostname
549
- , rc->chart ? rc->chart : "NOCHART"
550
- , rc->name
551
- , rc->calculation->parsed_as
552
- , rc->calculation->result
553
- , buffer_tostring(rc->calculation->error_msg)
554
- , rc->source
555
- );
556
-
557
- rc->value = rc->calculation->result;
558
-
559
- if(rc->local) rc->local->last_updated = now;
560
- if(rc->family) rc->family->last_updated = now;
561
- if(rc->hostid) rc->hostid->last_updated = now;
562
- if(rc->hostname) rc->hostname->last_updated = now;
563
- }
564
- }
565
- }
566
- rrdhost_unlock(host);
567
-
568
- if(unlikely(runnable && !netdata_exit)) {
569
- rrdhost_rdlock(host);
570
-
571
- for(rc = host->alarms; rc; rc = rc->next) {
572
- if(unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_RUNNABLE)))
573
- continue;
574
-
575
- RRDCALC_STATUS warning_status = RRDCALC_STATUS_UNDEFINED;
576
- RRDCALC_STATUS critical_status = RRDCALC_STATUS_UNDEFINED;
577
-
578
- // --------------------------------------------------------
579
- // check the warning expression
580
-
581
- if(likely(rc->warning)) {
582
- if(unlikely(!expression_evaluate(rc->warning))) {
583
- // calculation failed
584
- rc->rrdcalc_flags |= RRDCALC_FLAG_WARN_ERROR;
585
-
586
- debug(D_HEALTH
587
- , "Health on host '%s', alarm '%s.%s': warning expression failed with error: %s"
588
- , host->hostname
589
- , rc->chart ? rc->chart : "NOCHART"
590
- , rc->name
591
- , buffer_tostring(rc->warning->error_msg)
592
- );
593
- }
594
- else {
595
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_WARN_ERROR;
596
- debug(D_HEALTH
597
- , "Health on host '%s', alarm '%s.%s': warning expression gave value " CALCULATED_NUMBER_FORMAT ": %s (source: %s)"
598
- , host->hostname
599
- , rc->chart ? rc->chart : "NOCHART"
600
- , rc->name
601
- , rc->warning->result
602
- , buffer_tostring(rc->warning->error_msg)
603
- , rc->source
604
- );
605
- warning_status = rrdcalc_value2status(rc->warning->result);
606
- }
607
- }
608
-
609
- // --------------------------------------------------------
610
- // check the critical expression
611
-
612
- if(likely(rc->critical)) {
613
- if(unlikely(!expression_evaluate(rc->critical))) {
614
- // calculation failed
615
- rc->rrdcalc_flags |= RRDCALC_FLAG_CRIT_ERROR;
616
-
617
- debug(D_HEALTH
618
- , "Health on host '%s', alarm '%s.%s': critical expression failed with error: %s"
619
- , host->hostname
620
- , rc->chart ? rc->chart : "NOCHART"
621
- , rc->name
622
- , buffer_tostring(rc->critical->error_msg)
623
- );
624
- }
625
- else {
626
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_CRIT_ERROR;
627
- debug(D_HEALTH
628
- , "Health on host '%s', alarm '%s.%s': critical expression gave value " CALCULATED_NUMBER_FORMAT ": %s (source: %s)"
629
- , host->hostname
630
- , rc->chart ? rc->chart : "NOCHART"
631
- , rc->name
632
- , rc->critical->result
633
- , buffer_tostring(rc->critical->error_msg)
634
- , rc->source
635
- );
636
- critical_status = rrdcalc_value2status(rc->critical->result);
637
- }
638
- }
639
-
640
- // --------------------------------------------------------
641
- // decide the final alarm status
642
-
643
- RRDCALC_STATUS status = RRDCALC_STATUS_UNDEFINED;
644
-
645
- switch(warning_status) {
646
- case RRDCALC_STATUS_CLEAR:
647
- status = RRDCALC_STATUS_CLEAR;
648
- break;
649
-
650
- case RRDCALC_STATUS_RAISED:
651
- status = RRDCALC_STATUS_WARNING;
652
- break;
653
-
654
- default:
655
- break;
656
- }
657
-
658
- switch(critical_status) {
659
- case RRDCALC_STATUS_CLEAR:
660
- if(status == RRDCALC_STATUS_UNDEFINED)
661
- status = RRDCALC_STATUS_CLEAR;
662
- break;
663
-
664
- case RRDCALC_STATUS_RAISED:
665
- status = RRDCALC_STATUS_CRITICAL;
666
- break;
667
-
668
- default:
669
- break;
670
- }
671
-
672
- // --------------------------------------------------------
673
- // check if the new status and the old differ
674
-
675
- if(status != rc->status) {
676
- int delay = 0;
677
-
678
- // apply trigger hysteresis
679
-
680
- if(now > rc->delay_up_to_timestamp) {
681
- rc->delay_up_current = rc->delay_up_duration;
682
- rc->delay_down_current = rc->delay_down_duration;
683
- rc->delay_last = 0;
684
- rc->delay_up_to_timestamp = 0;
685
- }
686
- else {
687
- rc->delay_up_current = (int) (rc->delay_up_current * rc->delay_multiplier);
688
- if(rc->delay_up_current > rc->delay_max_duration)
689
- rc->delay_up_current = rc->delay_max_duration;
690
-
691
- rc->delay_down_current = (int) (rc->delay_down_current * rc->delay_multiplier);
692
- if(rc->delay_down_current > rc->delay_max_duration)
693
- rc->delay_down_current = rc->delay_max_duration;
694
- }
695
-
696
- if(status > rc->status)
697
- delay = rc->delay_up_current;
698
- else
699
- delay = rc->delay_down_current;
700
-
701
- // COMMENTED: because we do need to send raising alarms
702
- // if(now + delay < rc->delay_up_to_timestamp)
703
- // delay = (int)(rc->delay_up_to_timestamp - now);
704
-
705
- rc->delay_last = delay;
706
- rc->delay_up_to_timestamp = now + delay;
707
-
708
- // add the alarm into the log
709
-
710
- health_alarm_log(
711
- host
712
- , rc->id
713
- , rc->next_event_id++
714
- , now
715
- , rc->name
716
- , rc->rrdset->id
717
- , rc->rrdset->family
718
- , rc->exec
719
- , rc->recipient
720
- , now - rc->last_status_change
721
- , rc->old_value
722
- , rc->value
723
- , rc->status
724
- , status
725
- , rc->source
726
- , rc->units
727
- , rc->info
728
- , rc->delay_last
729
- , (rc->options & RRDCALC_FLAG_NO_CLEAR_NOTIFICATION) ? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0
730
- );
484
+ info("Postponing alarm checks for %ld seconds, because it seems that the system was just resumed from suspension.",
485
+ hibernation_delay
486
+ );
487
+ }
488
732
- rc->last_status_change = now;
733
- rc->status = status;
734
- }
489
+ if (unlikely(silencers->all_alarms && silencers->stype == STYPE_DISABLE_ALARMS)) {
490
+ static int logged=0;
491
+ if (!logged) {
492
+ info("Skipping health checks, because all alarms are disabled via a %s command.",
493
+ HEALTH_CMDAPI_CMD_DISABLEALL);
494
+ logged = 1;
495
+ }
496
+ }
497
736
- rc->last_updated = now;
737
- rc->next_update = now + rc->update_every;
498
+ rrd_rdlock();
499
739
- if(next_run > rc->next_update)
740
- next_run = rc->next_update;
741
- }
500
+ RRDHOST *host;
501
+ rrdhost_foreach_read(host) {
502
+ if (unlikely(!host->health_enabled))
503
+ continue;
504
+
505
+ if (unlikely(apply_hibernation_delay)) {
506
+
507
+ info("Postponing health checks for %ld seconds, on host '%s'.", hibernation_delay, host->hostname
508
+ );
509
+
510
+ host->health_delay_up_to = now + hibernation_delay;
511
+ }
512
+
513
+ if (unlikely(host->health_delay_up_to)) {
514
+ if (unlikely(now < host->health_delay_up_to))
515
+ continue;
516
+
517
+ info("Resuming health checks on host '%s'.", host->hostname);
518
+ host->health_delay_up_to = 0;
519
+ }
520
+
521
+ rrdhost_rdlock(host);
522
+
523
+ // the first loop is to lookup values from the db
524
+ for (rc = host->alarms; rc; rc = rc->next) {
525
+
526
+ if (update_disabled_silenced(host, rc))
527
+ continue;
528
+
529
+ if (unlikely(!rrdcalc_isrunnable(rc, now, &next_run))) {
530
+ if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_RUNNABLE))
531
+ rc->rrdcalc_flags &= ~RRDCALC_FLAG_RUNNABLE;
532
+ continue;
533
+ }
534
+
535
+ runnable++;
536
+ rc->old_value = rc->value;
537
+ rc->rrdcalc_flags |= RRDCALC_FLAG_RUNNABLE;
538
+
539
+ // ------------------------------------------------------------
540
+ // if there is database lookup, do it
541
+
542
+ if (unlikely(RRDCALC_HAS_DB_LOOKUP(rc))) {
543
+ /* time_t old_db_timestamp = rc->db_before; */
544
+ int value_is_null = 0;
545
+
546
+ int ret = rrdset2value_api_v1(rc->rrdset, NULL, &rc->value, rc->dimensions, 1, rc->after,
547
+ rc->before, rc->group, 0, rc->options, &rc->db_after,
548
+ &rc->db_before, &value_is_null
549
+ );
550
+
551
+ if (unlikely(ret != 200)) {
552
+ // database lookup failed
553
+ rc->value = NAN;
554
+ rc->rrdcalc_flags |= RRDCALC_FLAG_DB_ERROR;
555
+
556
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database lookup returned error %d",
557
+ host->hostname, rc->chart ? rc->chart : "NOCHART", rc->name, ret
558
+ );
559
+ } else
560
+ rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_ERROR;
561
+
562
+ /* - RRDCALC_FLAG_DB_STALE not currently used
563
+ if (unlikely(old_db_timestamp == rc->db_before)) {
564
+ // database is stale
565
+
566
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database is stale", host->hostname, rc->chart?rc->chart:"NOCHART", rc->name);
567
+
568
+ if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE))) {
569
+ rc->rrdcalc_flags |= RRDCALC_FLAG_DB_STALE;
570
+ error("Health on host '%s', alarm '%s.%s': database is stale", host->hostname, rc->chart?rc->chart:"NOCHART", rc->name);
571
+ }
572
+ }
573
+ else if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE))
574
+ rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_STALE;
575
+ */
576
+
577
+ if (unlikely(value_is_null)) {
578
+ // collected value is null
579
+ rc->value = NAN;
580
+ rc->rrdcalc_flags |= RRDCALC_FLAG_DB_NAN;
581
+
582
+ debug(D_HEALTH,
583
+ "Health on host '%s', alarm '%s.%s': database lookup returned empty value (possibly value is not collected yet)",
584
+ host->hostname, rc->chart ? rc->chart : "NOCHART", rc->name
585
+ );
586
+ } else
587
+ rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_NAN;
588
+
589
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database lookup gave value "
590
+ CALCULATED_NUMBER_FORMAT, host->hostname, rc->chart ? rc->chart : "NOCHART", rc->name,
591
+ rc->value
592
+ );
593
+ }
594
+
595
+ // ------------------------------------------------------------
596
+ // if there is calculation expression, run it
597
+
598
+ if (unlikely(rc->calculation)) {
599
+ if (unlikely(!expression_evaluate(rc->calculation))) {
600
+ // calculation failed
601
+ rc->value = NAN;
602
+ rc->rrdcalc_flags |= RRDCALC_FLAG_CALC_ERROR;
603
+
604
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': expression '%s' failed: %s",
605
+ host->hostname, rc->chart ? rc->chart : "NOCHART", rc->name,
606
+ rc->calculation->parsed_as, buffer_tostring(rc->calculation->error_msg)
607
+ );
608
+ } else {
609
+ rc->rrdcalc_flags &= ~RRDCALC_FLAG_CALC_ERROR;
610
+
611
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': expression '%s' gave value "
612
+ CALCULATED_NUMBER_FORMAT
613
+ ": %s (source: %s)", host->hostname, rc->chart ? rc->chart : "NOCHART", rc->name,
614
+ rc->calculation->parsed_as, rc->calculation->result,
615
+ buffer_tostring(rc->calculation->error_msg), rc->source
616
+ );
617
+
618
+ rc->value = rc->calculation->result;
619
+
620
+ if (rc->local) rc->local->last_updated = now;
621
+ if (rc->family) rc->family->last_updated = now;
622
+ if (rc->hostid) rc->hostid->last_updated = now;
623
+ if (rc->hostname) rc->hostname->last_updated = now;
624
+ }
625
+ }
626
+ }
627
+
628
+ rrdhost_unlock(host);
629
+
630
+ if (unlikely(runnable && !netdata_exit)) {
631
+ rrdhost_rdlock(host);
632
+
633
+ for (rc = host->alarms; rc; rc = rc->next) {
634
+ if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_RUNNABLE)))
635
+ continue;
636
+
637
+ if (rc->rrdcalc_flags & RRDCALC_FLAG_DISABLED) {
638
+ continue;
639
+ }
640
+ RRDCALC_STATUS warning_status = RRDCALC_STATUS_UNDEFINED;
641
+ RRDCALC_STATUS critical_status = RRDCALC_STATUS_UNDEFINED;
642
+
643
+ // --------------------------------------------------------
644
+ // check the warning expression
645
+
646
+ if (likely(rc->warning)) {
647
+ if (unlikely(!expression_evaluate(rc->warning))) {
648
+ // calculation failed
649
+ rc->rrdcalc_flags |= RRDCALC_FLAG_WARN_ERROR;
650
+
651
+ debug(D_HEALTH,
652
+ "Health on host '%s', alarm '%s.%s': warning expression failed with error: %s",
653
+ host->hostname, rc->chart ? rc->chart : "NOCHART", rc->name,
654
+ buffer_tostring(rc->warning->error_msg)
655
+ );
656
+ } else {
657
+ rc->rrdcalc_flags &= ~RRDCALC_FLAG_WARN_ERROR;
658
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': warning expression gave value "
659
+ CALCULATED_NUMBER_FORMAT
660
+ ": %s (source: %s)", host->hostname, rc->chart ? rc->chart : "NOCHART",
661
+ rc->name, rc->warning->result, buffer_tostring(rc->warning->error_msg), rc->source
662
+ );
663
+ warning_status = rrdcalc_value2status(rc->warning->result);
664
+ }
665
+ }
666
+
667
+ // --------------------------------------------------------
668
+ // check the critical expression
669
+
670
+ if (likely(rc->critical)) {
671
+ if (unlikely(!expression_evaluate(rc->critical))) {
672
+ // calculation failed
673
+ rc->rrdcalc_flags |= RRDCALC_FLAG_CRIT_ERROR;
674
+
675
+ debug(D_HEALTH,
676
+ "Health on host '%s', alarm '%s.%s': critical expression failed with error: %s",
677
+ host->hostname, rc->chart ? rc->chart : "NOCHART", rc->name,
678
+ buffer_tostring(rc->critical->error_msg)
679
+ );
680
+ } else {
681
+ rc->rrdcalc_flags &= ~RRDCALC_FLAG_CRIT_ERROR;
682
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': critical expression gave value "
683
+ CALCULATED_NUMBER_FORMAT
684
+ ": %s (source: %s)", host->hostname, rc->chart ? rc->chart : "NOCHART",
685
+ rc->name, rc->critical->result, buffer_tostring(rc->critical->error_msg),
686
+ rc->source
687
+ );
688
+ critical_status = rrdcalc_value2status(rc->critical->result);
689
+ }
690
+ }
691
+
692
+ // --------------------------------------------------------
693
+ // decide the final alarm status
694
+
695
+ RRDCALC_STATUS status = RRDCALC_STATUS_UNDEFINED;
696
+
697
+ switch (warning_status) {
698
+ case RRDCALC_STATUS_CLEAR:
699
+ status = RRDCALC_STATUS_CLEAR;
700
+ break;
701
+
702
+ case RRDCALC_STATUS_RAISED:
703
+ status = RRDCALC_STATUS_WARNING;
704
+ break;
705
+
706
+ default:
707
+ break;
708
+ }
709
+
710
+ switch (critical_status) {
711
+ case RRDCALC_STATUS_CLEAR:
712
+ if (status == RRDCALC_STATUS_UNDEFINED)
713
+ status = RRDCALC_STATUS_CLEAR;
714
+ break;
715
+
716
+ case RRDCALC_STATUS_RAISED:
717
+ status = RRDCALC_STATUS_CRITICAL;
718
+ break;
719
+
720
+ default:
721
+ break;
722
+ }
723
+
724
+ // --------------------------------------------------------
725
+ // check if the new status and the old differ
726
+
727
+ if (status != rc->status) {
728
+ int delay = 0;
729
+
730
+ // apply trigger hysteresis
731
+
732
+ if (now > rc->delay_up_to_timestamp) {
733
+ rc->delay_up_current = rc->delay_up_duration;
734
+ rc->delay_down_current = rc->delay_down_duration;
735
+ rc->delay_last = 0;
736
+ rc->delay_up_to_timestamp = 0;
737
+ } else {
738
+ rc->delay_up_current = (int) (rc->delay_up_current * rc->delay_multiplier);
739
+ if (rc->delay_up_current > rc->delay_max_duration)
740
+ rc->delay_up_current = rc->delay_max_duration;
741
+
742
+ rc->delay_down_current = (int) (rc->delay_down_current * rc->delay_multiplier);
743
+ if (rc->delay_down_current > rc->delay_max_duration)
744
+ rc->delay_down_current = rc->delay_max_duration;
745
+ }
746
+
747
+ if (status > rc->status)
748
+ delay = rc->delay_up_current;
749
+ else
750
+ delay = rc->delay_down_current;
751
+
752
+ // COMMENTED: because we do need to send raising alarms
753
+ // if(now + delay < rc->delay_up_to_timestamp)
754
+ // delay = (int)(rc->delay_up_to_timestamp - now);
755
+
756
+ rc->delay_last = delay;
757
+ rc->delay_up_to_timestamp = now + delay;
758
+
759
+ health_alarm_log(
760
+ host, rc->id, rc->next_event_id++, now, rc->name, rc->rrdset->id,
761
+ rc->rrdset->family, rc->exec, rc->recipient, now - rc->last_status_change,
762
+ rc->old_value, rc->value, rc->status, status, rc->source, rc->units, rc->info,
763
+ rc->delay_last,
764
+ (
765
+ ((rc->options & RRDCALC_FLAG_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
766
+ ((rc->rrdcalc_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0)
767
+ )
768
+
769
+ );
770
+
771
+ rc->last_status_change = now;
772
+ rc->status = status;
773
+ }
774
+
775
+ rc->last_updated = now;
776
+ rc->next_update = now + rc->update_every;
777
+
778
+ if (next_run > rc->next_update)
779
+ next_run = rc->next_update;
780
+ }
781
+
782
+ rrdhost_unlock(host);
783
+ }
784
743
- rrdhost_unlock(host);
744
- }
785
+ if (unlikely(netdata_exit))
786
+ break;
787
746
- if(unlikely(netdata_exit))
747
- break;
788
+ // execute notifications
789
+ // and cleanup
790
+ health_alarm_log_process(host);
791
749
- // execute notifications
750
- // and cleanup
751
- health_alarm_log_process(host);
792
+ if (unlikely(netdata_exit))
793
+ break;
794
753
- if(unlikely(netdata_exit))
754
- break;
795
+ } /* rrdhost_foreach */
796
756
- } /* rrdhost_foreach */
797
+ rrd_unlock();
798
758
- rrd_unlock();
799
800
if(unlikely(netdata_exit))
801
break;
health/health.h
+68
-2
@@ -22,9 +22,74 @@ extern unsigned int default_health_enabled;
22
#define HEALTH_ENTRY_FLAG_UPDATED 0x00000002
23
#define HEALTH_ENTRY_FLAG_EXEC_RUN 0x00000004
24
#define HEALTH_ENTRY_FLAG_EXEC_FAILED 0x00000008
25
+#define HEALTH_ENTRY_FLAG_SILENCED 0x00000008
26
+
27
#define HEALTH_ENTRY_FLAG_SAVED 0x10000000
28
#define HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION 0x80000000
29
30
+#ifndef HEALTH_LISTEN_PORT
31
+#define HEALTH_LISTEN_PORT 19998
32
+#endif
33
+
34
+#ifndef HEALTH_LISTEN_BACKLOG
35
+#define HEALTH_LISTEN_BACKLOG 4096
36
+#endif
37
+
38
+#define HEALTH_ALARM_KEY "alarm"
39
+#define HEALTH_TEMPLATE_KEY "template"
40
+#define HEALTH_ON_KEY "on"
41
+#define HEALTH_CONTEXT_KEY "context"
42
+#define HEALTH_CHART_KEY "chart"
43
+#define HEALTH_HOST_KEY "hosts"
44
+#define HEALTH_OS_KEY "os"
45
+#define HEALTH_FAMILIES_KEY "families"
46
+#define HEALTH_LOOKUP_KEY "lookup"
47
+#define HEALTH_CALC_KEY "calc"
48
+#define HEALTH_EVERY_KEY "every"
49
+#define HEALTH_GREEN_KEY "green"
50
+#define HEALTH_RED_KEY "red"
51
+#define HEALTH_WARN_KEY "warn"
52
+#define HEALTH_CRIT_KEY "crit"
53
+#define HEALTH_EXEC_KEY "exec"
54
+#define HEALTH_RECIPIENT_KEY "to"
55
+#define HEALTH_UNITS_KEY "units"
56
+#define HEALTH_INFO_KEY "info"
57
+#define HEALTH_DELAY_KEY "delay"
58
+#define HEALTH_OPTIONS_KEY "options"
59
+
60
+typedef struct silencer {
61
+ char *alarms;
62
+ SIMPLE_PATTERN *alarms_pattern;
63
+
64
+ char *hosts;
65
+ SIMPLE_PATTERN *hosts_pattern;
66
+
67
+ char *contexts;
68
+ SIMPLE_PATTERN *contexts_pattern;
69
+
70
+ char *charts;
71
+ SIMPLE_PATTERN *charts_pattern;
72
+
73
+ char *families;
74
+ SIMPLE_PATTERN *families_pattern;
75
+
76
+ struct silencer *next;
77
+} SILENCER;
78
+
79
+typedef enum silence_type {
80
+ STYPE_NONE,
81
+ STYPE_DISABLE_ALARMS,
82
+ STYPE_SILENCE_NOTIFICATIONS
83
+} SILENCE_TYPE;
84
+
85
+typedef struct silencers {
86
+ int all_alarms;
87
+ SILENCE_TYPE stype;
88
+ SILENCER *silencers;
89
+} SILENCERS;
90
+
91
+SILENCERS *silencers;
92
+
93
extern void health_init(void);
94
extern void *health_main(void *ptr);
95
@@ -62,8 +127,7 @@ extern void health_alarm_log(
127
const char *units,
128
const char *info,
129
int delay,
65
- uint32_t flags
66
-);
130
+ uint32_t flags);
131
132
extern void health_readdir(RRDHOST *host, const char *user_path, const char *stock_path, const char *subpath);
133
extern char *health_user_config_dir(void);
@@ -73,4 +137,6 @@ extern void health_alarm_log_free(RRDHOST *host);
137
138
extern void health_alarm_log_free_one_nochecks_nounlink(ALARM_ENTRY *ae);
139
140
+extern void *health_cmdapi_thread(void *ptr);
141
+
142
#endif //NETDATA_HEALTH_H
health/health_json.c
+6
@@ -43,6 +43,7 @@ static inline void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae, R
43
"\t\t\"updates_id\": %u,\n"
44
"\t\t\"value_string\": \"%s\",\n"
45
"\t\t\"old_value_string\": \"%s\",\n"
46
+ "\t\t\"silenced\": \"%s\",\n"
47
, host->hostname
48
, ae->unique_id
49
, ae->alarm_id
@@ -70,6 +71,7 @@ static inline void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae, R
71
, ae->updates_id
72
, ae->new_value_string
73
, ae->old_value_string
74
+ , (ae->flags & HEALTH_ENTRY_FLAG_SILENCED)?"true":"false"
75
);
76
77
health_string2json(wb, "\t\t", "info", ae->info?ae->info:"", ",\n");
@@ -120,6 +122,8 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
122
"\t\t\t\"chart\": \"%s\",\n"
123
"\t\t\t\"family\": \"%s\",\n"
124
"\t\t\t\"active\": %s,\n"
125
+ "\t\t\t\"disabled\": %s,\n"
126
+ "\t\t\t\"silenced\": %s,\n"
127
"\t\t\t\"exec\": \"%s\",\n"
128
"\t\t\t\"recipient\": \"%s\",\n"
129
"\t\t\t\"source\": \"%s\",\n"
@@ -143,6 +147,8 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
147
, rc->chart
148
, (rc->rrdset && rc->rrdset->family)?rc->rrdset->family:""
149
, (rc->rrdset)?"true":"false"
150
+ , (rc->rrdcalc_flags & RRDCALC_FLAG_DISABLED)?"true":"false"
151
+ , (rc->rrdcalc_flags & RRDCALC_FLAG_SILENCED)?"true":"false"
152
, rc->exec?rc->exec:host->health_default_exec
153
, rc->recipient?rc->recipient:host->health_default_recipient
154
, rc->source
health/health_log.c
-1
@@ -396,7 +396,6 @@ inline void health_alarm_log(
396
ae->duration = duration;
397
ae->delay = delay;
398
ae->delay_up_to_timestamp = when + delay;
399
-
399
ae->flags |= flags;
400
401
if(ae->old_status == RRDCALC_STATUS_WARNING || ae->old_status == RRDCALC_STATUS_CRITICAL)
libnetdata/socket/socket.c
+60
-18
@@ -248,7 +248,7 @@ int create_listen_socket6(int socktype, uint32_t scope_id, const char *ip, int p
248
return sock;
249
}
250
251
-static inline int listen_sockets_add(LISTEN_SOCKETS *sockets, int fd, int family, int socktype, const char *protocol, const char *ip, uint16_t port) {
251
+static inline int listen_sockets_add(LISTEN_SOCKETS *sockets, int fd, int family, int socktype, const char *protocol, const char *ip, uint16_t port, int acl_flags) {
252
if(sockets->opened >= MAX_LISTEN_FDS) {
253
error("LISTENER: Too many listening sockets. Failed to add listening %s socket at ip '%s' port %d, protocol %s, socktype %d", protocol, ip, port, protocol, socktype);
254
close(fd);
@@ -259,6 +259,7 @@ static inline int listen_sockets_add(LISTEN_SOCKETS *sockets, int fd, int family
259
sockets->fds_types[sockets->opened] = socktype;
260
sockets->fds_families[sockets->opened] = family;
261
sockets->fds_names[sockets->opened] = strdup_client_description(family, protocol, ip, port);
262
+ sockets->fds_acl_flags[sockets->opened] = acl_flags;
263
264
sockets->opened++;
265
return 0;
@@ -300,8 +301,20 @@ void listen_sockets_close(LISTEN_SOCKETS *sockets) {
301
sockets->failed = 0;
302
}
303
304
+WEB_CLIENT_ACL read_acl(char *st) {
305
+ if (!strcmp(st,"dashboard")) return WEB_CLIENT_ACL_DASHBOARD;
306
+ if (!strcmp(st,"registry")) return WEB_CLIENT_ACL_REGISTRY;
307
+ if (!strcmp(st,"badges")) return WEB_CLIENT_ACL_BADGE;
308
+ if (!strcmp(st,"management")) return WEB_CLIENT_ACL_MGMT;
309
+ if (!strcmp(st,"streaming")) return WEB_CLIENT_ACL_STREAMING;
310
+ if (!strcmp(st,"netdata.conf")) return WEB_CLIENT_ACL_NETDATACONF;
311
+ return WEB_CLIENT_ACL_NONE;
312
+}
313
+
314
static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition, uint16_t default_port, int listen_backlog) {
315
int added = 0;
316
+ WEB_CLIENT_ACL acl_flags = WEB_CLIENT_ACL_NONE;
317
+
318
struct addrinfo hints;
319
struct addrinfo *result = NULL, *rp = NULL;
320
@@ -311,10 +324,11 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
324
char buffer2[10 + 1];
325
snprintfz(buffer2, 10, "%d", default_port);
326
314
- char *ip = buffer, *port = buffer2, *interface = "";;
327
+ char *ip = buffer, *port = buffer2, *interface = "", *portconfig;;
328
329
int protocol = IPPROTO_TCP, socktype = SOCK_STREAM;
330
const char *protocol_str = "tcp";
331
+ int unix_socket=0;
332
333
if(strncmp(ip, "tcp:", 4) == 0) {
334
ip += 4;
@@ -329,20 +343,10 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
343
protocol_str = "udp";
344
}
345
else if(strncmp(ip, "unix:", 5) == 0) {
332
- char *path = ip + 5;
346
+ ip += 5;
347
socktype = SOCK_STREAM;
348
protocol_str = "unix";
335
-
336
- int fd = create_listen_socket_unix(path, listen_backlog);
337
- if (fd == -1) {
338
- error("LISTENER: Cannot create unix socket '%s'", path);
339
- sockets->failed++;
340
- }
341
- else {
342
- listen_sockets_add(sockets, fd, AF_UNIX, socktype, protocol_str, path, 0);
343
- added++;
344
- }
345
- return added;
349
+ unix_socket=1;
350
}
351
352
char *e = ip;
@@ -355,21 +359,53 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
359
}
360
}
361
else {
358
- while(*e && *e != ':' && *e != '%') e++;
362
+ while(*e && *e != ':' && *e != '%' && *e != '=') e++;
363
}
364
365
if(*e == '%') {
366
*e = '\0';
367
e++;
368
interface = e;
365
- while(*e && *e != ':') e++;
369
+ while(*e && *e != ':' && *e != '=') e++;
370
}
371
372
if(*e == ':') {
373
port = e + 1;
374
*e = '\0';
375
+ while(*e && *e != '=') e++;
376
}
377
378
+ if(*e == '=') {
379
+ *e='\0';
380
+ e++;
381
+ portconfig = e;
382
+ while (*e != '\0') {
383
+ if (*e == '|') {
384
+ *e = '\0';
385
+ acl_flags |= read_acl(portconfig);
386
+ e++;
387
+ portconfig = e;
388
+ continue;
389
+ }
390
+ e++;
391
+ }
392
+ acl_flags |= read_acl(portconfig);
393
+ } else {
394
+ acl_flags = WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_REGISTRY | WEB_CLIENT_ACL_BADGE | WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_NETDATACONF | WEB_CLIENT_ACL_STREAMING;
395
+ }
396
+
397
+ if (unix_socket) {
398
+ int fd = create_listen_socket_unix(port, listen_backlog);
399
+ if (fd == -1) {
400
+ error("LISTENER: Cannot create unix socket '%s'", port);
401
+ sockets->failed++;
402
+ } else {
403
+ listen_sockets_add(sockets, fd, AF_UNIX, socktype, protocol_str, port, 0, acl_flags);
404
+ added++;
405
+ }
406
+ return added;
407
+ }
408
+
409
uint32_t scope_id = 0;
410
if(*interface) {
411
scope_id = if_nametoindex(interface);
@@ -435,7 +471,7 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
471
sockets->failed++;
472
}
473
else {
438
- listen_sockets_add(sockets, fd, family, socktype, protocol_str, rip, rport);
474
+ listen_sockets_add(sockets, fd, family, socktype, protocol_str, rip, rport, acl_flags);
475
added++;
476
}
477
}
@@ -975,6 +1011,7 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien
1011
inline POLLINFO *poll_add_fd(POLLJOB *p
1012
, int fd
1013
, int socktype
1014
+ , WEB_CLIENT_ACL port_acl
1015
, uint32_t flags
1016
, const char *client_ip
1017
, const char *client_port
@@ -1013,6 +1050,8 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
1050
p->inf[i].slot = (size_t)i;
1051
p->inf[i].flags = 0;
1052
p->inf[i].socktype = -1;
1053
+ p->inf[i].port_acl = -1;
1054
+
1055
p->inf[i].client_ip = NULL;
1056
p->inf[i].client_port = NULL;
1057
p->inf[i].del_callback = p->del_callback;
@@ -1042,6 +1081,7 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
1081
pi->fd = fd;
1082
pi->p = p;
1083
pi->socktype = socktype;
1084
+ pi->port_acl = port_acl;
1085
pi->flags = flags;
1086
pi->next = NULL;
1087
pi->client_ip = strdupz(client_ip);
@@ -1272,6 +1312,7 @@ static void poll_events_process(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, sho
1312
poll_add_fd(p
1313
, nfd
1314
, SOCK_STREAM
1315
+ , pi->port_acl
1316
, POLLINFO_FLAG_CLIENT_SOCKET
1317
, client_ip
1318
, client_port
@@ -1414,6 +1455,7 @@ void poll_events(LISTEN_SOCKETS *sockets
1455
POLLINFO *pi = poll_add_fd(&p
1456
, sockets->fds[i]
1457
, sockets->fds_types[i]
1458
+ , sockets->fds_acl_flags[i]
1459
, POLLINFO_FLAG_SERVER_SOCKET
1460
, (sockets->fds_names[i])?sockets->fds_names[i]:"UNKNOWN"
1461
, ""
@@ -1457,7 +1499,7 @@ void poll_events(LISTEN_SOCKETS *sockets
1499
}
1500
1501
usec_t dt_usec = next_timer_usec - now_usec;
1460
- if(dt_usec > 1000 * USEC_PER_MS)
1502
+ if(dt_usec < 1000 * USEC_PER_MS)
1503
timeout_ms = 1000;
1504
else
1505
timeout_ms = (int)(dt_usec / USEC_PER_MS);
libnetdata/socket/socket.h
+21
@@ -9,6 +9,24 @@
9
#define MAX_LISTEN_FDS 50
10
#endif
11
12
+typedef enum web_client_acl {
13
+ WEB_CLIENT_ACL_NONE = 0,
14
+ WEB_CLIENT_ACL_NOCHECK = 0,
15
+ WEB_CLIENT_ACL_DASHBOARD = 1 << 0,
16
+ WEB_CLIENT_ACL_REGISTRY = 1 << 1,
17
+ WEB_CLIENT_ACL_BADGE = 1 << 2,
18
+ WEB_CLIENT_ACL_MGMT = 1 << 3,
19
+ WEB_CLIENT_ACL_STREAMING = 1 << 4,
20
+ WEB_CLIENT_ACL_NETDATACONF = 1 << 5
21
+} WEB_CLIENT_ACL;
22
+
23
+#define web_client_can_access_dashboard(w) ((w)->acl & WEB_CLIENT_ACL_DASHBOARD)
24
+#define web_client_can_access_registry(w) ((w)->acl & WEB_CLIENT_ACL_REGISTRY)
25
+#define web_client_can_access_badges(w) ((w)->acl & WEB_CLIENT_ACL_BADGE)
26
+#define web_client_can_access_mgmt(w) ((w)->acl & WEB_CLIENT_ACL_MGMT)
27
+#define web_client_can_access_stream(w) ((w)->acl & WEB_CLIENT_ACL_STREAMING)
28
+#define web_client_can_access_netdataconf(w) ((w)->acl & WEB_CLIENT_ACL_NETDATACONF)
29
+
30
typedef struct listen_sockets {
31
struct config *config; // the config file to use
32
const char *config_section; // the netdata configuration section to read settings from
@@ -22,6 +40,7 @@ typedef struct listen_sockets {
40
char *fds_names[MAX_LISTEN_FDS]; // descriptions for the open sockets
41
int fds_types[MAX_LISTEN_FDS]; // the socktype for the open sockets (SOCK_STREAM, SOCK_DGRAM)
42
int fds_families[MAX_LISTEN_FDS]; // the family of the open sockets (AF_UNIX, AF_INET, AF_INET6)
43
+ WEB_CLIENT_ACL fds_acl_flags[MAX_LISTEN_FDS]; // the acl to apply to the open sockets (dashboard, badges, streaming, netdata.conf, management)
44
} LISTEN_SOCKETS;
45
46
extern char *strdup_client_description(int family, const char *protocol, const char *ip, uint16_t port);
@@ -73,6 +92,7 @@ typedef struct pollinfo {
92
93
int fd; // the file descriptor
94
int socktype; // the client socket type
95
+ WEB_CLIENT_ACL port_acl; // the access lists permitted on this web server port (it's -1 for client sockets)
96
char *client_ip; // the connected client IP
97
char *client_port; // the connected client port
98
@@ -138,6 +158,7 @@ extern void *poll_default_add_callback(POLLINFO *pi, short int *events, void *da
158
extern POLLINFO *poll_add_fd(POLLJOB *p
159
, int fd
160
, int socktype
161
+ , WEB_CLIENT_ACL port_acl
162
, uint32_t flags
163
, const char *client_ip
164
, const char *client_port
registry/README.md
+2
-2
@@ -36,11 +36,11 @@ The registry keeps track of 3 entities:
36
37
For each netdata installation (each `machine_guid`) the registry keeps track of the different URLs it is accessed.
38
39
-1. **persons**: i.e. the web browsers accessing the netdata installations (a random GUID generated by the registry the first time it sees a new web browser; we call this **person_guid**)
39
+2. **persons**: i.e. the web browsers accessing the netdata installations (a random GUID generated by the registry the first time it sees a new web browser; we call this **person_guid**)
40
41
For each person, the registry keeps track of the netdata installations it has accessed and their URLs.
42
43
-1. **URLs** of netdata installations (as seen by the web browsers)
43
+3. **URLs** of netdata installations (as seen by the web browsers)
44
45
For each URL, the registry keeps the URL and nothing more. Each URL is linked to *persons* and *machines*. The only way to find a URL is to know its **machine_guid** or have a **person_guid** it is linked to it.
46
registry/registry.h
+1
@@ -72,6 +72,7 @@ extern int registry_request_hello_json(RRDHOST *host, struct web_client *w);
72
extern void registry_statistics(void);
73
74
extern char *registry_get_this_machine_guid(void);
75
+extern char *registry_get_mgmt_api_key(void);
76
extern char *registry_get_this_machine_hostname(void);
77
78
extern int regenerate_guid(const char *guid, char *result);
registry/registry_person.c
+1
-1
@@ -79,7 +79,7 @@ REGISTRY_PERSON_URL *registry_person_url_allocate(REGISTRY_PERSON *p, REGISTRY_M
79
REGISTRY_PERSON_URL *tpu = registry_person_url_index_add(p, pu);
80
if(tpu != pu) {
81
error("Registry: Attempted to add duplicate person url '%s' with name '%s' to person '%s'", u->url, name, p->guid);
82
- free(pu);
82
+ freez(pu);
83
pu = tpu;
84
}
85
else
registry/registry_url.c
+1
-1
@@ -51,7 +51,7 @@ REGISTRY_URL *registry_url_get(const char *url, size_t urllen) {
51
n = registry_url_index_add(u);
52
if(n != u) {
53
error("INTERNAL ERROR: registry_url_get(): url '%s' already exists in the registry as '%s'", u->url, n->url);
54
- free(u);
54
+ freez(u);
55
u = n;
56
}
57
else
streaming/README.md
+1
-1
@@ -81,7 +81,7 @@ monitoring (there cannot be health monitoring without a database).
81
82
```
83
[web]
84
- mode = none | static-threaded | single-threaded | multi-threaded
84
+ mode = none | static-threaded
85
accept a streaming request every seconds = 0
86
```
87
tests/Makefile.am
+14
@@ -1,7 +1,15 @@
1
# SPDX-License-Identifier: GPL-3.0-or-later
2
3
+AUTOMAKE_OPTIONS = subdir-objects
4
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5
6
+CLEANFILES = \
7
+ health_mgmtapi/health-cmdapi-test.sh \
8
+ $(NULL)
9
+
10
+include $(top_srcdir)/build/subst.inc
11
+SUFFIXES = .in
12
+
13
dist_noinst_DATA = \
14
README.md \
15
web/lib/jasmine-jquery.js \
@@ -13,8 +21,14 @@ dist_noinst_DATA = \
21
node.d/fronius.parse.spec.js \
22
node.d/fronius.process.spec.js \
23
node.d/fronius.validation.spec.js \
24
+ health_mgmtapi/health-cmdapi-test.sh.in \
25
+ $(NULL)
26
+
27
+dist_plugins_SCRIPTS = \
28
+ health_mgmtapi/health-cmdapi-test.sh \
29
$(NULL)
30
31
dist_noinst_SCRIPTS = \
32
stress.sh \
33
$(NULL)
34
+
tests/health_mgmtapi/README.md
new
+13
@@ -0,0 +1,13 @@
1
+# Health command API tester
2
+
3
+The directory `tests/health_cmdapi` contains the test script `health-cmdapi-test.sh` for the [health command API](../../web/api/health).
4
+
5
+The script can be executed with options to prepare the system for the tests, run them and restore the system to its previous state.
6
+
7
+It depends on the management API being accessible and on the responses to the api/v1/alarms?all requests being functional.
8
+
9
+Run it with `tests/health_mgmtapi/health-cmdapi-test.sh -h` to see the options.
10
+
11
+[]()
12
+
13
+
tests/health_mgmtapi/health-cmdapi-test.sh.in
new
+263
@@ -0,0 +1,263 @@
1
+#!/usr/bin/env bash
2
+
3
+NETDATA_USER_CONFIG_DIR="@configdir_POST@"
4
+NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
5
+NETDATA_VARLIB_DIR="@varlibdir_POST@"
6
+
7
+printhelp () {
8
+ echo "Usage: health-cmdapi-test.sh [OPTIONS]
9
+ -s SETUP config files for python example tests
10
+ -c CLEANUP config files from python example tests
11
+ -r RESTART netdata after SETUP and CLEANUP, using systemctl restart netdata.
12
+ -t TEST scenarios execution
13
+ -u <URL> changes the host:port from localhost:19999 to <URL>
14
+ "
15
+}
16
+
17
+check () {
18
+ echo -e "${GRAY}Check: '${1}' in 2 sec"
19
+ sleep 2
20
+ resp=$(curl -s "http://$URL/api/v1/alarms?all")
21
+ r=$(echo "${resp}" | \
22
+ python3 -c "import sys, json; d=json.load(sys.stdin); \
23
+ print(\
24
+ d['alarms']['example.random.example_alarm1']['disabled'], \
25
+ d['alarms']['example.random.example_alarm1']['silenced'] , \
26
+ d['alarms']['example.random.example_alarm2']['disabled'], \
27
+ d['alarms']['example.random.example_alarm2']['silenced'], \
28
+ d['alarms']['system.load.load_trigger']['disabled'], \
29
+ d['alarms']['system.load.load_trigger']['silenced'], \
30
+ );" 2>&1)
31
+ if [ $? -ne 0 ] ; then
32
+ echo -e "${RED}ERROR: Unexpected response '$resp'"
33
+ err=$((err+1))
34
+ elif [ "${r}" != "${2}" ] ; then
35
+ echo -e "${RED}ERROR: 'Got ${r}'. Expected '${2}'"
36
+ err=$((err+1))
37
+ else
38
+ echo -e "${GREEN}Success"
39
+ fi
40
+}
41
+
42
+cmd () {
43
+ echo -e "${WHITE}Cmd '${1}', expecting '${2}'"
44
+ RESPONSE=$(curl -s "http://$URL/api/v1/manage/health?${1}" -H "Authorization: Bearer $TOKEN" 2>&1)
45
+ if [ "${RESPONSE}" != "${2}" ] ; then
46
+ echo -e "${RED}ERROR: Response '${RESPONSE}' != '${2}'"
47
+ err=$((err+1))
48
+ else
49
+ echo -e "${GREEN}Success"
50
+ fi
51
+}
52
+
53
+WHITE='\033[0;37m'
54
+RED='\033[0;31m'
55
+GREEN='\033[0;32m'
56
+GRAY='\033[0;37m'
57
+
58
+SETUP=0
59
+RESTART=0
60
+CLEANUP=0
61
+TEST=0
62
+URL="localhost:19999"
63
+
64
+while getopts :srctu: option
65
+do
66
+ case "$option" in
67
+ s)
68
+ SETUP=1
69
+ ;;
70
+ r)
71
+ RESTART=1
72
+ ;;
73
+ c)
74
+ CLEANUP=1
75
+ ;;
76
+ t)
77
+ TEST=1
78
+ ;;
79
+ u)
80
+ URL=$OPTARG
81
+ ;;
82
+ *)
83
+ printhelp
84
+ exit 1
85
+ ;;
86
+ esac
87
+done
88
+
89
+if [ $SETUP -eq 1 ] ; then
90
+ echo "Preparing netdata configuration for testing"
91
+ # Prep netdata for tests
92
+ if [ -f "${NETDATA_USER_CONFIG_DIR}/python.d.conf" ] ; then
93
+ cp -f "${NETDATA_USER_CONFIG_DIR}/python.d.conf" /tmp/python.d.conf
94
+ else
95
+ cp "${NETDATA_STOCK_CONFIG_DIR}/python.d.conf" "${NETDATA_USER_CONFIG_DIR}/"
96
+ fi
97
+ sed -i -e "s/example: no/example: yes/g" "${NETDATA_USER_CONFIG_DIR}/python.d.conf"
98
+
99
+ mypath=$(cd ${0%/*} && echo $PWD)
100
+
101
+ cp -f "${mypath}/python-example.conf" "${NETDATA_USER_CONFIG_DIR}/health.d/"
102
+
103
+ # netdata.conf
104
+ if [ -f "${NETDATA_USER_CONFIG_DIR}/netdata.conf" ] ; then
105
+ cp -f "${NETDATA_USER_CONFIG_DIR}/netdata.conf" /tmp/netdata.conf
106
+ fi
107
+ printf "[health]\nrun at least every seconds = 1\n" > "${NETDATA_USER_CONFIG_DIR}/netdata.conf"
108
+
109
+ chmod +r "${NETDATA_USER_CONFIG_DIR}/python.d.conf" "${NETDATA_USER_CONFIG_DIR}/netdata.conf" "${NETDATA_USER_CONFIG_DIR}/health.d/python-example.conf" "${NETDATA_STOCK_CONFIG_DIR}/health.d/load.conf"
110
+ # Restart netdata
111
+ if [ $RESTART -eq 1 ] ; then
112
+ echo "Restarting netdata"
113
+ systemctl restart netdata
114
+ fi
115
+fi
116
+
117
+err=0
118
+
119
+# Execute tests
120
+if [ $TEST -eq 1 ] ; then
121
+
122
+ HEALTH_CMDAPI_MSG_AUTHERROR="Auth Error"
123
+ HEALTH_CMDAPI_MSG_SILENCEALL="All alarm notifications are silenced"
124
+ HEALTH_CMDAPI_MSG_DISABLEALL="All health checks are disabled"
125
+ HEALTH_CMDAPI_MSG_RESET="All health checks and notifications are enabled"
126
+ HEALTH_CMDAPI_MSG_DISABLE="Health checks disabled for alarms matching the selectors"
127
+ HEALTH_CMDAPI_MSG_SILENCE="Alarm notifications silenced for alarms matching the selectors"
128
+ HEALTH_CMDAPI_MSG_ADDED="Alarm selector added"
129
+ HEALTH_CMDAPI_MSG_INVALID_KEY="Invalid key. Ignoring it."
130
+ HEALTH_CMDAPI_MSG_STYPEWARNING="WARNING: Added alarm selector to silence/disable alarms without a SILENCE or DISABLE command."
131
+ HEALTH_CMDAPI_MSG_NOSELECTORWARNING="WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors."
132
+
133
+ if [ -f "${NETDATA_VARLIB_DIR}/netdata.api.key" ] ;then
134
+ read -r CORRECT_TOKEN < "${NETDATA_VARLIB_DIR}/netdata.api.key"
135
+ else
136
+ echo "${NETDATA_VARLIB_DIR}/netdata.api.key not found"
137
+ exit 1
138
+ fi
139
+ # Set correct token
140
+ TOKEN="${CORRECT_TOKEN}"
141
+
142
+ # Test default state
143
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
144
+ check "Default State" "False False False False False False"
145
+
146
+ # Test auth failure
147
+ TOKEN="Wrong token"
148
+ cmd "cmd=DISABLE ALL" "$HEALTH_CMDAPI_MSG_AUTHERROR"
149
+ check "Default State" "False False False False False False"
150
+
151
+ # Set correct token
152
+ TOKEN="${CORRECT_TOKEN}"
153
+
154
+ # Test disable
155
+ cmd "cmd=DISABLE ALL" "$HEALTH_CMDAPI_MSG_DISABLEALL"
156
+ check "All disabled" "True False True False True False"
157
+
158
+ # Reset
159
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
160
+ check "Default State" "False False False False False False"
161
+
162
+ # Test silence
163
+ cmd "cmd=SILENCE ALL" "$HEALTH_CMDAPI_MSG_SILENCEALL"
164
+ check "All silenced" "False True False True False True"
165
+
166
+ # Reset
167
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
168
+ check "Default State" "False False False False False False"
169
+
170
+ # Add silencer by name
171
+ printf -v resp "$HEALTH_CMDAPI_MSG_SILENCE\n$HEALTH_CMDAPI_MSG_ADDED"
172
+ cmd "cmd=SILENCE&alarm=*example_alarm1 *load_trigger" "${resp}"
173
+ check "Silence notifications for alarm1 and load_trigger" "False True False False False True"
174
+
175
+ # Convert to disable health checks
176
+ cmd "cmd=DISABLE" "$HEALTH_CMDAPI_MSG_DISABLE"
177
+ check "Disable notifications for alarm1 and load_trigger" "True False False False True False"
178
+
179
+ # Convert back to silence notifications
180
+ cmd "cmd=SILENCE" "$HEALTH_CMDAPI_MSG_SILENCE"
181
+ check "Silence notifications for alarm1 and load_trigger" "False True False False False True"
182
+
183
+ # Add second silencer by name
184
+ cmd "alarm=*example_alarm2" "$HEALTH_CMDAPI_MSG_ADDED"
185
+ check "Silence notifications for alarm1,alarm2 and load_trigger" "False True False True False True"
186
+
187
+ # Reset
188
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
189
+
190
+ # Add silencer by chart
191
+ printf -v resp "$HEALTH_CMDAPI_MSG_DISABLE\n$HEALTH_CMDAPI_MSG_ADDED"
192
+ cmd "cmd=DISABLE&chart=system.load" "${resp}"
193
+ check "Default State" "False False False False True False"
194
+
195
+ # Add silencer by context
196
+ cmd "context=random" "$HEALTH_CMDAPI_MSG_ADDED"
197
+ check "Default State" "True False True False True False"
198
+
199
+ # Reset
200
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
201
+
202
+ # Add second condition to a selector (AND)
203
+ printf -v resp "$HEALTH_CMDAPI_MSG_SILENCE\n$HEALTH_CMDAPI_MSG_ADDED"
204
+ cmd "cmd=SILENCE&alarm=*example_alarm1 *load_trigger&chart=system.load" "${resp}"
205
+ check "Silence notifications load_trigger" "False False False False False True"
206
+
207
+ # Add second selector with two conditions
208
+ cmd "alarm=*example_alarm1 *load_trigger&context=random" "$HEALTH_CMDAPI_MSG_ADDED"
209
+ check "Silence notifications load_trigger" "False True False False False True"
210
+
211
+ # Reset
212
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
213
+
214
+ # Add silencer without a command to disable or silence alarms
215
+ printf -v resp "$HEALTH_CMDAPI_MSG_ADDED\n$HEALTH_CMDAPI_MSG_STYPEWARNING"
216
+ cmd "families=load" "${resp}"
217
+ check "Family selector with no command" "False False False False False False"
218
+
219
+ # Add silence command
220
+ cmd "cmd=SILENCE" "$HEALTH_CMDAPI_MSG_SILENCE"
221
+ check "Silence family load" "False False False False False True"
222
+
223
+ # Reset
224
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
225
+
226
+ # Add command without silencers
227
+ printf -v resp "$HEALTH_CMDAPI_MSG_SILENCE\n$HEALTH_CMDAPI_MSG_NOSELECTORWARNING"
228
+ cmd "cmd=SILENCE" "${resp}"
229
+ check "Command with no selector" "False False False False False False"
230
+
231
+ # Add hosts silencer
232
+ cmd "hosts=*" "$HEALTH_CMDAPI_MSG_ADDED"
233
+ check "Silence all hosts" "False True False True False True"
234
+
235
+ # Reset
236
+ cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
237
+
238
+fi
239
+
240
+# Cleanup
241
+if [ $CLEANUP -eq 1 ] ; then
242
+ echo -e "${WHITE}Restoring netdata configuration"
243
+ for f in "python.d.conf" "netdata.conf" ; do
244
+ if [ -f "/tmp/$f" ] ; then
245
+ mv -f "/tmp/$f" "${NETDATA_USER_CONFIG_DIR}/"
246
+ else
247
+ rm -f "${NETDATA_USER_CONFIG_DIR}/$f"
248
+ fi
249
+ done
250
+
251
+ rm -f "${NETDATA_USER_CONFIG_DIR}/health.d/python-example.conf"
252
+
253
+ # Restart netdata
254
+ if [ $RESTART -eq 1 ] ; then
255
+ echo "Restarting netdata"
256
+ systemctl restart netdata
257
+ fi
258
+fi
259
+
260
+if [ $err -gt 0 ] ; then
261
+ echo "$err error(s) found"
262
+ exit 1
263
+fi
\ No newline at end of file
tests/health_mgmtapi/python-example.conf
new
+16
@@ -0,0 +1,16 @@
1
+alarm: example_alarm1
2
+ on: example.random
3
+ every: 2s
4
+ warn: $random1 > (($status >= $WARNING) ? (55) : (75))
5
+ crit: $random1 > (($status == $CRITICAL) ? (75) : (95))
6
+ info: random
7
+ to: sysadmin
8
+
9
+alarm: example_alarm2
10
+ on: example.random
11
+ every: 2s
12
+ warn: $random2 > (($status >= $WARNING) ? (55) : (75))
13
+ crit: $random2 > (($status == $CRITICAL) ? (75) : (95))
14
+ info: random
15
+ to: sysadmin
16
+
web/api/Makefile.am
+1
@@ -8,6 +8,7 @@ SUBDIRS = \
8
queries \
9
exporters \
10
formatters \
11
+ health \
12
$(NULL)
13
14
dist_noinst_DATA = \
web/api/health/Makefile.am
renamed
-3
@@ -3,9 +3,6 @@
3
AUTOMAKE_OPTIONS = subdir-objects
4
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5
6
-SUBDIRS = \
7
- $(NULL)
8
-
6
dist_noinst_DATA = \
7
README.md \
8
$(NULL)
web/api/health/README.md
+124
-5
@@ -1,6 +1,8 @@
1
# Health API Calls
2
3
-## Enabled Alarms
3
+## Health Read API
4
+
5
+### Enabled Alarms
6
7
NetData enables alarms on demand, i.e. when the chart they should be linked to starts collecting data. So, although many more alarms are configured, only the useful ones are enabled.
8
@@ -8,13 +10,13 @@ To get the list of all enabled alarms:
10
11
`http://your.netdata.ip:19999/api/v1/alarms?all`
12
11
-## Raised Alarms
13
+### Raised Alarms
14
15
This API call will return the alarms currently in WARNING or CRITICAL state.
16
17
`http://your.netdata.ip:19999/api/v1/alarms`
18
17
-## Event Log
19
+### Event Log
20
21
The size of the alarm log is configured in `netdata.conf`. There are 2 settings: the rotation of the alarm log file and the in memory size of the alarm log.
22
@@ -28,17 +30,134 @@ The API call retrieves all entries of the alarm log:
30
31
`http://your.netdata.ip:19999/api/v1/alarm_log`
32
31
-## Alarm Log Incremental Updates
33
+### Alarm Log Incremental Updates
34
35
`http://your.netdata.ip:19999/api/v1/alarm_log?after=UNIQUEID`
36
37
The above returns all the events in the alarm log that occurred after UNIQUEID (you poll it once without `after=`, remember the last UNIQUEID of the returned set, which you give back to get incrementally the next events).
38
37
-## Alarm badges
39
+### Alarm badges
40
41
The following will return an SVG badge of the alarm named `NAME`, attached to the chart named `CHART`.
42
43
`http://your.netdata.ip:19999/api/v1/badge.svg?alarm=NAME&chart=CHART`
44
45
+## Health Management API
46
+
47
+Netdata v1.12 and beyond provides a command API to control health checks and notifications at runtime. The feature is especially useful for maintenance periods, during which you receive meaningless alarms.
48
+
49
+Specifically, the API allows you to:
50
+ - Disable health checks completely. Alarm conditions will not be evaluated at all and no entries will be added to the alarm log.
51
+ - Silence alarm notifications. Alarm conditions will be evaluated, the alarms will appear in the log and the netdata UI will show the alarms as active, but no notifications will be sent.
52
+ - Disable or Silence specific alarms that match selectors on alarm/template name, chart, context, host and family.
53
+
54
+The API is available by default, but it is protected by an `api authorization token` that is stored in the file you will see in the following entry of `http://localhost:19999/netdata.conf`:
55
+
56
+```bash
57
+[registry]
58
+ # netdata management api key file = /var/lib/netdata/netdata.api.key
59
+```
60
+
61
+You can access the API via GET requests, by adding the bearer token to an `Authorization` http header, like this:
62
+
63
+```
64
+curl "http://myserver/api/v1/manage/health?cmd=RESET" -H "Authorization: Bearer Mytoken"
65
+```
66
+
67
+The command `RESET` just returns netdata to the default operation, with all health checks and notifications enabled.
68
+If you've configured and entered your token correclty, you should see the plain text response `All health checks and notifications are enabled`.
69
+
70
+### Disable or silence all alarms
71
+
72
+If all you need is temporarily disable all health checks, then you issue the following before your maintenance period starts:
73
+```
74
+curl "http://myserver/api/v1/manage/health?cmd=DISABLE ALL" -H "Authorization: Bearer Mytoken"
75
+```
76
+The effect of disabling health checks is that the alarm criteria are not evaluated at all and nothing is written in the alarm log.
77
+If you want the health checks to be running but to not receive any notifications during your maintenance period, you can instead use this:
78
+
79
+```
80
+curl "http://myserver/api/v1/manage/health?cmd=SILENCE ALL" -H "Authorization: Bearer Mytoken"
81
+```
82
+
83
+Alarms may then still be raised and logged in netdata, so you'll be able to see them via the UI.
84
+
85
+Regardless of the option you choose, at the end of your maintenance period you revert to the normal state via the RESET command.
86
+
87
+```
88
+ curl "http://myserver/api/v1/manage/health?cmd=RESET" -H "Authorization: Bearer Mytoken"
89
+```
90
+
91
+### Disable or silence specific alarms
92
+
93
+If you do not wish to disable/silence all alarms, then the `DISABLE ALL` and `SILENCE ALL` commands can't be used.
94
+Instead, the following commands expect that one or more alarm selectors will be added, so that only alarms that match the selectors are disabled or silenced.
95
+- `DISABLE` : Set the mode to disable health checks.
96
+- `SILENCE` : Set the mode to silence notifications.
97
+
98
+You will normally put one of these commands in the same request with your first alarm selector, but it's possible to issue them separately as well.
99
+You will get a warning in the response, if a selector was added without a SILENCE/DISABLE command, or vice versa.
100
+
101
+Each request can specify a single alarm `selector`, with one or more `selection criteria`.
102
+A single alarm will match a `selector` if all selection criteria match the alarm.
103
+You can add as many selectors as you like.
104
+In essence, the rule is: IF (alarm matches all the criteria in selector1 OR all the criteria in selector2 OR ...) THEN apply the DISABLE or SILENCE command.
105
+
106
+To clear all selectors and reset the mode to default, use the `RESET` command.
107
+
108
+The following example silences notifications for all the alarms with context=load:
109
+
110
+```
111
+curl "http://myserver/api/v1/manage/health?cmd=SILENCE&context=load" -H "Authorization: Bearer Mytoken"
112
+```
113
+
114
+#### Selection criteria
115
+
116
+The `selection criteria` are key/value pairs, in the format `key : value`, where value is a netdata [simple pattern](../../../libnetdata/simple_pattern/). This means that you can create very powerful selectors (you will rarely need more than one or two).
117
+
118
+The accepted keys for the `selection criteria` are the following:
119
+- `alarm` : The expression provided will match both `alarm` and `template` names.
120
+- `chart` : Chart ids/names, as shown on the dashboard. These will match the `on` entry of a configured `alarm`.
121
+- `context` : Chart context, as shown on the dashboard. These will match the `on` entry of a configured `template`.
122
+- `hosts` : The hostnames that will need to match.
123
+- `families` : The alarm families.
124
+
125
+You can add any of the selection criteria you need on the request, to ensure that only the alarms you are interested in are matched and disabled/silenced. e.g. there is no reason to add `hosts: *`, if you want the criteria to be applied to alarms for all hosts.
126
+
127
+Example 1: Disable all health checks for context = `random`
128
+
129
+```
130
+http://localhost/api/v1/manage/health?cmd=DISABLE&context=random
131
+```
132
+
133
+Example 2: Silence all alarms and templates with name starting with `out_of` on host `myhost`
134
+
135
+```
136
+http://localhost/api/v1/manage/health?cmd=SILENCE&alarm=out_of*&hosts=myhost
137
+```
138
+
139
+Example 2.2: Add one more selector, to also silence alarms for cpu1 and cpu2
140
+
141
+```
142
+http://localhost/api/v1/manage/health?families=cpu1 cpu2
143
+```
144
+
145
+### Responses
146
+
147
+- "Auth Error" : Token authentication failed
148
+- "All alarm notifications are silenced" : Successful response to cmd=SILENCE ALL
149
+- "All health checks are disabled" : Successful response to cmd=DISABLE ALL
150
+- "All health checks and notifications are enabled" : Successful response to cmd=RESET
151
+- "Health checks disabled for alarms matching the selectors" : Added to the response for a cmd=DISABLE
152
+- "Alarm notifications silenced for alarms matching the selectors" : Added to the response for a cmd=SILENCE
153
+- "Alarm selector added" : Added to the response when a new selector is added
154
+- "Invalid key. Ignoring it." : Wrong name of a parameter. Added to the response and ignored.
155
+- "WARNING: Added alarm selector to silence/disable alarms without a SILENCE or DISABLE command." : Added to the response if a selector is added without a selector-specific command.
156
+- "WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors." : Added to the response if a selector-specific command is issued without a selector.
157
+
158
+### Further reading
159
+
160
+The test script under [tests/health_mgmtapi](../../../tests/health_mgmtapi) contains a series of tests that you can either run or read through to understand the various calls and responses better.
161
+
162
163
[]()
web/api/health/health_cmdapi.c
new
+166
@@ -0,0 +1,166 @@
1
+//
2
+// Created by christopher on 11/12/18.
3
+//
4
+
5
+#include "health_cmdapi.h"
6
+
7
+
8
+static SILENCER *create_silencer(void) {
9
+ SILENCER *t = callocz(1, sizeof(SILENCER));
10
+ debug(D_HEALTH, "HEALTH command API: Created empty silencer");
11
+
12
+ return t;
13
+}
14
+
15
+void free_silencers(SILENCER *t) {
16
+ if (!t) return;
17
+ if (t->next) free_silencers(t->next);
18
+ debug(D_HEALTH, "HEALTH command API: Freeing silencer %s:%s:%s:%s:%s", t->alarms,
19
+ t->charts, t->contexts, t->hosts, t->families);
20
+ simple_pattern_free(t->alarms_pattern);
21
+ simple_pattern_free(t->charts_pattern);
22
+ simple_pattern_free(t->contexts_pattern);
23
+ simple_pattern_free(t->hosts_pattern);
24
+ simple_pattern_free(t->families_pattern);
25
+ freez(t->alarms);
26
+ freez(t->charts);
27
+ freez(t->contexts);
28
+ freez(t->hosts);
29
+ freez(t->families);
30
+ freez(t);
31
+ return;
32
+}
33
+
34
+
35
+
36
+int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, char *url) {
37
+ int ret = 400;
38
+ (void) host;
39
+
40
+
41
+
42
+ BUFFER *wb = w->response.data;
43
+ buffer_flush(wb);
44
+ wb->contenttype = CT_TEXT_PLAIN;
45
+
46
+ buffer_flush(w->response.data);
47
+
48
+ static uint32_t
49
+ hash_alarm = 0,
50
+ hash_template = 0,
51
+ hash_chart = 0,
52
+ hash_context = 0,
53
+ hash_host = 0,
54
+ hash_families = 0;
55
+
56
+ if (unlikely(!hash_alarm)) {
57
+ hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
58
+ hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
59
+ hash_chart = simple_uhash(HEALTH_CHART_KEY);
60
+ hash_context = simple_uhash(HEALTH_CONTEXT_KEY);
61
+ hash_host = simple_uhash(HEALTH_HOST_KEY);
62
+ hash_families = simple_uhash(HEALTH_FAMILIES_KEY);
63
+ }
64
+
65
+ SILENCER *silencer = NULL;
66
+
67
+ if (!w->auth_bearer_token) {
68
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR);
69
+ ret = 403;
70
+ } else {
71
+ debug(D_HEALTH, "HEALTH command API: Comparing secret '%s' to '%s'", w->auth_bearer_token, api_secret);
72
+ if (strcmp(w->auth_bearer_token, api_secret)) {
73
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR);
74
+ ret = 403;
75
+ } else {
76
+ while (url) {
77
+ char *value = mystrsep(&url, "&");
78
+ if (!value || !*value) continue;
79
+
80
+ char *key = mystrsep(&value, "=");
81
+ if (!key || !*key) continue;
82
+ if (!value || !*value) continue;
83
+
84
+ debug(D_WEB_CLIENT, "%llu: API v1 health query param '%s' with value '%s'", w->id, key, value);
85
+
86
+ // name and value are now the parameters
87
+ if (!strcmp(key, "cmd")) {
88
+ if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCEALL)) {
89
+ silencers->all_alarms = 1;
90
+ silencers->stype = STYPE_SILENCE_NOTIFICATIONS;
91
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCEALL);
92
+ } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLEALL)) {
93
+ silencers->all_alarms = 1;
94
+ silencers->stype = STYPE_DISABLE_ALARMS;
95
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLEALL);
96
+ } else if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCE)) {
97
+ silencers->stype = STYPE_SILENCE_NOTIFICATIONS;
98
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_SILENCE);
99
+ } else if (!strcmp(value, HEALTH_CMDAPI_CMD_DISABLE)) {
100
+ silencers->stype = STYPE_DISABLE_ALARMS;
101
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_DISABLE);
102
+ } else if (!strcmp(value, HEALTH_CMDAPI_CMD_RESET)) {
103
+ silencers->all_alarms = 0;
104
+ silencers->stype = STYPE_NONE;
105
+ free_silencers(silencers->silencers);
106
+ silencers->silencers = NULL;
107
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_RESET);
108
+ }
109
+ } else {
110
+ uint32_t hash = simple_uhash(key);
111
+ if (unlikely(silencer == NULL)) {
112
+ if (
113
+ (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) ||
114
+ (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) ||
115
+ (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) ||
116
+ (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) ||
117
+ (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) ||
118
+ (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY))
119
+ ) {
120
+ silencer = create_silencer();
121
+ }
122
+ }
123
+
124
+ if (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) {
125
+ silencer->alarms = strdupz(value);
126
+ silencer->alarms_pattern = simple_pattern_create(silencer->alarms, NULL, SIMPLE_PATTERN_EXACT);
127
+ } else if (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) {
128
+ silencer->charts = strdupz(value);
129
+ silencer->charts_pattern = simple_pattern_create(silencer->charts, NULL, SIMPLE_PATTERN_EXACT);
130
+ } else if (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) {
131
+ silencer->contexts = strdupz(value);
132
+ silencer->contexts_pattern = simple_pattern_create(silencer->contexts, NULL, SIMPLE_PATTERN_EXACT);
133
+ } else if (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) {
134
+ silencer->hosts = strdupz(value);
135
+ silencer->hosts_pattern = simple_pattern_create(silencer->hosts, NULL, SIMPLE_PATTERN_EXACT);
136
+ } else if (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) {
137
+ silencer->families = strdupz(value);
138
+ silencer->families_pattern = simple_pattern_create(silencer->families, NULL, SIMPLE_PATTERN_EXACT);
139
+ } else {
140
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_INVALID_KEY);
141
+ }
142
+ }
143
+
144
+ }
145
+ if (likely(silencer)) {
146
+ // Add the created instance to the linked list in silencers
147
+ silencer->next = silencers->silencers;
148
+ silencers->silencers = silencer;
149
+ debug(D_HEALTH, "HEALTH command API: Added silencer %s:%s:%s:%s:%s", silencer->alarms,
150
+ silencer->charts, silencer->contexts, silencer->hosts, silencer->families
151
+ );
152
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_ADDED);
153
+ if (silencers->stype == STYPE_NONE) {
154
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_STYPEWARNING);
155
+ }
156
+ }
157
+ if (unlikely(silencers->stype != STYPE_NONE && !silencers->all_alarms && !silencers->silencers)) {
158
+ buffer_strcat(wb, HEALTH_CMDAPI_MSG_NOSELECTORWARNING);
159
+ }
160
+ ret = 200;
161
+ }
162
+ }
163
+ w->response.data = wb;
164
+ buffer_no_cacheable(w->response.data);
165
+ return ret;
166
+}
web/api/health/health_cmdapi.h
new
+31
@@ -0,0 +1,31 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#ifndef NETDATA_WEB_HEALTH_SVG_H
4
+#define NETDATA_WEB_HEALTH_SVG_H 1
5
+
6
+#include "libnetdata/libnetdata.h"
7
+#include "web/server/web_client.h"
8
+#include "health/health.h"
9
+
10
+#define HEALTH_CMDAPI_CMD_SILENCEALL "SILENCE ALL"
11
+#define HEALTH_CMDAPI_CMD_DISABLEALL "DISABLE ALL"
12
+#define HEALTH_CMDAPI_CMD_SILENCE "SILENCE"
13
+#define HEALTH_CMDAPI_CMD_DISABLE "DISABLE"
14
+#define HEALTH_CMDAPI_CMD_RESET "RESET"
15
+
16
+#define HEALTH_CMDAPI_MSG_AUTHERROR "Auth Error\n"
17
+#define HEALTH_CMDAPI_MSG_SILENCEALL "All alarm notifications are silenced\n"
18
+#define HEALTH_CMDAPI_MSG_DISABLEALL "All health checks are disabled\n"
19
+#define HEALTH_CMDAPI_MSG_RESET "All health checks and notifications are enabled\n"
20
+#define HEALTH_CMDAPI_MSG_DISABLE "Health checks disabled for alarms matching the selectors\n"
21
+#define HEALTH_CMDAPI_MSG_SILENCE "Alarm notifications silenced for alarms matching the selectors\n"
22
+#define HEALTH_CMDAPI_MSG_ADDED "Alarm selector added\n"
23
+#define HEALTH_CMDAPI_MSG_INVALID_KEY "Invalid key. Ignoring it.\n"
24
+#define HEALTH_CMDAPI_MSG_STYPEWARNING "WARNING: Added alarm selector to silence/disable alarms without a SILENCE or DISABLE command.\n"
25
+#define HEALTH_CMDAPI_MSG_NOSELECTORWARNING "WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors.\n"
26
+
27
+extern int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, char *url);
28
+
29
+#include "web/api/web_api_v1.h"
30
+
31
+#endif /* NETDATA_WEB_HEALTH_SVG_H */
web/api/netdata-swagger.json
+395
@@ -545,6 +545,116 @@
545
}
546
}
547
}
548
+ },
549
+ "/alarms": {
550
+ "get": {
551
+ "summary": "Get a list of active or raised alarms on the server",
552
+ "description": "The alarms endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing \"?all\", all the enabled alarms are returned.",
553
+ "parameters": [
554
+ {
555
+ "name": "all",
556
+ "in": "query",
557
+ "description": "If passed, all enabled alarms are returned",
558
+ "required": false,
559
+ "type": "boolean",
560
+ "allowEmptyValue": true
561
+ }
562
+ ],
563
+ "responses": {
564
+ "200": {
565
+ "description": "An object containing general info and a linked list of alarms",
566
+ "schema": {
567
+ "$ref": "#/definitions/alarms"
568
+ }
569
+ }
570
+ }
571
+ }
572
+ },
573
+ "/alarm_log": {
574
+ "get": {
575
+ "summary": "Retrieves the entries of the alarm log",
576
+ "description": "Returns an array of alarm_log entries, with historical information on raised and cleared alarms.",
577
+ "parameters": [
578
+ {
579
+ "name": "after",
580
+ "in": "query",
581
+ "description": "Passing the parameter after=UNIQUEID returns all the events in the alarm log that occurred after UNIQUEID. An automated series of calls would call the interface once without after=, store the last UNIQUEID of the returned set, and give it back to get incrementally the next events",
582
+ "required": false,
583
+ "type": "integer"
584
+ }
585
+ ],
586
+ "responses": {
587
+ "200": {
588
+ "description": "An array of alarm log entries",
589
+ "schema": {
590
+ "type": "array",
591
+ "items": {
592
+ "$ref": "#/definitions/alarm_log_entry"
593
+ }
594
+ }
595
+ }
596
+ }
597
+ }
598
+ },
599
+ "/manage/health": {
600
+ "get": {
601
+ "summary": "Accesses the health management API to control health checks and notifications at runtime.",
602
+ "description": "Available from Netdata v1.12 and above, protected via bearer authorization. Especially useful for maintenance periods, the API allows you to disable health checks completely, silence alarm notifications, or Disable/Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. For the simple disable/silence all scenaria, only the cmd parameter is required. The other parameters are used to define alarm selectors. For more information and examples, refer to the netdata documentation.",
603
+ "parameters": [
604
+ {
605
+ "name": "cmd",
606
+ "in": "query",
607
+ "description": "DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors.",
608
+ "required": false,
609
+ "type": "string",
610
+ "enum": [
611
+ "DISABLE ALL",
612
+ "SILENCE ALL",
613
+ "DISABLE",
614
+ "SILENCE",
615
+ "RESET"
616
+ ]
617
+ },
618
+ {
619
+ "name": "alarm",
620
+ "in": "query",
621
+ "description": "The expression provided will match both `alarm` and `template` names.",
622
+ "type": "string"
623
+ },
624
+ {
625
+ "name": "chart",
626
+ "in": "query",
627
+ "description": "Chart ids/names, as shown on the dashboard. These will match the `on` entry of a configured `alarm`",
628
+ "type": "string"
629
+ },
630
+ {
631
+ "name": "context",
632
+ "in": "query",
633
+ "description": "Chart context, as shown on the dashboard. These will match the `on` entry of a configured `template`.",
634
+ "type": "string"
635
+ },
636
+ {
637
+ "name": "hosts",
638
+ "in": "query",
639
+ "description": "The hostnames that will need to match.",
640
+ "type": "string"
641
+ },
642
+ {
643
+ "name": "families",
644
+ "in": "query",
645
+ "description": "The alarm families.",
646
+ "type": "string"
647
+ }
648
+ ],
649
+ "responses": {
650
+ "200": {
651
+ "description": "A plain text response based on the result of the command"
652
+ },
653
+ "403": {
654
+ "description": "Bearer authentication error."
655
+ }
656
+ }
657
+ }
658
}
659
},
660
"definitions": {
@@ -830,6 +940,291 @@
940
"description": "The result requested, in the format requested."
941
}
942
}
943
+ },
944
+ "alarms": {
945
+ "type": "object",
946
+ "properties": {
947
+ "hostname": {
948
+ "type": "string"
949
+ },
950
+ "latest_alarm_log_unique_id": {
951
+ "type": "integer",
952
+ "format": "int32"
953
+ },
954
+ "status": {
955
+ "type": "boolean"
956
+ },
957
+ "now": {
958
+ "type": "integer",
959
+ "format": "int32"
960
+ },
961
+ "alarms": {
962
+ "type": "object",
963
+ "properties": {
964
+ "chart-name.alarm-name": {
965
+ "type": "object",
966
+ "properties": {
967
+ "id": {
968
+ "type": "integer",
969
+ "format": "int32"
970
+ },
971
+ "name": {
972
+ "type": "string",
973
+ "description": "Full alarm name"
974
+ },
975
+ "chart": {
976
+ "type": "string"
977
+ },
978
+ "family": {
979
+ "type": "string"
980
+ },
981
+ "active": {
982
+ "type": "boolean",
983
+ "description": "Will be false only if the alarm is disabled in the configuration"
984
+ },
985
+ "disabled": {
986
+ "type": "boolean",
987
+ "description": "Whether the health check for this alarm has been disabled via a health command API DISABLE command."
988
+ },
989
+ "silenced": {
990
+ "type": "boolean",
991
+ "description": "Whether notifications for this alarm have been silenced via a health command API SILENCE command."
992
+ },
993
+ "exec": {
994
+ "type": "string"
995
+ },
996
+ "recipient": {
997
+ "type": "string"
998
+ },
999
+ "source": {
1000
+ "type": "string"
1001
+ },
1002
+ "units": {
1003
+ "type": "string"
1004
+ },
1005
+ "info": {
1006
+ "type": "string"
1007
+ },
1008
+ "status": {
1009
+ "type": "string"
1010
+ },
1011
+ "last_status_change": {
1012
+ "type": "integer",
1013
+ "format": "int32"
1014
+ },
1015
+ "last_updated": {
1016
+ "type": "integer",
1017
+ "format": "int32"
1018
+ },
1019
+ "next_update": {
1020
+ "type": "integer",
1021
+ "format": "int32"
1022
+ },
1023
+ "update_every": {
1024
+ "type": "integer",
1025
+ "format": "int32"
1026
+ },
1027
+ "delay_up_duration": {
1028
+ "type": "integer",
1029
+ "format": "int32"
1030
+ },
1031
+ "delay_down_duration": {
1032
+ "type": "integer",
1033
+ "format": "int32"
1034
+ },
1035
+ "delay_max_duration": {
1036
+ "type": "integer",
1037
+ "format": "int32"
1038
+ },
1039
+ "delay_multiplier": {
1040
+ "type": "integer",
1041
+ "format": "int32"
1042
+ },
1043
+ "delay": {
1044
+ "type": "integer",
1045
+ "format": "int32"
1046
+ },
1047
+ "delay_up_to_timestamp": {
1048
+ "type": "integer",
1049
+ "format": "int32"
1050
+ },
1051
+ "value_string": {
1052
+ "type": "string"
1053
+ },
1054
+ "no_clear_notification": {
1055
+ "type": "boolean"
1056
+ },
1057
+ "lookup_dimensions": {
1058
+ "type": "string"
1059
+ },
1060
+ "db_after": {
1061
+ "type": "integer",
1062
+ "format": "int32"
1063
+ },
1064
+ "db_before": {
1065
+ "type": "integer",
1066
+ "format": "int32"
1067
+ },
1068
+ "lookup_method": {
1069
+ "type": "string"
1070
+ },
1071
+ "lookup_after": {
1072
+ "type": "integer",
1073
+ "format": "int32"
1074
+ },
1075
+ "lookup_before": {
1076
+ "type": "integer",
1077
+ "format": "int32"
1078
+ },
1079
+ "lookup_options": {
1080
+ "type": "string"
1081
+ },
1082
+ "calc": {
1083
+ "type": "string"
1084
+ },
1085
+ "calc_parsed": {
1086
+ "type": "string"
1087
+ },
1088
+ "warn": {
1089
+ "type": "string"
1090
+ },
1091
+ "warn_parsed": {
1092
+ "type": "string"
1093
+ },
1094
+ "crit": {
1095
+ "type": "string"
1096
+ },
1097
+ "crit_parsed": {
1098
+ "type": "string"
1099
+ },
1100
+ "green": {
1101
+ "type": "string",
1102
+ "format": "nullable"
1103
+ },
1104
+ "red": {
1105
+ "type": "string",
1106
+ "format": "nullable"
1107
+ },
1108
+ "value": {
1109
+ "type": "number"
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+ }
1116
+ },
1117
+ "alarm_log_entry": {
1118
+ "type": "object",
1119
+ "properties": {
1120
+ "hostname": {
1121
+ "type": "string"
1122
+ },
1123
+ "unique_id": {
1124
+ "type": "integer",
1125
+ "format": "int32"
1126
+ },
1127
+ "alarm_id": {
1128
+ "type": "integer",
1129
+ "format": "int32"
1130
+ },
1131
+ "alarm_event_id": {
1132
+ "type": "integer",
1133
+ "format": "int32"
1134
+ },
1135
+ "name": {
1136
+ "type": "string"
1137
+ },
1138
+ "chart": {
1139
+ "type": "string"
1140
+ },
1141
+ "family": {
1142
+ "type": "string"
1143
+ },
1144
+ "processed": {
1145
+ "type": "boolean"
1146
+ },
1147
+ "updated": {
1148
+ "type": "boolean"
1149
+ },
1150
+ "exec_run": {
1151
+ "type": "integer",
1152
+ "format": "int32"
1153
+ },
1154
+ "exec_failed": {
1155
+ "type": "boolean"
1156
+ },
1157
+ "exec": {
1158
+ "type": "string"
1159
+ },
1160
+ "recipient": {
1161
+ "type": "string"
1162
+ },
1163
+ "exec_code": {
1164
+ "type": "integer",
1165
+ "format": "int32"
1166
+ },
1167
+ "source": {
1168
+ "type": "string"
1169
+ },
1170
+ "units": {
1171
+ "type": "string"
1172
+ },
1173
+ "when": {
1174
+ "type": "integer",
1175
+ "format": "int32"
1176
+ },
1177
+ "duration": {
1178
+ "type": "integer",
1179
+ "format": "int32"
1180
+ },
1181
+ "non_clear_duration": {
1182
+ "type": "integer",
1183
+ "format": "int32"
1184
+ },
1185
+ "status": {
1186
+ "type": "string"
1187
+ },
1188
+ "old_status": {
1189
+ "type": "string"
1190
+ },
1191
+ "delay": {
1192
+ "type": "integer",
1193
+ "format": "int32"
1194
+ },
1195
+ "delay_up_to_timestamp": {
1196
+ "type": "integer",
1197
+ "format": "int32"
1198
+ },
1199
+ "updated_by_id": {
1200
+ "type": "integer",
1201
+ "format": "int32"
1202
+ },
1203
+ "updates_id": {
1204
+ "type": "integer",
1205
+ "format": "int32"
1206
+ },
1207
+ "value_string": {
1208
+ "type": "string"
1209
+ },
1210
+ "old_value_string": {
1211
+ "type": "string"
1212
+ },
1213
+ "silenced": {
1214
+ "type": "string"
1215
+ },
1216
+ "info": {
1217
+ "type": "string"
1218
+ },
1219
+ "value": {
1220
+ "type": "string",
1221
+ "format": "nullable"
1222
+ },
1223
+ "old_value": {
1224
+ "type": "string",
1225
+ "format": "nullable"
1226
+ }
1227
+ }
1228
}
1229
}
1230
}
\ No newline at end of file
web/api/netdata-swagger.yaml
+270
-1
@@ -357,6 +357,75 @@ paths:
357
description: 'All the metrics returned in the format requested'
358
'400':
359
description: 'The format requested is not supported'
360
+ /alarms:
361
+ get:
362
+ summary: 'Get a list of active or raised alarms on the server'
363
+ description: 'The alarms endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing "?all", all the enabled alarms are returned.'
364
+ parameters:
365
+ - name: all
366
+ in: query
367
+ description: 'If passed, all enabled alarms are returned'
368
+ required: false
369
+ type: boolean
370
+ allowEmptyValue: true
371
+ responses:
372
+ '200':
373
+ description: 'An object containing general info and a linked list of alarms'
374
+ schema:
375
+ $ref: '#/definitions/alarms'
376
+ /alarm_log:
377
+ get:
378
+ summary: 'Retrieves the entries of the alarm log'
379
+ description: 'Returns an array of alarm_log entries, with historical information on raised and cleared alarms.'
380
+ parameters:
381
+ - name: after
382
+ in: query
383
+ description: 'Passing the parameter after=UNIQUEID returns all the events in the alarm log that occurred after UNIQUEID. An automated series of calls would call the interface once without after=, store the last UNIQUEID of the returned set, and give it back to get incrementally the next events'
384
+ required: false
385
+ type: integer
386
+ responses:
387
+ '200':
388
+ description: 'An array of alarm log entries'
389
+ schema:
390
+ type: array
391
+ items:
392
+ $ref: '#/definitions/alarm_log_entry'
393
+ /manage/health:
394
+ get:
395
+ summary: 'Accesses the health management API to control health checks and notifications at runtime.'
396
+ description: 'Available from Netdata v1.12 and above, protected via bearer authorization. Especially useful for maintenance periods, the API allows you to disable health checks completely, silence alarm notifications, or Disable/Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. For the simple disable/silence all scenaria, only the cmd parameter is required. The other parameters are used to define alarm selectors. For more information and examples, refer to the netdata documentation.'
397
+ parameters:
398
+ - name: cmd
399
+ in: query
400
+ description: 'DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors.'
401
+ required: false
402
+ type: string
403
+ enum: ['DISABLE ALL', 'SILENCE ALL', 'DISABLE', 'SILENCE', 'RESET']
404
+ - name: alarm
405
+ in: query
406
+ description: 'The expression provided will match both `alarm` and `template` names.'
407
+ type: string
408
+ - name: chart
409
+ in: query
410
+ description: 'Chart ids/names, as shown on the dashboard. These will match the `on` entry of a configured `alarm`'
411
+ type: string
412
+ - name: context
413
+ in: query
414
+ description: 'Chart context, as shown on the dashboard. These will match the `on` entry of a configured `template`.'
415
+ type: string
416
+ - name: hosts
417
+ in: query
418
+ description: 'The hostnames that will need to match.'
419
+ type: string
420
+ - name: families
421
+ in: query
422
+ description: 'The alarm families.'
423
+ type: string
424
+ responses:
425
+ '200':
426
+ description: 'A plain text response based on the result of the command'
427
+ '403':
428
+ description: 'Bearer authentication error.'
429
definitions:
430
info:
431
type: object
@@ -491,7 +560,6 @@ definitions:
560
name:
561
type: string
562
description: 'The name of the dimension'
494
-
563
json_wrap:
564
type: object
565
properties:
@@ -559,3 +627,204 @@ definitions:
627
description: 'The format of the result returned.'
628
result:
629
description: 'The result requested, in the format requested.'
630
+ alarms:
631
+ type: object
632
+ properties:
633
+ hostname:
634
+ type: string
635
+ latest_alarm_log_unique_id:
636
+ type: integer
637
+ format: int32
638
+ status:
639
+ type: boolean
640
+ now:
641
+ type: integer
642
+ format: int32
643
+ alarms:
644
+ type: object
645
+ properties:
646
+ chart-name.alarm-name:
647
+ type: object
648
+ properties:
649
+ id:
650
+ type: integer
651
+ format: int32
652
+ name:
653
+ type: string
654
+ description: Full alarm name
655
+ chart:
656
+ type: string
657
+ family:
658
+ type: string
659
+ active:
660
+ type: boolean
661
+ description: Will be false only if the alarm is disabled in the configuration
662
+ disabled:
663
+ type: boolean
664
+ description: Whether the health check for this alarm has been disabled via a health command API DISABLE command.
665
+ silenced:
666
+ type: boolean
667
+ description: Whether notifications for this alarm have been silenced via a health command API SILENCE command.
668
+ exec:
669
+ type: string
670
+ recipient:
671
+ type: string
672
+ source:
673
+ type: string
674
+ units:
675
+ type: string
676
+ info:
677
+ type: string
678
+ status:
679
+ type: string
680
+ last_status_change:
681
+ type: integer
682
+ format: int32
683
+ last_updated:
684
+ type: integer
685
+ format: int32
686
+ next_update:
687
+ type: integer
688
+ format: int32
689
+ update_every:
690
+ type: integer
691
+ format: int32
692
+ delay_up_duration:
693
+ type: integer
694
+ format: int32
695
+ delay_down_duration:
696
+ type: integer
697
+ format: int32
698
+ delay_max_duration:
699
+ type: integer
700
+ format: int32
701
+ delay_multiplier:
702
+ type: integer
703
+ format: int32
704
+ delay:
705
+ type: integer
706
+ format: int32
707
+ delay_up_to_timestamp:
708
+ type: integer
709
+ format: int32
710
+ value_string:
711
+ type: string
712
+ no_clear_notification:
713
+ type: boolean
714
+ lookup_dimensions:
715
+ type: string
716
+ db_after:
717
+ type: integer
718
+ format: int32
719
+ db_before:
720
+ type: integer
721
+ format: int32
722
+ lookup_method:
723
+ type: string
724
+ lookup_after:
725
+ type: integer
726
+ format: int32
727
+ lookup_before:
728
+ type: integer
729
+ format: int32
730
+ lookup_options:
731
+ type: string
732
+ calc:
733
+ type: string
734
+ calc_parsed:
735
+ type: string
736
+ warn:
737
+ type: string
738
+ warn_parsed:
739
+ type: string
740
+ crit:
741
+ type: string
742
+ crit_parsed:
743
+ type: string
744
+ green:
745
+ type: string
746
+ format: nullable
747
+ red:
748
+ type: string
749
+ format: nullable
750
+ value:
751
+ type: number
752
+ alarm_log_entry:
753
+ type: object
754
+ properties:
755
+ hostname:
756
+ type: string
757
+ unique_id:
758
+ type: integer
759
+ format: int32
760
+ alarm_id:
761
+ type: integer
762
+ format: int32
763
+ alarm_event_id:
764
+ type: integer
765
+ format: int32
766
+ name:
767
+ type: string
768
+ chart:
769
+ type: string
770
+ family:
771
+ type: string
772
+ processed:
773
+ type: boolean
774
+ updated:
775
+ type: boolean
776
+ exec_run:
777
+ type: integer
778
+ format: int32
779
+ exec_failed:
780
+ type: boolean
781
+ exec:
782
+ type: string
783
+ recipient:
784
+ type: string
785
+ exec_code:
786
+ type: integer
787
+ format: int32
788
+ source:
789
+ type: string
790
+ units:
791
+ type: string
792
+ when:
793
+ type: integer
794
+ format: int32
795
+ duration:
796
+ type: integer
797
+ format: int32
798
+ non_clear_duration:
799
+ type: integer
800
+ format: int32
801
+ status:
802
+ type: string
803
+ old_status:
804
+ type: string
805
+ delay:
806
+ type: integer
807
+ format: int32
808
+ delay_up_to_timestamp:
809
+ type: integer
810
+ format: int32
811
+ updated_by_id:
812
+ type: integer
813
+ format: int32
814
+ updates_id:
815
+ type: integer
816
+ format: int32
817
+ value_string:
818
+ type: string
819
+ old_value_string:
820
+ type: string
821
+ silenced:
822
+ type: string
823
+ info:
824
+ type: string
825
+ value:
826
+ type: string
827
+ format: nullable
828
+ old_value:
829
+ type: string
830
+ format: nullable
web/api/web_api_v1.c
+64
-2
@@ -83,6 +83,68 @@ void web_client_api_v1_init(void) {
83
api_v1_data_google_formats[i].hash = simple_hash(api_v1_data_google_formats[i].name);
84
85
web_client_api_v1_init_grouping();
86
+
87
+ uuid_t uuid;
88
+
89
+ // generate
90
+ uuid_generate(uuid);
91
+
92
+ // unparse (to string)
93
+ char uuid_str[37];
94
+ uuid_unparse_lower(uuid, uuid_str);
95
+}
96
+
97
+char *get_mgmt_api_key(void) {
98
+ char filename[FILENAME_MAX + 1];
99
+ snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir);
100
+ char *api_key_filename=config_get(CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
101
+ static char guid[GUID_LEN + 1] = "";
102
+
103
+ if(likely(guid[0]))
104
+ return guid;
105
+
106
+ // read it from disk
107
+ int fd = open(api_key_filename, O_RDONLY);
108
+ if(fd != -1) {
109
+ char buf[GUID_LEN + 1];
110
+ if(read(fd, buf, GUID_LEN) != GUID_LEN)
111
+ error("Failed to read management API key from '%s'", api_key_filename);
112
+ else {
113
+ buf[GUID_LEN] = '\0';
114
+ if(regenerate_guid(buf, guid) == -1) {
115
+ error("Failed to validate management API key '%s' from '%s'.",
116
+ buf, api_key_filename);
117
+
118
+ guid[0] = '\0';
119
+ }
120
+ }
121
+ close(fd);
122
+ }
123
+
124
+ // generate a new one?
125
+ if(!guid[0]) {
126
+ uuid_t uuid;
127
+
128
+ uuid_generate_time(uuid);
129
+ uuid_unparse_lower(uuid, guid);
130
+ guid[GUID_LEN] = '\0';
131
+
132
+ // save it
133
+ fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC, 444);
134
+ if(fd == -1)
135
+ fatal("Cannot create unique management API key file '%s'. Please fix this.", api_key_filename);
136
+
137
+ if(write(fd, guid, GUID_LEN) != GUID_LEN)
138
+ fatal("Cannot write the unique management API key file '%s'. Please fix this.", api_key_filename);
139
+
140
+ close(fd);
141
+ }
142
+
143
+ return guid;
144
+}
145
+
146
+void web_client_api_v1_management_init(void) {
147
+ api_secret = get_mgmt_api_key();
148
}
149
150
inline uint32_t web_client_api_request_v1_data_options(char *o) {
@@ -697,7 +759,7 @@ static struct api_command {
759
{ "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_log },
760
{ "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_variables },
761
{ "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_allmetrics },
700
-
762
+ { "manage/health", 0, WEB_CLIENT_ACL_MGMT, web_client_api_request_v1_mgmt_health },
763
// terminator
764
{ NULL, 0, WEB_CLIENT_ACL_NONE, NULL },
765
};
@@ -721,7 +783,7 @@ inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *
783
784
for(i = 0; api_commands[i].command ;i++) {
785
if(unlikely(hash == api_commands[i].hash && !strcmp(tok, api_commands[i].command))) {
724
- if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl))
786
+ if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl))
787
return web_client_permission_denied(w);
788
789
return api_commands[i].callback(host, w, url);
web/api/web_api_v1.h
+4
@@ -6,6 +6,7 @@
6
#include "daemon/common.h"
7
#include "web/api/badges/web_buffer_svg.h"
8
#include "web/api/formatters/rrd2json.h"
9
+#include "web/api/health/health_cmdapi.h"
10
11
extern uint32_t web_client_api_request_v1_data_options(char *o);
12
extern uint32_t web_client_api_request_v1_data_format(char *name);
@@ -23,5 +24,8 @@ extern int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, c
24
extern int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url);
25
26
extern void web_client_api_v1_init(void);
27
+extern void web_client_api_v1_management_init(void);
28
+
29
+char *api_secret;
30
31
#endif //NETDATA_WEB_API_V1_H
web/server/Makefile.am
-2
@@ -4,8 +4,6 @@ AUTOMAKE_OPTIONS = subdir-objects
4
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5
6
SUBDIRS = \
7
- single \
8
- multi \
7
static \
8
$(NULL)
9
web/server/README.md
+26
-27
@@ -1,34 +1,21 @@
1
# Web server
2
3
-Netdata supports 3 implementations of its internal web server:
4
-
5
-- `static-threaded` is a web server with a fix (configured number of threads)
6
-- `single-threaded` is a simple web server running with a single thread
7
-- `multi-threaded` is a web server that spawns a thread for each client connection
8
-- `none` to disable the web server
9
-
10
-We suggest to use the `static-threaded` one. It is the most efficient.
11
-
12
-All versions of the web servers use non-blocking I/O.
13
-
14
-All web servers respect the `keep-alive` HTTP header to serve multiple HTTP requests via the same connection.
3
+The Netdata web server runs as `static-threaded`, i.e. with a fixed, configurable number of threads.
4
+It uses non-blocking I/O and respects the `keep-alive` HTTP header to serve multiple HTTP requests via the same connection.
5
6
## Configuration
7
18
-### Selecting the web server
19
-
20
-You can select the web server implementation by editing `netdata.conf` and setting:
8
+You can disable the web server by editing `netdata.conf` and setting:
9
10
```
11
[web]
24
- mode = none | single-threaded | multi-threaded | static-threaded
12
+ mode = none
13
```
14
27
-The `static` web server supports also these settings:
15
+With the web server enabled, you can control the number of threads and sockets with the following settings:
16
17
```
18
[web]
31
- mode = static-threaded
19
web server threads = 4
20
web server max sockets = 512
21
```
@@ -39,28 +26,37 @@ The `web server max sockets` setting is automatically adjusted to 50% of the max
26
27
### Binding netdata to multiple ports
28
42
-Netdata can bind to multiple IPs and ports. Up to 100 sockets can be used (you can increase it at compile time with `CFLAGS="-DMAX_LISTEN_FDS=200" ./netdata-installer.sh ...`).
29
+Netdata can bind to multiple IPs and ports, offering access to different services on each. Up to 100 sockets can be used (you can increase it at compile time with `CFLAGS="-DMAX_LISTEN_FDS=200" ./netdata-installer.sh ...`).
30
31
The ports to bind are controlled via `[web].bind to`, like this:
32
33
```
34
[web]
35
default port = 19999
49
- bind to = 127.0.0.1 10.1.1.1:19998 hostname:19997 [::]:19996 localhost:19995 *:http unix:/tmp/netdata.sock
36
+ bind to = 127.0.0.1=dashboard 10.1.1.1:19998=management|netdata.conf hostname:19997=badges [::]:19996=streaming localhost:19995=registry *:http=dashboard unix:/tmp/netdata.sock
37
```
38
39
Using the above, netdata will bind to:
40
54
-- IPv4 127.0.0.1 at port 19999 (port was used from `default port`)
55
-- IPv4 10.1.1.1 at port 19998
56
-- All the IPs `hostname` resolves to (both IPv4 and IPv6 depending on the resolved IPs) at port 19997
57
-- All IPv6 IPs at port 19996
58
-- All the IPs `localhost` resolves to (both IPv4 and IPv6 depending the resolved IPs) at port 19996
59
-- All IPv4 and IPv6 IPs at port `http` as set in `/etc/services`
60
-- Unix domain socket `/tmp/netdata.sock`
41
+- IPv4 127.0.0.1 at port 19999 (port was used from `default port`). Only the UI (dashboard) and the read API will be accessible on this port.
42
+- IPv4 10.1.1.1 at port 19998. The management API and netdata.conf will be accessible on this port.
43
+- All the IPs `hostname` resolves to (both IPv4 and IPv6 depending on the resolved IPs) at port 19997. Only badges will be accessible on this port.
44
+- All IPv6 IPs at port 19996. Only metric streaming requests from other netdata agents will be accepted on this port.
45
+- All the IPs `localhost` resolves to (both IPv4 and IPv6 depending the resolved IPs) at port 19996. This port will only accept registry API requests.
46
+- All IPv4 and IPv6 IPs at port `http` as set in `/etc/services`. Only the UI (dashboard) and the read API will be accessible on this port.
47
+- Unix domain socket `/tmp/netdata.sock`. All requests are serviceable on this socket.
48
49
The option `[web].default port` is used when an entries in `[web].bind to` do not specify a port.
50
51
+Note that the access permissions specified with the `=request type|request type|...` format are available from version 1.12 onwards.
52
+As shown in the example above, these permissions are optional, with the default being to permit all request types on the specified port.
53
+The request types are strings identical to the `allow X from` directives of the access lists, i.e. `dashboard`, `streaming`, `registry`, `netdata.conf`, `badges` and `management`.
54
+The access lists themselves and the general setting `allow connections from` in the next section are applied regardless of the ports that are configured to provide these services.
55
+The API requests are serviced as follows:
56
+- `dashboard` gives access to the UI, the read API and badges API calls.
57
+- `badges` gives access only to the badges API calls.
58
+- `management` gives access only to the management API calls.
59
+
60
### Access lists
61
62
Netdata supports access lists in `netdata.conf`:
@@ -72,6 +68,7 @@ Netdata supports access lists in `netdata.conf`:
68
allow badges from = *
69
allow streaming from = *
70
allow netdata.conf from = localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.*
71
+ allow management from = localhost
72
```
73
74
`*` does string matches on the IPs of the clients.
@@ -92,6 +89,8 @@ Netdata supports access lists in `netdata.conf`:
89
- `allow netdata.conf from` checks the IP to allow `http://netdata.host:19999/netdata.conf`.
90
The IPs listed are all the private IPv4 addresses, including link local IPv6 addresses. Keep in mind that connections to netdata API ports are filtered by `allow connections from`. So, IPs allowed by `allow netdata.conf from` should also be allowed by `allow connections from`.
91
92
+- `allow management from` checks the IPs to allow API management calls. Management via the API is currently supported for [health](../api/health/#health-management-api)
93
+
94
### Other netdata.conf [web] section options
95
setting | default | info
96
:------:|:-------:|:----
web/server/multi/README.md
deleted
-9
@@ -1,9 +0,0 @@
1
-# `multi-threaded` web server
2
-
3
-The `multi-threaded` web server spawns a thread for each connection it receives.
4
-
5
-Each thread uses non-blocking I/O so it can serve any number of web requests in parallel,
6
-though this is not supported by HTTP, so in practice each thread serves all the requests sequentially.
7
-
8
-Each thread respects the `keep-alive` HTTP header to serve multiple HTTP requests via the same connection.
9
-[]()
web/server/multi/multi-threaded.c
deleted
-314
@@ -1,314 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-#define WEB_SERVER_INTERNALS 1
4
-#include "multi-threaded.h"
5
-
6
-// --------------------------------------------------------------------------------------
7
-// the thread of a single client - for the MULTI-THREADED web server
8
-
9
-// 1. waits for input and output, using async I/O
10
-// 2. it processes HTTP requests
11
-// 3. it generates HTTP responses
12
-// 4. it copies data from input to output if mode is FILECOPY
13
-
14
-int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS;
15
-int web_client_first_request_timeout = DEFAULT_TIMEOUT_TO_RECEIVE_FIRST_WEB_REQUEST;
16
-long web_client_streaming_rate_t = 0L;
17
-
18
-static void multi_threaded_web_client_worker_main_cleanup(void *ptr) {
19
- struct web_client *w = ptr;
20
- WEB_CLIENT_IS_DEAD(w);
21
- w->running = 0;
22
-}
23
-
24
-static void *multi_threaded_web_client_worker_main(void *ptr) {
25
- netdata_thread_cleanup_push(multi_threaded_web_client_worker_main_cleanup, ptr);
26
-
27
- struct web_client *w = ptr;
28
- w->running = 1;
29
-
30
- struct pollfd fds[2], *ifd, *ofd;
31
- int retval, timeout_ms;
32
- nfds_t fdmax = 0;
33
-
34
- while(!netdata_exit) {
35
- if(unlikely(web_client_check_dead(w))) {
36
- debug(D_WEB_CLIENT, "%llu: client is dead.", w->id);
37
- break;
38
- }
39
- else if(unlikely(!web_client_has_wait_receive(w) && !web_client_has_wait_send(w))) {
40
- debug(D_WEB_CLIENT, "%llu: client is not set for neither receiving nor sending data.", w->id);
41
- break;
42
- }
43
-
44
- if(unlikely(w->ifd < 0 || w->ofd < 0)) {
45
- error("%llu: invalid file descriptor, ifd = %d, ofd = %d (required 0 <= fd", w->id, w->ifd, w->ofd);
46
- break;
47
- }
48
-
49
- if(w->ifd == w->ofd) {
50
- fds[0].fd = w->ifd;
51
- fds[0].events = 0;
52
- fds[0].revents = 0;
53
-
54
- if(web_client_has_wait_receive(w)) fds[0].events |= POLLIN;
55
- if(web_client_has_wait_send(w)) fds[0].events |= POLLOUT;
56
-
57
- fds[1].fd = -1;
58
- fds[1].events = 0;
59
- fds[1].revents = 0;
60
-
61
- ifd = ofd = &fds[0];
62
-
63
- fdmax = 1;
64
- }
65
- else {
66
- fds[0].fd = w->ifd;
67
- fds[0].events = 0;
68
- fds[0].revents = 0;
69
- if(web_client_has_wait_receive(w)) fds[0].events |= POLLIN;
70
- ifd = &fds[0];
71
-
72
- fds[1].fd = w->ofd;
73
- fds[1].events = 0;
74
- fds[1].revents = 0;
75
- if(web_client_has_wait_send(w)) fds[1].events |= POLLOUT;
76
- ofd = &fds[1];
77
-
78
- fdmax = 2;
79
- }
80
-
81
- debug(D_WEB_CLIENT, "%llu: Waiting socket async I/O for %s %s", w->id, web_client_has_wait_receive(w)?"INPUT":"", web_client_has_wait_send(w)?"OUTPUT":"");
82
- errno = 0;
83
- timeout_ms = web_client_timeout * 1000;
84
- retval = poll(fds, fdmax, timeout_ms);
85
-
86
- if(unlikely(netdata_exit)) break;
87
-
88
- if(unlikely(retval == -1)) {
89
- if(errno == EAGAIN || errno == EINTR) {
90
- debug(D_WEB_CLIENT, "%llu: EAGAIN received.", w->id);
91
- continue;
92
- }
93
-
94
- debug(D_WEB_CLIENT, "%llu: LISTENER: poll() failed (input fd = %d, output fd = %d). Closing client.", w->id, w->ifd, w->ofd);
95
- break;
96
- }
97
- else if(unlikely(!retval)) {
98
- debug(D_WEB_CLIENT, "%llu: Timeout while waiting socket async I/O for %s %s", w->id, web_client_has_wait_receive(w)?"INPUT":"", web_client_has_wait_send(w)?"OUTPUT":"");
99
- break;
100
- }
101
-
102
- if(unlikely(netdata_exit)) break;
103
-
104
- int used = 0;
105
- if(web_client_has_wait_send(w) && ofd->revents & POLLOUT) {
106
- used++;
107
- if(web_client_send(w) < 0) {
108
- debug(D_WEB_CLIENT, "%llu: Cannot send data to client. Closing client.", w->id);
109
- break;
110
- }
111
- }
112
-
113
- if(unlikely(netdata_exit)) break;
114
-
115
- if(web_client_has_wait_receive(w) && (ifd->revents & POLLIN || ifd->revents & POLLPRI)) {
116
- used++;
117
- if(web_client_receive(w) < 0) {
118
- debug(D_WEB_CLIENT, "%llu: Cannot receive data from client. Closing client.", w->id);
119
- break;
120
- }
121
-
122
- if(w->mode == WEB_CLIENT_MODE_NORMAL) {
123
- debug(D_WEB_CLIENT, "%llu: Attempting to process received data.", w->id);
124
- web_client_process_request(w);
125
-
126
- // if the sockets are closed, may have transferred this client
127
- // to plugins.d
128
- if(unlikely(w->mode == WEB_CLIENT_MODE_STREAM))
129
- break;
130
- }
131
- }
132
-
133
- if(unlikely(!used)) {
134
- debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on socket.", w->id);
135
- break;
136
- }
137
- }
138
-
139
- if(w->mode != WEB_CLIENT_MODE_STREAM)
140
- web_server_log_connection(w, "DISCONNECTED");
141
-
142
- web_client_request_done(w);
143
-
144
- debug(D_WEB_CLIENT, "%llu: done...", w->id);
145
-
146
- // close the sockets/files now
147
- // to free file descriptors
148
- if(w->ifd == w->ofd) {
149
- if(w->ifd != -1) close(w->ifd);
150
- }
151
- else {
152
- if(w->ifd != -1) close(w->ifd);
153
- if(w->ofd != -1) close(w->ofd);
154
- }
155
- w->ifd = -1;
156
- w->ofd = -1;
157
-
158
- netdata_thread_cleanup_pop(1);
159
- return NULL;
160
-}
161
-
162
-// --------------------------------------------------------------------------------------
163
-// the main socket listener - MULTI-THREADED
164
-
165
-// 1. it accepts new incoming requests on our port
166
-// 2. creates a new web_client for each connection received
167
-// 3. spawns a new netdata_thread to serve the client (this is optimal for keep-alive clients)
168
-// 4. cleans up old web_clients that their netdata_threads have been exited
169
-
170
-static void web_client_multi_threaded_web_server_release_clients(void) {
171
- struct web_client *w;
172
- for(w = web_clients_cache.used; w ; ) {
173
- if(unlikely(!w->running && web_client_check_dead(w))) {
174
- struct web_client *t = w->next;
175
- web_client_release(w);
176
- w = t;
177
- }
178
- else
179
- w = w->next;
180
- }
181
-}
182
-
183
-static void web_client_multi_threaded_web_server_stop_all_threads(void) {
184
- struct web_client *w;
185
-
186
- int found = 1;
187
- usec_t max = 2 * USEC_PER_SEC, step = 50000;
188
- for(w = web_clients_cache.used; w ; w = w->next) {
189
- if(w->running) {
190
- found++;
191
- info("stopping web client %s, id %llu", w->client_ip, w->id);
192
- netdata_thread_cancel(w->thread);
193
- }
194
- }
195
-
196
- while(found && max > 0) {
197
- max -= step;
198
- info("Waiting %d web threads to finish...", found);
199
- sleep_usec(step);
200
- found = 0;
201
- for(w = web_clients_cache.used; w ; w = w->next)
202
- if(w->running) found++;
203
- }
204
-
205
- if(found)
206
- error("%d web threads are taking too long to finish. Giving up.", found);
207
-}
208
-
209
-static struct pollfd *socket_listen_main_multi_threaded_fds = NULL;
210
-
211
-static void socket_listen_main_multi_threaded_cleanup(void *data) {
212
- struct netdata_static_thread *static_thread = (struct netdata_static_thread *)data;
213
- static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
214
-
215
- info("cleaning up...");
216
-
217
- info("releasing allocated memory...");
218
- freez(socket_listen_main_multi_threaded_fds);
219
-
220
- info("closing all sockets...");
221
- listen_sockets_close(&api_sockets);
222
-
223
- info("stopping all running web server threads...");
224
- web_client_multi_threaded_web_server_stop_all_threads();
225
-
226
- info("freeing web clients cache...");
227
- web_client_cache_destroy();
228
-
229
- info("cleanup completed.");
230
- static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
231
-}
232
-
233
-#define CLEANUP_EVERY_EVENTS 60
234
-void *socket_listen_main_multi_threaded(void *ptr) {
235
- netdata_thread_cleanup_push(socket_listen_main_multi_threaded_cleanup, ptr);
236
-
237
- web_server_mode = WEB_SERVER_MODE_MULTI_THREADED;
238
- web_server_is_multithreaded = 1;
239
-
240
- struct web_client *w;
241
- int retval, counter = 0;
242
-
243
- if(!api_sockets.opened)
244
- fatal("LISTENER: No sockets to listen to.");
245
-
246
- socket_listen_main_multi_threaded_fds = callocz(sizeof(struct pollfd), api_sockets.opened);
247
-
248
- size_t i;
249
- for(i = 0; i < api_sockets.opened ;i++) {
250
- socket_listen_main_multi_threaded_fds[i].fd = api_sockets.fds[i];
251
- socket_listen_main_multi_threaded_fds[i].events = POLLIN;
252
- socket_listen_main_multi_threaded_fds[i].revents = 0;
253
-
254
- info("Listening on '%s'", (api_sockets.fds_names[i])?api_sockets.fds_names[i]:"UNKNOWN");
255
- }
256
-
257
- int timeout_ms = 1 * 1000;
258
-
259
- while(!netdata_exit) {
260
-
261
- // debug(D_WEB_CLIENT, "LISTENER: Waiting...");
262
- retval = poll(socket_listen_main_multi_threaded_fds, api_sockets.opened, timeout_ms);
263
-
264
- if(unlikely(retval == -1)) {
265
- error("LISTENER: poll() failed.");
266
- continue;
267
- }
268
- else if(unlikely(!retval)) {
269
- debug(D_WEB_CLIENT, "LISTENER: poll() timeout.");
270
- counter++;
271
- continue;
272
- }
273
-
274
- for(i = 0 ; i < api_sockets.opened ; i++) {
275
- short int revents = socket_listen_main_multi_threaded_fds[i].revents;
276
-
277
- // check for new incoming connections
278
- if(revents & POLLIN || revents & POLLPRI) {
279
- socket_listen_main_multi_threaded_fds[i].revents = 0;
280
-
281
- w = web_client_create_on_listenfd(socket_listen_main_multi_threaded_fds[i].fd);
282
- if(unlikely(!w)) {
283
- // no need for error log - web_client_create_on_listenfd already logged the error
284
- continue;
285
- }
286
-
287
- if(api_sockets.fds_families[i] == AF_UNIX)
288
- web_client_set_unix(w);
289
- else
290
- web_client_set_tcp(w);
291
-
292
- char tag[NETDATA_THREAD_TAG_MAX + 1];
293
- snprintfz(tag, NETDATA_THREAD_TAG_MAX, "WEB_CLIENT[%llu,[%s]:%s]", w->id, w->client_ip, w->client_port);
294
-
295
- w->running = 1;
296
- if(netdata_thread_create(&w->thread, tag, NETDATA_THREAD_OPTION_DONT_LOG, multi_threaded_web_client_worker_main, w) != 0) {
297
- w->running = 0;
298
- web_client_release(w);
299
- }
300
- }
301
- }
302
-
303
- counter++;
304
- if(counter > CLEANUP_EVERY_EVENTS) {
305
- counter = 0;
306
- web_client_multi_threaded_web_server_release_clients();
307
- }
308
- }
309
-
310
- netdata_thread_cleanup_pop(1);
311
- return NULL;
312
-}
313
-
314
-
web/server/multi/multi-threaded.h
deleted
-10
@@ -1,10 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-#ifndef NETDATA_WEB_SERVER_MULTI_THREADED_H
4
-#define NETDATA_WEB_SERVER_MULTI_THREADED_H
5
-
6
-#include "web/server/web_server.h"
7
-
8
-extern void *socket_listen_main_multi_threaded(void *ptr);
9
-
10
-#endif //NETDATA_WEB_SERVER_MULTI_THREADED_H
web/server/single/Makefile.am
deleted
-11
@@ -1,11 +0,0 @@
1
-# SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-AUTOMAKE_OPTIONS = subdir-objects
4
-MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5
-
6
-SUBDIRS = \
7
- $(NULL)
8
-
9
-dist_noinst_DATA = \
10
- README.md \
11
- $(NULL)
web/server/single/README.md
deleted
-7
@@ -1,7 +0,0 @@
1
-# `single-threaded` web server
2
-
3
-The `single-threaded` web server runs as a single thread inside netdata.
4
-It uses non-blocking I/O so it can serve any number of web requests in parallel.
5
-
6
-This web server respects the `keep-alive` HTTP header to serve multiple HTTP requests via the same connection.
7
-[]()
web/server/single/single-threaded.c
deleted
-194
@@ -1,194 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-#define WEB_SERVER_INTERNALS 1
4
-#include "single-threaded.h"
5
-
6
-// --------------------------------------------------------------------------------------
7
-// the main socket listener - SINGLE-THREADED
8
-
9
-struct web_client *single_threaded_clients[FD_SETSIZE];
10
-
11
-static inline int single_threaded_link_client(struct web_client *w, fd_set *ifds, fd_set *ofds, fd_set *efds, int *max) {
12
- if(unlikely(web_client_check_dead(w) || (!web_client_has_wait_receive(w) && !web_client_has_wait_send(w)))) {
13
- return 1;
14
- }
15
-
16
- if(unlikely(w->ifd < 0 || w->ifd >= (int)FD_SETSIZE || w->ofd < 0 || w->ofd >= (int)FD_SETSIZE)) {
17
- error("%llu: invalid file descriptor, ifd = %d, ofd = %d (required 0 <= fd < FD_SETSIZE (%d)", w->id, w->ifd, w->ofd, (int)FD_SETSIZE);
18
- return 1;
19
- }
20
-
21
- FD_SET(w->ifd, efds);
22
- if(unlikely(*max < w->ifd)) *max = w->ifd;
23
-
24
- if(unlikely(w->ifd != w->ofd)) {
25
- if(*max < w->ofd) *max = w->ofd;
26
- FD_SET(w->ofd, efds);
27
- }
28
-
29
- if(web_client_has_wait_receive(w)) FD_SET(w->ifd, ifds);
30
- if(web_client_has_wait_send(w)) FD_SET(w->ofd, ofds);
31
-
32
- single_threaded_clients[w->ifd] = w;
33
- single_threaded_clients[w->ofd] = w;
34
-
35
- return 0;
36
-}
37
-
38
-static inline int single_threaded_unlink_client(struct web_client *w, fd_set *ifds, fd_set *ofds, fd_set *efds) {
39
- FD_CLR(w->ifd, efds);
40
- if(unlikely(w->ifd != w->ofd)) FD_CLR(w->ofd, efds);
41
-
42
- if(web_client_has_wait_receive(w)) FD_CLR(w->ifd, ifds);
43
- if(web_client_has_wait_send(w)) FD_CLR(w->ofd, ofds);
44
-
45
- single_threaded_clients[w->ifd] = NULL;
46
- single_threaded_clients[w->ofd] = NULL;
47
-
48
- if(unlikely(web_client_check_dead(w) || (!web_client_has_wait_receive(w) && !web_client_has_wait_send(w)))) {
49
- return 1;
50
- }
51
-
52
- return 0;
53
-}
54
-
55
-static void socket_listen_main_single_threaded_cleanup(void *data) {
56
- struct netdata_static_thread *static_thread = (struct netdata_static_thread *)data;
57
- static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
58
-
59
- info("closing all sockets...");
60
- listen_sockets_close(&api_sockets);
61
-
62
- info("freeing web clients cache...");
63
- web_client_cache_destroy();
64
-
65
- info("cleanup completed.");
66
- static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
67
-}
68
-
69
-void *socket_listen_main_single_threaded(void *ptr) {
70
- netdata_thread_cleanup_push(socket_listen_main_single_threaded_cleanup, ptr);
71
- web_server_mode = WEB_SERVER_MODE_SINGLE_THREADED;
72
- web_server_is_multithreaded = 0;
73
-
74
- struct web_client *w;
75
-
76
- if(!api_sockets.opened)
77
- fatal("LISTENER: no listen sockets available.");
78
-
79
- size_t i;
80
- for(i = 0; i < (size_t)FD_SETSIZE ; i++)
81
- single_threaded_clients[i] = NULL;
82
-
83
- fd_set ifds, ofds, efds, rifds, rofds, refds;
84
- FD_ZERO (&ifds);
85
- FD_ZERO (&ofds);
86
- FD_ZERO (&efds);
87
- int fdmax = 0;
88
-
89
- for(i = 0; i < api_sockets.opened ; i++) {
90
- if (api_sockets.fds[i] < 0 || api_sockets.fds[i] >= (int)FD_SETSIZE)
91
- fatal("LISTENER: Listen socket %d is not ready, or invalid.", api_sockets.fds[i]);
92
-
93
- info("Listening on '%s'", (api_sockets.fds_names[i])?api_sockets.fds_names[i]:"UNKNOWN");
94
-
95
- FD_SET(api_sockets.fds[i], &ifds);
96
- FD_SET(api_sockets.fds[i], &efds);
97
- if(fdmax < api_sockets.fds[i])
98
- fdmax = api_sockets.fds[i];
99
- }
100
-
101
- while(!netdata_exit) {
102
- debug(D_WEB_CLIENT_ACCESS, "LISTENER: single threaded web server waiting (fdmax = %d)...", fdmax);
103
-
104
- struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
105
- rifds = ifds;
106
- rofds = ofds;
107
- refds = efds;
108
- int retval = select(fdmax+1, &rifds, &rofds, &refds, &tv);
109
-
110
- if(unlikely(retval == -1)) {
111
- error("LISTENER: select() failed.");
112
- continue;
113
- }
114
- else if(likely(retval)) {
115
- debug(D_WEB_CLIENT_ACCESS, "LISTENER: got something.");
116
-
117
- for(i = 0; i < api_sockets.opened ; i++) {
118
- if (FD_ISSET(api_sockets.fds[i], &rifds)) {
119
- debug(D_WEB_CLIENT_ACCESS, "LISTENER: new connection.");
120
- w = web_client_create_on_listenfd(api_sockets.fds[i]);
121
- if(unlikely(!w))
122
- continue;
123
-
124
- if(api_sockets.fds_families[i] == AF_UNIX)
125
- web_client_set_unix(w);
126
- else
127
- web_client_set_tcp(w);
128
-
129
- if (single_threaded_link_client(w, &ifds, &ofds, &ifds, &fdmax) != 0) {
130
- web_client_release(w);
131
- }
132
- }
133
- }
134
-
135
- for(i = 0 ; i <= (size_t)fdmax ; i++) {
136
- if(likely(!FD_ISSET(i, &rifds) && !FD_ISSET(i, &rofds) && !FD_ISSET(i, &refds)))
137
- continue;
138
-
139
- w = single_threaded_clients[i];
140
- if(unlikely(!w)) {
141
- // error("no client on slot %zu", i);
142
- continue;
143
- }
144
-
145
- if(unlikely(single_threaded_unlink_client(w, &ifds, &ofds, &efds) != 0)) {
146
- // error("failed to unlink client %zu", i);
147
- web_client_release(w);
148
- continue;
149
- }
150
-
151
- if (unlikely(FD_ISSET(w->ifd, &refds) || FD_ISSET(w->ofd, &refds))) {
152
- // error("no input on client %zu", i);
153
- web_client_release(w);
154
- continue;
155
- }
156
-
157
- if (unlikely(web_client_has_wait_receive(w) && FD_ISSET(w->ifd, &rifds))) {
158
- if (unlikely(web_client_receive(w) < 0)) {
159
- // error("cannot read from client %zu", i);
160
- web_client_release(w);
161
- continue;
162
- }
163
-
164
- if (w->mode != WEB_CLIENT_MODE_FILECOPY) {
165
- debug(D_WEB_CLIENT, "%llu: Processing received data.", w->id);
166
- web_client_process_request(w);
167
- }
168
- }
169
-
170
- if (unlikely(web_client_has_wait_send(w) && FD_ISSET(w->ofd, &rofds))) {
171
- if (unlikely(web_client_send(w) < 0)) {
172
- // error("cannot send data to client %zu", i);
173
- debug(D_WEB_CLIENT, "%llu: Cannot send data to client. Closing client.", w->id);
174
- web_client_release(w);
175
- continue;
176
- }
177
- }
178
-
179
- if(unlikely(single_threaded_link_client(w, &ifds, &ofds, &efds, &fdmax) != 0)) {
180
- // error("failed to link client %zu", i);
181
- web_client_release(w);
182
- }
183
- }
184
- }
185
- else {
186
- debug(D_WEB_CLIENT_ACCESS, "LISTENER: single threaded web server timeout.");
187
- }
188
- }
189
-
190
- netdata_thread_cleanup_pop(1);
191
- return NULL;
192
-}
193
-
194
-
web/server/single/single-threaded.h
deleted
-10
@@ -1,10 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-#ifndef NETDATA_WEB_SERVER_SINGLE_THREADED_H
4
-#define NETDATA_WEB_SERVER_SINGLE_THREADED_H
5
-
6
-#include "web/server/web_server.h"
7
-
8
-extern void *socket_listen_main_single_threaded(void *ptr);
9
-
10
-#endif //NETDATA_WEB_SERVER_SINGLE_THREADED_H
web/server/static/static-threaded.c
+15
-2
@@ -3,10 +3,14 @@
3
#define WEB_SERVER_INTERNALS 1
4
#include "static-threaded.h"
5
6
+int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS;
7
+int web_client_first_request_timeout = DEFAULT_TIMEOUT_TO_RECEIVE_FIRST_WEB_REQUEST;
8
+long web_client_streaming_rate_t = 0L;
9
+
10
// ----------------------------------------------------------------------------
11
// high level web clients connection management
12
9
-static struct web_client *web_client_create_on_fd(int fd, const char *client_ip, const char *client_port) {
13
+static struct web_client *web_client_create_on_fd(int fd, const char *client_ip, const char *client_port, int port_acl) {
14
struct web_client *w;
15
16
w = web_client_get_from_cache_or_allocate();
@@ -17,6 +21,7 @@ static struct web_client *web_client_create_on_fd(int fd, const char *client_ip,
21
22
if(unlikely(!*w->client_ip)) strcpy(w->client_ip, "-");
23
if(unlikely(!*w->client_port)) strcpy(w->client_port, "-");
24
+ w->port_acl = port_acl;
25
26
web_client_initialize_connection(w);
27
return(w);
@@ -44,6 +49,7 @@ struct web_server_static_threaded_worker {
49
};
50
51
static long long static_threaded_workers_count = 1;
52
+
53
static struct web_server_static_threaded_worker *static_workers_private_data = NULL;
54
static __thread struct web_server_static_threaded_worker *worker_private = NULL;
55
@@ -143,7 +149,7 @@ static void *web_server_add_callback(POLLINFO *pi, short int *events, void *data
149
*events = POLLIN;
150
151
debug(D_WEB_CLIENT_ACCESS, "LISTENER on %d: new connection.", pi->fd);
146
- struct web_client *w = web_client_create_on_fd(pi->fd, pi->client_ip, pi->client_port);
152
+ struct web_client *w = web_client_create_on_fd(pi->fd, pi->client_ip, pi->client_port, pi->port_acl);
153
w->pollinfo_slot = pi->slot;
154
155
if(unlikely(pi->socktype == AF_UNIX))
@@ -200,6 +206,7 @@ static int web_server_rcv_callback(POLLINFO *pi, short int *events) {
206
POLLINFO *fpi = poll_add_fd(
207
pi->p
208
, w->ifd
209
+ , pi->port_acl
210
, 0
211
, POLLINFO_FLAG_CLIENT_SOCKET
212
, "FILENAME"
@@ -394,7 +401,13 @@ void *socket_listen_main_static_threaded(void *ptr) {
401
// so, if the machine has more CPUs, avoid using resources unnecessarily
402
int def_thread_count = (processors > 6)?6:processors;
403
404
+ if (!strcmp(config_get(CONFIG_SECTION_WEB, "mode", ""),"single-threaded")) {
405
+ info("Running web server with one thread, because mode is single-threaded");
406
+ config_set(CONFIG_SECTION_WEB, "mode", "static-threaded");
407
+ def_thread_count = 1;
408
+ }
409
static_threaded_workers_count = config_get_number(CONFIG_SECTION_WEB, "web server threads", def_thread_count);
410
+
411
if(static_threaded_workers_count < 1) static_threaded_workers_count = 1;
412
413
size_t max_sockets = (size_t)config_get_number(CONFIG_SECTION_WEB, "web server max sockets", (long long int)(rlimit_nofile.rlim_cur / 2));
web/server/web_client.c
+38
-5
@@ -157,6 +157,10 @@ void web_client_request_done(struct web_client *w) {
157
w->origin[1] = '\0';
158
159
freez(w->user_agent); w->user_agent = NULL;
160
+ if (w->auth_bearer_token) {
161
+ freez(w->auth_bearer_token);
162
+ w->auth_bearer_token = NULL;
163
+ }
164
165
w->mode = WEB_CLIENT_MODE_NORMAL;
166
@@ -577,6 +581,13 @@ static inline int check_host_and_dashboard_acl_and_call(RRDHOST *host, struct we
581
return check_host_and_call(host, w, url, func);
582
}
583
584
+static inline int check_host_and_mgmt_acl_and_call(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) {
585
+ if(!web_client_can_access_mgmt(w))
586
+ return web_client_permission_denied(w);
587
+
588
+ return check_host_and_call(host, w, url, func);
589
+}
590
+
591
int web_client_api_request(RRDHOST *host, struct web_client *w, char *url)
592
{
593
// get the api version
@@ -713,7 +724,7 @@ const char *web_response_code_to_string(int code) {
724
}
725
726
static inline char *http_header_parse(struct web_client *w, char *s, int parse_useragent) {
716
- static uint32_t hash_origin = 0, hash_connection = 0, hash_accept_encoding = 0, hash_donottrack = 0, hash_useragent = 0;
727
+ static uint32_t hash_origin = 0, hash_connection = 0, hash_accept_encoding = 0, hash_donottrack = 0, hash_useragent = 0, hash_authorization = 0;
728
729
if(unlikely(!hash_origin)) {
730
hash_origin = simple_uhash("Origin");
@@ -721,6 +732,7 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u
732
hash_accept_encoding = simple_uhash("Accept-Encoding");
733
hash_donottrack = simple_uhash("DNT");
734
hash_useragent = simple_uhash("User-Agent");
735
+ hash_authorization = simple_uhash("Authorization");
736
}
737
738
char *e = s;
@@ -765,6 +777,15 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u
777
}
778
else if(parse_useragent && hash == hash_useragent && !strcasecmp(s, "User-Agent")) {
779
w->user_agent = strdupz(v);
780
+ } else if(hash == hash_authorization&& !strcasecmp(s, "Authorization")) {
781
+ if (strlen(v) > 8) { // Must contain at least "Bearer "
782
+ char *auth_key=v+6;
783
+ *auth_key='\0';
784
+ if (!strcasecmp(v,"Bearer")) {
785
+ auth_key++;
786
+ w->auth_bearer_token=strdupz(auth_key);
787
+ }
788
+ }
789
}
790
#ifdef NETDATA_WITH_ZLIB
791
else if(hash == hash_accept_encoding && !strcasecmp(s, "Accept-Encoding")) {
@@ -1239,9 +1260,15 @@ void web_client_process_request(struct web_client *w) {
1260
return;
1261
1262
case WEB_CLIENT_MODE_OPTIONS:
1242
- if(unlikely(!web_client_can_access_dashboard(w) && !web_client_can_access_registry(w) && !web_client_can_access_badges(w))) {
1263
+ if(unlikely(
1264
+ !web_client_can_access_dashboard(w) &&
1265
+ !web_client_can_access_registry(w) &&
1266
+ !web_client_can_access_badges(w) &&
1267
+ !web_client_can_access_mgmt(w) &&
1268
+ !web_client_can_access_netdataconf(w)
1269
+ )) {
1270
web_client_permission_denied(w);
1244
- return;
1271
+ break;
1272
}
1273
1274
w->response.data->contenttype = CT_TEXT_PLAIN;
@@ -1252,9 +1279,15 @@ void web_client_process_request(struct web_client *w) {
1279
1280
case WEB_CLIENT_MODE_FILECOPY:
1281
case WEB_CLIENT_MODE_NORMAL:
1255
- if(unlikely(!web_client_can_access_dashboard(w) && !web_client_can_access_registry(w) && !web_client_can_access_badges(w))) {
1282
+ if(unlikely(
1283
+ !web_client_can_access_dashboard(w) &&
1284
+ !web_client_can_access_registry(w) &&
1285
+ !web_client_can_access_badges(w) &&
1286
+ !web_client_can_access_mgmt(w) &&
1287
+ !web_client_can_access_netdataconf(w)
1288
+ )) {
1289
web_client_permission_denied(w);
1257
- return;
1290
+ break;
1291
}
1292
1293
w->response.code = web_client_process_url(localhost, w, w->decoded_url);
web/server/web_client.h
+2
-19
@@ -108,31 +108,14 @@ struct response {
108
109
};
110
111
-typedef enum web_client_acl {
112
- WEB_CLIENT_ACL_NONE = 0,
113
- WEB_CLIENT_ACL_NOCHECK = 0,
114
- WEB_CLIENT_ACL_DASHBOARD = 1 << 0,
115
- WEB_CLIENT_ACL_REGISTRY = 1 << 1,
116
- WEB_CLIENT_ACL_BADGE = 1 << 2
117
-} WEB_CLIENT_ACL;
118
-
119
-#define web_client_can_access_dashboard(w) ((w)->acl & WEB_CLIENT_ACL_DASHBOARD)
120
-#define web_client_can_access_registry(w) ((w)->acl & WEB_CLIENT_ACL_REGISTRY)
121
-#define web_client_can_access_badges(w) ((w)->acl & WEB_CLIENT_ACL_BADGE)
122
-
123
-#define web_client_can_access_stream(w) \
124
- (!web_allow_streaming_from || simple_pattern_matches(web_allow_streaming_from, (w)->client_ip))
125
-
126
-#define web_client_can_access_netdataconf(w) \
127
- (!web_allow_netdataconf_from || simple_pattern_matches(web_allow_netdataconf_from, (w)->client_ip))
128
-
111
struct web_client {
112
unsigned long long id;
113
114
WEB_CLIENT_FLAGS flags; // status flags for the client
115
WEB_CLIENT_MODE mode; // the operational mode of the client
116
WEB_CLIENT_ACL acl; // the access list of the client
135
-
117
+ int port_acl; // the operations permitted on the port the client connected to
118
+ char *auth_bearer_token; // the Bearer auth token (if sent)
119
size_t header_parse_tries;
120
size_t header_parse_last_size;
121
web/server/web_server.c
+51
-55
@@ -3,12 +3,6 @@
3
#define WEB_SERVER_INTERNALS 1
4
#include "web_server.h"
5
6
-// this file includes 3 web servers:
7
-//
8
-// 1. single-threaded, based on select()
9
-// 2. multi-threaded, based on poll() that spawns threads to handle the requests, based on select()
10
-// 3. static-threaded, based on poll() using a fixed number of threads (configured at netdata.conf)
11
-
6
WEB_SERVER_MODE web_server_mode = WEB_SERVER_MODE_STATIC_THREADED;
7
8
// --------------------------------------------------------------------------------------
@@ -16,28 +10,18 @@ WEB_SERVER_MODE web_server_mode = WEB_SERVER_MODE_STATIC_THREADED;
10
WEB_SERVER_MODE web_server_mode_id(const char *mode) {
11
if(!strcmp(mode, "none"))
12
return WEB_SERVER_MODE_NONE;
19
- else if(!strcmp(mode, "single") || !strcmp(mode, "single-threaded"))
20
- return WEB_SERVER_MODE_SINGLE_THREADED;
21
- else if(!strcmp(mode, "static") || !strcmp(mode, "static-threaded"))
13
+ else
14
return WEB_SERVER_MODE_STATIC_THREADED;
23
- else // if(!strcmp(mode, "multi") || !strcmp(mode, "multi-threaded"))
24
- return WEB_SERVER_MODE_MULTI_THREADED;
15
+
16
}
17
18
const char *web_server_mode_name(WEB_SERVER_MODE id) {
19
switch(id) {
20
case WEB_SERVER_MODE_NONE:
21
return "none";
31
-
32
- case WEB_SERVER_MODE_SINGLE_THREADED:
33
- return "single-threaded";
34
-
22
+ default:
23
case WEB_SERVER_MODE_STATIC_THREADED:
24
return "static-threaded";
37
-
38
- default:
39
- case WEB_SERVER_MODE_MULTI_THREADED:
40
- return "multi-threaded";
25
}
26
}
27
@@ -45,20 +29,44 @@ const char *web_server_mode_name(WEB_SERVER_MODE id) {
29
// API sockets
30
31
LISTEN_SOCKETS api_sockets = {
48
- .config = &netdata_config,
49
- .config_section = CONFIG_SECTION_WEB,
50
- .default_bind_to = "*",
51
- .default_port = API_LISTEN_PORT,
52
- .backlog = API_LISTEN_BACKLOG
32
+ .config = &netdata_config,
33
+ .config_section = CONFIG_SECTION_WEB,
34
+ .default_bind_to = "*",
35
+ .default_port = API_LISTEN_PORT,
36
+ .backlog = API_LISTEN_BACKLOG
37
};
38
55
-int api_listen_sockets_setup(void) {
56
- int socks = listen_sockets_setup(&api_sockets);
39
+void debug_sockets() {
40
+ BUFFER *wb = buffer_create(256 * sizeof(char));
41
+ int i;
42
+
43
+ for(i = 0 ; i < (int)api_sockets.opened ; i++) {
44
+ buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_NOCHECK)?"NONE ":"");
45
+ buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_DASHBOARD)?"dashboard ":"");
46
+ buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_REGISTRY)?"registry ":"");
47
+ buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_BADGE)?"badges ":"");
48
+ buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_MGMT)?"management ":"");
49
+ buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_STREAMING)?"streaming ":"");
50
+ buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & WEB_CLIENT_ACL_NETDATACONF)?"netdata.conf ":"");
51
+ debug(D_WEB_CLIENT, "Socket fd %d name '%s' acl_flags: %s",
52
+ i,
53
+ api_sockets.fds_names[i],
54
+ buffer_tostring(wb));
55
+ buffer_reset(wb);
56
+ }
57
+ buffer_free(wb);
58
+}
59
+
60
+void api_listen_sockets_setup(void) {
61
+ int socks = listen_sockets_setup(&api_sockets);
62
+
63
+ if(!socks)
64
+ fatal("LISTENER: Cannot listen on any API socket. Exiting...");
65
58
- if(!socks)
59
- fatal("LISTENER: Cannot listen on any API socket. Exiting...");
66
+ if(unlikely(debug_flags & D_WEB_CLIENT))
67
+ debug_sockets();
68
61
- return socks;
69
+ return;
70
}
71
72
@@ -66,13 +74,14 @@ int api_listen_sockets_setup(void) {
74
// access lists
75
76
SIMPLE_PATTERN *web_allow_connections_from = NULL;
69
-SIMPLE_PATTERN *web_allow_streaming_from = NULL;
70
-SIMPLE_PATTERN *web_allow_netdataconf_from = NULL;
77
78
// WEB_CLIENT_ACL
79
SIMPLE_PATTERN *web_allow_dashboard_from = NULL;
80
SIMPLE_PATTERN *web_allow_registry_from = NULL;
81
SIMPLE_PATTERN *web_allow_badges_from = NULL;
82
+SIMPLE_PATTERN *web_allow_mgmt_from = NULL;
83
+SIMPLE_PATTERN *web_allow_streaming_from = NULL;
84
+SIMPLE_PATTERN *web_allow_netdataconf_from = NULL;
85
86
void web_client_update_acl_matches(struct web_client *w) {
87
w->acl = WEB_CLIENT_ACL_NONE;
@@ -85,6 +94,17 @@ void web_client_update_acl_matches(struct web_client *w) {
94
95
if(!web_allow_badges_from || simple_pattern_matches(web_allow_badges_from, w->client_ip))
96
w->acl |= WEB_CLIENT_ACL_BADGE;
97
+
98
+ if(!web_allow_mgmt_from || simple_pattern_matches(web_allow_mgmt_from, w->client_ip))
99
+ w->acl |= WEB_CLIENT_ACL_MGMT;
100
+
101
+ if(!web_allow_streaming_from || simple_pattern_matches(web_allow_streaming_from, w->client_ip))
102
+ w->acl |= WEB_CLIENT_ACL_STREAMING;
103
+
104
+ if(!web_allow_netdataconf_from || simple_pattern_matches(web_allow_netdataconf_from, w->client_ip))
105
+ w->acl |= WEB_CLIENT_ACL_NETDATACONF;
106
+
107
+ w->acl &= w->port_acl;
108
}
109
110
@@ -119,28 +139,4 @@ void web_client_initialize_connection(struct web_client *w) {
139
web_client_cache_verify(0);
140
}
141
122
-struct web_client *web_client_create_on_listenfd(int listener) {
123
- struct web_client *w;
124
-
125
- w = web_client_get_from_cache_or_allocate();
126
- w->ifd = w->ofd = accept_socket(listener, SOCK_NONBLOCK, w->client_ip, sizeof(w->client_ip), w->client_port, sizeof(w->client_port), web_allow_connections_from);
127
-
128
- if(unlikely(!*w->client_ip)) strcpy(w->client_ip, "-");
129
- if(unlikely(!*w->client_port)) strcpy(w->client_port, "-");
130
-
131
- if (w->ifd == -1) {
132
- if(errno == EPERM)
133
- web_server_log_connection(w, "ACCESS DENIED");
134
- else {
135
- web_server_log_connection(w, "CONNECTION FAILED");
136
- error("%llu: Failed to accept new incoming connection.", w->id);
137
- }
138
-
139
- web_client_release(w);
140
- return NULL;
141
- }
142
-
143
- web_client_initialize_connection(w);
144
- return(w);
145
-}
142
web/server/web_server.h
+2
-5
@@ -15,9 +15,7 @@
15
#endif
16
17
typedef enum web_server_mode {
18
- WEB_SERVER_MODE_SINGLE_THREADED,
18
WEB_SERVER_MODE_STATIC_THREADED,
20
- WEB_SERVER_MODE_MULTI_THREADED,
19
WEB_SERVER_MODE_NONE
20
} WEB_SERVER_MODE;
21
@@ -27,13 +25,14 @@ extern SIMPLE_PATTERN *web_allow_registry_from;
25
extern SIMPLE_PATTERN *web_allow_badges_from;
26
extern SIMPLE_PATTERN *web_allow_streaming_from;
27
extern SIMPLE_PATTERN *web_allow_netdataconf_from;
28
+extern SIMPLE_PATTERN *web_allow_mgmt_from;
29
30
extern WEB_SERVER_MODE web_server_mode;
31
32
extern WEB_SERVER_MODE web_server_mode_id(const char *mode);
33
extern const char *web_server_mode_name(WEB_SERVER_MODE id);
34
36
-extern int api_listen_sockets_setup(void);
35
+extern void api_listen_sockets_setup(void);
36
37
#define DEFAULT_TIMEOUT_TO_RECEIVE_FIRST_WEB_REQUEST 60
38
#define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
@@ -51,8 +50,6 @@ extern struct web_client *web_client_create_on_listenfd(int listener);
50
#include "web_client_cache.h"
51
#endif // WEB_SERVER_INTERNALS
52
54
-#include "single/single-threaded.h"
55
-#include "multi/multi-threaded.h"
53
#include "static/static-threaded.h"
54
55
#include "daemon/common.h"