From cd4377fab21e0f500bef7f06543fa848a039c1e0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 20 Jul 2023 06:50:01 +0200 Subject: Merging upstream version 1.41.0. Signed-off-by: Daniel Baumann --- web/api/badges/web_buffer_svg.c | 7 +- web/api/exporters/shell/allmetrics_shell.c | 21 +- web/api/formatters/charts2json.c | 4 +- web/api/formatters/csv/csv.c | 7 +- web/api/formatters/json/json.c | 7 +- web/api/formatters/json_wrapper.c | 15 +- web/api/formatters/rrdset2json.c | 8 +- web/api/formatters/ssv/ssv.c | 4 +- web/api/health/README.md | 4 +- web/api/health/health_cmdapi.c | 10 +- web/api/queries/query.c | 23 +- web/api/queries/rrdr.h | 20 ++ web/api/queries/weights.c | 16 +- web/api/tests/valid_urls.c | 2 +- web/api/tests/web_api.c | 6 +- web/api/web_api.c | 33 ++- web/api/web_api.h | 6 + web/api/web_api_v1.c | 193 ++++++++++++---- web/api/web_api_v1.h | 5 + web/api/web_api_v2.c | 354 +++++++++++++++++++++++++++-- 20 files changed, 605 insertions(+), 140 deletions(-) (limited to 'web/api') 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 @@ -50,6 +50,35 @@ static struct { , {NULL , 0 , 0} }; +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; @@ -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) { -- cgit v1.2.3