summaryrefslogtreecommitdiffstats
path: root/web/api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-07-20 04:49:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-07-20 04:49:55 +0000
commitab1bb5b7f1c3c3a7b240ab7fc8661459ecd7decb (patch)
tree7a900833aad3ccc685712c6c2a7d87576d54f427 /web/api
parentAdding upstream version 1.40.1. (diff)
downloadnetdata-ab1bb5b7f1c3c3a7b240ab7fc8661459ecd7decb.tar.xz
netdata-ab1bb5b7f1c3c3a7b240ab7fc8661459ecd7decb.zip
Adding upstream version 1.41.0.upstream/1.41.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'web/api')
-rw-r--r--web/api/badges/web_buffer_svg.c7
-rw-r--r--web/api/exporters/shell/allmetrics_shell.c21
-rw-r--r--web/api/formatters/charts2json.c4
-rw-r--r--web/api/formatters/csv/csv.c7
-rw-r--r--web/api/formatters/json/json.c7
-rw-r--r--web/api/formatters/json_wrapper.c15
-rw-r--r--web/api/formatters/rrdset2json.c8
-rw-r--r--web/api/formatters/ssv/ssv.c4
-rw-r--r--web/api/health/README.md4
-rw-r--r--web/api/health/health_cmdapi.c10
-rw-r--r--web/api/queries/query.c23
-rw-r--r--web/api/queries/rrdr.h20
-rw-r--r--web/api/queries/weights.c16
-rw-r--r--web/api/tests/valid_urls.c2
-rw-r--r--web/api/tests/web_api.c6
-rw-r--r--web/api/web_api.c33
-rw-r--r--web/api/web_api.h6
-rw-r--r--web/api/web_api_v1.c193
-rw-r--r--web/api/web_api_v1.h5
-rw-r--r--web/api/web_api_v2.c354
20 files changed, 605 insertions, 140 deletions
diff --git a/web/api/badges/web_buffer_svg.c b/web/api/badges/web_buffer_svg.c
index b69f35afa..36150b93e 100644
--- a/web/api/badges/web_buffer_svg.c
+++ b/web/api/badges/web_buffer_svg.c
@@ -905,7 +905,7 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u
if(!name || !*name) continue;
if(!value || !*value) continue;
- debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
// name and value are now the parameters
// they are not null and not empty
@@ -1040,7 +1040,7 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u
units = rrdset_units(st);
}
- debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'"
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'"
, w->id
, chart
, alarm?alarm:""
@@ -1055,7 +1055,8 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u
if(rc) {
if (refresh > 0) {
buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
- w->response.data->expires = now_realtime_sec() + refresh;
+ w->response.data->date = now_realtime_sec();
+ w->response.data->expires = w->response.data->date + refresh;
}
else buffer_no_cacheable(w->response.data);
diff --git a/web/api/exporters/shell/allmetrics_shell.c b/web/api/exporters/shell/allmetrics_shell.c
index fbfd6b574..c8248c148 100644
--- a/web/api/exporters/shell/allmetrics_shell.c
+++ b/web/api/exporters/shell/allmetrics_shell.c
@@ -31,21 +31,22 @@ void rrd_stats_api_v1_charts_allmetrics_shell(RRDHOST *host, const char *filter_
rrdset_foreach_read(st, host) {
if (filter && !simple_pattern_matches_string(filter, st->name))
continue;
+ if (rrdset_is_available_for_viewers(st)) {
+ NETDATA_DOUBLE total = 0.0;
- NETDATA_DOUBLE total = 0.0;
- char chart[SHELL_ELEMENT_MAX + 1];
- shell_name_copy(chart, st->name?rrdset_name(st):rrdset_id(st), SHELL_ELEMENT_MAX);
+ char chart[SHELL_ELEMENT_MAX + 1];
+ shell_name_copy(chart, st->name ? rrdset_name(st) : rrdset_id(st), SHELL_ELEMENT_MAX);
+
+ buffer_sprintf(wb, "\n# chart: %s (name: %s)\n", rrdset_id(st), rrdset_name(st));
- buffer_sprintf(wb, "\n# chart: %s (name: %s)\n", rrdset_id(st), rrdset_name(st));
- if(rrdset_is_available_for_viewers(st)) {
// for each dimension
RRDDIM *rd;
rrddim_foreach_read(rd, st) {
- if(rd->collections_counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) {
+ if(rd->collector.counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) {
char dimension[SHELL_ELEMENT_MAX + 1];
shell_name_copy(dimension, rd->name?rrddim_name(rd):rrddim_id(rd), SHELL_ELEMENT_MAX);
- NETDATA_DOUBLE n = rd->last_stored_value;
+ NETDATA_DOUBLE n = rd->collector.last_stored_value;
if(isnan(n) || isinf(n))
buffer_sprintf(wb, "NETDATA_%s_%s=\"\" # %s\n", chart, dimension, rrdset_units(st));
@@ -135,7 +136,7 @@ void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_s
// for each dimension
RRDDIM *rd;
rrddim_foreach_read(rd, st) {
- if(rd->collections_counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) {
+ if(rd->collector.counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) {
buffer_sprintf(
wb,
"%s\n"
@@ -146,10 +147,10 @@ void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_s
rrddim_id(rd),
rrddim_name(rd));
- if(isnan(rd->last_stored_value))
+ if(isnan(rd->collector.last_stored_value))
buffer_strcat(wb, "null");
else
- buffer_sprintf(wb, NETDATA_DOUBLE_FORMAT, rd->last_stored_value);
+ buffer_sprintf(wb, NETDATA_DOUBLE_FORMAT, rd->collector.last_stored_value);
buffer_strcat(wb, "\n\t\t\t}");
diff --git a/web/api/formatters/charts2json.c b/web/api/formatters/charts2json.c
index 4b6b095c2..2ad068d89 100644
--- a/web/api/formatters/charts2json.c
+++ b/web/api/formatters/charts2json.c
@@ -10,7 +10,7 @@ const char* get_release_channel() {
if (use_stable == -1) {
char filename[FILENAME_MAX + 1];
snprintfz(filename, FILENAME_MAX, "%s/.environment", netdata_configured_user_config_dir);
- procfile *ff = procfile_open(filename, "=", PROCFILE_FLAG_ERROR_ON_ERROR_LOG);
+ procfile *ff = procfile_open(filename, "=", PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
if (ff) {
procfile_set_quotes(ff, "'\"");
ff = procfile_readall(ff);
@@ -53,7 +53,7 @@ void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived
",\n\t\"os\": \"%s\""
",\n\t\"timezone\": \"%s\""
",\n\t\"update_every\": %d"
- ",\n\t\"history\": %ld"
+ ",\n\t\"history\": %d"
",\n\t\"memory_mode\": \"%s\""
",\n\t\"custom_info\": \"%s\""
",\n\t\"charts\": {"
diff --git a/web/api/formatters/csv/csv.c b/web/api/formatters/csv/csv.c
index 8f4950ddd..d81ddb34e 100644
--- a/web/api/formatters/csv/csv.c
+++ b/web/api/formatters/csv/csv.c
@@ -4,7 +4,7 @@
#include "csv.h"
void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const char *startline, const char *separator, const char *endline, const char *betweenlines) {
- //info("RRD2CSV(): %s: BEGIN", r->st->id);
+ //netdata_log_info("RRD2CSV(): %s: BEGIN", r->st->id);
long c, i;
const long used = (long)r->d;
@@ -79,7 +79,8 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const
else {
// generate the local date time
struct tm tmbuf, *tm = localtime_r(&now, &tmbuf);
- if(!tm) { error("localtime() failed."); continue; }
+ if(!tm) {
+ netdata_log_error("localtime() failed."); continue; }
buffer_date(wb, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
}
@@ -104,5 +105,5 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const
buffer_strcat(wb, endline);
}
- //info("RRD2CSV(): %s: END", r->st->id);
+ //netdata_log_info("RRD2CSV(): %s: END", r->st->id);
}
diff --git a/web/api/formatters/json/json.c b/web/api/formatters/json/json.c
index 3a7a23ba1..7e3f400e9 100644
--- a/web/api/formatters/json/json.c
+++ b/web/api/formatters/json/json.c
@@ -6,7 +6,7 @@
#define JSON_DATES_TIMESTAMP 2
void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) {
- //info("RRD2JSON(): %s: BEGIN", r->st->id);
+ //netdata_log_info("RRD2JSON(): %s: BEGIN", r->st->id);
int row_annotations = 0, dates, dates_with_new = 0;
char kq[2] = "", // key quote
sq[2] = "", // string quote
@@ -159,7 +159,8 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) {
if(dates == JSON_DATES_JS) {
// generate the local date time
struct tm tmbuf, *tm = localtime_r(&now, &tmbuf);
- if(!tm) { error("localtime_r() failed."); continue; }
+ if(!tm) {
+ netdata_log_error("localtime_r() failed."); continue; }
if(likely(i != start)) buffer_fast_strcat(wb, ",\n", 2);
buffer_fast_strcat(wb, pre_date, pre_date_len);
@@ -241,7 +242,7 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) {
}
buffer_strcat(wb, finish);
- //info("RRD2JSON(): %s: END", r->st->id);
+ //netdata_log_info("RRD2JSON(): %s: END", r->st->id);
}
void rrdr2json_v2(RRDR *r, BUFFER *wb) {
diff --git a/web/api/formatters/json_wrapper.c b/web/api/formatters/json_wrapper.c
index 6bcbb8d5a..6a66cbcca 100644
--- a/web/api/formatters/json_wrapper.c
+++ b/web/api/formatters/json_wrapper.c
@@ -250,7 +250,7 @@ static void query_target_summary_nodes_v2(BUFFER *wb, QUERY_TARGET *qt, const ch
QUERY_NODE *qn = query_node(qt, c);
RRDHOST *host = qn->rrdhost;
buffer_json_add_array_item_object(wb);
- buffer_json_node_add_v2(wb, host, qn->slot, qn->duration_ut);
+ buffer_json_node_add_v2(wb, host, qn->slot, qn->duration_ut, true);
query_target_instance_counts(wb, &qn->instances);
query_target_metric_counts(wb, &qn->metrics);
query_target_alerts_counts(wb, &qn->alerts, NULL, false);
@@ -615,7 +615,7 @@ static void query_target_summary_alerts_v2(BUFFER *wb, QUERY_TARGET *qt, const c
QUERY_INSTANCE *qi = query_instance(qt, c);
RRDSET *st = rrdinstance_acquired_rrdset(qi->ria);
if (st) {
- netdata_rwlock_rdlock(&st->alerts.rwlock);
+ rw_spinlock_read_lock(&st->alerts.spinlock);
if (st->alerts.base) {
for (RRDCALC *rc = st->alerts.base; rc; rc = rc->next) {
z = dictionary_set(dict, string2str(rc->name), NULL, sizeof(*z));
@@ -642,7 +642,7 @@ static void query_target_summary_alerts_v2(BUFFER *wb, QUERY_TARGET *qt, const c
}
}
}
- netdata_rwlock_unlock(&st->alerts.rwlock);
+ rw_spinlock_read_unlock(&st->alerts.spinlock);
}
}
dfe_start_read(dict, z)
@@ -665,7 +665,7 @@ static inline void query_target_functions(BUFFER *wb, const char *key, RRDR *r)
continue;
ria = qi->ria;
- chart_functions_to_dict(rrdinstance_acquired_functions(ria), funcs);
+ chart_functions_to_dict(rrdinstance_acquired_functions(ria), funcs, NULL, 0);
}
buffer_json_member_add_array(wb, key);
@@ -931,7 +931,7 @@ void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb) {
static void rrdset_rrdcalc_entries_v2(BUFFER *wb, RRDINSTANCE_ACQUIRED *ria) {
RRDSET *st = rrdinstance_acquired_rrdset(ria);
if(st) {
- netdata_rwlock_rdlock(&st->alerts.rwlock);
+ rw_spinlock_read_lock(&st->alerts.spinlock);
if(st->alerts.base) {
buffer_json_member_add_object(wb, "alerts");
for(RRDCALC *rc = st->alerts.base; rc ;rc = rc->next) {
@@ -946,7 +946,7 @@ static void rrdset_rrdcalc_entries_v2(BUFFER *wb, RRDINSTANCE_ACQUIRED *ria) {
}
buffer_json_object_close(wb);
}
- netdata_rwlock_unlock(&st->alerts.rwlock);
+ rw_spinlock_read_unlock(&st->alerts.spinlock);
}
}
@@ -1268,6 +1268,7 @@ static void query_target_detailed_objects_tree(BUFFER *wb, RRDR *r, RRDR_OPTIONS
void version_hashes_api_v2(BUFFER *wb, struct query_versions *versions) {
buffer_json_member_add_object(wb, "versions");
+ buffer_json_member_add_uint64(wb, "routing_hard_hash", 1);
buffer_json_member_add_uint64(wb, "nodes_hard_hash", dictionary_version(rrdhost_root_index));
buffer_json_member_add_uint64(wb, "contexts_hard_hash", versions->contexts_hard_hash);
buffer_json_member_add_uint64(wb, "contexts_soft_hash", versions->contexts_soft_hash);
@@ -1569,7 +1570,7 @@ void rrdr_json_wrapper_end2(RRDR *r, BUFFER *wb) {
}
buffer_json_object_close(wb); // view
- buffer_json_agents_array_v2(wb, &r->internal.qt->timings, 0);
+ buffer_json_agents_v2(wb, &r->internal.qt->timings, 0, false, true);
buffer_json_cloud_timings(wb, "timings", &r->internal.qt->timings);
buffer_json_finalize(wb);
}
diff --git a/web/api/formatters/rrdset2json.c b/web/api/formatters/rrdset2json.c
index 156f4486b..04250dd68 100644
--- a/web/api/formatters/rrdset2json.c
+++ b/web/api/formatters/rrdset2json.c
@@ -37,7 +37,7 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor
"\t\t\t\"family\": \"%s\",\n"
"\t\t\t\"context\": \"%s\",\n"
"\t\t\t\"title\": \"%s (%s)\",\n"
- "\t\t\t\"priority\": %ld,\n"
+ "\t\t\t\"priority\": %d,\n"
"\t\t\t\"plugin\": \"%s\",\n"
"\t\t\t\"module\": \"%s\",\n"
"\t\t\t\"units\": \"%s\",\n"
@@ -90,7 +90,7 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor
rrddim_foreach_read(rd, st) {
if(rrddim_option_check(rd, RRDDIM_OPTION_HIDDEN) || rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) continue;
- memory += sizeof(RRDDIM) + rd->memsize;
+ memory += rrddim_size() + rd->db.memsize;
if (dimensions)
buffer_strcat(wb, ",\n\t\t\t\t\"");
@@ -120,7 +120,7 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor
buffer_strcat(wb, ",\n\t\t\t\"alarms\": {\n");
size_t alarms = 0;
RRDCALC *rc;
- netdata_rwlock_rdlock(&st->alerts.rwlock);
+ rw_spinlock_read_lock(&st->alerts.spinlock);
DOUBLE_LINKED_LIST_FOREACH_FORWARD(st->alerts.base, rc, prev, next) {
buffer_sprintf(
wb,
@@ -136,7 +136,7 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor
alarms++;
}
- netdata_rwlock_unlock(&st->alerts.rwlock);
+ rw_spinlock_read_unlock(&st->alerts.spinlock);
buffer_sprintf(wb,
"\n\t\t\t}"
);
diff --git a/web/api/formatters/ssv/ssv.c b/web/api/formatters/ssv/ssv.c
index 65de0464b..2eb26b459 100644
--- a/web/api/formatters/ssv/ssv.c
+++ b/web/api/formatters/ssv/ssv.c
@@ -3,7 +3,7 @@
#include "ssv.h"
void rrdr2ssv(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, const char *prefix, const char *separator, const char *suffix) {
- //info("RRD2SSV(): %s: BEGIN", r->st->id);
+ //netdata_log_info("RRD2SSV(): %s: BEGIN", r->st->id);
long i;
buffer_strcat(wb, prefix);
@@ -41,5 +41,5 @@ void rrdr2ssv(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, const char *prefix, con
buffer_print_netdata_double(wb, v);
}
buffer_strcat(wb, suffix);
- //info("RRD2SSV(): %s: END", r->st->id);
+ //netdata_log_info("RRD2SSV(): %s: END", r->st->id);
}
diff --git a/web/api/health/README.md b/web/api/health/README.md
index dd46854a1..f820263b1 100644
--- a/web/api/health/README.md
+++ b/web/api/health/README.md
@@ -28,12 +28,12 @@ This API call will return the alarms currently in WARNING or CRITICAL state.
### Event Log
-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.
+The size of the alarm log is configured in `netdata.conf`. There are 2 settings: the event history kept in the DB (in seconds), and the in memory size of the alarm log.
```
[health]
in memory max health log entries = 1000
- rotate log every lines = 2000
+ health log history = 432000
```
The API call retrieves all entries of the alarm log:
diff --git a/web/api/health/health_cmdapi.c b/web/api/health/health_cmdapi.c
index 7c4869bd3..e8d6845e3 100644
--- a/web/api/health/health_cmdapi.c
+++ b/web/api/health/health_cmdapi.c
@@ -14,7 +14,7 @@
void free_silencers(SILENCER *t) {
if (!t) return;
if (t->next) free_silencers(t->next);
- debug(D_HEALTH, "HEALTH command API: Freeing silencer %s:%s:%s:%s:%s", t->alarms,
+ netdata_log_debug(D_HEALTH, "HEALTH command API: Freeing silencer %s:%s:%s:%s:%s", t->alarms,
t->charts, t->contexts, t->hosts, t->families);
simple_pattern_free(t->alarms_pattern);
simple_pattern_free(t->charts_pattern);
@@ -96,12 +96,12 @@ void health_silencers2file(BUFFER *wb) {
if(fd) {
size_t written = (size_t)fprintf(fd, "%s", wb->buffer) ;
if (written == wb->len ) {
- info("Silencer changes written to %s", silencers_filename);
+ netdata_log_info("Silencer changes written to %s", silencers_filename);
}
fclose(fd);
return;
}
- error("Silencer changes could not be written to %s. Error %s", silencers_filename, strerror(errno));
+ netdata_log_error("Silencer changes could not be written to %s. Error %s", silencers_filename, strerror(errno));
}
/**
@@ -133,7 +133,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR);
ret = HTTP_RESP_FORBIDDEN;
} else {
- debug(D_HEALTH, "HEALTH command API: Comparing secret '%s' to '%s'", w->auth_bearer_token, api_secret);
+ netdata_log_debug(D_HEALTH, "HEALTH command API: Comparing secret '%s' to '%s'", w->auth_bearer_token, api_secret);
if (strcmp(w->auth_bearer_token, api_secret)) {
buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR);
ret = HTTP_RESP_FORBIDDEN;
@@ -146,7 +146,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
if (!key || !*key) continue;
if (!value || !*value) continue;
- debug(D_WEB_CLIENT, "%llu: API v1 health query param '%s' with value '%s'", w->id, key, value);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 health query param '%s' with value '%s'", w->id, key, value);
// name and value are now the parameters
if (!strcmp(key, "cmd")) {
diff --git a/web/api/queries/query.c b/web/api/queries/query.c
index a0347f6fe..74a925bc3 100644
--- a/web/api/queries/query.c
+++ b/web/api/queries/query.c
@@ -1627,7 +1627,7 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_
new_point.sp.start_time_s = last1_point.sp.end_time_s;
new_point.sp.end_time_s = now_end_time;
//
-// if(debug_this) info("QUERY: is finished() returned true");
+// if(debug_this) netdata_log_info("QUERY: is finished() returned true");
//
break;
}
@@ -1687,7 +1687,7 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_
query_point_set_id(new_point, ops->db_total_points_read);
// if(debug_this)
-// info("QUERY: got point %zu, from time %ld to %ld // now from %ld to %ld // query from %ld to %ld",
+// netdata_log_info("QUERY: got point %zu, from time %ld to %ld // now from %ld to %ld // query from %ld to %ld",
// new_point.id, new_point.start_time, new_point.end_time, now_start_time, now_end_time, after_wanted, before_wanted);
//
// get the right value from the point we got
@@ -1952,7 +1952,7 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s
if(unlikely(!t)) return;
time_t latest_time_s = storage_engine_latest_time_s(t->backend, t->db_metric_handle);
- time_t granularity = (time_t)t->tier_grouping * (time_t)rd->update_every;
+ time_t granularity = (time_t)t->tier_grouping * (time_t)rd->rrdset->update_every;
time_t time_diff = now_s - latest_time_s;
// if the user wants only NEW backfilling, and we don't have any data
@@ -2158,7 +2158,7 @@ bool rrdr_relative_window_to_absolute(time_t *after, time_t *before, time_t *now
#define query_debug_log_init() BUFFER *debug_log = buffer_create(1000)
#define query_debug_log(args...) buffer_sprintf(debug_log, ##args)
#define query_debug_log_fin() { \
- info("QUERY: '%s', after:%ld, before:%ld, duration:%ld, points:%zu, res:%ld - wanted => after:%ld, before:%ld, points:%zu, group:%zu, granularity:%ld, resgroup:%ld, resdiv:" NETDATA_DOUBLE_FORMAT_AUTO " %s", qt->id, after_requested, before_requested, before_requested - after_requested, points_requested, resampling_time_requested, after_wanted, before_wanted, points_wanted, group, query_granularity, resampling_group, resampling_divisor, buffer_tostring(debug_log)); \
+ netdata_log_info("QUERY: '%s', after:%ld, before:%ld, duration:%ld, points:%zu, res:%ld - wanted => after:%ld, before:%ld, points:%zu, group:%zu, granularity:%ld, resgroup:%ld, resdiv:" NETDATA_DOUBLE_FORMAT_AUTO " %s", qt->id, after_requested, before_requested, before_requested - after_requested, points_requested, resampling_time_requested, after_wanted, before_wanted, points_wanted, group, query_granularity, resampling_group, resampling_divisor, buffer_tostring(debug_log)); \
buffer_free(debug_log); \
debug_log = NULL; \
}
@@ -2516,13 +2516,6 @@ void rrdr_json_group_by_labels(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTION
buffer_json_object_close(wb); // key
}
-static int group_by_label_is_space(char c) {
- if(c == ',' || c == '|')
- return 1;
-
- return 0;
-}
-
static void rrd2rrdr_set_timestamps(RRDR *r) {
QUERY_TARGET *qt = r->internal.qt;
@@ -2755,9 +2748,9 @@ static RRDR *rrd2rrdr_group_by_initialize(ONEWAYALLOC *owa, QUERY_TARGET *qt) {
for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
if (qt->request.group_by[g].group_by & RRDR_GROUP_BY_LABEL &&
qt->request.group_by[g].group_by_label && *qt->request.group_by[g].group_by_label)
- qt->group_by[g].used = quoted_strings_splitter(
+ qt->group_by[g].used = quoted_strings_splitter_query_group_by_label(
qt->request.group_by[g].group_by_label, qt->group_by[g].label_keys,
- GROUP_BY_MAX_LABEL_KEYS, group_by_label_is_space);
+ GROUP_BY_MAX_LABEL_KEYS);
if (!qt->group_by[g].used)
qt->request.group_by[g].group_by &= ~RRDR_GROUP_BY_LABEL;
@@ -3692,12 +3685,12 @@ RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) {
bool cancel = false;
if (qt->request.interrupt_callback && qt->request.interrupt_callback(qt->request.interrupt_callback_data)) {
cancel = true;
- log_access("QUERY INTERRUPTED");
+ netdata_log_access("QUERY INTERRUPTED");
}
if (qt->request.timeout_ms && ((NETDATA_DOUBLE)(now_ut - qt->timings.received_ut) / 1000.0) > (NETDATA_DOUBLE)qt->request.timeout_ms) {
cancel = true;
- log_access("QUERY CANCELED RUNTIME EXCEEDED %0.2f ms (LIMIT %lld ms)",
+ netdata_log_access("QUERY CANCELED RUNTIME EXCEEDED %0.2f ms (LIMIT %lld ms)",
(NETDATA_DOUBLE)(now_ut - qt->timings.received_ut) / 1000.0, (long long)qt->request.timeout_ms);
}
diff --git a/web/api/queries/rrdr.h b/web/api/queries/rrdr.h
index c57be67f5..c4a1f83f2 100644
--- a/web/api/queries/rrdr.h
+++ b/web/api/queries/rrdr.h
@@ -51,6 +51,26 @@ typedef enum rrdr_options {
RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate
} RRDR_OPTIONS;
+typedef enum context_v2_options {
+ CONTEXT_V2_OPTION_MINIFY = (1 << 0), // remove JSON spaces and newlines from JSON output
+ CONTEXT_V2_OPTION_DEBUG = (1 << 1), // show the request
+ CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS = (1 << 2), // include alert configurations (used by /api/v2/alert_transitions)
+ CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES = (1 << 3), // include alert instances (used by /api/v2/alerts)
+ CONTEXT_V2_OPTION_ALERTS_WITH_VALUES = (1 << 4), // include alert latest values (used by /api/v2/alerts)
+ CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY = (1 << 5), // include alerts summary counters (used by /api/v2/alerts)
+} CONTEXTS_V2_OPTIONS;
+
+typedef enum context_v2_alert_status {
+ CONTEXT_V2_ALERT_UNINITIALIZED = (1 << 5), // include UNINITIALIZED alerts
+ CONTEXT_V2_ALERT_UNDEFINED = (1 << 6), // include UNDEFINED alerts
+ CONTEXT_V2_ALERT_CLEAR = (1 << 7), // include CLEAR alerts
+ CONTEXT_V2_ALERT_RAISED = (1 << 8), // include WARNING & CRITICAL alerts
+ CONTEXT_V2_ALERT_WARNING = (1 << 9), // include WARNING alerts
+ CONTEXT_V2_ALERT_CRITICAL = (1 << 10), // include CRITICAL alerts
+} CONTEXTS_V2_ALERT_STATUS;
+
+#define CONTEXTS_V2_ALERT_STATUSES (CONTEXT_V2_ALERT_UNINITIALIZED|CONTEXT_V2_ALERT_UNDEFINED|CONTEXT_V2_ALERT_CLEAR|CONTEXT_V2_ALERT_RAISED|CONTEXT_V2_ALERT_WARNING|CONTEXT_V2_ALERT_CRITICAL)
+
typedef enum __attribute__ ((__packed__)) rrdr_value_flag {
// IMPORTANT:
diff --git a/web/api/queries/weights.c b/web/api/queries/weights.c
index 0830a969a..8ffd8951a 100644
--- a/web/api/queries/weights.c
+++ b/web/api/queries/weights.c
@@ -874,7 +874,7 @@ static size_t registered_results_to_json_multinode_no_group_by(
continue;
buffer_json_add_array_item_object(wb);
- buffer_json_node_add_v2(wb, dun->host, dun->i, dun->duration_ut);
+ buffer_json_node_add_v2(wb, dun->host, dun->i, dun->duration_ut, true);
buffer_json_object_close(wb);
}
dfe_done(dun);
@@ -936,7 +936,7 @@ static size_t registered_results_to_json_multinode_no_group_by(
buffer_json_object_close(wb); //dictionaries
- buffer_json_agents_array_v2(wb, &qwd->timings, 0);
+ buffer_json_agents_v2(wb, &qwd->timings, 0, false, true);
buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions);
buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions);
buffer_json_finalize(wb);
@@ -1067,7 +1067,7 @@ static size_t registered_results_to_json_multinode_group_by(
dfe_done(aw);
buffer_json_array_close(wb); // result
- buffer_json_agents_array_v2(wb, &qwd->timings, 0);
+ buffer_json_agents_v2(wb, &qwd->timings, 0, false, true);
buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions);
buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions);
buffer_json_finalize(wb);
@@ -1244,7 +1244,7 @@ static double kstwo(
return NAN;
if(unlikely(base_size != baseline_points - 1 || high_size != highlight_points - 1)) {
- error("Metric correlations: internal error - calculate_pairs_diff() returns the wrong number of entries");
+ netdata_log_error("Metric correlations: internal error - calculate_pairs_diff() returns the wrong number of entries");
return NAN;
}
@@ -1292,7 +1292,7 @@ NETDATA_DOUBLE *rrd2rrdr_ks2(
stats->db_points_per_tier[tr] += r->internal.qt->db.tiers[tr].points;
if(r->d != 1 || r->internal.qt->query.used != 1) {
- error("WEIGHTS: on query '%s' expected 1 dimension in RRDR but got %zu r->d and %zu qt->query.used",
+ netdata_log_error("WEIGHTS: on query '%s' expected 1 dimension in RRDR but got %zu r->d and %zu qt->query.used",
r->internal.qt->id, r->d, (size_t)r->internal.qt->query.used);
goto cleanup;
}
@@ -1368,11 +1368,11 @@ static void rrdset_metric_correlations_ks2(
// these conditions should never happen, but still let's check
if(unlikely(prob < 0.0)) {
- error("Metric correlations: kstwo() returned a negative number: %f", prob);
+ netdata_log_error("Metric correlations: kstwo() returned a negative number: %f", prob);
prob = -prob;
}
if(unlikely(prob > 1.0)) {
- error("Metric correlations: kstwo() returned a number above 1.0: %f", prob);
+ netdata_log_error("Metric correlations: kstwo() returned a number above 1.0: %f", prob);
prob = 1.0;
}
@@ -1447,7 +1447,7 @@ static void rrdset_metric_correlations_volume(
merge_query_value_to_stats(&highlight_countif, stats, 1);
if(!netdata_double_isnumber(highlight_countif.value)) {
- info("WEIGHTS: highlighted countif query failed, but highlighted average worked - strange...");
+ netdata_log_info("WEIGHTS: highlighted countif query failed, but highlighted average worked - strange...");
return;
}
diff --git a/web/api/tests/valid_urls.c b/web/api/tests/valid_urls.c
index 8a2a87f10..764d02807 100644
--- a/web/api/tests/valid_urls.c
+++ b/web/api/tests/valid_urls.c
@@ -46,7 +46,7 @@ void repr(char *result, int result_size, char const *buf, int size)
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
{
- info("Mocking send: %zu bytes\n", len);
+ netdata_log_info("Mocking send: %zu bytes\n", len);
(void)sockfd;
(void)buf;
(void)flags;
diff --git a/web/api/tests/web_api.c b/web/api/tests/web_api.c
index 93e6454ee..694929a94 100644
--- a/web/api/tests/web_api.c
+++ b/web/api/tests/web_api.c
@@ -46,7 +46,7 @@ void repr(char *result, int result_size, char const *buf, int size)
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
{
- info("Mocking send: %zu bytes\n", len);
+ netdata_log_info("Mocking send: %zu bytes\n", len);
(void)sockfd;
(void)buf;
(void)flags;
@@ -85,7 +85,7 @@ int __wrap_web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *
{
char url_repr[160];
repr(url_repr, sizeof(url_repr), url, strlen(url));
- info("web_client_api_request_v1(url=\"%s\")\n", url_repr);
+ netdata_log_info("web_client_api_request_v1(url=\"%s\")\n", url_repr);
check_expected_ptr(host);
check_expected_ptr(w);
check_expected_ptr(url_repr);
@@ -302,7 +302,7 @@ static void api_info(void **state)
char buffer_repr[1024];
repr(buffer_repr, sizeof(buffer_repr), def->instance->response.data->buffer,def->prefix_len);
- info("Buffer contains: %s [first %zu]", buffer_repr,def->prefix_len);
+ netdata_log_info("Buffer contains: %s [first %zu]", buffer_repr,def->prefix_len);
if (def->prefix_len == def->full_len) {
expect_value(__wrap_web_client_api_request_v1, host, localhost);
expect_value(__wrap_web_client_api_request_v1, w, def->instance);
diff --git a/web/api/web_api.c b/web/api/web_api.c
index 7c1d0fa09..4372bb8cb 100644
--- a/web/api/web_api.c
+++ b/web/api/web_api.c
@@ -2,6 +2,35 @@
#include "web_api.h"
+bool netdata_is_protected_by_bearer = false; // this is controlled by cloud, at the point the agent logs in - this should also be saved to /var/lib/netdata
+DICTIONARY *netdata_authorized_bearers = NULL;
+
+static bool web_client_check_acl_and_bearer(struct web_client *w, WEB_CLIENT_ACL endpoint_acl) {
+ if(endpoint_acl == WEB_CLIENT_ACL_NOCHECK)
+ // the endpoint is totally public
+ return true;
+
+ bool acl_allows = w->acl & endpoint_acl;
+ if(!acl_allows)
+ // the channel we received the request from (w->acl) is not compatible with the endpoint
+ return false;
+
+ if(!netdata_is_protected_by_bearer && !(endpoint_acl & WEB_CLIENT_ACL_BEARER_REQUIRED))
+ // bearer protection is not enabled and is not required by the endpoint
+ return true;
+
+ if(!(endpoint_acl & (WEB_CLIENT_ACL_BEARER_REQUIRED|WEB_CLIENT_ACL_BEARER_OPTIONAL)))
+ // endpoint does not require a bearer
+ return true;
+
+ if((w->acl & (WEB_CLIENT_ACL_ACLK|WEB_CLIENT_ACL_WEBRTC)) || api_check_bearer_token(w))
+ // the request is coming from ACLK or WEBRTC (authorized already),
+ // or we have a valid bearer on the request
+ return true;
+
+ return false;
+}
+
int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_path_endpoint, struct web_api_command *api_commands) {
if(unlikely(!url_path_endpoint || !*url_path_endpoint)) {
buffer_flush(w->response.data);
@@ -13,8 +42,8 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
for(int i = 0; api_commands[i].command ; i++) {
if(unlikely(hash == api_commands[i].hash && !strcmp(url_path_endpoint, api_commands[i].command))) {
- if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl))
- return web_client_permission_denied(w);
+ if(unlikely(!web_client_check_acl_and_bearer(w, api_commands[i].acl)))
+ return web_client_bearer_required(w);
char *query_string = (char *)buffer_tostring(w->url_query_string_decoded);
diff --git a/web/api/web_api.h b/web/api/web_api.h
index 0ca91841f..840ac8dcb 100644
--- a/web/api/web_api.h
+++ b/web/api/web_api.h
@@ -9,6 +9,12 @@
#include "web/api/health/health_cmdapi.h"
#include "web/api/queries/weights.h"
+extern bool netdata_is_protected_by_bearer;
+extern DICTIONARY *netdata_authorized_bearers;
+bool api_check_bearer_token(struct web_client *w);
+bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t dst_len);
+void bearer_tokens_init(void);
+
struct web_api_command {
const char *command;
uint32_t hash;
diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c
index 637329696..1e4f9c41c 100644
--- a/web/api/web_api_v1.c
+++ b/web/api/web_api_v1.c
@@ -53,6 +53,35 @@ static struct {
static struct {
const char *name;
uint32_t hash;
+ CONTEXTS_V2_OPTIONS value;
+} contexts_v2_options[] = {
+ {"minify" , 0 , CONTEXT_V2_OPTION_MINIFY}
+ , {"debug" , 0 , CONTEXT_V2_OPTION_DEBUG}
+ , {"config" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS}
+ , {"instances" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES}
+ , {"values" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_VALUES}
+ , {"summary" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY}
+ , {NULL , 0 , 0}
+};
+
+static struct {
+ const char *name;
+ uint32_t hash;
+ CONTEXTS_V2_ALERT_STATUS value;
+} contexts_v2_alert_status[] = {
+ {"uninitialized" , 0 , CONTEXT_V2_ALERT_UNINITIALIZED}
+ , {"undefined" , 0 , CONTEXT_V2_ALERT_UNDEFINED}
+ , {"clear" , 0 , CONTEXT_V2_ALERT_CLEAR}
+ , {"raised" , 0 , CONTEXT_V2_ALERT_RAISED}
+ , {"active" , 0 , CONTEXT_V2_ALERT_RAISED}
+ , {"warning" , 0 , CONTEXT_V2_ALERT_WARNING}
+ , {"critical" , 0 , CONTEXT_V2_ALERT_CRITICAL}
+ , {NULL , 0 , 0}
+};
+
+static struct {
+ const char *name;
+ uint32_t hash;
DATASOURCE_FORMAT value;
} api_v1_data_formats[] = {
{ DATASOURCE_FORMAT_DATATABLE_JSON , 0 , DATASOURCE_DATATABLE_JSON}
@@ -91,9 +120,15 @@ static struct {
void web_client_api_v1_init(void) {
int i;
+ for(i = 0; contexts_v2_alert_status[i].name ; i++)
+ contexts_v2_alert_status[i].hash = simple_hash(contexts_v2_alert_status[i].name);
+
for(i = 0; api_v1_data_options[i].name ; i++)
api_v1_data_options[i].hash = simple_hash(api_v1_data_options[i].name);
+ for(i = 0; contexts_v2_options[i].name ; i++)
+ contexts_v2_options[i].hash = simple_hash(contexts_v2_options[i].name);
+
for(i = 0; api_v1_data_formats[i].name ; i++)
api_v1_data_formats[i].hash = simple_hash(api_v1_data_formats[i].name);
@@ -126,11 +161,11 @@ char *get_mgmt_api_key(void) {
if(fd != -1) {
char buf[GUID_LEN + 1];
if(read(fd, buf, GUID_LEN) != GUID_LEN)
- error("Failed to read management API key from '%s'", api_key_filename);
+ netdata_log_error("Failed to read management API key from '%s'", api_key_filename);
else {
buf[GUID_LEN] = '\0';
if(regenerate_guid(buf, guid) == -1) {
- error("Failed to validate management API key '%s' from '%s'.",
+ netdata_log_error("Failed to validate management API key '%s' from '%s'.",
buf, api_key_filename);
guid[0] = '\0';
@@ -150,12 +185,12 @@ char *get_mgmt_api_key(void) {
// save it
fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC, 444);
if(fd == -1) {
- error("Cannot create unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", api_key_filename);
+ netdata_log_error("Cannot create unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", api_key_filename);
goto temp_key;
}
if(write(fd, guid, GUID_LEN) != GUID_LEN) {
- error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", api_key_filename);
+ netdata_log_error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", api_key_filename);
close(fd);
goto temp_key;
}
@@ -166,7 +201,7 @@ char *get_mgmt_api_key(void) {
return guid;
temp_key:
- info("You can still continue to use the alarm management API using the authorization token %s during this Netdata session only.", guid);
+ netdata_log_info("You can still continue to use the alarm management API using the authorization token %s during this Netdata session only.", guid);
return guid;
}
@@ -175,7 +210,7 @@ void web_client_api_v1_management_init(void) {
}
inline RRDR_OPTIONS web_client_api_request_v1_data_options(char *o) {
- RRDR_OPTIONS ret = 0x00000000;
+ RRDR_OPTIONS ret = 0;
char *tok;
while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
@@ -194,6 +229,78 @@ inline RRDR_OPTIONS web_client_api_request_v1_data_options(char *o) {
return ret;
}
+inline CONTEXTS_V2_OPTIONS web_client_api_request_v2_context_options(char *o) {
+ CONTEXTS_V2_OPTIONS ret = 0;
+ char *tok;
+
+ while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
+ if(!*tok) continue;
+
+ uint32_t hash = simple_hash(tok);
+ int i;
+ for(i = 0; contexts_v2_options[i].name ; i++) {
+ if (unlikely(hash == contexts_v2_options[i].hash && !strcmp(tok, contexts_v2_options[i].name))) {
+ ret |= contexts_v2_options[i].value;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+inline CONTEXTS_V2_ALERT_STATUS web_client_api_request_v2_alert_status(char *o) {
+ CONTEXTS_V2_ALERT_STATUS ret = 0;
+ char *tok;
+
+ while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
+ if(!*tok) continue;
+
+ uint32_t hash = simple_hash(tok);
+ int i;
+ for(i = 0; contexts_v2_alert_status[i].name ; i++) {
+ if (unlikely(hash == contexts_v2_alert_status[i].hash && !strcmp(tok, contexts_v2_alert_status[i].name))) {
+ ret |= contexts_v2_alert_status[i].value;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+void web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_ALERT_STATUS options) {
+ buffer_json_member_add_array(wb, key);
+
+ RRDR_OPTIONS used = 0; // to prevent adding duplicates
+ for(int i = 0; contexts_v2_alert_status[i].name ; i++) {
+ if (unlikely((contexts_v2_alert_status[i].value & options) && !(contexts_v2_alert_status[i].value & used))) {
+ const char *name = contexts_v2_alert_status[i].name;
+ used |= contexts_v2_alert_status[i].value;
+
+ buffer_json_add_array_item_string(wb, name);
+ }
+ }
+
+ buffer_json_array_close(wb);
+}
+
+void web_client_api_request_v2_contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_OPTIONS options) {
+ buffer_json_member_add_array(wb, key);
+
+ RRDR_OPTIONS used = 0; // to prevent adding duplicates
+ for(int i = 0; contexts_v2_options[i].name ; i++) {
+ if (unlikely((contexts_v2_options[i].value & options) && !(contexts_v2_options[i].value & used))) {
+ const char *name = contexts_v2_options[i].name;
+ used |= contexts_v2_options[i].value;
+
+ buffer_json_add_array_item_string(wb, name);
+ }
+ }
+
+ buffer_json_array_close(wb);
+}
+
void web_client_api_request_v1_data_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options) {
buffer_json_member_add_array(wb, key);
@@ -233,7 +340,7 @@ void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RR
*write = *end = '\0';
}
-inline DATASOURCE_FORMAT web_client_api_request_v1_data_format(char *name) {
+inline uint32_t web_client_api_request_v1_data_format(char *name) {
uint32_t hash = simple_hash(name);
int i;
@@ -307,7 +414,7 @@ inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_clien
if(!name || !*name) continue;
if(!value || !*value) continue;
- debug(D_WEB_CLIENT, "%llu: API v1 alarm_count query param '%s' with value '%s'", w->id, name, value);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 alarm_count query param '%s' with value '%s'", w->id, name, value);
char* p = value;
if(!strcmp(name, "status")) {
@@ -547,7 +654,7 @@ inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w,
// returns the HTTP code
static inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) {
- debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
int ret = HTTP_RESP_BAD_REQUEST;
BUFFER *dimensions = NULL;
@@ -586,7 +693,7 @@ static inline int web_client_api_request_v1_data(RRDHOST *host, struct web_clien
if(!name || !*name) continue;
if(!value || !*value) continue;
- debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
// name and value are now the parameters
// they are not null and not empty
@@ -732,14 +839,14 @@ static inline int web_client_api_request_v1_data(RRDHOST *host, struct web_clien
if(outFileName && *outFileName) {
buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
- debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
}
if(format == DATASOURCE_DATATABLE_JSONP) {
if(responseHandler == NULL)
responseHandler = "google.visualization.Query.setResponse";
- debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
+ netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
);
@@ -793,7 +900,7 @@ cleanup:
// /api/v1/registry?action=delete&machine=${machine_guid}&name=${hostname}&url=${url}&delete_url=${delete_url}
//
// Search for the URLs of a machine:
-// /api/v1/registry?action=search&machine=${machine_guid}&name=${hostname}&url=${url}&for=${machine_guid}
+// /api/v1/registry?action=search&for=${machine_guid}
//
// Impersonate:
// /api/v1/registry?action=switch&machine=${machine_guid}&name=${hostname}&url=${url}&to=${new_person_guid}
@@ -820,16 +927,17 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
*/
}
- char person_guid[GUID_LEN + 1] = "";
-
- debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
// TODO
// The browser may send multiple cookies with our id
+ char person_guid[UUID_STR_LEN] = "";
char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
if(cookie)
- strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], 36);
+ strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], UUID_STR_LEN - 1);
+ else if(!extract_bearer_token_from_request(w, person_guid, sizeof(person_guid)))
+ person_guid[0] = '\0';
char action = '\0';
char *machine_guid = NULL,
@@ -853,7 +961,7 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
if (!name || !*name) continue;
if (!value || !*value) continue;
- debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
uint32_t hash = simple_hash(name);
@@ -866,7 +974,7 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
#ifdef NETDATA_INTERNAL_CHECKS
- else error("unknown registry action '%s'", value);
+ else netdata_log_error("unknown registry action '%s'", value);
#endif /* NETDATA_INTERNAL_CHECKS */
}
/*
@@ -896,7 +1004,7 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
to_person_guid = value;
}
#ifdef NETDATA_INTERNAL_CHECKS
- else error("unused registry URL parameter '%s' with value '%s'", name, value);
+ else netdata_log_error("unused registry URL parameter '%s' with value '%s'", name, value);
#endif /* NETDATA_INTERNAL_CHECKS */
}
@@ -918,10 +1026,12 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
return web_client_permission_denied(w);
}
+ buffer_no_cacheable(w->response.data);
+
switch(action) {
case 'A':
if(unlikely(!machine_guid || !machine_url || !url_name)) {
- error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')", machine_guid ? machine_guid : "UNSET", machine_url ? machine_url : "UNSET", url_name ? url_name : "UNSET");
+ netdata_log_error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')", machine_guid ? machine_guid : "UNSET", machine_url ? machine_url : "UNSET", url_name ? url_name : "UNSET");
buffer_flush(w->response.data);
buffer_strcat(w->response.data, "Invalid registry Access request.");
return HTTP_RESP_BAD_REQUEST;
@@ -932,7 +1042,7 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
case 'D':
if(unlikely(!machine_guid || !machine_url || !delete_url)) {
- error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
+ netdata_log_error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
buffer_flush(w->response.data);
buffer_strcat(w->response.data, "Invalid registry Delete request.");
return HTTP_RESP_BAD_REQUEST;
@@ -942,19 +1052,19 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
case 'S':
- if(unlikely(!machine_guid || !machine_url || !search_machine_guid)) {
- error("Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET");
+ if(unlikely(!search_machine_guid)) {
+ netdata_log_error("Invalid registry request - search requires these parameters: for ('%s')", search_machine_guid?search_machine_guid:"UNSET");
buffer_flush(w->response.data);
buffer_strcat(w->response.data, "Invalid registry Search request.");
return HTTP_RESP_BAD_REQUEST;
}
web_client_enable_tracking_required(w);
- return registry_request_search_json(host, w, person_guid, machine_guid, machine_url, search_machine_guid, now_realtime_sec());
+ return registry_request_search_json(host, w, person_guid, search_machine_guid);
case 'W':
if(unlikely(!machine_guid || !machine_url || !to_person_guid)) {
- error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
+ netdata_log_error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
buffer_flush(w->response.data);
buffer_strcat(w->response.data, "Invalid registry Switch request.");
return HTTP_RESP_BAD_REQUEST;
@@ -1129,12 +1239,7 @@ inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb)
host_functions2json(host, wb);
host_collectors(host, wb);
-#ifdef DISABLE_CLOUD
- buffer_json_member_add_boolean(wb, "cloud-enabled", false);
-#else
- buffer_json_member_add_boolean(wb, "cloud-enabled",
- appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", true));
-#endif
+ buffer_json_member_add_boolean(wb, "cloud-enabled", netdata_cloud_enabled);
#ifdef ENABLE_ACLK
buffer_json_member_add_boolean(wb, "cloud-available", true);
@@ -1160,12 +1265,12 @@ inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb)
buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE);
buffer_json_member_add_boolean(wb, "stream-enabled", default_rrdpush_enabled);
-#ifdef ENABLE_COMPRESSION
+#ifdef ENABLE_RRDPUSH_COMPRESSION
buffer_json_member_add_boolean(wb, "stream-compression",
host->sender && stream_has_capability(host->sender, STREAM_CAP_COMPRESSION));
-#else
+#else // ! ENABLE_RRDPUSH_COMPRESSION
buffer_json_member_add_boolean(wb, "stream-compression", false);
-#endif //ENABLE_COMPRESSION
+#endif // ENABLE_RRDPUSH_COMPRESSION
#ifdef ENABLE_HTTPS
buffer_json_member_add_boolean(wb, "https-enabled", true);
@@ -1414,12 +1519,6 @@ int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struc
}
#endif
-#ifdef NETDATA_DEV_MODE
-#define ACL_DEV_OPEN_ACCESS WEB_CLIENT_ACL_DASHBOARD
-#else
-#define ACL_DEV_OPEN_ACCESS 0
-#endif
-
static struct web_api_command api_commands_v1[] = {
{ "info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_info },
{ "data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_data },
@@ -1429,7 +1528,7 @@ static struct web_api_command api_commands_v1[] = {
{ "contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_contexts },
// registry checks the ACL by itself, so we allow everything
- { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry },
+ { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry },
// badges can be fetched with both dashboard and badge permissions
{ "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC | WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge },
@@ -1443,21 +1542,21 @@ static struct web_api_command api_commands_v1[] = {
#if defined(ENABLE_ML)
{ "ml_info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_ml_info },
- { "ml_models", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_ml_models },
+ // { "ml_models", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_ml_models },
#endif
- { "manage/health", 0, WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_mgmt_health },
+ {"manage/health", 0, WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER, web_client_api_request_v1_mgmt_health },
{ "aclk", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_aclk_state },
{ "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_metric_correlations },
{ "weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_weights },
- { "function", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function },
- { "functions", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions },
+ {"function", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function },
+ {"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions },
{ "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_dbengine_stats },
// terminator
- { NULL, 0, WEB_CLIENT_ACL_NONE, NULL },
+ { NULL, 0, WEB_CLIENT_ACL_NONE, NULL },
};
inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
diff --git a/web/api/web_api_v1.h b/web/api/web_api_v1.h
index 6fa8de017..5845f3ec2 100644
--- a/web/api/web_api_v1.h
+++ b/web/api/web_api_v1.h
@@ -7,6 +7,11 @@
struct web_client;
+CONTEXTS_V2_OPTIONS web_client_api_request_v2_context_options(char *o);
+CONTEXTS_V2_ALERT_STATUS web_client_api_request_v2_alert_status(char *o);
+void web_client_api_request_v2_contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_OPTIONS options);
+void web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_ALERT_STATUS options);
+
RRDR_OPTIONS web_client_api_request_v1_data_options(char *o);
void web_client_api_request_v1_data_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options);
void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
diff --git a/web/api/web_api_v2.c b/web/api/web_api_v2.c
index 7280c0427..97647f5d6 100644
--- a/web/api/web_api_v2.c
+++ b/web/api/web_api_v2.c
@@ -3,7 +3,206 @@
#include "web_api_v2.h"
#include "../rtc/webrtc.h"
-static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_OPTIONS options) {
+#define BEARER_TOKEN_EXPIRATION 86400
+
+struct bearer_token {
+ time_t created_s;
+ time_t expires_s;
+};
+
+static void bearer_token_cleanup(void) {
+ static time_t attempts = 0;
+
+ if(++attempts % 1000 != 0)
+ return;
+
+ time_t now_s = now_monotonic_sec();
+
+ struct bearer_token *z;
+ dfe_start_read(netdata_authorized_bearers, z) {
+ if(z->expires_s < now_s)
+ dictionary_del(netdata_authorized_bearers, z_dfe.name);
+ }
+ dfe_done(z);
+
+ dictionary_garbage_collect(netdata_authorized_bearers);
+}
+
+void bearer_tokens_init(void) {
+ netdata_authorized_bearers = dictionary_create_advanced(
+ DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ NULL, sizeof(struct bearer_token));
+}
+
+static time_t bearer_get_token(uuid_t *uuid) {
+ char uuid_str[UUID_STR_LEN];
+
+ uuid_generate_random(*uuid);
+ uuid_unparse_lower(*uuid, uuid_str);
+
+ struct bearer_token t = { 0 }, *z;
+ z = dictionary_set(netdata_authorized_bearers, uuid_str, &t, sizeof(t));
+ if(!z->created_s) {
+ z->created_s = now_monotonic_sec();
+ z->expires_s = z->created_s + BEARER_TOKEN_EXPIRATION;
+ }
+
+ bearer_token_cleanup();
+
+ return now_realtime_sec() + BEARER_TOKEN_EXPIRATION;
+}
+
+#define HTTP_REQUEST_AUTHORIZATION_BEARER "\r\nAuthorization: Bearer "
+
+bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t dst_len) {
+ const char *req = buffer_tostring(w->response.data);
+ size_t req_len = buffer_strlen(w->response.data);
+ const char *bearer = strcasestr(req, HTTP_REQUEST_AUTHORIZATION_BEARER);
+
+ if(!bearer)
+ return false;
+
+ const char *token_start = bearer + sizeof(HTTP_REQUEST_AUTHORIZATION_BEARER) - 1;
+
+ while(isspace(*token_start))
+ token_start++;
+
+ const char *token_end = token_start + UUID_STR_LEN - 1 + 2;
+ if (token_end > req + req_len)
+ return false;
+
+ strncpyz(dst, token_start, dst_len - 1);
+ uuid_t uuid;
+ if (uuid_parse(dst, uuid) != 0)
+ return false;
+
+ return true;
+}
+
+bool api_check_bearer_token(struct web_client *w) {
+ if(!netdata_authorized_bearers)
+ return false;
+
+ char token[UUID_STR_LEN];
+ if(!extract_bearer_token_from_request(w, token, sizeof(token)))
+ return false;
+
+ struct bearer_token *z = dictionary_get(netdata_authorized_bearers, token);
+ return z && z->expires_s > now_monotonic_sec();
+}
+
+static bool verify_agent_uuids(const char *machine_guid, const char *node_id, const char *claim_id) {
+ if(!machine_guid || !node_id || !claim_id)
+ return false;
+
+ if(strcmp(machine_guid, localhost->machine_guid) != 0)
+ return false;
+
+ char *agent_claim_id = get_agent_claimid();
+
+ bool not_verified = (!agent_claim_id || strcmp(claim_id, agent_claim_id) != 0);
+ freez(agent_claim_id);
+
+ if(not_verified || !localhost->node_id)
+ return false;
+
+ char buf[UUID_STR_LEN];
+ uuid_unparse_lower(*localhost->node_id, buf);
+
+ if(strcmp(node_id, buf) != 0)
+ return false;
+
+ return true;
+}
+
+int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url) {
+ char *machine_guid = NULL;
+ char *claim_id = NULL;
+ char *node_id = NULL;
+ bool protection = netdata_is_protected_by_bearer;
+
+ while (url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name) continue;
+ if (!value || !*value) continue;
+
+ if(!strcmp(name, "bearer_protection")) {
+ if(!strcmp(value, "on") || !strcmp(value, "true") || !strcmp(value, "yes"))
+ protection = true;
+ else
+ protection = false;
+ }
+ else if(!strcmp(name, "machine_guid"))
+ machine_guid = value;
+ else if(!strcmp(name, "claim_id"))
+ claim_id = value;
+ else if(!strcmp(name, "node_id"))
+ node_id = value;
+ }
+
+ if(!verify_agent_uuids(machine_guid, node_id, claim_id)) {
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ netdata_is_protected_by_bearer = protection;
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, false);
+ buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
+ buffer_json_finalize(wb);
+
+ return HTTP_RESP_OK;
+}
+
+int api_v2_bearer_token(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
+ char *machine_guid = NULL;
+ char *claim_id = NULL;
+ char *node_id = NULL;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name) continue;
+ if (!value || !*value) continue;
+
+ if(!strcmp(name, "machine_guid"))
+ machine_guid = value;
+ else if(!strcmp(name, "claim_id"))
+ claim_id = value;
+ else if(!strcmp(name, "node_id"))
+ node_id = value;
+ }
+
+ if(!verify_agent_uuids(machine_guid, node_id, claim_id)) {
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ uuid_t uuid;
+ time_t expires_s = bearer_get_token(&uuid);
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, false);
+ buffer_json_member_add_string(wb, "mg", localhost->machine_guid);
+ buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
+ buffer_json_member_add_uuid(wb, "token", &uuid);
+ buffer_json_member_add_time_t(wb, "expiration", expires_s);
+ buffer_json_finalize(wb);
+
+ return HTTP_RESP_OK;
+}
+
+static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_MODE mode) {
struct api_v2_contexts_request req = { 0 };
while(url) {
@@ -17,38 +216,133 @@ static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unu
// name and value are now the parameters
// they are not null and not empty
- if(!strcmp(name, "scope_nodes")) req.scope_nodes = value;
- else if((options & (CONTEXTS_V2_NODES | CONTEXTS_V2_CONTEXTS)) && !strcmp(name, "nodes")) req.nodes = value;
- else if((options & CONTEXTS_V2_CONTEXTS) && !strcmp(name, "scope_contexts")) req.scope_contexts = value;
- else if((options & CONTEXTS_V2_CONTEXTS) && !strcmp(name, "contexts")) req.contexts = value;
- else if((options & CONTEXTS_V2_SEARCH) && !strcmp(name, "q")) req.q = value;
- else if(!strcmp(name, "timeout")) req.timeout_ms = str2l(value);
+ if(!strcmp(name, "scope_nodes"))
+ req.scope_nodes = value;
+ else if(!strcmp(name, "nodes"))
+ req.nodes = value;
+ else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "scope_contexts"))
+ req.scope_contexts = value;
+ else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "contexts"))
+ req.contexts = value;
+ else if((mode & CONTEXTS_V2_SEARCH) && !strcmp(name, "q"))
+ req.q = value;
+ else if(!strcmp(name, "options"))
+ req.options = web_client_api_request_v2_context_options(value);
+ else if(!strcmp(name, "after"))
+ req.after = str2l(value);
+ else if(!strcmp(name, "before"))
+ req.before = str2l(value);
+ else if(!strcmp(name, "timeout"))
+ req.timeout_ms = str2l(value);
+ else if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) {
+ if (!strcmp(name, "alert"))
+ req.alerts.alert = value;
+ else if (!strcmp(name, "transition"))
+ req.alerts.transition = value;
+ else if(mode & CONTEXTS_V2_ALERTS) {
+ if (!strcmp(name, "status"))
+ req.alerts.status = web_client_api_request_v2_alert_status(value);
+ }
+ else if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
+ if (!strcmp(name, "last"))
+ req.alerts.last = strtoul(value, NULL, 0);
+ else if(!strcmp(name, "context"))
+ req.contexts = value;
+ else if (!strcmp(name, "anchor_gi")) {
+ req.alerts.global_id_anchor = str2ull(value, NULL);
+ }
+ else {
+ for(int i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
+ if(!strcmp(name, alert_transition_facets[i].query_param))
+ req.alerts.facets[i] = value;
+ }
+ }
+ }
+ }
}
- options |= CONTEXTS_V2_DEBUG;
+ if ((mode & CONTEXTS_V2_ALERT_TRANSITIONS) && !req.alerts.last)
+ req.alerts.last = 1;
buffer_flush(w->response.data);
buffer_no_cacheable(w->response.data);
- return rrdcontext_to_json_v2(w->response.data, &req, options);
+ return rrdcontext_to_json_v2(w->response.data, &req, mode);
+}
+
+static int web_client_api_request_v2_alert_transitions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERT_TRANSITIONS | CONTEXTS_V2_NODES);
+}
+
+static int web_client_api_request_v2_alerts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERTS | CONTEXTS_V2_NODES);
+}
+
+static int web_client_api_request_v2_functions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
+}
+
+static int web_client_api_request_v2_versions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_VERSIONS);
}
static int web_client_api_request_v2_q(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES);
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
}
static int web_client_api_request_v2_contexts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_CONTEXTS);
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
}
static int web_client_api_request_v2_nodes(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_DETAILED);
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_INFO);
+}
+
+static int web_client_api_request_v2_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO);
+}
+
+static int web_client_api_request_v2_node_instances(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODE_INSTANCES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO | CONTEXTS_V2_VERSIONS);
}
static int web_client_api_request_v2_weights(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE,
- WEIGHTS_FORMAT_MULTINODE, 2);
+ return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, WEIGHTS_FORMAT_MULTINODE, 2);
}
+static int web_client_api_request_v2_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_claim(w, url);
+}
+
+static int web_client_api_request_v2_alert_config(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ const char *config = NULL;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "config"))
+ config = value;
+ }
+
+ buffer_flush(w->response.data);
+
+ if(!config) {
+ w->response.data->content_type = CT_TEXT_PLAIN;
+ buffer_strcat(w->response.data, "A config hash ID is required. Add ?config=UUID query param");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ return contexts_v2_alert_config_to_json(w, config);
+}
+
+
#define GROUP_BY_KEY_MAX_LENGTH 30
static struct {
char group_by[GROUP_BY_KEY_MAX_LENGTH + 1];
@@ -290,14 +584,14 @@ static int web_client_api_request_v2_data(RRDHOST *host __maybe_unused, struct w
if(outFileName && *outFileName) {
buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
- debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
}
if(format == DATASOURCE_DATATABLE_JSONP) {
if(responseHandler == NULL)
responseHandler = "google.visualization.Query.setResponse";
- debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
+ netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
);
@@ -346,16 +640,30 @@ static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct
}
static struct web_api_command api_commands_v2[] = {
- {"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data},
- {"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes},
- {"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts},
- {"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights},
- {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q},
+ {"info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_info},
+
+ {"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data},
+ {"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights},
+
+ {"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts},
+ {"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes},
+ {"node_instances", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_node_instances},
+ {"versions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_versions},
+ {"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_functions},
+ {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q},
+ {"alerts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts},
+
+ {"alert_transitions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_transitions},
+ {"alert_config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_config},
+
+ {"claim", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v2_claim},
- {"rtc_offer", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v2_webrtc},
+ {"rtc_offer", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_webrtc},
+ {"bearer_protection", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_protection},
+ {"bearer_get_token", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_token},
// terminator
- {NULL, 0, WEB_CLIENT_ACL_NONE, NULL},
+ {NULL, 0, WEB_CLIENT_ACL_NONE, NULL},
};
inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {