@cryptotaxi247 / netdata-1 / commits / 4c0122063

WIP - Netdata v2 (#18125)

* split claiming into multiple files; WIP claiming with api * pidfile is now dynamically allocated * netdata_exe_path is now dynamically allocated * remove ENABLE_CLOUD and ENABLE_ACLK * fix compilation * remove ENABLE_HTTPS and ENABLE_OPENSSL * remove the ability to disable cloud * remove netdata_cloud_enabled variable; split rooms into a json array * global libcurl initialization * detect common claiming errors * more common claiming errors * finished claiming via API * same as before * same as before * remove the old claiming logic that runs the claim script * working claim.conf * cleanup * fix log message; default proxy is env * fix log message * remove netdata-claim.sh from run.sh * remove netdata-claim.sh from everywhere, except kickstart scripts * create cloud.d if it does not exist. * better error handling and logging * handle proxy disable * merged master * fix cmakelists for new files * left-overs removal * Include libcurl in required dependencies. * Fix typo in dependency script. * Use pkg-config for finding cURL. This properly handles transitive dependencies, unlike the FindCURL module. * netdata installer writes claiming info to /etc/netdata/claim.conf * remove claim from netdata * add libcurl to windows packages * add libcurl to windows packages * compile-on-windows.sh installs too * add NODE_ID streaming back to child and INDIRECT cloud status * log child kill on windows * fixes for spawn server on windows to ensure we have a valid pid and the process is properly terminated * better handling to windows processes exit code * pass the cloud url from parents to children * add retries and timeout to claiming curl request * remove FILE * from plugins.d * spawn-tester to unittest spawning processes communication * spawn-tester now tests FILE pointer I/O * external plugins run in posix mode * set blocking I/O on all pipes * working spawn server on windows * latest changes in spawn_popen applied to linux tools * push environment * repeated tests of fds * export variable CYGWIN_BASE_PATH * renamed to NETDATA_CYGWIN_BASE_PATH * added cmd and help to adapt the command and the information to be presented to users during claiming * split spawn server versions into files * restored spawn server libuv based * working libuv based spawn server * fixes in libuv for windows * working spawn server based on posix_spawn() * fix fd leads on all spawn servers * fixed windows spawn server * fix signal handling to ensure proper cooperation with libuv * switched windows to posix_spawn() based spawn server * improvement on libuv version * callocz() event loop * simplification of libuv spawn server * minor fixes in libuv and spawn tester * api split into parts and separated by version; introduced /api/v3; no changes to old /api/v1 and /api/v2 * completed APIs splitting * function renames * remove dead code * split basic functions into a directory * execute external plugins in nofork spawn server with posix_spawn() for improved performance * reset signals when using posix_spawn() * fix spawn server logs and log cmdline in posix server * bearer_get_token() implemented as function * agent cloud status now exposes parent claim_id in indirect mode * fixes for node id streaming from parent to children * extract claimed id to separate file * claim_id is no longer in host structure; there is a global claim_id for this agent and there are parent and origin claim ids in host structure * fix issue on older compilers * implement /api/v3 using calls from v1 and v2 * prevent asan leaks on local-sockets callback * codacy fixes * moved claim web api to web/api/v2 * when the agent is offline, prefer indirect connection when available; log a warning when a node changes node id * improve inheritance of claim id from parent * claim_id for bearer token show match any of the claim ids known * aclk_connected replaced with functions * aclk api can now be limited to node information, implementing [cloud].scope = license manager * comment out most options in stream.conf so that internal defaults will be applied * respect negative matches for send charts matching * hidden functions are not accessible via the API; bearer_get_token function checks the request is coming from Netdata Cloud * /api/v3/settings API * added error logs to settings api * saving and loading of bearer tokens * Fix parameter when calling send_to_plugin * Prevent overflow * expose struct parser and typedef PARSER to enforce strict type checking on send_to_plugin() * ensure the parser will not go away randomly from the receiver - it is now cleared when the receiver lock is acquired; also ensure the output sockets are set in the parser as long as the parser runs * Add newline * Send parent claim id downstream * do not send anything when nodeid is zero * code re-organization and cleanup * add aclk capabilities, nodes summary and api version and protection to /api/v2,3/info * added /api/v3/me which returns information about the current user * make /api/v3/info accessible always * Partially revert "remove netdata-claim.sh from everywhere, except kickstart scripts" Due to how we handle files in our static builds and local builds, we actually need to continue installing `netdata-claim.sh` to enable a seamless transition to the new claiming mechanims without breaking compatibility with existing installs or existing automation tooling that is directly invoking the claiming script. The script itself will be rewritten in a subsequent commit to simply wrap the new claiming methodology, together with some additional changes to ensure that a warning is issued if the script is invoked by anything other than the kickstart script. * Rewrite claiming script to use new claiming method. * Revert "netdata installer writes claiming info to /etc/netdata/claim.conf" Same reasoning as for 2e27bedb3fbf9df523bff407f2e8c8428e350e38. We need to keep the old claiming support code in the kickstart script for the forseeable future so that existing installs can still be claimed, since the kickstart script is _NOT_ versioned with the agent. A later commit will add native support for the new claiming method and use that in preference to the claiming script if it appears to be available. * Add support for new claiming method to kickstart.sh. This adds native support to the kickstart script to use the new claiming method without depending on the claiming script, as well as adding a few extra tweaks to the claiming script to enable it to better handle the transition. Expected behavior is for the kickstart script to use the new claiming code path if the claiming script is either not installed, or does not contain the specific string `%%NEW_CLAIMING_METHOD%%`. This way we will skip the claiming script on systems which have the updated copy that uses the new claiming approach, which should keep kickstart behavior consistent with what Netdata itself supports. * Depend on JSON-C 0.14 as a minimum supported version. Needed for uint64 functions. * Fix claiming option validation in kickstart script. * do not cache auth in web client * reuse bearer tokens when the request to create one matches an existing * dictionaries dfe loops now allow using return statement * bearer token files are now fixed for specific agents by having the machine guid of the agent in them * systemd journal now respects facets and disables the default facets when not given * fixed commands.c * restored log for not openning config file * Fix Netdata group templating for claiming script. * Warn on failed templating in claiming script. * Make `--require-cloud` a slient no-op. We don’t need to warn users that it does nothing, we should just have ti do nothing. * added debugging info to claiming * log also the response * do not send double / at the url * properly remove keyword from parameters * disable debug during claimming * fix log messages * Update packaging/installer/kickstart.sh * Update packaging/installer/kickstart.sh * implemented POST request payload parsing for systemd-journal * added missing reset of facets in json parsing * JSON payload does not need hashes any more. I can accept the raw values --------- Co-authored-by: Ilya Mashchenko <ilya@netdata.cloud> Co-authored-by: Austin S. Hemmelgarn <austin@netdata.cloud> Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com> Co-authored-by: Austin S. Hemmelgarn <ahferroin7@gmail.com>

Costa Tsaousis committed Aug 23, 2024 at 14:12 UTC 4c0122063fd22affd5cd4878c02ef9d83ddc0860
271 files changed +12740 -9305
.github/dockerfiles/Dockerfile.clang
+1 -1
@@ -16,4 +16,4 @@ WORKDIR /netdata
16 COPY . .
17
18 # Build Netdata
19 -RUN ./netdata-installer.sh --dont-wait --dont-start-it --disable-go --require-cloud
19 +RUN ./netdata-installer.sh --dont-wait --dont-start-it --disable-go
.github/workflows/build.yml
+3 -9
@@ -930,24 +930,18 @@ jobs:
930 id: load
931 if: needs.file-check.outputs.run == 'true'
932 run: docker load --input image.tar
933 - - name: netdata-installer on ${{ matrix.distro }}, disable cloud
934 - id: build-no-cloud
935 - if: needs.file-check.outputs.run == 'true'
936 - run: |
937 - docker run --security-opt seccomp=unconfined -w /netdata test:${{ matrix.artifact_key }} \
938 - /bin/sh -c './netdata-installer.sh --dont-wait --dont-start-it --disable-cloud --one-time-build ${{ needs.file-check.outputs.skip-go }}'
933 - name: netdata-installer on ${{ matrix.distro }}, require cloud
934 id: build-cloud
935 if: needs.file-check.outputs.run == 'true'
936 run: |
937 docker run --security-opt seccomp=unconfined -w /netdata test:${{ matrix.artifact_key }} \
944 - /bin/sh -c './netdata-installer.sh --dont-wait --dont-start-it --require-cloud --one-time-build ${{ needs.file-check.outputs.skip-go }}'
938 + /bin/sh -c './netdata-installer.sh --dont-wait --dont-start-it --one-time-build ${{ needs.file-check.outputs.skip-go }}'
939 - name: netdata-installer on ${{ matrix.distro }}, require cloud, no JSON-C
940 id: build-no-jsonc
941 if: matrix.jsonc_removal != '' && needs.file-check.outputs.run == 'true'
942 run: |
943 docker run --security-opt seccomp=unconfined -w /netdata test:${{ matrix.artifact_key }} \
950 - /bin/sh -c '/rmjsonc.sh && ./netdata-installer.sh --dont-wait --dont-start-it --require-cloud --one-time-build ${{ needs.file-check.outputs.skip-go }}'
944 + /bin/sh -c '/rmjsonc.sh && ./netdata-installer.sh --dont-wait --dont-start-it --one-time-build ${{ needs.file-check.outputs.skip-go }}'
945 - name: Failure Notification
946 uses: rtCamp/action-slack-notify@v2
947 env:
@@ -1015,7 +1009,7 @@ jobs:
1009 id: build-source
1010 if: needs.file-check.outputs.run == 'true'
1011 run: |
1018 - sudo bash ./netdata-installer.sh --install-no-prefix /usr/local/netdata --dont-wait --dont-start-it --require-cloud --one-time-build
1012 + sudo bash ./netdata-installer.sh --install-no-prefix /usr/local/netdata --dont-wait --dont-start-it --one-time-build
1013 - name: Test Agent start up
1014 id: test-agent
1015 if: needs.file-check.outputs.run == 'true'
.gitignore
-1
@@ -88,7 +88,6 @@ system/systemd/netdata-updater.service
88 !system/systemd/netdata.service.*.in
89
90 src/health/notifications/alarm-notify.sh
91 -claim/netdata-claim.sh
91 src/collectors/cgroups.plugin/cgroup-name.sh
92 src/collectors/cgroups.plugin/cgroup-network-helper.sh
93 src/collectors/tc.plugin/tc-qos-helper.sh
CMakeLists.txt
+111 -57
@@ -147,8 +147,6 @@ option(DEFAULT_FEATURE_STATE "Specify the default state for most optional featur
147 mark_as_advanced(DEFAULT_FEATURE_STATE)
148
149 # High-level features
150 -option(ENABLE_ACLK "Enable Netdata Cloud support (ACLK)" ${DEFAULT_FEATURE_STATE})
151 -option(ENABLE_CLOUD "Enable Netdata Cloud by default at runtime" ${DEFAULT_FEATURE_STATE})
150 option(ENABLE_ML "Enable machine learning features" ${DEFAULT_FEATURE_STATE})
151 option(ENABLE_DBENGINE "Enable dbengine metrics storage" True)
152
@@ -198,11 +196,7 @@ mark_as_advanced(BUILD_FOR_PACKAGING)
196 cmake_dependent_option(FORCE_LEGACY_LIBBPF "Force usage of libbpf 0.0.9 instead of the latest version." False "ENABLE_PLUGIN_EBPF" False)
197 mark_as_advanced(FORCE_LEGACY_LIBBPF)
198
201 -if(ENABLE_ACLK OR ENABLE_EXPORTER_PROMETHEUS_REMOTE_WRITE)
202 - set(NEED_PROTOBUF True)
203 -else()
204 - set(NEED_PROTOBUF False)
205 -endif()
199 +set(NEED_PROTOBUF True)
200
201 if(ENABLE_PLUGIN_GO)
202 include(NetdataGoTools)
@@ -261,6 +255,9 @@ if(ENABLE_PLUGIN_EBPF)
255 netdata_fetch_ebpf_co_re()
256 endif()
257
258 +pkg_check_modules(CURL libcurl>=7.21 REQUIRED IMPORTED_TARGET)
259 +set(HAVE_LIBCURL TRUE)
260 +
261 #
262 # Libm
263 #
@@ -524,7 +521,6 @@ if(OS_FREEBSD OR OS_MACOS)
521 endif()
522
523 # openssl/crypto
527 -set(ENABLE_OPENSSL True)
524 pkg_check_modules(TLS IMPORTED_TARGET openssl)
525
526 if(NOT TARGET PkgConfig::TLS)
@@ -743,14 +739,23 @@ set(LIBNETDATA_FILES
739 src/libnetdata/os/setenv.h
740 src/libnetdata/os/strndup.c
741 src/libnetdata/os/strndup.h
746 - src/libnetdata/spawn_server/spawn_server.c
742 + src/libnetdata/spawn_server/spawn_server_nofork.c
743 src/libnetdata/spawn_server/spawn_server.h
744 src/libnetdata/spawn_server/spawn_popen.c
745 src/libnetdata/spawn_server/spawn_popen.h
746 + src/libnetdata/spawn_server/spawn_server_windows.c
747 + src/libnetdata/spawn_server/spawn_server_internals.h
748 + src/libnetdata/spawn_server/spawn_server_libuv.c
749 + src/libnetdata/spawn_server/spawn_server_posix.c
750 + src/libnetdata/spawn_server/spawn_library.c
751 + src/libnetdata/spawn_server/spawn_library.h
752 src/libnetdata/os/close_range.c
753 src/libnetdata/os/close_range.h
754 src/libnetdata/os/setproctitle.c
755 src/libnetdata/os/setproctitle.h
756 + src/libnetdata/paths/paths.c
757 + src/libnetdata/paths/paths.h
758 + src/libnetdata/json/json-c-parser-inline.c
759 )
760
761 if(ENABLE_PLUGIN_EBPF)
@@ -849,14 +854,15 @@ set(DAEMON_FILES
854 src/daemon/common.h
855 src/daemon/daemon.c
856 src/daemon/daemon.h
852 - src/daemon/event_loop.c
853 - src/daemon/event_loop.h
857 + src/daemon/libuv_workers.c
858 + src/daemon/libuv_workers.h
859 src/daemon/global_statistics.c
860 src/daemon/global_statistics.h
861 src/daemon/analytics.c
862 src/daemon/analytics.h
863 src/daemon/main.c
864 src/daemon/main.h
865 + src/daemon/environment.c
866 src/daemon/win_system-info.c
867 src/daemon/win_system-info.h
868 src/daemon/signals.c
@@ -905,16 +911,65 @@ set(API_PLUGIN_FILES
911 src/web/api/web_api_v1.h
912 src/web/api/web_api_v2.c
913 src/web/api/web_api_v2.h
914 + src/web/api/web_api_v3.c
915 + src/web/api/web_api_v3.h
916 src/web/api/http_auth.c
917 src/web/api/http_auth.h
918 src/web/api/http_header.c
919 src/web/api/http_header.h
912 - src/web/api/badges/web_buffer_svg.c
913 - src/web/api/badges/web_buffer_svg.h
914 - src/web/api/exporters/allmetrics.c
915 - src/web/api/exporters/allmetrics.h
916 - src/web/api/exporters/shell/allmetrics_shell.c
917 - src/web/api/exporters/shell/allmetrics_shell.h
920 + src/web/api/maps/rrdr_options.c
921 + src/web/api/maps/rrdr_options.h
922 + src/web/api/maps/contexts_options.c
923 + src/web/api/maps/contexts_options.h
924 + src/web/api/maps/datasource_formats.c
925 + src/web/api/maps/datasource_formats.h
926 + src/web/api/maps/maps.h
927 + src/web/api/maps/contexts_alert_statuses.c
928 + src/web/api/maps/contexts_alert_statuses.h
929 + src/web/api/v1/api_v1_allmetrics.c
930 + src/web/api/v1/api_v1_badge/web_buffer_svg.c
931 + src/web/api/v1/api_v1_function.c
932 + src/web/api/v1/api_v1_manage.c
933 + src/web/api/v1/api_v1_calls.h
934 + src/web/api/v1/api_v1_dbengine.c
935 + src/web/api/v1/api_v1_config.c
936 + src/web/api/v1/api_v1_functions.c
937 + src/web/api/v1/api_v1_weights.c
938 + src/web/api/v1/api_v1_info.c
939 + src/web/api/v1/api_v1_registry.c
940 + src/web/api/v1/api_v1_data.c
941 + src/web/api/v1/api_v1_contexts.c
942 + src/web/api/v1/api_v1_ml_info.c
943 + src/web/api/v1/api_v1_aclk.c
944 + src/web/api/v1/api_v1_context.c
945 + src/web/api/v1/api_v1_alarms.c
946 + src/web/api/v1/api_v1_charts.c
947 + src/web/api/v2/api_v2_info.c
948 + src/web/api/v2/api_v2_nodes.c
949 + src/web/api/v2/api_v2_node_instances.c
950 + src/web/api/v2/api_v2_q.c
951 + src/web/api/v2/api_v2_versions.c
952 + src/web/api/v2/api_v2_functions.c
953 + src/web/api/v2/api_v2_alerts.c
954 + src/web/api/v2/api_v2_alert_transitions.c
955 + src/web/api/v2/api_v2_ilove/ilove.c
956 + src/web/api/v2/api_v2_bearer.c
957 + src/web/api/v2/api_v2_calls.h
958 + src/web/api/v2/api_v2_data.c
959 + src/web/api/v2/api_v2_progress.c
960 + src/web/api/v2/api_v2_weights.c
961 + src/web/api/v2/api_v2_alert_config.c
962 + src/web/api/v2/api_v2_contexts.c
963 + src/web/api/v2/api_v2_claim.c
964 + src/web/api/v2/api_v2_webrtc.c
965 + src/web/api/v3/api_v3_calls.h
966 + src/web/api/v3/api_v3_settings.c
967 + src/web/api/functions/functions.c
968 + src/web/api/functions/functions.h
969 + src/web/api/functions/function-progress.c
970 + src/web/api/functions/function-progress.h
971 + src/web/api/functions/function-streaming.c
972 + src/web/api/functions/function-streaming.h
973 src/web/api/queries/rrdr.c
974 src/web/api/queries/rrdr.h
975 src/web/api/queries/query.c
@@ -961,10 +1016,11 @@ set(API_PLUGIN_FILES
1016 src/web/api/formatters/charts2json.h
1017 src/web/api/formatters/rrdset2json.c
1018 src/web/api/formatters/rrdset2json.h
964 - src/web/api/ilove/ilove.c
965 - src/web/api/ilove/ilove.h
1019 src/web/rtc/webrtc.c
1020 src/web/rtc/webrtc.h
1021 + src/web/api/functions/function-bearer_get_token.c
1022 + src/web/api/functions/function-bearer_get_token.h
1023 + src/web/api/v3/api_v3_me.c
1024 )
1025
1026 set(EXPORTING_ENGINE_FILES
@@ -1055,8 +1111,14 @@ set(PLUGINSD_PLUGIN_FILES
1111 )
1112
1113 set(RRD_PLUGIN_FILES
1058 - src/database/contexts/api_v1.c
1059 - src/database/contexts/api_v2.c
1114 + src/database/contexts/api_v1_contexts.c
1115 + src/database/contexts/api_v2_contexts.c
1116 + src/database/contexts/api_v2_contexts.h
1117 + src/database/contexts/api_v2_contexts_agents.c
1118 + src/database/contexts/api_v2_contexts_alerts.c
1119 + src/database/contexts/api_v2_contexts_alerts.h
1120 + src/database/contexts/api_v2_contexts_alert_transitions.c
1121 + src/database/contexts/api_v2_contexts_alert_config.c
1122 src/database/contexts/context.c
1123 src/database/contexts/instance.c
1124 src/database/contexts/internal.h
@@ -1073,10 +1135,6 @@ set(RRD_PLUGIN_FILES
1135 src/database/rrdfunctions.h
1136 src/database/rrdfunctions-inline.c
1137 src/database/rrdfunctions-inline.h
1076 - src/database/rrdfunctions-progress.c
1077 - src/database/rrdfunctions-progress.h
1078 - src/database/rrdfunctions-streaming.c
1079 - src/database/rrdfunctions-streaming.h
1138 src/database/rrdhost.c
1139 src/database/rrdlabels.c
1140 src/database/rrd.c
@@ -1200,6 +1258,10 @@ set(STREAMING_PLUGIN_FILES
1258 src/streaming/replication.c
1259 src/streaming/replication.h
1260 src/streaming/common.h
1261 + src/streaming/protocol/command-nodeid.c
1262 + src/streaming/protocol/commands.c
1263 + src/streaming/protocol/commands.h
1264 + src/streaming/protocol/command-claimed_id.c
1265 )
1266
1267 set(WEB_PLUGIN_FILES
@@ -1216,6 +1278,12 @@ set(WEB_PLUGIN_FILES
1278 set(CLAIM_PLUGIN_FILES
1279 src/claim/claim.c
1280 src/claim/claim.h
1281 + src/claim/claim_id.c
1282 + src/claim/claim_id.h
1283 + src/claim/cloud-conf.c
1284 + src/claim/claim-with-api.c
1285 + src/claim/cloud-status.c
1286 + src/claim/cloud-status.h
1287 )
1288
1289 set(ACLK_ALWAYS_BUILD
@@ -1677,10 +1745,7 @@ endif()
1745 #
1746 # mqtt library
1747 #
1680 -if (ENABLE_H2O OR ENABLE_ACLK)
1681 - set(ENABLE_MQTTWEBSOCKETS True)
1682 -endif()
1683 -
1748 +set(ENABLE_MQTTWEBSOCKETS True)
1749 if(ENABLE_MQTTWEBSOCKETS)
1750 add_library(mqttwebsockets STATIC ${MQTT_WEBSOCKETS_FILES})
1751
@@ -1695,20 +1760,17 @@ if(ENABLE_MQTTWEBSOCKETS)
1760
1761 endif()
1762
1698 -if(ENABLE_ACLK)
1699 - #
1700 - # proto definitions
1701 - #
1702 - netdata_protoc_generate_cpp("${CMAKE_SOURCE_DIR}/src/aclk/aclk-schemas"
1703 - "${CMAKE_SOURCE_DIR}/src/aclk/aclk-schemas"
1704 - ACLK_PROTO_BUILT_SRCS
1705 - ACLK_PROTO_BUILT_HDRS
1706 - ${ACLK_PROTO_DEFS})
1707 -
1708 - list(APPEND ACLK_FILES ${ACLK_PROTO_BUILT_SRCS}
1709 - ${ACLK_PROTO_BUILT_HDRS})
1763 +#
1764 +# proto definitions
1765 +#
1766 +netdata_protoc_generate_cpp("${CMAKE_SOURCE_DIR}/src/aclk/aclk-schemas"
1767 + "${CMAKE_SOURCE_DIR}/src/aclk/aclk-schemas"
1768 + ACLK_PROTO_BUILT_SRCS
1769 + ACLK_PROTO_BUILT_HDRS
1770 + ${ACLK_PROTO_DEFS})
1771
1711 -endif()
1772 +list(APPEND ACLK_FILES ${ACLK_PROTO_BUILT_SRCS}
1773 + ${ACLK_PROTO_BUILT_HDRS})
1774
1775 #
1776 # build plugins
@@ -1740,6 +1802,9 @@ if(ENABLE_PLUGIN_DEBUGFS)
1802 endif()
1803 endif()
1804
1805 +add_executable(spawn-tester src/libnetdata/spawn_server/spawn-tester.c)
1806 +target_link_libraries(spawn-tester libnetdata)
1807 +
1808 if(ENABLE_PLUGIN_APPS)
1809 pkg_check_modules(CAP QUIET libcap)
1810
@@ -2164,7 +2229,7 @@ endif()
2229
2230 add_executable(netdata
2231 ${NETDATA_FILES}
2167 - "$<$<BOOL:${ENABLE_ACLK}>:${ACLK_FILES}>"
2232 + "${ACLK_FILES}"
2233 "$<$<BOOL:${ENABLE_H2O}>:${H2O_FILES}>"
2234 "$<$<BOOL:${ENABLE_EXPORTER_MONGODB}>:${MONGODB_EXPORTING_FILES}>"
2235 "$<$<BOOL:${ENABLE_EXPORTER_PROMETHEUS_REMOTE_WRITE}>:${PROMETHEUS_REMOTE_WRITE_EXPORTING_FILES}>"
@@ -2180,7 +2245,7 @@ target_compile_options(netdata PRIVATE
2245 )
2246
2247 target_include_directories(netdata PRIVATE
2183 - "$<$<BOOL:${ENABLE_ACLK}>:${CMAKE_SOURCE_DIR}/src/aclk/aclk-schemas>"
2248 + "${CMAKE_SOURCE_DIR}/src/aclk/aclk-schemas"
2249 "$<$<BOOL:${ENABLE_EXPORTER_MONGODB}>:${MONGOC_INCLUDE_DIRS}>"
2250 "$<$<BOOL:${ENABLE_EXPORTER_PROMETHEUS_REMOTE_WRITE}>:${SNAPPY_INCLUDE_DIRS}>"
2251 )
@@ -2196,6 +2261,7 @@ target_link_libraries(netdata PRIVATE
2261 "$<$<BOOL:${ENABLE_SENTRY}>:sentry>"
2262 "$<$<BOOL:${ENABLE_WEBRTC}>:LibDataChannel::LibDataChannelStatic>"
2263 "$<$<BOOL:${ENABLE_H2O}>:h2o>"
2264 + "$<$<BOOL:${CURL_FOUND}>:PkgConfig::CURL>"
2265 )
2266
2267 if(NEED_PROTOBUF)
@@ -2349,19 +2415,7 @@ set(cachedir_POST "${NETDATA_RUNTIME_PREFIX}/var/cache/netdata")
2415 set(registrydir_POST "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata/registry")
2416 set(varlibdir_POST "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata")
2417 set(netdata_user_POST "${NETDATA_USER}")
2352 -
2353 -# netdata-claim.sh
2354 -if(ENABLE_CLOUD)
2355 - set(enable_cloud_POST "yes")
2356 -else()
2357 - set(enable_cloud_POST "no")
2358 -endif()
2359 -
2360 -if(ENABLE_ACLK)
2361 - set(enable_aclk_POST "yes")
2362 -else()
2363 - set(enable_aclk_POST "no")
2364 -endif()
2418 +set(netdata_group_POST "${NETDATA_USER}")
2419
2420 configure_file(src/claim/netdata-claim.sh.in src/claim/netdata-claim.sh @ONLY)
2421 install(PROGRAMS
netdata-installer.sh
+3 -21
@@ -202,12 +202,9 @@ USAGE: ${PROGRAM} [options]
202 --nightly-channel Use most recent nightly updates instead of GitHub releases.
203 This results in more frequent updates.
204 --disable-ebpf Disable eBPF Kernel plugin. Default: enabled.
205 - --disable-cloud Disable all Netdata Cloud functionality.
206 - --require-cloud Fail the install if it can't build Netdata Cloud support.
205 --force-legacy-cxx Force usage of an older C++ standard to allow building on older systems. This will usually be autodetected.
206 --enable-plugin-freeipmi Enable the FreeIPMI plugin. Default: enable it when libipmimonitoring is available.
207 --disable-plugin-freeipmi Explicitly disable the FreeIPMI plugin.
210 - --disable-https Explicitly disable TLS support.
208 --disable-dbengine Explicitly disable DB engine support.
209 --enable-plugin-go Enable the Go plugin. Default: Enabled when possible.
210 --disable-plugin-go Disable the Go plugin.
@@ -257,7 +254,6 @@ NETDATA_ENABLE_ML=""
254 ENABLE_DBENGINE=1
255 ENABLE_GO=1
256 ENABLE_H2O=1
260 -ENABLE_CLOUD=1
257 FORCE_LEGACY_CXX=0
258 NETDATA_CMAKE_OPTIONS="${NETDATA_CMAKE_OPTIONS-}"
259
@@ -279,9 +275,7 @@ while [ -n "${1}" ]; do
275 "--enable-plugin-freeipmi") ENABLE_FREEIPMI=1 ;;
276 "--disable-plugin-freeipmi") ENABLE_FREEIPMI=0 ;;
277 "--disable-https")
282 - ENABLE_DBENGINE=0
283 - ENABLE_H2O=0
284 - ENABLE_CLOUD=0
278 + warning "HTTPS cannot be disabled."
279 ;;
280 "--disable-dbengine") ENABLE_DBENGINE=0 ;;
281 "--enable-plugin-go") ENABLE_GO=1 ;;
@@ -328,21 +322,9 @@ while [ -n "${1}" ]; do
322 # XXX: No longer supported
323 ;;
324 "--disable-cloud")
331 - if [ -n "${NETDATA_REQUIRE_CLOUD}" ]; then
332 - warning "Cloud explicitly enabled, ignoring --disable-cloud."
333 - else
334 - ENABLE_CLOUD=0
335 - NETDATA_DISABLE_CLOUD=1
336 - fi
337 - ;;
338 - "--require-cloud")
339 - if [ -n "${NETDATA_DISABLE_CLOUD}" ]; then
340 - warning "Cloud explicitly disabled, ignoring --require-cloud."
341 - else
342 - ENABLE_CLOUD=1
343 - NETDATA_REQUIRE_CLOUD=1
344 - fi
325 + warning "Cloud cannot be disabled."
326 ;;
327 + "--require-cloud") ;;
328 "--build-json-c")
329 NETDATA_BUILD_JSON_C=1
330 ;;
netdata.spec.in
-2
@@ -388,8 +388,6 @@ happened, on your systems and applications.
388 %else
389 -DENABLE_EXPORTER_MONGODB=Off \
390 %endif
391 - -DENABLE_ACLK=On \
392 - -DENABLE_CLOUD=On \
391 -DENABLE_DBENGINE=On \
392 -DENABLE_H2O=On \
393 -DENABLE_PLUGIN_APPS=On \
packaging/build-package.sh
-2
@@ -26,8 +26,6 @@ add_cmake_option() {
26
27 add_cmake_option CMAKE_BUILD_TYPE RelWithDebInfo
28 add_cmake_option CMAKE_INSTALL_PREFIX /
29 -add_cmake_option ENABLE_ACLK On
30 -add_cmake_option ENABLE_CLOUD On
29 add_cmake_option ENABLE_DBENGINE On
30 add_cmake_option ENABLE_H2O On
31 add_cmake_option ENABLE_ML On
packaging/cmake/Modules/NetdataJSONC.cmake
+1 -1
@@ -71,7 +71,7 @@ endfunction()
71 # NETDATA_JSONC_* variables for later use.
72 macro(netdata_detect_jsonc)
73 if(NOT ENABLE_BUNDLED_JSONC)
74 - pkg_check_modules(JSONC json-c)
74 + pkg_check_modules(JSONC json-c>=0.14)
75 endif()
76
77 if(NOT JSONC_FOUND)
packaging/cmake/config.cmake.h.in
+1 -5
@@ -67,6 +67,7 @@
67 #cmakedefine HAVE_GETPRIORITY
68 #cmakedefine HAVE_SETENV
69 #cmakedefine HAVE_DLSYM
70 +#cmakedefine HAVE_LIBCURL
71
72 #cmakedefine HAVE_BACKTRACE
73 #cmakedefine HAVE_CLOSE_RANGE
@@ -103,14 +104,10 @@
104
105 // enabled features
106
106 -#cmakedefine ENABLE_OPENSSL
107 -#cmakedefine ENABLE_CLOUD
108 -#cmakedefine ENABLE_ACLK
107 #cmakedefine ENABLE_ML
108 #cmakedefine ENABLE_EXPORTING_MONGODB
109 #cmakedefine ENABLE_H2O
110 #cmakedefine ENABLE_DBENGINE
113 -#cmakedefine ENABLE_HTTPS
111 #cmakedefine ENABLE_LZ4
112 #cmakedefine ENABLE_ZSTD
113 #cmakedefine ENABLE_BROTLI
@@ -182,7 +179,6 @@
179 // #cmakedefine ENABLE_PROMETHEUS_REMOTE_WRITE
180
181 // /* NSA spy stuff */
185 -// #define ENABLE_HTTPS 1
182 // #cmakedefine01 HAVE_X509_VERIFY_PARAM_set1_host
183
184 #define HAVE_CRYPTO
packaging/dag/imageutils.py
-1
@@ -345,7 +345,6 @@ def static_build_netdata(
345 "--dont-wait",
346 "--dont-start-it",
347 "--disable-exporting-mongodb",
348 - "--require-cloud",
348 "--use-system-protobuf",
349 "--dont-scrub-cflags-even-though-it-may-break-things",
350 "--one-time-build",
packaging/docker/Dockerfile
-1
@@ -47,7 +47,6 @@ RUN mkdir -p /app/usr/sbin/ \
47 mv /var/lib/netdata /app/var/lib/ && \
48 mv /etc/netdata /app/etc/ && \
49 mv /usr/sbin/netdata /app/usr/sbin/ && \
50 - mv /usr/sbin/netdata-claim.sh /app/usr/sbin/ && \
50 mv /usr/sbin/netdatacli /app/usr/sbin/ && \
51 mv /usr/sbin/systemd-cat-native /app/usr/sbin/ && \
52 mv packaging/docker/run.sh /app/usr/sbin/ && \
packaging/docker/run.sh
-10
@@ -110,14 +110,4 @@ if [ -w "/etc/netdata" ]; then
110 fi
111 fi
112
113 -if [ -n "${NETDATA_CLAIM_URL}" ] && [ -n "${NETDATA_CLAIM_TOKEN}" ] && [ ! -f /var/lib/netdata/cloud.d/claimed_id ]; then
114 - # shellcheck disable=SC2086
115 - /usr/sbin/netdata-claim.sh -token="${NETDATA_CLAIM_TOKEN}" \
116 - -url="${NETDATA_CLAIM_URL}" \
117 - ${NETDATA_CLAIM_ROOMS:+-rooms="${NETDATA_CLAIM_ROOMS}"} \
118 - ${NETDATA_CLAIM_PROXY:+-proxy="${NETDATA_CLAIM_PROXY}"} \
119 - ${NETDATA_EXTRA_CLAIM_OPTS} \
120 - -daemon-not-running
121 -fi
122 -
113 exec /usr/sbin/netdata -u "${DOCKER_USR}" -D -s /host -p "${NETDATA_LISTENER_PORT}" "$@"
packaging/installer/functions.sh
-2
@@ -341,8 +341,6 @@ prepare_cmake_options() {
341 enable_feature PLUGIN_NETWORK_VIEWER "${IS_LINUX}"
342 enable_feature PLUGIN_EBPF "${ENABLE_EBPF:-0}"
343
344 - enable_feature ACLK "${ENABLE_CLOUD:-1}"
345 - enable_feature CLOUD "${ENABLE_CLOUD:-1}"
344 enable_feature BUNDLED_JSONC "${NETDATA_BUILD_JSON_C:-0}"
345 enable_feature DBENGINE "${ENABLE_DBENGINE:-1}"
346 enable_feature H2O "${ENABLE_H2O:-1}"
packaging/installer/install-required-packages.sh
+12
@@ -825,6 +825,17 @@ declare -A pkg_libuuid_dev=(
825 ['default']=""
826 )
827
828 +declare -A pkg_libcurl_dev=(
829 + ['alpine']="curl-dev"
830 + ['arch']="curl"
831 + ['clearlinux']="devpkg-curl"
832 + ['debian']="libcurl4-openssl-dev"
833 + ['gentoo']="net-misc/curl"
834 + ['ubuntu']="libcurl4-openssl-dev"
835 + ['macos']="curl"
836 + ['default']="libcurl-devel"
837 +)
838 +
839 declare -A pkg_libmnl_dev=(
840 ['alpine']="libmnl-dev"
841 ['arch']="libmnl"
@@ -1246,6 +1257,7 @@ packages() {
1257 suitable_package libyaml-dev
1258 suitable_package libsystemd-dev
1259 suitable_package pcre2
1260 + suitable_package libcurl-dev
1261 fi
1262
1263 # -------------------------------------------------------------------------
packaging/installer/kickstart.sh
+112 -97
@@ -53,11 +53,9 @@ INSTALL_PREFIX=""
53 NETDATA_AUTO_UPDATES="default"
54 NETDATA_CLAIM_URL="https://app.netdata.cloud"
55 NETDATA_COMMAND="default"
56 -NETDATA_DISABLE_CLOUD=0
56 NETDATA_INSTALLER_OPTIONS=""
57 NETDATA_FORCE_METHOD=""
58 NETDATA_OFFLINE_INSTALL_SOURCE=""
60 -NETDATA_REQUIRE_CLOUD=1
59 NETDATA_WARNINGS=""
60 RELEASE_CHANNEL="default"
61
@@ -149,8 +147,6 @@ main() {
147
148 if [ -n "${NETDATA_CLAIM_TOKEN}" ]; then
149 claim
152 - elif [ "${NETDATA_DISABLE_CLOUD}" -eq 1 ]; then
153 - soft_disable_cloud
150 fi
151
152 set_auto_updates
@@ -185,8 +181,6 @@ USAGE: kickstart.sh [options]
181 --native-only Only install if native binary packages are available.
182 --static-only Only install if a static build is available.
183 --build-only Only install using a local build.
188 - --disable-cloud Disable support for Netdata Cloud (default: detect)
189 - --require-cloud Only install if Netdata Cloud can be enabled. Overrides --disable-cloud.
184 --install-prefix <path> Specify an installation prefix for local builds (default: autodetect based on system type).
185 --old-install-prefix <path> Specify an old local builds installation prefix for uninstall/reinstall (if it's not default).
186 --install-version <version> Specify the version of Netdata to install.
@@ -1183,41 +1177,6 @@ handle_existing_install() {
1177 esac
1178 }
1179
1186 -soft_disable_cloud() {
1187 - set_tmpdir
1188 -
1189 - cloud_prefix="${INSTALL_PREFIX}/var/lib/netdata/cloud.d"
1190 -
1191 - run_as_root mkdir -p "${cloud_prefix}"
1192 -
1193 - cat > "${tmpdir}/cloud.conf" << EOF
1194 -[global]
1195 - enabled = no
1196 -EOF
1197 -
1198 - run_as_root cp "${tmpdir}/cloud.conf" "${cloud_prefix}/cloud.conf"
1199 -
1200 - if [ -z "${NETDATA_NO_START}" ]; then
1201 - case "${SYSTYPE}" in
1202 - Darwin) run_as_root launchctl kickstart -k com.github.netdata ;;
1203 - FreeBSD) run_as_root service netdata restart ;;
1204 - Linux)
1205 - initpath="$(run_as_root readlink /proc/1/exe)"
1206 -
1207 - if command -v service > /dev/null 2>&1; then
1208 - run_as_root service netdata restart
1209 - elif command -v rc-service > /dev/null 2>&1; then
1210 - run_as_root rc-service netdata restart
1211 - elif [ "$(basename "${initpath}" 2> /dev/null)" = "systemd" ]; then
1212 - run_as_root systemctl restart netdata
1213 - elif [ -f /etc/init.d/netdata ]; then
1214 - run_as_root /etc/init.d/netdata restart
1215 - fi
1216 - ;;
1217 - esac
1218 - fi
1219 -}
1220 -
1180 confirm_install_prefix() {
1181 if [ -n "${INSTALL_PREFIX}" ] && [ "${NETDATA_FORCE_METHOD}" != 'build' ]; then
1182 fatal "The --install-prefix option is only supported together with the --build-only option." F0204
@@ -1246,10 +1205,9 @@ check_claim_opts() {
1205 # shellcheck disable=SC2235,SC2030
1206 if [ -z "${NETDATA_CLAIM_TOKEN}" ] && [ -n "${NETDATA_CLAIM_ROOMS}" ]; then
1207 fatal "Invalid claiming options, claim rooms may only be specified when a token is specified." F0204
1249 - elif [ -z "${NETDATA_CLAIM_TOKEN}" ] && [ -n "${NETDATA_CLAIM_EXTRA}" ]; then
1208 + elif [ -z "${NETDATA_CLAIM_TOKEN}" ] && [ -n "${NETDATA_CLAIM_EXTRA}${NETDATA_CLAIM_PROXY}${NETDATA_CLAIM_NORELOAD}${NETDATA_CLAIM_INSECURE}" ]; then
1209 + # The above condition checks if _any_ claiming options other than the rooms have been set when the token is unset.
1210 fatal "Invalid claiming options, a claiming token must be specified." F0204
1251 - elif [ "${NETDATA_DISABLE_CLOUD}" -eq 1 ] && [ -n "${NETDATA_CLAIM_TOKEN}" ]; then
1252 - fatal "Cloud explicitly disabled, but automatic claiming requested. Either enable Netdata Cloud, or remove the --claim-* options." F0204
1211 fi
1212 }
1213
@@ -1277,6 +1235,93 @@ is_netdata_running() {
1235 fi
1236 }
1237
1238 +write_claim_config() {
1239 + if [ -z "${INSTALL_PREFIX}" ] || [ "${INSTALL_PREFIX}" = "/" ]; then
1240 + config_path="/etc/netdata"
1241 + netdatacli="$(command -v netdatacli)"
1242 + elif [ "${INSTALL_PREFIX}" = "/opt/netdata" ]; then
1243 + config_path="/opt/netdata/etc/netdata"
1244 + netdatacli="/opt/netdata/bin/netdatacli"
1245 + elif [ ! -d "${INSTALL_PREFIX}/netdata" ]; then
1246 + config_path="${INSTALL_PREFIX}/etc/netdata"
1247 + netdatacli="${INSTALL_PREFIX}/usr/sbin/netdatacli"
1248 + else
1249 + config_path="${INSTALL_PREFIX}/netdata/etc/netdata"
1250 + netdatacli="${INSTALL_PREFIX}/netdata/usr/sbin/netdatacli"
1251 + fi
1252 +
1253 + claim_config="${config_path}/claim.conf"
1254 +
1255 + if [ "${DRY_RUN}" -eq 1 ]; then
1256 + progress "Would attempt to write claiming configuration to ${claim_config}"
1257 + return 0
1258 + fi
1259 +
1260 + progress "Writing claiming configuration to ${claim_config}"
1261 +
1262 + config="[global]"
1263 + config="${config}\n url = ${NETDATA_CLAIM_URL}"
1264 + config="${config}\n token = ${NETDATA_CLAIM_TOKEN}"
1265 + if [ -n "${NETDATA_CLAIM_ROOMS}" ]; then
1266 + config="${config}\n rooms = ${NETDATA_CLAIM_ROOMS}"
1267 + fi
1268 + if [ -n "${NETDATA_CLAIM_PROXY}" ]; then
1269 + config="${config}\n proxy = ${NETDATA_CLAIM_PROXY}"
1270 + fi
1271 + if [ -n "${NETDATA_CLAIM_INSECURE}" ]; then
1272 + config="${config}\n insecure = ${NETDATA_CLAIM_INSECURE}"
1273 + fi
1274 +
1275 + run_as_root touch "${claim_config}.tmp" || return 1
1276 + run_as_root chmod 0640 "${claim_config}.tmp" || return 1
1277 + run_as_root chown ":${NETDATA_CLAIM_GROUP:-netdata}" "${claim_config}.tmp" || return 1
1278 + run_as_root echo "${config}" > "${claim_config}.tmp" || return 1
1279 + run_as_root mv -f "${claim_config}.tmp" "${claim_config}" || return 1
1280 +
1281 + if [ -z "${NETDATA_CLAIM_NORELOAD}" ]; then
1282 + run_as_root "${netdatacli}" reload-claiming-state || return 1
1283 + fi
1284 +}
1285 +
1286 +run_claim_script() {
1287 + if [ -n "${NETDATA_CLAIM_NORELOAD}" ]; then
1288 + NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -daemon-not-running"
1289 + fi
1290 +
1291 + if [ -n "${NETDATA_CLAIM_INSECURE}" ]; then
1292 + NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -insecure"
1293 + fi
1294 +
1295 + if [ -n "${NETDATA_CLAIM_PROXY}" ]; then
1296 + if [ "${NETDATA_CLAIM_PROXY}" = "none" ]; then
1297 + NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -noproxy"
1298 + else
1299 + NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -proxy=${NETDATA_CLAIM_PROXY}"
1300 + fi
1301 + fi
1302 +
1303 + # shellcheck disable=SC2086
1304 + run_as_root "${NETDATA_CLAIM_PATH}" -token="${NETDATA_CLAIM_TOKEN}" -rooms="${NETDATA_CLAIM_ROOMS}" -url="${NETDATA_CLAIM_URL}" ${NETDATA_CLAIM_EXTRA}
1305 + case $? in
1306 + 0) progress "Successfully claimed node" ;;
1307 + 1) warning "Unable to claim node due to invalid claiming options. If you are seeing this message, you’ve probably found a bug and should open a bug report at ${AGENT_BUG_REPORT_URL}" ;;
1308 + 2) warning "Unable to claim node due to issues creating the claiming directory or preparing the local claiming key. Make sure you have a working openssl command and that ${INSTALL_PREFIX}/var/lib/netdata/cloud.d exists, then try again." ;;
1309 + 3) warning "Unable to claim node due to missing dependencies. Usually this means that the Netdata Agent was built without support for Netdata Cloud. If you built the agent from source, please install all needed dependencies for Cloud support. If you used the regular installation script and see this error, please file a bug report at ${AGENT_BUG_REPORT_URL}." ;;
1310 + 4) warning "Failed to claim node due to inability to connect to ${NETDATA_CLAIM_URL}. Usually this either means that the specified claiming URL is wrong, or that you are having networking problems." ;;
1311 + 5) progress "Successfully claimed node, but was not able to notify the Netdata Agent. You will need to restart the Netdata service on this node before it will show up in the Cloud." ;;
1312 + 8) warning "Failed to claim node due to an invalid agent ID. You can usually resolve this by removing ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restarting the agent. Then try to claim it again using the same options." ;;
1313 + 9) warning "Failed to claim node due to an invalid node name. This probably means you tried to specify a custom name for this node (for example, using the --claim-hostname option), but the hostname itself was either empty or consisted solely of whitespace. You can resolve this by specifying a valid host name and trying again." ;;
1314 + 10) warning "Failed to claim node due to an invalid room ID. This issue is most likely caused by a typo. Please check if the room(s) you are trying to add appear on the list of rooms provided to the --claim-rooms option ('${NETDATA_CLAIM_ROOMS}'). Then verify if the rooms are visible in Netdata Cloud and try again." ;;
1315 + 11) warning "Failed to claim node due to an issue with the generated RSA key pair. You can usually resolve this by removing all files in ${INSTALL_PREFIX}/var/lib/netdata/cloud.d and then trying again." ;;
1316 + 12) warning "Failed to claim node due to an invalid or expired claiming token. Please check that the token specified with the --claim-token option ('${NETDATA_CLAIM_TOKEN}') matches what you see in the Cloud and try again." ;;
1317 + 13) warning "Failed to claim node because the Cloud thinks it is already claimed. If this node was created by cloning a VM or as a container from a template, please remove the file ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restart the agent. Then try to claim it again with the same options. Otherwise, if you are certain this node has never been claimed before, you can use the --claim-id option to specify a new node ID to use for claiming, for example by using the uuidgen command like so: --claim-id \"\$(uuidgen)\"" ;;
1318 + 14) warning "Failed to claim node because the node is already in the process of being claimed. You should not need to do anything to resolve this, the node should show up properly in the Cloud soon. If it does not, please report a bug at ${AGENT_BUG_REPORT_URL}." ;;
1319 + 15|16|17) warning "Failed to claim node due to an internal server error in the Cloud. Please retry claiming this node later, and if you still see this message file a bug report at ${CLOUD_BUG_REPORT_URL}." ;;
1320 + 18) warning "Unable to claim node because this Netdata installation does not have a unique ID yet. Make sure the agent is running and started up correctly, and then try again." ;;
1321 + *) warning "Failed to claim node for an unknown reason. This usually means either networking problems or a bug. Please retry claiming later, and if you still see this message file a bug report at ${AGENT_BUG_REPORT_URL}" ;;
1322 + esac
1323 +}
1324 +
1325 claim() {
1326 if [ "${DRY_RUN}" -eq 1 ]; then
1327 progress "Would attempt to claim agent to ${NETDATA_CLAIM_URL}"
@@ -1300,17 +1345,18 @@ claim() {
1345 NETDATA_CLAIM_PATH="${INSTALL_PREFIX}/netdata/usr/sbin/netdata-claim.sh"
1346 fi
1347
1348 + method="script"
1349 err_msg=
1350 err_code=
1351 if [ -z "${NETDATA_CLAIM_PATH}" ]; then
1306 - err_msg="Unable to claim node: could not find usable claiming script. Reinstalling Netdata may resolve this."
1307 - err_code=F050B
1352 + method="config"
1353 elif [ ! -e "${NETDATA_CLAIM_PATH}" ]; then
1309 - err_msg="Unable to claim node: ${NETDATA_CLAIM_PATH} does not exist."
1310 - err_code=F0512
1354 + method="config"
1355 elif [ ! -f "${NETDATA_CLAIM_PATH}" ]; then
1356 err_msg="Unable to claim node: ${NETDATA_CLAIM_PATH} is not a file."
1357 err_code=F0513
1358 + elif grep -q '%%NEW_CLAIMING_METHOD%%' "${NETDATA_CLAIM_PATH}"; then
1359 + method="config"
1360 elif [ ! -x "${NETDATA_CLAIM_PATH}" ]; then
1361 err_msg="Unable to claim node: claiming script at ${NETDATA_CLAIM_PATH} is not executable. Reinstalling Netdata may resolve this."
1362 err_code=F0514
@@ -1326,34 +1372,16 @@ claim() {
1372 fi
1373
1374 if ! is_netdata_running; then
1329 - NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -daemon-not-running"
1375 + NETDATA_CLAIM_NORELOAD=1
1376 fi
1377
1332 - # shellcheck disable=SC2086
1333 - run_as_root "${NETDATA_CLAIM_PATH}" -token="${NETDATA_CLAIM_TOKEN}" -rooms="${NETDATA_CLAIM_ROOMS}" -url="${NETDATA_CLAIM_URL}" ${NETDATA_CLAIM_EXTRA}
1334 - case $? in
1335 - 0)
1336 - progress "Successfully claimed node"
1337 - return 0
1338 - ;;
1339 - 1) warning "Unable to claim node due to invalid claiming options. If you are seeing this message, you’ve probably found a bug and should open a bug report at ${AGENT_BUG_REPORT_URL}" ;;
1340 - 2) warning "Unable to claim node due to issues creating the claiming directory or preparing the local claiming key. Make sure you have a working openssl command and that ${INSTALL_PREFIX}/var/lib/netdata/cloud.d exists, then try again." ;;
1341 - 3) warning "Unable to claim node due to missing dependencies. Usually this means that the Netdata Agent was built without support for Netdata Cloud. If you built the agent from source, please install all needed dependencies for Cloud support. If you used the regular installation script and see this error, please file a bug report at ${AGENT_BUG_REPORT_URL}." ;;
1342 - 4) warning "Failed to claim node due to inability to connect to ${NETDATA_CLAIM_URL}. Usually this either means that the specified claiming URL is wrong, or that you are having networking problems." ;;
1343 - 5)
1344 - progress "Successfully claimed node, but was not able to notify the Netdata Agent. You will need to restart the Netdata service on this node before it will show up in the Cloud."
1345 - return 0
1378 + case ${method} in
1379 + script) run_claim_script ;;
1380 + config)
1381 + if ! write_claim_config; then
1382 + warning "Failed to write claiming configuration. This usually means you do not have permissions to access the configuration directory."
1383 + fi
1384 ;;
1347 - 8) warning "Failed to claim node due to an invalid agent ID. You can usually resolve this by removing ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restarting the agent. Then try to claim it again using the same options." ;;
1348 - 9) warning "Failed to claim node due to an invalid node name. This probably means you tried to specify a custom name for this node (for example, using the --claim-hostname option), but the hostname itself was either empty or consisted solely of whitespace. You can resolve this by specifying a valid host name and trying again." ;;
1349 - 10) warning "Failed to claim node due to an invalid room ID. This issue is most likely caused by a typo. Please check if the room(s) you are trying to add appear on the list of rooms provided to the --claim-rooms option ('${NETDATA_CLAIM_ROOMS}'). Then verify if the rooms are visible in Netdata Cloud and try again." ;;
1350 - 11) warning "Failed to claim node due to an issue with the generated RSA key pair. You can usually resolve this by removing all files in ${INSTALL_PREFIX}/var/lib/netdata/cloud.d and then trying again." ;;
1351 - 12) warning "Failed to claim node due to an invalid or expired claiming token. Please check that the token specified with the --claim-token option ('${NETDATA_CLAIM_TOKEN}') matches what you see in the Cloud and try again." ;;
1352 - 13) warning "Failed to claim node because the Cloud thinks it is already claimed. If this node was created by cloning a VM or as a container from a template, please remove the file ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restart the agent. Then try to claim it again with the same options. Otherwise, if you are certain this node has never been claimed before, you can use the --claim-id option to specify a new node ID to use for claiming, for example by using the uuidgen command like so: --claim-id \"\$(uuidgen)\"" ;;
1353 - 14) warning "Failed to claim node because the node is already in the process of being claimed. You should not need to do anything to resolve this, the node should show up properly in the Cloud soon. If it does not, please report a bug at ${AGENT_BUG_REPORT_URL}." ;;
1354 - 15|16|17) warning "Failed to claim node due to an internal server error in the Cloud. Please retry claiming this node later, and if you still see this message file a bug report at ${CLOUD_BUG_REPORT_URL}." ;;
1355 - 18) warning "Unable to claim node because this Netdata installation does not have a unique ID yet. Make sure the agent is running and started up correctly, and then try again." ;;
1356 - *) warning "Failed to claim node for an unknown reason. This usually means either networking problems or a bug. Please retry claiming later, and if you still see this message file a bug report at ${AGENT_BUG_REPORT_URL}" ;;
1385 esac
1386
1387 if [ "${ACTION}" = "claim" ]; then
@@ -1938,12 +1966,6 @@ build_and_install() {
1966 opts="${opts} --stable-channel"
1967 fi
1968
1941 - if [ "${NETDATA_REQUIRE_CLOUD}" -eq 1 ]; then
1942 - opts="${opts} --require-cloud"
1943 - elif [ "${NETDATA_DISABLE_CLOUD}" -eq 1 ]; then
1944 - opts="${opts} --disable-cloud"
1945 - fi
1946 -
1969 # shellcheck disable=SC2086
1970 run_script ./netdata-installer.sh ${opts}
1971
@@ -2392,12 +2414,10 @@ parse_args() {
2414 esac
2415 ;;
2416 "--disable-cloud")
2395 - NETDATA_DISABLE_CLOUD=1
2396 - NETDATA_REQUIRE_CLOUD=0
2417 + warning "Cloud cannot be disabled"
2418 ;;
2419 "--require-cloud")
2399 - NETDATA_DISABLE_CLOUD=0
2400 - NETDATA_REQUIRE_CLOUD=1
2420 + warning "Cloud is always required"
2421 ;;
2422 "--dont-start-it")
2423 NETDATA_NO_START=1
@@ -2447,26 +2467,21 @@ parse_args() {
2467 "--native-only") NETDATA_FORCE_METHOD="native" ;;
2468 "--static-only") NETDATA_FORCE_METHOD="static" ;;
2469 "--build-only") NETDATA_FORCE_METHOD="build" ;;
2450 - "--claim-token")
2451 - NETDATA_CLAIM_TOKEN="${2}"
2452 - shift 1
2453 - ;;
2454 - "--claim-rooms")
2455 - NETDATA_CLAIM_ROOMS="${2}"
2456 - shift 1
2457 - ;;
2458 - "--claim-url")
2459 - NETDATA_CLAIM_URL="${2}"
2460 - shift 1
2461 - ;;
2470 "--claim-"*)
2471 optname="$(echo "${1}" | cut -d '-' -f 4-)"
2472 case "${optname}" in
2465 - id|proxy|user|hostname)
2473 + token) NETDATA_CLAIM_TOKEN="${2}"; shift 1 ;;
2474 + rooms) NETDATA_CLAIM_ROOMS="${2}"; shift 1 ;;
2475 + url) NETDATA_CLAIM_URL="${2}"; shift 1 ;;
2476 + proxy) NETDATA_CLAIM_PROXY="${2}"; shift 1 ;;
2477 + noproxy) NETDATA_CLAIM_PROXY="none" ;;
2478 + insecure) NETDATA_CLAIM_INSECURE=yes ;;
2479 + noreload) NETDATA_CLAIM_NORELOAD=1 ;;
2480 + id|user|hostname)
2481 NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -${optname}=${2}"
2482 shift 1
2483 ;;
2469 - verbose|insecure|noproxy|noreload|daemon-not-running) NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -${optname}" ;;
2484 + verbose|daemon-not-running) NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -${optname}" ;;
2485 *) warning "Ignoring unrecognized claiming option ${optname}" ;;
2486 esac
2487 ;;
packaging/installer/methods/freebsd.md
+1 -4
@@ -67,7 +67,7 @@ gunzip netdata*.tar.gz && tar xf netdata*.tar && rm -rf netdata*.tar
67 Install Netdata in `/opt/netdata`. If you want to enable automatic updates, add `--auto-update` or `-u` to install `netdata-updater` in `cron` (**need root permission**):
68
69 ```sh
70 -cd netdata-v* && ./netdata-installer.sh --install-prefix /opt && cp /opt/netdata/usr/sbin/netdata-claim.sh /usr/sbin/
70 +cd netdata-v* && ./netdata-installer.sh --install-prefix /opt
71 ```
72
73 You also need to enable the `netdata` service in `/etc/rc.conf`:
@@ -113,9 +113,6 @@ The `kickstart.sh` script accepts a number of optional parameters to control how
113 - `--native-only`: Only install if native binary packages are available.
114 - `--static-only`: Only install if a static build is available.
115 - `--build-only`: Only install using a local build.
116 -- `--disable-cloud`: For local builds, don’t build any of the cloud code at all. For native packages and static builds,
117 - use runtime configuration to disable cloud support.
118 -- `--require-cloud`: Only install if Netdata Cloud can be enabled. Overrides `--disable-cloud`.
116 - `--install-prefix`: Specify an installation prefix for local builds (by default, we use a sane prefix based on the type of system).
117 - `--install-version`: Specify the version of Netdata to install.
118 - `--old-install-prefix`: Specify the custom local build's installation prefix that should be removed.
packaging/installer/methods/kickstart.md
-4
@@ -245,10 +245,6 @@ By default, the kickstart script will provide a Netdata agent installation that
245 Specify a proxy to use when connecting to the cloud in the form of `http://[user:pass@]host:ip` for an HTTP(S) proxy. See [connecting through a proxy](/src/claim/README.md#connect-through-a-proxy) for details.
246 - `--claim-only`
247 If there is an existing install, only try to claim it without attempting to update it. If there is no existing install, install and claim Netdata normally.
248 -- `--require-cloud`
249 - Only install if Netdata Cloud can be enabled.
250 -- `--disable-cloud`
251 - For local builds, don’t build any of the Netdata Cloud code at all. For native packages and static builds, use runtime configuration to disable Netdata Cloud support.
248
249 ### anonymous telemetry
250
packaging/makeself/jobs/70-netdata-git.install.sh
-1
@@ -32,7 +32,6 @@ run ./netdata-installer.sh \
32 --dont-wait \
33 --dont-start-it \
34 --disable-exporting-mongodb \
35 - --require-cloud \
35 --use-system-protobuf \
36 --dont-scrub-cflags-even-though-it-may-break-things \
37 --one-time-build \
packaging/utils/compile-on-windows.sh new
+78
@@ -0,0 +1,78 @@
1 +#!/bin/sh
2 +
3 +# On MSYS2, install these dependencies to build netdata:
4 +install_dependencies() {
5 + pacman -S \
6 + git cmake ninja base-devel msys2-devel \
7 + libyaml-devel libzstd-devel libutil-linux libutil-linux-devel \
8 + mingw-w64-x86_64-toolchain mingw-w64-ucrt-x86_64-toolchain \
9 + mingw64/mingw-w64-x86_64-mold ucrt64/mingw-w64-ucrt-x86_64-mold \
10 + msys/gdb ucrt64/mingw-w64-ucrt-x86_64-gdb mingw64/mingw-w64-x86_64-gdb \
11 + msys/zlib-devel mingw64/mingw-w64-x86_64-zlib ucrt64/mingw-w64-ucrt-x86_64-zlib \
12 + msys/libuv-devel ucrt64/mingw-w64-ucrt-x86_64-libuv mingw64/mingw-w64-x86_64-libuv \
13 + liblz4-devel mingw64/mingw-w64-x86_64-lz4 ucrt64/mingw-w64-ucrt-x86_64-lz4 \
14 + openssl-devel mingw64/mingw-w64-x86_64-openssl ucrt64/mingw-w64-ucrt-x86_64-openssl \
15 + protobuf-devel mingw64/mingw-w64-x86_64-protobuf ucrt64/mingw-w64-ucrt-x86_64-protobuf \
16 + msys/pcre2-devel mingw64/mingw-w64-x86_64-pcre2 ucrt64/mingw-w64-ucrt-x86_64-pcre2 \
17 + msys/brotli-devel mingw64/mingw-w64-x86_64-brotli ucrt64/mingw-w64-ucrt-x86_64-brotli \
18 + msys/ccache ucrt64/mingw-w64-ucrt-x86_64-ccache mingw64/mingw-w64-x86_64-ccache \
19 + mingw64/mingw-w64-x86_64-go ucrt64/mingw-w64-ucrt-x86_64-go \
20 + mingw64/mingw-w64-x86_64-nsis \
21 + msys/libcurl msys/libcurl-devel
22 +}
23 +
24 +if [ "${1}" = "install" ]
25 +then
26 + install_dependencies || exit 1
27 + exit 0
28 +fi
29 +
30 +BUILD_FOR_PACKAGING="Off"
31 +if [ "${1}" = "package" ]
32 +then
33 + BUILD_FOR_PACKAGING="On"
34 +fi
35 +
36 +export PATH="/usr/local/bin:${PATH}"
37 +
38 +WT_ROOT="$(pwd)"
39 +BUILD_TYPE="Debug"
40 +NULL=""
41 +
42 +if [ -z "${MSYSTEM}" ]; then
43 + build="${WT_ROOT}/build-${OSTYPE}"
44 +else
45 + build="${WT_ROOT}/build-${OSTYPE}-${MSYSTEM}"
46 +fi
47 +
48 +if [ "$USER" = "vk" ]; then
49 + build="${WT_ROOT}/build"
50 +fi
51 +
52 +set -exu -o pipefail
53 +
54 +if [ -d "${build}" ]
55 +then
56 + rm -rf "${build}"
57 +fi
58 +
59 +/usr/bin/cmake -S "${WT_ROOT}" -B "${build}" \
60 + -G Ninja \
61 + -DCMAKE_INSTALL_PREFIX="/opt/netdata" \
62 + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
63 + -DCMAKE_C_FLAGS="-fstack-protector-all -O0 -ggdb -Wall -Wextra -Wno-char-subscripts -Wa,-mbig-obj -pipe -DNETDATA_INTERNAL_CHECKS=1 -D_FILE_OFFSET_BITS=64 -D__USE_MINGW_ANSI_STDIO=1" \
64 + -DBUILD_FOR_PACKAGING=${BUILD_FOR_PACKAGING} \
65 + -DUSE_MOLD=Off \
66 + -DNETDATA_USER="${USER}" \
67 + -DDEFAULT_FEATURE_STATE=Off \
68 + -DENABLE_H2O=Off \
69 + -DENABLE_ML=On \
70 + -DENABLE_BUNDLED_JSONC=On \
71 + -DENABLE_BUNDLED_PROTOBUF=Off \
72 + ${NULL}
73 +
74 +ninja -v -C "${build}" install || ninja -v -C "${build}" -j 1
75 +
76 +echo
77 +echo "Compile with:"
78 +echo "ninja -v -C \"${build}\" install || ninja -v -C \"${build}\" -j 1"
packaging/windows/msys2-dependencies.sh
+12 -10
@@ -15,11 +15,23 @@ pacman -S --noconfirm --needed \
15 base-devel \
16 cmake \
17 git \
18 + ninja \
19 + python \
20 liblz4-devel \
21 libutil-linux \
22 libutil-linux-devel \
23 libyaml-devel \
24 libzstd-devel \
25 + msys2-devel \
26 + msys/brotli-devel \
27 + msys/libuv-devel \
28 + msys/pcre2-devel \
29 + msys/zlib-devel \
30 + msys/libcurl-devel \
31 + openssl-devel \
32 + protobuf-devel \
33 + mingw-w64-x86_64-toolchain \
34 + mingw-w64-ucrt-x86_64-toolchain \
35 mingw64/mingw-w64-x86_64-brotli \
36 mingw64/mingw-w64-x86_64-go \
37 mingw64/mingw-w64-x86_64-libuv \
@@ -29,16 +41,6 @@ pacman -S --noconfirm --needed \
41 mingw64/mingw-w64-x86_64-pcre2 \
42 mingw64/mingw-w64-x86_64-protobuf \
43 mingw64/mingw-w64-x86_64-zlib \
32 - mingw-w64-ucrt-x86_64-toolchain \
33 - mingw-w64-x86_64-toolchain \
34 - msys2-devel \
35 - msys/brotli-devel \
36 - msys/libuv-devel \
37 - msys/pcre2-devel \
38 - msys/zlib-devel \
39 - openssl-devel \
40 - protobuf-devel \
41 - python \
44 ucrt64/mingw-w64-ucrt-x86_64-brotli \
45 ucrt64/mingw-w64-ucrt-x86_64-go \
46 ucrt64/mingw-w64-ucrt-x86_64-libuv \
src/aclk/README.md
+2 -91
@@ -28,18 +28,10 @@ However, to be able to offer the stunning visualizations and advanced functional
28
29 ## Enable and configure the ACLK
30
31 -The ACLK is enabled by default, with its settings automatically configured and stored in the Agent's memory. No file is
32 -created at `/var/lib/netdata/cloud.d/cloud.conf` until you either connect a node or create it yourself. The default
33 -configuration uses two settings:
34 -
35 -```conf
36 -[global]
37 - enabled = yes
38 - cloud base url = https://app.netdata.cloud
39 -```
31 +The ACLK is enabled by default, with its settings automatically configured and stored in the Agent's memory.
32
33 If your Agent needs to use a proxy to access the internet, you must [set up a proxy for
42 -connecting to cloud](/src/claim/README.md#connect-through-a-proxy).
34 +connecting to cloud](/src/claim/README.md).
35
36 You can configure following keys in the `netdata.conf` section `[cloud]`:
37 ```
@@ -50,84 +42,3 @@ You can configure following keys in the `netdata.conf` section `[cloud]`:
42
43 - `statistics` enables/disables ACLK related statistics and their charts. You can disable this to save some space in the database and slightly reduce memory usage of Netdata Agent.
44 - `query thread count` specifies the number of threads to process cloud queries. Increasing this setting is useful for nodes with many children (streaming), which can expect to handle more queries (and/or more complicated queries).
53 -
54 -## Disable the ACLK
55 -
56 -You have two options if you prefer to disable the ACLK and not use Netdata Cloud.
57 -
58 -### Disable at installation
59 -
60 -You can pass the `--disable-cloud` parameter to the Agent installation when using a kickstart script
61 -([kickstart.sh](/packaging/installer/methods/kickstart.md), or a [manual installation from
62 -Git](/packaging/installer/methods/manual.md).
63 -
64 -When you pass this parameter, the installer does not download or compile any extra libraries. Once running, the Agent
65 -kills the thread responsible for the ACLK and connecting behavior, and behaves as though the ACLK, and thus Netdata Cloud,
66 -does not exist.
67 -
68 -### Disable at runtime
69 -
70 -You can change a runtime setting in your `cloud.conf` file to disable the ACLK. This setting only stops the Agent from
71 -attempting any connection via the ACLK, but does not prevent the installer from downloading and compiling the ACLK's
72 -dependencies.
73 -
74 -The file typically exists at `/var/lib/netdata/cloud.d/cloud.conf`, but can change if you set a prefix during
75 -installation. To disable the ACLK, open that file and change the `enabled` setting to `no`:
76 -
77 -```conf
78 -[global]
79 - enabled = no
80 -```
81 -
82 -If the file at `/var/lib/netdata/cloud.d/cloud.conf` doesn't exist, you need to create it.
83 -
84 -Copy and paste the first two lines from below, which will change your prompt to `cat`.
85 -
86 -```bash
87 -cd /var/lib/netdata/cloud.d
88 -cat > cloud.conf << EOF
89 -```
90 -
91 -Copy and paste in lines 3-6, and after the final `EOF`, hit **Enter**. The final line must contain only `EOF`. Hit **Enter** again to return to your normal prompt with the newly-created file.
92 -
93 -To get your normal prompt back, the final line
94 -must contain only `EOF`.
95 -
96 -```bash
97 -[global]
98 - enabled = no
99 - cloud base url = https://app.netdata.cloud
100 -EOF
101 -```
102 -
103 -You also need to change the file's permissions. Use `grep "run as user" /etc/netdata/netdata.conf` to figure out which
104 -user your Agent runs as (typically `netdata`), and replace `netdata:netdata` as shown below if necessary:
105 -
106 -```bash
107 -sudo chmod 0770 cloud.conf
108 -sudo chown netdata:netdata cloud.conf
109 -```
110 -
111 -Restart your Agent to disable the ACLK.
112 -
113 -### Re-enable the ACLK
114 -
115 -If you first disable the ACLK and any Cloud functionality and then decide you would like to use Cloud, you must either
116 -[reinstall Netdata](/packaging/installer/REINSTALL.md) with Cloud enabled or change the runtime setting in your
117 -`cloud.conf` file.
118 -
119 -If you passed `--disable-cloud` to `netdata-installer.sh` during installation, you must
120 -[reinstall](/packaging/installer/REINSTALL.md) your Agent. Use the same method as before, but pass `--require-cloud` to
121 -the installer. When installation finishes you can [connect your node](/src/claim/README.md#how-to-connect-a-node).
122 -
123 -If you changed the runtime setting in your `var/lib/netdata/cloud.d/cloud.conf` file, edit the file again and change
124 -`enabled` to `yes`:
125 -
126 -```conf
127 -[global]
128 - enabled = yes
129 -```
130 -
131 -Restart your Agent and [connect your node](/src/claim/README.md#how-to-connect-a-node).
132 -
133 -
src/aclk/aclk.c
+74 -90
@@ -2,7 +2,6 @@
2
3 #include "aclk.h"
4
5 -#ifdef ENABLE_ACLK
5 #include "aclk_stats.h"
6 #include "mqtt_websockets/mqtt_wss_client.h"
7 #include "aclk_otp.h"
@@ -14,7 +13,6 @@
13 #include "https_client.h"
14 #include "schema-wrappers/schema_wrappers.h"
15 #include "aclk_capas.h"
17 -
16 #include "aclk_proxy.h"
17
18 #ifdef ACLK_LOG_CONVERSATION_DIR
@@ -25,14 +23,35 @@
23
24 #define ACLK_STABLE_TIMEOUT 3 // Minimum delay to mark AGENT as stable
25
28 -#endif /* ENABLE_ACLK */
29 -
26 int aclk_pubacks_per_conn = 0; // How many PubAcks we got since MQTT conn est.
27 int aclk_rcvd_cloud_msgs = 0;
28 int aclk_connection_counter = 0;
29 int disconnect_req = 0;
30
35 -int aclk_connected = 0;
31 +static bool aclk_connected = false;
32 +static inline void aclk_set_connected(void) {
33 + __atomic_store_n(&aclk_connected, true, __ATOMIC_RELAXED);
34 +}
35 +static inline void aclk_set_disconnected(void) {
36 + __atomic_store_n(&aclk_connected, false, __ATOMIC_RELAXED);
37 +}
38 +
39 +inline bool aclk_online(void) {
40 + return __atomic_load_n(&aclk_connected, __ATOMIC_RELAXED);
41 +}
42 +
43 +bool aclk_online_for_contexts(void) {
44 + return aclk_online() && aclk_query_scope_has(HTTP_ACL_METRICS);
45 +}
46 +
47 +bool aclk_online_for_alerts(void) {
48 + return aclk_online() && aclk_query_scope_has(HTTP_ACL_ALERTS);
49 +}
50 +
51 +bool aclk_online_for_nodes(void) {
52 + return aclk_online() && aclk_query_scope_has(HTTP_ACL_NODES);
53 +}
54 +
55 int aclk_ctx_based = 0;
56 int aclk_disable_runtime = 0;
57 int aclk_stats_enabled;
@@ -49,7 +68,6 @@ float last_backoff_value = 0;
68
69 time_t aclk_block_until = 0;
70
52 -#ifdef ENABLE_ACLK
71 mqtt_wss_client mqttwss_client;
72
73 //netdata_mutex_t aclk_shared_state_mutex = NETDATA_MUTEX_INITIALIZER;
@@ -152,19 +170,6 @@ biofailed:
170 return 1;
171 }
172
155 -static int wait_till_cloud_enabled()
156 -{
157 - nd_log(NDLS_DAEMON, NDLP_INFO,
158 - "Waiting for Cloud to be enabled");
159 -
160 - while (!netdata_cloud_enabled) {
161 - sleep_usec(USEC_PER_SEC * 1);
162 - if (!service_running(SERVICE_ACLK))
163 - return 1;
164 - }
165 - return 0;
166 -}
167 -
173 /**
174 * Will block until agent is claimed. Returns only if agent claimed
175 * or if agent needs to shutdown.
@@ -175,14 +180,13 @@ static int wait_till_cloud_enabled()
180 static int wait_till_agent_claimed(void)
181 {
182 //TODO prevent malloc and freez
178 - char *agent_id = get_agent_claimid();
179 - while (likely(!agent_id)) {
183 + ND_UUID uuid = claim_id_get_uuid();
184 + while (likely(UUIDiszero(uuid))) {
185 sleep_usec(USEC_PER_SEC * 1);
186 if (!service_running(SERVICE_ACLK))
187 return 1;
183 - agent_id = get_agent_claimid();
188 + uuid = claim_id_get_uuid();
189 }
185 - freez(agent_id);
190 return 0;
191 }
192
@@ -204,7 +208,7 @@ static int wait_till_agent_claim_ready()
208
209 // The NULL return means the value was never initialised, but this value has been initialized in post_conf_load.
210 // We trap the impossible NULL here to keep the linter happy without using a fatal() in the code.
207 - char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL);
211 + const char *cloud_base_url = cloud_config_url_get();
212 if (cloud_base_url == NULL) {
213 netdata_log_error("Do not move the cloud base url out of post_conf_load!!");
214 return 1;
@@ -387,7 +391,7 @@ static inline void mqtt_connected_actions(mqtt_wss_client client)
391 mqtt_wss_subscribe(client, topic, 1);
392
393 aclk_stats_upd_online(1);
390 - aclk_connected = 1;
394 + aclk_set_connected();
395 aclk_pubacks_per_conn = 0;
396 aclk_rcvd_cloud_msgs = 0;
397 aclk_connection_counter++;
@@ -427,7 +431,7 @@ void aclk_graceful_disconnect(mqtt_wss_client client)
431
432 aclk_stats_upd_online(0);
433 last_disconnect_time = now_realtime_sec();
430 - aclk_connected = 0;
434 + aclk_set_disconnected();
435
436 nd_log(NDLS_DAEMON, NDLP_DEBUG,
437 "Attempting to gracefully shutdown the MQTT/WSS connection");
@@ -601,7 +605,7 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
605 #endif
606
607 while (service_running(SERVICE_ACLK)) {
604 - aclk_cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL);
608 + aclk_cloud_base_url = cloud_config_url_get();
609 if (aclk_cloud_base_url == NULL) {
610 error_report("Do not move the cloud base url out of post_conf_load!!");
611 aclk_status = ACLK_STATUS_NO_CLOUD_URL;
@@ -817,18 +821,8 @@ void *aclk_main(void *ptr)
821
822 unsigned int proto_hdl_cnt = aclk_init_rx_msg_handlers();
823
820 -#if defined( DISABLE_CLOUD ) || !defined( ENABLE_ACLK )
821 - nd_log(NDLS_DAEMON, NDLP_INFO,
822 - "Killing ACLK thread -> cloud functionality has been disabled");
823 -
824 - static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
825 - return NULL;
826 -#endif
824 query_threads.count = read_query_thread_count();
825
829 - if (wait_till_cloud_enabled())
830 - goto exit;
831 -
826 if (wait_till_agent_claim_ready())
827 goto exit;
828
@@ -875,7 +869,7 @@ void *aclk_main(void *ptr)
869 if (handle_connection(mqttwss_client)) {
870 aclk_stats_upd_online(0);
871 last_disconnect_time = now_realtime_sec();
878 - aclk_connected = 0;
872 + aclk_set_disconnected();
873 nd_log(NDLS_ACCESS, NDLP_WARNING, "ACLK DISCONNECTED");
874 }
875 } while (service_running(SERVICE_ACLK));
@@ -914,11 +908,11 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable)
908 nd_uuid_t node_id;
909 int ret = 0;
910
917 - if (!aclk_connected)
911 + if (!aclk_online())
912 return;
913
920 - if (host->node_id && !uuid_is_null(*host->node_id)) {
921 - uuid_copy(node_id, *host->node_id);
914 + if (!uuid_is_null(host->node_id)) {
915 + uuid_copy(node_id, host->node_id);
916 }
917 else {
918 ret = get_node_id(&host->host_uuid, &node_id);
@@ -931,15 +925,17 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable)
925 // node_id not found
926 aclk_query_t create_query;
927 create_query = aclk_query_new(REGISTER_NODE);
934 - rrdhost_aclk_state_lock(localhost);
928 + CLAIM_ID claim_id = claim_id_get();
929 +
930 node_instance_creation_t node_instance_creation = {
936 - .claim_id = localhost->aclk_state.claimed_id,
931 + .claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL,
932 .hops = host->system_info->hops,
933 .hostname = rrdhost_hostname(host),
934 .machine_guid = host->machine_guid};
935 +
936 create_query->data.bin_payload.payload =
937 generate_node_instance_creation(&create_query->data.bin_payload.size, &node_instance_creation);
942 - rrdhost_aclk_state_unlock(localhost);
938 +
939 create_query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE;
940 create_query->data.bin_payload.msg_name = "CreateNodeInstance";
941 nd_log(NDLS_DAEMON, NDLP_DEBUG,
@@ -962,10 +958,9 @@ void aclk_host_state_update(RRDHOST *host, int cmd, int queryable)
958
959 node_state_update.capabilities = aclk_get_agent_capas();
960
965 - rrdhost_aclk_state_lock(localhost);
966 - node_state_update.claim_id = localhost->aclk_state.claimed_id;
961 + CLAIM_ID claim_id = claim_id_get();
962 + node_state_update.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL;
963 query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update);
968 - rrdhost_aclk_state_unlock(localhost);
964
965 nd_log(NDLS_DAEMON, NDLP_DEBUG,
966 "Queuing status update for node=%s, live=%d, hops=%u, queryable=%d",
@@ -1007,10 +1002,9 @@ void aclk_send_node_instances()
1002 }
1003 node_state_update.capabilities = aclk_get_node_instance_capas(host);
1004
1010 - rrdhost_aclk_state_lock(localhost);
1011 - node_state_update.claim_id = localhost->aclk_state.claimed_id;
1005 + CLAIM_ID claim_id = claim_id_get();
1006 + node_state_update.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL;
1007 query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update);
1013 - rrdhost_aclk_state_unlock(localhost);
1008
1009 nd_log(NDLS_DAEMON, NDLP_DEBUG,
1010 "Queuing status update for node=%s, live=%d, hops=%d, queryable=1",
@@ -1032,10 +1026,10 @@ void aclk_send_node_instances()
1026 uuid_unparse_lower(list->host_id, (char*)node_instance_creation.machine_guid);
1027 create_query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE;
1028 create_query->data.bin_payload.msg_name = "CreateNodeInstance";
1035 - rrdhost_aclk_state_lock(localhost);
1036 - node_instance_creation.claim_id = localhost->aclk_state.claimed_id,
1029 +
1030 + CLAIM_ID claim_id = claim_id_get();
1031 + node_instance_creation.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL,
1032 create_query->data.bin_payload.payload = generate_node_instance_creation(&create_query->data.bin_payload.size, &node_instance_creation);
1038 - rrdhost_aclk_state_unlock(localhost);
1033
1034 nd_log(NDLS_DAEMON, NDLP_DEBUG,
1035 "Queuing registration for host=%s, hops=%d",
@@ -1087,16 +1081,15 @@ char *aclk_state(void)
1081 );
1082 buffer_sprintf(wb, "Protocol Used: Protobuf\nMQTT Version: %d\nClaimed: ", 5);
1083
1090 - char *agent_id = get_agent_claimid();
1091 - if (agent_id == NULL)
1084 + CLAIM_ID claim_id = claim_id_get();
1085 + if (!claim_id_is_set(claim_id))
1086 buffer_strcat(wb, "No\n");
1087 else {
1094 - char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL);
1095 - buffer_sprintf(wb, "Yes\nClaimed Id: %s\nCloud URL: %s\n", agent_id, cloud_base_url ? cloud_base_url : "null");
1096 - freez(agent_id);
1088 + const char *cloud_base_url = cloud_config_url_get();
1089 + buffer_sprintf(wb, "Yes\nClaimed Id: %s\nCloud URL: %s\n", claim_id.str, cloud_base_url ? cloud_base_url : "null");
1090 }
1091
1099 - buffer_sprintf(wb, "Online: %s\nReconnect count: %d\nBanned By Cloud: %s\n", aclk_connected ? "Yes" : "No", aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0, aclk_disable_runtime ? "Yes" : "No");
1092 + buffer_sprintf(wb, "Online: %s\nReconnect count: %d\nBanned By Cloud: %s\n", aclk_online() ? "Yes" : "No", aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0, aclk_disable_runtime ? "Yes" : "No");
1093 if (last_conn_time_mqtt && (tmptr = localtime_r(&last_conn_time_mqtt, &tmbuf)) ) {
1094 char timebuf[26];
1095 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
@@ -1112,13 +1105,13 @@ char *aclk_state(void)
1105 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
1106 buffer_sprintf(wb, "Last Disconnect Time: %s\n", timebuf);
1107 }
1115 - if (!aclk_connected && next_connection_attempt && (tmptr = localtime_r(&next_connection_attempt, &tmbuf)) ) {
1108 + if (!aclk_online() && next_connection_attempt && (tmptr = localtime_r(&next_connection_attempt, &tmbuf)) ) {
1109 char timebuf[26];
1110 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
1111 buffer_sprintf(wb, "Next Connection Attempt At: %s\nLast Backoff: %.3f", timebuf, last_backoff_value);
1112 }
1113
1121 - if (aclk_connected) {
1114 + if (aclk_online()) {
1115 buffer_sprintf(wb, "Received Cloud MQTT Messages: %d\nMQTT Messages Confirmed by Remote Broker (PUBACKs): %d", aclk_rcvd_cloud_msgs, aclk_pubacks_per_conn);
1116
1117 RRDHOST *host;
@@ -1127,19 +1120,17 @@ char *aclk_state(void)
1120 buffer_sprintf(wb, "\n\n> Node Instance for mGUID: \"%s\" hostname \"%s\"\n", host->machine_guid, rrdhost_hostname(host));
1121
1122 buffer_strcat(wb, "\tClaimed ID: ");
1130 - rrdhost_aclk_state_lock(host);
1131 - if (host->aclk_state.claimed_id)
1132 - buffer_strcat(wb, host->aclk_state.claimed_id);
1123 + claim_id = rrdhost_claim_id_get(host);
1124 + if(claim_id_is_set(claim_id))
1125 + buffer_strcat(wb, claim_id.str);
1126 else
1127 buffer_strcat(wb, "null");
1135 - rrdhost_aclk_state_unlock(host);
1128
1137 -
1138 - if (host->node_id == NULL || uuid_is_null(*host->node_id)) {
1129 + if (uuid_is_null(host->node_id))
1130 buffer_strcat(wb, "\n\tNode ID: null\n");
1140 - } else {
1131 + else {
1132 char node_id[GUID_LEN + 1];
1142 - uuid_unparse_lower(*host->node_id, node_id);
1133 + uuid_unparse_lower(host->node_id, node_id);
1134 buffer_sprintf(wb, "\n\tNode ID: %s\n", node_id);
1135 }
1136
@@ -1204,22 +1195,21 @@ char *aclk_state_json(void)
1195 json_object_array_add(grp, tmp);
1196 json_object_object_add(msg, "protocols-supported", grp);
1197
1207 - char *agent_id = get_agent_claimid();
1208 - tmp = json_object_new_boolean(agent_id != NULL);
1198 + CLAIM_ID claim_id = claim_id_get();
1199 + tmp = json_object_new_boolean(claim_id_is_set(claim_id));
1200 json_object_object_add(msg, "agent-claimed", tmp);
1201
1211 - if (agent_id) {
1212 - tmp = json_object_new_string(agent_id);
1213 - freez(agent_id);
1214 - } else
1202 + if (claim_id_is_set(claim_id))
1203 + tmp = json_object_new_string(claim_id.str);
1204 + else
1205 tmp = NULL;
1206 json_object_object_add(msg, "claimed-id", tmp);
1207
1218 - char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL);
1208 + const char *cloud_base_url = cloud_config_url_get();
1209 tmp = cloud_base_url ? json_object_new_string(cloud_base_url) : NULL;
1210 json_object_object_add(msg, "cloud-url", tmp);
1211
1222 - tmp = json_object_new_boolean(aclk_connected);
1212 + tmp = json_object_new_boolean(aclk_online());
1213 json_object_object_add(msg, "online", tmp);
1214
1215 tmp = json_object_new_string("Protobuf");
@@ -1240,9 +1230,9 @@ char *aclk_state_json(void)
1230 json_object_object_add(msg, "last-connect-time-utc", timestamp_to_json(&last_conn_time_mqtt));
1231 json_object_object_add(msg, "last-connect-time-puback-utc", timestamp_to_json(&last_conn_time_appl));
1232 json_object_object_add(msg, "last-disconnect-time-utc", timestamp_to_json(&last_disconnect_time));
1243 - json_object_object_add(msg, "next-connection-attempt-utc", !aclk_connected ? timestamp_to_json(&next_connection_attempt) : NULL);
1233 + json_object_object_add(msg, "next-connection-attempt-utc", !aclk_online() ? timestamp_to_json(&next_connection_attempt) : NULL);
1234 tmp = NULL;
1245 - if (!aclk_connected && last_backoff_value)
1235 + if (!aclk_online() && last_backoff_value)
1236 tmp = json_object_new_double(last_backoff_value);
1237 json_object_object_add(msg, "last-backoff-value", tmp);
1238
@@ -1262,19 +1252,18 @@ char *aclk_state_json(void)
1252 tmp = json_object_new_string(host->machine_guid);
1253 json_object_object_add(nodeinstance, "mguid", tmp);
1254
1265 - rrdhost_aclk_state_lock(host);
1266 - if (host->aclk_state.claimed_id) {
1267 - tmp = json_object_new_string(host->aclk_state.claimed_id);
1255 + claim_id = rrdhost_claim_id_get(host);
1256 + if(claim_id_is_set(claim_id)) {
1257 + tmp = json_object_new_string(claim_id.str);
1258 json_object_object_add(nodeinstance, "claimed_id", tmp);
1259 } else
1260 json_object_object_add(nodeinstance, "claimed_id", NULL);
1271 - rrdhost_aclk_state_unlock(host);
1261
1273 - if (host->node_id == NULL || uuid_is_null(*host->node_id)) {
1262 + if (uuid_is_null(host->node_id)) {
1263 json_object_object_add(nodeinstance, "node-id", NULL);
1264 } else {
1265 char node_id[GUID_LEN + 1];
1277 - uuid_unparse_lower(*host->node_id, node_id);
1266 + uuid_unparse_lower(host->node_id, node_id);
1267 tmp = json_object_new_string(node_id);
1268 json_object_object_add(nodeinstance, "node-id", tmp);
1269 }
@@ -1301,12 +1290,10 @@ char *aclk_state_json(void)
1290 json_object_put(msg);
1291 return str;
1292 }
1304 -#endif /* ENABLE_ACLK */
1293
1294 void add_aclk_host_labels(void) {
1295 RRDLABELS *labels = localhost->rrdlabels;
1296
1309 -#ifdef ENABLE_ACLK
1297 rrdlabels_add(labels, "_aclk_available", "true", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK);
1298 ACLK_PROXY_TYPE aclk_proxy;
1299 char *proxy_str;
@@ -1327,9 +1314,6 @@ void add_aclk_host_labels(void) {
1314 rrdlabels_add(labels, "_mqtt_version", "5", RRDLABEL_SRC_AUTO);
1315 rrdlabels_add(labels, "_aclk_proxy", proxy_str, RRDLABEL_SRC_AUTO);
1316 rrdlabels_add(labels, "_aclk_ng_new_cloud_protocol", "true", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK);
1330 -#else
1331 - rrdlabels_add(labels, "_aclk_available", "false", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK);
1332 -#endif
1317 }
1318
1319 void aclk_queue_node_info(RRDHOST *host, bool immediate)
src/aclk/aclk.h
+8 -6
@@ -4,14 +4,12 @@
4
5 #include "daemon/common.h"
6
7 -#ifdef ENABLE_ACLK
7 #include "aclk_util.h"
8 #include "aclk_rrdhost_state.h"
9
10 // How many MQTT PUBACKs we need to get to consider connection
11 // stable for the purposes of TBEB (truncated binary exponential backoff)
12 #define ACLK_PUBACKS_CONN_STABLE 3
14 -#endif /* ENABLE_ACLK */
13
14 typedef enum __attribute__((packed)) {
15 ACLK_STATUS_CONNECTED = 0,
@@ -39,12 +37,19 @@ extern ACLK_STATUS aclk_status;
37 extern const char *aclk_cloud_base_url;
38 const char *aclk_status_to_string(void);
39
42 -extern int aclk_connected;
40 extern int aclk_ctx_based;
41 extern int aclk_disable_runtime;
42 extern int aclk_stats_enabled;
43 extern int aclk_kill_link;
44
45 +bool aclk_online(void);
46 +bool aclk_online_for_contexts(void);
47 +bool aclk_online_for_alerts(void);
48 +bool aclk_online_for_nodes(void);
49 +
50 +void aclk_config_get_query_scope(void);
51 +bool aclk_query_scope_has(HTTP_ACL acl);
52 +
53 extern time_t last_conn_time_mqtt;
54 extern time_t last_conn_time_appl;
55 extern time_t last_disconnect_time;
@@ -59,7 +64,6 @@ extern time_t aclk_block_until;
64 extern int aclk_connection_counter;
65 extern int disconnect_req;
66
62 -#ifdef ENABLE_ACLK
67 void *aclk_main(void *ptr);
68
69 extern netdata_mutex_t aclk_shared_state_mutex;
@@ -80,8 +84,6 @@ void aclk_send_node_instances(void);
84
85 void aclk_send_bin_msg(char *msg, size_t msg_len, enum aclk_topics subtopic, const char *msgname);
86
83 -#endif /* ENABLE_ACLK */
84 -
87 char *aclk_state(void);
88 char *aclk_state_json(void);
89 void add_aclk_host_labels(void);
src/aclk/aclk_capas.c
+4
@@ -6,6 +6,10 @@
6
7 #define HTTP_API_V2_VERSION 6
8
9 +size_t aclk_get_http_api_version(void) {
10 + return HTTP_API_V2_VERSION;
11 +}
12 +
13 const struct capability *aclk_get_agent_capas()
14 {
15 static struct capability agent_capabilities[] = {
src/aclk/aclk_capas.h
+1
@@ -8,6 +8,7 @@
8
9 #include "schema-wrappers/capability.h"
10
11 +size_t aclk_get_http_api_version(void);
12 const struct capability *aclk_get_agent_capas();
13 struct capability *aclk_get_node_instance_capas(RRDHOST *host);
14
src/aclk/aclk_otp.c
+7 -14
@@ -488,16 +488,15 @@ int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_p
488 unsigned char *challenge = NULL;
489 int challenge_bytes;
490
491 - char *agent_id = get_agent_claimid();
492 - if (agent_id == NULL) {
491 + CLAIM_ID claim_id = claim_id_get();
492 + if (!claim_id_is_set(claim_id)) {
493 netdata_log_error("Agent was not claimed - cannot perform challenge/response");
494 return 1;
495 }
496
497 // Get Challenge
498 - if (aclk_get_otp_challenge(target, agent_id, &challenge, &challenge_bytes)) {
498 + if (aclk_get_otp_challenge(target, claim_id.str, &challenge, &challenge_bytes)) {
499 netdata_log_error("Error getting challenge");
500 - freez(agent_id);
500 return 1;
501 }
502
@@ -508,17 +507,15 @@ int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_p
507 netdata_log_error("Couldn't decrypt the challenge received");
508 freez(response_plaintext);
509 freez(challenge);
511 - freez(agent_id);
510 return 1;
511 }
512 freez(challenge);
513
514 // Encode and Send Challenge
515 struct auth_data data = { .client_id = NULL, .passwd = NULL, .username = NULL };
518 - if (aclk_send_otp_response(agent_id, response_plaintext, response_plaintext_bytes, target, &data)) {
516 + if (aclk_send_otp_response(claim_id.str, response_plaintext, response_plaintext_bytes, target, &data)) {
517 netdata_log_error("Error getting response");
518 freez(response_plaintext);
521 - freez(agent_id);
519 return 1;
520 }
521
@@ -527,7 +524,6 @@ int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_p
524 *mqtt_id = data.client_id;
525
526 freez(response_plaintext);
530 - freez(agent_id);
527 return 0;
528 }
529
@@ -831,17 +827,14 @@ int aclk_get_env(aclk_env_t *env, const char* aclk_hostname, int aclk_port) {
827
828 req.request_type = HTTP_REQ_GET;
829
834 - char *agent_id = get_agent_claimid();
835 - if (agent_id == NULL)
836 - {
830 + CLAIM_ID claim_id = claim_id_get();
831 + if (!claim_id_is_set(claim_id)) {
832 netdata_log_error("Agent was not claimed - cannot perform challenge/response");
833 buffer_free(buf);
834 return 1;
835 }
836
842 - buffer_sprintf(buf, "/api/v1/env?v=%s&cap=proto,ctx&claim_id=%s", &(NETDATA_VERSION[1]) /* skip 'v' at beginning */, agent_id);
843 -
844 - freez(agent_id);
837 + buffer_sprintf(buf, "/api/v1/env?v=%s&cap=proto,ctx&claim_id=%s", &(NETDATA_VERSION[1]) /* skip 'v' at beginning */, claim_id.str);
838
839 req.host = (char*)aclk_hostname;
840 req.port = aclk_port;
src/aclk/aclk_proxy.c
+4 -8
@@ -79,7 +79,7 @@ static inline int check_socks_enviroment(const char **proxy)
79 {
80 char *tmp = getenv("socks_proxy");
81
82 - if (!tmp)
82 + if (!tmp || !*tmp)
83 return 1;
84
85 if (aclk_verify_proxy(tmp) == PROXY_TYPE_SOCKS5) {
@@ -97,7 +97,7 @@ static inline int check_http_enviroment(const char **proxy)
97 {
98 char *tmp = getenv("http_proxy");
99
100 - if (!tmp)
100 + if (!tmp || !*tmp)
101 return 1;
102
103 if (aclk_verify_proxy(tmp) == PROXY_TYPE_HTTP) {
@@ -113,15 +113,11 @@ static inline int check_http_enviroment(const char **proxy)
113
114 const char *aclk_lws_wss_get_proxy_setting(ACLK_PROXY_TYPE *type)
115 {
116 - const char *proxy = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, ACLK_PROXY_CONFIG_VAR, ACLK_PROXY_ENV);
117 -
118 - // backward compatibility: "proxy" was in "netdata.conf"
119 - if (config_exists(CONFIG_SECTION_CLOUD, ACLK_PROXY_CONFIG_VAR))
120 - proxy = config_get(CONFIG_SECTION_CLOUD, ACLK_PROXY_CONFIG_VAR, ACLK_PROXY_ENV);
116 + const char *proxy = cloud_config_proxy_get();
117
118 *type = PROXY_DISABLED;
119
124 - if (strcmp(proxy, "none") == 0)
120 + if (!proxy || !*proxy || strcmp(proxy, "none") == 0)
121 return proxy;
122
123 if (strcmp(proxy, ACLK_PROXY_ENV) == 0) {
src/aclk/aclk_query.c
+13 -1
@@ -7,6 +7,8 @@
7
8 #define WEB_HDR_ACCEPT_ENC "Accept-Encoding:"
9
10 +static HTTP_ACL default_aclk_http_acl = HTTP_ACL_ALL_FEATURES;
11 +
12 pthread_cond_t query_cond_wait = PTHREAD_COND_INITIALIZER;
13 pthread_mutex_t query_lock_wait = PTHREAD_MUTEX_INITIALIZER;
14 #define QUERY_THREAD_LOCK pthread_mutex_lock(&query_lock_wait)
@@ -24,6 +26,16 @@ struct pending_req_list {
26 static struct pending_req_list *pending_req_list_head = NULL;
27 static pthread_mutex_t pending_req_list_lock = PTHREAD_MUTEX_INITIALIZER;
28
29 +void aclk_config_get_query_scope(void) {
30 + const char *s = config_get(CONFIG_SECTION_CLOUD, "scope", "full");
31 + if(strcmp(s, "license manager") == 0)
32 + default_aclk_http_acl = HTTP_ACL_ACLK_LICENSE_MANAGER;
33 +}
34 +
35 +bool aclk_query_scope_has(HTTP_ACL acl) {
36 + return (default_aclk_http_acl & acl) == acl;
37 +}
38 +
39 static struct pending_req_list *pending_req_list_add(const char *msg_id)
40 {
41 struct pending_req_list *new = callocz(1, sizeof(struct pending_req_list));
@@ -106,7 +118,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query)
118
119 struct web_client *w = web_client_get_from_cache();
120 web_client_set_conn_cloud(w);
109 - w->port_acl = HTTP_ACL_ACLK | HTTP_ACL_ALL_FEATURES;
121 + w->port_acl = HTTP_ACL_ACLK | default_aclk_http_acl;
122 w->acl = w->port_acl;
123 web_client_set_permissions(w, HTTP_ACCESS_MAP_OLD_MEMBER, HTTP_USER_ROLE_MEMBER, WEB_CLIENT_FLAG_AUTH_CLOUD);
124
src/aclk/aclk_rx_msgs.c
+3 -4
@@ -268,7 +268,7 @@ int create_node_instance_result(const char *msg, size_t msg_len)
268 freez(res.node_id);
269 return 1;
270 }
271 - update_node_id(&host_id, &node_id);
271 + sql_update_node_id(&host_id, &node_id);
272
273 aclk_query_t query = aclk_query_new(NODE_STATE_UPDATE);
274 node_instance_connection_t node_state_update = {
@@ -292,10 +292,9 @@ int create_node_instance_result(const char *msg, size_t msg_len)
292 node_state_update.capabilities = aclk_get_node_instance_capas(host);
293 }
294
295 - rrdhost_aclk_state_lock(localhost);
296 - node_state_update.claim_id = localhost->aclk_state.claimed_id;
295 + CLAIM_ID claim_id = claim_id_get();
296 + node_state_update.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL;
297 query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update);
298 - rrdhost_aclk_state_unlock(localhost);
298
299 freez((void *)node_state_update.capabilities);
300
src/aclk/aclk_tx_msgs.c
+13 -16
@@ -219,19 +219,19 @@ uint16_t aclk_send_agent_connection_update(mqtt_wss_client client, int reachable
219 .capabilities = aclk_get_agent_capas()
220 };
221
222 - rrdhost_aclk_state_lock(localhost);
223 - if (unlikely(!localhost->aclk_state.claimed_id)) {
222 + CLAIM_ID claim_id = claim_id_get();
223 + if (unlikely(!claim_id_is_set(claim_id))) {
224 netdata_log_error("Internal error. Should not come here if not claimed");
225 - rrdhost_aclk_state_unlock(localhost);
225 return 0;
226 }
228 - if (localhost->aclk_state.prev_claimed_id)
229 - conn.claim_id = localhost->aclk_state.prev_claimed_id;
227 +
228 + CLAIM_ID previous_claim_id = claim_id_get_last_working();
229 + if (claim_id_is_set(previous_claim_id))
230 + conn.claim_id = previous_claim_id.str;
231 else
231 - conn.claim_id = localhost->aclk_state.claimed_id;
232 + conn.claim_id = claim_id.str;
233
234 char *msg = generate_update_agent_connection(&len, &conn);
234 - rrdhost_aclk_state_unlock(localhost);
235
236 if (!msg) {
237 netdata_log_error("Error generating agent::v1::UpdateAgentConnection payload");
@@ -239,10 +239,9 @@ uint16_t aclk_send_agent_connection_update(mqtt_wss_client client, int reachable
239 }
240
241 pid = aclk_send_bin_message_subtopic_pid(client, msg, len, ACLK_TOPICID_AGENT_CONN, "UpdateAgentConnection");
242 - if (localhost->aclk_state.prev_claimed_id) {
243 - freez(localhost->aclk_state.prev_claimed_id);
244 - localhost->aclk_state.prev_claimed_id = NULL;
245 - }
242 + if (claim_id_is_set(previous_claim_id))
243 + claim_id_clear_previous_working();
244 +
245 return pid;
246 }
247
@@ -254,16 +253,14 @@ char *aclk_generate_lwt(size_t *size) {
253 .capabilities = NULL
254 };
255
257 - rrdhost_aclk_state_lock(localhost);
258 - if (unlikely(!localhost->aclk_state.claimed_id)) {
256 + CLAIM_ID claim_id = claim_id_get();
257 + if(!claim_id_is_set(claim_id)) {
258 netdata_log_error("Internal error. Should not come here if not claimed");
260 - rrdhost_aclk_state_unlock(localhost);
259 return NULL;
260 }
263 - conn.claim_id = localhost->aclk_state.claimed_id;
261 + conn.claim_id = claim_id.str;
262
263 char *msg = generate_update_agent_connection(size, &conn);
266 - rrdhost_aclk_state_unlock(localhost);
264
265 if (!msg)
266 netdata_log_error("Error generating agent::v1::UpdateAgentConnection payload for LWT");
src/aclk/aclk_util.c
+5 -10
@@ -2,8 +2,6 @@
2
3 #include "aclk_util.h"
4
5 -#ifdef ENABLE_ACLK
6 -
5 #include "aclk_proxy.h"
6
7 #include "daemon/common.h"
@@ -186,20 +184,18 @@ static void topic_generate_final(struct aclk_topic *t) {
184 if (!replace_tag)
185 return;
186
189 - rrdhost_aclk_state_lock(localhost);
190 - if (unlikely(!localhost->aclk_state.claimed_id)) {
187 + CLAIM_ID claim_id = claim_id_get();
188 + if (unlikely(!claim_id_is_set(claim_id))) {
189 netdata_log_error("This should never be called if agent not claimed");
192 - rrdhost_aclk_state_unlock(localhost);
190 return;
191 }
192
196 - t->topic = mallocz(strlen(t->topic_recvd) + 1 - strlen(CLAIM_ID_REPLACE_TAG) + strlen(localhost->aclk_state.claimed_id));
193 + t->topic = mallocz(strlen(t->topic_recvd) + 1 - strlen(CLAIM_ID_REPLACE_TAG) + strlen(claim_id.str));
194 memcpy(t->topic, t->topic_recvd, replace_tag - t->topic_recvd);
195 dest = t->topic + (replace_tag - t->topic_recvd);
196
200 - memcpy(dest, localhost->aclk_state.claimed_id, strlen(localhost->aclk_state.claimed_id));
201 - dest += strlen(localhost->aclk_state.claimed_id);
202 - rrdhost_aclk_state_unlock(localhost);
197 + memcpy(dest, claim_id.str, strlen(claim_id.str));
198 + dest += strlen(claim_id.str);
199 replace_tag += strlen(CLAIM_ID_REPLACE_TAG);
200 strcpy(dest, replace_tag);
201 dest += strlen(replace_tag);
@@ -440,7 +436,6 @@ void aclk_set_proxy(char **ohost, int *port, char **uname, char **pwd, enum mqtt
436
437 freez(proxy);
438 }
443 -#endif /* ENABLE_ACLK */
439
440 #if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110
441 static EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void)
src/aclk/aclk_util.h
-3
@@ -3,8 +3,6 @@
3 #define ACLK_UTIL_H
4
5 #include "libnetdata/libnetdata.h"
6 -
7 -#ifdef ENABLE_ACLK
6 #include "mqtt_websockets/mqtt_wss_client.h"
7
8 #define CLOUD_EC_MALFORMED_NODE_ID 1
@@ -114,7 +112,6 @@ unsigned long int aclk_tbeb_delay(int reset, int base, unsigned long int min, un
112 #define aclk_tbeb_reset(x) aclk_tbeb_delay(1, 0, 0, 0)
113
114 void aclk_set_proxy(char **ohost, int *port, char **uname, char **pwd, enum mqtt_wss_proxy_type *type);
117 -#endif /* ENABLE_ACLK */
115
116 int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len);
117
src/claim/README.md
+52 -165
@@ -11,8 +11,7 @@ features like centralized monitoring and easier collaboration.
11 There are two places in the UI where you can add/connect your Node:
12
13 - **Space/Room settings**: Click the cogwheel (the bottom-left corner or next to the Room name at the top) and
14 - select "Nodes." Click the "+" button to add
15 - a new node.
14 + select "Nodes." Click the "+" button to add a new node.
15 - [**Nodes tab**](/docs/dashboards-and-charts/nodes-tab.md): Click on the "Add nodes" button.
16
17 Netdata Cloud will generate a command that you can execute on your Node to install and claim the Agent. The command is
@@ -28,12 +27,13 @@ Once you've chosen your installation method, follow the provided instructions to
27
28 ### Connect an Existing Agent
29
31 -There are two methods to connect an already installed Netdata Agent to your Netdata Cloud Space:
30 +There are three methods to connect an already installed Netdata Agent to your Netdata Cloud Space:
31
33 -- using the Netdata Cloud user interface (UI).
34 -- using the claiming script.
32 +- Manually, via the UI
33 +- Automatically, via a provisioning system (or the command line)
34 +- Automatically, via environment variables (e.g. kubernetes, docker, etc)
35
36 -#### Using the UI (recommended)
36 +#### Manually, via the UI
37
38 The UI method is the easiest and recommended way to connect your Agent. Here's how:
39
@@ -42,36 +42,54 @@ The UI method is the easiest and recommended way to connect your Agent. Here's h
42 3. Click the "Connect" button.
43 4. Follow the on-screen instructions to connect your Agent.
44
45 -#### Using claiming script
45 +#### Automatically, via a provisioning system or the command line
46
47 -You can connect an Agent by running
48 -the [netdata-claim.sh](https://github.com/netdata/netdata/blob/master/src/claim/netdata-claim.sh.in) script directly.
49 -You can either run it with root privileges using `sudo` or as the user running the Agent (typically `netdata`).
50 -
51 -The claiming script accepts options that control the connection process. You can specify these options using the
52 -following format:
47 +Netdata Agents can be connected to Netdata Cloud by creating the file `/etc/netdata/claim.conf`
48 +(or `/opt/netdata/etc/netdata/claim.conf` depending on your installation), like this:
49
50 ```bash
55 -netdata-claim.sh -OPTION=VALUE ...
51 +[global]
52 + url = The Netdata Cloud base URL (optional, defaults to `https://app.netdata.cloud`)
53 + token = The claiming token for your Netdata Cloud Space (required)
54 + rooms = A comma-separated list of Rooms to add the Agent to (optional)
55 + proxy = The URL of a proxy server to use for the connection, or none, or env (optional, defaults to env)
56 + insecure = Either yes or no (optional)
57 ```
58
58 -Claiming script options:
59 +- `proxy` can get anything libcurl accepts as proxy, or the keywords `none` and `env`. `none` or just empty disables proxy configuration, while `env` instructs libcurl to use the environment for determining proxy configuration (usually the environment variable `https_proxy`).
60 +- `insecure` is a boolean (either `yes`, or `no`) and when set to `yes` it instructs libcurl to disable host verification.
61
60 -| Option | Description | Required | Default value |
61 -|--------|--------------------------------------------------------------------|:--------:|:------------------------------------------------------|
62 -| token | The claiming token for your Netdata Cloud Space. | yes | |
63 -| rooms | A comma-separated list of Rooms to add the Agent to. | no | The Agent will be added to the "All nodes" Room only. |
64 -| id | The unique identifier of the Agent. | no | The Agent's MACHINE_GUID. |
65 -| proxy | The URL of a proxy server to use for the connection, if necessary. | no | |
66 -
67 -Example:
62 +example:
63
64 ```bash
70 -netdata-claim.sh -token=MYTOKEN1234567 -rooms=room1,room2
65 +[global]
66 + url = https://app.netdata.cloud
67 + token = NETDATA_CLOUD_SPACE_TOKEN
68 + rooms = ROOM_KEY1,ROOM_KEY2,ROOM_KEY3
69 + proxy = http://username:password@myproxy:8080
70 + insecure = no
71 ```
72
73 -This command connects the Agent and adds it to the "room1" and "room2" Rooms using your claiming token
74 -MYTOKEN1234567.
73 +If the agent is already running, you can either run `netdatacli reload-claiming-state` or restart the agent.
74 +Otherwise, the agent will be claimed when it starts.
75 +
76 +If claiming fails for whatever reason, daemon.log will log the reason (search for `CLAIM`),
77 +and also `http://ip:19999/api/v2/info` would also state the reason at the `cloud` section of the response.
78 +
79 +#### Automatically, via environment variables
80 +
81 +Netdata will use the following environment variables:
82 +
83 +- `NETDATA_CLAIM_URL`: The Netdata Cloud base URL (optional, defaults to `https://app.netdata.cloud`)
84 +- `NETDATA_CLAIM_TOKEN`: The claiming token for your Netdata Cloud Space (required)
85 +- `NETDATA_CLAIM_ROOMS`: A comma-separated list of Rooms to add the Agent to (optional)
86 +- `NETDATA_CLAIM_PROXY`: The URL of a proxy server to use for the connection (optional)
87 +- `NETDATA_EXTRA_CLAIM_OPTS`, may contain a space separated list of options. The option `-insecure` is the only currently used.
88 +
89 +The `NETDATA_CLAIM_TOKEN` alone is enough for triggering the claiming process.
90 +
91 +If claiming fails for whatever reason, daemon.log will log the reason (search for `CLAIM`),
92 +and also `http://ip:19999/api/v2/info` would also state the reason at the `cloud` section of the response.
93
94 ## Reconnect
95
@@ -84,19 +102,12 @@ cd /var/lib/netdata # Replace with your Netdata library directory, if not /var
102 sudo rm -rf cloud.d/
103 ```
104
105 +> IMPORTANT:<br/>
106 +> Keep in mind that the Agent will be **re-claimed automatically** if the environment variables or `claim.conf` exist when the agent is restarted.
107 +
108 This node no longer has access to the credentials it was used when connecting to Netdata Cloud via the ACLK. You will
109 still be able to see this node in your Rooms in an **unreachable** state.
110
90 -If you want to reconnect this node, you need to:
91 -
92 -1. Ensure that the `/var/lib/netdata/cloud.d` directory doesn't exist. In some installations, the path
93 - is `/opt/netdata/var/lib/netdata/cloud.d`
94 -2. Stop the Agent
95 -3. Ensure that the `uuidgen-runtime` package is installed. Run ```echo "$(uuidgen)"``` and validate you get back a UUID
96 -4. Copy the kickstart.sh command to add a node from your space and add to the end of it `--claim-id "$(uuidgen)"`. Run
97 - the command and look for the message `Node was successfully claimed.`
98 -5. Start the Agent
99 -
111 ### Docker based installations
112
113 To remove a node from you Space in Netdata Cloud, and connect it to another Space, follow these steps:
@@ -113,7 +124,6 @@ To remove a node from you Space in Netdata Cloud, and connect it to another Spac
124
125 ```bash
126 rm -rf /var/lib/netdata/cloud.d/
116 -
127 rm /var/lib/netdata/registry/netdata.public.unique.id
128 ```
129
@@ -123,7 +133,6 @@ To remove a node from you Space in Netdata Cloud, and connect it to another Spac
133
134 ```bash
135 docker stop CONTAINER_NAME
126 -
136 docker rm CONTAINER_NAME
137 ```
138
@@ -163,16 +172,9 @@ Only the administrators of a Space in Netdata Cloud can trigger this action.
172 If you're having trouble connecting a node, this may be because
173 the [ACLK](/src/aclk/README.md) cannot connect to Cloud.
174
166 -With the Netdata Agent running, visit `http://NODE:19999/api/v1/info` in your browser, replacing `NODE` with the IP
167 -address or hostname of your Agent. The returned JSON contains four keys that will be helpful to diagnose any issues you
168 -might be having with the ACLK or connection process.
169 -
170 -```
171 -"cloud-enabled"
172 -"cloud-available"
173 -"agent-claimed"
174 -"aclk-available"
175 -```
175 +With the Netdata Agent running, visit `http://NODE:19999/api/v2/info` in your browser, replacing `NODE` with the IP
176 +address or hostname of your Agent. The returned JSON contains a section called `cloud` with helpful information to
177 +diagnose any issues you might be having with the ACLK or connection process.
178
179 > **Note**
180 >
@@ -216,28 +218,12 @@ Failed to write new machine GUID. Please make sure you have rights to write to /
218 For a successful execution you will need to run the script with root privileges or run it with the user that is running
219 the Agent.
220
219 -### bash: netdata-claim.sh: command not found
220 -
221 -If you run the claiming script and see a `command not found` error, you either installed Netdata in a non-standard
222 -location or are using an unsupported package. If you installed Netdata in a non-standard path using
223 -the `--install-prefix` option, you need to update your `$PATH` or run `netdata-claim.sh` using the full path.
224 -
225 -For example, if you installed Netdata to `/opt/netdata`, use `/opt/netdata/bin/netdata-claim.sh` to run the claiming
226 -script.
227 -
228 -> **Note**
229 ->
230 -> If you are using an unsupported package, such as a third-party `.deb`/`.rpm` package provided by your distribution,
231 -> please remove that package and reinstall using
232 ->
233 -our [recommended kickstart script](/packaging/installer/methods/kickstart.md).
234 -
221 ### Connecting on older distributions (Ubuntu 14.04, Debian 8, CentOS 6)
222
223 If you're running an older Linux distribution or one that has reached EOL, such as Ubuntu 14.04 LTS, Debian 8, or CentOS
224 6, your Agent may not be able to securely connect to Netdata Cloud due to an outdated version of OpenSSL. These old
239 -versions of OpenSSL cannot perform [hostname validation](https://wiki.openssl.org/index.php/Hostname_validation), which
240 -helps securely encrypt SSL connections.
225 +versions of OpenSSL cannot perform [hostname validation](https://wiki.openssl.org/index.php/Hostname_validation),
226 +which helps securely encrypt SSL connections.
227
228 We recommend you reinstall Netdata with
229 a [static build](/packaging/installer/methods/kickstart.md#static-builds),
@@ -246,102 +232,3 @@ which uses an up-to-date version of OpenSSL with hostname validation enabled.
232 If you choose to continue using the outdated version of OpenSSL, your node will still connect to Netdata Cloud, albeit
233 with hostname verification disabled. Without verification, your Netdata Cloud connection could be vulnerable to
234 man-in-the-middle attacks.
249 -
250 -### cloud-enabled is false
251 -
252 -If `cloud-enabled` is `false`, you probably ran the installer with `--disable-cloud` option.
253 -
254 -Additionally, check that the `enabled` setting in `var/lib/netdata/cloud.d/cloud.conf` is set to `true`:
255 -
256 -```conf
257 -[global]
258 - enabled = true
259 -```
260 -
261 -To fix this issue, reinstall Netdata using
262 -your [preferred method](/packaging/installer/README.md) and do not add
263 -the `--disable-cloud` option.
264 -
265 -### cloud-available is false / ACLK Available: No
266 -
267 -If `cloud-available` is `false` after you verified Cloud is enabled in the previous step, the most likely issue is that
268 -Cloud features failed to build during installation.
269 -
270 -If Cloud features fail to build, the installer continues and finishes the process without Cloud functionality as opposed
271 -to failing the installation altogether.
272 -
273 -We do this to ensure the Agent will always finish installing.
274 -
275 -If you can't see an explicit error in the installer's output, you can run the installer with the `--require-cloud`
276 -option. This option causes the installation to fail if Cloud functionality can't be built and enabled, and the
277 -installer's output should give you more error details.
278 -
279 -You may see one of the following error messages during installation:
280 -
281 -- `Failed to build libmosquitto. The install process will continue, but you will not be able to connect this node to Netdata Cloud.`
282 -- `Unable to fetch sources for libmosquitto. The install process will continue, but you will not be able to connect this node to Netdata Cloud.`
283 -- `Failed to build libwebsockets. The install process will continue, but you may not be able to connect this node to Netdata Cloud.`
284 -- `Unable to fetch sources for libwebsockets. The install process will continue, but you may not be able to connect this node to Netdata Cloud.`
285 -- `Could not find cmake, which is required to build libwebsockets. The install process will continue, but you may not be able to connect this node to Netdata Cloud.`
286 -- `Could not find cmake, which is required to build JSON-C. The install process will continue, but Netdata Cloud support will be disabled.`
287 -- `Failed to build JSON-C. Netdata Cloud support will be disabled.`
288 -- `Unable to fetch sources for JSON-C. Netdata Cloud support will be disabled.`
289 -
290 -One common cause of the installer failing to build Cloud features is not having one of the following dependencies on
291 -your system: `cmake`, `json-c` and `OpenSSL`, including corresponding `devel` packages.
292 -
293 -You can also look for error messages in `/var/log/netdata/error.log`. Try one of the following two commands to search
294 -for ACLK-related errors.
295 -
296 -```bash
297 -less /var/log/netdata/error.log
298 -grep -i ACLK /var/log/netdata/error.log
299 -```
300 -
301 -If the installer's output does not help you enable Cloud features, contact us
302 -by [creating an issue on GitHub](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml&title=The+installer+failed+to+prepare+the+required+dependencies+for+Netdata+Cloud+functionality)
303 -with details about your system and relevant output from `error.log`.
304 -
305 -### agent-claimed is false / Claimed: No
306 -
307 -You must [connect your node](#connect).
308 -
309 -### aclk-available is false / Online: No
310 -
311 -If `aclk-available` is `false` and all other keys are `true`, your Agent is having trouble connecting to the Cloud
312 -through the ACLK. Please check your system's firewall.
313 -
314 -If your Agent needs to use a proxy to access the internet, you must set up a proxy for connecting.
315 -
316 -If you are certain firewall and proxy settings are not the issue, you should consult the Agent's `error.log`
317 -at `/var/log/netdata/error.log` and contact us
318 -by [creating an issue on GitHub](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml&title=ACLK-available-is-false)
319 -with details about your system and relevant output from `error.log`.
320 -
321 -## Connecting reference
322 -
323 -In the sections below, you can find reference material for the kickstart script, claiming script, connecting via the
324 -Agent's command line tool, and details about the files found in `cloud.d`.
325 -
326 -### The `cloud.conf` file
327 -
328 -This section defines how and whether your Agent connects to Netdata Cloud using
329 -the [Agent-Cloud link](/src/aclk/README.md)(ACLK).
330 -
331 -| setting | default | info |
332 -|:---------------|:----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------|
333 -| enabled | yes | Controls whether the ACLK is active. Set to no to prevent the Agent from connecting to Netdata Cloud. |
334 -| cloud base url | <https://app.netdata.cloud> | The URL for the Netdata Cloud web application. Typically, this should not be changed. |
335 -| proxy | env | Specifies the proxy setting for the ACLK. Options: none (no proxy), env (use environment's proxy), or a URL (e.g., `http://proxy.example.com:1080`). |
336 -
337 -### Connection directory
338 -
339 -Netdata stores the Agent's connection-related state in the Netdata library directory under `cloud.d`. For a default
340 -installation, this directory exists at `/var/lib/netdata/cloud.d`. The directory and its files should be owned by the
341 -user that runs the Agent, which is typically the `netdata` user.
342 -
343 -The `cloud.d/token` file should contain the claiming-token and the `cloud.d/rooms` file should contain the list of War
344 -Rooms you added that node to.
345 -
346 -The user can also put the Cloud endpoint's full certificate chain in `cloud.d/cloud_fullchain.pem` so that the Agent
347 -can trust the endpoint if necessary.
src/claim/claim-with-api.c new
+491
@@ -0,0 +1,491 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "claim.h"
4 +
5 +#include "registry/registry.h"
6 +
7 +#include <curl/curl.h>
8 +#include <openssl/evp.h>
9 +#include <openssl/pem.h>
10 +#include <openssl/err.h>
11 +
12 +static bool check_and_generate_certificates() {
13 + FILE *fp;
14 + EVP_PKEY *pkey = NULL;
15 + EVP_PKEY_CTX *pctx = NULL;
16 +
17 + CLEAN_CHAR_P *private_key_file = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "private.pem");
18 + CLEAN_CHAR_P *public_key_file = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "public.pem");
19 +
20 + // Check if private key exists
21 + fp = fopen(public_key_file, "r");
22 + if (fp) {
23 + fclose(fp);
24 + return true;
25 + }
26 +
27 + // Generate the RSA key
28 + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
29 + if (!pctx) {
30 + claim_agent_failure_reason_set("Cannot generate RSA key, EVP_PKEY_CTX_new_id() failed");
31 + return false;
32 + }
33 +
34 + if (EVP_PKEY_keygen_init(pctx) <= 0) {
35 + claim_agent_failure_reason_set("Cannot generate RSA key, EVP_PKEY_keygen_init() failed");
36 + EVP_PKEY_CTX_free(pctx);
37 + return false;
38 + }
39 +
40 + if (EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048) <= 0) {
41 + claim_agent_failure_reason_set("Cannot generate RSA key, EVP_PKEY_CTX_set_rsa_keygen_bits() failed");
42 + EVP_PKEY_CTX_free(pctx);
43 + return false;
44 + }
45 +
46 + if (EVP_PKEY_keygen(pctx, &pkey) <= 0) {
47 + claim_agent_failure_reason_set("Cannot generate RSA key, EVP_PKEY_keygen() failed");
48 + EVP_PKEY_CTX_free(pctx);
49 + return false;
50 + }
51 +
52 + EVP_PKEY_CTX_free(pctx);
53 +
54 + // Save private key
55 + fp = fopen(private_key_file, "wb");
56 + if (!fp || !PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) {
57 + claim_agent_failure_reason_set("Cannot write private key file: %s", private_key_file);
58 + if (fp) fclose(fp);
59 + EVP_PKEY_free(pkey);
60 + return false;
61 + }
62 + fclose(fp);
63 +
64 + // Save public key
65 + fp = fopen(public_key_file, "wb");
66 + if (!fp || !PEM_write_PUBKEY(fp, pkey)) {
67 + claim_agent_failure_reason_set("Cannot write public key file: %s", public_key_file);
68 + if (fp) fclose(fp);
69 + EVP_PKEY_free(pkey);
70 + return false;
71 + }
72 + fclose(fp);
73 +
74 + EVP_PKEY_free(pkey);
75 + return true;
76 +}
77 +
78 +static size_t response_write_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
79 + BUFFER *wb = stream;
80 + size_t real_size = size * nmemb;
81 +
82 + buffer_memcat(wb, ptr, real_size);
83 +
84 + return real_size;
85 +}
86 +
87 +static const char *curl_add_json_room(BUFFER *wb, const char *start, const char *end) {
88 + size_t len = end - start;
89 +
90 + // copy the item to an new buffer and terminate it
91 + char buf[len + 1];
92 + memcpy(buf, start, len);
93 + buf[len] = '\0';
94 +
95 + // add it to the json array
96 + const char *trimmed = trim(buf); // remove leading and trailing spaces
97 + if(trimmed)
98 + buffer_json_add_array_item_string(wb, trimmed);
99 +
100 + // prepare for the next item
101 + start = end + 1;
102 +
103 + // skip multiple separators or spaces
104 + while(*start == ',' || *start == ' ') start++;
105 +
106 + return start;
107 +}
108 +
109 +void curl_add_rooms_json_array(BUFFER *wb, const char *rooms) {
110 + buffer_json_member_add_array(wb, "rooms");
111 + if(rooms && *rooms) {
112 + const char *start = rooms, *end = NULL;
113 +
114 + // Skip initial separators or spaces
115 + while (*start == ',' || *start == ' ')
116 + start++;
117 +
118 + // Process each item in the comma-separated list
119 + while ((end = strchr(start, ',')) != NULL)
120 + start = curl_add_json_room(wb, start, end);
121 +
122 + // Process the last item if any
123 + if (*start)
124 + curl_add_json_room(wb, start, &start[strlen(start)]);
125 + }
126 + buffer_json_array_close(wb);
127 +}
128 +
129 +static int debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
130 + (void)handle; // Unused
131 + (void)userptr; // Unused
132 +
133 + if (type == CURLINFO_TEXT)
134 + nd_log(NDLS_DAEMON, NDLP_INFO, "CLAIM: Info: %s", data);
135 + else if (type == CURLINFO_HEADER_OUT)
136 + nd_log(NDLS_DAEMON, NDLP_INFO, "CLAIM: Send header: %.*s", (int)size, data);
137 + else if (type == CURLINFO_DATA_OUT)
138 + nd_log(NDLS_DAEMON, NDLP_INFO, "CLAIM: Send data: %.*s", (int)size, data);
139 + else if (type == CURLINFO_SSL_DATA_OUT)
140 + nd_log(NDLS_DAEMON, NDLP_INFO, "CLAIM: Send SSL data: %.*s", (int)size, data);
141 + else if (type == CURLINFO_HEADER_IN)
142 + nd_log(NDLS_DAEMON, NDLP_INFO, "CLAIM: Receive header: %.*s", (int)size, data);
143 + else if (type == CURLINFO_DATA_IN)
144 + nd_log(NDLS_DAEMON, NDLP_INFO, "CLAIM: Receive data: %.*s", (int)size, data);
145 + else if (type == CURLINFO_SSL_DATA_IN)
146 + nd_log(NDLS_DAEMON, NDLP_INFO, "CLAIM: Receive SSL data: %.*s", (int)size, data);
147 +
148 + return 0;
149 +}
150 +
151 +static bool send_curl_request(const char *machine_guid, const char *hostname, const char *token, const char *rooms, const char *url, const char *proxy, int insecure, bool *can_retry) {
152 + CURL *curl;
153 + CURLcode res;
154 + char target_url[2048];
155 + char public_key[2048] = ""; // Adjust size as needed
156 + FILE *fp;
157 + struct curl_slist *headers = NULL;
158 +
159 + // create a new random claim id
160 + nd_uuid_t claimed_id;
161 + uuid_generate_random(claimed_id);
162 + char claimed_id_str[UUID_STR_LEN];
163 + uuid_unparse_lower(claimed_id, claimed_id_str);
164 +
165 + // generate the URL to post
166 + snprintf(target_url, sizeof(target_url), "%s%sapi/v1/spaces/nodes/%s",
167 + url, strendswith(url, "/") ? "" : "/", claimed_id_str);
168 +
169 + // Read the public key
170 + CLEAN_CHAR_P *public_key_file = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "public.pem");
171 + fp = fopen(public_key_file, "r");
172 + if (!fp || fread(public_key, 1, sizeof(public_key), fp) == 0) {
173 + claim_agent_failure_reason_set("cannot read public key file '%s'", public_key_file);
174 + if (fp) fclose(fp);
175 + *can_retry = false;
176 + return false;
177 + }
178 + fclose(fp);
179 +
180 + // check if we have trusted.pem
181 + // or cloud_fullchain.pem, for backwards compatibility
182 + CLEAN_CHAR_P *trusted_key_file = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "trusted.pem");
183 + fp = fopen(trusted_key_file, "r");
184 + if(fp)
185 + fclose(fp);
186 + else {
187 + freez(trusted_key_file);
188 + trusted_key_file = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "cloud_fullchain.pem");
189 + fp = fopen(trusted_key_file, "r");
190 + if(fp)
191 + fclose(fp);
192 + else {
193 + freez(trusted_key_file);
194 + trusted_key_file = NULL;
195 + }
196 + }
197 +
198 + // generate the JSON request message
199 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
200 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
201 +
202 + buffer_json_member_add_object(wb, "node");
203 + {
204 + buffer_json_member_add_string(wb, "id", claimed_id_str);
205 + buffer_json_member_add_string(wb, "hostname", hostname);
206 + }
207 + buffer_json_object_close(wb); // node
208 +
209 + buffer_json_member_add_string(wb, "token", token);
210 + curl_add_rooms_json_array(wb, rooms);
211 + buffer_json_member_add_string(wb, "publicKey", public_key);
212 + buffer_json_member_add_string(wb, "mGUID", machine_guid);
213 + buffer_json_finalize(wb);
214 +
215 + // initialize libcurl
216 + curl = curl_easy_init();
217 + if(!curl) {
218 + claim_agent_failure_reason_set("Cannot initialize request (curl_easy_init() failed)");
219 + *can_retry = true;
220 + return false;
221 + }
222 +
223 + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
224 + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, debug_callback);
225 +
226 + // we will receive the response in this
227 + CLEAN_BUFFER *response = buffer_create(0, NULL);
228 +
229 + // configure the request
230 + headers = curl_slist_append(headers, "Content-Type: application/json");
231 + curl_easy_setopt(curl, CURLOPT_URL, target_url);
232 + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
233 + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, buffer_tostring(wb));
234 + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
235 + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, response_write_callback);
236 + curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
237 +
238 + if(trusted_key_file)
239 + curl_easy_setopt(curl, CURLOPT_CAINFO, trusted_key_file);
240 +
241 + // Proxy configuration
242 + if (proxy) {
243 + if (!*proxy || strcmp(proxy, "none") == 0)
244 + // disable proxy configuration in libcurl
245 + curl_easy_setopt(curl, CURLOPT_PROXY, "");
246 +
247 + else if (strcmp(proxy, "env") != 0)
248 + // set the custom proxy for libcurl
249 + curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
250 +
251 + // otherwise, libcurl will use its own proxy environment variables
252 + }
253 +
254 + // Insecure option
255 + if (insecure) {
256 + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
257 + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
258 + }
259 +
260 + // Set timeout options
261 + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
262 + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
263 +
264 + // execute the request
265 + res = curl_easy_perform(curl);
266 + if (res != CURLE_OK) {
267 + claim_agent_failure_reason_set("Request failed with error: %s", curl_easy_strerror(res));
268 + curl_easy_cleanup(curl);
269 + *can_retry = true;
270 + return false;
271 + }
272 +
273 + // Get HTTP response code
274 + long http_status_code;
275 + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status_code);
276 +
277 + bool ret = false;
278 + if(http_status_code == 204) {
279 + if(!cloud_conf_regenerate(claimed_id_str, machine_guid, hostname, token, rooms, url, proxy, insecure)) {
280 + claim_agent_failure_reason_set("Failed to save claiming info to disk");
281 + }
282 + else {
283 + claim_agent_failure_reason_set(NULL);
284 + ret = true;
285 + }
286 +
287 + *can_retry = false;
288 + }
289 + else if (http_status_code == 422) {
290 + if(buffer_strlen(response)) {
291 + struct json_object *parsed_json;
292 + struct json_object *error_key_obj;
293 + const char *error_key = NULL;
294 +
295 + parsed_json = json_tokener_parse(buffer_tostring(response));
296 + if(parsed_json) {
297 + if (json_object_object_get_ex(parsed_json, "errorMsgKey", &error_key_obj))
298 + error_key = json_object_get_string(error_key_obj);
299 +
300 + if (strcmp(error_key, "ErrInvalidNodeID") == 0)
301 + claim_agent_failure_reason_set("Failed: the node id is invalid");
302 + else if (strcmp(error_key, "ErrInvalidNodeName") == 0)
303 + claim_agent_failure_reason_set("Failed: the node name is invalid");
304 + else if (strcmp(error_key, "ErrInvalidRoomID") == 0)
305 + claim_agent_failure_reason_set("Failed: one or more room ids are invalid");
306 + else if (strcmp(error_key, "ErrInvalidPublicKey") == 0)
307 + claim_agent_failure_reason_set("Failed: the public key is invalid");
308 + else
309 + claim_agent_failure_reason_set("Failed with description '%s'", error_key);
310 +
311 + json_object_put(parsed_json);
312 + }
313 + else
314 + claim_agent_failure_reason_set("Failed with a response code %ld", http_status_code);
315 + }
316 + else
317 + claim_agent_failure_reason_set("Failed with an empty response, code %ld", http_status_code);
318 +
319 + *can_retry = false;
320 + }
321 + else if(http_status_code == 102) {
322 + claim_agent_failure_reason_set("Claiming is in progress");
323 + *can_retry = false;
324 + }
325 + else if(http_status_code == 403) {
326 + claim_agent_failure_reason_set("Failed: token is expired, not found, or invalid");
327 + *can_retry = false;
328 + }
329 + else if(http_status_code == 409) {
330 + claim_agent_failure_reason_set("Failed: agent is already claimed");
331 + *can_retry = false;
332 + }
333 + else if(http_status_code == 500) {
334 + claim_agent_failure_reason_set("Failed: received Internal Server Error");
335 + *can_retry = true;
336 + }
337 + else if(http_status_code == 503) {
338 + claim_agent_failure_reason_set("Failed: Netdata Cloud is unavailable");
339 + *can_retry = true;
340 + }
341 + else if(http_status_code == 504) {
342 + claim_agent_failure_reason_set("Failed: Gateway Timeout");
343 + *can_retry = true;
344 + }
345 + else {
346 + claim_agent_failure_reason_set("Failed with response code %ld", http_status_code);
347 + *can_retry = true;
348 + }
349 +
350 + curl_easy_cleanup(curl);
351 + return ret;
352 +}
353 +
354 +bool claim_agent(const char *url, const char *token, const char *rooms, const char *proxy, bool insecure) {
355 + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
356 + spinlock_lock(&spinlock);
357 +
358 + if (!check_and_generate_certificates()) {
359 + spinlock_unlock(&spinlock);
360 + return false;
361 + }
362 +
363 + bool done = false, can_retry = true;
364 + size_t retries = 0;
365 + do {
366 + done = send_curl_request(registry_get_this_machine_guid(), registry_get_this_machine_hostname(), token, rooms, url, proxy, insecure, &can_retry);
367 + if (done) break;
368 + sleep_usec(300 * USEC_PER_MS + 100 * retries * USEC_PER_MS);
369 + retries++;
370 + } while(can_retry && retries < 5);
371 +
372 + spinlock_unlock(&spinlock);
373 + return done;
374 +}
375 +
376 +bool claim_agent_from_environment(void) {
377 + const char *url = getenv("NETDATA_CLAIM_URL");
378 + if(!url || !*url) {
379 + url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "url", DEFAULT_CLOUD_BASE_URL);
380 + if(!url || !*url) return false;
381 + }
382 +
383 + const char *token = getenv("NETDATA_CLAIM_TOKEN");
384 + if(!token || !*token)
385 + return false;
386 +
387 + const char *rooms = getenv("NETDATA_CLAIM_ROOMS");
388 + if(!rooms)
389 + rooms = "";
390 +
391 + const char *proxy = getenv("NETDATA_CLAIM_PROXY");
392 + if(!proxy || !*proxy)
393 + proxy = "";
394 +
395 + bool insecure = CONFIG_BOOLEAN_NO;
396 + const char *from_env = getenv("NETDATA_EXTRA_CLAIM_OPTS");
397 + if(from_env && *from_env && strstr(from_env, "-insecure") == 0)
398 + insecure = CONFIG_BOOLEAN_YES;
399 +
400 + return claim_agent(url, token, rooms, proxy, insecure);
401 +}
402 +
403 +bool claim_agent_from_claim_conf(void) {
404 + static struct config claim_config = {
405 + .first_section = NULL,
406 + .last_section = NULL,
407 + .mutex = NETDATA_MUTEX_INITIALIZER,
408 + .index = {
409 + .avl_tree = {
410 + .root = NULL,
411 + .compar = appconfig_section_compare
412 + },
413 + .rwlock = AVL_LOCK_INITIALIZER
414 + }
415 + };
416 + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
417 + bool ret = false;
418 +
419 + spinlock_lock(&spinlock);
420 +
421 + errno_clear();
422 + char *filename = filename_from_path_entry_strdupz(netdata_configured_user_config_dir, "claim.conf");
423 + bool loaded = appconfig_load(&claim_config, filename, 1, NULL);
424 + freez(filename);
425 +
426 + if(loaded) {
427 + const char *url = appconfig_get(&claim_config, CONFIG_SECTION_GLOBAL, "url", DEFAULT_CLOUD_BASE_URL);
428 + const char *token = appconfig_get(&claim_config, CONFIG_SECTION_GLOBAL, "token", "");
429 + const char *rooms = appconfig_get(&claim_config, CONFIG_SECTION_GLOBAL, "rooms", "");
430 + const char *proxy = appconfig_get(&claim_config, CONFIG_SECTION_GLOBAL, "proxy", "");
431 + bool insecure = appconfig_get_boolean(&claim_config, CONFIG_SECTION_GLOBAL, "insecure", CONFIG_BOOLEAN_NO);
432 +
433 + if(token && *token && url && *url)
434 + ret = claim_agent(url, token, rooms, proxy, insecure);
435 + }
436 +
437 + spinlock_unlock(&spinlock);
438 +
439 + return ret;
440 +}
441 +
442 +bool claim_agent_from_split_files(void) {
443 + char filename[FILENAME_MAX + 1];
444 +
445 + snprintfz(filename, sizeof(filename), "%s/token", netdata_configured_cloud_dir);
446 + long token_len = 0;
447 + char *token = read_by_filename(filename, &token_len);
448 + if(!token || !*token)
449 + return false;
450 +
451 + snprintfz(filename, sizeof(filename), "%s/rooms", netdata_configured_cloud_dir);
452 + long rooms_len = 0;
453 + char *rooms = read_by_filename(filename, &rooms_len);
454 + if(!rooms || !*rooms)
455 + rooms = NULL;
456 +
457 + bool ret = claim_agent(cloud_config_url_get(), token, rooms, cloud_config_proxy_get(), cloud_config_insecure_get());
458 +
459 + if(ret) {
460 + snprintfz(filename, sizeof(filename), "%s/token", netdata_configured_cloud_dir);
461 + unlink(filename);
462 +
463 + snprintfz(filename, sizeof(filename), "%s/rooms", netdata_configured_cloud_dir);
464 + unlink(filename);
465 + }
466 +
467 + return ret;
468 +}
469 +
470 +bool claim_agent_automatically(void) {
471 + // Use /etc/netdata/claim.conf
472 +
473 + if(claim_agent_from_claim_conf())
474 + return true;
475 +
476 + // Users may set NETDATA_CLAIM_TOKEN and NETDATA_CLAIM_ROOMS
477 + // A good choice for docker container users.
478 +
479 + if(claim_agent_from_environment())
480 + return true;
481 +
482 + // Users may store token and rooms in /var/lib/netdata/cloud.d
483 + // This was a bad choice, since users may have to create this directory
484 + // which may end up with the wrong permissions, preventing netdata from storing
485 + // the required information there.
486 +
487 + if(claim_agent_from_split_files())
488 + return true;
489 +
490 + return false;
491 +}
src/claim/claim.c
+147 -408
@@ -1,470 +1,209 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "claim.h"
4 -#include "registry/registry_internals.h"
5 -#include "aclk/aclk.h"
6 -#include "aclk/aclk_proxy.h"
7 -
8 -char *claiming_pending_arguments = NULL;
9 -
10 -static char *claiming_errors[] = {
11 - "Agent claimed successfully", // 0
12 - "Unknown argument", // 1
13 - "Problems with claiming working directory", // 2
14 - "Missing dependencies", // 3
15 - "Failure to connect to endpoint", // 4
16 - "The CLI didn't work", // 5
17 - "Wrong user", // 6
18 - "Unknown HTTP error message", // 7
19 - "invalid node id", // 8
20 - "invalid node name", // 9
21 - "invalid room id", // 10
22 - "invalid public key", // 11
23 - "token expired/token not found/invalid token", // 12
24 - "already claimed", // 13
25 - "processing claiming", // 14
26 - "Internal Server Error", // 15
27 - "Gateway Timeout", // 16
28 - "Service Unavailable", // 17
29 - "Agent Unique Id Not Readable" // 18
30 -};
31 -
32 -/* Retrieve the claim id for the agent.
33 - * Caller owns the string.
34 -*/
35 -char *get_agent_claimid()
36 -{
37 - char *result;
38 - rrdhost_aclk_state_lock(localhost);
39 - result = (localhost->aclk_state.claimed_id == NULL) ? NULL : strdupz(localhost->aclk_state.claimed_id);
40 - rrdhost_aclk_state_unlock(localhost);
41 - return result;
42 -}
43 -
44 -#define CLAIMING_COMMAND_LENGTH 16384
45 -#define CLAIMING_PROXY_LENGTH (CLAIMING_COMMAND_LENGTH/4)
4
47 -/* rrd_init() and post_conf_load() must have been called before this function */
48 -CLAIM_AGENT_RESPONSE claim_agent(const char *claiming_arguments, bool force, const char **msg __maybe_unused)
49 -{
50 - if (!force || !netdata_cloud_enabled) {
51 - netdata_log_error("Refusing to claim agent -> cloud functionality has been disabled");
52 - return CLAIM_AGENT_CLOUD_DISABLED;
53 - }
5 +// --------------------------------------------------------------------------------------------------------------------
6 +// keep track of the last claiming failure reason
7
55 -#ifndef DISABLE_CLOUD
56 - char command_exec_buffer[CLAIMING_COMMAND_LENGTH + 1];
57 - char command_line_buffer[CLAIMING_COMMAND_LENGTH + 1];
8 +static char cloud_claim_failure_reason[4096] = "";
9
59 - // This is guaranteed to be set early in main via post_conf_load()
60 - char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL);
61 - if (cloud_base_url == NULL) {
62 - internal_fatal(true, "Do not move the cloud base url out of post_conf_load!!");
63 - return CLAIM_AGENT_NO_CLOUD_URL;
10 +void claim_agent_failure_reason_set(const char *format, ...) {
11 + if(!format || !*format) {
12 + cloud_claim_failure_reason[0] = '\0';
13 + return;
14 }
15
66 - const char *proxy_str;
67 - ACLK_PROXY_TYPE proxy_type;
68 - char proxy_flag[CLAIMING_PROXY_LENGTH] = "-noproxy";
69 -
70 - proxy_str = aclk_get_proxy(&proxy_type);
71 -
72 - if (proxy_type == PROXY_TYPE_SOCKS5 || proxy_type == PROXY_TYPE_HTTP)
73 - snprintf(proxy_flag, CLAIMING_PROXY_LENGTH, "-proxy=\"%s\"", proxy_str);
74 -
75 - snprintfz(command_exec_buffer, CLAIMING_COMMAND_LENGTH,
76 - "exec \"%s%snetdata-claim.sh\"",
77 - netdata_exe_path[0] ? netdata_exe_path : "",
78 - netdata_exe_path[0] ? "/" : ""
79 - );
80 -
81 - snprintfz(command_line_buffer,
82 - CLAIMING_COMMAND_LENGTH,
83 - "%s %s -hostname=%s -id=%s -url=%s -noreload %s",
84 - command_exec_buffer,
85 - proxy_flag,
86 - netdata_configured_hostname,
87 - localhost->machine_guid,
88 - cloud_base_url,
89 - claiming_arguments);
90 -
91 - netdata_log_info("Executing agent claiming command: %s", command_exec_buffer);
92 - POPEN_INSTANCE *instance = spawn_popen_run(command_line_buffer);
93 - if(!instance) {
94 - netdata_log_error("Cannot popen(\"%s\").", command_exec_buffer);
95 - return CLAIM_AGENT_CANNOT_EXECUTE_CLAIM_SCRIPT;
96 - }
16 + va_list args;
17 + va_start(args, format);
18 + vsnprintf(cloud_claim_failure_reason, sizeof(cloud_claim_failure_reason), format, args);
19 + va_end(args);
20 +
21 + nd_log(NDLS_DAEMON, NDLP_ERR,
22 + "CLAIM: %s", cloud_claim_failure_reason);
23 +}
24
98 - netdata_log_info("Waiting for claiming command '%s' to finish.", command_exec_buffer);
99 - char read_buffer[100 + 1];
100 - while (fgets(read_buffer, 100, instance->child_stdout_fp) != NULL) ;
25 +const char *claim_agent_failure_reason_get(void) {
26 + if(!cloud_claim_failure_reason[0])
27 + return "Agent is not claimed yet";
28 + else
29 + return cloud_claim_failure_reason;
30 +}
31
102 - int exit_code = spawn_popen_wait(instance);
32 +// --------------------------------------------------------------------------------------------------------------------
33 +// claimed_id load/save
34
104 - netdata_log_info("Agent claiming command '%s' returned with code %d", command_exec_buffer, exit_code);
105 - if (0 == exit_code) {
106 - load_claiming_state();
107 - return CLAIM_AGENT_OK;
108 - }
109 - if (exit_code < 0) {
110 - netdata_log_error("Agent claiming command '%s' failed to complete its run", command_exec_buffer);
111 - return CLAIM_AGENT_CLAIM_SCRIPT_FAILED;
35 +bool claimed_id_save_to_file(const char *claimed_id_str) {
36 + bool ret;
37 + const char *filename = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "claimed_id");
38 + FILE *fp = fopen(filename, "w");
39 + if(fp) {
40 + fprintf(fp, "%s", claimed_id_str);
41 + fclose(fp);
42 + ret = true;
43 }
113 - errno_clear();
114 - unsigned maximum_known_exit_code = sizeof(claiming_errors) / sizeof(claiming_errors[0]) - 1;
115 -
116 - if ((unsigned)exit_code > maximum_known_exit_code) {
117 - netdata_log_error("Agent failed to be claimed with an unknown error. Cmd: '%s'", command_exec_buffer);
118 - return CLAIM_AGENT_CLAIM_SCRIPT_RETURNED_INVALID_CODE;
44 + else {
45 + nd_log(NDLS_DAEMON, NDLP_ERR,
46 + "CLAIM: cannot open file '%s' for writing.", filename);
47 + ret = false;
48 }
49
121 - netdata_log_error("Agent failed to be claimed using the command '%s' with the following error message: %s",
122 - command_exec_buffer, claiming_errors[exit_code]);
50 + freez((void *)filename);
51 + return ret;
52 +}
53
124 - if(msg) *msg = claiming_errors[exit_code];
54 +static ND_UUID claimed_id_parse(const char *claimed_id, const char *source) {
55 + ND_UUID uuid;
56
126 -#else
127 - UNUSED(claiming_arguments);
128 - UNUSED(claiming_errors);
129 -#endif
57 + if(uuid_parse_flexi(claimed_id, uuid.uuid) != 0) {
58 + uuid = UUID_ZERO;
59 + nd_log(NDLS_DAEMON, NDLP_ERR,
60 + "CLAIM: claimed_id '%s' (loaded from '%s'), is not a valid UUID.",
61 + claimed_id, source);
62 + }
63
131 - return CLAIM_AGENT_FAILED_WITH_MESSAGE;
64 + return uuid;
65 }
66
134 -/* Change the claimed state of the agent.
135 - *
136 - * This only happens when the user has explicitly requested it:
137 - * - via the cli tool by reloading the claiming state
138 - * - after spawning the claim because of a command-line argument
139 - * If this happens with the ACLK active under an old claim then we MUST KILL THE LINK
140 - */
141 -void load_claiming_state(void)
142 -{
143 - // --------------------------------------------------------------------
144 - // Check if the cloud is enabled
145 -#if defined( DISABLE_CLOUD ) || !defined( ENABLE_ACLK )
146 - netdata_cloud_enabled = false;
147 -#else
148 - nd_uuid_t uuid;
149 -
150 - // Propagate into aclk and registry. Be kind of atomic...
151 - appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", DEFAULT_CLOUD_BASE_URL);
152 -
153 - rrdhost_aclk_state_lock(localhost);
154 - if (localhost->aclk_state.claimed_id) {
155 - if (aclk_connected)
156 - localhost->aclk_state.prev_claimed_id = strdupz(localhost->aclk_state.claimed_id);
157 - freez(localhost->aclk_state.claimed_id);
158 - localhost->aclk_state.claimed_id = NULL;
159 - }
160 - if (aclk_connected)
161 - {
162 - netdata_log_info("Agent was already connected to Cloud - forcing reconnection under new credentials");
163 - aclk_kill_link = 1;
164 - }
165 - aclk_disable_runtime = 0;
166 -
167 - char filename[FILENAME_MAX + 1];
168 - snprintfz(filename, FILENAME_MAX, "%s/cloud.d/claimed_id", netdata_configured_varlib_dir);
67 +static ND_UUID claimed_id_load_from_file(void) {
68 + ND_UUID uuid;
69
70 long bytes_read;
71 + const char *filename = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "claimed_id");
72 char *claimed_id = read_by_filename(filename, &bytes_read);
172 - if(claimed_id && uuid_parse(claimed_id, uuid)) {
173 - netdata_log_error("claimed_id \"%s\" doesn't look like valid UUID", claimed_id);
174 - freez(claimed_id);
175 - claimed_id = NULL;
176 - }
177 -
178 - if(claimed_id) {
179 - localhost->aclk_state.claimed_id = mallocz(UUID_STR_LEN);
180 - uuid_unparse_lower(uuid, localhost->aclk_state.claimed_id);
181 - }
182 -
183 - rrdhost_aclk_state_unlock(localhost);
184 - invalidate_node_instances(&localhost->host_uuid, claimed_id ? &uuid : NULL);
185 - metaqueue_store_claim_id(&localhost->host_uuid, claimed_id ? &uuid : NULL);
73
187 - if (!claimed_id) {
188 - netdata_log_info("Unable to load '%s', setting state to AGENT_UNCLAIMED", filename);
189 - return;
190 - }
74 + if(!claimed_id)
75 + uuid = UUID_ZERO;
76 + else
77 + uuid = claimed_id_parse(claimed_id, filename);
78
79 freez(claimed_id);
193 -
194 - netdata_log_info("File '%s' was found. Setting state to AGENT_CLAIMED.", filename);
195 - netdata_cloud_enabled = appconfig_get_boolean_ondemand(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", netdata_cloud_enabled);
196 -#endif
197 -}
198 -
199 -struct config cloud_config = { .first_section = NULL,
200 - .last_section = NULL,
201 - .mutex = NETDATA_MUTEX_INITIALIZER,
202 - .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare },
203 - .rwlock = AVL_LOCK_INITIALIZER } };
204 -
205 -void load_cloud_conf(int silent)
206 -{
207 - char *nd_disable_cloud = getenv("NETDATA_DISABLE_CLOUD");
208 - if (nd_disable_cloud && !strncmp(nd_disable_cloud, "1", 1))
209 - netdata_cloud_enabled = CONFIG_BOOLEAN_NO;
210 -
211 - char *filename;
212 - errno_clear();
213 -
214 - int ret = 0;
215 -
216 - filename = strdupz_path_subpath(netdata_configured_varlib_dir, "cloud.d/cloud.conf");
217 -
218 - ret = appconfig_load(&cloud_config, filename, 1, NULL);
219 - if(!ret && !silent)
220 - netdata_log_info("CONFIG: cannot load cloud config '%s'. Running with internal defaults.", filename);
221 -
222 - freez(filename);
223 -
224 - // --------------------------------------------------------------------
225 - // Check if the cloud is enabled
226 -
227 -#if defined( DISABLE_CLOUD ) || !defined( ENABLE_ACLK )
228 - netdata_cloud_enabled = CONFIG_BOOLEAN_NO;
229 -#else
230 - netdata_cloud_enabled = appconfig_get_boolean_ondemand(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", netdata_cloud_enabled);
231 -#endif
232 -
233 - // This must be set before any point in the code that accesses it. Do not move it from this function.
234 - appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", DEFAULT_CLOUD_BASE_URL);
80 + freez((void *)filename);
81 + return uuid;
82 }
83
237 -static char *netdata_random_session_id_filename = NULL;
238 -static nd_uuid_t netdata_random_session_id = { 0 };
239 -
240 -bool netdata_random_session_id_generate(void) {
241 - static char guid[UUID_STR_LEN] = "";
242 -
243 - uuid_generate_random(netdata_random_session_id);
244 - uuid_unparse_lower(netdata_random_session_id, guid);
245 -
246 - char filename[FILENAME_MAX + 1];
247 - snprintfz(filename, FILENAME_MAX, "%s/netdata_random_session_id", netdata_configured_varlib_dir);
248 -
249 - bool ret = true;
250 -
251 - (void)unlink(filename);
252 -
253 - // save it
254 - int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 640);
255 - if(fd == -1) {
256 - netdata_log_error("Cannot create random session id file '%s'.", filename);
257 - ret = false;
84 +static ND_UUID claimed_id_get_from_cloud_conf(void) {
85 + if(appconfig_exists(&cloud_config, CONFIG_SECTION_GLOBAL, "claimed_id")) {
86 + const char *claimed_id = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "claimed_id", "");
87 + if(claimed_id && *claimed_id)
88 + return claimed_id_parse(claimed_id, "cloud.conf");
89 }
259 - else {
260 - if (write(fd, guid, UUID_STR_LEN - 1) != UUID_STR_LEN - 1) {
261 - netdata_log_error("Cannot write the random session id file '%s'.", filename);
262 - ret = false;
263 - } else {
264 - ssize_t bytes = write(fd, "\n", 1);
265 - UNUSED(bytes);
266 - }
267 - close(fd);
268 - }
269 -
270 - if(ret && (!netdata_random_session_id_filename || strcmp(netdata_random_session_id_filename, filename) != 0)) {
271 - freez(netdata_random_session_id_filename);
272 - netdata_random_session_id_filename = strdupz(filename);
273 - }
274 -
275 - return ret;
90 + return UUID_ZERO;
91 }
92
278 -const char *netdata_random_session_id_get_filename(void) {
279 - if(!netdata_random_session_id_filename)
280 - netdata_random_session_id_generate();
93 +static ND_UUID claimed_id_load(void) {
94 + ND_UUID uuid = claimed_id_get_from_cloud_conf();
95 + if(UUIDiszero(uuid))
96 + uuid = claimed_id_load_from_file();
97
282 - return netdata_random_session_id_filename;
98 + return uuid;
99 }
100
285 -bool netdata_random_session_id_matches(const char *guid) {
286 - if(uuid_is_null(netdata_random_session_id))
287 - return false;
101 +bool is_agent_claimed(void) {
102 + ND_UUID uuid = claim_id_get_uuid();
103 + return !UUIDiszero(uuid);
104 +}
105
289 - nd_uuid_t uuid;
106 +// --------------------------------------------------------------------------------------------------------------------
107
291 - if(uuid_parse(guid, uuid))
108 +bool claim_id_matches(const char *claim_id) {
109 + ND_UUID this_one = UUID_ZERO;
110 + if(uuid_parse_flexi(claim_id, this_one.uuid) != 0 || UUIDiszero(this_one))
111 return false;
112
294 - if(uuid_compare(netdata_random_session_id, uuid) == 0)
113 + ND_UUID having = claim_id_get_uuid();
114 + if(!UUIDiszero(having) && UUIDeq(having, this_one))
115 return true;
116
117 return false;
118 }
119
300 -static bool check_claim_param(const char *s) {
301 - if(!s || !*s) return true;
120 +bool claim_id_matches_any(const char *claim_id) {
121 + ND_UUID this_one = UUID_ZERO;
122 + if(uuid_parse_flexi(claim_id, this_one.uuid) != 0 || UUIDiszero(this_one))
123 + return false;
124
303 - do {
304 - if(isalnum((uint8_t)*s) || *s == '.' || *s == ',' || *s == '-' || *s == ':' || *s == '/' || *s == '_')
305 - ;
306 - else
307 - return false;
125 + ND_UUID having = claim_id_get_uuid();
126 + if(!UUIDiszero(having) && UUIDeq(having, this_one))
127 + return true;
128
309 - } while(*++s);
129 + having = localhost->aclk.claim_id_of_parent;
130 + if(!UUIDiszero(having) && UUIDeq(having, this_one))
131 + return true;
132
311 - return true;
312 -}
133 + having = localhost->aclk.claim_id_of_origin;
134 + if(!UUIDiszero(having) && UUIDeq(having, this_one))
135 + return true;
136
314 -void claim_reload_all(void) {
315 - nd_log_limits_unlimited();
316 - load_claiming_state();
317 - registry_update_cloud_base_url();
318 - rrdpush_send_claimed_id(localhost);
319 - nd_log_limits_reset();
137 + return false;
138 }
139
322 -int api_v2_claim(struct web_client *w, char *url) {
323 - char *key = NULL;
324 - char *token = NULL;
325 - char *rooms = NULL;
326 - char *base_url = NULL;
327 -
328 - while (url) {
329 - char *value = strsep_skip_consecutive_separators(&url, "&");
330 - if (!value || !*value) continue;
331 -
332 - char *name = strsep_skip_consecutive_separators(&value, "=");
333 - if (!name || !*name) continue;
334 - if (!value || !*value) continue;
335 -
336 - if(!strcmp(name, "key"))
337 - key = value;
338 - else if(!strcmp(name, "token"))
339 - token = value;
340 - else if(!strcmp(name, "rooms"))
341 - rooms = value;
342 - else if(!strcmp(name, "url"))
343 - base_url = value;
140 +/* Change the claimed state of the agent.
141 + *
142 + * This only happens when the user has explicitly requested it:
143 + * - via the cli tool by reloading the claiming state
144 + * - after spawning the claim because of a command-line argument
145 + * If this happens with the ACLK active under an old claim then we MUST KILL THE LINK
146 + */
147 +bool load_claiming_state(void) {
148 + if (aclk_online()) {
149 + nd_log(NDLS_DAEMON, NDLP_ERR,
150 + "CLAIM: agent was already connected to NC - forcing reconnection under new credentials");
151 + aclk_kill_link = 1;
152 + }
153 + aclk_disable_runtime = 0;
154 +
155 + ND_UUID uuid = claimed_id_load();
156 + if(UUIDiszero(uuid)) {
157 + // not found
158 + if(claim_agent_automatically())
159 + uuid = claimed_id_load();
160 }
161
346 - BUFFER *wb = w->response.data;
347 - buffer_flush(wb);
348 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
349 -
350 - time_t now_s = now_realtime_sec();
351 - CLOUD_STATUS status = buffer_json_cloud_status(wb, now_s);
352 -
353 - bool can_be_claimed = false;
354 - switch(status) {
355 - case CLOUD_STATUS_AVAILABLE:
356 - case CLOUD_STATUS_DISABLED:
357 - case CLOUD_STATUS_OFFLINE:
358 - can_be_claimed = true;
359 - break;
360 -
361 - case CLOUD_STATUS_UNAVAILABLE:
362 - case CLOUD_STATUS_BANNED:
363 - case CLOUD_STATUS_ONLINE:
364 - can_be_claimed = false;
365 - break;
162 + bool have_claimed_id = false;
163 + if(!UUIDiszero(uuid)) {
164 + // we go it somehow
165 + claim_id_set(uuid);
166 + have_claimed_id = true;
167 }
168
368 - buffer_json_member_add_boolean(wb, "can_be_claimed", can_be_claimed);
369 -
370 - if(can_be_claimed && key) {
371 - if(!netdata_random_session_id_matches(key)) {
372 - buffer_reset(wb);
373 - buffer_strcat(wb, "invalid key");
374 - netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
375 - return HTTP_RESP_FORBIDDEN;
376 - }
377 -
378 - if(!token || !base_url || !check_claim_param(token) || !check_claim_param(base_url) || (rooms && !check_claim_param(rooms))) {
379 - buffer_reset(wb);
380 - buffer_strcat(wb, "invalid parameters");
381 - netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
382 - return HTTP_RESP_BAD_REQUEST;
383 - }
384 -
385 - netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
386 -
387 - netdata_cloud_enabled = CONFIG_BOOLEAN_AUTO;
388 - appconfig_set_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", CONFIG_BOOLEAN_AUTO);
389 - appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", base_url);
390 -
391 - nd_uuid_t claimed_id;
392 - uuid_generate_random(claimed_id);
393 - char claimed_id_str[UUID_STR_LEN];
394 - uuid_unparse_lower(claimed_id, claimed_id_str);
395 -
396 - BUFFER *t = buffer_create(1024, NULL);
397 - if(rooms)
398 - buffer_sprintf(t, "-id=%s -token=%s -rooms=%s", claimed_id_str, token, rooms);
399 - else
400 - buffer_sprintf(t, "-id=%s -token=%s", claimed_id_str, token);
401 -
402 - bool success = false;
403 - const char *msg = NULL;
404 - CLAIM_AGENT_RESPONSE rc = claim_agent(buffer_tostring(t), true, &msg);
405 - switch(rc) {
406 - case CLAIM_AGENT_OK:
407 - msg = "ok";
408 - success = true;
409 - can_be_claimed = false;
410 - claim_reload_all();
411 - {
412 - int ms = 0;
413 - do {
414 - status = cloud_status();
415 - if (status == CLOUD_STATUS_ONLINE && __atomic_load_n(&localhost->node_id, __ATOMIC_RELAXED))
416 - break;
417 -
418 - sleep_usec(50 * USEC_PER_MS);
419 - ms += 50;
420 - } while (ms < 10000);
421 - }
422 - break;
169 + invalidate_node_instances(&localhost->host_uuid, have_claimed_id ? &uuid.uuid : NULL);
170 + metaqueue_store_claim_id(&localhost->host_uuid, have_claimed_id ? &uuid.uuid : NULL);
171
424 - case CLAIM_AGENT_NO_CLOUD_URL:
425 - msg = "No Netdata Cloud URL.";
426 - break;
172 + errno_clear();
173
428 - case CLAIM_AGENT_CLAIM_SCRIPT_FAILED:
429 - msg = "Claiming script failed.";
430 - break;
174 + if (!have_claimed_id)
175 + nd_log(NDLS_DAEMON, NDLP_ERR,
176 + "CLAIM: Unable to find our claimed_id, setting state to AGENT_UNCLAIMED");
177 + else
178 + nd_log(NDLS_DAEMON, NDLP_INFO,
179 + "CLAIM: Found a valid claimed_id, setting state to AGENT_CLAIMED");
180
432 - case CLAIM_AGENT_CLOUD_DISABLED:
433 - msg = "Netdata Cloud is disabled on this agent.";
434 - break;
181 + return have_claimed_id;
182 +}
183
436 - case CLAIM_AGENT_CANNOT_EXECUTE_CLAIM_SCRIPT:
437 - msg = "Failed to execute claiming script.";
438 - break;
184 +CLOUD_STATUS claim_reload_and_wait_online(void) {
185 + nd_log(NDLS_DAEMON, NDLP_INFO,
186 + "CLAIM: Reloading Agent Claiming configuration.");
187
440 - case CLAIM_AGENT_CLAIM_SCRIPT_RETURNED_INVALID_CODE:
441 - msg = "Claiming script returned invalid code.";
442 - break;
188 + nd_log_limits_unlimited();
189 + cloud_conf_load(0);
190 + bool claimed = load_claiming_state();
191 + registry_update_cloud_base_url();
192 + rrdpush_sender_send_claimed_id(localhost);
193 + nd_log_limits_reset();
194
444 - default:
445 - case CLAIM_AGENT_FAILED_WITH_MESSAGE:
446 - if(!msg)
447 - msg = "Unknown error";
195 + CLOUD_STATUS status = cloud_status();
196 + if(claimed) {
197 + int ms = 0;
198 + do {
199 + status = cloud_status();
200 + if ((status == CLOUD_STATUS_ONLINE || status == CLOUD_STATUS_INDIRECT) && !uuid_is_null(localhost->host_uuid))
201 break;
449 - }
450 -
451 - // our status may have changed
452 - // refresh the status in our output
453 - buffer_flush(wb);
454 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
455 - now_s = now_realtime_sec();
456 - buffer_json_cloud_status(wb, now_s);
457 -
458 - // and this is the status of the claiming command we run
459 - buffer_json_member_add_boolean(wb, "success", success);
460 - buffer_json_member_add_string(wb, "message", msg);
461 - }
462 -
463 - if(can_be_claimed)
464 - buffer_json_member_add_string(wb, "key_filename", netdata_random_session_id_get_filename());
202
466 - buffer_json_agents_v2(wb, NULL, now_s, false, false);
467 - buffer_json_finalize(wb);
203 + sleep_usec(50 * USEC_PER_MS);
204 + ms += 50;
205 + } while (ms < 10000);
206 + }
207
469 - return HTTP_RESP_OK;
208 + return status;
209 }
src/claim/claim.h
+24 -21
@@ -4,29 +4,32 @@
4 #define NETDATA_CLAIM_H 1
5
6 #include "daemon/common.h"
7 +#include "cloud-status.h"
8 +#include "claim_id.h"
9 +
10 +const char *claim_agent_failure_reason_get(void);
11 +void claim_agent_failure_reason_set(const char *format, ...) PRINTFLIKE(1, 2);
12
8 -extern char *claiming_pending_arguments;
13 extern struct config cloud_config;
14
11 -typedef enum __attribute__((packed)) {
12 - CLAIM_AGENT_OK,
13 - CLAIM_AGENT_CLOUD_DISABLED,
14 - CLAIM_AGENT_NO_CLOUD_URL,
15 - CLAIM_AGENT_CANNOT_EXECUTE_CLAIM_SCRIPT,
16 - CLAIM_AGENT_CLAIM_SCRIPT_FAILED,
17 - CLAIM_AGENT_CLAIM_SCRIPT_RETURNED_INVALID_CODE,
18 - CLAIM_AGENT_FAILED_WITH_MESSAGE,
19 -} CLAIM_AGENT_RESPONSE;
20 -
21 -CLAIM_AGENT_RESPONSE claim_agent(const char *claiming_arguments, bool force, const char **msg);
22 -char *get_agent_claimid(void);
23 -void load_claiming_state(void);
24 -void load_cloud_conf(int silent);
25 -void claim_reload_all(void);
26 -
27 -bool netdata_random_session_id_generate(void);
28 -const char *netdata_random_session_id_get_filename(void);
29 -bool netdata_random_session_id_matches(const char *guid);
30 -int api_v2_claim(struct web_client *w, char *url);
15 +bool claim_agent(const char *url, const char *token, const char *rooms, const char *proxy, bool insecure);
16 +bool claim_agent_automatically(void);
17 +
18 +bool claimed_id_save_to_file(const char *claimed_id_str);
19 +
20 +bool is_agent_claimed(void);
21 +bool claim_id_matches(const char *claim_id);
22 +bool claim_id_matches_any(const char *claim_id);
23 +bool load_claiming_state(void);
24 +void cloud_conf_load(int silent);
25 +void cloud_conf_init_after_registry(void);
26 +bool cloud_conf_save(void);
27 +bool cloud_conf_regenerate(const char *claimed_id_str, const char *machine_guid, const char *hostname, const char *token, const char *rooms, const char *url, const char *proxy, int insecure);
28 +CLOUD_STATUS claim_reload_and_wait_online(void);
29 +
30 +const char *cloud_config_url_get(void);
31 +void cloud_config_url_set(const char *url);
32 +const char *cloud_config_proxy_get(void);
33 +bool cloud_config_insecure_get(void);
34
35 #endif //NETDATA_CLAIM_H
src/claim/claim_id.c new
+123
@@ -0,0 +1,123 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "claim_id.h"
4 +
5 +static struct {
6 + SPINLOCK spinlock;
7 + ND_UUID claim_uuid;
8 + ND_UUID claim_uuid_saved;
9 +} claim = {
10 + .spinlock = NETDATA_SPINLOCK_INITIALIZER,
11 +};
12 +
13 +void claim_id_clear_previous_working(void) {
14 + spinlock_lock(&claim.spinlock);
15 + claim.claim_uuid_saved = UUID_ZERO;
16 + spinlock_unlock(&claim.spinlock);
17 +}
18 +
19 +void claim_id_set(ND_UUID new_claim_id) {
20 + spinlock_lock(&claim.spinlock);
21 +
22 + if(!UUIDiszero(claim.claim_uuid)) {
23 + if(aclk_online())
24 + claim.claim_uuid_saved = claim.claim_uuid;
25 + claim.claim_uuid = UUID_ZERO;
26 + }
27 +
28 + claim.claim_uuid = new_claim_id;
29 + if(localhost)
30 + localhost->aclk.claim_id_of_origin = claim.claim_uuid;
31 +
32 + spinlock_unlock(&claim.spinlock);
33 +}
34 +
35 +// returns true when the supplied str is a valid UUID.
36 +// giving NULL, an empty string, or "NULL" is valid.
37 +bool claim_id_set_str(const char *claim_id_str) {
38 + bool rc;
39 +
40 + ND_UUID uuid;
41 + if(!claim_id_str || !*claim_id_str || strcmp(claim_id_str, "NULL") == 0) {
42 + uuid = UUID_ZERO,
43 + rc = true;
44 + }
45 + else
46 + rc = uuid_parse(claim_id_str, uuid.uuid) == 0;
47 +
48 + claim_id_set(uuid);
49 +
50 + return rc;
51 +}
52 +
53 +ND_UUID claim_id_get_uuid(void) {
54 + static ND_UUID uuid;
55 + spinlock_lock(&claim.spinlock);
56 + uuid = claim.claim_uuid;
57 + spinlock_unlock(&claim.spinlock);
58 + return uuid;
59 +}
60 +
61 +void claim_id_get_str(char str[UUID_STR_LEN]) {
62 + ND_UUID uuid = claim_id_get_uuid();
63 +
64 + if(UUIDiszero(uuid))
65 + memset(str, 0, UUID_STR_LEN);
66 + else
67 + uuid_unparse_lower(uuid.uuid, str);
68 +}
69 +
70 +const char *claim_id_get_str_mallocz(void) {
71 + char *str = mallocz(UUID_STR_LEN);
72 + claim_id_get_str(str);
73 + return str;
74 +}
75 +
76 +CLAIM_ID claim_id_get(void) {
77 + CLAIM_ID ret = {
78 + .uuid = claim_id_get_uuid(),
79 + };
80 +
81 + if(claim_id_is_set(ret))
82 + uuid_unparse_lower(ret.uuid.uuid, ret.str);
83 + else
84 + ret.str[0] = '\0';
85 +
86 + return ret;
87 +}
88 +
89 +CLAIM_ID claim_id_get_last_working(void) {
90 + CLAIM_ID ret = { 0 };
91 +
92 + spinlock_lock(&claim.spinlock);
93 + ret.uuid = claim.claim_uuid_saved;
94 + spinlock_unlock(&claim.spinlock);
95 +
96 + if(claim_id_is_set(ret))
97 + uuid_unparse_lower(ret.uuid.uuid, ret.str);
98 + else
99 + ret.str[0] = '\0';
100 +
101 + return ret;
102 +}
103 +
104 +CLAIM_ID rrdhost_claim_id_get(RRDHOST *host) {
105 + CLAIM_ID ret = { 0 };
106 +
107 + if(host == localhost) {
108 + ret.uuid = claim_id_get_uuid();
109 + if(UUIDiszero(ret.uuid))
110 + ret.uuid = host->aclk.claim_id_of_parent;
111 + }
112 + else {
113 + if (!UUIDiszero(host->aclk.claim_id_of_origin))
114 + ret.uuid = host->aclk.claim_id_of_origin;
115 + else
116 + ret.uuid = host->aclk.claim_id_of_parent;
117 + }
118 +
119 + if(claim_id_is_set(ret))
120 + uuid_unparse_lower(ret.uuid.uuid, ret.str);
121 +
122 + return ret;
123 +}
src/claim/claim_id.h new
+28
@@ -0,0 +1,28 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_CLAIM_ID_H
4 +#define NETDATA_CLAIM_ID_H
5 +
6 +#include "claim.h"
7 +
8 +void claim_id_keep_current(void);
9 +
10 +bool claim_id_set_str(const char *claim_id_str);
11 +void claim_id_set(ND_UUID new_claim_id);
12 +void claim_id_clear_previous_working(void);
13 +ND_UUID claim_id_get_uuid(void);
14 +void claim_id_get_str(char str[UUID_STR_LEN]);
15 +const char *claim_id_get_str_mallocz(void);
16 +
17 +typedef struct {
18 + ND_UUID uuid;
19 + char str[UUID_STR_LEN];
20 +} CLAIM_ID;
21 +
22 +#define claim_id_is_set(claim_id) (!UUIDiszero(claim_id.uuid))
23 +
24 +CLAIM_ID claim_id_get(void);
25 +CLAIM_ID claim_id_get_last_working(void);
26 +CLAIM_ID rrdhost_claim_id_get(RRDHOST *host);
27 +
28 +#endif //NETDATA_CLAIM_ID_H
src/claim/cloud-conf.c new
+128
@@ -0,0 +1,128 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "claim.h"
4 +
5 +struct config cloud_config = {
6 + .first_section = NULL,
7 + .last_section = NULL,
8 + .mutex = NETDATA_MUTEX_INITIALIZER,
9 + .index = {
10 + .avl_tree = {
11 + .root = NULL,
12 + .compar = appconfig_section_compare
13 + },
14 + .rwlock = AVL_LOCK_INITIALIZER
15 + }
16 +};
17 +
18 +const char *cloud_config_url_get(void) {
19 + return appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "url", DEFAULT_CLOUD_BASE_URL);
20 +}
21 +
22 +void cloud_config_url_set(const char *url) {
23 + if(!url || *url) return;
24 +
25 + const char *existing = cloud_config_url_get();
26 + if(strcmp(existing, url) != 0)
27 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "url", url);
28 +}
29 +
30 +const char *cloud_config_proxy_get(void) {
31 + // load cloud.conf or internal default
32 + const char *proxy = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "proxy", "env");
33 +
34 + // backwards compatibility, from when proxy was in netdata.conf
35 + // netdata.conf has bigger priority
36 + if (config_exists(CONFIG_SECTION_CLOUD, "proxy")) {
37 + // get it from netdata.conf
38 + proxy = config_get(CONFIG_SECTION_CLOUD, "proxy", proxy);
39 +
40 + // update cloud.conf
41 + proxy = appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "proxy", proxy);
42 + }
43 + else {
44 + // set in netdata.conf the proxy of cloud.conf
45 + config_set(CONFIG_SECTION_CLOUD, "proxy", proxy);
46 + }
47 +
48 + return proxy;
49 +}
50 +
51 +bool cloud_config_insecure_get(void) {
52 + // load it from cloud.conf or use internal default
53 + return appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "insecure", CONFIG_BOOLEAN_NO);
54 +}
55 +
56 +static void cloud_conf_load_defaults(void) {
57 + appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "url", DEFAULT_CLOUD_BASE_URL);
58 + appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "proxy", "env");
59 + appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "token", "");
60 + appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "rooms", "");
61 + appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "insecure", CONFIG_BOOLEAN_NO);
62 + appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "machine_guid", "");
63 + appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "claimed_id", "");
64 + appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "hostname", "");
65 +}
66 +
67 +void cloud_conf_load(int silent) {
68 + errno_clear();
69 + char *filename = filename_from_path_entry_strdupz(netdata_configured_cloud_dir, "cloud.conf");
70 + int ret = appconfig_load(&cloud_config, filename, 1, NULL);
71 +
72 + if(!ret && !silent)
73 + nd_log(NDLS_DAEMON, NDLP_ERR,
74 + "CLAIM: cannot load cloud config '%s'. Running with internal defaults.", filename);
75 +
76 + freez(filename);
77 +
78 + appconfig_move(&cloud_config,
79 + CONFIG_SECTION_GLOBAL, "cloud base url",
80 + CONFIG_SECTION_GLOBAL, "url");
81 +
82 + cloud_conf_load_defaults();
83 +}
84 +
85 +void cloud_conf_init_after_registry(void) {
86 + const char *machine_guid = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "machine_guid", "");
87 + const char *hostname = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "hostname", "");
88 +
89 + // for machine guid and hostname we have to use appconfig_set() for that they will be saved uncommented
90 + if(!machine_guid || !*machine_guid)
91 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "machine_guid", registry_get_this_machine_guid());
92 +
93 + if(!hostname || !*hostname)
94 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "hostname", registry_get_this_machine_hostname());
95 +}
96 +
97 +bool cloud_conf_save(void) {
98 + char filename[FILENAME_MAX + 1];
99 +
100 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
101 + appconfig_generate(&cloud_config, wb, false, false);
102 + snprintfz(filename, sizeof(filename), "%s/cloud.conf", netdata_configured_cloud_dir);
103 + FILE *fp = fopen(filename, "w");
104 + if(!fp) {
105 + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot open file '%s' for writing.", filename);
106 + return false;
107 + }
108 +
109 + fprintf(fp, "%s", buffer_tostring(wb));
110 + fclose(fp);
111 + return true;
112 +}
113 +
114 +bool cloud_conf_regenerate(const char *claimed_id_str, const char *machine_guid, const char *hostname, const char *token, const char *rooms, const char *url, const char *proxy, int insecure) {
115 + // for backwards compatibility (older agents), save the claimed_id to its file
116 + claimed_id_save_to_file(claimed_id_str);
117 +
118 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "url", url);
119 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "proxy", proxy ? proxy : "");
120 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "token", token ? token : "");
121 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "rooms", rooms ? rooms : "");
122 + appconfig_set_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "insecure", insecure);
123 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "machine_guid", machine_guid ? machine_guid : "");
124 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "claimed_id", claimed_id_str ? claimed_id_str : "");
125 + appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "hostname", hostname ? hostname : "");
126 +
127 + return cloud_conf_save();
128 +}
src/claim/cloud-status.c new
+134
@@ -0,0 +1,134 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "claim.h"
4 +
5 +const char *cloud_status_to_string(CLOUD_STATUS status) {
6 + switch(status) {
7 + default:
8 + case CLOUD_STATUS_AVAILABLE:
9 + return "available";
10 +
11 + case CLOUD_STATUS_BANNED:
12 + return "banned";
13 +
14 + case CLOUD_STATUS_OFFLINE:
15 + return "offline";
16 +
17 + case CLOUD_STATUS_ONLINE:
18 + return "online";
19 +
20 + case CLOUD_STATUS_INDIRECT:
21 + return "indirect";
22 + }
23 +}
24 +
25 +CLOUD_STATUS cloud_status(void) {
26 + if(unlikely(aclk_disable_runtime))
27 + return CLOUD_STATUS_BANNED;
28 +
29 + if(likely(aclk_online()))
30 + return CLOUD_STATUS_ONLINE;
31 +
32 + if(localhost->sender &&
33 + rrdhost_flag_check(localhost, RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS) &&
34 + stream_has_capability(localhost->sender, STREAM_CAP_NODE_ID) &&
35 + !uuid_is_null(localhost->node_id) &&
36 + !UUIDiszero(localhost->aclk.claim_id_of_parent))
37 + return CLOUD_STATUS_INDIRECT;
38 +
39 + if(is_agent_claimed())
40 + return CLOUD_STATUS_OFFLINE;
41 +
42 + return CLOUD_STATUS_AVAILABLE;
43 +}
44 +
45 +time_t cloud_last_change(void) {
46 + time_t ret = MAX(last_conn_time_mqtt, last_disconnect_time);
47 + if(!ret) ret = netdata_start_time;
48 + return ret;
49 +}
50 +
51 +time_t cloud_next_connection_attempt(void) {
52 + return next_connection_attempt;
53 +}
54 +
55 +size_t cloud_connection_id(void) {
56 + return aclk_connection_counter;
57 +}
58 +
59 +const char *cloud_status_aclk_offline_reason() {
60 + if(aclk_disable_runtime)
61 + return "banned";
62 +
63 + return aclk_status_to_string();
64 +}
65 +
66 +const char *cloud_status_aclk_base_url() {
67 + return aclk_cloud_base_url;
68 +}
69 +
70 +CLOUD_STATUS buffer_json_cloud_status(BUFFER *wb, time_t now_s) {
71 + CLOUD_STATUS status = cloud_status();
72 +
73 + buffer_json_member_add_object(wb, "cloud");
74 + {
75 + size_t id = cloud_connection_id();
76 + time_t last_change = cloud_last_change();
77 + time_t next_connect = cloud_next_connection_attempt();
78 + buffer_json_member_add_uint64(wb, "id", id);
79 + buffer_json_member_add_string(wb, "status", cloud_status_to_string(status));
80 + buffer_json_member_add_time_t(wb, "since", last_change);
81 + buffer_json_member_add_time_t(wb, "age", now_s - last_change);
82 +
83 + switch(status) {
84 + default:
85 + case CLOUD_STATUS_AVAILABLE:
86 + // the agent is not claimed
87 + buffer_json_member_add_string(wb, "url", cloud_config_url_get());
88 + buffer_json_member_add_string(wb, "reason", claim_agent_failure_reason_get());
89 + break;
90 +
91 + case CLOUD_STATUS_BANNED: {
92 + // the agent is claimed, but has been banned from NC
93 + CLAIM_ID claim_id = claim_id_get();
94 + buffer_json_member_add_string(wb, "claim_id", claim_id.str);
95 + buffer_json_member_add_string(wb, "url", cloud_status_aclk_base_url());
96 + buffer_json_member_add_string(wb, "reason", "Agent is banned from Netdata Cloud");
97 + buffer_json_member_add_string(wb, "url", cloud_config_url_get());
98 + break;
99 + }
100 +
101 + case CLOUD_STATUS_OFFLINE: {
102 + // the agent is claimed, but cannot get online
103 + CLAIM_ID claim_id = claim_id_get();
104 + buffer_json_member_add_string(wb, "claim_id", claim_id.str);
105 + buffer_json_member_add_string(wb, "url", cloud_status_aclk_base_url());
106 + buffer_json_member_add_string(wb, "reason", cloud_status_aclk_offline_reason());
107 + if (next_connect > now_s) {
108 + buffer_json_member_add_time_t(wb, "next_check", next_connect);
109 + buffer_json_member_add_time_t(wb, "next_in", next_connect - now_s);
110 + }
111 + break;
112 + }
113 +
114 + case CLOUD_STATUS_ONLINE: {
115 + // the agent is claimed and online
116 + CLAIM_ID claim_id = claim_id_get();
117 + buffer_json_member_add_string(wb, "claim_id", claim_id.str);
118 + buffer_json_member_add_string(wb, "url", cloud_status_aclk_base_url());
119 + buffer_json_member_add_string(wb, "reason", "");
120 + break;
121 + }
122 +
123 + case CLOUD_STATUS_INDIRECT: {
124 + CLAIM_ID claim_id = rrdhost_claim_id_get(localhost);
125 + buffer_json_member_add_string(wb, "claim_id", claim_id.str);
126 + buffer_json_member_add_string(wb, "url", cloud_config_url_get());
127 + break;
128 + }
129 + }
130 + }
131 + buffer_json_object_close(wb); // cloud
132 +
133 + return status;
134 +}
src/claim/cloud-status.h new
+26
@@ -0,0 +1,26 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_CLOUD_STATUS_H
4 +#define NETDATA_CLOUD_STATUS_H
5 +
6 +#include "daemon/common.h"
7 +
8 +typedef enum __attribute__((packed)) {
9 + CLOUD_STATUS_AVAILABLE = 1, // cloud and aclk functionality is available, but the agent is not claimed
10 + CLOUD_STATUS_BANNED, // the agent has been banned from cloud
11 + CLOUD_STATUS_OFFLINE, // the agent tries to connect to cloud, but cannot do it
12 + CLOUD_STATUS_INDIRECT, // the agent is connected to cloud via a parent
13 + CLOUD_STATUS_ONLINE, // the agent is connected to cloud
14 +} CLOUD_STATUS;
15 +
16 +const char *cloud_status_to_string(CLOUD_STATUS status);
17 +CLOUD_STATUS cloud_status(void);
18 +
19 +time_t cloud_last_change(void);
20 +time_t cloud_next_connection_attempt(void);
21 +size_t cloud_connection_id(void);
22 +const char *cloud_status_aclk_offline_reason(void);
23 +const char *cloud_status_aclk_base_url(void);
24 +CLOUD_STATUS buffer_json_cloud_status(BUFFER *wb, time_t now_s);
25 +
26 +#endif //NETDATA_CLOUD_STATUS_H
src/claim/netdata-claim.sh.in
+87 -427
@@ -1,451 +1,111 @@
1 -#!/usr/bin/env bash
2 -# netdata
3 -# real-time performance and health monitoring, done right!
4 -# (C) 2023 Netdata Inc.
5 -# SPDX-License-Identifier: GPL-3.0-or-later
6 -
7 -# Exit code: 0 - Success
8 -# Exit code: 1 - Unknown argument
9 -# Exit code: 2 - Problems with claiming working directory
10 -# Exit code: 3 - Missing dependencies
11 -# Exit code: 4 - Failure to connect to endpoint
12 -# Exit code: 5 - The CLI didn't work
13 -# Exit code: 6 - Wrong user
14 -# Exit code: 7 - Unknown HTTP error message
15 -#
16 -# OK: Agent claimed successfully
17 -# HTTP Status code: 204
18 -# Exit code: 0
1 +#!/bin/sh
2 #
20 -# Unknown HTTP error message
21 -# HTTP Status code: 422
22 -# Exit code: 7
23 -ERROR_KEYS[7]="None"
24 -ERROR_MESSAGES[7]="Unknown HTTP error message"
25 -
26 -# Error: The agent id is invalid; it does not fulfill the constraints
27 -# HTTP Status code: 422
28 -# Exit code: 8
29 -ERROR_KEYS[8]="ErrInvalidNodeID"
30 -ERROR_MESSAGES[8]="invalid node id"
31 -
32 -# Error: The agent hostname is invalid; it does not fulfill the constraints
33 -# HTTP Status code: 422
34 -# Exit code: 9
35 -ERROR_KEYS[9]="ErrInvalidNodeName"
36 -ERROR_MESSAGES[9]="invalid node name"
37 -
38 -# Error: At least one of the given rooms ids is invalid; it does not fulfill the constraints
39 -# HTTP Status code: 422
40 -# Exit code: 10
41 -ERROR_KEYS[10]="ErrInvalidRoomID"
42 -ERROR_MESSAGES[10]="invalid room id"
43 -
44 -# Error: Invalid public key; the public key is empty or not present
45 -# HTTP Status code: 422
46 -# Exit code: 11
47 -ERROR_KEYS[11]="ErrInvalidPublicKey"
48 -ERROR_MESSAGES[11]="invalid public key"
3 +# Copyright (c) 2024 Netdata Inc.
4 +# SPDX-License-Identifier: GPL-3.0-or-later
5 #
50 -# Error: Expired, missing or invalid token
51 -# HTTP Status code: 403
52 -# Exit code: 12
53 -ERROR_KEYS[12]="ErrForbidden"
54 -ERROR_MESSAGES[12]="token expired/token not found/invalid token"
55 -
56 -# Error: Duplicate agent id; an agent with the same id is already registered in the cloud
57 -# HTTP Status code: 409
58 -# Exit code: 13
59 -ERROR_KEYS[13]="ErrAlreadyClaimed"
60 -ERROR_MESSAGES[13]="already claimed"
61 -
62 -# Error: The node claiming process is still in progress.
63 -# HTTP Status code: 102
64 -# Exit code: 14
65 -ERROR_KEYS[14]="ErrProcessingClaim"
66 -ERROR_MESSAGES[14]="processing claiming"
6 +# %%NEW_CLAIMING_METHOD%%
7
68 -# Error: Internal server error. Any other unexpected error (DB problems, etc.)
69 -# HTTP Status code: 500
70 -# Exit code: 15
71 -ERROR_KEYS[15]="ErrInternalServerError"
72 -ERROR_MESSAGES[15]="Internal Server Error"
8 +set -e
9
74 -# Error: There was a timeout processing the claim.
75 -# HTTP Status code: 504
76 -# Exit code: 16
77 -ERROR_KEYS[16]="ErrGatewayTimeout"
78 -ERROR_MESSAGES[16]="Gateway Timeout"
79 -
80 -# Error: The service cannot handle the claiming request at this time.
81 -# HTTP Status code: 503
82 -# Exit code: 17
83 -ERROR_KEYS[17]="ErrServiceUnavailable"
84 -ERROR_MESSAGES[17]="Service Unavailable"
85 -
86 -# Exit code: 18 - Agent unique id is not generated yet.
87 -
88 -NETDATA_RUNNING=1
89 -
90 -get_config_value() {
91 - conf_file="${1}"
92 - section="${2}"
93 - key_name="${3}"
94 - if [ "${NETDATA_RUNNING}" -eq 1 ]; then
95 - config_result=$(@sbindir_POST@/netdatacli 2>/dev/null read-config "$conf_file|$section|$key_name"; exit $?)
96 - result="$?"
97 - if [ "${result}" -ne 0 ]; then
98 - echo >&2 "Unable to communicate with Netdata daemon, querying config from disk instead."
99 - NETDATA_RUNNING=0
100 - fi
101 - fi
102 - if [ "${NETDATA_RUNNING}" -eq 0 ]; then
103 - config_result=$(@sbindir_POST@/netdata 2>/dev/null -W get2 "$conf_file" "$section" "$key_name" unknown_default)
104 - fi
105 - echo "$config_result"
10 +warning() {
11 + printf "WARNING: %s\n" "${1}" 1>&2
12 }
107 -if command -v curl >/dev/null 2>&1 ; then
108 - URLTOOL="curl"
109 -elif command -v wget >/dev/null 2>&1 ; then
110 - URLTOOL="wget"
111 -else
112 - echo >&2 "I need curl or wget to proceed, but neither is available on this system."
113 - exit 3
114 -fi
115 -if ! command -v openssl >/dev/null 2>&1 ; then
116 - echo >&2 "I need openssl to proceed, but it is not available on this system."
117 - exit 3
118 -fi
119 -
120 -# shellcheck disable=SC2050
121 -if [ "@enable_cloud_POST@" = "no" ]; then
122 - echo >&2 "This agent was built with --disable-cloud and cannot be claimed"
123 - exit 3
124 -fi
125 -# shellcheck disable=SC2050
126 -if [ "@enable_aclk_POST@" != "yes" ]; then
127 - echo >&2 "This agent was built without the dependencies for Cloud and cannot be claimed"
128 - exit 3
129 -fi
130 -
131 -# -----------------------------------------------------------------------------
132 -# defaults to allow running this script by hand
133 -
134 -[ -z "${NETDATA_VARLIB_DIR}" ] && NETDATA_VARLIB_DIR="@varlibdir_POST@"
135 -MACHINE_GUID_FILE="@registrydir_POST@/netdata.public.unique.id"
136 -CLAIMING_DIR="${NETDATA_VARLIB_DIR}/cloud.d"
137 -TOKEN="unknown"
138 -URL_BASE=$(get_config_value cloud global "cloud base url")
139 -[ -z "$URL_BASE" ] && URL_BASE="https://app.netdata.cloud" # Cover post-install with --dont-start
140 -ID="unknown"
141 -ROOMS=""
142 -[ -z "$HOSTNAME" ] && HOSTNAME=$(hostname)
143 -CLOUD_CERTIFICATE_FILE="${CLAIMING_DIR}/cloud_fullchain.pem"
144 -VERBOSE=0
145 -INSECURE=0
146 -RELOAD=1
147 -NETDATA_USER=$(get_config_value netdata global "run as user")
148 -[ -z "$EUID" ] && EUID="$(id -u)"
13
14 +error() {
15 + printf "ERROR: %s\n" "${1}" 1>&2
16 + exit "${2}"
17 +}
18
151 -gen_id() {
152 - local id
153 -
154 - if command -v uuidgen > /dev/null 2>&1; then
155 - id="$(uuidgen | tr '[:upper:]' '[:lower:]')"
156 - elif [ -r /proc/sys/kernel/random/uuid ]; then
157 - id="$(cat /proc/sys/kernel/random/uuid)"
158 - else
159 - echo >&2 "Unable to generate machine ID."
160 - exit 18
161 - fi
162 -
163 - if [ "${id}" = "8a795b0c-2311-11e6-8563-000c295076a6" ] || [ "${id}" = "4aed1458-1c3e-11e6-a53f-000c290fc8f5" ]; then
164 - gen_id
19 +get_templated_value() {
20 + value="$1"
21 + default="$2"
22 + override="$3"
23 +
24 + if [ -n "${override}" ]; then
25 + echo "${override}"
26 + elif [ -z "${value}" ]; then
27 + error "Expected templated value not present"
28 + elif (echo "${value}" | grep -q '@'); then
29 + echo "${default}"
30 else
166 - echo "${id}"
31 + echo "${value}"
32 fi
33 }
34
170 -# get the MACHINE_GUID by default
171 -if [ -r "${MACHINE_GUID_FILE}" ]; then
172 - ID="$(cat "${MACHINE_GUID_FILE}")"
173 - MGUID=$ID
174 -elif [ -f "${MACHINE_GUID_FILE}" ]; then
175 - echo >&2 "netdata.public.unique.id is not readable. Please make sure you have rights to read it (Filename: ${MACHINE_GUID_FILE})."
176 - exit 18
177 -else
178 - if mkdir -p "${MACHINE_GUID_FILE%/*}" && echo -n "$(gen_id)" > "${MACHINE_GUID_FILE}"; then
179 - ID="$(cat "${MACHINE_GUID_FILE}")"
180 - MGUID=$ID
181 - else
182 - echo >&2 "Failed to write new machine GUID. Please make sure you have rights to write to ${MACHINE_GUID_FILE}."
183 - exit 18
184 - fi
185 -fi
186 -
187 -# get token from file
188 -if [ -r "${CLAIMING_DIR}/token" ]; then
189 - TOKEN="$(cat "${CLAIMING_DIR}/token")"
190 -fi
191 -
192 -# get rooms from file
193 -if [ -r "${CLAIMING_DIR}/rooms" ]; then
194 - ROOMS="$(cat "${CLAIMING_DIR}/rooms")"
195 -fi
196 -
197 -variable_to_set=
198 -for arg in "$@"
199 -do
200 - if [ -z "$variable_to_set" ]; then
201 - case $arg in
202 - --claim-token) variable_to_set="TOKEN" ;;
203 - --claim-rooms) variable_to_set="ROOMS" ;;
204 - --claim-url) variable_to_set="URL_BASE" ;;
205 - -token=*) TOKEN=${arg:7} ;;
206 - -url=*) [ -n "${arg:5}" ] && URL_BASE=${arg:5} ;;
207 - -id=*) ID=$(echo "${arg:4}" | tr '[:upper:]' '[:lower:]');;
208 - -rooms=*) ROOMS=${arg:7} ;;
209 - -hostname=*) HOSTNAME=${arg:10} ;;
210 - -verbose) VERBOSE=1 ;;
211 - -insecure) INSECURE=1 ;;
212 - -proxy=*) PROXY=${arg:7} ;;
213 - -noproxy) NOPROXY=yes ;;
214 - -noreload) RELOAD=0 ;;
215 - -user=*) NETDATA_USER=${arg:6} ;;
216 - -daemon-not-running) NETDATA_RUNNING=0 ;;
217 - *) echo >&2 "Unknown argument ${arg}"
218 - exit 1 ;;
219 - esac
220 - else
221 - case "$variable_to_set" in
222 - TOKEN) TOKEN="$arg" ;;
223 - ROOMS) ROOMS="$arg" ;;
224 - URL_BASE) URL_BASE="$arg" ;;
225 - esac
226 - variable_to_set=
227 - fi
228 - shift 1
229 -done
230 -
231 -if [ "$EUID" != "0" ] && [ "$(whoami)" != "$NETDATA_USER" ]; then
232 - echo >&2 "This script must be run by the $NETDATA_USER user account"
233 - exit 6
234 -fi
235 -
236 -# if curl not installed give warning SOCKS can't be used
237 -if [[ "${URLTOOL}" != "curl" && "${PROXY:0:5}" = socks ]] ; then
238 - echo >&2 "wget doesn't support SOCKS. Please install curl or disable SOCKS proxy."
239 - exit 1
240 -fi
241 -
242 -echo >&2 "Token: ****************"
243 -echo >&2 "Base URL: $URL_BASE"
244 -echo >&2 "Id: $ID"
245 -echo >&2 "Rooms: $ROOMS"
246 -echo >&2 "Hostname: $HOSTNAME"
247 -echo >&2 "Proxy: $PROXY"
248 -echo >&2 "Netdata user: $NETDATA_USER"
249 -
250 -# create the claiming directory for this user
251 -if [ ! -d "${CLAIMING_DIR}" ] ; then
252 - mkdir -p "${CLAIMING_DIR}" && chmod 0770 "${CLAIMING_DIR}"
253 -# shellcheck disable=SC2181
254 - if [ $? -ne 0 ] ; then
255 - echo >&2 "Failed to create claiming working directory ${CLAIMING_DIR}"
256 - exit 2
257 - fi
258 -fi
259 -if [ ! -w "${CLAIMING_DIR}" ] ; then
260 - echo >&2 "No write permission in claiming working directory ${CLAIMING_DIR}"
261 - exit 2
262 -fi
263 -
264 -if [ ! -f "${CLAIMING_DIR}/private.pem" ] ; then
265 - echo >&2 "Generating private/public key for the first time."
266 - if ! openssl genrsa -out "${CLAIMING_DIR}/private.pem" 2048 ; then
267 - echo >&2 "Failed to generate private/public key pair."
268 - exit 2
269 - fi
270 -fi
271 -if [ ! -f "${CLAIMING_DIR}/public.pem" ] ; then
272 - echo >&2 "Extracting public key from private key."
273 - if ! openssl rsa -in "${CLAIMING_DIR}/private.pem" -outform PEM -pubout -out "${CLAIMING_DIR}/public.pem" ; then
274 - echo >&2 "Failed to extract public key."
275 - exit 2
276 - fi
277 -fi
278 -
279 -TARGET_URL="${URL_BASE%/}/api/v1/spaces/nodes/${ID}"
280 -# shellcheck disable=SC2002
281 -KEY=$(cat "${CLAIMING_DIR}/public.pem" | tr '\n' '!' | sed -e 's/!/\\n/g')
282 -# shellcheck disable=SC2001
283 -[ -n "$ROOMS" ] && ROOMS=\"$(echo "$ROOMS" | sed s'/,/", "/g')\"
35 +config_dir="$(get_templated_value "@configdir_POST@" "/etc/netdata" "${NETDATA_CLAIM_CONFIG_DIR}")"
36 +claim_config="${config_dir}/claim.conf"
37 +netdatacli="$(get_templated_value "@sbindir_POST@/netdatacli" "$(command -v netdatacli 2>/dev/null)" "${NETDATA_CLAIM_NETDATACLI_PATH}")"
38 +netdata_group="$(get_templated_value "@netdata_group_POST@" "netdata" "${NETDATA_CLAIM_CONFIG_GROUP}")"
39 +
40 +write_config() {
41 + config="[global]"
42 + config="${config}\n url = ${NETDATA_CLAIM_URL}"
43 + config="${config}\n token = ${NETDATA_CLAIM_TOKEN}"
44 + if [ -n "${NETDATA_CLAIM_ROOMS}" ]; then
45 + config="${config}\n rooms = ${NETDATA_CLAIM_ROOMS}"
46 + fi
47 + if [ -n "${NETDATA_CLAIM_PROXY}" ]; then
48 + config="${config}\n proxy = ${NETDATA_CLAIM_PROXY}"
49 + fi
50 + if [ -n "${NETDATA_CLAIM_INSECURE}" ]; then
51 + config="${config}\n insecure = ${NETDATA_CLAIM_INSECURE}"
52 + fi
53
285 -cat > "${CLAIMING_DIR}/tmpin.txt" <<EMBED_JSON
286 -{
287 - "node": {
288 - "id": "$ID",
289 - "hostname": "$HOSTNAME"
290 - },
291 - "token": "$TOKEN",
292 - "rooms" : [ $ROOMS ],
293 - "publicKey" : "$KEY",
294 - "mGUID" : "$MGUID"
54 + touch "${claim_config}.tmp"
55 + chmod 0660 "${claim_config}.tmp"
56 + chown "root:${netdata_group}" "${claim_config}.tmp"
57 + echo "${config}" > "${claim_config}.tmp"
58 + chmod 0640 "${claim_config}.tmp"
59 + mv -f "${claim_config}.tmp" "${claim_config}"
60 }
296 -EMBED_JSON
297 -
298 -if [ "${VERBOSE}" == 1 ] ; then
299 - echo "Request to server:"
300 - cat "${CLAIMING_DIR}/tmpin.txt"
301 -fi
61
303 -
304 -if [ "${URLTOOL}" = "curl" ] ; then
305 - URLCOMMAND="curl --connect-timeout 30 --retry 0 -s -i -X PUT -d \"@${CLAIMING_DIR}/tmpin.txt\""
306 - if [ "${NOPROXY}" = "yes" ] ; then
307 - URLCOMMAND="${URLCOMMAND} -x \"\""
308 - elif [ -n "${PROXY}" ] ; then
309 - URLCOMMAND="${URLCOMMAND} -x \"${PROXY}\""
310 - fi
311 -else
312 - URLCOMMAND="wget -T 15 -O - -q --server-response --content-on-error=on --method=PUT \
313 - --body-file=\"${CLAIMING_DIR}/tmpin.txt\""
314 - if [ "${NOPROXY}" = "yes" ] ; then
315 - URLCOMMAND="${URLCOMMAND} --no-proxy"
316 - elif [ "${PROXY:0:4}" = http ] ; then
317 - URLCOMMAND="export http_proxy=${PROXY}; ${URLCOMMAND}"
318 - fi
319 -fi
320 -
321 -if [ "${INSECURE}" == 1 ] ; then
322 - if [ "${URLTOOL}" = "curl" ] ; then
323 - URLCOMMAND="${URLCOMMAND} --insecure"
324 - else
325 - URLCOMMAND="${URLCOMMAND} --no-check-certificate"
62 +reload_claiming() {
63 + if [ -z "${NORELOAD}" ]; then
64 + "${netdatacli}" reload-claiming-state
65 fi
327 -fi
328 -
329 -if [ -r "${CLOUD_CERTIFICATE_FILE}" ] ; then
330 - if [ "${URLTOOL}" = "curl" ] ; then
331 - URLCOMMAND="${URLCOMMAND} --cacert \"${CLOUD_CERTIFICATE_FILE}\""
332 - else
333 - URLCOMMAND="${URLCOMMAND} --ca-certificate \"${CLOUD_CERTIFICATE_FILE}\""
334 - fi
335 -fi
336 -
337 -if [ "${VERBOSE}" == 1 ]; then
338 - echo "${URLCOMMAND} \"${TARGET_URL}\""
339 -fi
340 -
341 -attempt_contact () {
342 - if [ "${URLTOOL}" = "curl" ] ; then
343 - eval "${URLCOMMAND} \"${TARGET_URL}\"" >"${CLAIMING_DIR}/tmpout.txt"
344 - else
345 - eval "${URLCOMMAND} \"${TARGET_URL}\"" >"${CLAIMING_DIR}/tmpout.txt" 2>&1
346 - fi
347 - URLCOMMAND_EXIT_CODE=$?
348 - if [ "${URLTOOL}" = "wget" ] && [ "${URLCOMMAND_EXIT_CODE}" -eq 8 ] ; then
349 - # We consider the server issuing an error response a successful attempt at communicating
350 - URLCOMMAND_EXIT_CODE=0
351 - fi
352 -
353 - # Check if URLCOMMAND connected and received reply
354 - if [ "${URLCOMMAND_EXIT_CODE}" -ne 0 ] ; then
355 - echo >&2 "Failed to connect to ${URL_BASE}, return code ${URLCOMMAND_EXIT_CODE}"
356 - rm -f "${CLAIMING_DIR}/tmpout.txt"
357 - return 4
358 - fi
359 -
360 - if [ "${VERBOSE}" == 1 ] ; then
361 - echo "Response from server:"
362 - cat "${CLAIMING_DIR}/tmpout.txt"
363 - fi
364 -
365 - return 0
66 }
67
368 -for i in {1..3}
369 -do
370 - if attempt_contact ; then
371 - echo "Connection attempt $i successful"
372 - break
373 - fi
374 - echo "Connection attempt $i failed. Retry in ${i}s."
375 - if [ "$i" -eq 5 ] ; then
376 - rm -f "${CLAIMING_DIR}/tmpin.txt"
377 - exit 4
378 - fi
379 - sleep "$i"
380 -done
68 +parse_args() {
69 + while [ -n "${1}" ]; do
70 + case "${1}" in
71 + --claim-token) NETDATA_CLAIM_TOKEN="${2}"; shift 1 ;;
72 + -token=*) NETDATA_CLAIM_TOKEN="$(echo "${1}" | sed 's/^-token=//')" ;;
73 + --claim-rooms) NETDATA_CLAIM_ROOMS="${2}"; shift 1 ;;
74 + -rooms=*) NETDATA_CLAIM_ROOMS="$(echo "${1}" | sed 's/^-rooms=//')" ;;
75 + --claim-url) NETDATA_CLAIM_URL="${2}"; shift 1 ;;
76 + -url=*) NETDATA_CLAIM_URL="$(echo "${1}" | sed 's/^-url=/')" ;;
77 + --claim-proxy) NETDATA_CLAIM_PROXY="${2}"; shift 1 ;;
78 + -proxy=*) NETDATA_CLAIM_PROXY="$(echo "${1}" | sed 's/-proxy=//')" ;;
79 + -noproxy|--noproxy) NETDATA_CLAIM_PROXY="none" ;;
80 + -noreload|--noreload) NORELOAD=1 ;;
81 + -insecure|--insecure) NETDATA_CLAIM_INSECURE=yes ;;
82 + -verbose) true ;;
83 + -daemon-not-running) true ;;
84 + -id=*) warning "-id option is no longer supported. Remove the node ID file instead." ;;
85 + -hostname=*) warning "-hostname option is no longer supported. Update the main netdata configuration manually instead." ;;
86 + -user=*) warning "-user option is no longer supported." ;;
87 + *) warning "Ignoring unrecognized option ${1}";;
88 + esac
89
382 -rm -f "${CLAIMING_DIR}/tmpin.txt"
383 -
384 -ERROR_KEY=$(grep "\"errorMsgKey\":" "${CLAIMING_DIR}/tmpout.txt" | awk -F "errorMsgKey\":\"" '{print $2}' | awk -F "\"" '{print $1}')
385 -case ${ERROR_KEY} in
386 - "ErrInvalidNodeID") EXIT_CODE=8 ;;
387 - "ErrInvalidNodeName") EXIT_CODE=9 ;;
388 - "ErrInvalidRoomID") EXIT_CODE=10 ;;
389 - "ErrInvalidPublicKey") EXIT_CODE=11 ;;
390 - "ErrForbidden") EXIT_CODE=12 ;;
391 - "ErrAlreadyClaimed") EXIT_CODE=13 ;;
392 - "ErrProcessingClaim") EXIT_CODE=14 ;;
393 - "ErrInternalServerError") EXIT_CODE=15 ;;
394 - "ErrGatewayTimeout") EXIT_CODE=16 ;;
395 - "ErrServiceUnavailable") EXIT_CODE=17 ;;
396 - *) EXIT_CODE=7 ;;
397 -esac
398 -
399 -HTTP_STATUS_CODE=$(grep "HTTP" "${CLAIMING_DIR}/tmpout.txt" | tail -1 | awk -F " " '{print $2}')
400 -if [ "${HTTP_STATUS_CODE}" = "204" ] ; then
401 - EXIT_CODE=0
402 -fi
90 + shift 1
91 + done
92
404 -if [ "${HTTP_STATUS_CODE}" = "204" ] || [ "${ERROR_KEY}" = "ErrAlreadyClaimed" ] ; then
405 - rm -f "${CLAIMING_DIR}/tmpout.txt"
406 - if [ "${HTTP_STATUS_CODE}" = "204" ] ; then
407 - echo -n "${ID}" >"${CLAIMING_DIR}/claimed_id" || (echo >&2 "Claiming failed"; set -e; exit 2)
408 - fi
409 - rm -f "${CLAIMING_DIR}/token" || (echo >&2 "Claiming failed"; set -e; exit 2)
93 + if [ -z "${NETDATA_CLAIM_TOKEN}" ]; then
94 + error "Claim token must be specified" 1
95 + fi
96
411 - # Rewrite the cloud.conf on the disk
412 - cat > "$CLAIMING_DIR/cloud.conf" <<HERE_DOC
413 -[global]
414 - enabled = yes
415 - cloud base url = $URL_BASE
416 -${PROXY:+ proxy = $PROXY}
417 -HERE_DOC
418 - if [ "$EUID" == "0" ]; then
419 - chown -R "${NETDATA_USER}:${NETDATA_USER}" "${CLAIMING_DIR}" || (echo >&2 "Claiming failed"; set -e; exit 2)
420 - fi
421 - if [ "${RELOAD}" == "0" ] ; then
422 - exit $EXIT_CODE
423 - fi
97 + if [ -z "${NETDATA_CLAIM_URL}" ]; then
98 + NETDATA_CLAIM_URL="https://app.netdata.cloud/"
99 + fi
100 +}
101
425 - # Update cloud.conf in the agent memory
426 - @sbindir_POST@/netdatacli write-config 'cloud|global|enabled|yes' && \
427 - @sbindir_POST@/netdatacli write-config "cloud|global|cloud base url|$URL_BASE" && \
428 - @sbindir_POST@/netdatacli reload-claiming-state && \
429 - if [ "${HTTP_STATUS_CODE}" = "204" ] ; then
430 - echo >&2 "Node was successfully claimed."
431 - else
432 - echo >&2 "The agent cloud base url is set to the url provided."
433 - echo >&2 "The cloud may have different credentials already registered for this agent ID and it cannot be reclaimed under different credentials for security reasons. If you are unable to connect use -id=\$(uuidgen) to overwrite this agent ID with a fresh value if the original credentials cannot be restored."
434 - echo >&2 "Failed to claim node with the following error message:\"${ERROR_MESSAGES[$EXIT_CODE]}\""
435 - fi && exit $EXIT_CODE
102
437 - if [ "${ERROR_KEY}" = "ErrAlreadyClaimed" ] ; then
438 - echo >&2 "The cloud may have different credentials already registered for this agent ID and it cannot be reclaimed under different credentials for security reasons. If you are unable to connect use -id=\$(uuidgen) to overwrite this agent ID with a fresh value if the original credentials cannot be restored."
439 - echo >&2 "Failed to claim node with the following error message:\"${ERROR_MESSAGES[$EXIT_CODE]}\""
440 - exit $EXIT_CODE
441 - fi
442 - echo >&2 "The claim was successful but the agent could not be notified ($?)- it requires a restart to connect to the cloud."
443 - [ "$NETDATA_RUNNING" -eq 0 ] && exit 0 || exit 5
103 +[ -z "$EUID" ] && EUID="$(id -u)"
104 +if [ "${EUID}" != "0" ] && [ ! -w "${config_dir}" ]; then
105 + error "Script must be run by a user with write access to ${config_dir}." 32
106 fi
107
446 -echo >&2 "Failed to claim node with the following error message:\"${ERROR_MESSAGES[$EXIT_CODE]}\""
447 -if [ "${VERBOSE}" == 1 ]; then
448 - echo >&2 "Error key was:\"${ERROR_KEYS[$EXIT_CODE]}\""
449 -fi
450 -rm -f "${CLAIMING_DIR}/tmpout.txt"
451 -exit $EXIT_CODE
108 +warning "This script is deprecated and will be officially unsupported in the near future. Please either use the kickstart script with the appropriate '--claim-*' options, or directly write out the claiming configuration instead."
109 +parse_args "${@}"
110 +write_config
111 +reload_claiming
src/collectors/apps.plugin/README.md
+1 -1
@@ -237,7 +237,7 @@ Examples below for process group `sql`:
237 - Open Pipes ![image](https://registry.my-netdata.io/api/v1/badge.svg?chart=apps.fds_pipes&dimensions=sql&value_color=green=0%7Cred)
238 - Open Sockets ![image](https://registry.my-netdata.io/api/v1/badge.svg?chart=apps.fds_sockets&dimensions=sql&value_color=green%3E=3%7Cred)
239
240 -For more information about badges check [Generating Badges](/src/web/api/badges/README.md)
240 +For more information about badges check [Generating Badges](/src/web/api/v2/api_v3_badge/README.md)
241
242 ## Comparison with console tools
243
src/collectors/cgroups.plugin/cgroup-discovery.c
+2 -2
@@ -188,7 +188,7 @@ static inline void discovery_rename_cgroup(struct cgroup *cg) {
188 }
189
190 char buffer[CGROUP_CHARTID_LINE_MAX + 1];
191 - char *new_name = fgets(buffer, CGROUP_CHARTID_LINE_MAX, instance->child_stdout_fp);
191 + char *new_name = fgets(buffer, CGROUP_CHARTID_LINE_MAX, spawn_popen_stdout(instance));
192 int exit_code = spawn_popen_wait(instance);
193
194 switch (exit_code) {
@@ -1101,7 +1101,7 @@ static inline void read_cgroup_network_interfaces(struct cgroup *cg) {
1101
1102 char *s;
1103 char buffer[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
1104 - while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, instance->child_stdout_fp))) {
1104 + while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, spawn_popen_stdout(instance)))) {
1105 trim(s);
1106
1107 if(*s && *s != '\n') {
src/collectors/cgroups.plugin/cgroup-internals.h
+2 -2
@@ -394,8 +394,8 @@ static inline char *cgroup_chart_type(char *buffer, struct cgroup *cg) {
394 #define RRDFUNCTIONS_CGTOP_HELP "View running containers"
395 #define RRDFUNCTIONS_SYSTEMD_SERVICES_HELP "View systemd services"
396
397 -int cgroup_function_cgroup_top(BUFFER *wb, const char *function);
398 -int cgroup_function_systemd_top(BUFFER *wb, const char *function);
397 +int cgroup_function_cgroup_top(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
398 +int cgroup_function_systemd_top(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
399
400 void cgroup_netdev_link_init(void);
401 const DICTIONARY_ITEM *cgroup_netdev_get(struct cgroup *cg);
src/collectors/cgroups.plugin/cgroup-network.c
+1 -1
@@ -518,7 +518,7 @@ void call_the_helper(pid_t pid, const char *cgroup) {
518 if(pi) {
519 char buffer[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
520 char *s;
521 - while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, pi->child_stdout_fp))) {
521 + while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, spawn_popen_stdout(pi)))) {
522 trim(s);
523
524 if(*s && *s != '\n') {
src/collectors/cgroups.plugin/cgroup-top.c
+2 -2
@@ -98,7 +98,7 @@ void cgroup_netdev_get_bandwidth(struct cgroup *cg, NETDATA_DOUBLE *received, NE
98 *sent = t->sent[slot];
99 }
100
101 -int cgroup_function_cgroup_top(BUFFER *wb, const char *function __maybe_unused) {
101 +int cgroup_function_cgroup_top(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
102 buffer_flush(wb);
103 wb->content_type = CT_APPLICATION_JSON;
104 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
@@ -341,7 +341,7 @@ int cgroup_function_cgroup_top(BUFFER *wb, const char *function __maybe_unused)
341 return HTTP_RESP_OK;
342 }
343
344 -int cgroup_function_systemd_top(BUFFER *wb, const char *function __maybe_unused) {
344 +int cgroup_function_systemd_top(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
345 buffer_flush(wb);
346 wb->content_type = CT_APPLICATION_JSON;
347 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
src/collectors/cgroups.plugin/sys_fs_cgroup.c
+6 -6
@@ -82,7 +82,7 @@ static enum cgroups_systemd_setting cgroups_detect_systemd(const char *exec)
82 return retval;
83
84 struct pollfd pfd;
85 - pfd.fd = spawn_server_instance_read_fd(pi->si);
85 + pfd.fd = spawn_popen_read_fd(pi);
86 pfd.events = POLLIN;
87
88 int timeout = 3000; // milliseconds
@@ -93,7 +93,7 @@ static enum cgroups_systemd_setting cgroups_detect_systemd(const char *exec)
93 } else if (ret == 0) {
94 collector_info("Cannot get the output of \"%s\" within timeout (%d ms)", exec, timeout);
95 } else {
96 - while (fgets(buf, MAXSIZE_PROC_CMDLINE, pi->child_stdout_fp) != NULL) {
96 + while (fgets(buf, MAXSIZE_PROC_CMDLINE, spawn_popen_stdout(pi)) != NULL) {
97 if ((begin = strstr(buf, SYSTEMD_HIERARCHY_STRING))) {
98 end = begin = begin + strlen(SYSTEMD_HIERARCHY_STRING);
99 if (!*begin)
@@ -153,18 +153,18 @@ static enum cgroups_type cgroups_try_detect_version()
153 int cgroups2_available = 0;
154
155 // 1. check if cgroups2 available on system at all
156 - POPEN_INSTANCE *instance = spawn_popen_run("grep cgroup /proc/filesystems");
157 - if(!instance) {
156 + POPEN_INSTANCE *pi = spawn_popen_run("grep cgroup /proc/filesystems");
157 + if(!pi) {
158 collector_error("cannot run 'grep cgroup /proc/filesystems'");
159 return CGROUPS_AUTODETECT_FAIL;
160 }
161 - while (fgets(buf, MAXSIZE_PROC_CMDLINE, instance->child_stdout_fp) != NULL) {
161 + while (fgets(buf, MAXSIZE_PROC_CMDLINE, spawn_popen_stdout(pi)) != NULL) {
162 if (strstr(buf, "cgroup2")) {
163 cgroups2_available = 1;
164 break;
165 }
166 }
167 - if(spawn_popen_wait(instance) != 0)
167 + if(spawn_popen_wait(pi) != 0)
168 return CGROUPS_AUTODETECT_FAIL;
169
170 if(!cgroups2_available)
src/collectors/diskspace.plugin/plugin_diskspace.c
+1 -1
@@ -629,7 +629,7 @@ static void diskspace_main_cleanup(void *pptr) {
629 #error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 3
630 #endif
631
632 -int diskspace_function_mount_points(BUFFER *wb, const char *function __maybe_unused) {
632 +static int diskspace_function_mount_points(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
633 netdata_mutex_lock(&slow_mountinfo_mutex);
634
635 buffer_flush(wb);
src/collectors/freeipmi.plugin/freeipmi_plugin.c
+2 -1
@@ -1637,7 +1637,8 @@ close_and_send:
1637 // ----------------------------------------------------------------------------
1638 // main, command line arguments parsing
1639
1640 -static NORETURN void plugin_exit(int code) {
1640 +static void plugin_exit(int code) NORETURN;
1641 +static void plugin_exit(int code) {
1642 fflush(stdout);
1643 function_plugin_should_exit = true;
1644 exit(code);
src/collectors/plugins.d/plugins_d.c
+5 -2
@@ -158,7 +158,7 @@ static void *pluginsd_worker_thread(void *arg) {
158 rrdhost_hostname(cd->host), cd->cmd);
159 break;
160 }
161 - cd->unsafe.pid = spawn_server_instance_pid(cd->unsafe.pi->si);
161 + cd->unsafe.pid = spawn_popen_pid(cd->unsafe.pi);
162
163 nd_log(NDLS_DAEMON, NDLP_DEBUG,
164 "PLUGINSD: 'host:%s' connected to '%s' running on pid %d",
@@ -181,7 +181,10 @@ static void *pluginsd_worker_thread(void *arg) {
181 };
182 ND_LOG_STACK_PUSH(lgs);
183
184 - count = pluginsd_process(cd->host, cd, cd->unsafe.pi->child_stdin_fp, cd->unsafe.pi->child_stdout_fp, 0);
184 + count = pluginsd_process(cd->host, cd,
185 + spawn_popen_read_fd(cd->unsafe.pi),
186 + spawn_popen_write_fd(cd->unsafe.pi),
187 + 0);
188
189 nd_log(NDLS_DAEMON, NDLP_DEBUG,
190 "PLUGINSD: 'host:%s', '%s' (pid %d) disconnected after %zu successful data collections (ENDs).",
src/collectors/plugins.d/plugins_d.h
+4 -1
@@ -46,7 +46,10 @@ struct plugind {
46
47 extern struct plugind *pluginsd_root;
48
49 -size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugin_input, FILE *fp_plugin_output, int trust_durations);
49 +size_t pluginsd_process(RRDHOST *host, struct plugind *cd, int fd_input, int fd_output, int trust_durations);
50 +
51 +struct parser;
52 +void pluginsd_process_cleanup(struct parser *parser);
53 void pluginsd_process_thread_cleanup(void *pptr);
54
55 size_t pluginsd_initialize_plugin_directories();
src/collectors/plugins.d/pluginsd_internals.c
+7 -28
@@ -2,10 +2,8 @@
2
3 #include "pluginsd_internals.h"
4
5 -ssize_t send_to_plugin(const char *txt, void *data) {
6 - PARSER *parser = data;
7 -
8 - if(!txt || !*txt)
5 +ssize_t send_to_plugin(const char *txt, PARSER *parser) {
6 + if(!txt || !*txt || !parser)
7 return 0;
8
9 #ifdef ENABLE_H2O
@@ -17,7 +15,6 @@ ssize_t send_to_plugin(const char *txt, void *data) {
15 spinlock_lock(&parser->writer.spinlock);
16 ssize_t bytes = -1;
17
20 -#ifdef ENABLE_HTTPS
18 NETDATA_SSL *ssl = parser->ssl_output;
19 if(ssl) {
20
@@ -30,29 +27,14 @@ ssize_t send_to_plugin(const char *txt, void *data) {
27 spinlock_unlock(&parser->writer.spinlock);
28 return bytes;
29 }
33 -#endif
34 -
35 - if(parser->fp_output) {
36 -
37 - bytes = fprintf(parser->fp_output, "%s", txt);
38 - if(bytes <= 0) {
39 - netdata_log_error("PLUGINSD: cannot send command (FILE)");
40 - bytes = -2;
41 - }
42 - else
43 - fflush(parser->fp_output);
30
45 - spinlock_unlock(&parser->writer.spinlock);
46 - return bytes;
47 - }
48 -
49 - if(parser->fd != -1) {
31 + if(parser->fd_output != -1) {
32 bytes = 0;
33 ssize_t total = (ssize_t)strlen(txt);
34 ssize_t sent;
35
36 do {
55 - sent = write(parser->fd, &txt[bytes], total - bytes);
37 + sent = write(parser->fd_output, &txt[bytes], total - bytes);
38 if(sent <= 0) {
39 netdata_log_error("PLUGINSD: cannot send command (fd)");
40 spinlock_unlock(&parser->writer.spinlock);
@@ -100,19 +82,16 @@ void parser_destroy(PARSER *parser) {
82 }
83
84
103 -PARSER *parser_init(struct parser_user_object *user, FILE *fp_input, FILE *fp_output, int fd,
85 +PARSER *parser_init(struct parser_user_object *user, int fd_input, int fd_output,
86 PARSER_INPUT_TYPE flags, void *ssl __maybe_unused) {
87 PARSER *parser;
88
89 parser = callocz(1, sizeof(*parser));
90 if(user)
91 parser->user = *user;
110 - parser->fd = fd;
111 - parser->fp_input = fp_input;
112 - parser->fp_output = fp_output;
113 -#ifdef ENABLE_HTTPS
92 + parser->fd_input = fd_input;
93 + parser->fd_output = fd_output;
94 parser->ssl_output = ssl;
115 -#endif
95 parser->flags = flags;
96
97 spinlock_init(&parser->writer.spinlock);
src/collectors/plugins.d/pluginsd_internals.h
+1 -1
@@ -13,7 +13,7 @@
13
14 PARSER_RC PLUGINSD_DISABLE_PLUGIN(PARSER *parser, const char *keyword, const char *msg);
15
16 -ssize_t send_to_plugin(const char *txt, void *data);
16 +ssize_t send_to_plugin(const char *txt, PARSER *parser);
17
18 static inline RRDHOST *pluginsd_require_scope_host(PARSER *parser, const char *cmd) {
19 RRDHOST *host = parser->user.host;
src/collectors/plugins.d/pluginsd_parser.c
+14 -73
@@ -1081,52 +1081,7 @@ static inline PARSER_RC pluginsd_exit(char **words __maybe_unused, size_t num_wo
1081 return PARSER_RC_STOP;
1082 }
1083
1084 -static inline PARSER_RC streaming_claimed_id(char **words, size_t num_words, PARSER *parser)
1085 -{
1086 - const char *host_uuid_str = get_word(words, num_words, 1);
1087 - const char *claim_id_str = get_word(words, num_words, 2);
1088 -
1089 - if (!host_uuid_str || !claim_id_str) {
1090 - netdata_log_error("Command CLAIMED_ID came malformed, uuid = '%s', claim_id = '%s'",
1091 - host_uuid_str ? host_uuid_str : "[unset]",
1092 - claim_id_str ? claim_id_str : "[unset]");
1093 - return PARSER_RC_ERROR;
1094 - }
1095 -
1096 - nd_uuid_t uuid;
1097 - RRDHOST *host = parser->user.host;
1098 -
1099 - // We don't need the parsed UUID
1100 - // just do it to check the format
1101 - if(uuid_parse(host_uuid_str, uuid)) {
1102 - netdata_log_error("1st parameter (host GUID) to CLAIMED_ID command is not valid GUID. Received: \"%s\".", host_uuid_str);
1103 - return PARSER_RC_ERROR;
1104 - }
1105 - if(uuid_parse(claim_id_str, uuid) && strcmp(claim_id_str, "NULL") != 0) {
1106 - netdata_log_error("2nd parameter (Claim ID) to CLAIMED_ID command is not valid GUID. Received: \"%s\".", claim_id_str);
1107 - return PARSER_RC_ERROR;
1108 - }
1109 -
1110 - if(strcmp(host_uuid_str, host->machine_guid) != 0) {
1111 - netdata_log_error("Claim ID is for host \"%s\" but it came over connection for \"%s\"", host_uuid_str, host->machine_guid);
1112 - return PARSER_RC_OK; //the message is OK problem must be somewhere else
1113 - }
1114 -
1115 - rrdhost_aclk_state_lock(host);
1116 -
1117 - if (host->aclk_state.claimed_id)
1118 - freez(host->aclk_state.claimed_id);
1119 -
1120 - host->aclk_state.claimed_id = strcmp(claim_id_str, "NULL") ? strdupz(claim_id_str) : NULL;
1121 -
1122 - rrdhost_aclk_state_unlock(host);
1123 -
1124 - rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_CLAIMID |RRDHOST_FLAG_METADATA_UPDATE);
1125 -
1126 - rrdpush_send_claimed_id(host);
1127 -
1128 - return PARSER_RC_OK;
1129 -}
1084 +PARSER_RC rrdpush_receiver_pluginsd_claimed_id(char **words, size_t num_words, PARSER *parser);
1085
1086 // ----------------------------------------------------------------------------
1087
@@ -1135,8 +1090,7 @@ void pluginsd_cleanup_v2(PARSER *parser) {
1090 pluginsd_clear_scope_chart(parser, "THREAD CLEANUP");
1091 }
1092
1138 -void pluginsd_process_thread_cleanup(void *pptr) {
1139 - PARSER *parser = CLEANUP_FUNCTION_GET_PTR(pptr);
1093 +void pluginsd_process_cleanup(PARSER *parser) {
1094 if(!parser) return;
1095
1096 pluginsd_cleanup_v2(parser);
@@ -1154,6 +1108,11 @@ void pluginsd_process_thread_cleanup(void *pptr) {
1108 parser_destroy(parser);
1109 }
1110
1111 +void pluginsd_process_thread_cleanup(void *pptr) {
1112 + PARSER *parser = CLEANUP_FUNCTION_GET_PTR(pptr);
1113 + pluginsd_process_cleanup(parser);
1114 +}
1115 +
1116 bool parser_reconstruct_node(BUFFER *wb, void *ptr) {
1117 PARSER *parser = ptr;
1118 if(!parser || !parser->user.host)
@@ -1181,30 +1140,15 @@ bool parser_reconstruct_context(BUFFER *wb, void *ptr) {
1140 return true;
1141 }
1142
1184 -inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugin_input, FILE *fp_plugin_output, int trust_durations)
1143 +inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, int fd_input, int fd_output, int trust_durations)
1144 {
1145 int enabled = cd->unsafe.enabled;
1146
1188 - if (!fp_plugin_input || !fp_plugin_output || !enabled) {
1147 + if (fd_input == -1 || fd_output == -1 || !enabled) {
1148 cd->unsafe.enabled = 0;
1149 return 0;
1150 }
1151
1193 - if (unlikely(fileno(fp_plugin_input) == -1)) {
1194 - netdata_log_error("input file descriptor given is not a valid stream");
1195 - cd->serial_failures++;
1196 - return 0;
1197 - }
1198 -
1199 - if (unlikely(fileno(fp_plugin_output) == -1)) {
1200 - netdata_log_error("output file descriptor given is not a valid stream");
1201 - cd->serial_failures++;
1202 - return 0;
1203 - }
1204 -
1205 - clearerr(fp_plugin_input);
1206 - clearerr(fp_plugin_output);
1207 -
1152 PARSER *parser;
1153 {
1154 PARSER_USER_OBJECT user = {
@@ -1214,8 +1158,7 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi
1158 .trust_durations = trust_durations
1159 };
1160
1217 - // fp_plugin_output = our input; fp_plugin_input = our output
1218 - parser = parser_init(&user, fp_plugin_output, fp_plugin_input, -1, PARSER_INPUT_SPLIT, NULL);
1161 + parser = parser_init(&user, fd_input, fd_output, PARSER_INPUT_SPLIT, NULL);
1162 }
1163
1164 pluginsd_keywords_init(parser, PARSER_INIT_PLUGINSD);
@@ -1240,10 +1183,8 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi
1183
1184 if(unlikely(!buffered_reader_next_line(&parser->reader, buffer))) {
1185 buffered_reader_ret_t ret = buffered_reader_read_timeout(
1243 - &parser->reader,
1244 - fileno((FILE *) parser->fp_input),
1245 - 2 * 60 * MSEC_PER_SEC, true
1246 - );
1186 + &parser->reader, parser->fd_input,
1187 + 2 * 60 * MSEC_PER_SEC, true);
1188
1189 if(unlikely(ret != BUFFERED_READER_READ_OK))
1190 break;
@@ -1320,7 +1261,7 @@ PARSER_RC parser_execute(PARSER *parser, const PARSER_KEYWORD *keyword, char **w
1261 case PLUGINSD_KEYWORD_ID_VARIABLE:
1262 return pluginsd_variable(words, num_words, parser);
1263 case PLUGINSD_KEYWORD_ID_CLAIMED_ID:
1323 - return streaming_claimed_id(words, num_words, parser);
1264 + return rrdpush_receiver_pluginsd_claimed_id(words, num_words, parser);
1265 case PLUGINSD_KEYWORD_ID_HOST:
1266 return pluginsd_host(words, num_words, parser);
1267 case PLUGINSD_KEYWORD_ID_HOST_DEFINE:
@@ -1362,7 +1303,7 @@ void parser_init_repertoire(PARSER *parser, PARSER_REPERTOIRE repertoire) {
1303 }
1304
1305 int pluginsd_parser_unittest(void) {
1365 - PARSER *p = parser_init(NULL, NULL, NULL, -1, PARSER_INPUT_SPLIT, NULL);
1306 + PARSER *p = parser_init(NULL, -1, -1, PARSER_INPUT_SPLIT, NULL);
1307 pluginsd_keywords_init(p, PARSER_INIT_PLUGINSD | PARSER_INIT_STREAMING);
1308
1309 char *lines[] = {
src/collectors/plugins.d/pluginsd_parser.h
+7 -8
@@ -93,17 +93,15 @@ typedef struct parser_user_object {
93 } v2;
94 } PARSER_USER_OBJECT;
95
96 -typedef struct parser {
96 +struct parser {
97 uint8_t version; // Parser version
98 PARSER_REPERTOIRE repertoire;
99 uint32_t flags;
100 - int fd; // Socket
101 - FILE *fp_input; // Input source e.g. stream
102 - FILE *fp_output; // Stream to send commands to plugin
100 + int fd_input;
101 + int fd_output;
102
104 -#ifdef ENABLE_HTTPS
103 NETDATA_SSL *ssl_output;
106 -#endif
104 +
105 #ifdef ENABLE_H2O
106 void *h2o_ctx; // if set we use h2o_stream functions to send data
107 #endif
@@ -129,10 +127,11 @@ typedef struct parser {
127 struct {
128 SPINLOCK spinlock;
129 } writer;
130 +};
131
133 -} PARSER;
132 +typedef struct parser PARSER;
133
135 -PARSER *parser_init(struct parser_user_object *user, FILE *fp_input, FILE *fp_output, int fd, PARSER_INPUT_TYPE flags, void *ssl);
134 +PARSER *parser_init(struct parser_user_object *user, int fd_input, int fd_output, PARSER_INPUT_TYPE flags, void *ssl);
135 void parser_init_repertoire(PARSER *parser, PARSER_REPERTOIRE repertoire);
136 void parser_destroy(PARSER *working_parser);
137 void pluginsd_cleanup_v2(PARSER *parser);
src/collectors/proc.plugin/proc_diskstats.c
+1 -1
@@ -998,7 +998,7 @@ static void disk_labels_cb(RRDSET *st, void *data) {
998 add_labels_to_disk(data, st);
999 }
1000
1001 -static int diskstats_function_block_devices(BUFFER *wb, const char *function __maybe_unused) {
1001 +static int diskstats_function_block_devices(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
1002 buffer_flush(wb);
1003 wb->content_type = CT_APPLICATION_JSON;
1004 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
src/collectors/proc.plugin/proc_net_dev.c
+1 -1
@@ -473,7 +473,7 @@ static void netdev_rename_this_device(struct netdev *d) {
473
474 // ----------------------------------------------------------------------------
475
476 -int netdev_function_net_interfaces(BUFFER *wb, const char *function __maybe_unused) {
476 +static int netdev_function_net_interfaces(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
477 buffer_flush(wb);
478 wb->content_type = CT_APPLICATION_JSON;
479 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
src/collectors/statsd.plugin/statsd.c
+1 -1
@@ -1283,7 +1283,7 @@ static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHA
1283 // find the directory name from the file we already read
1284 char *filename2 = strdupz(filename); // copy filename, since dirname() will change it
1285 char *dir = dirname(filename2); // find the directory part of the filename
1286 - tmp = strdupz_path_subpath(dir, s); // compose the new filename to read;
1286 + tmp = filename_from_path_entry_strdupz(dir, s); // compose the new filename to read;
1287 freez(filename2); // free the filename we copied
1288 }
1289 statsd_readfile(tmp, app, chart, dict);
src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c
+4
@@ -58,6 +58,10 @@ static int systemd_journal_directories_dyncfg_update(BUFFER *result, BUFFER *pay
58 struct json_object *journalDirectories;
59 json_object_object_get_ex(jobj, JOURNAL_DIRECTORIES_JSON_NODE, &journalDirectories);
60
61 + if (json_object_get_type(journalDirectories) != json_type_array)
62 + return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST,
63 + "member " JOURNAL_DIRECTORIES_JSON_NODE " is not an array");
64 +
65 size_t n_directories = json_object_array_length(journalDirectories);
66 if(n_directories > MAX_JOURNAL_DIRECTORIES)
67 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "too many directories configured");
src/collectors/systemd-journal.plugin/systemd-journal.c
+456 -217
@@ -1413,7 +1413,7 @@ static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets, FUNCTION_QU
1413 }
1414
1415 static void netdata_systemd_journal_function_help(const char *transaction) {
1416 - BUFFER *wb = buffer_create(0, NULL);
1416 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
1417 buffer_sprintf(wb,
1418 "%s / %s\n"
1419 "\n"
@@ -1517,162 +1517,225 @@ static void netdata_systemd_journal_function_help(const char *transaction) {
1517 netdata_mutex_lock(&stdout_mutex);
1518 pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600, wb);
1519 netdata_mutex_unlock(&stdout_mutex);
1520 +}
1521
1521 - buffer_free(wb);
1522 +typedef struct {
1523 + FACET_KEY_OPTIONS default_facet;
1524 + bool info;
1525 + bool data_only;
1526 + bool slice;
1527 + bool delta;
1528 + bool tail;
1529 + time_t after_s;
1530 + time_t before_s;
1531 + usec_t anchor;
1532 + usec_t if_modified_since;
1533 + size_t last;
1534 + FACETS_ANCHOR_DIRECTION direction;
1535 + const char *query;
1536 + const char *chart;
1537 + SIMPLE_PATTERN *sources;
1538 + SD_JOURNAL_FILE_SOURCE_TYPE source_type;
1539 + size_t filters;
1540 + size_t sampling;
1541 +} JOURNAL_QUERY;
1542 +
1543 +static SD_JOURNAL_FILE_SOURCE_TYPE get_internal_source_type(const char *value) {
1544 + if(strcmp(value, SDJF_SOURCE_ALL_NAME) == 0)
1545 + return SDJF_ALL;
1546 + else if(strcmp(value, SDJF_SOURCE_LOCAL_NAME) == 0)
1547 + return SDJF_LOCAL_ALL;
1548 + else if(strcmp(value, SDJF_SOURCE_REMOTES_NAME) == 0)
1549 + return SDJF_REMOTE_ALL;
1550 + else if(strcmp(value, SDJF_SOURCE_NAMESPACES_NAME) == 0)
1551 + return SDJF_LOCAL_NAMESPACE;
1552 + else if(strcmp(value, SDJF_SOURCE_LOCAL_SYSTEM_NAME) == 0)
1553 + return SDJF_LOCAL_SYSTEM;
1554 + else if(strcmp(value, SDJF_SOURCE_LOCAL_USERS_NAME) == 0)
1555 + return SDJF_LOCAL_USER;
1556 + else if(strcmp(value, SDJF_SOURCE_LOCAL_OTHER_NAME) == 0)
1557 + return SDJF_LOCAL_OTHER;
1558 +
1559 + return SDJF_NONE;
1560 }
1561
1524 -void function_systemd_journal(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled,
1525 - BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
1526 - const char *source __maybe_unused, void *data __maybe_unused) {
1527 - fstat_thread_calls = 0;
1528 - fstat_thread_cached_responses = 0;
1562 +static FACETS_ANCHOR_DIRECTION get_direction(const char *value) {
1563 + return strcasecmp(value, "forward") == 0 ? FACETS_ANCHOR_DIRECTION_FORWARD : FACETS_ANCHOR_DIRECTION_BACKWARD;
1564 +}
1565
1530 - BUFFER *wb = buffer_create(0, NULL);
1531 - buffer_flush(wb);
1532 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
1566 +struct post_query_data {
1567 + const char *transaction;
1568 + FACETS *facets;
1569 + JOURNAL_QUERY *q;
1570 + BUFFER *wb;
1571 +};
1572 +
1573 +static bool parse_json_payload(json_object *jobj, const char *path, void *data, BUFFER *error) {
1574 + struct post_query_data *qd = data;
1575 + JOURNAL_QUERY *q = qd->q;
1576 + BUFFER *wb = qd->wb;
1577 + FACETS *facets = qd->facets;
1578 + // const char *transaction = qd->transaction;
1579 +
1580 + buffer_flush(error);
1581 +
1582 + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_INFO, q->info, error, false);
1583 + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_DELTA, q->delta, error, false);
1584 + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_TAIL, q->tail, error, false);
1585 + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_SLICE, q->slice, error, false);
1586 + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_DATA_ONLY, q->data_only, error, false);
1587 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_SAMPLING, q->sampling, error, false);
1588 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_AFTER, q->after_s, error, false);
1589 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_BEFORE, q->before_s, error, false);
1590 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_IF_MODIFIED_SINCE, q->if_modified_since, error, false);
1591 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_ANCHOR, q->anchor, error, false);
1592 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_LAST, q->last, error, false);
1593 + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_DIRECTION, get_direction, q->direction, error, false);
1594 + JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_QUERY, q->query, error, false);
1595 + JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, JOURNAL_PARAMETER_HISTOGRAM, q->chart, error, false);
1596 +
1597 + json_object *sources;
1598 + if (json_object_object_get_ex(jobj, JOURNAL_PARAMETER_SOURCE, &sources)) {
1599 + if (json_object_get_type(sources) != json_type_array) {
1600 + buffer_sprintf(error, "member '%s' is not an array", JOURNAL_PARAMETER_SOURCE);
1601 + return false;
1602 + }
1603
1534 - FUNCTION_QUERY_STATUS tmp_fqs = {
1535 - .cancelled = cancelled,
1536 - .stop_monotonic_ut = stop_monotonic_ut,
1537 - };
1538 - FUNCTION_QUERY_STATUS *fqs = NULL;
1604 + buffer_json_member_add_array(wb, JOURNAL_PARAMETER_SOURCE);
1605
1540 - FACETS *facets = facets_create(50, FACETS_OPTION_ALL_KEYS_FTS,
1541 - SYSTEMD_ALWAYS_VISIBLE_KEYS,
1542 - SYSTEMD_KEYS_INCLUDED_IN_FACETS,
1543 - SYSTEMD_KEYS_EXCLUDED_FROM_FACETS);
1606 + CLEAN_BUFFER *sources_list = buffer_create(0, NULL);
1607
1545 - facets_accepted_param(facets, JOURNAL_PARAMETER_INFO);
1546 - facets_accepted_param(facets, JOURNAL_PARAMETER_SOURCE);
1547 - facets_accepted_param(facets, JOURNAL_PARAMETER_AFTER);
1548 - facets_accepted_param(facets, JOURNAL_PARAMETER_BEFORE);
1549 - facets_accepted_param(facets, JOURNAL_PARAMETER_ANCHOR);
1550 - facets_accepted_param(facets, JOURNAL_PARAMETER_DIRECTION);
1551 - facets_accepted_param(facets, JOURNAL_PARAMETER_LAST);
1552 - facets_accepted_param(facets, JOURNAL_PARAMETER_QUERY);
1553 - facets_accepted_param(facets, JOURNAL_PARAMETER_FACETS);
1554 - facets_accepted_param(facets, JOURNAL_PARAMETER_HISTOGRAM);
1555 - facets_accepted_param(facets, JOURNAL_PARAMETER_IF_MODIFIED_SINCE);
1556 - facets_accepted_param(facets, JOURNAL_PARAMETER_DATA_ONLY);
1557 - facets_accepted_param(facets, JOURNAL_PARAMETER_DELTA);
1558 - facets_accepted_param(facets, JOURNAL_PARAMETER_TAIL);
1559 - facets_accepted_param(facets, JOURNAL_PARAMETER_SAMPLING);
1608 + q->source_type = SDJF_NONE;
1609
1561 -#ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
1562 - facets_accepted_param(facets, JOURNAL_PARAMETER_SLICE);
1563 -#endif // HAVE_SD_JOURNAL_RESTART_FIELDS
1610 + size_t sources_len = json_object_array_length(sources);
1611 + for (size_t i = 0; i < sources_len; i++) {
1612 + json_object *src = json_object_array_get_idx(sources, i);
1613
1565 - // register the fields in the order you want them on the dashboard
1614 + if (json_object_get_type(src) != json_type_string) {
1615 + buffer_sprintf(error, "sources array item %zu is not a string", i);
1616 + return false;
1617 + }
1618
1567 - facets_register_row_severity(facets, syslog_priority_to_facet_severity, NULL);
1619 + const char *value = json_object_get_string(src);
1620 + buffer_json_add_array_item_string(wb, value);
1621
1569 - facets_register_key_name(facets, "_HOSTNAME",
1570 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_VISIBLE);
1622 + SD_JOURNAL_FILE_SOURCE_TYPE t = get_internal_source_type(value);
1623 + if(t != SDJF_NONE) {
1624 + q->source_type |= t;
1625 + value = NULL;
1626 + }
1627 + else {
1628 + // else, match the source, whatever it is
1629 + if(buffer_strlen(sources_list))
1630 + buffer_putc(sources_list, '|');
1631
1572 - facets_register_dynamic_key_name(facets, JOURNAL_KEY_ND_JOURNAL_PROCESS,
1573 - FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_VISIBLE,
1574 - netdata_systemd_journal_dynamic_row_id, NULL);
1632 + buffer_strcat(sources_list, value);
1633 + }
1634 + }
1635
1576 - facets_register_key_name(facets, "MESSAGE",
1577 - FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT |
1578 - FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS);
1636 + if(buffer_strlen(sources_list)) {
1637 + simple_pattern_free(q->sources);
1638 + q->sources = simple_pattern_create(buffer_tostring(sources_list), "|", SIMPLE_PATTERN_EXACT, false);
1639 + }
1640
1580 -// facets_register_dynamic_key_name(facets, "MESSAGE",
1581 -// FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_RICH_TEXT |
1582 -// FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS,
1583 -// netdata_systemd_journal_rich_message, NULL);
1641 + buffer_json_array_close(wb); // source
1642 + }
1643
1585 - facets_register_key_name_transformation(facets, "PRIORITY",
1586 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW |
1587 - FACET_KEY_OPTION_EXPANDED_FILTER,
1588 - netdata_systemd_journal_transform_priority, NULL);
1644 + json_object *fcts;
1645 + if (json_object_object_get_ex(jobj, JOURNAL_PARAMETER_FACETS, &fcts)) {
1646 + if (json_object_get_type(sources) != json_type_array) {
1647 + buffer_sprintf(error, "member '%s' is not an array", JOURNAL_PARAMETER_FACETS);
1648 + return false;
1649 + }
1650
1590 - facets_register_key_name_transformation(facets, "SYSLOG_FACILITY",
1591 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW |
1592 - FACET_KEY_OPTION_EXPANDED_FILTER,
1593 - netdata_systemd_journal_transform_syslog_facility, NULL);
1651 + q->default_facet = FACET_KEY_OPTION_NONE;
1652 + facets_reset_and_disable_all_facets(facets);
1653
1595 - facets_register_key_name_transformation(facets, "ERRNO",
1596 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1597 - netdata_systemd_journal_transform_errno, NULL);
1654 + buffer_json_member_add_array(wb, JOURNAL_PARAMETER_FACETS);
1655
1599 - facets_register_key_name(facets, JOURNAL_KEY_ND_JOURNAL_FILE,
1600 - FACET_KEY_OPTION_NEVER_FACET);
1656 + size_t facets_len = json_object_array_length(fcts);
1657 + for (size_t i = 0; i < facets_len; i++) {
1658 + json_object *fct = json_object_array_get_idx(fcts, i);
1659
1602 - facets_register_key_name(facets, "SYSLOG_IDENTIFIER",
1603 - FACET_KEY_OPTION_FACET);
1660 + if (json_object_get_type(fct) != json_type_string) {
1661 + buffer_sprintf(error, "facets array item %zu is not a string", i);
1662 + return false;
1663 + }
1664
1605 - facets_register_key_name(facets, "UNIT",
1606 - FACET_KEY_OPTION_FACET);
1665 + const char *value = json_object_get_string(fct);
1666 + facets_register_facet(facets, value, FACET_KEY_OPTION_FACET|FACET_KEY_OPTION_FTS|FACET_KEY_OPTION_REORDER);
1667 + buffer_json_add_array_item_string(wb, value);
1668 + }
1669
1608 - facets_register_key_name(facets, "USER_UNIT",
1609 - FACET_KEY_OPTION_FACET);
1670 + buffer_json_array_close(wb); // facets
1671 + }
1672
1611 - facets_register_key_name_transformation(facets, "MESSAGE_ID",
1612 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW |
1613 - FACET_KEY_OPTION_EXPANDED_FILTER,
1614 - netdata_systemd_journal_transform_message_id, NULL);
1673 + json_object *selections;
1674 + if (json_object_object_get_ex(jobj, "selections", &selections)) {
1675 + if (json_object_get_type(selections) != json_type_object) {
1676 + buffer_sprintf(error, "member 'selections' is not an object");
1677 + return false;
1678 + }
1679
1616 - facets_register_key_name_transformation(facets, "_BOOT_ID",
1617 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1618 - netdata_systemd_journal_transform_boot_id, NULL);
1680 + buffer_json_member_add_object(wb, "selections");
1681
1620 - facets_register_key_name_transformation(facets, "_SYSTEMD_OWNER_UID",
1621 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1622 - netdata_systemd_journal_transform_uid, NULL);
1682 + json_object_object_foreach(selections, key, val) {
1683 + if (json_object_get_type(val) != json_type_array) {
1684 + buffer_sprintf(error, "selection '%s' is not an array", key);
1685 + return false;
1686 + }
1687
1624 - facets_register_key_name_transformation(facets, "_UID",
1625 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1626 - netdata_systemd_journal_transform_uid, NULL);
1688 + buffer_json_member_add_array(wb, key);
1689
1628 - facets_register_key_name_transformation(facets, "OBJECT_SYSTEMD_OWNER_UID",
1629 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1630 - netdata_systemd_journal_transform_uid, NULL);
1690 + size_t values_len = json_object_array_length(val);
1691 + for (size_t i = 0; i < values_len; i++) {
1692 + json_object *value_obj = json_object_array_get_idx(val, i);
1693
1632 - facets_register_key_name_transformation(facets, "OBJECT_UID",
1633 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1634 - netdata_systemd_journal_transform_uid, NULL);
1694 + if (json_object_get_type(value_obj) != json_type_string) {
1695 + buffer_sprintf(error, "selection '%s' array item %zu is not a string", key, i);
1696 + return false;
1697 + }
1698
1636 - facets_register_key_name_transformation(facets, "_GID",
1637 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1638 - netdata_systemd_journal_transform_gid, NULL);
1699 + const char *value = json_object_get_string(value_obj);
1700
1640 - facets_register_key_name_transformation(facets, "OBJECT_GID",
1641 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1642 - netdata_systemd_journal_transform_gid, NULL);
1701 + // Call facets_register_facet_id_filter for each value
1702 + facets_register_facet_filter(
1703 + facets, key, value, FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_REORDER);
1704
1644 - facets_register_key_name_transformation(facets, "_CAP_EFFECTIVE",
1645 - FACET_KEY_OPTION_TRANSFORM_VIEW,
1646 - netdata_systemd_journal_transform_cap_effective, NULL);
1705 + buffer_json_add_array_item_string(wb, value);
1706 + q->filters++;
1707 + }
1708
1648 - facets_register_key_name_transformation(facets, "_AUDIT_LOGINUID",
1649 - FACET_KEY_OPTION_TRANSFORM_VIEW,
1650 - netdata_systemd_journal_transform_uid, NULL);
1709 + buffer_json_array_close(wb); // key
1710 + }
1711
1652 - facets_register_key_name_transformation(facets, "OBJECT_AUDIT_LOGINUID",
1653 - FACET_KEY_OPTION_TRANSFORM_VIEW,
1654 - netdata_systemd_journal_transform_uid, NULL);
1712 + buffer_json_object_close(wb); // selections
1713 + }
1714
1656 - facets_register_key_name_transformation(facets, "_SOURCE_REALTIME_TIMESTAMP",
1657 - FACET_KEY_OPTION_TRANSFORM_VIEW,
1658 - netdata_systemd_journal_transform_timestamp_usec, NULL);
1715 + return true;
1716 +}
1717
1660 - // ------------------------------------------------------------------------
1661 - // parse the parameters
1718 +static bool parse_post_params(FACETS *facets, JOURNAL_QUERY *q, BUFFER *wb, BUFFER *payload, const char *transaction) {
1719 + struct post_query_data qd = {
1720 + .transaction = transaction,
1721 + .facets = facets,
1722 + .q = q,
1723 + .wb = wb,
1724 + };
1725
1663 - bool info = false, data_only = false, slice = JOURNAL_DEFAULT_SLICE_MODE, delta = false, tail = false;
1664 - time_t after_s = 0, before_s = 0;
1665 - usec_t anchor = 0;
1666 - usec_t if_modified_since = 0;
1667 - size_t last = 0;
1668 - FACETS_ANCHOR_DIRECTION direction = JOURNAL_DEFAULT_DIRECTION;
1669 - const char *query = NULL;
1670 - const char *chart = NULL;
1671 - SIMPLE_PATTERN *sources = NULL;
1672 - SD_JOURNAL_FILE_SOURCE_TYPE source_type = SDJF_ALL;
1673 - size_t filters = 0;
1674 - size_t sampling = SYSTEMD_JOURNAL_DEFAULT_ITEMS_SAMPLING;
1726 + int code;
1727 + CLEAN_JSON_OBJECT *jobj = json_parse_function_payload_or_error(wb, payload, &code, parse_json_payload, &qd);
1728 + if(!jobj || code != HTTP_RESP_OK) {
1729 + netdata_mutex_lock(&stdout_mutex);
1730 + pluginsd_function_result_to_stdout(transaction, code, "application/json", now_realtime_sec() + 1, wb);
1731 + netdata_mutex_unlock(&stdout_mutex);
1732 + return false;
1733 + }
1734 +
1735 + return true;
1736 +}
1737
1738 +static bool parse_get_params(FACETS *facets, JOURNAL_QUERY *q, BUFFER *wb, char *function, const char *transaction) {
1739 buffer_json_member_add_object(wb, "_request");
1740
1741 char *words[SYSTEMD_JOURNAL_MAX_PARAMS] = { NULL };
@@ -1683,54 +1746,54 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
1746
1747 if(strcmp(keyword, JOURNAL_PARAMETER_HELP) == 0) {
1748 netdata_systemd_journal_function_help(transaction);
1686 - goto cleanup;
1749 + return false;
1750 }
1751 else if(strcmp(keyword, JOURNAL_PARAMETER_INFO) == 0) {
1689 - info = true;
1752 + q->info = true;
1753 }
1754 else if(strncmp(keyword, JOURNAL_PARAMETER_DELTA ":", sizeof(JOURNAL_PARAMETER_DELTA ":") - 1) == 0) {
1755 char *v = &keyword[sizeof(JOURNAL_PARAMETER_DELTA ":") - 1];
1756
1757 if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
1695 - delta = false;
1758 + q->delta = false;
1759 else
1697 - delta = true;
1760 + q->delta = true;
1761 }
1762 else if(strncmp(keyword, JOURNAL_PARAMETER_TAIL ":", sizeof(JOURNAL_PARAMETER_TAIL ":") - 1) == 0) {
1763 char *v = &keyword[sizeof(JOURNAL_PARAMETER_TAIL ":") - 1];
1764
1765 if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
1703 - tail = false;
1766 + q->tail = false;
1767 else
1705 - tail = true;
1768 + q->tail = true;
1769 }
1770 else if(strncmp(keyword, JOURNAL_PARAMETER_SAMPLING ":", sizeof(JOURNAL_PARAMETER_SAMPLING ":") - 1) == 0) {
1708 - sampling = str2ul(&keyword[sizeof(JOURNAL_PARAMETER_SAMPLING ":") - 1]);
1771 + q->sampling = str2ul(&keyword[sizeof(JOURNAL_PARAMETER_SAMPLING ":") - 1]);
1772 }
1773 else if(strncmp(keyword, JOURNAL_PARAMETER_DATA_ONLY ":", sizeof(JOURNAL_PARAMETER_DATA_ONLY ":") - 1) == 0) {
1774 char *v = &keyword[sizeof(JOURNAL_PARAMETER_DATA_ONLY ":") - 1];
1775
1776 if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
1714 - data_only = false;
1777 + q->data_only = false;
1778 else
1716 - data_only = true;
1779 + q->data_only = true;
1780 }
1781 else if(strncmp(keyword, JOURNAL_PARAMETER_SLICE ":", sizeof(JOURNAL_PARAMETER_SLICE ":") - 1) == 0) {
1782 char *v = &keyword[sizeof(JOURNAL_PARAMETER_SLICE ":") - 1];
1783
1784 if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
1722 - slice = false;
1785 + q->slice = false;
1786 else
1724 - slice = true;
1787 + q->slice = true;
1788 }
1789 else if(strncmp(keyword, JOURNAL_PARAMETER_SOURCE ":", sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1) == 0) {
1790 const char *value = &keyword[sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1];
1791
1792 buffer_json_member_add_array(wb, JOURNAL_PARAMETER_SOURCE);
1793
1731 - BUFFER *sources_list = buffer_create(0, NULL);
1794 + CLEAN_BUFFER *sources_list = buffer_create(0, NULL);
1795
1733 - source_type = SDJF_NONE;
1796 + q->source_type = SDJF_NONE;
1797 while(value) {
1798 char *sep = strchr(value, ',');
1799 if(sep)
@@ -1738,38 +1801,15 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
1801
1802 buffer_json_add_array_item_string(wb, value);
1803
1741 - if(strcmp(value, SDJF_SOURCE_ALL_NAME) == 0) {
1742 - source_type |= SDJF_ALL;
1743 - value = NULL;
1744 - }
1745 - else if(strcmp(value, SDJF_SOURCE_LOCAL_NAME) == 0) {
1746 - source_type |= SDJF_LOCAL_ALL;
1747 - value = NULL;
1748 - }
1749 - else if(strcmp(value, SDJF_SOURCE_REMOTES_NAME) == 0) {
1750 - source_type |= SDJF_REMOTE_ALL;
1751 - value = NULL;
1752 - }
1753 - else if(strcmp(value, SDJF_SOURCE_NAMESPACES_NAME) == 0) {
1754 - source_type |= SDJF_LOCAL_NAMESPACE;
1755 - value = NULL;
1756 - }
1757 - else if(strcmp(value, SDJF_SOURCE_LOCAL_SYSTEM_NAME) == 0) {
1758 - source_type |= SDJF_LOCAL_SYSTEM;
1759 - value = NULL;
1760 - }
1761 - else if(strcmp(value, SDJF_SOURCE_LOCAL_USERS_NAME) == 0) {
1762 - source_type |= SDJF_LOCAL_USER;
1763 - value = NULL;
1764 - }
1765 - else if(strcmp(value, SDJF_SOURCE_LOCAL_OTHER_NAME) == 0) {
1766 - source_type |= SDJF_LOCAL_OTHER;
1804 + SD_JOURNAL_FILE_SOURCE_TYPE t = get_internal_source_type(value);
1805 + if(t != SDJF_NONE) {
1806 + q->source_type |= t;
1807 value = NULL;
1808 }
1809 else {
1810 // else, match the source, whatever it is
1811 if(buffer_strlen(sources_list))
1772 - buffer_strcat(sources_list, ",");
1812 + buffer_putc(sources_list, '|');
1813
1814 buffer_strcat(sources_list, value);
1815 }
@@ -1778,39 +1818,42 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
1818 }
1819
1820 if(buffer_strlen(sources_list)) {
1781 - simple_pattern_free(sources);
1782 - sources = simple_pattern_create(buffer_tostring(sources_list), ",", SIMPLE_PATTERN_EXACT, false);
1821 + simple_pattern_free(q->sources);
1822 + q->sources = simple_pattern_create(buffer_tostring(sources_list), "|", SIMPLE_PATTERN_EXACT, false);
1823 }
1824
1785 - buffer_free(sources_list);
1786 -
1825 buffer_json_array_close(wb); // source
1826 }
1827 else if(strncmp(keyword, JOURNAL_PARAMETER_AFTER ":", sizeof(JOURNAL_PARAMETER_AFTER ":") - 1) == 0) {
1790 - after_s = str2l(&keyword[sizeof(JOURNAL_PARAMETER_AFTER ":") - 1]);
1828 + q->after_s = str2l(&keyword[sizeof(JOURNAL_PARAMETER_AFTER ":") - 1]);
1829 }
1830 else if(strncmp(keyword, JOURNAL_PARAMETER_BEFORE ":", sizeof(JOURNAL_PARAMETER_BEFORE ":") - 1) == 0) {
1793 - before_s = str2l(&keyword[sizeof(JOURNAL_PARAMETER_BEFORE ":") - 1]);
1831 + q->before_s = str2l(&keyword[sizeof(JOURNAL_PARAMETER_BEFORE ":") - 1]);
1832 }
1833 else if(strncmp(keyword, JOURNAL_PARAMETER_IF_MODIFIED_SINCE ":", sizeof(JOURNAL_PARAMETER_IF_MODIFIED_SINCE ":") - 1) == 0) {
1796 - if_modified_since = str2ull(&keyword[sizeof(JOURNAL_PARAMETER_IF_MODIFIED_SINCE ":") - 1], NULL);
1834 + q->if_modified_since = str2ull(&keyword[sizeof(JOURNAL_PARAMETER_IF_MODIFIED_SINCE ":") - 1], NULL);
1835 }
1836 else if(strncmp(keyword, JOURNAL_PARAMETER_ANCHOR ":", sizeof(JOURNAL_PARAMETER_ANCHOR ":") - 1) == 0) {
1799 - anchor = str2ull(&keyword[sizeof(JOURNAL_PARAMETER_ANCHOR ":") - 1], NULL);
1837 + q->anchor = str2ull(&keyword[sizeof(JOURNAL_PARAMETER_ANCHOR ":") - 1], NULL);
1838 }
1839 else if(strncmp(keyword, JOURNAL_PARAMETER_DIRECTION ":", sizeof(JOURNAL_PARAMETER_DIRECTION ":") - 1) == 0) {
1802 - direction = strcasecmp(&keyword[sizeof(JOURNAL_PARAMETER_DIRECTION ":") - 1], "forward") == 0 ? FACETS_ANCHOR_DIRECTION_FORWARD : FACETS_ANCHOR_DIRECTION_BACKWARD;
1840 + q->direction = get_direction(&keyword[sizeof(JOURNAL_PARAMETER_DIRECTION ":") - 1]);
1841 }
1842 else if(strncmp(keyword, JOURNAL_PARAMETER_LAST ":", sizeof(JOURNAL_PARAMETER_LAST ":") - 1) == 0) {
1805 - last = str2ul(&keyword[sizeof(JOURNAL_PARAMETER_LAST ":") - 1]);
1843 + q->last = str2ul(&keyword[sizeof(JOURNAL_PARAMETER_LAST ":") - 1]);
1844 }
1845 else if(strncmp(keyword, JOURNAL_PARAMETER_QUERY ":", sizeof(JOURNAL_PARAMETER_QUERY ":") - 1) == 0) {
1808 - query= &keyword[sizeof(JOURNAL_PARAMETER_QUERY ":") - 1];
1846 + freez((void *)q->query);
1847 + q->query= strdupz(&keyword[sizeof(JOURNAL_PARAMETER_QUERY ":") - 1]);
1848 }
1849 else if(strncmp(keyword, JOURNAL_PARAMETER_HISTOGRAM ":", sizeof(JOURNAL_PARAMETER_HISTOGRAM ":") - 1) == 0) {
1811 - chart = &keyword[sizeof(JOURNAL_PARAMETER_HISTOGRAM ":") - 1];
1850 + freez((void *)q->chart);
1851 + q->chart = strdupz(&keyword[sizeof(JOURNAL_PARAMETER_HISTOGRAM ":") - 1]);
1852 }
1853 else if(strncmp(keyword, JOURNAL_PARAMETER_FACETS ":", sizeof(JOURNAL_PARAMETER_FACETS ":") - 1) == 0) {
1854 + q->default_facet = FACET_KEY_OPTION_NONE;
1855 + facets_reset_and_disable_all_facets(facets);
1856 +
1857 char *value = &keyword[sizeof(JOURNAL_PARAMETER_FACETS ":") - 1];
1858 if(*value) {
1859 buffer_json_member_add_array(wb, JOURNAL_PARAMETER_FACETS);
@@ -1841,9 +1884,14 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
1884 if(sep)
1885 *sep++ = '\0';
1886
1844 - facets_register_facet_id_filter(facets, keyword, value, FACET_KEY_OPTION_FACET|FACET_KEY_OPTION_FTS|FACET_KEY_OPTION_REORDER);
1887 + facets_register_facet_filter_id(
1888 + facets,
1889 + keyword,
1890 + value,
1891 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_REORDER);
1892 +
1893 buffer_json_add_array_item_string(wb, value);
1846 - filters++;
1894 + q->filters++;
1895
1896 value = sep;
1897 }
@@ -1853,6 +1901,197 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
1901 }
1902 }
1903
1904 + return true;
1905 +}
1906 +
1907 +void function_systemd_journal(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled,
1908 + BUFFER *payload, HTTP_ACCESS access __maybe_unused,
1909 + const char *source __maybe_unused, void *data __maybe_unused) {
1910 + fstat_thread_calls = 0;
1911 + fstat_thread_cached_responses = 0;
1912 +
1913 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
1914 + buffer_flush(wb);
1915 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
1916 +
1917 + FUNCTION_QUERY_STATUS tmp_fqs = {
1918 + .cancelled = cancelled,
1919 + .stop_monotonic_ut = stop_monotonic_ut,
1920 + };
1921 + FUNCTION_QUERY_STATUS *fqs = NULL;
1922 +
1923 + FACETS *facets = facets_create(50, FACETS_OPTION_ALL_KEYS_FTS,
1924 + SYSTEMD_ALWAYS_VISIBLE_KEYS,
1925 + SYSTEMD_KEYS_INCLUDED_IN_FACETS,
1926 + SYSTEMD_KEYS_EXCLUDED_FROM_FACETS);
1927 +
1928 + facets_accepted_param(facets, JOURNAL_PARAMETER_INFO);
1929 + facets_accepted_param(facets, JOURNAL_PARAMETER_SOURCE);
1930 + facets_accepted_param(facets, JOURNAL_PARAMETER_AFTER);
1931 + facets_accepted_param(facets, JOURNAL_PARAMETER_BEFORE);
1932 + facets_accepted_param(facets, JOURNAL_PARAMETER_ANCHOR);
1933 + facets_accepted_param(facets, JOURNAL_PARAMETER_DIRECTION);
1934 + facets_accepted_param(facets, JOURNAL_PARAMETER_LAST);
1935 + facets_accepted_param(facets, JOURNAL_PARAMETER_QUERY);
1936 + facets_accepted_param(facets, JOURNAL_PARAMETER_FACETS);
1937 + facets_accepted_param(facets, JOURNAL_PARAMETER_HISTOGRAM);
1938 + facets_accepted_param(facets, JOURNAL_PARAMETER_IF_MODIFIED_SINCE);
1939 + facets_accepted_param(facets, JOURNAL_PARAMETER_DATA_ONLY);
1940 + facets_accepted_param(facets, JOURNAL_PARAMETER_DELTA);
1941 + facets_accepted_param(facets, JOURNAL_PARAMETER_TAIL);
1942 + facets_accepted_param(facets, JOURNAL_PARAMETER_SAMPLING);
1943 +
1944 +#ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
1945 + facets_accepted_param(facets, JOURNAL_PARAMETER_SLICE);
1946 +#endif // HAVE_SD_JOURNAL_RESTART_FIELDS
1947 +
1948 + // ------------------------------------------------------------------------
1949 + // parse the parameters
1950 +
1951 + JOURNAL_QUERY q = {
1952 + .default_facet = FACET_KEY_OPTION_FACET,
1953 + .info = false,
1954 + .data_only = false,
1955 + .slice = JOURNAL_DEFAULT_SLICE_MODE,
1956 + .delta = false,
1957 + .tail = false,
1958 + .after_s = 0,
1959 + .before_s = 0,
1960 + .anchor = 0,
1961 + .if_modified_since = 0,
1962 + .last = 0,
1963 + .direction = JOURNAL_DEFAULT_DIRECTION,
1964 + .query = NULL,
1965 + .chart = NULL,
1966 + .sources = NULL,
1967 + .source_type = SDJF_ALL,
1968 + .filters = 0,
1969 + .sampling = SYSTEMD_JOURNAL_DEFAULT_ITEMS_SAMPLING,
1970 + };
1971 +
1972 + if( (payload && !parse_post_params(facets, &q, wb, payload, transaction)) ||
1973 + (!payload && !parse_get_params(facets, &q, wb, function, transaction)) )
1974 + goto cleanup;
1975 +
1976 + // ----------------------------------------------------------------------------------------------------------------
1977 + // register the fields in the order you want them on the dashboard
1978 +
1979 + facets_register_row_severity(facets, syslog_priority_to_facet_severity, NULL);
1980 +
1981 + facets_register_key_name(
1982 + facets, "_HOSTNAME",
1983 + q.default_facet | FACET_KEY_OPTION_VISIBLE);
1984 +
1985 + facets_register_dynamic_key_name(
1986 + facets, JOURNAL_KEY_ND_JOURNAL_PROCESS,
1987 + FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_VISIBLE,
1988 + netdata_systemd_journal_dynamic_row_id, NULL);
1989 +
1990 + facets_register_key_name(
1991 + facets, "MESSAGE",
1992 + FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT |
1993 + FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS);
1994 +
1995 + // facets_register_dynamic_key_name(
1996 + // facets, "MESSAGE",
1997 + // FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_RICH_TEXT |
1998 + // FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS,
1999 + // netdata_systemd_journal_rich_message, NULL);
2000 +
2001 + facets_register_key_name_transformation(
2002 + facets, "PRIORITY",
2003 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW |
2004 + FACET_KEY_OPTION_EXPANDED_FILTER,
2005 + netdata_systemd_journal_transform_priority, NULL);
2006 +
2007 + facets_register_key_name_transformation(
2008 + facets, "SYSLOG_FACILITY",
2009 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW |
2010 + FACET_KEY_OPTION_EXPANDED_FILTER,
2011 + netdata_systemd_journal_transform_syslog_facility, NULL);
2012 +
2013 + facets_register_key_name_transformation(
2014 + facets, "ERRNO",
2015 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2016 + netdata_systemd_journal_transform_errno, NULL);
2017 +
2018 + facets_register_key_name(
2019 + facets, JOURNAL_KEY_ND_JOURNAL_FILE,
2020 + FACET_KEY_OPTION_NEVER_FACET);
2021 +
2022 + facets_register_key_name(
2023 + facets, "SYSLOG_IDENTIFIER",
2024 + q.default_facet);
2025 +
2026 + facets_register_key_name(
2027 + facets, "UNIT",
2028 + q.default_facet);
2029 +
2030 + facets_register_key_name(
2031 + facets, "USER_UNIT",
2032 + q.default_facet);
2033 +
2034 + facets_register_key_name_transformation(
2035 + facets, "MESSAGE_ID",
2036 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW |
2037 + FACET_KEY_OPTION_EXPANDED_FILTER,
2038 + netdata_systemd_journal_transform_message_id, NULL);
2039 +
2040 + facets_register_key_name_transformation(
2041 + facets, "_BOOT_ID",
2042 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2043 + netdata_systemd_journal_transform_boot_id, NULL);
2044 +
2045 + facets_register_key_name_transformation(
2046 + facets, "_SYSTEMD_OWNER_UID",
2047 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2048 + netdata_systemd_journal_transform_uid, NULL);
2049 +
2050 + facets_register_key_name_transformation(
2051 + facets, "_UID",
2052 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2053 + netdata_systemd_journal_transform_uid, NULL);
2054 +
2055 + facets_register_key_name_transformation(
2056 + facets, "OBJECT_SYSTEMD_OWNER_UID",
2057 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2058 + netdata_systemd_journal_transform_uid, NULL);
2059 +
2060 + facets_register_key_name_transformation(
2061 + facets, "OBJECT_UID",
2062 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2063 + netdata_systemd_journal_transform_uid, NULL);
2064 +
2065 + facets_register_key_name_transformation(
2066 + facets, "_GID",
2067 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2068 + netdata_systemd_journal_transform_gid, NULL);
2069 +
2070 + facets_register_key_name_transformation(
2071 + facets, "OBJECT_GID",
2072 + q.default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW,
2073 + netdata_systemd_journal_transform_gid, NULL);
2074 +
2075 + facets_register_key_name_transformation(
2076 + facets, "_CAP_EFFECTIVE",
2077 + FACET_KEY_OPTION_TRANSFORM_VIEW,
2078 + netdata_systemd_journal_transform_cap_effective, NULL);
2079 +
2080 + facets_register_key_name_transformation(
2081 + facets, "_AUDIT_LOGINUID",
2082 + FACET_KEY_OPTION_TRANSFORM_VIEW,
2083 + netdata_systemd_journal_transform_uid, NULL);
2084 +
2085 + facets_register_key_name_transformation(
2086 + facets, "OBJECT_AUDIT_LOGINUID",
2087 + FACET_KEY_OPTION_TRANSFORM_VIEW,
2088 + netdata_systemd_journal_transform_uid, NULL);
2089 +
2090 + facets_register_key_name_transformation(
2091 + facets, "_SOURCE_REALTIME_TIMESTAMP",
2092 + FACET_KEY_OPTION_TRANSFORM_VIEW,
2093 + netdata_systemd_journal_transform_timestamp_usec, NULL);
2094 +
2095 // ------------------------------------------------------------------------
2096 // put this request into the progress db
2097
@@ -1864,71 +2103,70 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
2103 time_t now_s = now_realtime_sec();
2104 time_t expires = now_s + 1;
2105
1867 - if(!after_s && !before_s) {
1868 - before_s = now_s;
1869 - after_s = before_s - SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION;
2106 + if(!q.after_s && !q.before_s) {
2107 + q.before_s = now_s;
2108 + q.after_s = q.before_s - SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION;
2109 }
2110 else
1872 - rrdr_relative_window_to_absolute(&after_s, &before_s, now_s);
2111 + rrdr_relative_window_to_absolute(&q.after_s, &q.before_s, now_s);
2112
1874 - if(after_s > before_s) {
1875 - time_t tmp = after_s;
1876 - after_s = before_s;
1877 - before_s = tmp;
2113 + if(q.after_s > q.before_s) {
2114 + time_t tmp = q.after_s;
2115 + q.after_s = q.before_s;
2116 + q.before_s = tmp;
2117 }
2118
1880 - if(after_s == before_s)
1881 - after_s = before_s - SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION;
1882 -
1883 - if(!last)
1884 - last = SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY;
2119 + if(q.after_s == q.before_s)
2120 + q.after_s = q.before_s - SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION;
2121
2122 + if(!q.last)
2123 + q.last = SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY;
2124
2125 // ------------------------------------------------------------------------
2126 // set query time-frame, anchors and direction
2127
2128 fqs->transaction = transaction;
1891 - fqs->after_ut = after_s * USEC_PER_SEC;
1892 - fqs->before_ut = (before_s * USEC_PER_SEC) + USEC_PER_SEC - 1;
1893 - fqs->if_modified_since = if_modified_since;
1894 - fqs->data_only = data_only;
1895 - fqs->delta = (fqs->data_only) ? delta : false;
1896 - fqs->tail = (fqs->data_only && fqs->if_modified_since) ? tail : false;
1897 - fqs->sources = sources;
1898 - fqs->source_type = source_type;
1899 - fqs->entries = last;
2129 + fqs->after_ut = q.after_s * USEC_PER_SEC;
2130 + fqs->before_ut = (q.before_s * USEC_PER_SEC) + USEC_PER_SEC - 1;
2131 + fqs->if_modified_since = q.if_modified_since;
2132 + fqs->data_only = q.data_only;
2133 + fqs->delta = (fqs->data_only) ? q.delta : false;
2134 + fqs->tail = (fqs->data_only && fqs->if_modified_since) ? q.tail : false;
2135 + fqs->sources = q.sources;
2136 + fqs->source_type = q.source_type;
2137 + fqs->entries = q.last;
2138 fqs->last_modified = 0;
1901 - fqs->filters = filters;
1902 - fqs->query = (query && *query) ? query : NULL;
1903 - fqs->histogram = (chart && *chart) ? chart : NULL;
1904 - fqs->direction = direction;
1905 - fqs->anchor.start_ut = anchor;
2139 + fqs->filters = q.filters;
2140 + fqs->query = (q.query && *q.query) ? q.query : NULL;
2141 + fqs->histogram = (q.chart && *q.chart) ? q.chart : NULL;
2142 + fqs->direction = q.direction;
2143 + fqs->anchor.start_ut = q.anchor;
2144 fqs->anchor.stop_ut = 0;
1907 - fqs->sampling = sampling;
2145 + fqs->sampling = q.sampling;
2146
2147 if(fqs->anchor.start_ut && fqs->tail) {
2148 // a tail request
2149 // we need the top X entries from BEFORE
2150 // but, we need to calculate the facets and the
2151 // histogram up to the anchor
1914 - fqs->direction = direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2152 + fqs->direction = q.direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2153 fqs->anchor.start_ut = 0;
1916 - fqs->anchor.stop_ut = anchor;
2154 + fqs->anchor.stop_ut = q.anchor;
2155 }
2156
1919 - if(anchor && anchor < fqs->after_ut) {
2157 + if(q.anchor && q.anchor < fqs->after_ut) {
2158 log_fqs(fqs, "received anchor is too small for query timeframe, ignoring anchor");
1921 - anchor = 0;
2159 + q.anchor = 0;
2160 fqs->anchor.start_ut = 0;
2161 fqs->anchor.stop_ut = 0;
1924 - fqs->direction = direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2162 + fqs->direction = q.direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2163 }
1926 - else if(anchor > fqs->before_ut) {
2164 + else if(q.anchor > fqs->before_ut) {
2165 log_fqs(fqs, "received anchor is too big for query timeframe, ignoring anchor");
1928 - anchor = 0;
2166 + q.anchor = 0;
2167 fqs->anchor.start_ut = 0;
2168 fqs->anchor.stop_ut = 0;
1931 - fqs->direction = direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2169 + fqs->direction = q.direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2170 }
2171
2172 facets_set_anchor(facets, fqs->anchor.start_ut, fqs->anchor.stop_ut, fqs->direction);
@@ -1945,8 +2183,8 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
2183 facets_set_query(facets, fqs->query);
2184
2185 #ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
1948 - fqs->slice = slice;
1949 - if(slice)
2186 + fqs->slice = q.slice;
2187 + if(q.slice)
2188 facets_enable_slice_mode(facets);
2189 #else
2190 fqs->slice = false;
@@ -1971,7 +2209,7 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
2209 buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_AFTER, fqs->after_ut / USEC_PER_SEC);
2210 buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_BEFORE, fqs->before_ut / USEC_PER_SEC);
2211 buffer_json_member_add_uint64(wb, "if_modified_since", fqs->if_modified_since);
1974 - buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_ANCHOR, anchor);
2212 + buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_ANCHOR, q.anchor);
2213 buffer_json_member_add_string(wb, JOURNAL_PARAMETER_DIRECTION, fqs->direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward");
2214 buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_LAST, fqs->entries);
2215 buffer_json_member_add_string(wb, JOURNAL_PARAMETER_QUERY, fqs->query);
@@ -1985,7 +2223,7 @@ void function_systemd_journal(const char *transaction, char *function, usec_t *s
2223
2224 int response;
2225
1988 - if(info) {
2226 + if(q.info) {
2227 facets_accepted_parameters_to_json_array(facets, wb, false);
2228 buffer_json_member_add_array(wb, "required_params");
2229 {
@@ -2033,7 +2271,8 @@ output:
2271 netdata_mutex_unlock(&stdout_mutex);
2272
2273 cleanup:
2036 - simple_pattern_free(sources);
2274 + freez((void *)q.query);
2275 + freez((void *)q.chart);
2276 + simple_pattern_free(q.sources);
2277 facets_destroy(facets);
2038 - buffer_free(wb);
2278 }
src/collectors/systemd-journal.plugin/systemd-main.c
+2 -1
@@ -46,7 +46,8 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
46
47 bool cancelled = false;
48 usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC;
49 - char buf[] = "systemd-journal after:-8640000 before:0 direction:backward last:200 data_only:false slice:true source:all";
49 + // char buf[] = "systemd-journal after:-8640000 before:0 direction:backward last:200 data_only:false slice:true source:all";
50 + char buf[] = "systemd-journal after:-8640000 before:0 direction:backward last:200 data_only:false slice:true facets: source:all";
51 // char buf[] = "systemd-journal after:1695332964 before:1695937764 direction:backward last:100 slice:true source:all DHKucpqUoe1:PtVoyIuX.MU";
52 // char buf[] = "systemd-journal after:1694511062 before:1694514662 anchor:1694514122024403";
53 function_systemd_journal("123", buf, &stop_monotonic_ut, &cancelled,
src/collectors/tc.plugin/plugin_tc.c
+1 -1
@@ -928,7 +928,7 @@ void *tc_main(void *ptr) {
928 }
929
930 char buffer[TC_LINE_MAX+1] = "";
931 - while(fgets(buffer, TC_LINE_MAX, tc_child_instance->child_stdout_fp) != NULL) {
931 + while(fgets(buffer, TC_LINE_MAX, spawn_popen_stdout(tc_child_instance)) != NULL) {
932 if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
933
934 buffer[TC_LINE_MAX] = '\0';
src/daemon/README.md
-3
@@ -104,9 +104,6 @@ The command line options of the Netdata 1.10.0 version are the following:
104 -W simple-pattern pattern string
105 Check if string matches pattern and exit.
106
107 - -W "claim -token=TOKEN -rooms=ROOM1,ROOM2 url=https://app.netdata.cloud"
108 - Connect the agent to the workspace Rooms pointed to by TOKEN and ROOM*.
109 -
107 Signals netdata handles:
108
109 - HUP Close and reopen log files.
src/daemon/analytics.c
+58 -167
@@ -334,7 +334,7 @@ void analytics_alarms_notifications(void)
334 if (instance) {
335 char line[200 + 1];
336
337 - while (fgets(line, 200, instance->child_stdout_fp) != NULL) {
337 + while (fgets(line, 200, spawn_popen_stdout(instance)) != NULL) {
338 char *end = line;
339 while (*end && *end != '\n')
340 end++;
@@ -375,7 +375,6 @@ static void analytics_get_install_type(struct rrdhost_system_info *system_info)
375 void analytics_https(void)
376 {
377 BUFFER *b = buffer_create(30, NULL);
378 -#ifdef ENABLE_HTTPS
378 analytics_exporting_connectors_ssl(b);
379
380 buffer_strcat(b, netdata_ssl_streaming_sender_ctx &&
@@ -383,9 +382,6 @@ void analytics_https(void)
382 SSL_connection(&localhost->sender->ssl) ? "streaming|" : "|");
383
384 buffer_strcat(b, netdata_ssl_web_server_ctx ? "web" : "");
386 -#else
387 - buffer_strcat(b, "||");
388 -#endif
385
386 analytics_set_data_str(&analytics_data.netdata_config_https_available, (char *)buffer_tostring(b));
387 buffer_free(b);
@@ -468,13 +464,8 @@ void analytics_alarms(void)
464 */
465 void analytics_misc(void)
466 {
471 -#ifdef ENABLE_ACLK
467 analytics_set_data(&analytics_data.netdata_host_cloud_available, "true");
468 analytics_set_data_str(&analytics_data.netdata_host_aclk_implementation, "Next Generation");
474 -#else
475 - analytics_set_data(&analytics_data.netdata_host_cloud_available, "false");
476 - analytics_set_data_str(&analytics_data.netdata_host_aclk_implementation, "");
477 -#endif
469
470 analytics_data.exporting_enabled = appconfig_get_boolean(&exporting_config, CONFIG_SECTION_EXPORTING, "enabled", CONFIG_BOOLEAN_NO);
471 analytics_set_data(&analytics_data.netdata_config_exporting_enabled, analytics_data.exporting_enabled ? "true" : "false");
@@ -495,13 +486,11 @@ void analytics_misc(void)
486
487 void analytics_aclk(void)
488 {
498 -#ifdef ENABLE_ACLK
499 - if (aclk_connected) {
489 + if (aclk_online()) {
490 analytics_set_data(&analytics_data.netdata_host_aclk_available, "true");
491 analytics_set_data_str(&analytics_data.netdata_host_aclk_protocol, "New");
492 }
493 else
504 -#endif
494 analytics_set_data(&analytics_data.netdata_host_aclk_available, "false");
495 }
496
@@ -535,9 +524,7 @@ void analytics_gather_mutable_meta_data(void)
524 analytics_set_data(
525 &analytics_data.netdata_config_is_parent, (rrdhost_hosts_available() > 1 || configured_as_parent()) ? "true" : "false");
526
538 - char *claim_id = get_agent_claimid();
539 - analytics_set_data(&analytics_data.netdata_host_agent_claimed, claim_id ? "true" : "false");
540 - freez(claim_id);
527 + analytics_set_data(&analytics_data.netdata_host_agent_claimed, is_agent_claimed() ? "true" : "false");
528
529 {
530 char b[21];
@@ -627,46 +614,15 @@ cleanup:
614 return NULL;
615 }
616
630 -static const char *verify_required_directory(const char *dir)
631 -{
632 - if (chdir(dir) == -1)
633 - fatal("Cannot change directory to '%s'", dir);
634 -
635 - DIR *d = opendir(dir);
636 - if (!d)
637 - fatal("Cannot examine the contents of directory '%s'", dir);
638 - closedir(d);
639 -
640 - return dir;
641 -}
642 -
643 -static const char *verify_or_create_required_directory(const char *dir) {
644 - int result;
645 -
646 - result = mkdir(dir, 0755);
647 -
648 - if (result != 0 && errno != EEXIST)
649 - fatal("Cannot create required directory '%s'", dir);
650 -
651 - return verify_required_directory(dir);
652 -}
653 -
617 /*
618 * This is called after the rrdinit
619 * These values will be sent on the START event
620 */
658 -void set_late_global_environment(struct rrdhost_system_info *system_info)
621 +void set_late_analytics_variables(struct rrdhost_system_info *system_info)
622 {
623 analytics_set_data(&analytics_data.netdata_config_stream_enabled, default_rrdpush_enabled ? "true" : "false");
624 analytics_set_data_str(&analytics_data.netdata_config_memory_mode, (char *)rrd_memory_mode_name(default_rrd_memory_mode));
662 -
663 -#ifdef DISABLE_CLOUD
664 - analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "false");
665 -#else
666 - analytics_set_data(
667 - &analytics_data.netdata_host_cloud_enabled,
668 - appconfig_get_boolean_ondemand(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", netdata_cloud_enabled) ? "true" : "false");
669 -#endif
625 + analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "true");
626
627 #ifdef ENABLE_DBENGINE
628 {
@@ -679,11 +635,7 @@ void set_late_global_environment(struct rrdhost_system_info *system_info)
635 }
636 #endif
637
682 -#ifdef ENABLE_HTTPS
638 analytics_set_data(&analytics_data.netdata_config_https_enabled, "true");
684 -#else
685 - analytics_set_data(&analytics_data.netdata_config_https_enabled, "false");
686 -#endif
639
640 if (web_server_mode == WEB_SERVER_MODE_NONE)
641 analytics_set_data(&analytics_data.netdata_config_web_enabled, "false");
@@ -831,119 +783,6 @@ void get_system_timezone(void)
783 }
784 }
785
834 -void set_global_environment() {
835 - {
836 - char b[16];
837 - snprintfz(b, sizeof(b) - 1, "%d", default_rrd_update_every);
838 - setenv("NETDATA_UPDATE_EVERY", b, 1);
839 - }
840 -
841 - setenv("NETDATA_VERSION", NETDATA_VERSION, 1);
842 - setenv("NETDATA_HOSTNAME", netdata_configured_hostname, 1);
843 - setenv("NETDATA_CONFIG_DIR", verify_required_directory(netdata_configured_user_config_dir), 1);
844 - setenv("NETDATA_USER_CONFIG_DIR", verify_required_directory(netdata_configured_user_config_dir), 1);
845 - setenv("NETDATA_STOCK_CONFIG_DIR", verify_required_directory(netdata_configured_stock_config_dir), 1);
846 - setenv("NETDATA_PLUGINS_DIR", verify_required_directory(netdata_configured_primary_plugins_dir), 1);
847 - setenv("NETDATA_WEB_DIR", verify_required_directory(netdata_configured_web_dir), 1);
848 - setenv("NETDATA_CACHE_DIR", verify_or_create_required_directory(netdata_configured_cache_dir), 1);
849 - setenv("NETDATA_LIB_DIR", verify_or_create_required_directory(netdata_configured_varlib_dir), 1);
850 - setenv("NETDATA_LOCK_DIR", verify_or_create_required_directory(netdata_configured_lock_dir), 1);
851 - setenv("NETDATA_LOG_DIR", verify_or_create_required_directory(netdata_configured_log_dir), 1);
852 - setenv("NETDATA_HOST_PREFIX", netdata_configured_host_prefix, 1);
853 -
854 - {
855 - BUFFER *user_plugins_dirs = buffer_create(FILENAME_MAX, NULL);
856 -
857 - for (size_t i = 1; i < PLUGINSD_MAX_DIRECTORIES && plugin_directories[i]; i++) {
858 - if (i > 1)
859 - buffer_strcat(user_plugins_dirs, " ");
860 - buffer_strcat(user_plugins_dirs, plugin_directories[i]);
861 - }
862 -
863 - setenv("NETDATA_USER_PLUGINS_DIRS", buffer_tostring(user_plugins_dirs), 1);
864 -
865 - buffer_free(user_plugins_dirs);
866 - }
867 -
868 - analytics_data.data_length = 0;
869 - analytics_set_data(&analytics_data.netdata_config_stream_enabled, "null");
870 - analytics_set_data(&analytics_data.netdata_config_memory_mode, "null");
871 - analytics_set_data(&analytics_data.netdata_config_exporting_enabled, "null");
872 - analytics_set_data(&analytics_data.netdata_exporting_connectors, "null");
873 - analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, "null");
874 - analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, "null");
875 - analytics_set_data(&analytics_data.netdata_allmetrics_json_used, "null");
876 - analytics_set_data(&analytics_data.netdata_dashboard_used, "null");
877 - analytics_set_data(&analytics_data.netdata_collectors, "null");
878 - analytics_set_data(&analytics_data.netdata_collectors_count, "null");
879 - analytics_set_data(&analytics_data.netdata_buildinfo, "null");
880 - analytics_set_data(&analytics_data.netdata_config_page_cache_size, "null");
881 - analytics_set_data(&analytics_data.netdata_config_multidb_disk_quota, "null");
882 - analytics_set_data(&analytics_data.netdata_config_https_enabled, "null");
883 - analytics_set_data(&analytics_data.netdata_config_web_enabled, "null");
884 - analytics_set_data(&analytics_data.netdata_config_release_channel, "null");
885 - analytics_set_data(&analytics_data.netdata_mirrored_host_count, "null");
886 - analytics_set_data(&analytics_data.netdata_mirrored_hosts_reachable, "null");
887 - analytics_set_data(&analytics_data.netdata_mirrored_hosts_unreachable, "null");
888 - analytics_set_data(&analytics_data.netdata_notification_methods, "null");
889 - analytics_set_data(&analytics_data.netdata_alarms_normal, "null");
890 - analytics_set_data(&analytics_data.netdata_alarms_warning, "null");
891 - analytics_set_data(&analytics_data.netdata_alarms_critical, "null");
892 - analytics_set_data(&analytics_data.netdata_charts_count, "null");
893 - analytics_set_data(&analytics_data.netdata_metrics_count, "null");
894 - analytics_set_data(&analytics_data.netdata_config_is_parent, "null");
895 - analytics_set_data(&analytics_data.netdata_config_hosts_available, "null");
896 - analytics_set_data(&analytics_data.netdata_host_cloud_available, "null");
897 - analytics_set_data(&analytics_data.netdata_host_aclk_implementation, "null");
898 - analytics_set_data(&analytics_data.netdata_host_aclk_available, "null");
899 - analytics_set_data(&analytics_data.netdata_host_aclk_protocol, "null");
900 - analytics_set_data(&analytics_data.netdata_host_agent_claimed, "null");
901 - analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "null");
902 - analytics_set_data(&analytics_data.netdata_config_https_available, "null");
903 - analytics_set_data(&analytics_data.netdata_install_type, "null");
904 - analytics_set_data(&analytics_data.netdata_config_is_private_registry, "null");
905 - analytics_set_data(&analytics_data.netdata_config_use_private_registry, "null");
906 - analytics_set_data(&analytics_data.netdata_config_oom_score, "null");
907 - analytics_set_data(&analytics_data.netdata_prebuilt_distro, "null");
908 - analytics_set_data(&analytics_data.netdata_fail_reason, "null");
909 -
910 - analytics_data.prometheus_hits = 0;
911 - analytics_data.shell_hits = 0;
912 - analytics_data.json_hits = 0;
913 - analytics_data.dashboard_hits = 0;
914 - analytics_data.charts_count = 0;
915 - analytics_data.metrics_count = 0;
916 - analytics_data.exporting_enabled = false;
917 -
918 - char *default_port = appconfig_get(&netdata_config, CONFIG_SECTION_WEB, "default port", NULL);
919 - int clean = 0;
920 - if (!default_port) {
921 - default_port = strdupz("19999");
922 - clean = 1;
923 - }
924 -
925 - setenv("NETDATA_LISTEN_PORT", default_port, 1);
926 - if (clean)
927 - freez(default_port);
928 -
929 - // set the path we need
930 - char path[4096], *p = getenv("PATH");
931 - if (!p) p = "/bin:/usr/bin";
932 - snprintfz(path, sizeof(path), "%s:%s", p, "/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin");
933 - setenv("PATH", config_get(CONFIG_SECTION_ENV_VARS, "PATH", path), 1);
934 -
935 - // python options
936 - p = getenv("PYTHONPATH");
937 - if (!p) p = "";
938 - setenv("PYTHONPATH", config_get(CONFIG_SECTION_ENV_VARS, "PYTHONPATH", p), 1);
939 -
940 - // disable buffering for python plugins
941 - setenv("PYTHONUNBUFFERED", "1", 1);
942 -
943 - // switch to standard locale for plugins
944 - setenv("LC_ALL", "C", 1);
945 -}
946 -
786 void analytics_statistic_send(const analytics_statistic_t *statistic) {
787 if (!statistic)
788 return;
@@ -1053,7 +892,7 @@ void analytics_statistic_send(const analytics_statistic_t *statistic) {
892 POPEN_INSTANCE *instance = spawn_popen_run(command_to_run);
893 if (instance) {
894 char buffer[4 + 1];
1056 - char *s = fgets(buffer, 4, instance->child_stdout_fp);
895 + char *s = fgets(buffer, 4, spawn_popen_stdout(instance));
896 int exit_code = spawn_popen_wait(instance);
897 if (exit_code)
898
@@ -1075,6 +914,58 @@ void analytics_statistic_send(const analytics_statistic_t *statistic) {
914 freez(command_to_run);
915 }
916
917 +void analytics_reset(void) {
918 + analytics_data.data_length = 0;
919 + analytics_set_data(&analytics_data.netdata_config_stream_enabled, "null");
920 + analytics_set_data(&analytics_data.netdata_config_memory_mode, "null");
921 + analytics_set_data(&analytics_data.netdata_config_exporting_enabled, "null");
922 + analytics_set_data(&analytics_data.netdata_exporting_connectors, "null");
923 + analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, "null");
924 + analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, "null");
925 + analytics_set_data(&analytics_data.netdata_allmetrics_json_used, "null");
926 + analytics_set_data(&analytics_data.netdata_dashboard_used, "null");
927 + analytics_set_data(&analytics_data.netdata_collectors, "null");
928 + analytics_set_data(&analytics_data.netdata_collectors_count, "null");
929 + analytics_set_data(&analytics_data.netdata_buildinfo, "null");
930 + analytics_set_data(&analytics_data.netdata_config_page_cache_size, "null");
931 + analytics_set_data(&analytics_data.netdata_config_multidb_disk_quota, "null");
932 + analytics_set_data(&analytics_data.netdata_config_https_enabled, "null");
933 + analytics_set_data(&analytics_data.netdata_config_web_enabled, "null");
934 + analytics_set_data(&analytics_data.netdata_config_release_channel, "null");
935 + analytics_set_data(&analytics_data.netdata_mirrored_host_count, "null");
936 + analytics_set_data(&analytics_data.netdata_mirrored_hosts_reachable, "null");
937 + analytics_set_data(&analytics_data.netdata_mirrored_hosts_unreachable, "null");
938 + analytics_set_data(&analytics_data.netdata_notification_methods, "null");
939 + analytics_set_data(&analytics_data.netdata_alarms_normal, "null");
940 + analytics_set_data(&analytics_data.netdata_alarms_warning, "null");
941 + analytics_set_data(&analytics_data.netdata_alarms_critical, "null");
942 + analytics_set_data(&analytics_data.netdata_charts_count, "null");
943 + analytics_set_data(&analytics_data.netdata_metrics_count, "null");
944 + analytics_set_data(&analytics_data.netdata_config_is_parent, "null");
945 + analytics_set_data(&analytics_data.netdata_config_hosts_available, "null");
946 + analytics_set_data(&analytics_data.netdata_host_cloud_available, "null");
947 + analytics_set_data(&analytics_data.netdata_host_aclk_implementation, "null");
948 + analytics_set_data(&analytics_data.netdata_host_aclk_available, "null");
949 + analytics_set_data(&analytics_data.netdata_host_aclk_protocol, "null");
950 + analytics_set_data(&analytics_data.netdata_host_agent_claimed, "null");
951 + analytics_set_data(&analytics_data.netdata_host_cloud_enabled, "null");
952 + analytics_set_data(&analytics_data.netdata_config_https_available, "null");
953 + analytics_set_data(&analytics_data.netdata_install_type, "null");
954 + analytics_set_data(&analytics_data.netdata_config_is_private_registry, "null");
955 + analytics_set_data(&analytics_data.netdata_config_use_private_registry, "null");
956 + analytics_set_data(&analytics_data.netdata_config_oom_score, "null");
957 + analytics_set_data(&analytics_data.netdata_prebuilt_distro, "null");
958 + analytics_set_data(&analytics_data.netdata_fail_reason, "null");
959 +
960 + analytics_data.prometheus_hits = 0;
961 + analytics_data.shell_hits = 0;
962 + analytics_data.json_hits = 0;
963 + analytics_data.dashboard_hits = 0;
964 + analytics_data.charts_count = 0;
965 + analytics_data.metrics_count = 0;
966 + analytics_data.exporting_enabled = false;
967 +}
968 +
969 void analytics_init(void)
970 {
971 spinlock_init(&analytics_data.spinlock);
src/daemon/analytics.h
+2 -2
@@ -76,9 +76,8 @@ struct analytics_data {
76 bool exporting_enabled;
77 };
78
79 -void set_late_global_environment(struct rrdhost_system_info *system_info);
79 +void set_late_analytics_variables(struct rrdhost_system_info *system_info);
80 void analytics_free_data(void);
81 -void set_global_environment(void);
81 void analytics_log_shell(void);
82 void analytics_log_json(void);
83 void analytics_log_prometheus(void);
@@ -86,6 +85,7 @@ void analytics_log_dashboard(void);
85 void analytics_gather_mutable_meta_data(void);
86 void analytics_report_oom_score(long long int score);
87 void get_system_timezone(void);
88 +void analytics_reset(void);
89 void analytics_init(void);
90
91 typedef struct {
src/daemon/buildinfo.c
-14
@@ -1069,18 +1069,8 @@ __attribute__((constructor)) void initialize_build_info(void) {
1069 #endif
1070 #endif
1071
1072 -#ifdef ENABLE_ACLK
1072 build_info_set_status(BIB_FEATURE_CLOUD, true);
1073 build_info_set_status(BIB_CONNECTIVITY_ACLK, true);
1075 -#else
1076 - build_info_set_status(BIB_FEATURE_CLOUD, false);
1077 -#ifdef DISABLE_CLOUD
1078 - build_info_set_value(BIB_FEATURE_CLOUD, "disabled");
1079 -#else
1080 - build_info_set_value(BIB_FEATURE_CLOUD, "unavailable");
1081 -#endif
1082 -#endif
1083 -
1074 build_info_set_status(BIB_FEATURE_HEALTH, true);
1075 build_info_set_status(BIB_FEATURE_STREAMING, true);
1076 build_info_set_status(BIB_FEATURE_BACKFILLING, true);
@@ -1126,9 +1116,7 @@ __attribute__((constructor)) void initialize_build_info(void) {
1116 #ifdef ENABLE_WEBRTC
1117 build_info_set_status(BIB_CONNECTIVITY_WEBRTC, true);
1118 #endif
1129 -#ifdef ENABLE_HTTPS
1119 build_info_set_status(BIB_CONNECTIVITY_NATIVE_HTTPS, true);
1131 -#endif
1120 #if defined(HAVE_X509_VERIFY_PARAM_set1_host) && HAVE_X509_VERIFY_PARAM_set1_host == 1
1121 build_info_set_status(BIB_CONNECTIVITY_TLS_HOST_VERIFY, true);
1122 #endif
@@ -1162,9 +1150,7 @@ __attribute__((constructor)) void initialize_build_info(void) {
1150 #ifdef HAVE_LIBDATACHANNEL
1151 build_info_set_status(BIB_LIB_LIBDATACHANNEL, true);
1152 #endif
1165 -#ifdef ENABLE_OPENSSL
1153 build_info_set_status(BIB_LIB_OPENSSL, true);
1167 -#endif
1154 #ifdef ENABLE_JSONC
1155 build_info_set_status(BIB_LIB_JSONC, true);
1156 #endif
src/daemon/commands.c
+38 -30
@@ -47,9 +47,7 @@ static cmd_status_t cmd_ping_execute(char *args, char **message);
47 static cmd_status_t cmd_aclk_state(char *args, char **message);
48 static cmd_status_t cmd_version(char *args, char **message);
49 static cmd_status_t cmd_dumpconfig(char *args, char **message);
50 -#ifdef ENABLE_ACLK
50 static cmd_status_t cmd_remove_node(char *args, char **message);
52 -#endif
51
52 static command_info_t command_info_array[] = {
53 {"help", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY}, // show help menu
@@ -65,9 +63,7 @@ static command_info_t command_info_array[] = {
63 {"aclk-state", cmd_aclk_state, CMD_TYPE_ORTHOGONAL},
64 {"version", cmd_version, CMD_TYPE_ORTHOGONAL},
65 {"dumpconfig", cmd_dumpconfig, CMD_TYPE_ORTHOGONAL},
68 -#ifdef ENABLE_ACLK
66 {"remove-stale-node", cmd_remove_node, CMD_TYPE_ORTHOGONAL}
70 -#endif
67 };
68
69 /* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */
@@ -135,10 +131,8 @@ static cmd_status_t cmd_help_execute(char *args, char **message)
131 " Returns current state of ACLK and Cloud connection. (optionally in json).\n"
132 "dumpconfig\n"
133 " Returns the current netdata.conf on stdout.\n"
138 -#ifdef ENABLE_ACLK
134 "remove-stale-node node_id|machine_guid|hostname|ALL_NODES\n"
135 " Unregisters and removes a node from the cloud.\n"
141 -#endif
136 "version\n"
137 " Returns the netdata version.\n",
138 MAX_COMMAND_LENGTH - 1);
@@ -193,17 +187,42 @@ static cmd_status_t cmd_fatal_execute(char *args, char **message)
187 return CMD_STATUS_SUCCESS;
188 }
189
196 -static cmd_status_t cmd_reload_claiming_state_execute(char *args, char **message)
197 -{
198 - (void)args;
199 - (void)message;
200 -#if defined(DISABLE_CLOUD) || !defined(ENABLE_ACLK)
201 - netdata_log_info("The claiming feature has been explicitly disabled");
202 - *message = strdupz("This agent cannot be claimed, it was built without support for Cloud");
203 - return CMD_STATUS_FAILURE;
204 -#endif
205 - netdata_log_info("COMMAND: Reloading Agent Claiming configuration.");
206 - claim_reload_all();
190 +static cmd_status_t cmd_reload_claiming_state_execute(char *args __maybe_unused, char **message) {
191 + char msg[1024];
192 +
193 + CLOUD_STATUS status = claim_reload_and_wait_online();
194 + switch(status) {
195 + case CLOUD_STATUS_ONLINE:
196 + snprintfz(msg, sizeof(msg),
197 + "Netdata Agent is claimed to Netdata Cloud and is currently online.");
198 + break;
199 +
200 + case CLOUD_STATUS_BANNED:
201 + snprintfz(msg, sizeof(msg),
202 + "Netdata Agent is claimed to Netdata Cloud, but it is banned.");
203 + break;
204 +
205 + default:
206 + case CLOUD_STATUS_AVAILABLE:
207 + snprintfz(msg, sizeof(msg),
208 + "Netdata Agent is not claimed to Netdata Cloud: %s",
209 + claim_agent_failure_reason_get());
210 + break;
211 +
212 + case CLOUD_STATUS_OFFLINE:
213 + snprintfz(msg, sizeof(msg),
214 + "Netdata Agent is claimed to Netdata Cloud, but it is currently offline: %s",
215 + cloud_status_aclk_offline_reason());
216 + break;
217 +
218 + case CLOUD_STATUS_INDIRECT:
219 + snprintfz(msg, sizeof(msg),
220 + "Netdata Agent is not claimed to Netdata Cloud, but it is currently online via parent.");
221 + break;
222 + }
223 +
224 + *message = strdupz(msg);
225 +
226 return CMD_STATUS_SUCCESS;
227 }
228
@@ -306,17 +325,10 @@ static cmd_status_t cmd_ping_execute(char *args, char **message)
325 static cmd_status_t cmd_aclk_state(char *args, char **message)
326 {
327 netdata_log_info("COMMAND: Reopening aclk/cloud state.");
309 -#ifdef ENABLE_ACLK
328 if (strstr(args, "json"))
329 *message = aclk_state_json();
330 else
331 *message = aclk_state();
314 -#else
315 - if (strstr(args, "json"))
316 - *message = strdupz("{\"aclk-available\":false}");
317 - else
318 - *message = strdupz("ACLK Available: No");
319 -#endif
332
333 return CMD_STATUS_SUCCESS;
334 }
@@ -338,14 +350,12 @@ static cmd_status_t cmd_dumpconfig(char *args, char **message)
350 (void)args;
351
352 BUFFER *wb = buffer_create(1024, NULL);
341 - config_generate(wb, 0);
353 + netdata_conf_generate(wb, 0);
354 *message = strdupz(buffer_tostring(wb));
355 buffer_free(wb);
356 return CMD_STATUS_SUCCESS;
357 }
358
347 -#ifdef ENABLE_ACLK
348 -
359 static int remove_ephemeral_host(BUFFER *wb, RRDHOST *host, bool report_error)
360 {
361 if (host == localhost) {
@@ -365,8 +375,7 @@ static int remove_ephemeral_host(BUFFER *wb, RRDHOST *host, bool report_error)
375 sql_set_host_label(&host->host_uuid, "_is_ephemeral", "true");
376 aclk_host_state_update(host, 0, 0);
377 unregister_node(host->machine_guid);
368 - freez(host->node_id);
369 - host->node_id = NULL;
378 + uuid_clear(host->node_id);
379 buffer_sprintf(wb, "Unregistering node with machine guid %s, hostname = %s", host->machine_guid, rrdhost_hostname(host));
380 rrd_wrlock();
381 rrdhost_free___while_having_rrd_wrlock(host, true);
@@ -438,7 +447,6 @@ done:
447 buffer_free(wb);
448 return CMD_STATUS_SUCCESS;
449 }
441 -#endif
450
451 static void cmd_lock_exclusive(unsigned index)
452 {
src/daemon/commands.h
-2
@@ -20,9 +20,7 @@ typedef enum cmd {
20 CMD_ACLK_STATE,
21 CMD_VERSION,
22 CMD_DUMPCONFIG,
23 -#ifdef ENABLE_ACLK
23 CMD_REMOVE_NODE,
25 -#endif
24 CMD_TOTAL_COMMANDS
25 } cmd_t;
26
src/daemon/common.c
+1 -138
@@ -11,6 +11,7 @@ char *netdata_configured_web_dir = WEB_DIR;
11 char *netdata_configured_cache_dir = CACHE_DIR;
12 char *netdata_configured_varlib_dir = VARLIB_DIR;
13 char *netdata_configured_lock_dir = VARLIB_DIR "/lock";
14 +char *netdata_configured_cloud_dir = VARLIB_DIR "/cloud.d";
15 char *netdata_configured_home_dir = VARLIB_DIR;
16 char *netdata_configured_host_prefix = NULL;
17 char *netdata_configured_timezone = NULL;
@@ -19,12 +20,6 @@ int32_t netdata_configured_utc_offset = 0;
20
21 bool netdata_ready = false;
22
22 -#if defined( DISABLE_CLOUD ) || !defined( ENABLE_ACLK )
23 -int netdata_cloud_enabled = CONFIG_BOOLEAN_NO;
24 -#else
25 -int netdata_cloud_enabled = CONFIG_BOOLEAN_AUTO;
26 -#endif
27 -
23 long get_netdata_cpus(void) {
24 static long processors = 0;
25
@@ -63,135 +58,3 @@ long get_netdata_cpus(void) {
58
59 return processors;
60 }
66 -
67 -const char *cloud_status_to_string(CLOUD_STATUS status) {
68 - switch(status) {
69 - default:
70 - case CLOUD_STATUS_UNAVAILABLE:
71 - return "unavailable";
72 -
73 - case CLOUD_STATUS_AVAILABLE:
74 - return "available";
75 -
76 - case CLOUD_STATUS_DISABLED:
77 - return "disabled";
78 -
79 - case CLOUD_STATUS_BANNED:
80 - return "banned";
81 -
82 - case CLOUD_STATUS_OFFLINE:
83 - return "offline";
84 -
85 - case CLOUD_STATUS_ONLINE:
86 - return "online";
87 - }
88 -}
89 -
90 -CLOUD_STATUS cloud_status(void) {
91 -#ifdef ENABLE_ACLK
92 - if(aclk_disable_runtime)
93 - return CLOUD_STATUS_BANNED;
94 -
95 - if(aclk_connected)
96 - return CLOUD_STATUS_ONLINE;
97 -
98 - if(netdata_cloud_enabled == CONFIG_BOOLEAN_YES) {
99 - char *agent_id = get_agent_claimid();
100 - bool claimed = agent_id != NULL;
101 - freez(agent_id);
102 -
103 - if(claimed)
104 - return CLOUD_STATUS_OFFLINE;
105 - }
106 -
107 - if(netdata_cloud_enabled != CONFIG_BOOLEAN_NO)
108 - return CLOUD_STATUS_AVAILABLE;
109 -
110 - return CLOUD_STATUS_DISABLED;
111 -#else
112 - return CLOUD_STATUS_UNAVAILABLE;
113 -#endif
114 -}
115 -
116 -time_t cloud_last_change(void) {
117 -#ifdef ENABLE_ACLK
118 - time_t ret = MAX(last_conn_time_mqtt, last_disconnect_time);
119 - if(!ret) ret = netdata_start_time;
120 - return ret;
121 -#else
122 - return netdata_start_time;
123 -#endif
124 -}
125 -
126 -time_t cloud_next_connection_attempt(void) {
127 -#ifdef ENABLE_ACLK
128 - return next_connection_attempt;
129 -#else
130 - return 0;
131 -#endif
132 -}
133 -
134 -size_t cloud_connection_id(void) {
135 -#ifdef ENABLE_ACLK
136 - return aclk_connection_counter;
137 -#else
138 - return 0;
139 -#endif
140 -}
141 -
142 -const char *cloud_offline_reason() {
143 -#ifdef ENABLE_ACLK
144 - if(!netdata_cloud_enabled)
145 - return "disabled";
146 -
147 - if(aclk_disable_runtime)
148 - return "banned";
149 -
150 - return aclk_status_to_string();
151 -#else
152 - return "disabled";
153 -#endif
154 -}
155 -
156 -const char *cloud_base_url() {
157 -#ifdef ENABLE_ACLK
158 - return aclk_cloud_base_url;
159 -#else
160 - return NULL;
161 -#endif
162 -}
163 -
164 -CLOUD_STATUS buffer_json_cloud_status(BUFFER *wb, time_t now_s) {
165 - CLOUD_STATUS status = cloud_status();
166 -
167 - buffer_json_member_add_object(wb, "cloud");
168 - {
169 - size_t id = cloud_connection_id();
170 - time_t last_change = cloud_last_change();
171 - time_t next_connect = cloud_next_connection_attempt();
172 - buffer_json_member_add_uint64(wb, "id", id);
173 - buffer_json_member_add_string(wb, "status", cloud_status_to_string(status));
174 - buffer_json_member_add_time_t(wb, "since", last_change);
175 - buffer_json_member_add_time_t(wb, "age", now_s - last_change);
176 -
177 - if (status != CLOUD_STATUS_ONLINE)
178 - buffer_json_member_add_string(wb, "reason", cloud_offline_reason());
179 -
180 - if (status == CLOUD_STATUS_OFFLINE && next_connect > now_s) {
181 - buffer_json_member_add_time_t(wb, "next_check", next_connect);
182 - buffer_json_member_add_time_t(wb, "next_in", next_connect - now_s);
183 - }
184 -
185 - if (cloud_base_url())
186 - buffer_json_member_add_string(wb, "url", cloud_base_url());
187 -
188 - char *claim_id = get_agent_claimid();
189 - if(claim_id) {
190 - buffer_json_member_add_string(wb, "claim_id", claim_id);
191 - freez(claim_id);
192 - }
193 - }
194 - buffer_json_object_close(wb); // cloud
195 -
196 - return status;
197 -}
src/daemon/common.h
+6 -21
@@ -4,7 +4,7 @@
4 #define NETDATA_COMMON_H 1
5
6 #include "libnetdata/libnetdata.h"
7 -#include "event_loop.h"
7 +#include "libuv_workers.h"
8
9 // ----------------------------------------------------------------------------
10 // shortcuts for the default netdata configuration
@@ -26,7 +26,7 @@
26 #define config_exists(section, name) appconfig_exists(&netdata_config, section, name)
27 #define config_move(section_old, name_old, section_new, name_new) appconfig_move(&netdata_config, section_old, name_old, section_new, name_new)
28
29 -#define config_generate(buffer, only_changed) appconfig_generate(&netdata_config, buffer, only_changed)
29 +#define netdata_conf_generate(buffer, only_changed) appconfig_generate(&netdata_config, buffer, only_changed, true)
30
31 #define config_section_destroy(section) appconfig_section_destroy_non_loaded(&netdata_config, section)
32 #define config_section_option_destroy(section, name) appconfig_section_option_destroy_non_loaded(&netdata_config, section, name)
@@ -34,6 +34,8 @@
34 // ----------------------------------------------------------------------------
35 // netdata include files
36
37 +#include "web/api/maps/maps.h"
38 +
39 #include "daemon/config/dyncfg.h"
40
41 #include "global_statistics.h"
@@ -103,6 +105,7 @@ extern char *netdata_configured_web_dir;
105 extern char *netdata_configured_cache_dir;
106 extern char *netdata_configured_varlib_dir;
107 extern char *netdata_configured_lock_dir;
108 +extern char *netdata_configured_cloud_dir;
109 extern char *netdata_configured_home_dir;
110 extern char *netdata_configured_host_prefix;
111 extern char *netdata_configured_timezone;
@@ -111,28 +114,10 @@ extern int32_t netdata_configured_utc_offset;
114 extern int netdata_anonymous_statistics_enabled;
115
116 extern bool netdata_ready;
114 -extern int netdata_cloud_enabled;
115 -
117 extern time_t netdata_start_time;
118
119 long get_netdata_cpus(void);
120
120 -typedef enum __attribute__((packed)) {
121 - CLOUD_STATUS_UNAVAILABLE = 0, // cloud and aclk functionality is not available on this agent
122 - CLOUD_STATUS_AVAILABLE, // cloud and aclk functionality is available, but the agent is not claimed
123 - CLOUD_STATUS_DISABLED, // cloud and aclk functionality is available, but it is disabled
124 - CLOUD_STATUS_BANNED, // the agent has been banned from cloud
125 - CLOUD_STATUS_OFFLINE, // the agent tries to connect to cloud, but cannot do it
126 - CLOUD_STATUS_ONLINE, // the agent is connected to cloud
127 -} CLOUD_STATUS;
128 -
129 -const char *cloud_status_to_string(CLOUD_STATUS status);
130 -CLOUD_STATUS cloud_status(void);
131 -time_t cloud_last_change(void);
132 -time_t cloud_next_connection_attempt(void);
133 -size_t cloud_connection_id(void);
134 -const char *cloud_offline_reason(void);
135 -const char *cloud_base_url(void);
136 -CLOUD_STATUS buffer_json_cloud_status(BUFFER *wb, time_t now_s);
121 +void set_environment_for_plugins_and_scripts(void);
122
123 #endif /* NETDATA_COMMON_H */
src/daemon/config/dyncfg-echo.c
+3 -3
@@ -96,7 +96,7 @@ void dyncfg_echo(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id __maybe
96 dyncfg_echo_cb, e,
97 NULL, NULL,
98 NULL, NULL,
99 - NULL, string2str(df->dyncfg.source));
99 + NULL, string2str(df->dyncfg.source), false);
100 }
101
102 // ----------------------------------------------------------------------------
@@ -129,7 +129,7 @@ void dyncfg_echo_update(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id)
129 dyncfg_echo_cb, e,
130 NULL, NULL,
131 NULL, NULL,
132 - df->dyncfg.payload, string2str(df->dyncfg.source));
132 + df->dyncfg.payload, string2str(df->dyncfg.source), false);
133 }
134
135 // ----------------------------------------------------------------------------
@@ -164,7 +164,7 @@ static void dyncfg_echo_payload_add(const DICTIONARY_ITEM *item_template __maybe
164 dyncfg_echo_cb, e,
165 NULL, NULL,
166 NULL, NULL,
167 - df_job->dyncfg.payload, string2str(df_job->dyncfg.source));
167 + df_job->dyncfg.payload, string2str(df_job->dyncfg.source), false);
168 }
169
170 void dyncfg_echo_add(const DICTIONARY_ITEM *item_template, const DICTIONARY_ITEM *item_job, DYNCFG *df_template, DYNCFG *df_job, const char *template_id, const char *job_name) {
src/daemon/config/dyncfg-unittest.c
+1 -1
@@ -473,7 +473,7 @@ static int dyncfg_unittest_run(const char *cmd, BUFFER *wb, const char *payload,
473 NULL, NULL,
474 NULL, NULL,
475 NULL, NULL,
476 - pld, source);
476 + pld, source, false);
477 if(!DYNCFG_RESP_SUCCESS(rc)) {
478 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG UNITTEST: failed to run: %s; returned code %d", cmd, rc);
479 dyncfg_unittest_register_error(NULL, NULL);
src/daemon/daemon.c
+17 -30
@@ -3,34 +3,24 @@
3 #include "common.h"
4 #include <sched.h>
5
6 -char pidfile[FILENAME_MAX + 1] = "";
7 -char claiming_directory[FILENAME_MAX + 1];
8 -char netdata_exe_path[FILENAME_MAX + 1];
9 -char netdata_exe_file[FILENAME_MAX + 1];
6 +char *pidfile = NULL;
7 +char *netdata_exe_path = NULL;
8
9 void get_netdata_execution_path(void) {
12 - int ret;
13 - size_t exepath_size = 0;
14 - struct passwd *passwd = NULL;
15 - char *user = NULL;
16 -
17 - passwd = getpwuid(getuid());
18 - user = (passwd && passwd->pw_name) ? passwd->pw_name : "";
19 -
20 - exepath_size = sizeof(netdata_exe_file) - 1;
21 - ret = uv_exepath(netdata_exe_file, &exepath_size);
22 - if (0 != ret) {
23 - netdata_log_error("uv_exepath(\"%s\", %u) (user: %s) failed (%s).", netdata_exe_file, (unsigned)exepath_size, user,
24 - uv_strerror(ret));
25 - fatal("Cannot start netdata without getting execution path.");
10 + struct passwd *passwd = getpwuid(getuid());
11 + char *user = (passwd && passwd->pw_name) ? passwd->pw_name : "";
12 +
13 + char b[FILENAME_MAX + 1];
14 + size_t b_size = sizeof(b) - 1;
15 + int ret = uv_exepath(b, &b_size);
16 + if (ret != 0) {
17 + fatal("Cannot start netdata without getting execution path. "
18 + "(uv_exepath(\"%s\", %zu), user: '%s', failed: %s).",
19 + b, b_size, user, uv_strerror(ret));
20 }
21 + b[b_size] = '\0';
22
28 - netdata_exe_file[exepath_size] = '\0';
29 -
30 - // macOS's dirname(3) does not modify passed string
31 - char *tmpdir = strdupz(netdata_exe_file);
32 - strcpy(netdata_exe_path, dirname(tmpdir));
33 - freez(tmpdir);
23 + netdata_exe_path = strdupz(b);
24 }
25
26 static void fix_directory_file_permissions(const char *dirname, uid_t uid, gid_t gid, bool recursive)
@@ -89,7 +79,7 @@ static void prepare_required_directories(uid_t uid, gid_t gid) {
79 change_dir_ownership(netdata_configured_varlib_dir, uid, gid, false);
80 change_dir_ownership(netdata_configured_lock_dir, uid, gid, false);
81 change_dir_ownership(netdata_configured_log_dir, uid, gid, false);
92 - change_dir_ownership(claiming_directory, uid, gid, false);
82 + change_dir_ownership(netdata_configured_cloud_dir, uid, gid, false);
83
84 char filename[FILENAME_MAX + 1];
85 snprintfz(filename, FILENAME_MAX, "%s/registry", netdata_configured_varlib_dir);
@@ -112,7 +102,7 @@ static int become_user(const char *username, int pid_fd) {
102
103 prepare_required_directories(uid, gid);
104
115 - if(pidfile[0]) {
105 + if(pidfile && *pidfile) {
106 if(chown(pidfile, uid, gid) == -1)
107 netdata_log_error("Cannot chown '%s' to %u:%u", pidfile, (unsigned int)uid, (unsigned int)gid);
108 }
@@ -465,7 +455,7 @@ int become_daemon(int dont_fork, const char *user)
455
456 // generate our pid file
457 int pidfd = -1;
468 - if(pidfile[0]) {
458 + if(pidfile && *pidfile) {
459 pidfd = open(pidfile, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
460 if(pidfd >= 0) {
461 if(ftruncate(pidfd, 0) != 0)
@@ -490,9 +480,6 @@ int become_daemon(int dont_fork, const char *user)
480 // never become a problem
481 sched_setscheduler_set();
482
493 - // Set claiming directory based on user config directory with correct ownership
494 - snprintfz(claiming_directory, FILENAME_MAX, "%s/cloud.d", netdata_configured_varlib_dir);
495 -
483 if(user && *user) {
484 if(become_user(user, pidfd) != 0) {
485 netdata_log_error("Cannot become user '%s'. Continuing as we are.", user);
src/daemon/daemon.h
+2 -3
@@ -9,8 +9,7 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re
9
10 void get_netdata_execution_path(void);
11
12 -extern char pidfile[];
13 -extern char netdata_exe_file[];
14 -extern char netdata_exe_path[];
12 +extern char *pidfile;
13 +extern char *netdata_exe_path;
14
15 #endif /* NETDATA_DAEMON_H */
src/daemon/environment.c new
+99
@@ -0,0 +1,99 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "common.h"
4 +
5 +static const char *verify_required_directory(const char *dir)
6 +{
7 + if (chdir(dir) == -1)
8 + fatal("Cannot change directory to '%s'", dir);
9 +
10 + DIR *d = opendir(dir);
11 + if (!d)
12 + fatal("Cannot examine the contents of directory '%s'", dir);
13 + closedir(d);
14 +
15 + return dir;
16 +}
17 +
18 +static const char *verify_or_create_required_directory(const char *dir) {
19 + errno_clear();
20 +
21 + if (mkdir(dir, 0755) != 0 && errno != EEXIST)
22 + fatal("Cannot create required directory '%s'", dir);
23 +
24 + return verify_required_directory(dir);
25 +}
26 +
27 +static const char *verify_or_create_required_private_directory(const char *dir) {
28 + errno_clear();
29 +
30 + if (mkdir(dir, 0770) != 0 && errno != EEXIST)
31 + fatal("Cannot create required directory '%s'", dir);
32 +
33 + return verify_required_directory(dir);
34 +}
35 +
36 +void set_environment_for_plugins_and_scripts(void) {
37 + {
38 + char b[16];
39 + snprintfz(b, sizeof(b) - 1, "%d", default_rrd_update_every);
40 + nd_setenv("NETDATA_UPDATE_EVERY", b, 1);
41 + }
42 +
43 + nd_setenv("NETDATA_VERSION", NETDATA_VERSION, 1);
44 + nd_setenv("NETDATA_HOSTNAME", netdata_configured_hostname, 1);
45 + nd_setenv("NETDATA_CONFIG_DIR", verify_required_directory(netdata_configured_user_config_dir), 1);
46 + nd_setenv("NETDATA_USER_CONFIG_DIR", verify_required_directory(netdata_configured_user_config_dir), 1);
47 + nd_setenv("NETDATA_STOCK_CONFIG_DIR", verify_required_directory(netdata_configured_stock_config_dir), 1);
48 + nd_setenv("NETDATA_PLUGINS_DIR", verify_required_directory(netdata_configured_primary_plugins_dir), 1);
49 + nd_setenv("NETDATA_WEB_DIR", verify_required_directory(netdata_configured_web_dir), 1);
50 + nd_setenv("NETDATA_CACHE_DIR", verify_or_create_required_directory(netdata_configured_cache_dir), 1);
51 + nd_setenv("NETDATA_LIB_DIR", verify_or_create_required_directory(netdata_configured_varlib_dir), 1);
52 + nd_setenv("NETDATA_LOCK_DIR", verify_or_create_required_directory(netdata_configured_lock_dir), 1);
53 + nd_setenv("NETDATA_LOG_DIR", verify_or_create_required_directory(netdata_configured_log_dir), 1);
54 + nd_setenv("NETDATA_HOST_PREFIX", netdata_configured_host_prefix, 1);
55 +
56 + nd_setenv("CLAIMING_DIR", verify_or_create_required_private_directory(netdata_configured_cloud_dir), 1);
57 +
58 + {
59 + BUFFER *user_plugins_dirs = buffer_create(FILENAME_MAX, NULL);
60 +
61 + for (size_t i = 1; i < PLUGINSD_MAX_DIRECTORIES && plugin_directories[i]; i++) {
62 + if (i > 1)
63 + buffer_strcat(user_plugins_dirs, " ");
64 + buffer_strcat(user_plugins_dirs, plugin_directories[i]);
65 + }
66 +
67 + nd_setenv("NETDATA_USER_PLUGINS_DIRS", buffer_tostring(user_plugins_dirs), 1);
68 +
69 + buffer_free(user_plugins_dirs);
70 + }
71 +
72 + char *default_port = appconfig_get(&netdata_config, CONFIG_SECTION_WEB, "default port", NULL);
73 + int clean = 0;
74 + if (!default_port) {
75 + default_port = strdupz("19999");
76 + clean = 1;
77 + }
78 +
79 + nd_setenv("NETDATA_LISTEN_PORT", default_port, 1);
80 + if (clean)
81 + freez(default_port);
82 +
83 + // set the path we need
84 + char path[4096], *p = getenv("PATH");
85 + if (!p) p = "/bin:/usr/bin";
86 + snprintfz(path, sizeof(path), "%s:%s", p, "/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin");
87 + setenv("PATH", config_get(CONFIG_SECTION_ENV_VARS, "PATH", path), 1);
88 +
89 + // python options
90 + p = getenv("PYTHONPATH");
91 + if (!p) p = "";
92 + setenv("PYTHONPATH", config_get(CONFIG_SECTION_ENV_VARS, "PYTHONPATH", p), 1);
93 +
94 + // disable buffering for python plugins
95 + setenv("PYTHONUNBUFFERED", "1", 1);
96 +
97 + // switch to standard locale for plugins
98 + setenv("LC_ALL", "C", 1);
99 +}
src/daemon/libuv_workers.c renamed
+1 -1
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include <daemon/main.h>
4 -#include "event_loop.h"
4 +#include "libuv_workers.h"
5
6 // Register workers
7 void register_libuv_worker_jobs() {
src/daemon/libuv_workers.h renamed
src/daemon/main.c
+35 -70
@@ -6,6 +6,7 @@
6 #include "static_threads.h"
7
8 #include "database/engine/page_test.h"
9 +#include <curl/curl.h>
10
11 #ifdef OS_WINDOWS
12 #include "win_system-info.h"
@@ -480,15 +481,13 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re
481
482
483 // unlink the pid
483 - if(pidfile[0]) {
484 + if(pidfile && *pidfile) {
485 if(unlink(pidfile) != 0)
486 netdata_log_error("EXIT: cannot unlink pidfile '%s'.", pidfile);
487 }
488 watcher_step_complete(WATCHER_STEP_ID_REMOVE_PID_FILE);
489
489 -#ifdef ENABLE_HTTPS
490 netdata_ssl_cleanup();
491 -#endif
491 watcher_step_complete(WATCHER_STEP_ID_FREE_OPENSSL_STRUCTURES);
492
493 (void) unlink(agent_incomplete_shutdown_file);
@@ -496,6 +495,7 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re
495
496 watcher_shutdown_end();
497 watcher_thread_stop();
498 + curl_global_cleanup();
499
500 #ifdef OS_WINDOWS
501 return;
@@ -807,8 +807,6 @@ int help(int exitcode) {
807 " are enabled or not, in JSON format.\n\n"
808 " -W simple-pattern pattern string\n"
809 " Check if string matches pattern and exit.\n\n"
810 - " -W \"claim -token=TOKEN -rooms=ROOM1,ROOM2\"\n"
811 - " Claim the agent to the workspace rooms pointed to by TOKEN and ROOM*.\n\n"
810 #ifdef OS_WINDOWS
811 " -W perflibdump [key]\n"
812 " Dump the Windows Performance Counters Registry in JSON.\n\n"
@@ -825,7 +823,6 @@ int help(int exitcode) {
823 return exitcode;
824 }
825
828 -#ifdef ENABLE_HTTPS
826 static void security_init(){
827 char filename[FILENAME_MAX + 1];
828 snprintfz(filename, FILENAME_MAX, "%s/ssl/key.pem",netdata_configured_user_config_dir);
@@ -839,7 +836,6 @@ static void security_init(){
836
837 netdata_ssl_initialize_openssl();
838 }
842 -#endif
839
840 static void log_init(void) {
841 nd_log_set_facility(config_get(CONFIG_SECTION_LOGS, "facility", "daemon"));
@@ -881,21 +877,19 @@ static void log_init(void) {
877 snprintfz(filename, FILENAME_MAX, "%s/health.log", netdata_configured_log_dir);
878 nd_log_set_user_settings(NDLS_HEALTH, config_get(CONFIG_SECTION_LOGS, "health", filename));
879
884 -#ifdef ENABLE_ACLK
880 aclklog_enabled = config_get_boolean(CONFIG_SECTION_CLOUD, "conversation log", CONFIG_BOOLEAN_NO);
881 if (aclklog_enabled) {
882 snprintfz(filename, FILENAME_MAX, "%s/aclk.log", netdata_configured_log_dir);
883 nd_log_set_user_settings(NDLS_ACLK, config_get(CONFIG_SECTION_CLOUD, "conversation log file", filename));
884 }
890 -#endif
885 +
886 + aclk_config_get_query_scope();
887 }
888
893 -char *initialize_lock_directory_path(char *prefix)
894 -{
889 +static char *get_varlib_subdir_from_config(const char *prefix, const char *dir) {
890 char filename[FILENAME_MAX + 1];
896 - snprintfz(filename, FILENAME_MAX, "%s/lock", prefix);
897 -
898 - return config_get(CONFIG_SECTION_DIRECTORIES, "lock", filename);
891 + snprintfz(filename, FILENAME_MAX, "%s/%s", prefix, dir);
892 + return config_get(CONFIG_SECTION_DIRECTORIES, dir, filename);
893 }
894
895 static void backwards_compatible_config() {
@@ -1175,7 +1169,8 @@ static void get_netdata_configured_variables()
1169 netdata_configured_cache_dir = config_get(CONFIG_SECTION_DIRECTORIES, "cache", netdata_configured_cache_dir);
1170 netdata_configured_varlib_dir = config_get(CONFIG_SECTION_DIRECTORIES, "lib", netdata_configured_varlib_dir);
1171
1178 - netdata_configured_lock_dir = initialize_lock_directory_path(netdata_configured_varlib_dir);
1172 + netdata_configured_lock_dir = get_varlib_subdir_from_config(netdata_configured_varlib_dir, "lock");
1173 + netdata_configured_cloud_dir = get_varlib_subdir_from_config(netdata_configured_varlib_dir, "cloud.d");
1174
1175 {
1176 pluginsd_initialize_plugin_directories();
@@ -1309,14 +1304,14 @@ static bool load_netdata_conf(char *filename, char overwrite_used, char **user)
1304 netdata_log_error("CONFIG: cannot load config file '%s'.", filename);
1305 }
1306 else {
1312 - filename = strdupz_path_subpath(netdata_configured_user_config_dir, "netdata.conf");
1307 + filename = filename_from_path_entry_strdupz(netdata_configured_user_config_dir, "netdata.conf");
1308
1309 ret = config_load(filename, overwrite_used, NULL);
1310 if(!ret) {
1311 netdata_log_info("CONFIG: cannot load user config '%s'. Will try the stock version.", filename);
1312 freez(filename);
1313
1319 - filename = strdupz_path_subpath(netdata_configured_stock_config_dir, "netdata.conf");
1314 + filename = filename_from_path_entry_strdupz(netdata_configured_stock_config_dir, "netdata.conf");
1315 ret = config_load(filename, overwrite_used, NULL);
1316 if(!ret)
1317 netdata_log_info("CONFIG: cannot load stock config '%s'. Running with internal defaults.", filename);
@@ -1351,7 +1346,7 @@ int get_system_info(struct rrdhost_system_info *system_info) {
1346 char line[200 + 1];
1347 // Removed the double strlens, if the Coverity tainted string warning reappears I'll revert.
1348 // One time init code, but I'm curious about the warning...
1354 - while (fgets(line, 200, instance->child_stdout_fp) != NULL) {
1349 + while (fgets(line, 200, spawn_popen_stdout(instance)) != NULL) {
1350 char *value=line;
1351 while (*value && *value != '=') value++;
1352 if (*value=='=') {
@@ -1366,7 +1361,7 @@ int get_system_info(struct rrdhost_system_info *system_info) {
1361 if(unlikely(rrdhost_set_system_info_variable(system_info, line, value))) {
1362 netdata_log_error("Unexpected environment variable %s=%s", line, value);
1363 } else {
1369 - setenv(line, value, 1);
1364 + nd_setenv(line, value, 1);
1365 }
1366 }
1367 }
@@ -1405,6 +1400,7 @@ int unittest_rrdpush_compressions(void);
1400 int uuid_unittest(void);
1401 int progress_unittest(void);
1402 int dyncfg_unittest(void);
1403 +bool netdata_random_session_id_generate(void);
1404
1405 #ifdef OS_WINDOWS
1406 int windows_perflib_dump(const char *key);
@@ -1455,6 +1451,8 @@ int netdata_main(int argc, char **argv) {
1451 // set the name for logging
1452 program_name = "netdata";
1453
1454 + curl_global_init(CURL_GLOBAL_ALL);
1455 +
1456 // parse options
1457 {
1458 int num_opts = sizeof(option_definitions) / sizeof(struct option_def);
@@ -1483,7 +1481,7 @@ int netdata_main(int argc, char **argv) {
1481 }
1482 else {
1483 netdata_log_debug(D_OPTIONS, "Configuration loaded from %s.", optarg);
1486 - load_cloud_conf(1);
1484 + cloud_conf_load(1);
1485 config_loaded = 1;
1486 }
1487 break;
@@ -1499,8 +1497,7 @@ int netdata_main(int argc, char **argv) {
1497 config_set(CONFIG_SECTION_WEB, "bind to", optarg);
1498 break;
1499 case 'P':
1502 - strncpy(pidfile, optarg, FILENAME_MAX);
1503 - pidfile[FILENAME_MAX] = '\0';
1500 + pidfile = strdupz(optarg);
1501 break;
1502 case 'p':
1503 config_set(CONFIG_SECTION_GLOBAL, "default port", optarg);
@@ -1522,7 +1519,6 @@ int netdata_main(int argc, char **argv) {
1519 {
1520 char* stacksize_string = "stacksize=";
1521 char* debug_flags_string = "debug_flags=";
1525 - char* claim_string = "claim";
1522 #ifdef ENABLE_DBENGINE
1523 char* createdataset_string = "createdataset=";
1524 char* stresstest_string = "stresstest=";
@@ -1870,7 +1866,7 @@ int netdata_main(int argc, char **argv) {
1866 if(!config_loaded) {
1867 fprintf(stderr, "warning: no configuration file has been loaded. Use -c CONFIG_FILE, before -W get. Using default config.\n");
1868 load_netdata_conf(NULL, 0, &user);
1873 - load_cloud_conf(1);
1869 + cloud_conf_load(1);
1870 }
1871
1872 get_netdata_configured_variables();
@@ -1884,10 +1880,6 @@ int netdata_main(int argc, char **argv) {
1880 printf("%s\n", value);
1881 return 0;
1882 }
1887 - else if(strncmp(optarg, claim_string, strlen(claim_string)) == 0) {
1888 - /* will trigger a claiming attempt when the agent is initialized */
1889 - claiming_pending_arguments = optarg + strlen(claim_string);
1890 - }
1883 else if(strcmp(optarg, "buildinfo") == 0) {
1884 print_build_info();
1885 return 0;
@@ -1919,12 +1911,12 @@ int netdata_main(int argc, char **argv) {
1911 if (close_open_fds == true) {
1912 // close all open file descriptors, except the standard ones
1913 // the caller may have left open files (lxc-attach has this issue)
1922 - os_close_all_non_std_open_fds_except(NULL, 0);
1914 + os_close_all_non_std_open_fds_except(NULL, 0, 0);
1915 }
1916
1917 if(!config_loaded) {
1918 load_netdata_conf(NULL, 0, &user);
1927 - load_cloud_conf(0);
1919 + cloud_conf_load(0);
1920 }
1921
1922 // ------------------------------------------------------------------------
@@ -1970,7 +1962,8 @@ int netdata_main(int argc, char **argv) {
1962
1963 // prepare configuration environment variables for the plugins
1964 get_netdata_configured_variables();
1973 - set_global_environment();
1965 + set_environment_for_plugins_and_scripts();
1966 + analytics_reset();
1967
1968 // work while we are cd into config_dir
1969 // to allow the plugins refer to their config
@@ -1987,7 +1980,7 @@ int netdata_main(int argc, char **argv) {
1980 // get the debugging flags from the configuration file
1981
1982 char *flags = config_get(CONFIG_SECTION_LOGS, "debug flags", "0x0000000000000000");
1990 - setenv("NETDATA_DEBUG_FLAGS", flags, 1);
1983 + nd_setenv("NETDATA_DEBUG_FLAGS", flags, 1);
1984
1985 debug_flags = strtoull(flags, NULL, 0);
1986 netdata_log_debug(D_OPTIONS, "Debug flags set to '0x%" PRIX64 "'.", debug_flags);
@@ -2021,8 +2014,6 @@ int netdata_main(int argc, char **argv) {
2014
2015 get_system_timezone();
2016
2024 - bearer_tokens_init();
2025 -
2017 replication_initialize();
2018
2019 rrd_functions_inflight_init();
@@ -2030,9 +2021,7 @@ int netdata_main(int argc, char **argv) {
2021 // --------------------------------------------------------------------
2022 // get the certificate and start security
2023
2033 -#ifdef ENABLE_HTTPS
2024 security_init();
2035 -#endif
2025
2026 // --------------------------------------------------------------------
2027 // This is the safest place to start the SILENCERS structure
@@ -2053,8 +2042,7 @@ int netdata_main(int argc, char **argv) {
2042 // this causes the threads to block signals.
2043
2044 delta_startup_time("initialize signals");
2056 - signals_block();
2057 - signals_init(); // setup the signals we want to use
2045 + nd_initialize_signals(); // setup the signals we want to use
2046
2047 // --------------------------------------------------------------------
2048 // check which threads are enabled and initialize them
@@ -2086,7 +2074,7 @@ int netdata_main(int argc, char **argv) {
2074 st->init_routine();
2075
2076 if(st->env_name)
2089 - setenv(st->env_name, st->enabled?"YES":"NO", 1);
2077 + nd_setenv(st->env_name, st->enabled?"YES":"NO", 1);
2078
2079 if(st->global_variable)
2080 *st->global_variable = (st->enabled) ? true : false;
@@ -2097,7 +2085,7 @@ int netdata_main(int argc, char **argv) {
2085
2086 delta_startup_time("initialize web server");
2087
2100 - web_client_api_v1_init();
2088 + nd_web_api_init();
2089 web_server_threading_selection();
2090
2091 if(web_server_mode != WEB_SERVER_MODE_NONE) {
@@ -2165,7 +2153,7 @@ int netdata_main(int argc, char **argv) {
2153 netdata_configured_home_dir = config_get(CONFIG_SECTION_DIRECTORIES, "home", pw->pw_dir);
2154 }
2155
2168 - setenv("HOME", netdata_configured_home_dir, 1);
2156 + nd_setenv("HOME", netdata_configured_home_dir, 1);
2157
2158 dyncfg_init(true);
2159
@@ -2178,6 +2166,7 @@ int netdata_main(int argc, char **argv) {
2166 // initialize internal registry
2167 delta_startup_time("initialize registry");
2168 registry_init();
2169 + cloud_conf_init_after_registry();
2170 netdata_random_session_id_generate();
2171
2172 // ------------------------------------------------------------------------
@@ -2203,7 +2192,7 @@ int netdata_main(int argc, char **argv) {
2192 delta_startup_time("initialize RRD structures");
2193
2194 if(rrd_init(netdata_configured_hostname, system_info, false)) {
2206 - set_late_global_environment(system_info);
2195 + set_late_analytics_variables(system_info);
2196 fatal("Cannot initialize localhost instance with name '%s'.", netdata_configured_hostname);
2197 }
2198
@@ -2219,15 +2208,10 @@ int netdata_main(int argc, char **argv) {
2208 if (fd >= 0)
2209 close(fd);
2210
2222 -
2211 // ------------------------------------------------------------------------
2212 // Claim netdata agent to a cloud endpoint
2213
2214 delta_startup_time("collect claiming info");
2227 -
2228 - if (claiming_pending_arguments)
2229 - claim_agent(claiming_pending_arguments, false, NULL);
2230 -
2215 load_claiming_state();
2216
2217 // ------------------------------------------------------------------------
@@ -2242,11 +2226,13 @@ int netdata_main(int argc, char **argv) {
2226 // ------------------------------------------------------------------------
2227 // spawn the threads
2228
2229 + bearer_tokens_init();
2230 +
2231 delta_startup_time("start the static threads");
2232
2233 web_server_config_options();
2234
2249 - set_late_global_environment(system_info);
2235 + set_late_analytics_variables(system_info);
2236 for (i = 0; static_threads[i].name != NULL ; i++) {
2237 struct netdata_static_thread *st = &static_threads[i];
2238
@@ -2295,28 +2281,7 @@ int netdata_main(int argc, char **argv) {
2281 }
2282 }
2283
2298 - // ------------------------------------------------------------------------
2299 - // Report ACLK build failure
2300 -#ifndef ENABLE_ACLK
2301 - netdata_log_error("This agent doesn't have ACLK.");
2302 - char filename[FILENAME_MAX + 1];
2303 - snprintfz(filename, FILENAME_MAX, "%s/.aclk_report_sent", netdata_configured_varlib_dir);
2304 - if (netdata_anonymous_statistics_enabled > 0 && access(filename, F_OK)) { // -1 -> not initialized
2305 - analytics_statistic_t statistic = { "ACLK_DISABLED", "-", "-" };
2306 - analytics_statistic_send(&statistic);
2307 -
2308 - int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 444);
2309 - if (fd == -1)
2310 - netdata_log_error("Cannot create file '%s'. Please fix this.", filename);
2311 - else
2312 - close(fd);
2313 - }
2314 -#endif
2315 -
2284 webrtc_initialize();
2317 -
2318 - signals_unblock();
2319 -
2285 return 10;
2286 }
2287
@@ -2327,7 +2292,7 @@ int main(int argc, char *argv[])
2292 if (rc != 10)
2293 return rc;
2294
2330 - signals_handle();
2295 + nd_process_signals();
2296 return 1;
2297 }
2298 #endif
src/daemon/service.c
+3 -5
@@ -203,7 +203,7 @@ static void svc_rrd_cleanup_obsolete_charts_from_all_hosts() {
203 if (host == localhost)
204 continue;
205
206 - netdata_mutex_lock(&host->receiver_lock);
206 + spinlock_lock(&host->receiver_lock);
207
208 time_t now = now_realtime_sec();
209
@@ -215,7 +215,7 @@ static void svc_rrd_cleanup_obsolete_charts_from_all_hosts() {
215 host->trigger_chart_obsoletion_check = 0;
216 }
217
218 - netdata_mutex_unlock(&host->receiver_lock);
218 + spinlock_unlock(&host->receiver_lock);
219 }
220
221 rrd_rdunlock();
@@ -247,14 +247,12 @@ restart_after_removal:
247 }
248
249 worker_is_busy(WORKER_JOB_FREE_HOST);
250 -#ifdef ENABLE_ACLK
250 // in case we have cloud connection we inform cloud
251 // a child disconnected
253 - if (netdata_cloud_enabled && force) {
252 + if (force) {
253 aclk_host_state_update(host, 0, 0);
254 unregister_node(host->machine_guid);
255 }
257 -#endif
256 rrdhost_free___while_having_rrd_wrlock(host, force);
257 goto restart_after_removal;
258 }
src/daemon/signals.c
+20 -29
@@ -2,12 +2,6 @@
2
3 #include "common.h"
4
5 -/*
6 - * IMPORTANT: Libuv uv_spawn() uses SIGCHLD internally:
7 - * https://github.com/libuv/libuv/blob/cc51217a317e96510fbb284721d5e6bc2af31e33/src/unix/process.c#L485
8 - * Extreme care is needed when mixing and matching POSIX and libuv.
9 - */
10 -
5 typedef enum signal_action {
6 NETDATA_SIGNAL_END_OF_LIST,
7 NETDATA_SIGNAL_IGNORE,
@@ -56,24 +50,33 @@ static void signal_handler(int signo) {
50 }
51 }
52
59 -void signals_block(void) {
53 +// Mask all signals, to ensure they will only be unmasked at the threads that can handle them.
54 +// This means that all third party libraries (including libuv) cannot use signals anymore.
55 +// The signals they are interested must be unblocked at their corresponding event loops.
56 +static void posix_mask_all_signals(void) {
57 sigset_t sigset;
58 sigfillset(&sigset);
59
63 - if(pthread_sigmask(SIG_BLOCK, &sigset, NULL) == -1)
64 - netdata_log_error("SIGNAL: Could not block signals for threads");
60 + if(pthread_sigmask(SIG_BLOCK, &sigset, NULL) != 0)
61 + netdata_log_error("SIGNAL: cannot mask all signals");
62 }
63
67 -void signals_unblock(void) {
64 +// Unmask all signals the netdata main signal handler uses.
65 +// All other signals remain masked.
66 +static void posix_unmask_my_signals(void) {
67 sigset_t sigset;
69 - sigfillset(&sigset);
68 + sigemptyset(&sigset);
69
71 - if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
72 - netdata_log_error("SIGNAL: Could not unblock signals for threads");
73 - }
70 + for (int i = 0; signals_waiting[i].action != NETDATA_SIGNAL_END_OF_LIST; i++)
71 + sigaddset(&sigset, signals_waiting[i].signo);
72 +
73 + if (pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) != 0)
74 + netdata_log_error("SIGNAL: cannot unmask netdata signals");
75 }
76
76 -void signals_init(void) {
77 +void nd_initialize_signals(void) {
78 + posix_mask_all_signals(); // block all signals for all threads
79 +
80 // Catch signals which we want to use
81 struct sigaction sa;
82 sa.sa_flags = 0;
@@ -97,22 +100,10 @@ void signals_init(void) {
100 }
101 }
102
100 -void signals_reset(void) {
101 - struct sigaction sa;
102 - sigemptyset(&sa.sa_mask);
103 - sa.sa_handler = SIG_DFL;
104 - sa.sa_flags = 0;
105 -
106 - int i;
107 - for (i = 0; signals_waiting[i].action != NETDATA_SIGNAL_END_OF_LIST; i++) {
108 - if(sigaction(signals_waiting[i].signo, &sa, NULL) == -1)
109 - netdata_log_error("SIGNAL: Failed to reset signal handler for: %s", signals_waiting[i].name);
110 - }
111 -}
103 +void nd_process_signals(void) {
104 + posix_unmask_my_signals();
105
113 -void signals_handle(void) {
106 while(1) {
115 -
107 // pause() causes the calling process (or thread) to sleep until a signal
108 // is delivered that either terminates the process or causes the invocation
109 // of a signal-catching function.
src/daemon/signals.h
+2 -5
@@ -3,10 +3,7 @@
3 #ifndef NETDATA_SIGNALS_H
4 #define NETDATA_SIGNALS_H 1
5
6 -void signals_init(void);
7 -void signals_block(void);
8 -void signals_unblock(void);
9 -void signals_reset(void);
10 -void signals_handle(void) NORETURN;
6 +void nd_initialize_signals(void);
7 +void nd_process_signals(void) NORETURN;
8
9 #endif //NETDATA_SIGNALS_H
src/daemon/static_threads.c
-2
@@ -133,7 +133,6 @@ const struct netdata_static_thread static_threads_common[] = {
133 },
134 #endif
135
136 -#ifdef ENABLE_ACLK
136 {
137 .name = "ACLK_MAIN",
138 .config_section = NULL,
@@ -143,7 +142,6 @@ const struct netdata_static_thread static_threads_common[] = {
142 .init_routine = NULL,
143 .start_routine = aclk_main
144 },
146 -#endif
145
146 {
147 .name = "RRDCONTEXT",
src/daemon/unit_test.c
+2 -2
@@ -1437,8 +1437,8 @@ int check_strdupz_path_subpath() {
1437
1438 size_t i;
1439 for(i = 0; checks[i].result ; i++) {
1440 - char *s = strdupz_path_subpath(checks[i].path, checks[i].subpath);
1441 - fprintf(stderr, "strdupz_path_subpath(\"%s\", \"%s\") = \"%s\": ", checks[i].path, checks[i].subpath, s);
1440 + char *s = filename_from_path_entry_strdupz(checks[i].path, checks[i].subpath);
1441 + fprintf(stderr, "filename_from_path_entry_strdupz(\"%s\", \"%s\") = \"%s\": ", checks[i].path, checks[i].subpath, s);
1442 if(!s || strcmp(s, checks[i].result) != 0) {
1443 freez(s);
1444 fprintf(stderr, "FAILED\n");
src/daemon/winsvc.cc
+2 -2
@@ -4,7 +4,7 @@ extern "C" {
4 #include "libnetdata/libnetdata.h"
5
6 int netdata_main(int argc, char *argv[]);
7 -void signals_handle(void);
7 +void nd_process_signals(void);
8
9 }
10
@@ -231,7 +231,7 @@ int main(int argc, char *argv[])
231 if (rc != 10)
232 return rc;
233
234 - signals_handle();
234 + nd_process_signals();
235 return 1;
236 }
237 else
src/database/contexts/api_v1_contexts.c renamed
+4 -3
@@ -399,8 +399,8 @@ int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before,
399
400 char node_uuid[UUID_STR_LEN] = "";
401
402 - if(host->node_id)
403 - uuid_unparse(*host->node_id, node_uuid);
402 + if(!uuid_is_null(host->node_id))
403 + uuid_unparse_lower(host->node_id, node_uuid);
404
405 if(after != 0 && before != 0)
406 rrdr_relative_window_to_absolute_query(&after, &before, NULL, false);
@@ -409,7 +409,8 @@ int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before,
409 buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
410 buffer_json_member_add_string(wb, "machine_guid", host->machine_guid);
411 buffer_json_member_add_string(wb, "node_id", node_uuid);
412 - buffer_json_member_add_string(wb, "claim_id", host->aclk_state.claimed_id ? host->aclk_state.claimed_id : "");
412 + CLAIM_ID claim_id = rrdhost_claim_id_get(host);
413 + buffer_json_member_add_string(wb, "claim_id", claim_id.str);
414
415 if(options & RRDCONTEXT_OPTION_SHOW_LABELS) {
416 buffer_json_member_add_object(wb, "host_labels");
src/database/contexts/api_v2.c deleted
-2454
@@ -1,2454 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#include "internal.h"
4 -
5 -#include "aclk/aclk_capas.h"
6 -
7 -// ----------------------------------------------------------------------------
8 -// /api/v2/contexts API
9 -
10 -struct alert_transitions_facets alert_transition_facets[] = {
11 - [ATF_STATUS] = {
12 - .id = "f_status",
13 - .name = "Alert Status",
14 - .query_param = "f_status",
15 - .order = 1,
16 - },
17 - [ATF_TYPE] = {
18 - .id = "f_type",
19 - .name = "Alert Type",
20 - .query_param = "f_type",
21 - .order = 2,
22 - },
23 - [ATF_ROLE] = {
24 - .id = "f_role",
25 - .name = "Recipient Role",
26 - .query_param = "f_role",
27 - .order = 3,
28 - },
29 - [ATF_CLASS] = {
30 - .id = "f_class",
31 - .name = "Alert Class",
32 - .query_param = "f_class",
33 - .order = 4,
34 - },
35 - [ATF_COMPONENT] = {
36 - .id = "f_component",
37 - .name = "Alert Component",
38 - .query_param = "f_component",
39 - .order = 5,
40 - },
41 - [ATF_NODE] = {
42 - .id = "f_node",
43 - .name = "Alert Node",
44 - .query_param = "f_node",
45 - .order = 6,
46 - },
47 - [ATF_ALERT_NAME] = {
48 - .id = "f_alert",
49 - .name = "Alert Name",
50 - .query_param = "f_alert",
51 - .order = 7,
52 - },
53 - [ATF_CHART_NAME] = {
54 - .id = "f_instance",
55 - .name = "Instance Name",
56 - .query_param = "f_instance",
57 - .order = 8,
58 - },
59 - [ATF_CONTEXT] = {
60 - .id = "f_context",
61 - .name = "Context",
62 - .query_param = "f_context",
63 - .order = 9,
64 - },
65 -
66 - // terminator
67 - [ATF_TOTAL_ENTRIES] = {
68 - .id = NULL,
69 - .name = NULL,
70 - .query_param = NULL,
71 - .order = 9999,
72 - }
73 -};
74 -
75 -struct facet_entry {
76 - uint32_t count;
77 -};
78 -
79 -struct alert_transitions_callback_data {
80 - struct rrdcontext_to_json_v2_data *ctl;
81 - BUFFER *wb;
82 - bool debug;
83 - bool only_one_config;
84 -
85 - struct {
86 - SIMPLE_PATTERN *pattern;
87 - DICTIONARY *dict;
88 - } facets[ATF_TOTAL_ENTRIES];
89 -
90 - uint32_t max_items_to_return;
91 - uint32_t items_to_return;
92 -
93 - uint32_t items_evaluated;
94 - uint32_t items_matched;
95 -
96 -
97 - struct sql_alert_transition_fixed_size *base; // double linked list - last item is base->prev
98 - struct sql_alert_transition_fixed_size *last_added; // the last item added, not the last of the list
99 -
100 - struct {
101 - size_t first;
102 - size_t skips_before;
103 - size_t skips_after;
104 - size_t backwards;
105 - size_t forwards;
106 - size_t prepend;
107 - size_t append;
108 - size_t shifts;
109 - } operations;
110 -
111 - uint32_t configs_added;
112 -};
113 -
114 -typedef enum __attribute__ ((__packed__)) {
115 - FTS_MATCHED_NONE = 0,
116 - FTS_MATCHED_HOST,
117 - FTS_MATCHED_CONTEXT,
118 - FTS_MATCHED_INSTANCE,
119 - FTS_MATCHED_DIMENSION,
120 - FTS_MATCHED_LABEL,
121 - FTS_MATCHED_ALERT,
122 - FTS_MATCHED_ALERT_INFO,
123 - FTS_MATCHED_FAMILY,
124 - FTS_MATCHED_TITLE,
125 - FTS_MATCHED_UNITS,
126 -} FTS_MATCH;
127 -
128 -static const char *fts_match_to_string(FTS_MATCH match) {
129 - switch(match) {
130 - case FTS_MATCHED_HOST:
131 - return "HOST";
132 -
133 - case FTS_MATCHED_CONTEXT:
134 - return "CONTEXT";
135 -
136 - case FTS_MATCHED_INSTANCE:
137 - return "INSTANCE";
138 -
139 - case FTS_MATCHED_DIMENSION:
140 - return "DIMENSION";
141 -
142 - case FTS_MATCHED_ALERT:
143 - return "ALERT";
144 -
145 - case FTS_MATCHED_ALERT_INFO:
146 - return "ALERT_INFO";
147 -
148 - case FTS_MATCHED_LABEL:
149 - return "LABEL";
150 -
151 - case FTS_MATCHED_FAMILY:
152 - return "FAMILY";
153 -
154 - case FTS_MATCHED_TITLE:
155 - return "TITLE";
156 -
157 - case FTS_MATCHED_UNITS:
158 - return "UNITS";
159 -
160 - default:
161 - return "NONE";
162 - }
163 -}
164 -
165 -struct function_v2_entry {
166 - size_t size;
167 - size_t used;
168 - size_t *node_ids;
169 - STRING *help;
170 - STRING *tags;
171 - HTTP_ACCESS access;
172 - int priority;
173 -};
174 -
175 -struct context_v2_entry {
176 - size_t count;
177 - STRING *id;
178 - STRING *family;
179 - uint32_t priority;
180 - time_t first_time_s;
181 - time_t last_time_s;
182 - RRD_FLAGS flags;
183 - FTS_MATCH match;
184 -};
185 -
186 -struct alert_counts {
187 - size_t critical;
188 - size_t warning;
189 - size_t clear;
190 - size_t error;
191 -};
192 -
193 -struct alert_v2_entry {
194 - RRDCALC *tmp;
195 -
196 - STRING *name;
197 - STRING *summary;
198 - RRDLABELS *recipient;
199 - RRDLABELS *classification;
200 - RRDLABELS *context;
201 - RRDLABELS *component;
202 - RRDLABELS *type;
203 -
204 - size_t ati;
205 -
206 - struct alert_counts counts;
207 -
208 - size_t instances;
209 - DICTIONARY *nodes;
210 - DICTIONARY *configs;
211 -};
212 -
213 -struct alert_by_x_entry {
214 - struct {
215 - struct alert_counts counts;
216 - size_t silent;
217 - size_t total;
218 - } running;
219 -
220 - struct {
221 - size_t available;
222 - } prototypes;
223 -};
224 -
225 -typedef struct full_text_search_index {
226 - size_t searches;
227 - size_t string_searches;
228 - size_t char_searches;
229 -} FTS_INDEX;
230 -
231 -static inline bool full_text_search_string(FTS_INDEX *fts, SIMPLE_PATTERN *q, STRING *ptr) {
232 - fts->searches++;
233 - fts->string_searches++;
234 - return simple_pattern_matches_string(q, ptr);
235 -}
236 -
237 -static inline bool full_text_search_char(FTS_INDEX *fts, SIMPLE_PATTERN *q, char *ptr) {
238 - fts->searches++;
239 - fts->char_searches++;
240 - return simple_pattern_matches(q, ptr);
241 -}
242 -
243 -struct contexts_v2_node {
244 - size_t ni;
245 - RRDHOST *host;
246 -};
247 -
248 -struct rrdcontext_to_json_v2_data {
249 - time_t now;
250 -
251 - BUFFER *wb;
252 - struct api_v2_contexts_request *request;
253 -
254 - CONTEXTS_V2_MODE mode;
255 - CONTEXTS_V2_OPTIONS options;
256 - struct query_versions versions;
257 -
258 - struct {
259 - SIMPLE_PATTERN *scope_pattern;
260 - SIMPLE_PATTERN *pattern;
261 - size_t ni;
262 - DICTIONARY *dict; // the result set
263 - } nodes;
264 -
265 - struct {
266 - SIMPLE_PATTERN *scope_pattern;
267 - SIMPLE_PATTERN *pattern;
268 - size_t ci;
269 - DICTIONARY *dict; // the result set
270 - } contexts;
271 -
272 - struct {
273 - SIMPLE_PATTERN *alert_name_pattern;
274 - time_t alarm_id_filter;
275 -
276 - size_t ati;
277 -
278 - DICTIONARY *summary;
279 - DICTIONARY *alert_instances;
280 -
281 - DICTIONARY *by_type;
282 - DICTIONARY *by_component;
283 - DICTIONARY *by_classification;
284 - DICTIONARY *by_recipient;
285 - DICTIONARY *by_module;
286 - } alerts;
287 -
288 - struct {
289 - FTS_MATCH host_match;
290 - char host_node_id_str[UUID_STR_LEN];
291 - SIMPLE_PATTERN *pattern;
292 - FTS_INDEX fts;
293 - } q;
294 -
295 - struct {
296 - DICTIONARY *dict; // the result set
297 - } functions;
298 -
299 - struct {
300 - bool enabled;
301 - bool relative;
302 - time_t after;
303 - time_t before;
304 - } window;
305 -
306 - struct query_timings timings;
307 -};
308 -
309 -static void alert_counts_add(struct alert_counts *t, RRDCALC *rc) {
310 - switch(rc->status) {
311 - case RRDCALC_STATUS_CRITICAL:
312 - t->critical++;
313 - break;
314 -
315 - case RRDCALC_STATUS_WARNING:
316 - t->warning++;
317 - break;
318 -
319 - case RRDCALC_STATUS_CLEAR:
320 - t->clear++;
321 - break;
322 -
323 - case RRDCALC_STATUS_REMOVED:
324 - case RRDCALC_STATUS_UNINITIALIZED:
325 - break;
326 -
327 - case RRDCALC_STATUS_UNDEFINED:
328 - default:
329 - if(!netdata_double_isnumber(rc->value))
330 - t->error++;
331 -
332 - break;
333 - }
334 -}
335 -
336 -static void alerts_v2_add(struct alert_v2_entry *t, RRDCALC *rc) {
337 - t->instances++;
338 -
339 - alert_counts_add(&t->counts, rc);
340 -
341 - dictionary_set(t->nodes, rc->rrdset->rrdhost->machine_guid, NULL, 0);
342 -
343 - char key[UUID_STR_LEN + 1];
344 - uuid_unparse_lower(rc->config.hash_id, key);
345 - dictionary_set(t->configs, key, NULL, 0);
346 -}
347 -
348 -static void alerts_by_x_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
349 - static STRING *silent = NULL;
350 - if(unlikely(!silent)) silent = string_strdupz("silent");
351 -
352 - struct alert_by_x_entry *b = value;
353 - RRDCALC *rc = data;
354 - if(!rc) {
355 - // prototype
356 - b->prototypes.available++;
357 - }
358 - else {
359 - alert_counts_add(&b->running.counts, rc);
360 -
361 - b->running.total++;
362 -
363 - if (rc->config.recipient == silent)
364 - b->running.silent++;
365 - }
366 -}
367 -
368 -static bool alerts_by_x_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value __maybe_unused, void *data __maybe_unused) {
369 - alerts_by_x_insert_callback(item, old_value, data);
370 - return false;
371 -}
372 -
373 -static void alerts_v2_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
374 - struct rrdcontext_to_json_v2_data *ctl = data;
375 - struct alert_v2_entry *t = value;
376 - RRDCALC *rc = t->tmp;
377 - t->name = rc->config.name;
378 - t->summary = rc->config.summary; // the original summary
379 - t->context = rrdlabels_create();
380 - t->recipient = rrdlabels_create();
381 - t->classification = rrdlabels_create();
382 - t->component = rrdlabels_create();
383 - t->type = rrdlabels_create();
384 - if (string_strlen(rc->rrdset->context))
385 - rrdlabels_add(t->context, string2str(rc->rrdset->context), "yes", RRDLABEL_SRC_AUTO);
386 - if (string_strlen(rc->config.recipient))
387 - rrdlabels_add(t->recipient, string2str(rc->config.recipient), "yes", RRDLABEL_SRC_AUTO);
388 - if (string_strlen(rc->config.classification))
389 - rrdlabels_add(t->classification, string2str(rc->config.classification), "yes", RRDLABEL_SRC_AUTO);
390 - if (string_strlen(rc->config.component))
391 - rrdlabels_add(t->component, string2str(rc->config.component), "yes", RRDLABEL_SRC_AUTO);
392 - if (string_strlen(rc->config.type))
393 - rrdlabels_add(t->type, string2str(rc->config.type), "yes", RRDLABEL_SRC_AUTO);
394 - t->ati = ctl->alerts.ati++;
395 -
396 - t->nodes = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_VALUE_LINK_DONT_CLONE|DICT_OPTION_NAME_LINK_DONT_CLONE);
397 - t->configs = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_VALUE_LINK_DONT_CLONE|DICT_OPTION_NAME_LINK_DONT_CLONE);
398 -
399 - alerts_v2_add(t, rc);
400 -}
401 -
402 -static bool alerts_v2_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
403 - struct alert_v2_entry *t = old_value, *n = new_value;
404 - RRDCALC *rc = n->tmp;
405 - if (string_strlen(rc->rrdset->context))
406 - rrdlabels_add(t->context, string2str(rc->rrdset->context), "yes", RRDLABEL_SRC_AUTO);
407 - if (string_strlen(rc->config.recipient))
408 - rrdlabels_add(t->recipient, string2str(rc->config.recipient), "yes", RRDLABEL_SRC_AUTO);
409 - if (string_strlen(rc->config.classification))
410 - rrdlabels_add(t->classification, string2str(rc->config.classification), "yes", RRDLABEL_SRC_AUTO);
411 - if (string_strlen(rc->config.component))
412 - rrdlabels_add(t->component, string2str(rc->config.component), "yes", RRDLABEL_SRC_AUTO);
413 - if (string_strlen(rc->config.type))
414 - rrdlabels_add(t->type, string2str(rc->config.type), "yes", RRDLABEL_SRC_AUTO);
415 - alerts_v2_add(t, rc);
416 - return true;
417 -}
418 -
419 -static void alerts_v2_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
420 - struct alert_v2_entry *t = value;
421 -
422 - rrdlabels_destroy(t->context);
423 - rrdlabels_destroy(t->recipient);
424 - rrdlabels_destroy(t->classification);
425 - rrdlabels_destroy(t->component);
426 - rrdlabels_destroy(t->type);
427 -
428 - dictionary_destroy(t->nodes);
429 - dictionary_destroy(t->configs);
430 -}
431 -
432 -static void alert_instances_v2_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
433 - struct rrdcontext_to_json_v2_data *ctl = data;
434 - struct sql_alert_instance_v2_entry *t = value;
435 - RRDCALC *rc = t->tmp;
436 -
437 - t->context = rc->rrdset->context;
438 - t->chart_id = rc->rrdset->id;
439 - t->chart_name = rc->rrdset->name;
440 - t->family = rc->rrdset->family;
441 - t->units = rc->config.units;
442 - t->classification = rc->config.classification;
443 - t->type = rc->config.type;
444 - t->recipient = rc->config.recipient;
445 - t->component = rc->config.component;
446 - t->name = rc->config.name;
447 - t->source = rc->config.source;
448 - t->status = rc->status;
449 - t->flags = rc->run_flags;
450 - t->info = rc->config.info;
451 - t->summary = rc->summary;
452 - t->value = rc->value;
453 - t->last_updated = rc->last_updated;
454 - t->last_status_change = rc->last_status_change;
455 - t->last_status_change_value = rc->last_status_change_value;
456 - t->host = rc->rrdset->rrdhost;
457 - t->alarm_id = rc->id;
458 - t->ni = ctl->nodes.ni;
459 -
460 - uuid_copy(t->config_hash_id, rc->config.hash_id);
461 - health_alarm_log_get_global_id_and_transition_id_for_rrdcalc(rc, &t->global_id, &t->last_transition_id);
462 -}
463 -
464 -static bool alert_instances_v2_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value __maybe_unused, void *new_value __maybe_unused, void *data __maybe_unused) {
465 - internal_fatal(true, "This should never happen!");
466 - return true;
467 -}
468 -
469 -static void alert_instances_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
470 - ;
471 -}
472 -
473 -static FTS_MATCH rrdcontext_to_json_v2_full_text_search(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc, SIMPLE_PATTERN *q) {
474 - if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->id) ||
475 - full_text_search_string(&ctl->q.fts, q, rc->family)))
476 - return FTS_MATCHED_CONTEXT;
477 -
478 - if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->title)))
479 - return FTS_MATCHED_TITLE;
480 -
481 - if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->units)))
482 - return FTS_MATCHED_UNITS;
483 -
484 - FTS_MATCH matched = FTS_MATCHED_NONE;
485 - RRDINSTANCE *ri;
486 - dfe_start_read(rc->rrdinstances, ri) {
487 - if(matched) break;
488 -
489 - if(ctl->window.enabled && !query_matches_retention(ctl->window.after, ctl->window.before, ri->first_time_s, (ri->flags & RRD_FLAG_COLLECTED) ? ctl->now : ri->last_time_s, 0))
490 - continue;
491 -
492 - if(unlikely(full_text_search_string(&ctl->q.fts, q, ri->id)) ||
493 - (ri->name != ri->id && full_text_search_string(&ctl->q.fts, q, ri->name))) {
494 - matched = FTS_MATCHED_INSTANCE;
495 - break;
496 - }
497 -
498 - RRDMETRIC *rm;
499 - dfe_start_read(ri->rrdmetrics, rm) {
500 - if(ctl->window.enabled && !query_matches_retention(ctl->window.after, ctl->window.before, rm->first_time_s, (rm->flags & RRD_FLAG_COLLECTED) ? ctl->now : rm->last_time_s, 0))
501 - continue;
502 -
503 - if(unlikely(full_text_search_string(&ctl->q.fts, q, rm->id)) ||
504 - (rm->name != rm->id && full_text_search_string(&ctl->q.fts, q, rm->name))) {
505 - matched = FTS_MATCHED_DIMENSION;
506 - break;
507 - }
508 - }
509 - dfe_done(rm);
510 -
511 - size_t label_searches = 0;
512 - if(unlikely(ri->rrdlabels && rrdlabels_entries(ri->rrdlabels) &&
513 - rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, q, ':', &label_searches) == SP_MATCHED_POSITIVE)) {
514 - ctl->q.fts.searches += label_searches;
515 - ctl->q.fts.char_searches += label_searches;
516 - matched = FTS_MATCHED_LABEL;
517 - break;
518 - }
519 - ctl->q.fts.searches += label_searches;
520 - ctl->q.fts.char_searches += label_searches;
521 -
522 - if(ri->rrdset) {
523 - RRDSET *st = ri->rrdset;
524 - rw_spinlock_read_lock(&st->alerts.spinlock);
525 - for (RRDCALC *rcl = st->alerts.base; rcl; rcl = rcl->next) {
526 - if(unlikely(full_text_search_string(&ctl->q.fts, q, rcl->config.name))) {
527 - matched = FTS_MATCHED_ALERT;
528 - break;
529 - }
530 -
531 - if(unlikely(full_text_search_string(&ctl->q.fts, q, rcl->config.info))) {
532 - matched = FTS_MATCHED_ALERT_INFO;
533 - break;
534 - }
535 - }
536 - rw_spinlock_read_unlock(&st->alerts.spinlock);
537 - }
538 - }
539 - dfe_done(ri);
540 - return matched;
541 -}
542 -
543 -static bool rrdcontext_matches_alert(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc) {
544 - size_t matches = 0;
545 - RRDINSTANCE *ri;
546 - dfe_start_read(rc->rrdinstances, ri) {
547 - if(ri->rrdset) {
548 - RRDSET *st = ri->rrdset;
549 - rw_spinlock_read_lock(&st->alerts.spinlock);
550 - for (RRDCALC *rcl = st->alerts.base; rcl; rcl = rcl->next) {
551 - if(ctl->alerts.alert_name_pattern && !simple_pattern_matches_string(ctl->alerts.alert_name_pattern, rcl->config.name))
552 - continue;
553 -
554 - if(ctl->alerts.alarm_id_filter && ctl->alerts.alarm_id_filter != rcl->id)
555 - continue;
556 -
557 - size_t m = ctl->request->alerts.status & CONTEXTS_V2_ALERT_STATUSES ? 0 : 1;
558 -
559 - if (!m) {
560 - if ((ctl->request->alerts.status & CONTEXT_V2_ALERT_UNINITIALIZED) &&
561 - rcl->status == RRDCALC_STATUS_UNINITIALIZED)
562 - m++;
563 -
564 - if ((ctl->request->alerts.status & CONTEXT_V2_ALERT_UNDEFINED) &&
565 - rcl->status == RRDCALC_STATUS_UNDEFINED)
566 - m++;
567 -
568 - if ((ctl->request->alerts.status & CONTEXT_V2_ALERT_CLEAR) &&
569 - rcl->status == RRDCALC_STATUS_CLEAR)
570 - m++;
571 -
572 - if ((ctl->request->alerts.status & CONTEXT_V2_ALERT_RAISED) &&
573 - rcl->status >= RRDCALC_STATUS_RAISED)
574 - m++;
575 -
576 - if ((ctl->request->alerts.status & CONTEXT_V2_ALERT_WARNING) &&
577 - rcl->status == RRDCALC_STATUS_WARNING)
578 - m++;
579 -
580 - if ((ctl->request->alerts.status & CONTEXT_V2_ALERT_CRITICAL) &&
581 - rcl->status == RRDCALC_STATUS_CRITICAL)
582 - m++;
583 -
584 - if(!m)
585 - continue;
586 - }
587 -
588 - struct alert_v2_entry t = {
589 - .tmp = rcl,
590 - };
591 - struct alert_v2_entry *a2e =
592 - dictionary_set(ctl->alerts.summary, string2str(rcl->config.name),
593 - &t, sizeof(struct alert_v2_entry));
594 - size_t ati = a2e->ati;
595 - matches++;
596 -
597 - dictionary_set_advanced(ctl->alerts.by_type,
598 - string2str(rcl->config.type),
599 - (ssize_t)string_strlen(rcl->config.type),
600 - NULL,
601 - sizeof(struct alert_by_x_entry),
602 - rcl);
603 -
604 - dictionary_set_advanced(ctl->alerts.by_component,
605 - string2str(rcl->config.component),
606 - (ssize_t)string_strlen(rcl->config.component),
607 - NULL,
608 - sizeof(struct alert_by_x_entry),
609 - rcl);
610 -
611 - dictionary_set_advanced(ctl->alerts.by_classification,
612 - string2str(rcl->config.classification),
613 - (ssize_t)string_strlen(rcl->config.classification),
614 - NULL,
615 - sizeof(struct alert_by_x_entry),
616 - rcl);
617 -
618 - dictionary_set_advanced(ctl->alerts.by_recipient,
619 - string2str(rcl->config.recipient),
620 - (ssize_t)string_strlen(rcl->config.recipient),
621 - NULL,
622 - sizeof(struct alert_by_x_entry),
623 - rcl);
624 -
625 - char *module = NULL;
626 - rrdlabels_get_value_strdup_or_null(st->rrdlabels, &module, "_collect_module");
627 - if(!module || !*module) module = "[unset]";
628 -
629 - dictionary_set_advanced(ctl->alerts.by_module,
630 - module,
631 - -1,
632 - NULL,
633 - sizeof(struct alert_by_x_entry),
634 - rcl);
635 -
636 - if (ctl->options & (CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES | CONTEXT_V2_OPTION_ALERTS_WITH_VALUES)) {
637 - char key[20 + 1];
638 - snprintfz(key, sizeof(key) - 1, "%p", rcl);
639 -
640 - struct sql_alert_instance_v2_entry z = {
641 - .ati = ati,
642 - .tmp = rcl,
643 - };
644 - dictionary_set(ctl->alerts.alert_instances, key, &z, sizeof(z));
645 - }
646 - }
647 - rw_spinlock_read_unlock(&st->alerts.spinlock);
648 - }
649 - }
650 - dfe_done(ri);
651 -
652 - return matches != 0;
653 -}
654 -
655 -
656 -static ssize_t rrdcontext_to_json_v2_add_context(void *data, RRDCONTEXT_ACQUIRED *rca, bool queryable_context __maybe_unused) {
657 - struct rrdcontext_to_json_v2_data *ctl = data;
658 -
659 - RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
660 -
661 - if(ctl->window.enabled && !query_matches_retention(ctl->window.after, ctl->window.before, rc->first_time_s, (rc->flags & RRD_FLAG_COLLECTED) ? ctl->now : rc->last_time_s, 0))
662 - return 0; // continue to next context
663 -
664 - FTS_MATCH match = ctl->q.host_match;
665 - if((ctl->mode & CONTEXTS_V2_SEARCH) && ctl->q.pattern) {
666 - match = rrdcontext_to_json_v2_full_text_search(ctl, rc, ctl->q.pattern);
667 -
668 - if(match == FTS_MATCHED_NONE)
669 - return 0; // continue to next context
670 - }
671 -
672 - if(ctl->mode & CONTEXTS_V2_ALERTS) {
673 - if(!rrdcontext_matches_alert(ctl, rc))
674 - return 0; // continue to next context
675 - }
676 -
677 - if(ctl->contexts.dict) {
678 - struct context_v2_entry t = {
679 - .count = 1,
680 - .id = rc->id,
681 - .family = string_dup(rc->family),
682 - .priority = rc->priority,
683 - .first_time_s = rc->first_time_s,
684 - .last_time_s = rc->last_time_s,
685 - .flags = rc->flags,
686 - .match = match,
687 - };
688 -
689 - dictionary_set(ctl->contexts.dict, string2str(rc->id), &t, sizeof(struct context_v2_entry));
690 - }
691 -
692 - return 1;
693 -}
694 -
695 -void buffer_json_agent_status_id(BUFFER *wb, size_t ai, usec_t duration_ut) {
696 - buffer_json_member_add_object(wb, "st");
697 - {
698 - buffer_json_member_add_uint64(wb, "ai", ai);
699 - buffer_json_member_add_uint64(wb, "code", 200);
700 - buffer_json_member_add_string(wb, "msg", "");
701 - if (duration_ut)
702 - buffer_json_member_add_double(wb, "ms", (NETDATA_DOUBLE) duration_ut / 1000.0);
703 - }
704 - buffer_json_object_close(wb);
705 -}
706 -
707 -void buffer_json_node_add_v2(BUFFER *wb, RRDHOST *host, size_t ni, usec_t duration_ut, bool status) {
708 - buffer_json_member_add_string(wb, "mg", host->machine_guid);
709 -
710 - if(host->node_id)
711 - buffer_json_member_add_uuid(wb, "nd", host->node_id);
712 - buffer_json_member_add_string(wb, "nm", rrdhost_hostname(host));
713 - buffer_json_member_add_uint64(wb, "ni", ni);
714 -
715 - if(status)
716 - buffer_json_agent_status_id(wb, 0, duration_ut);
717 -}
718 -
719 -static void rrdhost_receiver_to_json(BUFFER *wb, RRDHOST_STATUS *s, const char *key) {
720 - buffer_json_member_add_object(wb, key);
721 - {
722 - buffer_json_member_add_uint64(wb, "id", s->ingest.id);
723 - buffer_json_member_add_uint64(wb, "hops", s->ingest.hops);
724 - buffer_json_member_add_string(wb, "type", rrdhost_ingest_type_to_string(s->ingest.type));
725 - buffer_json_member_add_string(wb, "status", rrdhost_ingest_status_to_string(s->ingest.status));
726 - buffer_json_member_add_time_t(wb, "since", s->ingest.since);
727 - buffer_json_member_add_time_t(wb, "age", s->now - s->ingest.since);
728 -
729 - if(s->ingest.type == RRDHOST_INGEST_TYPE_CHILD) {
730 - if(s->ingest.status == RRDHOST_INGEST_STATUS_OFFLINE)
731 - buffer_json_member_add_string(wb, "reason", stream_handshake_error_to_string(s->ingest.reason));
732 -
733 - if(s->ingest.status == RRDHOST_INGEST_STATUS_REPLICATING) {
734 - buffer_json_member_add_object(wb, "replication");
735 - {
736 - buffer_json_member_add_boolean(wb, "in_progress", s->ingest.replication.in_progress);
737 - buffer_json_member_add_double(wb, "completion", s->ingest.replication.completion);
738 - buffer_json_member_add_uint64(wb, "instances", s->ingest.replication.instances);
739 - }
740 - buffer_json_object_close(wb); // replication
741 - }
742 -
743 - if(s->ingest.status == RRDHOST_INGEST_STATUS_REPLICATING || s->ingest.status == RRDHOST_INGEST_STATUS_ONLINE) {
744 - buffer_json_member_add_object(wb, "source");
745 - {
746 - char buf[1024 + 1];
747 - snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->ingest.peers.local.ip, s->ingest.peers.local.port, s->ingest.ssl ? ":SSL" : "");
748 - buffer_json_member_add_string(wb, "local", buf);
749 -
750 - snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->ingest.peers.peer.ip, s->ingest.peers.peer.port, s->ingest.ssl ? ":SSL" : "");
751 - buffer_json_member_add_string(wb, "remote", buf);
752 -
753 - stream_capabilities_to_json_array(wb, s->ingest.capabilities, "capabilities");
754 - }
755 - buffer_json_object_close(wb); // source
756 - }
757 - }
758 - }
759 - buffer_json_object_close(wb); // collection
760 -}
761 -
762 -static void rrdhost_sender_to_json(BUFFER *wb, RRDHOST_STATUS *s, const char *key) {
763 - if(s->stream.status == RRDHOST_STREAM_STATUS_DISABLED)
764 - return;
765 -
766 - buffer_json_member_add_object(wb, key);
767 - {
768 - buffer_json_member_add_uint64(wb, "id", s->stream.id);
769 - buffer_json_member_add_uint64(wb, "hops", s->stream.hops);
770 - buffer_json_member_add_string(wb, "status", rrdhost_streaming_status_to_string(s->stream.status));
771 - buffer_json_member_add_time_t(wb, "since", s->stream.since);
772 - buffer_json_member_add_time_t(wb, "age", s->now - s->stream.since);
773 -
774 - if (s->stream.status == RRDHOST_STREAM_STATUS_OFFLINE)
775 - buffer_json_member_add_string(wb, "reason", stream_handshake_error_to_string(s->stream.reason));
776 -
777 - if (s->stream.status == RRDHOST_STREAM_STATUS_REPLICATING) {
778 - buffer_json_member_add_object(wb, "replication");
779 - {
780 - buffer_json_member_add_boolean(wb, "in_progress", s->stream.replication.in_progress);
781 - buffer_json_member_add_double(wb, "completion", s->stream.replication.completion);
782 - buffer_json_member_add_uint64(wb, "instances", s->stream.replication.instances);
783 - }
784 - buffer_json_object_close(wb);
785 - }
786 -
787 - buffer_json_member_add_object(wb, "destination");
788 - {
789 - char buf[1024 + 1];
790 - snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->stream.peers.local.ip, s->stream.peers.local.port, s->stream.ssl ? ":SSL" : "");
791 - buffer_json_member_add_string(wb, "local", buf);
792 -
793 - snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->stream.peers.peer.ip, s->stream.peers.peer.port, s->stream.ssl ? ":SSL" : "");
794 - buffer_json_member_add_string(wb, "remote", buf);
795 -
796 - stream_capabilities_to_json_array(wb, s->stream.capabilities, "capabilities");
797 -
798 - buffer_json_member_add_object(wb, "traffic");
799 - {
800 - buffer_json_member_add_boolean(wb, "compression", s->stream.compression);
801 - buffer_json_member_add_uint64(wb, "data", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_DATA]);
802 - buffer_json_member_add_uint64(wb, "metadata", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_METADATA]);
803 - buffer_json_member_add_uint64(wb, "functions", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_FUNCTIONS]);
804 - buffer_json_member_add_uint64(wb, "replication", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_REPLICATION]);
805 - buffer_json_member_add_uint64(wb, "dyncfg", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_DYNCFG]);
806 - }
807 - buffer_json_object_close(wb); // traffic
808 -
809 - buffer_json_member_add_array(wb, "candidates");
810 - struct rrdpush_destinations *d;
811 - for (d = s->host->destinations; d; d = d->next) {
812 - buffer_json_add_array_item_object(wb);
813 - buffer_json_member_add_uint64(wb, "attempts", d->attempts);
814 - {
815 -
816 - if (d->ssl) {
817 - snprintfz(buf, sizeof(buf) - 1, "%s:SSL", string2str(d->destination));
818 - buffer_json_member_add_string(wb, "destination", buf);
819 - }
820 - else
821 - buffer_json_member_add_string(wb, "destination", string2str(d->destination));
822 -
823 - buffer_json_member_add_time_t(wb, "since", d->since);
824 - buffer_json_member_add_time_t(wb, "age", s->now - d->since);
825 - buffer_json_member_add_string(wb, "last_handshake", stream_handshake_error_to_string(d->reason));
826 - if(d->postpone_reconnection_until > s->now) {
827 - buffer_json_member_add_time_t(wb, "next_check", d->postpone_reconnection_until);
828 - buffer_json_member_add_time_t(wb, "next_in", d->postpone_reconnection_until - s->now);
829 - }
830 - }
831 - buffer_json_object_close(wb); // each candidate
832 - }
833 - buffer_json_array_close(wb); // candidates
834 - }
835 - buffer_json_object_close(wb); // destination
836 - }
837 - buffer_json_object_close(wb); // streaming
838 -}
839 -
840 -static void agent_capabilities_to_json(BUFFER *wb, RRDHOST *host, const char *key) {
841 - buffer_json_member_add_array(wb, key);
842 -
843 - struct capability *capas = aclk_get_node_instance_capas(host);
844 - for(struct capability *capa = capas; capa->name ;capa++) {
845 - buffer_json_add_array_item_object(wb);
846 - {
847 - buffer_json_member_add_string(wb, "name", capa->name);
848 - buffer_json_member_add_uint64(wb, "version", capa->version);
849 - buffer_json_member_add_boolean(wb, "enabled", capa->enabled);
850 - }
851 - buffer_json_object_close(wb);
852 - }
853 - buffer_json_array_close(wb);
854 - freez(capas);
855 -}
856 -
857 -static inline void host_dyncfg_to_json_v2(BUFFER *wb, const char *key, RRDHOST_STATUS *s) {
858 - buffer_json_member_add_object(wb, key);
859 - {
860 - buffer_json_member_add_string(wb, "status", rrdhost_dyncfg_status_to_string(s->dyncfg.status));
861 - }
862 - buffer_json_object_close(wb); // health
863 -
864 -}
865 -
866 -static inline void rrdhost_health_to_json_v2(BUFFER *wb, const char *key, RRDHOST_STATUS *s) {
867 - buffer_json_member_add_object(wb, key);
868 - {
869 - buffer_json_member_add_string(wb, "status", rrdhost_health_status_to_string(s->health.status));
870 - if (s->health.status == RRDHOST_HEALTH_STATUS_RUNNING) {
871 - buffer_json_member_add_object(wb, "alerts");
872 - {
873 - buffer_json_member_add_uint64(wb, "critical", s->health.alerts.critical);
874 - buffer_json_member_add_uint64(wb, "warning", s->health.alerts.warning);
875 - buffer_json_member_add_uint64(wb, "clear", s->health.alerts.clear);
876 - buffer_json_member_add_uint64(wb, "undefined", s->health.alerts.undefined);
877 - buffer_json_member_add_uint64(wb, "uninitialized", s->health.alerts.uninitialized);
878 - }
879 - buffer_json_object_close(wb); // alerts
880 - }
881 - }
882 - buffer_json_object_close(wb); // health
883 -}
884 -
885 -static void rrdcontext_to_json_v2_rrdhost(BUFFER *wb, RRDHOST *host, struct rrdcontext_to_json_v2_data *ctl, size_t node_id) {
886 - buffer_json_add_array_item_object(wb); // this node
887 - buffer_json_node_add_v2(wb, host, node_id, 0,
888 - (ctl->mode & CONTEXTS_V2_AGENTS) && !(ctl->mode & CONTEXTS_V2_NODE_INSTANCES));
889 -
890 - if(ctl->mode & (CONTEXTS_V2_NODES_INFO | CONTEXTS_V2_NODE_INSTANCES)) {
891 - RRDHOST_STATUS s;
892 - rrdhost_status(host, ctl->now, &s);
893 -
894 - if (ctl->mode & (CONTEXTS_V2_NODES_INFO)) {
895 - buffer_json_member_add_string(wb, "v", rrdhost_program_version(host));
896 -
897 - host_labels2json(host, wb, "labels");
898 -
899 - if (host->system_info) {
900 - buffer_json_member_add_object(wb, "hw");
901 - {
902 - buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture);
903 - buffer_json_member_add_string_or_empty(wb, "cpu_frequency", host->system_info->host_cpu_freq);
904 - buffer_json_member_add_string_or_empty(wb, "cpus", host->system_info->host_cores);
905 - buffer_json_member_add_string_or_empty(wb, "memory", host->system_info->host_ram_total);
906 - buffer_json_member_add_string_or_empty(wb, "disk_space", host->system_info->host_disk_space);
907 - buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization);
908 - buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container);
909 - }
910 - buffer_json_object_close(wb);
911 -
912 - buffer_json_member_add_object(wb, "os");
913 - {
914 - buffer_json_member_add_string_or_empty(wb, "id", host->system_info->host_os_id);
915 - buffer_json_member_add_string_or_empty(wb, "nm", host->system_info->host_os_name);
916 - buffer_json_member_add_string_or_empty(wb, "v", host->system_info->host_os_version);
917 - buffer_json_member_add_object(wb, "kernel");
918 - buffer_json_member_add_string_or_empty(wb, "nm", host->system_info->kernel_name);
919 - buffer_json_member_add_string_or_empty(wb, "v", host->system_info->kernel_version);
920 - buffer_json_object_close(wb);
921 - }
922 - buffer_json_object_close(wb);
923 - }
924 -
925 - // created - the node is created but never connected to cloud
926 - // unreachable - not currently connected
927 - // stale - connected but not having live data
928 - // reachable - connected with live data
929 - // pruned - not connected for some time and has been removed
930 - buffer_json_member_add_string(wb, "state", rrdhost_state_cloud_emulation(host) ? "reachable" : "stale");
931 -
932 - rrdhost_health_to_json_v2(wb, "health", &s);
933 - agent_capabilities_to_json(wb, host, "capabilities");
934 - }
935 -
936 - if (ctl->mode & (CONTEXTS_V2_NODE_INSTANCES)) {
937 - buffer_json_member_add_array(wb, "instances");
938 - buffer_json_add_array_item_object(wb); // this instance
939 - {
940 - buffer_json_agent_status_id(wb, 0, 0);
941 -
942 - buffer_json_member_add_object(wb, "db");
943 - {
944 - buffer_json_member_add_string(wb, "status", rrdhost_db_status_to_string(s.db.status));
945 - buffer_json_member_add_string(wb, "liveness", rrdhost_db_liveness_to_string(s.db.liveness));
946 - buffer_json_member_add_string(wb, "mode", rrd_memory_mode_name(s.db.mode));
947 - buffer_json_member_add_time_t(wb, "first_time", s.db.first_time_s);
948 - buffer_json_member_add_time_t(wb, "last_time", s.db.last_time_s);
949 - buffer_json_member_add_uint64(wb, "metrics", s.db.metrics);
950 - buffer_json_member_add_uint64(wb, "instances", s.db.instances);
951 - buffer_json_member_add_uint64(wb, "contexts", s.db.contexts);
952 - }
953 - buffer_json_object_close(wb);
954 -
955 - rrdhost_receiver_to_json(wb, &s, "ingest");
956 - rrdhost_sender_to_json(wb, &s, "stream");
957 -
958 - buffer_json_member_add_object(wb, "ml");
959 - buffer_json_member_add_string(wb, "status", rrdhost_ml_status_to_string(s.ml.status));
960 - buffer_json_member_add_string(wb, "type", rrdhost_ml_type_to_string(s.ml.type));
961 - if (s.ml.status == RRDHOST_ML_STATUS_RUNNING) {
962 - buffer_json_member_add_object(wb, "metrics");
963 - {
964 - buffer_json_member_add_uint64(wb, "anomalous", s.ml.metrics.anomalous);
965 - buffer_json_member_add_uint64(wb, "normal", s.ml.metrics.normal);
966 - buffer_json_member_add_uint64(wb, "trained", s.ml.metrics.trained);
967 - buffer_json_member_add_uint64(wb, "pending", s.ml.metrics.pending);
968 - buffer_json_member_add_uint64(wb, "silenced", s.ml.metrics.silenced);
969 - }
970 - buffer_json_object_close(wb); // metrics
971 - }
972 - buffer_json_object_close(wb); // ml
973 -
974 - rrdhost_health_to_json_v2(wb, "health", &s);
975 -
976 - host_functions2json(host, wb); // functions
977 - agent_capabilities_to_json(wb, host, "capabilities");
978 -
979 - host_dyncfg_to_json_v2(wb, "dyncfg", &s);
980 - }
981 - buffer_json_object_close(wb); // this instance
982 - buffer_json_array_close(wb); // instances
983 - }
984 - }
985 - buffer_json_object_close(wb); // this node
986 -}
987 -
988 -static ssize_t rrdcontext_to_json_v2_add_host(void *data, RRDHOST *host, bool queryable_host) {
989 - if(!queryable_host || !host->rrdctx.contexts)
990 - // the host matches the 'scope_host' but does not match the 'host' patterns
991 - // or the host does not have any contexts
992 - return 0; // continue to next host
993 -
994 - struct rrdcontext_to_json_v2_data *ctl = data;
995 -
996 - if(ctl->window.enabled && !rrdhost_matches_window(host, ctl->window.after, ctl->window.before, ctl->now))
997 - // the host does not have data in the requested window
998 - return 0; // continue to next host
999 -
1000 - if(ctl->request->timeout_ms && now_monotonic_usec() > ctl->timings.received_ut + ctl->request->timeout_ms * USEC_PER_MS)
1001 - // timed out
1002 - return -2; // stop the query
1003 -
1004 - if(ctl->request->interrupt_callback && ctl->request->interrupt_callback(ctl->request->interrupt_callback_data))
1005 - // interrupted
1006 - return -1; // stop the query
1007 -
1008 - bool host_matched = (ctl->mode & CONTEXTS_V2_NODES);
1009 - bool do_contexts = (ctl->mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_ALERTS));
1010 -
1011 - ctl->q.host_match = FTS_MATCHED_NONE;
1012 - if((ctl->mode & CONTEXTS_V2_SEARCH)) {
1013 - // check if we match the host itself
1014 - if(ctl->q.pattern && (
1015 - full_text_search_string(&ctl->q.fts, ctl->q.pattern, host->hostname) ||
1016 - full_text_search_char(&ctl->q.fts, ctl->q.pattern, host->machine_guid) ||
1017 - (ctl->q.pattern && full_text_search_char(&ctl->q.fts, ctl->q.pattern, ctl->q.host_node_id_str)))) {
1018 - ctl->q.host_match = FTS_MATCHED_HOST;
1019 - do_contexts = true;
1020 - }
1021 - }
1022 -
1023 - if(do_contexts) {
1024 - // save it
1025 - SIMPLE_PATTERN *old_q = ctl->q.pattern;
1026 -
1027 - if(ctl->q.host_match == FTS_MATCHED_HOST)
1028 - // do not do pattern matching on contexts - we matched the host itself
1029 - ctl->q.pattern = NULL;
1030 -
1031 - ssize_t added = query_scope_foreach_context(
1032 - host, ctl->request->scope_contexts,
1033 - ctl->contexts.scope_pattern, ctl->contexts.pattern,
1034 - rrdcontext_to_json_v2_add_context, queryable_host, ctl);
1035 -
1036 - // restore it
1037 - ctl->q.pattern = old_q;
1038 -
1039 - if(unlikely(added < 0))
1040 - return -1; // stop the query
1041 -
1042 - if(added)
1043 - host_matched = true;
1044 - }
1045 -
1046 - if(!host_matched)
1047 - return 0;
1048 -
1049 - if(ctl->mode & CONTEXTS_V2_FUNCTIONS) {
1050 - struct function_v2_entry t = {
1051 - .used = 1,
1052 - .size = 1,
1053 - .node_ids = &ctl->nodes.ni,
1054 - .help = NULL,
1055 - .tags = NULL,
1056 - .access = HTTP_ACCESS_ALL,
1057 - .priority = RRDFUNCTIONS_PRIORITY_DEFAULT,
1058 - };
1059 - host_functions_to_dict(host, ctl->functions.dict, &t, sizeof(t), &t.help, &t.tags, &t.access, &t.priority);
1060 - }
1061 -
1062 - if(ctl->mode & CONTEXTS_V2_NODES) {
1063 - struct contexts_v2_node t = {
1064 - .ni = ctl->nodes.ni++,
1065 - .host = host,
1066 - };
1067 -
1068 - dictionary_set(ctl->nodes.dict, host->machine_guid, &t, sizeof(struct contexts_v2_node));
1069 - }
1070 -
1071 - return 1;
1072 -}
1073 -
1074 -static void buffer_json_contexts_v2_mode_to_array(BUFFER *wb, const char *key, CONTEXTS_V2_MODE mode) {
1075 - buffer_json_member_add_array(wb, key);
1076 -
1077 - if(mode & CONTEXTS_V2_VERSIONS)
1078 - buffer_json_add_array_item_string(wb, "versions");
1079 -
1080 - if(mode & CONTEXTS_V2_AGENTS)
1081 - buffer_json_add_array_item_string(wb, "agents");
1082 -
1083 - if(mode & CONTEXTS_V2_AGENTS_INFO)
1084 - buffer_json_add_array_item_string(wb, "agents-info");
1085 -
1086 - if(mode & CONTEXTS_V2_NODES)
1087 - buffer_json_add_array_item_string(wb, "nodes");
1088 -
1089 - if(mode & CONTEXTS_V2_NODES_INFO)
1090 - buffer_json_add_array_item_string(wb, "nodes-info");
1091 -
1092 - if(mode & CONTEXTS_V2_NODE_INSTANCES)
1093 - buffer_json_add_array_item_string(wb, "nodes-instances");
1094 -
1095 - if(mode & CONTEXTS_V2_CONTEXTS)
1096 - buffer_json_add_array_item_string(wb, "contexts");
1097 -
1098 - if(mode & CONTEXTS_V2_SEARCH)
1099 - buffer_json_add_array_item_string(wb, "search");
1100 -
1101 - if(mode & CONTEXTS_V2_ALERTS)
1102 - buffer_json_add_array_item_string(wb, "alerts");
1103 -
1104 - if(mode & CONTEXTS_V2_ALERT_TRANSITIONS)
1105 - buffer_json_add_array_item_string(wb, "alert_transitions");
1106 -
1107 - buffer_json_array_close(wb);
1108 -}
1109 -
1110 -void buffer_json_query_timings(BUFFER *wb, const char *key, struct query_timings *timings) {
1111 - timings->finished_ut = now_monotonic_usec();
1112 - if(!timings->executed_ut)
1113 - timings->executed_ut = timings->finished_ut;
1114 - if(!timings->preprocessed_ut)
1115 - timings->preprocessed_ut = timings->received_ut;
1116 - buffer_json_member_add_object(wb, key);
1117 - buffer_json_member_add_double(wb, "prep_ms", (NETDATA_DOUBLE)(timings->preprocessed_ut - timings->received_ut) / USEC_PER_MS);
1118 - buffer_json_member_add_double(wb, "query_ms", (NETDATA_DOUBLE)(timings->executed_ut - timings->preprocessed_ut) / USEC_PER_MS);
1119 - buffer_json_member_add_double(wb, "output_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->executed_ut) / USEC_PER_MS);
1120 - buffer_json_member_add_double(wb, "total_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS);
1121 - buffer_json_member_add_double(wb, "cloud_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS);
1122 - buffer_json_object_close(wb);
1123 -}
1124 -
1125 -void build_info_to_json_object(BUFFER *b);
1126 -
1127 -static void convert_seconds_to_dhms(time_t seconds, char *result, int result_size) {
1128 - int days, hours, minutes;
1129 -
1130 - days = (int) (seconds / (24 * 3600));
1131 - seconds = (int) (seconds % (24 * 3600));
1132 - hours = (int) (seconds / 3600);
1133 - seconds %= 3600;
1134 - minutes = (int) (seconds / 60);
1135 - seconds %= 60;
1136 -
1137 - // Format the result into the provided string buffer
1138 - BUFFER *buf = buffer_create(128, NULL);
1139 - if (days)
1140 - buffer_sprintf(buf,"%d day%s%s", days, days==1 ? "" : "s", hours || minutes ? ", " : "");
1141 - if (hours)
1142 - buffer_sprintf(buf,"%d hour%s%s", hours, hours==1 ? "" : "s", minutes ? ", " : "");
1143 - if (minutes)
1144 - buffer_sprintf(buf,"%d minute%s%s", minutes, minutes==1 ? "" : "s", seconds ? ", " : "");
1145 - if (seconds)
1146 - buffer_sprintf(buf,"%d second%s", (int) seconds, seconds==1 ? "" : "s");
1147 - strncpyz(result, buffer_tostring(buf), result_size);
1148 - buffer_free(buf);
1149 -}
1150 -
1151 -void buffer_json_agents_v2(BUFFER *wb, struct query_timings *timings, time_t now_s, bool info, bool array) {
1152 - if(!now_s)
1153 - now_s = now_realtime_sec();
1154 -
1155 - if(array) {
1156 - buffer_json_member_add_array(wb, "agents");
1157 - buffer_json_add_array_item_object(wb);
1158 - }
1159 - else
1160 - buffer_json_member_add_object(wb, "agent");
1161 -
1162 - buffer_json_member_add_string(wb, "mg", localhost->machine_guid);
1163 - buffer_json_member_add_uuid(wb, "nd", localhost->node_id);
1164 - buffer_json_member_add_string(wb, "nm", rrdhost_hostname(localhost));
1165 - buffer_json_member_add_time_t(wb, "now", now_s);
1166 -
1167 - if(array)
1168 - buffer_json_member_add_uint64(wb, "ai", 0);
1169 -
1170 - if(info) {
1171 - buffer_json_member_add_object(wb, "application");
1172 - build_info_to_json_object(wb);
1173 - buffer_json_object_close(wb); // netdata
1174 -
1175 - buffer_json_cloud_status(wb, now_s);
1176 -
1177 - buffer_json_member_add_array(wb, "db_size");
1178 - size_t group_seconds = localhost->rrd_update_every;
1179 - for (size_t tier = 0; tier < storage_tiers; tier++) {
1180 - STORAGE_ENGINE *eng = localhost->db[tier].eng;
1181 - if (!eng) continue;
1182 -
1183 - group_seconds *= storage_tiers_grouping_iterations[tier];
1184 - uint64_t max = storage_engine_disk_space_max(eng->seb, localhost->db[tier].si);
1185 - uint64_t used = storage_engine_disk_space_used(eng->seb, localhost->db[tier].si);
1186 -#ifdef ENABLE_DBENGINE
1187 - if (!max && eng->seb == STORAGE_ENGINE_BACKEND_DBENGINE) {
1188 - max = get_directory_free_bytes_space(multidb_ctx[tier]);
1189 - max += used;
1190 - }
1191 -#endif
1192 - time_t first_time_s = storage_engine_global_first_time_s(eng->seb, localhost->db[tier].si);
1193 - size_t currently_collected_metrics = storage_engine_collected_metrics(eng->seb, localhost->db[tier].si);
1194 -
1195 - NETDATA_DOUBLE percent;
1196 - if (used && max)
1197 - percent = (NETDATA_DOUBLE) used * 100.0 / (NETDATA_DOUBLE) max;
1198 - else
1199 - percent = 0.0;
1200 -
1201 - buffer_json_add_array_item_object(wb);
1202 - buffer_json_member_add_uint64(wb, "tier", tier);
1203 - char human_retention[128];
1204 - convert_seconds_to_dhms((time_t) group_seconds, human_retention, sizeof(human_retention) - 1);
1205 - buffer_json_member_add_string(wb, "point_every", human_retention);
1206 -
1207 - buffer_json_member_add_uint64(wb, "metrics", storage_engine_metrics(eng->seb, localhost->db[tier].si));
1208 - buffer_json_member_add_uint64(wb, "samples", storage_engine_samples(eng->seb, localhost->db[tier].si));
1209 -
1210 - if(used || max) {
1211 - buffer_json_member_add_uint64(wb, "disk_used", used);
1212 - buffer_json_member_add_uint64(wb, "disk_max", max);
1213 - buffer_json_member_add_double(wb, "disk_percent", percent);
1214 - }
1215 -
1216 - if(first_time_s) {
1217 - time_t retention = now_s - first_time_s;
1218 -
1219 - buffer_json_member_add_time_t(wb, "from", first_time_s);
1220 - buffer_json_member_add_time_t(wb, "to", now_s);
1221 - buffer_json_member_add_time_t(wb, "retention", retention);
1222 -
1223 - convert_seconds_to_dhms(retention, human_retention, sizeof(human_retention) - 1);
1224 - buffer_json_member_add_string(wb, "retention_human", human_retention);
1225 -
1226 - if(used || max) { // we have disk space information
1227 - time_t time_retention = 0;
1228 -#ifdef ENABLE_DBENGINE
1229 - time_retention = multidb_ctx[tier]->config.max_retention_s;
1230 -#endif
1231 - time_t space_retention = (time_t)((NETDATA_DOUBLE)(now_s - first_time_s) * 100.0 / percent);
1232 - time_t actual_retention = MIN(space_retention, time_retention ? time_retention : space_retention);
1233 -
1234 - if (time_retention) {
1235 - convert_seconds_to_dhms(time_retention, human_retention, sizeof(human_retention) - 1);
1236 - buffer_json_member_add_time_t(wb, "requested_retention", time_retention);
1237 - buffer_json_member_add_string(wb, "requested_retention_human", human_retention);
1238 - }
1239 -
1240 - convert_seconds_to_dhms(actual_retention, human_retention, sizeof(human_retention) - 1);
1241 - buffer_json_member_add_time_t(wb, "expected_retention", actual_retention);
1242 - buffer_json_member_add_string(wb, "expected_retention_human", human_retention);
1243 - }
1244 - }
1245 -
1246 - if(currently_collected_metrics)
1247 - buffer_json_member_add_uint64(wb, "currently_collected_metrics", currently_collected_metrics);
1248 -
1249 - buffer_json_object_close(wb);
1250 - }
1251 - buffer_json_array_close(wb); // db_size
1252 - }
1253 -
1254 - if(timings)
1255 - buffer_json_query_timings(wb, "timings", timings);
1256 -
1257 - buffer_json_object_close(wb);
1258 -
1259 - if(array)
1260 - buffer_json_array_close(wb);
1261 -}
1262 -
1263 -void buffer_json_cloud_timings(BUFFER *wb, const char *key, struct query_timings *timings) {
1264 - if(!timings->finished_ut)
1265 - timings->finished_ut = now_monotonic_usec();
1266 -
1267 - buffer_json_member_add_object(wb, key);
1268 - buffer_json_member_add_double(wb, "routing_ms", 0.0);
1269 - buffer_json_member_add_double(wb, "node_max_ms", 0.0);
1270 - buffer_json_member_add_double(wb, "total_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS);
1271 - buffer_json_object_close(wb);
1272 -}
1273 -
1274 -static void functions_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
1275 - struct function_v2_entry *t = value;
1276 -
1277 - // it is initialized with a static reference - we need to mallocz() the array
1278 - size_t *v = t->node_ids;
1279 - t->node_ids = mallocz(sizeof(size_t));
1280 - *t->node_ids = *v;
1281 - t->size = 1;
1282 - t->used = 1;
1283 -}
1284 -
1285 -static bool functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
1286 - struct function_v2_entry *t = old_value, *n = new_value;
1287 - size_t *v = n->node_ids;
1288 -
1289 - if(t->used >= t->size) {
1290 - t->node_ids = reallocz(t->node_ids, t->size * 2 * sizeof(size_t));
1291 - t->size *= 2;
1292 - }
1293 -
1294 - t->node_ids[t->used++] = *v;
1295 -
1296 - return true;
1297 -}
1298 -
1299 -static void functions_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
1300 - struct function_v2_entry *t = value;
1301 - freez(t->node_ids);
1302 -}
1303 -
1304 -static bool contexts_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
1305 - struct context_v2_entry *o = old_value;
1306 - struct context_v2_entry *n = new_value;
1307 -
1308 - o->count++;
1309 -
1310 - if(o->family != n->family) {
1311 - if((o->flags & RRD_FLAG_COLLECTED) && !(n->flags & RRD_FLAG_COLLECTED))
1312 - // keep old
1313 - ;
1314 - else if(!(o->flags & RRD_FLAG_COLLECTED) && (n->flags & RRD_FLAG_COLLECTED)) {
1315 - // keep new
1316 - string_freez(o->family);
1317 - o->family = string_dup(n->family);
1318 - }
1319 - else {
1320 - // merge
1321 - STRING *old_family = o->family;
1322 - o->family = string_2way_merge(o->family, n->family);
1323 - string_freez(old_family);
1324 - }
1325 - }
1326 -
1327 - if(o->priority != n->priority) {
1328 - if((o->flags & RRD_FLAG_COLLECTED) && !(n->flags & RRD_FLAG_COLLECTED))
1329 - // keep o
1330 - ;
1331 - else if(!(o->flags & RRD_FLAG_COLLECTED) && (n->flags & RRD_FLAG_COLLECTED))
1332 - // keep n
1333 - o->priority = n->priority;
1334 - else
1335 - // keep the min
1336 - o->priority = MIN(o->priority, n->priority);
1337 - }
1338 -
1339 - if(o->first_time_s && n->first_time_s)
1340 - o->first_time_s = MIN(o->first_time_s, n->first_time_s);
1341 - else if(!o->first_time_s)
1342 - o->first_time_s = n->first_time_s;
1343 -
1344 - if(o->last_time_s && n->last_time_s)
1345 - o->last_time_s = MAX(o->last_time_s, n->last_time_s);
1346 - else if(!o->last_time_s)
1347 - o->last_time_s = n->last_time_s;
1348 -
1349 - o->flags |= n->flags;
1350 - o->match = MIN(o->match, n->match);
1351 -
1352 - string_freez(n->family);
1353 -
1354 - return true;
1355 -}
1356 -
1357 -static void contexts_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
1358 - struct context_v2_entry *z = value;
1359 - string_freez(z->family);
1360 -}
1361 -
1362 -static void rrdcontext_v2_set_transition_filter(const char *machine_guid, const char *context, time_t alarm_id, void *data) {
1363 - struct rrdcontext_to_json_v2_data *ctl = data;
1364 -
1365 - if(machine_guid && *machine_guid) {
1366 - if(ctl->nodes.scope_pattern)
1367 - simple_pattern_free(ctl->nodes.scope_pattern);
1368 -
1369 - if(ctl->nodes.pattern)
1370 - simple_pattern_free(ctl->nodes.pattern);
1371 -
1372 - ctl->nodes.scope_pattern = string_to_simple_pattern(machine_guid);
1373 - ctl->nodes.pattern = NULL;
1374 - }
1375 -
1376 - if(context && *context) {
1377 - if(ctl->contexts.scope_pattern)
1378 - simple_pattern_free(ctl->contexts.scope_pattern);
1379 -
1380 - if(ctl->contexts.pattern)
1381 - simple_pattern_free(ctl->contexts.pattern);
1382 -
1383 - ctl->contexts.scope_pattern = string_to_simple_pattern(context);
1384 - ctl->contexts.pattern = NULL;
1385 - }
1386 -
1387 - ctl->alerts.alarm_id_filter = alarm_id;
1388 -}
1389 -
1390 -struct alert_instances_callback_data {
1391 - BUFFER *wb;
1392 - struct rrdcontext_to_json_v2_data *ctl;
1393 - bool debug;
1394 -};
1395 -
1396 -static void contexts_v2_alert_config_to_json_from_sql_alert_config_data(struct sql_alert_config_data *t, void *data) {
1397 - struct alert_transitions_callback_data *d = data;
1398 - BUFFER *wb = d->wb;
1399 - bool debug = d->debug;
1400 - d->configs_added++;
1401 -
1402 - if(d->only_one_config)
1403 - buffer_json_add_array_item_object(wb); // alert config
1404 -
1405 - {
1406 - buffer_json_member_add_string(wb, "name", t->name);
1407 - buffer_json_member_add_uuid(wb, "config_hash_id", t->config_hash_id);
1408 -
1409 - buffer_json_member_add_object(wb, "selectors");
1410 - {
1411 - bool is_template = t->selectors.on_template && *t->selectors.on_template ? true : false;
1412 - buffer_json_member_add_string(wb, "type", is_template ? "template" : "alarm");
1413 - buffer_json_member_add_string(wb, "on", is_template ? t->selectors.on_template : t->selectors.on_key);
1414 -
1415 - buffer_json_member_add_string(wb, "families", t->selectors.families);
1416 - buffer_json_member_add_string(wb, "host_labels", t->selectors.host_labels);
1417 - buffer_json_member_add_string(wb, "chart_labels", t->selectors.chart_labels);
1418 - }
1419 - buffer_json_object_close(wb); // selectors
1420 -
1421 - buffer_json_member_add_object(wb, "value"); // value
1422 - {
1423 - // buffer_json_member_add_string(wb, "every", t->value.every); // does not exist in Netdata Cloud
1424 - buffer_json_member_add_string(wb, "units", t->value.units);
1425 - buffer_json_member_add_uint64(wb, "update_every", t->value.update_every);
1426 -
1427 - if (t->value.db.after || debug) {
1428 - buffer_json_member_add_object(wb, "db");
1429 - {
1430 - // buffer_json_member_add_string(wb, "lookup", t->value.db.lookup); // does not exist in Netdata Cloud
1431 -
1432 - buffer_json_member_add_time_t(wb, "after", t->value.db.after);
1433 - buffer_json_member_add_time_t(wb, "before", t->value.db.before);
1434 - buffer_json_member_add_string(wb, "time_group_condition", alerts_group_conditions_id2txt(t->value.db.time_group_condition));
1435 - buffer_json_member_add_double(wb, "time_group_value", t->value.db.time_group_value);
1436 - buffer_json_member_add_string(wb, "dims_group", alerts_dims_grouping_id2group(t->value.db.dims_group));
1437 - buffer_json_member_add_string(wb, "data_source", alerts_data_source_id2source(t->value.db.data_source));
1438 - buffer_json_member_add_string(wb, "method", t->value.db.method);
1439 - buffer_json_member_add_string(wb, "dimensions", t->value.db.dimensions);
1440 - rrdr_options_to_buffer_json_array(wb, "options", (RRDR_OPTIONS)t->value.db.options);
1441 - }
1442 - buffer_json_object_close(wb); // db
1443 - }
1444 -
1445 - if (t->value.calc || debug)
1446 - buffer_json_member_add_string(wb, "calc", t->value.calc);
1447 - }
1448 - buffer_json_object_close(wb); // value
1449 -
1450 - if (t->status.warn || t->status.crit || debug) {
1451 - buffer_json_member_add_object(wb, "status"); // status
1452 - {
1453 - NETDATA_DOUBLE green = t->status.green ? str2ndd(t->status.green, NULL) : NAN;
1454 - NETDATA_DOUBLE red = t->status.red ? str2ndd(t->status.red, NULL) : NAN;
1455 -
1456 - if (!isnan(green) || debug)
1457 - buffer_json_member_add_double(wb, "green", green);
1458 -
1459 - if (!isnan(red) || debug)
1460 - buffer_json_member_add_double(wb, "red", red);
1461 -
1462 - if (t->status.warn || debug)
1463 - buffer_json_member_add_string(wb, "warn", t->status.warn);
1464 -
1465 - if (t->status.crit || debug)
1466 - buffer_json_member_add_string(wb, "crit", t->status.crit);
1467 - }
1468 - buffer_json_object_close(wb); // status
1469 - }
1470 -
1471 - buffer_json_member_add_object(wb, "notification");
1472 - {
1473 - buffer_json_member_add_string(wb, "type", "agent");
1474 - buffer_json_member_add_string(wb, "exec", t->notification.exec ? t->notification.exec : NULL);
1475 - buffer_json_member_add_string(wb, "to", t->notification.to_key ? t->notification.to_key : string2str(localhost->health.health_default_recipient));
1476 - buffer_json_member_add_string(wb, "delay", t->notification.delay);
1477 - buffer_json_member_add_string(wb, "repeat", t->notification.repeat);
1478 - buffer_json_member_add_string(wb, "options", t->notification.options);
1479 - }
1480 - buffer_json_object_close(wb); // notification
1481 -
1482 - buffer_json_member_add_string(wb, "class", t->classification);
1483 - buffer_json_member_add_string(wb, "component", t->component);
1484 - buffer_json_member_add_string(wb, "type", t->type);
1485 - buffer_json_member_add_string(wb, "info", t->info);
1486 - buffer_json_member_add_string(wb, "summary", t->summary);
1487 - // buffer_json_member_add_string(wb, "source", t->source); // moved to alert instance
1488 - }
1489 -
1490 - if(d->only_one_config)
1491 - buffer_json_object_close(wb);
1492 -}
1493 -
1494 -int contexts_v2_alert_config_to_json(struct web_client *w, const char *config_hash_id) {
1495 - struct alert_transitions_callback_data data = {
1496 - .wb = w->response.data,
1497 - .debug = false,
1498 - .only_one_config = false,
1499 - };
1500 - DICTIONARY *configs = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
1501 - dictionary_set(configs, config_hash_id, NULL, 0);
1502 -
1503 - buffer_flush(w->response.data);
1504 -
1505 - buffer_json_initialize(w->response.data, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
1506 -
1507 - int added = sql_get_alert_configuration(configs, contexts_v2_alert_config_to_json_from_sql_alert_config_data, &data, false);
1508 - buffer_json_finalize(w->response.data);
1509 -
1510 - int ret = HTTP_RESP_OK;
1511 -
1512 - if(added <= 0) {
1513 - buffer_flush(w->response.data);
1514 - w->response.data->content_type = CT_TEXT_PLAIN;
1515 - if(added < 0) {
1516 - buffer_strcat(w->response.data, "Failed to execute SQL query.");
1517 - ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
1518 - }
1519 - else {
1520 - buffer_strcat(w->response.data, "Config is not found.");
1521 - ret = HTTP_RESP_NOT_FOUND;
1522 - }
1523 - }
1524 -
1525 - return ret;
1526 -}
1527 -
1528 -static int contexts_v2_alert_instance_to_json_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
1529 - struct sql_alert_instance_v2_entry *t = value;
1530 - struct alert_instances_callback_data *d = data;
1531 - struct rrdcontext_to_json_v2_data *ctl = d->ctl; (void)ctl;
1532 - bool debug = d->debug; (void)debug;
1533 - BUFFER *wb = d->wb;
1534 -
1535 - buffer_json_add_array_item_object(wb);
1536 - {
1537 - buffer_json_member_add_uint64(wb, "ni", t->ni);
1538 -
1539 - buffer_json_member_add_string(wb, "nm", string2str(t->name));
1540 - buffer_json_member_add_string(wb, "ch", string2str(t->chart_id));
1541 - buffer_json_member_add_string(wb, "ch_n", string2str(t->chart_name));
1542 -
1543 - if(ctl->request->options & CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY)
1544 - buffer_json_member_add_uint64(wb, "ati", t->ati);
1545 -
1546 - if(ctl->request->options & CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES) {
1547 - buffer_json_member_add_string(wb, "units", string2str(t->units));
1548 - buffer_json_member_add_string(wb, "fami", string2str(t->family));
1549 - buffer_json_member_add_string(wb, "info", string2str(t->info));
1550 - buffer_json_member_add_string(wb, "sum", string2str(t->summary));
1551 - buffer_json_member_add_string(wb, "ctx", string2str(t->context));
1552 - buffer_json_member_add_string(wb, "st", rrdcalc_status2string(t->status));
1553 - buffer_json_member_add_uuid(wb, "tr_i", &t->last_transition_id);
1554 - buffer_json_member_add_double(wb, "tr_v", t->last_status_change_value);
1555 - buffer_json_member_add_time_t(wb, "tr_t", t->last_status_change);
1556 - buffer_json_member_add_uuid(wb, "cfg", &t->config_hash_id);
1557 - buffer_json_member_add_string(wb, "src", string2str(t->source));
1558 -
1559 - buffer_json_member_add_string(wb, "to", string2str(t->recipient));
1560 - buffer_json_member_add_string(wb, "tp", string2str(t->type));
1561 - buffer_json_member_add_string(wb, "cm", string2str(t->component));
1562 - buffer_json_member_add_string(wb, "cl", string2str(t->classification));
1563 -
1564 - // Agent specific fields
1565 - buffer_json_member_add_uint64(wb, "gi", t->global_id);
1566 - // rrdcalc_flags_to_json_array (wb, "flags", t->flags);
1567 - }
1568 -
1569 - if(ctl->request->options & CONTEXT_V2_OPTION_ALERTS_WITH_VALUES) {
1570 - // Netdata Cloud fetched these by querying the agents
1571 - buffer_json_member_add_double(wb, "v", t->value);
1572 - buffer_json_member_add_time_t(wb, "t", t->last_updated);
1573 - }
1574 - }
1575 - buffer_json_object_close(wb); // alert instance
1576 -
1577 - return 1;
1578 -}
1579 -
1580 -static void contexts_v2_alerts_by_x_update_prototypes(void *data, STRING *type, STRING *component, STRING *classification, STRING *recipient) {
1581 - struct rrdcontext_to_json_v2_data *ctl = data;
1582 -
1583 - dictionary_set_advanced(ctl->alerts.by_type, string2str(type), (ssize_t)string_strlen(type), NULL, sizeof(struct alert_by_x_entry), NULL);
1584 - dictionary_set_advanced(ctl->alerts.by_component, string2str(component), (ssize_t)string_strlen(component), NULL, sizeof(struct alert_by_x_entry), NULL);
1585 - dictionary_set_advanced(ctl->alerts.by_classification, string2str(classification), (ssize_t)string_strlen(classification), NULL, sizeof(struct alert_by_x_entry), NULL);
1586 - dictionary_set_advanced(ctl->alerts.by_recipient, string2str(recipient), (ssize_t)string_strlen(recipient), NULL, sizeof(struct alert_by_x_entry), NULL);
1587 -}
1588 -
1589 -static void contexts_v2_alerts_by_x_to_json(BUFFER *wb, DICTIONARY *dict, const char *key) {
1590 - buffer_json_member_add_array(wb, key);
1591 - {
1592 - struct alert_by_x_entry *b;
1593 - dfe_start_read(dict, b) {
1594 - buffer_json_add_array_item_object(wb);
1595 - {
1596 - buffer_json_member_add_string(wb, "name", b_dfe.name);
1597 - buffer_json_member_add_uint64(wb, "cr", b->running.counts.critical);
1598 - buffer_json_member_add_uint64(wb, "wr", b->running.counts.warning);
1599 - buffer_json_member_add_uint64(wb, "cl", b->running.counts.clear);
1600 - buffer_json_member_add_uint64(wb, "er", b->running.counts.error);
1601 - buffer_json_member_add_uint64(wb, "running", b->running.total);
1602 -
1603 - buffer_json_member_add_uint64(wb, "running_silent", b->running.silent);
1604 -
1605 - if(b->prototypes.available)
1606 - buffer_json_member_add_uint64(wb, "available", b->prototypes.available);
1607 - }
1608 - buffer_json_object_close(wb);
1609 - }
1610 - dfe_done(b);
1611 - }
1612 - buffer_json_array_close(wb);
1613 -}
1614 -
1615 -static void contexts_v2_alert_instances_to_json(BUFFER *wb, const char *key, struct rrdcontext_to_json_v2_data *ctl, bool debug) {
1616 - buffer_json_member_add_array(wb, key);
1617 - {
1618 - struct alert_instances_callback_data data = {
1619 - .wb = wb,
1620 - .ctl = ctl,
1621 - .debug = debug,
1622 - };
1623 - dictionary_walkthrough_rw(ctl->alerts.alert_instances, DICTIONARY_LOCK_READ,
1624 - contexts_v2_alert_instance_to_json_callback, &data);
1625 - }
1626 - buffer_json_array_close(wb); // alerts_instances
1627 -}
1628 -
1629 -static void contexts_v2_alerts_to_json(BUFFER *wb, struct rrdcontext_to_json_v2_data *ctl, bool debug) {
1630 - if(ctl->request->options & CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY) {
1631 - buffer_json_member_add_array(wb, "alerts");
1632 - {
1633 - struct alert_v2_entry *t;
1634 - dfe_start_read(ctl->alerts.summary, t)
1635 - {
1636 - buffer_json_add_array_item_object(wb);
1637 - {
1638 - buffer_json_member_add_uint64(wb, "ati", t->ati);
1639 -
1640 - buffer_json_member_add_array(wb, "ni");
1641 - void *host_guid;
1642 - dfe_start_read(t->nodes, host_guid) {
1643 - struct contexts_v2_node *cn = dictionary_get(ctl->nodes.dict,host_guid_dfe.name);
1644 - buffer_json_add_array_item_int64(wb, (int64_t) cn->ni);
1645 - }
1646 - dfe_done(host_guid);
1647 - buffer_json_array_close(wb);
1648 -
1649 - buffer_json_member_add_string(wb, "nm", string2str(t->name));
1650 - buffer_json_member_add_string(wb, "sum", string2str(t->summary));
1651 -
1652 - buffer_json_member_add_uint64(wb, "cr", t->counts.critical);
1653 - buffer_json_member_add_uint64(wb, "wr", t->counts.warning);
1654 - buffer_json_member_add_uint64(wb, "cl", t->counts.clear);
1655 - buffer_json_member_add_uint64(wb, "er", t->counts.error);
1656 -
1657 - buffer_json_member_add_uint64(wb, "in", t->instances);
1658 - buffer_json_member_add_uint64(wb, "nd", dictionary_entries(t->nodes));
1659 - buffer_json_member_add_uint64(wb, "cfg", dictionary_entries(t->configs));
1660 -
1661 - buffer_json_member_add_array(wb, "ctx");
1662 - rrdlabels_key_to_buffer_array_item(t->context, wb);
1663 - buffer_json_array_close(wb); // ctx
1664 -
1665 - buffer_json_member_add_array(wb, "cls");
1666 - rrdlabels_key_to_buffer_array_item(t->classification, wb);
1667 - buffer_json_array_close(wb); // classification
1668 -
1669 -
1670 - buffer_json_member_add_array(wb, "cp");
1671 - rrdlabels_key_to_buffer_array_item(t->component, wb);
1672 - buffer_json_array_close(wb); // component
1673 -
1674 - buffer_json_member_add_array(wb, "ty");
1675 - rrdlabels_key_to_buffer_array_item(t->type, wb);
1676 - buffer_json_array_close(wb); // type
1677 -
1678 - buffer_json_member_add_array(wb, "to");
1679 - rrdlabels_key_to_buffer_array_item(t->recipient, wb);
1680 - buffer_json_array_close(wb); // recipient
1681 - }
1682 - buffer_json_object_close(wb); // alert name
1683 - }
1684 - dfe_done(t);
1685 - }
1686 - buffer_json_array_close(wb); // alerts
1687 -
1688 - health_prototype_metadata_foreach(ctl, contexts_v2_alerts_by_x_update_prototypes);
1689 - contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_type, "alerts_by_type");
1690 - contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_component, "alerts_by_component");
1691 - contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_classification, "alerts_by_classification");
1692 - contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_recipient, "alerts_by_recipient");
1693 - contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_module, "alerts_by_module");
1694 - }
1695 -
1696 - if(ctl->request->options & (CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES|CONTEXT_V2_OPTION_ALERTS_WITH_VALUES)) {
1697 - contexts_v2_alert_instances_to_json(wb, "alert_instances", ctl, debug);
1698 - }
1699 -}
1700 -
1701 -#define SQL_TRANSITION_DATA_SMALL_STRING (6 * 8)
1702 -#define SQL_TRANSITION_DATA_MEDIUM_STRING (12 * 8)
1703 -#define SQL_TRANSITION_DATA_BIG_STRING 512
1704 -
1705 -struct sql_alert_transition_fixed_size {
1706 - usec_t global_id;
1707 - nd_uuid_t transition_id;
1708 - nd_uuid_t host_id;
1709 - nd_uuid_t config_hash_id;
1710 - uint32_t alarm_id;
1711 - char alert_name[SQL_TRANSITION_DATA_SMALL_STRING];
1712 - char chart[RRD_ID_LENGTH_MAX];
1713 - char chart_name[RRD_ID_LENGTH_MAX];
1714 - char chart_context[SQL_TRANSITION_DATA_MEDIUM_STRING];
1715 - char family[SQL_TRANSITION_DATA_SMALL_STRING];
1716 - char recipient[SQL_TRANSITION_DATA_MEDIUM_STRING];
1717 - char units[SQL_TRANSITION_DATA_SMALL_STRING];
1718 - char exec[SQL_TRANSITION_DATA_BIG_STRING];
1719 - char info[SQL_TRANSITION_DATA_BIG_STRING];
1720 - char summary[SQL_TRANSITION_DATA_BIG_STRING];
1721 - char classification[SQL_TRANSITION_DATA_SMALL_STRING];
1722 - char type[SQL_TRANSITION_DATA_SMALL_STRING];
1723 - char component[SQL_TRANSITION_DATA_SMALL_STRING];
1724 - time_t when_key;
1725 - time_t duration;
1726 - time_t non_clear_duration;
1727 - uint64_t flags;
1728 - time_t delay_up_to_timestamp;
1729 - time_t exec_run_timestamp;
1730 - int exec_code;
1731 - int new_status;
1732 - int old_status;
1733 - int delay;
1734 - time_t last_repeat;
1735 - NETDATA_DOUBLE new_value;
1736 - NETDATA_DOUBLE old_value;
1737 -
1738 - char machine_guid[UUID_STR_LEN];
1739 - struct sql_alert_transition_fixed_size *next;
1740 - struct sql_alert_transition_fixed_size *prev;
1741 -};
1742 -
1743 -static struct sql_alert_transition_fixed_size *contexts_v2_alert_transition_dup(struct sql_alert_transition_data *t, const char *machine_guid, struct sql_alert_transition_fixed_size *dst) {
1744 - struct sql_alert_transition_fixed_size *n = dst ? dst : mallocz(sizeof(*n));
1745 -
1746 - n->global_id = t->global_id;
1747 - uuid_copy(n->transition_id, *t->transition_id);
1748 - uuid_copy(n->host_id, *t->host_id);
1749 - uuid_copy(n->config_hash_id, *t->config_hash_id);
1750 - n->alarm_id = t->alarm_id;
1751 - strncpyz(n->alert_name, t->alert_name ? t->alert_name : "", sizeof(n->alert_name) - 1);
1752 - strncpyz(n->chart, t->chart ? t->chart : "", sizeof(n->chart) - 1);
1753 - strncpyz(n->chart_name, t->chart_name ? t->chart_name : n->chart, sizeof(n->chart_name) - 1);
1754 - strncpyz(n->chart_context, t->chart_context ? t->chart_context : "", sizeof(n->chart_context) - 1);
1755 - strncpyz(n->family, t->family ? t->family : "", sizeof(n->family) - 1);
1756 - strncpyz(n->recipient, t->recipient ? t->recipient : "", sizeof(n->recipient) - 1);
1757 - strncpyz(n->units, t->units ? t->units : "", sizeof(n->units) - 1);
1758 - strncpyz(n->exec, t->exec ? t->exec : "", sizeof(n->exec) - 1);
1759 - strncpyz(n->info, t->info ? t->info : "", sizeof(n->info) - 1);
1760 - strncpyz(n->summary, t->summary ? t->summary : "", sizeof(n->summary) - 1);
1761 - strncpyz(n->classification, t->classification ? t->classification : "", sizeof(n->classification) - 1);
1762 - strncpyz(n->type, t->type ? t->type : "", sizeof(n->type) - 1);
1763 - strncpyz(n->component, t->component ? t->component : "", sizeof(n->component) - 1);
1764 - n->when_key = t->when_key;
1765 - n->duration = t->duration;
1766 - n->non_clear_duration = t->non_clear_duration;
1767 - n->flags = t->flags;
1768 - n->delay_up_to_timestamp = t->delay_up_to_timestamp;
1769 - n->exec_run_timestamp = t->exec_run_timestamp;
1770 - n->exec_code = t->exec_code;
1771 - n->new_status = t->new_status;
1772 - n->old_status = t->old_status;
1773 - n->delay = t->delay;
1774 - n->last_repeat = t->last_repeat;
1775 - n->new_value = t->new_value;
1776 - n->old_value = t->old_value;
1777 -
1778 - memcpy(n->machine_guid, machine_guid, sizeof(n->machine_guid));
1779 - n->next = n->prev = NULL;
1780 -
1781 - return n;
1782 -}
1783 -
1784 -static void contexts_v2_alert_transition_free(struct sql_alert_transition_fixed_size *t) {
1785 - freez(t);
1786 -}
1787 -
1788 -static inline void contexts_v2_alert_transition_keep(struct alert_transitions_callback_data *d, struct sql_alert_transition_data *t, const char *machine_guid) {
1789 - d->items_matched++;
1790 -
1791 - if(unlikely(t->global_id <= d->ctl->request->alerts.global_id_anchor)) {
1792 - // this is in our past, we are not interested
1793 - d->operations.skips_before++;
1794 - return;
1795 - }
1796 -
1797 - if(unlikely(!d->base)) {
1798 - d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, NULL);
1799 - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
1800 - d->items_to_return++;
1801 - d->operations.first++;
1802 - return;
1803 - }
1804 -
1805 - struct sql_alert_transition_fixed_size *last = d->last_added;
1806 - while(last->prev != d->base->prev && t->global_id > last->prev->global_id) {
1807 - last = last->prev;
1808 - d->operations.backwards++;
1809 - }
1810 -
1811 - while(last->next && t->global_id < last->next->global_id) {
1812 - last = last->next;
1813 - d->operations.forwards++;
1814 - }
1815 -
1816 - if(d->items_to_return >= d->max_items_to_return) {
1817 - if(last == d->base->prev && t->global_id < last->global_id) {
1818 - d->operations.skips_after++;
1819 - return;
1820 - }
1821 - }
1822 -
1823 - d->items_to_return++;
1824 -
1825 - if(t->global_id > last->global_id) {
1826 - if(d->items_to_return > d->max_items_to_return) {
1827 - d->items_to_return--;
1828 - d->operations.shifts++;
1829 - d->last_added = d->base->prev;
1830 - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(d->base, d->last_added, prev, next);
1831 - d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, d->last_added);
1832 - }
1833 - DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
1834 - d->operations.prepend++;
1835 - }
1836 - else {
1837 - d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, NULL);
1838 - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
1839 - d->operations.append++;
1840 - }
1841 -
1842 - while(d->items_to_return > d->max_items_to_return) {
1843 - // we have to remove something
1844 -
1845 - struct sql_alert_transition_fixed_size *tmp = d->base->prev;
1846 - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(d->base, tmp, prev, next);
1847 - d->items_to_return--;
1848 -
1849 - if(unlikely(d->last_added == tmp))
1850 - d->last_added = d->base;
1851 -
1852 - contexts_v2_alert_transition_free(tmp);
1853 -
1854 - d->operations.shifts++;
1855 - }
1856 -}
1857 -
1858 -static void contexts_v2_alert_transition_callback(struct sql_alert_transition_data *t, void *data) {
1859 - struct alert_transitions_callback_data *d = data;
1860 - d->items_evaluated++;
1861 -
1862 - char machine_guid[UUID_STR_LEN] = "";
1863 - uuid_unparse_lower(*t->host_id, machine_guid);
1864 -
1865 - const char *facets[ATF_TOTAL_ENTRIES] = {
1866 - [ATF_STATUS] = rrdcalc_status2string(t->new_status),
1867 - [ATF_CLASS] = t->classification,
1868 - [ATF_TYPE] = t->type,
1869 - [ATF_COMPONENT] = t->component,
1870 - [ATF_ROLE] = t->recipient && *t->recipient ? t->recipient : string2str(localhost->health.health_default_recipient),
1871 - [ATF_NODE] = machine_guid,
1872 - [ATF_ALERT_NAME] = t->alert_name,
1873 - [ATF_CHART_NAME] = t->chart_name,
1874 - [ATF_CONTEXT] = t->chart_context,
1875 - };
1876 -
1877 - for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
1878 - if (!facets[i] || !*facets[i]) facets[i] = "unknown";
1879 -
1880 - struct facet_entry tmp = {
1881 - .count = 0,
1882 - };
1883 - dictionary_set(d->facets[i].dict, facets[i], &tmp, sizeof(tmp));
1884 - }
1885 -
1886 - bool selected[ATF_TOTAL_ENTRIES] = { 0 };
1887 -
1888 - uint32_t selected_by = 0;
1889 - for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
1890 - selected[i] = !d->facets[i].pattern || simple_pattern_matches(d->facets[i].pattern, facets[i]);
1891 - if(selected[i])
1892 - selected_by++;
1893 - }
1894 -
1895 - if(selected_by == ATF_TOTAL_ENTRIES) {
1896 - // this item is selected by all facets
1897 - // put it in our result (if it fits)
1898 - contexts_v2_alert_transition_keep(d, t, machine_guid);
1899 - }
1900 -
1901 - if(selected_by >= ATF_TOTAL_ENTRIES - 1) {
1902 - // this item is selected by all, or all except one facet
1903 - // in both cases we need to add it to our counters
1904 -
1905 - for (size_t i = 0; i < ATF_TOTAL_ENTRIES; i++) {
1906 - uint32_t counted_by = selected_by;
1907 -
1908 - if (counted_by != ATF_TOTAL_ENTRIES) {
1909 - counted_by = 0;
1910 - for (size_t j = 0; j < ATF_TOTAL_ENTRIES; j++) {
1911 - if (i == j || selected[j])
1912 - counted_by++;
1913 - }
1914 - }
1915 -
1916 - if (counted_by == ATF_TOTAL_ENTRIES) {
1917 - // we need to count it on this facet
1918 - struct facet_entry *x = dictionary_get(d->facets[i].dict, facets[i]);
1919 - internal_fatal(!x, "facet is not found");
1920 - if(x)
1921 - x->count++;
1922 - }
1923 - }
1924 - }
1925 -}
1926 -
1927 -static void contexts_v2_alert_transitions_to_json(BUFFER *wb, struct rrdcontext_to_json_v2_data *ctl, bool debug) {
1928 - struct alert_transitions_callback_data data = {
1929 - .wb = wb,
1930 - .ctl = ctl,
1931 - .debug = debug,
1932 - .only_one_config = true,
1933 - .max_items_to_return = ctl->request->alerts.last,
1934 - .items_to_return = 0,
1935 - .base = NULL,
1936 - };
1937 -
1938 - for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
1939 - data.facets[i].dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, sizeof(struct facet_entry));
1940 - if(ctl->request->alerts.facets[i])
1941 - data.facets[i].pattern = simple_pattern_create(ctl->request->alerts.facets[i], ",|", SIMPLE_PATTERN_EXACT, false);
1942 - }
1943 -
1944 - sql_alert_transitions(
1945 - ctl->nodes.dict,
1946 - ctl->window.after,
1947 - ctl->window.before,
1948 - ctl->request->contexts,
1949 - ctl->request->alerts.alert,
1950 - ctl->request->alerts.transition,
1951 - contexts_v2_alert_transition_callback,
1952 - &data,
1953 - debug);
1954 -
1955 - buffer_json_member_add_array(wb, "facets");
1956 - for (size_t i = 0; i < ATF_TOTAL_ENTRIES; i++) {
1957 - buffer_json_add_array_item_object(wb);
1958 - {
1959 - buffer_json_member_add_string(wb, "id", alert_transition_facets[i].id);
1960 - buffer_json_member_add_string(wb, "name", alert_transition_facets[i].name);
1961 - buffer_json_member_add_uint64(wb, "order", alert_transition_facets[i].order);
1962 - buffer_json_member_add_array(wb, "options");
1963 - {
1964 - struct facet_entry *x;
1965 - dfe_start_read(data.facets[i].dict, x) {
1966 - buffer_json_add_array_item_object(wb);
1967 - {
1968 - buffer_json_member_add_string(wb, "id", x_dfe.name);
1969 - if (i == ATF_NODE) {
1970 - RRDHOST *host = rrdhost_find_by_guid(x_dfe.name);
1971 - if (host)
1972 - buffer_json_member_add_string(wb, "name", rrdhost_hostname(host));
1973 - else
1974 - buffer_json_member_add_string(wb, "name", x_dfe.name);
1975 - } else
1976 - buffer_json_member_add_string(wb, "name", x_dfe.name);
1977 - buffer_json_member_add_uint64(wb, "count", x->count);
1978 - }
1979 - buffer_json_object_close(wb);
1980 - }
1981 - dfe_done(x);
1982 - }
1983 - buffer_json_array_close(wb); // options
1984 - }
1985 - buffer_json_object_close(wb); // facet
1986 - }
1987 - buffer_json_array_close(wb); // facets
1988 -
1989 - buffer_json_member_add_array(wb, "transitions");
1990 - for(struct sql_alert_transition_fixed_size *t = data.base; t ; t = t->next) {
1991 - buffer_json_add_array_item_object(wb);
1992 - {
1993 - RRDHOST *host = rrdhost_find_by_guid(t->machine_guid);
1994 -
1995 - buffer_json_member_add_uint64(wb, "gi", t->global_id);
1996 - buffer_json_member_add_uuid(wb, "transition_id", &t->transition_id);
1997 - buffer_json_member_add_uuid(wb, "config_hash_id", &t->config_hash_id);
1998 - buffer_json_member_add_string(wb, "machine_guid", t->machine_guid);
1999 -
2000 - if(host) {
2001 - buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
2002 -
2003 - if(host->node_id)
2004 - buffer_json_member_add_uuid(wb, "node_id", host->node_id);
2005 - }
2006 -
2007 - buffer_json_member_add_string(wb, "alert", *t->alert_name ? t->alert_name : NULL);
2008 - buffer_json_member_add_string(wb, "instance", *t->chart ? t->chart : NULL);
2009 - buffer_json_member_add_string(wb, "instance_n", *t->chart_name ? t->chart_name : NULL);
2010 - buffer_json_member_add_string(wb, "context", *t->chart_context ? t->chart_context : NULL);
2011 - // buffer_json_member_add_string(wb, "family", *t->family ? t->family : NULL);
2012 - buffer_json_member_add_string(wb, "component", *t->component ? t->component : NULL);
2013 - buffer_json_member_add_string(wb, "classification", *t->classification ? t->classification : NULL);
2014 - buffer_json_member_add_string(wb, "type", *t->type ? t->type : NULL);
2015 -
2016 - buffer_json_member_add_time_t(wb, "when", t->when_key);
2017 - buffer_json_member_add_string(wb, "info", *t->info ? t->info : "");
2018 - buffer_json_member_add_string(wb, "summary", *t->summary ? t->summary : "");
2019 - buffer_json_member_add_string(wb, "units", *t->units ? t->units : NULL);
2020 - buffer_json_member_add_object(wb, "new");
2021 - {
2022 - buffer_json_member_add_string(wb, "status", rrdcalc_status2string(t->new_status));
2023 - buffer_json_member_add_double(wb, "value", t->new_value);
2024 - }
2025 - buffer_json_object_close(wb); // new
2026 - buffer_json_member_add_object(wb, "old");
2027 - {
2028 - buffer_json_member_add_string(wb, "status", rrdcalc_status2string(t->old_status));
2029 - buffer_json_member_add_double(wb, "value", t->old_value);
2030 - buffer_json_member_add_time_t(wb, "duration", t->duration);
2031 - buffer_json_member_add_time_t(wb, "raised_duration", t->non_clear_duration);
2032 - }
2033 - buffer_json_object_close(wb); // old
2034 -
2035 - buffer_json_member_add_object(wb, "notification");
2036 - {
2037 - buffer_json_member_add_time_t(wb, "when", t->exec_run_timestamp);
2038 - buffer_json_member_add_time_t(wb, "delay", t->delay);
2039 - buffer_json_member_add_time_t(wb, "delay_up_to_time", t->delay_up_to_timestamp);
2040 - health_entry_flags_to_json_array(wb, "flags", t->flags);
2041 - buffer_json_member_add_string(wb, "exec", *t->exec ? t->exec : string2str(localhost->health.health_default_exec));
2042 - buffer_json_member_add_uint64(wb, "exec_code", t->exec_code);
2043 - buffer_json_member_add_string(wb, "to", *t->recipient ? t->recipient : string2str(localhost->health.health_default_recipient));
2044 - }
2045 - buffer_json_object_close(wb); // notification
2046 - }
2047 - buffer_json_object_close(wb); // a transition
2048 - }
2049 - buffer_json_array_close(wb); // all transitions
2050 -
2051 - if(ctl->options & CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS) {
2052 - DICTIONARY *configs = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
2053 -
2054 - for(struct sql_alert_transition_fixed_size *t = data.base; t ; t = t->next) {
2055 - char guid[UUID_STR_LEN];
2056 - uuid_unparse_lower(t->config_hash_id, guid);
2057 - dictionary_set(configs, guid, NULL, 0);
2058 - }
2059 -
2060 - buffer_json_member_add_array(wb, "configurations");
2061 - sql_get_alert_configuration(configs, contexts_v2_alert_config_to_json_from_sql_alert_config_data, &data, debug);
2062 - buffer_json_array_close(wb);
2063 -
2064 - dictionary_destroy(configs);
2065 - }
2066 -
2067 - while(data.base) {
2068 - struct sql_alert_transition_fixed_size *t = data.base;
2069 - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(data.base, t, prev, next);
2070 - contexts_v2_alert_transition_free(t);
2071 - }
2072 -
2073 - for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
2074 - dictionary_destroy(data.facets[i].dict);
2075 - simple_pattern_free(data.facets[i].pattern);
2076 - }
2077 -
2078 - buffer_json_member_add_object(wb, "items");
2079 - {
2080 - // all the items in the window, under the scope_nodes, ignoring the facets (filters)
2081 - buffer_json_member_add_uint64(wb, "evaluated", data.items_evaluated);
2082 -
2083 - // all the items matching the query (if you didn't put anchor_gi and last, these are all the items you would get back)
2084 - buffer_json_member_add_uint64(wb, "matched", data.items_matched);
2085 -
2086 - // the items included in this response
2087 - buffer_json_member_add_uint64(wb, "returned", data.items_to_return);
2088 -
2089 - // same as last=X parameter
2090 - buffer_json_member_add_uint64(wb, "max_to_return", data.max_items_to_return);
2091 -
2092 - // items before the first returned, this should be 0 if anchor_gi is not set
2093 - buffer_json_member_add_uint64(wb, "before", data.operations.skips_before);
2094 -
2095 - // items after the last returned, when this is zero there aren't any items after the current list
2096 - buffer_json_member_add_uint64(wb, "after", data.operations.skips_after + data.operations.shifts);
2097 - }
2098 - buffer_json_object_close(wb); // items
2099 -
2100 - if(debug) {
2101 - buffer_json_member_add_object(wb, "stats");
2102 - {
2103 - buffer_json_member_add_uint64(wb, "first", data.operations.first);
2104 - buffer_json_member_add_uint64(wb, "prepend", data.operations.prepend);
2105 - buffer_json_member_add_uint64(wb, "append", data.operations.append);
2106 - buffer_json_member_add_uint64(wb, "backwards", data.operations.backwards);
2107 - buffer_json_member_add_uint64(wb, "forwards", data.operations.forwards);
2108 - buffer_json_member_add_uint64(wb, "shifts", data.operations.shifts);
2109 - buffer_json_member_add_uint64(wb, "skips_before", data.operations.skips_before);
2110 - buffer_json_member_add_uint64(wb, "skips_after", data.operations.skips_after);
2111 - }
2112 - buffer_json_object_close(wb);
2113 - }
2114 -}
2115 -
2116 -int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTEXTS_V2_MODE mode) {
2117 - int resp = HTTP_RESP_OK;
2118 - bool run = true;
2119 -
2120 - if(mode & CONTEXTS_V2_SEARCH)
2121 - mode |= CONTEXTS_V2_CONTEXTS;
2122 -
2123 - if(mode & (CONTEXTS_V2_AGENTS_INFO))
2124 - mode |= CONTEXTS_V2_AGENTS;
2125 -
2126 - if(mode & (CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_NODES_INFO | CONTEXTS_V2_NODE_INSTANCES))
2127 - mode |= CONTEXTS_V2_NODES;
2128 -
2129 - if(mode & CONTEXTS_V2_ALERTS) {
2130 - mode |= CONTEXTS_V2_NODES;
2131 - req->options &= ~CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS;
2132 -
2133 - if(!(req->options & (CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY|CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES|CONTEXT_V2_OPTION_ALERTS_WITH_VALUES)))
2134 - req->options |= CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY;
2135 - }
2136 -
2137 - if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
2138 - mode |= CONTEXTS_V2_NODES;
2139 - req->options &= ~CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES;
2140 - }
2141 -
2142 - struct rrdcontext_to_json_v2_data ctl = {
2143 - .wb = wb,
2144 - .request = req,
2145 - .mode = mode,
2146 - .options = req->options,
2147 - .versions = { 0 },
2148 - .nodes.scope_pattern = string_to_simple_pattern(req->scope_nodes),
2149 - .nodes.pattern = string_to_simple_pattern(req->nodes),
2150 - .contexts.pattern = string_to_simple_pattern(req->contexts),
2151 - .contexts.scope_pattern = string_to_simple_pattern(req->scope_contexts),
2152 - .q.pattern = string_to_simple_pattern_nocase(req->q),
2153 - .alerts.alert_name_pattern = string_to_simple_pattern(req->alerts.alert),
2154 - .window = {
2155 - .enabled = false,
2156 - .relative = false,
2157 - .after = req->after,
2158 - .before = req->before,
2159 - },
2160 - .timings = {
2161 - .received_ut = now_monotonic_usec(),
2162 - }
2163 - };
2164 -
2165 - bool debug = ctl.options & CONTEXT_V2_OPTION_DEBUG;
2166 -
2167 - if(mode & CONTEXTS_V2_NODES) {
2168 - ctl.nodes.dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2169 - NULL, sizeof(struct contexts_v2_node));
2170 - }
2171 -
2172 - if(mode & CONTEXTS_V2_CONTEXTS) {
2173 - ctl.contexts.dict = dictionary_create_advanced(
2174 - DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL,
2175 - sizeof(struct context_v2_entry));
2176 -
2177 - dictionary_register_conflict_callback(ctl.contexts.dict, contexts_conflict_callback, &ctl);
2178 - dictionary_register_delete_callback(ctl.contexts.dict, contexts_delete_callback, &ctl);
2179 - }
2180 -
2181 - if(mode & CONTEXTS_V2_FUNCTIONS) {
2182 - ctl.functions.dict = dictionary_create_advanced(
2183 - DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL,
2184 - sizeof(struct function_v2_entry));
2185 -
2186 - dictionary_register_insert_callback(ctl.functions.dict, functions_insert_callback, &ctl);
2187 - dictionary_register_conflict_callback(ctl.functions.dict, functions_conflict_callback, &ctl);
2188 - dictionary_register_delete_callback(ctl.functions.dict, functions_delete_callback, &ctl);
2189 - }
2190 -
2191 - if(mode & CONTEXTS_V2_ALERTS) {
2192 - if(req->alerts.transition) {
2193 - ctl.options |= CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES|CONTEXT_V2_OPTION_ALERTS_WITH_VALUES;
2194 - run = sql_find_alert_transition(req->alerts.transition, rrdcontext_v2_set_transition_filter, &ctl);
2195 - if(!run) {
2196 - resp = HTTP_RESP_NOT_FOUND;
2197 - goto cleanup;
2198 - }
2199 - }
2200 -
2201 - ctl.alerts.summary = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2202 - NULL, sizeof(struct alert_v2_entry));
2203 -
2204 - dictionary_register_insert_callback(ctl.alerts.summary, alerts_v2_insert_callback, &ctl);
2205 - dictionary_register_conflict_callback(ctl.alerts.summary, alerts_v2_conflict_callback, &ctl);
2206 - dictionary_register_delete_callback(ctl.alerts.summary, alerts_v2_delete_callback, &ctl);
2207 -
2208 - ctl.alerts.by_type = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2209 - NULL, sizeof(struct alert_by_x_entry));
2210 -
2211 - dictionary_register_insert_callback(ctl.alerts.by_type, alerts_by_x_insert_callback, NULL);
2212 - dictionary_register_conflict_callback(ctl.alerts.by_type, alerts_by_x_conflict_callback, NULL);
2213 -
2214 - ctl.alerts.by_component = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2215 - NULL, sizeof(struct alert_by_x_entry));
2216 -
2217 - dictionary_register_insert_callback(ctl.alerts.by_component, alerts_by_x_insert_callback, NULL);
2218 - dictionary_register_conflict_callback(ctl.alerts.by_component, alerts_by_x_conflict_callback, NULL);
2219 -
2220 - ctl.alerts.by_classification = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2221 - NULL, sizeof(struct alert_by_x_entry));
2222 -
2223 - dictionary_register_insert_callback(ctl.alerts.by_classification, alerts_by_x_insert_callback, NULL);
2224 - dictionary_register_conflict_callback(ctl.alerts.by_classification, alerts_by_x_conflict_callback, NULL);
2225 -
2226 - ctl.alerts.by_recipient = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2227 - NULL, sizeof(struct alert_by_x_entry));
2228 -
2229 - dictionary_register_insert_callback(ctl.alerts.by_recipient, alerts_by_x_insert_callback, NULL);
2230 - dictionary_register_conflict_callback(ctl.alerts.by_recipient, alerts_by_x_conflict_callback, NULL);
2231 -
2232 - ctl.alerts.by_module = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2233 - NULL, sizeof(struct alert_by_x_entry));
2234 -
2235 - dictionary_register_insert_callback(ctl.alerts.by_module, alerts_by_x_insert_callback, NULL);
2236 - dictionary_register_conflict_callback(ctl.alerts.by_module, alerts_by_x_conflict_callback, NULL);
2237 -
2238 - if(ctl.options & (CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES | CONTEXT_V2_OPTION_ALERTS_WITH_VALUES)) {
2239 - ctl.alerts.alert_instances = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2240 - NULL, sizeof(struct sql_alert_instance_v2_entry));
2241 -
2242 - dictionary_register_insert_callback(ctl.alerts.alert_instances, alert_instances_v2_insert_callback, &ctl);
2243 - dictionary_register_conflict_callback(ctl.alerts.alert_instances, alert_instances_v2_conflict_callback, &ctl);
2244 - dictionary_register_delete_callback(ctl.alerts.alert_instances, alert_instances_delete_callback, &ctl);
2245 - }
2246 - }
2247 -
2248 - if(req->after || req->before) {
2249 - ctl.window.relative = rrdr_relative_window_to_absolute_query(&ctl.window.after, &ctl.window.before, &ctl.now
2250 - , false
2251 - );
2252 - ctl.window.enabled = !(mode & CONTEXTS_V2_ALERT_TRANSITIONS);
2253 - }
2254 - else
2255 - ctl.now = now_realtime_sec();
2256 -
2257 - buffer_json_initialize(wb, "\"", "\"", 0, true,
2258 - ((req->options & CONTEXT_V2_OPTION_MINIFY) && !(req->options & CONTEXT_V2_OPTION_DEBUG)) ? BUFFER_JSON_OPTIONS_MINIFY : BUFFER_JSON_OPTIONS_DEFAULT);
2259 -
2260 - buffer_json_member_add_uint64(wb, "api", 2);
2261 -
2262 - if(req->options & CONTEXT_V2_OPTION_DEBUG) {
2263 - buffer_json_member_add_object(wb, "request");
2264 - {
2265 - buffer_json_contexts_v2_mode_to_array(wb, "mode", mode);
2266 - web_client_api_request_v2_contexts_options_to_buffer_json_array(wb, "options", req->options);
2267 -
2268 - buffer_json_member_add_object(wb, "scope");
2269 - {
2270 - buffer_json_member_add_string(wb, "scope_nodes", req->scope_nodes);
2271 - if (mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS))
2272 - buffer_json_member_add_string(wb, "scope_contexts", req->scope_contexts);
2273 - }
2274 - buffer_json_object_close(wb);
2275 -
2276 - buffer_json_member_add_object(wb, "selectors");
2277 - {
2278 - buffer_json_member_add_string(wb, "nodes", req->nodes);
2279 -
2280 - if (mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS))
2281 - buffer_json_member_add_string(wb, "contexts", req->contexts);
2282 -
2283 - if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) {
2284 - buffer_json_member_add_object(wb, "alerts");
2285 -
2286 - if(mode & CONTEXTS_V2_ALERTS)
2287 - web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(wb, "status", req->alerts.status);
2288 -
2289 - if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
2290 - buffer_json_member_add_string(wb, "context", req->contexts);
2291 - buffer_json_member_add_uint64(wb, "anchor_gi", req->alerts.global_id_anchor);
2292 - buffer_json_member_add_uint64(wb, "last", req->alerts.last);
2293 - }
2294 -
2295 - buffer_json_member_add_string(wb, "alert", req->alerts.alert);
2296 - buffer_json_member_add_string(wb, "transition", req->alerts.transition);
2297 - buffer_json_object_close(wb); // alerts
2298 - }
2299 - }
2300 - buffer_json_object_close(wb); // selectors
2301 -
2302 - buffer_json_member_add_object(wb, "filters");
2303 - {
2304 - if (mode & CONTEXTS_V2_SEARCH)
2305 - buffer_json_member_add_string(wb, "q", req->q);
2306 -
2307 - buffer_json_member_add_time_t(wb, "after", req->after);
2308 - buffer_json_member_add_time_t(wb, "before", req->before);
2309 - }
2310 - buffer_json_object_close(wb); // filters
2311 -
2312 - if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
2313 - buffer_json_member_add_object(wb, "facets");
2314 - {
2315 - for (int i = 0; i < ATF_TOTAL_ENTRIES; i++) {
2316 - buffer_json_member_add_string(wb, alert_transition_facets[i].query_param, req->alerts.facets[i]);
2317 - }
2318 - }
2319 - buffer_json_object_close(wb); // facets
2320 - }
2321 - }
2322 - buffer_json_object_close(wb);
2323 - }
2324 -
2325 - ssize_t ret = 0;
2326 - if(run)
2327 - ret = query_scope_foreach_host(ctl.nodes.scope_pattern, ctl.nodes.pattern,
2328 - rrdcontext_to_json_v2_add_host, &ctl,
2329 - &ctl.versions, ctl.q.host_node_id_str);
2330 -
2331 - if(unlikely(ret < 0)) {
2332 - buffer_flush(wb);
2333 -
2334 - if(ret == -2) {
2335 - buffer_strcat(wb, "query timeout");
2336 - resp = HTTP_RESP_GATEWAY_TIMEOUT;
2337 - }
2338 - else {
2339 - buffer_strcat(wb, "query interrupted");
2340 - resp = HTTP_RESP_CLIENT_CLOSED_REQUEST;
2341 - }
2342 - goto cleanup;
2343 - }
2344 -
2345 - ctl.timings.executed_ut = now_monotonic_usec();
2346 -
2347 - if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
2348 - contexts_v2_alert_transitions_to_json(wb, &ctl, debug);
2349 - }
2350 - else {
2351 - if (mode & CONTEXTS_V2_NODES) {
2352 - buffer_json_member_add_array(wb, "nodes");
2353 - struct contexts_v2_node *t;
2354 - dfe_start_read(ctl.nodes.dict, t) {
2355 - rrdcontext_to_json_v2_rrdhost(wb, t->host, &ctl, t->ni);
2356 - }
2357 - dfe_done(t);
2358 - buffer_json_array_close(wb);
2359 - }
2360 -
2361 - if (mode & CONTEXTS_V2_FUNCTIONS) {
2362 - buffer_json_member_add_array(wb, "functions");
2363 - {
2364 - struct function_v2_entry *t;
2365 - dfe_start_read(ctl.functions.dict, t) {
2366 - buffer_json_add_array_item_object(wb);
2367 - {
2368 - buffer_json_member_add_string(wb, "name", t_dfe.name);
2369 - buffer_json_member_add_string(wb, "help", string2str(t->help));
2370 - buffer_json_member_add_array(wb, "ni");
2371 - {
2372 - for (size_t i = 0; i < t->used; i++)
2373 - buffer_json_add_array_item_uint64(wb, t->node_ids[i]);
2374 - }
2375 - buffer_json_array_close(wb);
2376 - buffer_json_member_add_string(wb, "tags", string2str(t->tags));
2377 - http_access2buffer_json_array(wb, "access", t->access);
2378 - buffer_json_member_add_uint64(wb, "priority", t->priority);
2379 - }
2380 - buffer_json_object_close(wb);
2381 - }
2382 - dfe_done(t);
2383 - }
2384 - buffer_json_array_close(wb);
2385 - }
2386 -
2387 - if (mode & CONTEXTS_V2_CONTEXTS) {
2388 - buffer_json_member_add_object(wb, "contexts");
2389 - {
2390 - struct context_v2_entry *z;
2391 - dfe_start_read(ctl.contexts.dict, z) {
2392 - bool collected = z->flags & RRD_FLAG_COLLECTED;
2393 -
2394 - buffer_json_member_add_object(wb, string2str(z->id));
2395 - {
2396 - buffer_json_member_add_string(wb, "family", string2str(z->family));
2397 - buffer_json_member_add_uint64(wb, "priority", z->priority);
2398 - buffer_json_member_add_time_t(wb, "first_entry", z->first_time_s);
2399 - buffer_json_member_add_time_t(wb, "last_entry", collected ? ctl.now : z->last_time_s);
2400 - buffer_json_member_add_boolean(wb, "live", collected);
2401 - if (mode & CONTEXTS_V2_SEARCH)
2402 - buffer_json_member_add_string(wb, "match", fts_match_to_string(z->match));
2403 - }
2404 - buffer_json_object_close(wb);
2405 - }
2406 - dfe_done(z);
2407 - }
2408 - buffer_json_object_close(wb); // contexts
2409 - }
2410 -
2411 - if (mode & CONTEXTS_V2_ALERTS)
2412 - contexts_v2_alerts_to_json(wb, &ctl, debug);
2413 -
2414 - if (mode & CONTEXTS_V2_SEARCH) {
2415 - buffer_json_member_add_object(wb, "searches");
2416 - {
2417 - buffer_json_member_add_uint64(wb, "strings", ctl.q.fts.string_searches);
2418 - buffer_json_member_add_uint64(wb, "char", ctl.q.fts.char_searches);
2419 - buffer_json_member_add_uint64(wb, "total", ctl.q.fts.searches);
2420 - }
2421 - buffer_json_object_close(wb);
2422 - }
2423 -
2424 - if (mode & (CONTEXTS_V2_VERSIONS))
2425 - version_hashes_api_v2(wb, &ctl.versions);
2426 -
2427 - if (mode & CONTEXTS_V2_AGENTS)
2428 - buffer_json_agents_v2(wb, &ctl.timings, ctl.now, mode & (CONTEXTS_V2_AGENTS_INFO), true);
2429 - }
2430 -
2431 - buffer_json_cloud_timings(wb, "timings", &ctl.timings);
2432 -
2433 - buffer_json_finalize(wb);
2434 -
2435 -cleanup:
2436 - dictionary_destroy(ctl.nodes.dict);
2437 - dictionary_destroy(ctl.contexts.dict);
2438 - dictionary_destroy(ctl.functions.dict);
2439 - dictionary_destroy(ctl.alerts.summary);
2440 - dictionary_destroy(ctl.alerts.alert_instances);
2441 - dictionary_destroy(ctl.alerts.by_type);
2442 - dictionary_destroy(ctl.alerts.by_component);
2443 - dictionary_destroy(ctl.alerts.by_classification);
2444 - dictionary_destroy(ctl.alerts.by_recipient);
2445 - dictionary_destroy(ctl.alerts.by_module);
2446 - simple_pattern_free(ctl.nodes.scope_pattern);
2447 - simple_pattern_free(ctl.nodes.pattern);
2448 - simple_pattern_free(ctl.contexts.pattern);
2449 - simple_pattern_free(ctl.contexts.scope_pattern);
2450 - simple_pattern_free(ctl.q.pattern);
2451 - simple_pattern_free(ctl.alerts.alert_name_pattern);
2452 -
2453 - return resp;
2454 -}
src/database/contexts/api_v2_contexts.c new
+1001
@@ -0,0 +1,1001 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_contexts.h"
4 +
5 +#include "aclk/aclk_capas.h"
6 +
7 +// ----------------------------------------------------------------------------
8 +// /api/v2/contexts API
9 +
10 +static const char *fts_match_to_string(FTS_MATCH match) {
11 + switch(match) {
12 + case FTS_MATCHED_HOST:
13 + return "HOST";
14 +
15 + case FTS_MATCHED_CONTEXT:
16 + return "CONTEXT";
17 +
18 + case FTS_MATCHED_INSTANCE:
19 + return "INSTANCE";
20 +
21 + case FTS_MATCHED_DIMENSION:
22 + return "DIMENSION";
23 +
24 + case FTS_MATCHED_ALERT:
25 + return "ALERT";
26 +
27 + case FTS_MATCHED_ALERT_INFO:
28 + return "ALERT_INFO";
29 +
30 + case FTS_MATCHED_LABEL:
31 + return "LABEL";
32 +
33 + case FTS_MATCHED_FAMILY:
34 + return "FAMILY";
35 +
36 + case FTS_MATCHED_TITLE:
37 + return "TITLE";
38 +
39 + case FTS_MATCHED_UNITS:
40 + return "UNITS";
41 +
42 + default:
43 + return "NONE";
44 + }
45 +}
46 +
47 +struct function_v2_entry {
48 + size_t size;
49 + size_t used;
50 + size_t *node_ids;
51 + STRING *help;
52 + STRING *tags;
53 + HTTP_ACCESS access;
54 + int priority;
55 +};
56 +
57 +struct context_v2_entry {
58 + size_t count;
59 + STRING *id;
60 + STRING *family;
61 + uint32_t priority;
62 + time_t first_time_s;
63 + time_t last_time_s;
64 + RRD_FLAGS flags;
65 + FTS_MATCH match;
66 +};
67 +
68 +static inline bool full_text_search_string(FTS_INDEX *fts, SIMPLE_PATTERN *q, STRING *ptr) {
69 + fts->searches++;
70 + fts->string_searches++;
71 + return simple_pattern_matches_string(q, ptr);
72 +}
73 +
74 +static inline bool full_text_search_char(FTS_INDEX *fts, SIMPLE_PATTERN *q, char *ptr) {
75 + fts->searches++;
76 + fts->char_searches++;
77 + return simple_pattern_matches(q, ptr);
78 +}
79 +
80 +static FTS_MATCH rrdcontext_to_json_v2_full_text_search(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc, SIMPLE_PATTERN *q) {
81 + if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->id) ||
82 + full_text_search_string(&ctl->q.fts, q, rc->family)))
83 + return FTS_MATCHED_CONTEXT;
84 +
85 + if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->title)))
86 + return FTS_MATCHED_TITLE;
87 +
88 + if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->units)))
89 + return FTS_MATCHED_UNITS;
90 +
91 + FTS_MATCH matched = FTS_MATCHED_NONE;
92 + RRDINSTANCE *ri;
93 + dfe_start_read(rc->rrdinstances, ri) {
94 + if(matched) break;
95 +
96 + if(ctl->window.enabled && !query_matches_retention(ctl->window.after, ctl->window.before, ri->first_time_s, (ri->flags & RRD_FLAG_COLLECTED) ? ctl->now : ri->last_time_s, 0))
97 + continue;
98 +
99 + if(unlikely(full_text_search_string(&ctl->q.fts, q, ri->id)) ||
100 + (ri->name != ri->id && full_text_search_string(&ctl->q.fts, q, ri->name))) {
101 + matched = FTS_MATCHED_INSTANCE;
102 + break;
103 + }
104 +
105 + RRDMETRIC *rm;
106 + dfe_start_read(ri->rrdmetrics, rm) {
107 + if(ctl->window.enabled && !query_matches_retention(ctl->window.after, ctl->window.before, rm->first_time_s, (rm->flags & RRD_FLAG_COLLECTED) ? ctl->now : rm->last_time_s, 0))
108 + continue;
109 +
110 + if(unlikely(full_text_search_string(&ctl->q.fts, q, rm->id)) ||
111 + (rm->name != rm->id && full_text_search_string(&ctl->q.fts, q, rm->name))) {
112 + matched = FTS_MATCHED_DIMENSION;
113 + break;
114 + }
115 + }
116 + dfe_done(rm);
117 +
118 + size_t label_searches = 0;
119 + if(unlikely(ri->rrdlabels && rrdlabels_entries(ri->rrdlabels) &&
120 + rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, q, ':', &label_searches) == SP_MATCHED_POSITIVE)) {
121 + ctl->q.fts.searches += label_searches;
122 + ctl->q.fts.char_searches += label_searches;
123 + matched = FTS_MATCHED_LABEL;
124 + break;
125 + }
126 + ctl->q.fts.searches += label_searches;
127 + ctl->q.fts.char_searches += label_searches;
128 +
129 + if(ri->rrdset) {
130 + RRDSET *st = ri->rrdset;
131 + rw_spinlock_read_lock(&st->alerts.spinlock);
132 + for (RRDCALC *rcl = st->alerts.base; rcl; rcl = rcl->next) {
133 + if(unlikely(full_text_search_string(&ctl->q.fts, q, rcl->config.name))) {
134 + matched = FTS_MATCHED_ALERT;
135 + break;
136 + }
137 +
138 + if(unlikely(full_text_search_string(&ctl->q.fts, q, rcl->config.info))) {
139 + matched = FTS_MATCHED_ALERT_INFO;
140 + break;
141 + }
142 + }
143 + rw_spinlock_read_unlock(&st->alerts.spinlock);
144 + }
145 + }
146 + dfe_done(ri);
147 + return matched;
148 +}
149 +
150 +static ssize_t rrdcontext_to_json_v2_add_context(void *data, RRDCONTEXT_ACQUIRED *rca, bool queryable_context __maybe_unused) {
151 + struct rrdcontext_to_json_v2_data *ctl = data;
152 +
153 + RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
154 +
155 + if(ctl->window.enabled && !query_matches_retention(ctl->window.after, ctl->window.before, rc->first_time_s, (rc->flags & RRD_FLAG_COLLECTED) ? ctl->now : rc->last_time_s, 0))
156 + return 0; // continue to next context
157 +
158 + FTS_MATCH match = ctl->q.host_match;
159 + if((ctl->mode & CONTEXTS_V2_SEARCH) && ctl->q.pattern) {
160 + match = rrdcontext_to_json_v2_full_text_search(ctl, rc, ctl->q.pattern);
161 +
162 + if(match == FTS_MATCHED_NONE)
163 + return 0; // continue to next context
164 + }
165 +
166 + if(ctl->mode & CONTEXTS_V2_ALERTS) {
167 + if(!rrdcontext_matches_alert(ctl, rc))
168 + return 0; // continue to next context
169 + }
170 +
171 + if(ctl->contexts.dict) {
172 + struct context_v2_entry t = {
173 + .count = 1,
174 + .id = rc->id,
175 + .family = string_dup(rc->family),
176 + .priority = rc->priority,
177 + .first_time_s = rc->first_time_s,
178 + .last_time_s = rc->last_time_s,
179 + .flags = rc->flags,
180 + .match = match,
181 + };
182 +
183 + dictionary_set(ctl->contexts.dict, string2str(rc->id), &t, sizeof(struct context_v2_entry));
184 + }
185 +
186 + return 1;
187 +}
188 +
189 +void buffer_json_agent_status_id(BUFFER *wb, size_t ai, usec_t duration_ut) {
190 + buffer_json_member_add_object(wb, "st");
191 + {
192 + buffer_json_member_add_uint64(wb, "ai", ai);
193 + buffer_json_member_add_uint64(wb, "code", 200);
194 + buffer_json_member_add_string(wb, "msg", "");
195 + if (duration_ut)
196 + buffer_json_member_add_double(wb, "ms", (NETDATA_DOUBLE) duration_ut / 1000.0);
197 + }
198 + buffer_json_object_close(wb);
199 +}
200 +
201 +void buffer_json_node_add_v2(BUFFER *wb, RRDHOST *host, size_t ni, usec_t duration_ut, bool status) {
202 + buffer_json_member_add_string(wb, "mg", host->machine_guid);
203 +
204 + if(!uuid_is_null(host->node_id))
205 + buffer_json_member_add_uuid(wb, "nd", host->node_id);
206 + buffer_json_member_add_string(wb, "nm", rrdhost_hostname(host));
207 + buffer_json_member_add_uint64(wb, "ni", ni);
208 +
209 + if(status)
210 + buffer_json_agent_status_id(wb, 0, duration_ut);
211 +}
212 +
213 +static void rrdhost_receiver_to_json(BUFFER *wb, RRDHOST_STATUS *s, const char *key) {
214 + buffer_json_member_add_object(wb, key);
215 + {
216 + buffer_json_member_add_uint64(wb, "id", s->ingest.id);
217 + buffer_json_member_add_uint64(wb, "hops", s->ingest.hops);
218 + buffer_json_member_add_string(wb, "type", rrdhost_ingest_type_to_string(s->ingest.type));
219 + buffer_json_member_add_string(wb, "status", rrdhost_ingest_status_to_string(s->ingest.status));
220 + buffer_json_member_add_time_t(wb, "since", s->ingest.since);
221 + buffer_json_member_add_time_t(wb, "age", s->now - s->ingest.since);
222 +
223 + if(s->ingest.type == RRDHOST_INGEST_TYPE_CHILD) {
224 + if(s->ingest.status == RRDHOST_INGEST_STATUS_OFFLINE)
225 + buffer_json_member_add_string(wb, "reason", stream_handshake_error_to_string(s->ingest.reason));
226 +
227 + if(s->ingest.status == RRDHOST_INGEST_STATUS_REPLICATING) {
228 + buffer_json_member_add_object(wb, "replication");
229 + {
230 + buffer_json_member_add_boolean(wb, "in_progress", s->ingest.replication.in_progress);
231 + buffer_json_member_add_double(wb, "completion", s->ingest.replication.completion);
232 + buffer_json_member_add_uint64(wb, "instances", s->ingest.replication.instances);
233 + }
234 + buffer_json_object_close(wb); // replication
235 + }
236 +
237 + if(s->ingest.status == RRDHOST_INGEST_STATUS_REPLICATING || s->ingest.status == RRDHOST_INGEST_STATUS_ONLINE) {
238 + buffer_json_member_add_object(wb, "source");
239 + {
240 + char buf[1024 + 1];
241 + snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->ingest.peers.local.ip, s->ingest.peers.local.port, s->ingest.ssl ? ":SSL" : "");
242 + buffer_json_member_add_string(wb, "local", buf);
243 +
244 + snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->ingest.peers.peer.ip, s->ingest.peers.peer.port, s->ingest.ssl ? ":SSL" : "");
245 + buffer_json_member_add_string(wb, "remote", buf);
246 +
247 + stream_capabilities_to_json_array(wb, s->ingest.capabilities, "capabilities");
248 + }
249 + buffer_json_object_close(wb); // source
250 + }
251 + }
252 + }
253 + buffer_json_object_close(wb); // collection
254 +}
255 +
256 +static void rrdhost_sender_to_json(BUFFER *wb, RRDHOST_STATUS *s, const char *key) {
257 + if(s->stream.status == RRDHOST_STREAM_STATUS_DISABLED)
258 + return;
259 +
260 + buffer_json_member_add_object(wb, key);
261 + {
262 + buffer_json_member_add_uint64(wb, "id", s->stream.id);
263 + buffer_json_member_add_uint64(wb, "hops", s->stream.hops);
264 + buffer_json_member_add_string(wb, "status", rrdhost_streaming_status_to_string(s->stream.status));
265 + buffer_json_member_add_time_t(wb, "since", s->stream.since);
266 + buffer_json_member_add_time_t(wb, "age", s->now - s->stream.since);
267 +
268 + if (s->stream.status == RRDHOST_STREAM_STATUS_OFFLINE)
269 + buffer_json_member_add_string(wb, "reason", stream_handshake_error_to_string(s->stream.reason));
270 +
271 + if (s->stream.status == RRDHOST_STREAM_STATUS_REPLICATING) {
272 + buffer_json_member_add_object(wb, "replication");
273 + {
274 + buffer_json_member_add_boolean(wb, "in_progress", s->stream.replication.in_progress);
275 + buffer_json_member_add_double(wb, "completion", s->stream.replication.completion);
276 + buffer_json_member_add_uint64(wb, "instances", s->stream.replication.instances);
277 + }
278 + buffer_json_object_close(wb);
279 + }
280 +
281 + buffer_json_member_add_object(wb, "destination");
282 + {
283 + char buf[1024 + 1];
284 + snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->stream.peers.local.ip, s->stream.peers.local.port, s->stream.ssl ? ":SSL" : "");
285 + buffer_json_member_add_string(wb, "local", buf);
286 +
287 + snprintfz(buf, sizeof(buf) - 1, "[%s]:%d%s", s->stream.peers.peer.ip, s->stream.peers.peer.port, s->stream.ssl ? ":SSL" : "");
288 + buffer_json_member_add_string(wb, "remote", buf);
289 +
290 + stream_capabilities_to_json_array(wb, s->stream.capabilities, "capabilities");
291 +
292 + buffer_json_member_add_object(wb, "traffic");
293 + {
294 + buffer_json_member_add_boolean(wb, "compression", s->stream.compression);
295 + buffer_json_member_add_uint64(wb, "data", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_DATA]);
296 + buffer_json_member_add_uint64(wb, "metadata", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_METADATA]);
297 + buffer_json_member_add_uint64(wb, "functions", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_FUNCTIONS]);
298 + buffer_json_member_add_uint64(wb, "replication", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_REPLICATION]);
299 + buffer_json_member_add_uint64(wb, "dyncfg", s->stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_DYNCFG]);
300 + }
301 + buffer_json_object_close(wb); // traffic
302 +
303 + buffer_json_member_add_array(wb, "candidates");
304 + struct rrdpush_destinations *d;
305 + for (d = s->host->destinations; d; d = d->next) {
306 + buffer_json_add_array_item_object(wb);
307 + buffer_json_member_add_uint64(wb, "attempts", d->attempts);
308 + {
309 +
310 + if (d->ssl) {
311 + snprintfz(buf, sizeof(buf) - 1, "%s:SSL", string2str(d->destination));
312 + buffer_json_member_add_string(wb, "destination", buf);
313 + }
314 + else
315 + buffer_json_member_add_string(wb, "destination", string2str(d->destination));
316 +
317 + buffer_json_member_add_time_t(wb, "since", d->since);
318 + buffer_json_member_add_time_t(wb, "age", s->now - d->since);
319 + buffer_json_member_add_string(wb, "last_handshake", stream_handshake_error_to_string(d->reason));
320 + if(d->postpone_reconnection_until > s->now) {
321 + buffer_json_member_add_time_t(wb, "next_check", d->postpone_reconnection_until);
322 + buffer_json_member_add_time_t(wb, "next_in", d->postpone_reconnection_until - s->now);
323 + }
324 + }
325 + buffer_json_object_close(wb); // each candidate
326 + }
327 + buffer_json_array_close(wb); // candidates
328 + }
329 + buffer_json_object_close(wb); // destination
330 + }
331 + buffer_json_object_close(wb); // streaming
332 +}
333 +
334 +void agent_capabilities_to_json(BUFFER *wb, RRDHOST *host, const char *key) {
335 + buffer_json_member_add_array(wb, key);
336 +
337 + struct capability *capas = aclk_get_node_instance_capas(host);
338 + for(struct capability *capa = capas; capa->name ;capa++) {
339 + buffer_json_add_array_item_object(wb);
340 + {
341 + buffer_json_member_add_string(wb, "name", capa->name);
342 + buffer_json_member_add_uint64(wb, "version", capa->version);
343 + buffer_json_member_add_boolean(wb, "enabled", capa->enabled);
344 + }
345 + buffer_json_object_close(wb);
346 + }
347 + buffer_json_array_close(wb);
348 + freez(capas);
349 +}
350 +
351 +static inline void host_dyncfg_to_json_v2(BUFFER *wb, const char *key, RRDHOST_STATUS *s) {
352 + buffer_json_member_add_object(wb, key);
353 + {
354 + buffer_json_member_add_string(wb, "status", rrdhost_dyncfg_status_to_string(s->dyncfg.status));
355 + }
356 + buffer_json_object_close(wb); // health
357 +
358 +}
359 +
360 +static inline void rrdhost_health_to_json_v2(BUFFER *wb, const char *key, RRDHOST_STATUS *s) {
361 + buffer_json_member_add_object(wb, key);
362 + {
363 + buffer_json_member_add_string(wb, "status", rrdhost_health_status_to_string(s->health.status));
364 + if (s->health.status == RRDHOST_HEALTH_STATUS_RUNNING) {
365 + buffer_json_member_add_object(wb, "alerts");
366 + {
367 + buffer_json_member_add_uint64(wb, "critical", s->health.alerts.critical);
368 + buffer_json_member_add_uint64(wb, "warning", s->health.alerts.warning);
369 + buffer_json_member_add_uint64(wb, "clear", s->health.alerts.clear);
370 + buffer_json_member_add_uint64(wb, "undefined", s->health.alerts.undefined);
371 + buffer_json_member_add_uint64(wb, "uninitialized", s->health.alerts.uninitialized);
372 + }
373 + buffer_json_object_close(wb); // alerts
374 + }
375 + }
376 + buffer_json_object_close(wb); // health
377 +}
378 +
379 +static void rrdcontext_to_json_v2_rrdhost(BUFFER *wb, RRDHOST *host, struct rrdcontext_to_json_v2_data *ctl, size_t node_id) {
380 + buffer_json_add_array_item_object(wb); // this node
381 + buffer_json_node_add_v2(wb, host, node_id, 0,
382 + (ctl->mode & CONTEXTS_V2_AGENTS) && !(ctl->mode & CONTEXTS_V2_NODE_INSTANCES));
383 +
384 + if(ctl->mode & (CONTEXTS_V2_NODES_INFO | CONTEXTS_V2_NODE_INSTANCES)) {
385 + RRDHOST_STATUS s;
386 + rrdhost_status(host, ctl->now, &s);
387 +
388 + if (ctl->mode & (CONTEXTS_V2_NODES_INFO)) {
389 + buffer_json_member_add_string(wb, "v", rrdhost_program_version(host));
390 +
391 + host_labels2json(host, wb, "labels");
392 +
393 + if (host->system_info) {
394 + buffer_json_member_add_object(wb, "hw");
395 + {
396 + buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture);
397 + buffer_json_member_add_string_or_empty(wb, "cpu_frequency", host->system_info->host_cpu_freq);
398 + buffer_json_member_add_string_or_empty(wb, "cpus", host->system_info->host_cores);
399 + buffer_json_member_add_string_or_empty(wb, "memory", host->system_info->host_ram_total);
400 + buffer_json_member_add_string_or_empty(wb, "disk_space", host->system_info->host_disk_space);
401 + buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization);
402 + buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container);
403 + }
404 + buffer_json_object_close(wb);
405 +
406 + buffer_json_member_add_object(wb, "os");
407 + {
408 + buffer_json_member_add_string_or_empty(wb, "id", host->system_info->host_os_id);
409 + buffer_json_member_add_string_or_empty(wb, "nm", host->system_info->host_os_name);
410 + buffer_json_member_add_string_or_empty(wb, "v", host->system_info->host_os_version);
411 + buffer_json_member_add_object(wb, "kernel");
412 + buffer_json_member_add_string_or_empty(wb, "nm", host->system_info->kernel_name);
413 + buffer_json_member_add_string_or_empty(wb, "v", host->system_info->kernel_version);
414 + buffer_json_object_close(wb);
415 + }
416 + buffer_json_object_close(wb);
417 + }
418 +
419 + // created - the node is created but never connected to cloud
420 + // unreachable - not currently connected
421 + // stale - connected but not having live data
422 + // reachable - connected with live data
423 + // pruned - not connected for some time and has been removed
424 + buffer_json_member_add_string(wb, "state", rrdhost_state_cloud_emulation(host) ? "reachable" : "stale");
425 +
426 + rrdhost_health_to_json_v2(wb, "health", &s);
427 + agent_capabilities_to_json(wb, host, "capabilities");
428 + }
429 +
430 + if (ctl->mode & (CONTEXTS_V2_NODE_INSTANCES)) {
431 + buffer_json_member_add_array(wb, "instances");
432 + buffer_json_add_array_item_object(wb); // this instance
433 + {
434 + buffer_json_agent_status_id(wb, 0, 0);
435 +
436 + buffer_json_member_add_object(wb, "db");
437 + {
438 + buffer_json_member_add_string(wb, "status", rrdhost_db_status_to_string(s.db.status));
439 + buffer_json_member_add_string(wb, "liveness", rrdhost_db_liveness_to_string(s.db.liveness));
440 + buffer_json_member_add_string(wb, "mode", rrd_memory_mode_name(s.db.mode));
441 + buffer_json_member_add_time_t(wb, "first_time", s.db.first_time_s);
442 + buffer_json_member_add_time_t(wb, "last_time", s.db.last_time_s);
443 + buffer_json_member_add_uint64(wb, "metrics", s.db.metrics);
444 + buffer_json_member_add_uint64(wb, "instances", s.db.instances);
445 + buffer_json_member_add_uint64(wb, "contexts", s.db.contexts);
446 + }
447 + buffer_json_object_close(wb);
448 +
449 + rrdhost_receiver_to_json(wb, &s, "ingest");
450 + rrdhost_sender_to_json(wb, &s, "stream");
451 +
452 + buffer_json_member_add_object(wb, "ml");
453 + buffer_json_member_add_string(wb, "status", rrdhost_ml_status_to_string(s.ml.status));
454 + buffer_json_member_add_string(wb, "type", rrdhost_ml_type_to_string(s.ml.type));
455 + if (s.ml.status == RRDHOST_ML_STATUS_RUNNING) {
456 + buffer_json_member_add_object(wb, "metrics");
457 + {
458 + buffer_json_member_add_uint64(wb, "anomalous", s.ml.metrics.anomalous);
459 + buffer_json_member_add_uint64(wb, "normal", s.ml.metrics.normal);
460 + buffer_json_member_add_uint64(wb, "trained", s.ml.metrics.trained);
461 + buffer_json_member_add_uint64(wb, "pending", s.ml.metrics.pending);
462 + buffer_json_member_add_uint64(wb, "silenced", s.ml.metrics.silenced);
463 + }
464 + buffer_json_object_close(wb); // metrics
465 + }
466 + buffer_json_object_close(wb); // ml
467 +
468 + rrdhost_health_to_json_v2(wb, "health", &s);
469 +
470 + host_functions2json(host, wb); // functions
471 + agent_capabilities_to_json(wb, host, "capabilities");
472 +
473 + host_dyncfg_to_json_v2(wb, "dyncfg", &s);
474 + }
475 + buffer_json_object_close(wb); // this instance
476 + buffer_json_array_close(wb); // instances
477 + }
478 + }
479 + buffer_json_object_close(wb); // this node
480 +}
481 +
482 +static ssize_t rrdcontext_to_json_v2_add_host(void *data, RRDHOST *host, bool queryable_host) {
483 + if(!queryable_host || !host->rrdctx.contexts)
484 + // the host matches the 'scope_host' but does not match the 'host' patterns
485 + // or the host does not have any contexts
486 + return 0; // continue to next host
487 +
488 + struct rrdcontext_to_json_v2_data *ctl = data;
489 +
490 + if(ctl->window.enabled && !rrdhost_matches_window(host, ctl->window.after, ctl->window.before, ctl->now))
491 + // the host does not have data in the requested window
492 + return 0; // continue to next host
493 +
494 + if(ctl->request->timeout_ms && now_monotonic_usec() > ctl->timings.received_ut + ctl->request->timeout_ms * USEC_PER_MS)
495 + // timed out
496 + return -2; // stop the query
497 +
498 + if(ctl->request->interrupt_callback && ctl->request->interrupt_callback(ctl->request->interrupt_callback_data))
499 + // interrupted
500 + return -1; // stop the query
501 +
502 + bool host_matched = (ctl->mode & CONTEXTS_V2_NODES);
503 + bool do_contexts = (ctl->mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_ALERTS));
504 +
505 + ctl->q.host_match = FTS_MATCHED_NONE;
506 + if((ctl->mode & CONTEXTS_V2_SEARCH)) {
507 + // check if we match the host itself
508 + if(ctl->q.pattern && (
509 + full_text_search_string(&ctl->q.fts, ctl->q.pattern, host->hostname) ||
510 + full_text_search_char(&ctl->q.fts, ctl->q.pattern, host->machine_guid) ||
511 + (ctl->q.pattern && full_text_search_char(&ctl->q.fts, ctl->q.pattern, ctl->q.host_node_id_str)))) {
512 + ctl->q.host_match = FTS_MATCHED_HOST;
513 + do_contexts = true;
514 + }
515 + }
516 +
517 + if(do_contexts) {
518 + // save it
519 + SIMPLE_PATTERN *old_q = ctl->q.pattern;
520 +
521 + if(ctl->q.host_match == FTS_MATCHED_HOST)
522 + // do not do pattern matching on contexts - we matched the host itself
523 + ctl->q.pattern = NULL;
524 +
525 + ssize_t added = query_scope_foreach_context(
526 + host, ctl->request->scope_contexts,
527 + ctl->contexts.scope_pattern, ctl->contexts.pattern,
528 + rrdcontext_to_json_v2_add_context, queryable_host, ctl);
529 +
530 + // restore it
531 + ctl->q.pattern = old_q;
532 +
533 + if(unlikely(added < 0))
534 + return -1; // stop the query
535 +
536 + if(added)
537 + host_matched = true;
538 + }
539 +
540 + if(!host_matched)
541 + return 0;
542 +
543 + if(ctl->mode & CONTEXTS_V2_FUNCTIONS) {
544 + struct function_v2_entry t = {
545 + .used = 1,
546 + .size = 1,
547 + .node_ids = &ctl->nodes.ni,
548 + .help = NULL,
549 + .tags = NULL,
550 + .access = HTTP_ACCESS_ALL,
551 + .priority = RRDFUNCTIONS_PRIORITY_DEFAULT,
552 + };
553 + host_functions_to_dict(host, ctl->functions.dict, &t, sizeof(t), &t.help, &t.tags, &t.access, &t.priority);
554 + }
555 +
556 + if(ctl->mode & CONTEXTS_V2_NODES) {
557 + struct contexts_v2_node t = {
558 + .ni = ctl->nodes.ni++,
559 + .host = host,
560 + };
561 +
562 + dictionary_set(ctl->nodes.dict, host->machine_guid, &t, sizeof(struct contexts_v2_node));
563 + }
564 +
565 + return 1;
566 +}
567 +
568 +static void buffer_json_contexts_v2_mode_to_array(BUFFER *wb, const char *key, CONTEXTS_V2_MODE mode) {
569 + buffer_json_member_add_array(wb, key);
570 +
571 + if(mode & CONTEXTS_V2_VERSIONS)
572 + buffer_json_add_array_item_string(wb, "versions");
573 +
574 + if(mode & CONTEXTS_V2_AGENTS)
575 + buffer_json_add_array_item_string(wb, "agents");
576 +
577 + if(mode & CONTEXTS_V2_AGENTS_INFO)
578 + buffer_json_add_array_item_string(wb, "agents-info");
579 +
580 + if(mode & CONTEXTS_V2_NODES)
581 + buffer_json_add_array_item_string(wb, "nodes");
582 +
583 + if(mode & CONTEXTS_V2_NODES_INFO)
584 + buffer_json_add_array_item_string(wb, "nodes-info");
585 +
586 + if(mode & CONTEXTS_V2_NODE_INSTANCES)
587 + buffer_json_add_array_item_string(wb, "nodes-instances");
588 +
589 + if(mode & CONTEXTS_V2_CONTEXTS)
590 + buffer_json_add_array_item_string(wb, "contexts");
591 +
592 + if(mode & CONTEXTS_V2_SEARCH)
593 + buffer_json_add_array_item_string(wb, "search");
594 +
595 + if(mode & CONTEXTS_V2_ALERTS)
596 + buffer_json_add_array_item_string(wb, "alerts");
597 +
598 + if(mode & CONTEXTS_V2_ALERT_TRANSITIONS)
599 + buffer_json_add_array_item_string(wb, "alert_transitions");
600 +
601 + buffer_json_array_close(wb);
602 +}
603 +
604 +void buffer_json_query_timings(BUFFER *wb, const char *key, struct query_timings *timings) {
605 + timings->finished_ut = now_monotonic_usec();
606 + if(!timings->executed_ut)
607 + timings->executed_ut = timings->finished_ut;
608 + if(!timings->preprocessed_ut)
609 + timings->preprocessed_ut = timings->received_ut;
610 + buffer_json_member_add_object(wb, key);
611 + buffer_json_member_add_double(wb, "prep_ms", (NETDATA_DOUBLE)(timings->preprocessed_ut - timings->received_ut) / USEC_PER_MS);
612 + buffer_json_member_add_double(wb, "query_ms", (NETDATA_DOUBLE)(timings->executed_ut - timings->preprocessed_ut) / USEC_PER_MS);
613 + buffer_json_member_add_double(wb, "output_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->executed_ut) / USEC_PER_MS);
614 + buffer_json_member_add_double(wb, "total_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS);
615 + buffer_json_member_add_double(wb, "cloud_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS);
616 + buffer_json_object_close(wb);
617 +}
618 +
619 +void buffer_json_cloud_timings(BUFFER *wb, const char *key, struct query_timings *timings) {
620 + if(!timings->finished_ut)
621 + timings->finished_ut = now_monotonic_usec();
622 +
623 + buffer_json_member_add_object(wb, key);
624 + buffer_json_member_add_double(wb, "routing_ms", 0.0);
625 + buffer_json_member_add_double(wb, "node_max_ms", 0.0);
626 + buffer_json_member_add_double(wb, "total_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS);
627 + buffer_json_object_close(wb);
628 +}
629 +
630 +static void functions_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
631 + struct function_v2_entry *t = value;
632 +
633 + // it is initialized with a static reference - we need to mallocz() the array
634 + size_t *v = t->node_ids;
635 + t->node_ids = mallocz(sizeof(size_t));
636 + *t->node_ids = *v;
637 + t->size = 1;
638 + t->used = 1;
639 +}
640 +
641 +static bool functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
642 + struct function_v2_entry *t = old_value, *n = new_value;
643 + size_t *v = n->node_ids;
644 +
645 + if(t->used >= t->size) {
646 + t->node_ids = reallocz(t->node_ids, t->size * 2 * sizeof(size_t));
647 + t->size *= 2;
648 + }
649 +
650 + t->node_ids[t->used++] = *v;
651 +
652 + return true;
653 +}
654 +
655 +static void functions_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
656 + struct function_v2_entry *t = value;
657 + freez(t->node_ids);
658 +}
659 +
660 +static bool contexts_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
661 + struct context_v2_entry *o = old_value;
662 + struct context_v2_entry *n = new_value;
663 +
664 + o->count++;
665 +
666 + if(o->family != n->family) {
667 + if((o->flags & RRD_FLAG_COLLECTED) && !(n->flags & RRD_FLAG_COLLECTED))
668 + // keep old
669 + ;
670 + else if(!(o->flags & RRD_FLAG_COLLECTED) && (n->flags & RRD_FLAG_COLLECTED)) {
671 + // keep new
672 + string_freez(o->family);
673 + o->family = string_dup(n->family);
674 + }
675 + else {
676 + // merge
677 + STRING *old_family = o->family;
678 + o->family = string_2way_merge(o->family, n->family);
679 + string_freez(old_family);
680 + }
681 + }
682 +
683 + if(o->priority != n->priority) {
684 + if((o->flags & RRD_FLAG_COLLECTED) && !(n->flags & RRD_FLAG_COLLECTED))
685 + // keep o
686 + ;
687 + else if(!(o->flags & RRD_FLAG_COLLECTED) && (n->flags & RRD_FLAG_COLLECTED))
688 + // keep n
689 + o->priority = n->priority;
690 + else
691 + // keep the min
692 + o->priority = MIN(o->priority, n->priority);
693 + }
694 +
695 + if(o->first_time_s && n->first_time_s)
696 + o->first_time_s = MIN(o->first_time_s, n->first_time_s);
697 + else if(!o->first_time_s)
698 + o->first_time_s = n->first_time_s;
699 +
700 + if(o->last_time_s && n->last_time_s)
701 + o->last_time_s = MAX(o->last_time_s, n->last_time_s);
702 + else if(!o->last_time_s)
703 + o->last_time_s = n->last_time_s;
704 +
705 + o->flags |= n->flags;
706 + o->match = MIN(o->match, n->match);
707 +
708 + string_freez(n->family);
709 +
710 + return true;
711 +}
712 +
713 +static void contexts_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
714 + struct context_v2_entry *z = value;
715 + string_freez(z->family);
716 +}
717 +
718 +int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTEXTS_V2_MODE mode) {
719 + int resp = HTTP_RESP_OK;
720 + bool run = true;
721 +
722 + if(mode & CONTEXTS_V2_SEARCH)
723 + mode |= CONTEXTS_V2_CONTEXTS;
724 +
725 + if(mode & (CONTEXTS_V2_AGENTS_INFO))
726 + mode |= CONTEXTS_V2_AGENTS;
727 +
728 + if(mode & (CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_NODES_INFO | CONTEXTS_V2_NODE_INSTANCES))
729 + mode |= CONTEXTS_V2_NODES;
730 +
731 + if(mode & CONTEXTS_V2_ALERTS) {
732 + mode |= CONTEXTS_V2_NODES;
733 + req->options &= ~CONTEXTS_OPTION_ALERTS_WITH_CONFIGURATIONS;
734 +
735 + if(!(req->options & (CONTEXTS_OPTION_ALERTS_WITH_SUMMARY | CONTEXTS_OPTION_ALERTS_WITH_INSTANCES |
736 + CONTEXTS_OPTION_ALERTS_WITH_VALUES)))
737 + req->options |= CONTEXTS_OPTION_ALERTS_WITH_SUMMARY;
738 + }
739 +
740 + if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
741 + mode |= CONTEXTS_V2_NODES;
742 + req->options &= ~CONTEXTS_OPTION_ALERTS_WITH_INSTANCES;
743 + }
744 +
745 + struct rrdcontext_to_json_v2_data ctl = {
746 + .wb = wb,
747 + .request = req,
748 + .mode = mode,
749 + .options = req->options,
750 + .versions = { 0 },
751 + .nodes.scope_pattern = string_to_simple_pattern(req->scope_nodes),
752 + .nodes.pattern = string_to_simple_pattern(req->nodes),
753 + .contexts.pattern = string_to_simple_pattern(req->contexts),
754 + .contexts.scope_pattern = string_to_simple_pattern(req->scope_contexts),
755 + .q.pattern = string_to_simple_pattern_nocase(req->q),
756 + .alerts.alert_name_pattern = string_to_simple_pattern(req->alerts.alert),
757 + .window = {
758 + .enabled = false,
759 + .relative = false,
760 + .after = req->after,
761 + .before = req->before,
762 + },
763 + .timings = {
764 + .received_ut = now_monotonic_usec(),
765 + }
766 + };
767 +
768 + bool debug = ctl.options & CONTEXTS_OPTION_DEBUG;
769 +
770 + if(mode & CONTEXTS_V2_NODES) {
771 + ctl.nodes.dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
772 + NULL, sizeof(struct contexts_v2_node));
773 + }
774 +
775 + if(mode & CONTEXTS_V2_CONTEXTS) {
776 + ctl.contexts.dict = dictionary_create_advanced(
777 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL,
778 + sizeof(struct context_v2_entry));
779 +
780 + dictionary_register_conflict_callback(ctl.contexts.dict, contexts_conflict_callback, &ctl);
781 + dictionary_register_delete_callback(ctl.contexts.dict, contexts_delete_callback, &ctl);
782 + }
783 +
784 + if(mode & CONTEXTS_V2_FUNCTIONS) {
785 + ctl.functions.dict = dictionary_create_advanced(
786 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL,
787 + sizeof(struct function_v2_entry));
788 +
789 + dictionary_register_insert_callback(ctl.functions.dict, functions_insert_callback, &ctl);
790 + dictionary_register_conflict_callback(ctl.functions.dict, functions_conflict_callback, &ctl);
791 + dictionary_register_delete_callback(ctl.functions.dict, functions_delete_callback, &ctl);
792 + }
793 +
794 + if(mode & CONTEXTS_V2_ALERTS) {
795 + if(!rrdcontexts_v2_init_alert_dictionaries(&ctl, req)) {
796 + resp = HTTP_RESP_NOT_FOUND;
797 + goto cleanup;
798 + }
799 + }
800 +
801 + if(req->after || req->before) {
802 + ctl.window.relative = rrdr_relative_window_to_absolute_query(
803 + &ctl.window.after, &ctl.window.before, &ctl.now, false);
804 +
805 + ctl.window.enabled = !(mode & CONTEXTS_V2_ALERT_TRANSITIONS);
806 + }
807 + else
808 + ctl.now = now_realtime_sec();
809 +
810 + buffer_json_initialize(wb, "\"", "\"", 0, true,
811 + ((req->options & CONTEXTS_OPTION_MINIFY) && !(req->options & CONTEXTS_OPTION_DEBUG)) ? BUFFER_JSON_OPTIONS_MINIFY : BUFFER_JSON_OPTIONS_DEFAULT);
812 +
813 + buffer_json_member_add_uint64(wb, "api", 2);
814 +
815 + if(req->options & CONTEXTS_OPTION_DEBUG) {
816 + buffer_json_member_add_object(wb, "request");
817 + {
818 + buffer_json_contexts_v2_mode_to_array(wb, "mode", mode);
819 + contexts_options_to_buffer_json_array(wb, "options", req->options);
820 +
821 + buffer_json_member_add_object(wb, "scope");
822 + {
823 + buffer_json_member_add_string(wb, "scope_nodes", req->scope_nodes);
824 + if (mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS))
825 + buffer_json_member_add_string(wb, "scope_contexts", req->scope_contexts);
826 + }
827 + buffer_json_object_close(wb);
828 +
829 + buffer_json_member_add_object(wb, "selectors");
830 + {
831 + buffer_json_member_add_string(wb, "nodes", req->nodes);
832 +
833 + if (mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS))
834 + buffer_json_member_add_string(wb, "contexts", req->contexts);
835 +
836 + if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) {
837 + buffer_json_member_add_object(wb, "alerts");
838 +
839 + if(mode & CONTEXTS_V2_ALERTS)
840 + contexts_alerts_status_to_buffer_json_array(wb, "status", req->alerts.status);
841 +
842 + if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
843 + buffer_json_member_add_string(wb, "context", req->contexts);
844 + buffer_json_member_add_uint64(wb, "anchor_gi", req->alerts.global_id_anchor);
845 + buffer_json_member_add_uint64(wb, "last", req->alerts.last);
846 + }
847 +
848 + buffer_json_member_add_string(wb, "alert", req->alerts.alert);
849 + buffer_json_member_add_string(wb, "transition", req->alerts.transition);
850 + buffer_json_object_close(wb); // alerts
851 + }
852 + }
853 + buffer_json_object_close(wb); // selectors
854 +
855 + buffer_json_member_add_object(wb, "filters");
856 + {
857 + if (mode & CONTEXTS_V2_SEARCH)
858 + buffer_json_member_add_string(wb, "q", req->q);
859 +
860 + buffer_json_member_add_time_t(wb, "after", req->after);
861 + buffer_json_member_add_time_t(wb, "before", req->before);
862 + }
863 + buffer_json_object_close(wb); // filters
864 +
865 + if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
866 + buffer_json_member_add_object(wb, "facets");
867 + {
868 + for (int i = 0; i < ATF_TOTAL_ENTRIES; i++) {
869 + buffer_json_member_add_string(wb, alert_transition_facets[i].query_param, req->alerts.facets[i]);
870 + }
871 + }
872 + buffer_json_object_close(wb); // facets
873 + }
874 + }
875 + buffer_json_object_close(wb);
876 + }
877 +
878 + ssize_t ret = 0;
879 + if(run)
880 + ret = query_scope_foreach_host(ctl.nodes.scope_pattern, ctl.nodes.pattern,
881 + rrdcontext_to_json_v2_add_host, &ctl,
882 + &ctl.versions, ctl.q.host_node_id_str);
883 +
884 + if(unlikely(ret < 0)) {
885 + buffer_flush(wb);
886 +
887 + if(ret == -2) {
888 + buffer_strcat(wb, "query timeout");
889 + resp = HTTP_RESP_GATEWAY_TIMEOUT;
890 + }
891 + else {
892 + buffer_strcat(wb, "query interrupted");
893 + resp = HTTP_RESP_CLIENT_CLOSED_REQUEST;
894 + }
895 + goto cleanup;
896 + }
897 +
898 + ctl.timings.executed_ut = now_monotonic_usec();
899 +
900 + if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
901 + contexts_v2_alert_transitions_to_json(wb, &ctl, debug);
902 + }
903 + else {
904 + if (mode & CONTEXTS_V2_NODES) {
905 + buffer_json_member_add_array(wb, "nodes");
906 + struct contexts_v2_node *t;
907 + dfe_start_read(ctl.nodes.dict, t) {
908 + rrdcontext_to_json_v2_rrdhost(wb, t->host, &ctl, t->ni);
909 + }
910 + dfe_done(t);
911 + buffer_json_array_close(wb);
912 + }
913 +
914 + if (mode & CONTEXTS_V2_FUNCTIONS) {
915 + buffer_json_member_add_array(wb, "functions");
916 + {
917 + struct function_v2_entry *t;
918 + dfe_start_read(ctl.functions.dict, t) {
919 + buffer_json_add_array_item_object(wb);
920 + {
921 + buffer_json_member_add_string(wb, "name", t_dfe.name);
922 + buffer_json_member_add_string(wb, "help", string2str(t->help));
923 + buffer_json_member_add_array(wb, "ni");
924 + {
925 + for (size_t i = 0; i < t->used; i++)
926 + buffer_json_add_array_item_uint64(wb, t->node_ids[i]);
927 + }
928 + buffer_json_array_close(wb);
929 + buffer_json_member_add_string(wb, "tags", string2str(t->tags));
930 + http_access2buffer_json_array(wb, "access", t->access);
931 + buffer_json_member_add_uint64(wb, "priority", t->priority);
932 + }
933 + buffer_json_object_close(wb);
934 + }
935 + dfe_done(t);
936 + }
937 + buffer_json_array_close(wb);
938 + }
939 +
940 + if (mode & CONTEXTS_V2_CONTEXTS) {
941 + buffer_json_member_add_object(wb, "contexts");
942 + {
943 + struct context_v2_entry *z;
944 + dfe_start_read(ctl.contexts.dict, z) {
945 + bool collected = z->flags & RRD_FLAG_COLLECTED;
946 +
947 + buffer_json_member_add_object(wb, string2str(z->id));
948 + {
949 + buffer_json_member_add_string(wb, "family", string2str(z->family));
950 + buffer_json_member_add_uint64(wb, "priority", z->priority);
951 + buffer_json_member_add_time_t(wb, "first_entry", z->first_time_s);
952 + buffer_json_member_add_time_t(wb, "last_entry", collected ? ctl.now : z->last_time_s);
953 + buffer_json_member_add_boolean(wb, "live", collected);
954 + if (mode & CONTEXTS_V2_SEARCH)
955 + buffer_json_member_add_string(wb, "match", fts_match_to_string(z->match));
956 + }
957 + buffer_json_object_close(wb);
958 + }
959 + dfe_done(z);
960 + }
961 + buffer_json_object_close(wb); // contexts
962 + }
963 +
964 + if (mode & CONTEXTS_V2_ALERTS)
965 + contexts_v2_alerts_to_json(wb, &ctl, debug);
966 +
967 + if (mode & CONTEXTS_V2_SEARCH) {
968 + buffer_json_member_add_object(wb, "searches");
969 + {
970 + buffer_json_member_add_uint64(wb, "strings", ctl.q.fts.string_searches);
971 + buffer_json_member_add_uint64(wb, "char", ctl.q.fts.char_searches);
972 + buffer_json_member_add_uint64(wb, "total", ctl.q.fts.searches);
973 + }
974 + buffer_json_object_close(wb);
975 + }
976 +
977 + if (mode & (CONTEXTS_V2_VERSIONS))
978 + version_hashes_api_v2(wb, &ctl.versions);
979 +
980 + if (mode & CONTEXTS_V2_AGENTS)
981 + buffer_json_agents_v2(wb, &ctl.timings, ctl.now, mode & (CONTEXTS_V2_AGENTS_INFO), true);
982 + }
983 +
984 + buffer_json_cloud_timings(wb, "timings", &ctl.timings);
985 +
986 + buffer_json_finalize(wb);
987 +
988 +cleanup:
989 + dictionary_destroy(ctl.nodes.dict);
990 + dictionary_destroy(ctl.contexts.dict);
991 + dictionary_destroy(ctl.functions.dict);
992 + rrdcontexts_v2_alerts_cleanup(&ctl);
993 + simple_pattern_free(ctl.nodes.scope_pattern);
994 + simple_pattern_free(ctl.nodes.pattern);
995 + simple_pattern_free(ctl.contexts.pattern);
996 + simple_pattern_free(ctl.contexts.scope_pattern);
997 + simple_pattern_free(ctl.q.pattern);
998 + simple_pattern_free(ctl.alerts.alert_name_pattern);
999 +
1000 + return resp;
1001 +}
src/database/contexts/api_v2_contexts.h new
+98
@@ -0,0 +1,98 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_API_V2_CONTEXTS_H
4 +#define NETDATA_API_V2_CONTEXTS_H
5 +
6 +#include "internal.h"
7 +
8 +typedef enum __attribute__ ((__packed__)) {
9 + FTS_MATCHED_NONE = 0,
10 + FTS_MATCHED_HOST,
11 + FTS_MATCHED_CONTEXT,
12 + FTS_MATCHED_INSTANCE,
13 + FTS_MATCHED_DIMENSION,
14 + FTS_MATCHED_LABEL,
15 + FTS_MATCHED_ALERT,
16 + FTS_MATCHED_ALERT_INFO,
17 + FTS_MATCHED_FAMILY,
18 + FTS_MATCHED_TITLE,
19 + FTS_MATCHED_UNITS,
20 +} FTS_MATCH;
21 +
22 +typedef struct full_text_search_index {
23 + size_t searches;
24 + size_t string_searches;
25 + size_t char_searches;
26 +} FTS_INDEX;
27 +
28 +struct contexts_v2_node {
29 + size_t ni;
30 + RRDHOST *host;
31 +};
32 +
33 +struct rrdcontext_to_json_v2_data {
34 + time_t now;
35 +
36 + BUFFER *wb;
37 + struct api_v2_contexts_request *request;
38 +
39 + CONTEXTS_V2_MODE mode;
40 + CONTEXTS_OPTIONS options;
41 + struct query_versions versions;
42 +
43 + struct {
44 + SIMPLE_PATTERN *scope_pattern;
45 + SIMPLE_PATTERN *pattern;
46 + size_t ni;
47 + DICTIONARY *dict; // the result set
48 + } nodes;
49 +
50 + struct {
51 + SIMPLE_PATTERN *scope_pattern;
52 + SIMPLE_PATTERN *pattern;
53 + size_t ci;
54 + DICTIONARY *dict; // the result set
55 + } contexts;
56 +
57 + struct {
58 + SIMPLE_PATTERN *alert_name_pattern;
59 + time_t alarm_id_filter;
60 +
61 + size_t ati;
62 +
63 + DICTIONARY *summary;
64 + DICTIONARY *alert_instances;
65 +
66 + DICTIONARY *by_type;
67 + DICTIONARY *by_component;
68 + DICTIONARY *by_classification;
69 + DICTIONARY *by_recipient;
70 + DICTIONARY *by_module;
71 + } alerts;
72 +
73 + struct {
74 + FTS_MATCH host_match;
75 + char host_node_id_str[UUID_STR_LEN];
76 + SIMPLE_PATTERN *pattern;
77 + FTS_INDEX fts;
78 + } q;
79 +
80 + struct {
81 + DICTIONARY *dict; // the result set
82 + } functions;
83 +
84 + struct {
85 + bool enabled;
86 + bool relative;
87 + time_t after;
88 + time_t before;
89 + } window;
90 +
91 + struct query_timings timings;
92 +};
93 +
94 +void agent_capabilities_to_json(BUFFER *wb, RRDHOST *host, const char *key);
95 +
96 +#include "api_v2_contexts_alerts.h"
97 +
98 +#endif //NETDATA_API_V2_CONTEXTS_H
src/database/contexts/api_v2_contexts_agents.c new
+178
@@ -0,0 +1,178 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_contexts.h"
4 +#include "aclk/aclk_capas.h"
5 +
6 +void build_info_to_json_object(BUFFER *b);
7 +
8 +static void convert_seconds_to_dhms(time_t seconds, char *result, int result_size) {
9 + int days, hours, minutes;
10 +
11 + days = (int) (seconds / (24 * 3600));
12 + seconds = (int) (seconds % (24 * 3600));
13 + hours = (int) (seconds / 3600);
14 + seconds %= 3600;
15 + minutes = (int) (seconds / 60);
16 + seconds %= 60;
17 +
18 + // Format the result into the provided string buffer
19 + BUFFER *buf = buffer_create(128, NULL);
20 + if (days)
21 + buffer_sprintf(buf,"%d day%s%s", days, days==1 ? "" : "s", hours || minutes ? ", " : "");
22 + if (hours)
23 + buffer_sprintf(buf,"%d hour%s%s", hours, hours==1 ? "" : "s", minutes ? ", " : "");
24 + if (minutes)
25 + buffer_sprintf(buf,"%d minute%s%s", minutes, minutes==1 ? "" : "s", seconds ? ", " : "");
26 + if (seconds)
27 + buffer_sprintf(buf,"%d second%s", (int) seconds, seconds==1 ? "" : "s");
28 + strncpyz(result, buffer_tostring(buf), result_size);
29 + buffer_free(buf);
30 +}
31 +
32 +void buffer_json_agents_v2(BUFFER *wb, struct query_timings *timings, time_t now_s, bool info, bool array) {
33 + if(!now_s)
34 + now_s = now_realtime_sec();
35 +
36 + if(array) {
37 + buffer_json_member_add_array(wb, "agents");
38 + buffer_json_add_array_item_object(wb);
39 + }
40 + else
41 + buffer_json_member_add_object(wb, "agent");
42 +
43 + buffer_json_member_add_string(wb, "mg", localhost->machine_guid);
44 + buffer_json_member_add_uuid(wb, "nd", localhost->node_id);
45 + buffer_json_member_add_string(wb, "nm", rrdhost_hostname(localhost));
46 + buffer_json_member_add_time_t(wb, "now", now_s);
47 +
48 + if(array)
49 + buffer_json_member_add_uint64(wb, "ai", 0);
50 +
51 + if(info) {
52 + buffer_json_member_add_object(wb, "application");
53 + build_info_to_json_object(wb);
54 + buffer_json_object_close(wb); // application
55 +
56 + buffer_json_cloud_status(wb, now_s);
57 +
58 + buffer_json_member_add_object(wb, "nodes");
59 + {
60 + size_t receiving = 0, archived = 0, sending = 0, total = 0;
61 + RRDHOST *host;
62 + dfe_start_read(rrdhost_root_index, host) {
63 + total++;
64 +
65 + if(host == localhost)
66 + continue;
67 +
68 + if(rrdhost_state_cloud_emulation(host))
69 + receiving++;
70 + else
71 + archived++;
72 +
73 + if(rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED))
74 + sending++;
75 + }
76 + dfe_done(host);
77 +
78 + buffer_json_member_add_uint64(wb, "total", total);
79 + buffer_json_member_add_uint64(wb, "receiving", receiving);
80 + buffer_json_member_add_uint64(wb, "sending", sending);
81 + buffer_json_member_add_uint64(wb, "archived", archived);
82 + }
83 + buffer_json_object_close(wb); // nodes
84 +
85 + agent_capabilities_to_json(wb, localhost, "capabilities");
86 +
87 + buffer_json_member_add_object(wb, "api");
88 + {
89 + buffer_json_member_add_uint64(wb, "version", aclk_get_http_api_version());
90 + buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
91 + }
92 + buffer_json_object_close(wb); // api
93 +
94 + buffer_json_member_add_array(wb, "db_size");
95 + size_t group_seconds = localhost->rrd_update_every;
96 + for (size_t tier = 0; tier < storage_tiers; tier++) {
97 + STORAGE_ENGINE *eng = localhost->db[tier].eng;
98 + if (!eng) continue;
99 +
100 + group_seconds *= storage_tiers_grouping_iterations[tier];
101 + uint64_t max = storage_engine_disk_space_max(eng->seb, localhost->db[tier].si);
102 + uint64_t used = storage_engine_disk_space_used(eng->seb, localhost->db[tier].si);
103 +#ifdef ENABLE_DBENGINE
104 + if (!max && eng->seb == STORAGE_ENGINE_BACKEND_DBENGINE) {
105 + max = get_directory_free_bytes_space(multidb_ctx[tier]);
106 + max += used;
107 + }
108 +#endif
109 + time_t first_time_s = storage_engine_global_first_time_s(eng->seb, localhost->db[tier].si);
110 + size_t currently_collected_metrics = storage_engine_collected_metrics(eng->seb, localhost->db[tier].si);
111 +
112 + NETDATA_DOUBLE percent;
113 + if (used && max)
114 + percent = (NETDATA_DOUBLE) used * 100.0 / (NETDATA_DOUBLE) max;
115 + else
116 + percent = 0.0;
117 +
118 + buffer_json_add_array_item_object(wb);
119 + buffer_json_member_add_uint64(wb, "tier", tier);
120 + char human_retention[128];
121 + convert_seconds_to_dhms((time_t) group_seconds, human_retention, sizeof(human_retention) - 1);
122 + buffer_json_member_add_string(wb, "point_every", human_retention);
123 +
124 + buffer_json_member_add_uint64(wb, "metrics", storage_engine_metrics(eng->seb, localhost->db[tier].si));
125 + buffer_json_member_add_uint64(wb, "samples", storage_engine_samples(eng->seb, localhost->db[tier].si));
126 +
127 + if(used || max) {
128 + buffer_json_member_add_uint64(wb, "disk_used", used);
129 + buffer_json_member_add_uint64(wb, "disk_max", max);
130 + buffer_json_member_add_double(wb, "disk_percent", percent);
131 + }
132 +
133 + if(first_time_s) {
134 + time_t retention = now_s - first_time_s;
135 +
136 + buffer_json_member_add_time_t(wb, "from", first_time_s);
137 + buffer_json_member_add_time_t(wb, "to", now_s);
138 + buffer_json_member_add_time_t(wb, "retention", retention);
139 +
140 + convert_seconds_to_dhms(retention, human_retention, sizeof(human_retention) - 1);
141 + buffer_json_member_add_string(wb, "retention_human", human_retention);
142 +
143 + if(used || max) { // we have disk space information
144 + time_t time_retention = 0;
145 +#ifdef ENABLE_DBENGINE
146 + time_retention = multidb_ctx[tier]->config.max_retention_s;
147 +#endif
148 + time_t space_retention = (time_t)((NETDATA_DOUBLE)(now_s - first_time_s) * 100.0 / percent);
149 + time_t actual_retention = MIN(space_retention, time_retention ? time_retention : space_retention);
150 +
151 + if (time_retention) {
152 + convert_seconds_to_dhms(time_retention, human_retention, sizeof(human_retention) - 1);
153 + buffer_json_member_add_time_t(wb, "requested_retention", time_retention);
154 + buffer_json_member_add_string(wb, "requested_retention_human", human_retention);
155 + }
156 +
157 + convert_seconds_to_dhms(actual_retention, human_retention, sizeof(human_retention) - 1);
158 + buffer_json_member_add_time_t(wb, "expected_retention", actual_retention);
159 + buffer_json_member_add_string(wb, "expected_retention_human", human_retention);
160 + }
161 + }
162 +
163 + if(currently_collected_metrics)
164 + buffer_json_member_add_uint64(wb, "currently_collected_metrics", currently_collected_metrics);
165 +
166 + buffer_json_object_close(wb);
167 + }
168 + buffer_json_array_close(wb); // db_size
169 + }
170 +
171 + if(timings)
172 + buffer_json_query_timings(wb, "timings", timings);
173 +
174 + buffer_json_object_close(wb);
175 +
176 + if(array)
177 + buffer_json_array_close(wb);
178 +}
src/database/contexts/api_v2_contexts_alert_config.c new
+135
@@ -0,0 +1,135 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_contexts_alerts.h"
4 +
5 +void contexts_v2_alert_config_to_json_from_sql_alert_config_data(struct sql_alert_config_data *t, void *data) {
6 + struct alert_transitions_callback_data *d = data;
7 + BUFFER *wb = d->wb;
8 + bool debug = d->debug;
9 + d->configs_added++;
10 +
11 + if(d->only_one_config)
12 + buffer_json_add_array_item_object(wb); // alert config
13 +
14 + {
15 + buffer_json_member_add_string(wb, "name", t->name);
16 + buffer_json_member_add_uuid_ptr(wb, "config_hash_id", t->config_hash_id);
17 +
18 + buffer_json_member_add_object(wb, "selectors");
19 + {
20 + bool is_template = t->selectors.on_template && *t->selectors.on_template ? true : false;
21 + buffer_json_member_add_string(wb, "type", is_template ? "template" : "alarm");
22 + buffer_json_member_add_string(wb, "on", is_template ? t->selectors.on_template : t->selectors.on_key);
23 +
24 + buffer_json_member_add_string(wb, "families", t->selectors.families);
25 + buffer_json_member_add_string(wb, "host_labels", t->selectors.host_labels);
26 + buffer_json_member_add_string(wb, "chart_labels", t->selectors.chart_labels);
27 + }
28 + buffer_json_object_close(wb); // selectors
29 +
30 + buffer_json_member_add_object(wb, "value"); // value
31 + {
32 + // buffer_json_member_add_string(wb, "every", t->value.every); // does not exist in Netdata Cloud
33 + buffer_json_member_add_string(wb, "units", t->value.units);
34 + buffer_json_member_add_uint64(wb, "update_every", t->value.update_every);
35 +
36 + if (t->value.db.after || debug) {
37 + buffer_json_member_add_object(wb, "db");
38 + {
39 + // buffer_json_member_add_string(wb, "lookup", t->value.db.lookup); // does not exist in Netdata Cloud
40 +
41 + buffer_json_member_add_time_t(wb, "after", t->value.db.after);
42 + buffer_json_member_add_time_t(wb, "before", t->value.db.before);
43 + buffer_json_member_add_string(wb, "time_group_condition", alerts_group_conditions_id2txt(t->value.db.time_group_condition));
44 + buffer_json_member_add_double(wb, "time_group_value", t->value.db.time_group_value);
45 + buffer_json_member_add_string(wb, "dims_group", alerts_dims_grouping_id2group(t->value.db.dims_group));
46 + buffer_json_member_add_string(wb, "data_source", alerts_data_source_id2source(t->value.db.data_source));
47 + buffer_json_member_add_string(wb, "method", t->value.db.method);
48 + buffer_json_member_add_string(wb, "dimensions", t->value.db.dimensions);
49 + rrdr_options_to_buffer_json_array(wb, "options", (RRDR_OPTIONS)t->value.db.options);
50 + }
51 + buffer_json_object_close(wb); // db
52 + }
53 +
54 + if (t->value.calc || debug)
55 + buffer_json_member_add_string(wb, "calc", t->value.calc);
56 + }
57 + buffer_json_object_close(wb); // value
58 +
59 + if (t->status.warn || t->status.crit || debug) {
60 + buffer_json_member_add_object(wb, "status"); // status
61 + {
62 + NETDATA_DOUBLE green = t->status.green ? str2ndd(t->status.green, NULL) : NAN;
63 + NETDATA_DOUBLE red = t->status.red ? str2ndd(t->status.red, NULL) : NAN;
64 +
65 + if (!isnan(green) || debug)
66 + buffer_json_member_add_double(wb, "green", green);
67 +
68 + if (!isnan(red) || debug)
69 + buffer_json_member_add_double(wb, "red", red);
70 +
71 + if (t->status.warn || debug)
72 + buffer_json_member_add_string(wb, "warn", t->status.warn);
73 +
74 + if (t->status.crit || debug)
75 + buffer_json_member_add_string(wb, "crit", t->status.crit);
76 + }
77 + buffer_json_object_close(wb); // status
78 + }
79 +
80 + buffer_json_member_add_object(wb, "notification");
81 + {
82 + buffer_json_member_add_string(wb, "type", "agent");
83 + buffer_json_member_add_string(wb, "exec", t->notification.exec ? t->notification.exec : NULL);
84 + buffer_json_member_add_string(wb, "to", t->notification.to_key ? t->notification.to_key : string2str(localhost->health.health_default_recipient));
85 + buffer_json_member_add_string(wb, "delay", t->notification.delay);
86 + buffer_json_member_add_string(wb, "repeat", t->notification.repeat);
87 + buffer_json_member_add_string(wb, "options", t->notification.options);
88 + }
89 + buffer_json_object_close(wb); // notification
90 +
91 + buffer_json_member_add_string(wb, "class", t->classification);
92 + buffer_json_member_add_string(wb, "component", t->component);
93 + buffer_json_member_add_string(wb, "type", t->type);
94 + buffer_json_member_add_string(wb, "info", t->info);
95 + buffer_json_member_add_string(wb, "summary", t->summary);
96 + // buffer_json_member_add_string(wb, "source", t->source); // moved to alert instance
97 + }
98 +
99 + if(d->only_one_config)
100 + buffer_json_object_close(wb);
101 +}
102 +
103 +int contexts_v2_alert_config_to_json(struct web_client *w, const char *config_hash_id) {
104 + struct alert_transitions_callback_data data = {
105 + .wb = w->response.data,
106 + .debug = false,
107 + .only_one_config = false,
108 + };
109 + DICTIONARY *configs = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
110 + dictionary_set(configs, config_hash_id, NULL, 0);
111 +
112 + buffer_flush(w->response.data);
113 +
114 + buffer_json_initialize(w->response.data, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
115 +
116 + int added = sql_get_alert_configuration(configs, contexts_v2_alert_config_to_json_from_sql_alert_config_data, &data, false);
117 + buffer_json_finalize(w->response.data);
118 +
119 + int ret = HTTP_RESP_OK;
120 +
121 + if(added <= 0) {
122 + buffer_flush(w->response.data);
123 + w->response.data->content_type = CT_TEXT_PLAIN;
124 + if(added < 0) {
125 + buffer_strcat(w->response.data, "Failed to execute SQL query.");
126 + ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
127 + }
128 + else {
129 + buffer_strcat(w->response.data, "Config is not found.");
130 + ret = HTTP_RESP_NOT_FOUND;
131 + }
132 + }
133 +
134 + return ret;
135 +}
src/database/contexts/api_v2_contexts_alert_transitions.c new
+487
@@ -0,0 +1,487 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_contexts_alerts.h"
4 +
5 +struct alert_transitions_facets alert_transition_facets[] = {
6 + [ATF_STATUS] = {
7 + .id = "f_status",
8 + .name = "Alert Status",
9 + .query_param = "f_status",
10 + .order = 1,
11 + },
12 + [ATF_TYPE] = {
13 + .id = "f_type",
14 + .name = "Alert Type",
15 + .query_param = "f_type",
16 + .order = 2,
17 + },
18 + [ATF_ROLE] = {
19 + .id = "f_role",
20 + .name = "Recipient Role",
21 + .query_param = "f_role",
22 + .order = 3,
23 + },
24 + [ATF_CLASS] = {
25 + .id = "f_class",
26 + .name = "Alert Class",
27 + .query_param = "f_class",
28 + .order = 4,
29 + },
30 + [ATF_COMPONENT] = {
31 + .id = "f_component",
32 + .name = "Alert Component",
33 + .query_param = "f_component",
34 + .order = 5,
35 + },
36 + [ATF_NODE] = {
37 + .id = "f_node",
38 + .name = "Alert Node",
39 + .query_param = "f_node",
40 + .order = 6,
41 + },
42 + [ATF_ALERT_NAME] = {
43 + .id = "f_alert",
44 + .name = "Alert Name",
45 + .query_param = "f_alert",
46 + .order = 7,
47 + },
48 + [ATF_CHART_NAME] = {
49 + .id = "f_instance",
50 + .name = "Instance Name",
51 + .query_param = "f_instance",
52 + .order = 8,
53 + },
54 + [ATF_CONTEXT] = {
55 + .id = "f_context",
56 + .name = "Context",
57 + .query_param = "f_context",
58 + .order = 9,
59 + },
60 +
61 + // terminator
62 + [ATF_TOTAL_ENTRIES] = {
63 + .id = NULL,
64 + .name = NULL,
65 + .query_param = NULL,
66 + .order = 9999,
67 + }
68 +};
69 +
70 +#define SQL_TRANSITION_DATA_SMALL_STRING (6 * 8)
71 +#define SQL_TRANSITION_DATA_MEDIUM_STRING (12 * 8)
72 +#define SQL_TRANSITION_DATA_BIG_STRING 512
73 +
74 +struct sql_alert_transition_fixed_size {
75 + usec_t global_id;
76 + nd_uuid_t transition_id;
77 + nd_uuid_t host_id;
78 + nd_uuid_t config_hash_id;
79 + uint32_t alarm_id;
80 + char alert_name[SQL_TRANSITION_DATA_SMALL_STRING];
81 + char chart[RRD_ID_LENGTH_MAX];
82 + char chart_name[RRD_ID_LENGTH_MAX];
83 + char chart_context[SQL_TRANSITION_DATA_MEDIUM_STRING];
84 + char family[SQL_TRANSITION_DATA_SMALL_STRING];
85 + char recipient[SQL_TRANSITION_DATA_MEDIUM_STRING];
86 + char units[SQL_TRANSITION_DATA_SMALL_STRING];
87 + char exec[SQL_TRANSITION_DATA_BIG_STRING];
88 + char info[SQL_TRANSITION_DATA_BIG_STRING];
89 + char summary[SQL_TRANSITION_DATA_BIG_STRING];
90 + char classification[SQL_TRANSITION_DATA_SMALL_STRING];
91 + char type[SQL_TRANSITION_DATA_SMALL_STRING];
92 + char component[SQL_TRANSITION_DATA_SMALL_STRING];
93 + time_t when_key;
94 + time_t duration;
95 + time_t non_clear_duration;
96 + uint64_t flags;
97 + time_t delay_up_to_timestamp;
98 + time_t exec_run_timestamp;
99 + int exec_code;
100 + int new_status;
101 + int old_status;
102 + int delay;
103 + time_t last_repeat;
104 + NETDATA_DOUBLE new_value;
105 + NETDATA_DOUBLE old_value;
106 +
107 + char machine_guid[UUID_STR_LEN];
108 + struct sql_alert_transition_fixed_size *next;
109 + struct sql_alert_transition_fixed_size *prev;
110 +};
111 +
112 +struct facet_entry {
113 + uint32_t count;
114 +};
115 +
116 +static struct sql_alert_transition_fixed_size *contexts_v2_alert_transition_dup(struct sql_alert_transition_data *t, const char *machine_guid, struct sql_alert_transition_fixed_size *dst) {
117 + struct sql_alert_transition_fixed_size *n = dst ? dst : mallocz(sizeof(*n));
118 +
119 + n->global_id = t->global_id;
120 + uuid_copy(n->transition_id, *t->transition_id);
121 + uuid_copy(n->host_id, *t->host_id);
122 + uuid_copy(n->config_hash_id, *t->config_hash_id);
123 + n->alarm_id = t->alarm_id;
124 + strncpyz(n->alert_name, t->alert_name ? t->alert_name : "", sizeof(n->alert_name) - 1);
125 + strncpyz(n->chart, t->chart ? t->chart : "", sizeof(n->chart) - 1);
126 + strncpyz(n->chart_name, t->chart_name ? t->chart_name : n->chart, sizeof(n->chart_name) - 1);
127 + strncpyz(n->chart_context, t->chart_context ? t->chart_context : "", sizeof(n->chart_context) - 1);
128 + strncpyz(n->family, t->family ? t->family : "", sizeof(n->family) - 1);
129 + strncpyz(n->recipient, t->recipient ? t->recipient : "", sizeof(n->recipient) - 1);
130 + strncpyz(n->units, t->units ? t->units : "", sizeof(n->units) - 1);
131 + strncpyz(n->exec, t->exec ? t->exec : "", sizeof(n->exec) - 1);
132 + strncpyz(n->info, t->info ? t->info : "", sizeof(n->info) - 1);
133 + strncpyz(n->summary, t->summary ? t->summary : "", sizeof(n->summary) - 1);
134 + strncpyz(n->classification, t->classification ? t->classification : "", sizeof(n->classification) - 1);
135 + strncpyz(n->type, t->type ? t->type : "", sizeof(n->type) - 1);
136 + strncpyz(n->component, t->component ? t->component : "", sizeof(n->component) - 1);
137 + n->when_key = t->when_key;
138 + n->duration = t->duration;
139 + n->non_clear_duration = t->non_clear_duration;
140 + n->flags = t->flags;
141 + n->delay_up_to_timestamp = t->delay_up_to_timestamp;
142 + n->exec_run_timestamp = t->exec_run_timestamp;
143 + n->exec_code = t->exec_code;
144 + n->new_status = t->new_status;
145 + n->old_status = t->old_status;
146 + n->delay = t->delay;
147 + n->last_repeat = t->last_repeat;
148 + n->new_value = t->new_value;
149 + n->old_value = t->old_value;
150 +
151 + memcpy(n->machine_guid, machine_guid, sizeof(n->machine_guid));
152 + n->next = n->prev = NULL;
153 +
154 + return n;
155 +}
156 +
157 +static void contexts_v2_alert_transition_free(struct sql_alert_transition_fixed_size *t) {
158 + freez(t);
159 +}
160 +
161 +static inline void contexts_v2_alert_transition_keep(struct alert_transitions_callback_data *d, struct sql_alert_transition_data *t, const char *machine_guid) {
162 + d->items_matched++;
163 +
164 + if(unlikely(t->global_id <= d->ctl->request->alerts.global_id_anchor)) {
165 + // this is in our past, we are not interested
166 + d->operations.skips_before++;
167 + return;
168 + }
169 +
170 + if(unlikely(!d->base)) {
171 + d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, NULL);
172 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
173 + d->items_to_return++;
174 + d->operations.first++;
175 + return;
176 + }
177 +
178 + struct sql_alert_transition_fixed_size *last = d->last_added;
179 + while(last->prev != d->base->prev && t->global_id > last->prev->global_id) {
180 + last = last->prev;
181 + d->operations.backwards++;
182 + }
183 +
184 + while(last->next && t->global_id < last->next->global_id) {
185 + last = last->next;
186 + d->operations.forwards++;
187 + }
188 +
189 + if(d->items_to_return >= d->max_items_to_return) {
190 + if(last == d->base->prev && t->global_id < last->global_id) {
191 + d->operations.skips_after++;
192 + return;
193 + }
194 + }
195 +
196 + d->items_to_return++;
197 +
198 + if(t->global_id > last->global_id) {
199 + if(d->items_to_return > d->max_items_to_return) {
200 + d->items_to_return--;
201 + d->operations.shifts++;
202 + d->last_added = d->base->prev;
203 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(d->base, d->last_added, prev, next);
204 + d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, d->last_added);
205 + }
206 + DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
207 + d->operations.prepend++;
208 + }
209 + else {
210 + d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, NULL);
211 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
212 + d->operations.append++;
213 + }
214 +
215 + while(d->items_to_return > d->max_items_to_return) {
216 + // we have to remove something
217 +
218 + struct sql_alert_transition_fixed_size *tmp = d->base->prev;
219 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(d->base, tmp, prev, next);
220 + d->items_to_return--;
221 +
222 + if(unlikely(d->last_added == tmp))
223 + d->last_added = d->base;
224 +
225 + contexts_v2_alert_transition_free(tmp);
226 +
227 + d->operations.shifts++;
228 + }
229 +}
230 +
231 +static void contexts_v2_alert_transition_callback(struct sql_alert_transition_data *t, void *data) {
232 + struct alert_transitions_callback_data *d = data;
233 + d->items_evaluated++;
234 +
235 + char machine_guid[UUID_STR_LEN] = "";
236 + uuid_unparse_lower(*t->host_id, machine_guid);
237 +
238 + const char *facets[ATF_TOTAL_ENTRIES] = {
239 + [ATF_STATUS] = rrdcalc_status2string(t->new_status),
240 + [ATF_CLASS] = t->classification,
241 + [ATF_TYPE] = t->type,
242 + [ATF_COMPONENT] = t->component,
243 + [ATF_ROLE] = t->recipient && *t->recipient ? t->recipient : string2str(localhost->health.health_default_recipient),
244 + [ATF_NODE] = machine_guid,
245 + [ATF_ALERT_NAME] = t->alert_name,
246 + [ATF_CHART_NAME] = t->chart_name,
247 + [ATF_CONTEXT] = t->chart_context,
248 + };
249 +
250 + for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
251 + if (!facets[i] || !*facets[i]) facets[i] = "unknown";
252 +
253 + struct facet_entry tmp = {
254 + .count = 0,
255 + };
256 + dictionary_set(d->facets[i].dict, facets[i], &tmp, sizeof(tmp));
257 + }
258 +
259 + bool selected[ATF_TOTAL_ENTRIES] = { 0 };
260 +
261 + uint32_t selected_by = 0;
262 + for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
263 + selected[i] = !d->facets[i].pattern || simple_pattern_matches(d->facets[i].pattern, facets[i]);
264 + if(selected[i])
265 + selected_by++;
266 + }
267 +
268 + if(selected_by == ATF_TOTAL_ENTRIES) {
269 + // this item is selected by all facets
270 + // put it in our result (if it fits)
271 + contexts_v2_alert_transition_keep(d, t, machine_guid);
272 + }
273 +
274 + if(selected_by >= ATF_TOTAL_ENTRIES - 1) {
275 + // this item is selected by all, or all except one facet
276 + // in both cases we need to add it to our counters
277 +
278 + for (size_t i = 0; i < ATF_TOTAL_ENTRIES; i++) {
279 + uint32_t counted_by = selected_by;
280 +
281 + if (counted_by != ATF_TOTAL_ENTRIES) {
282 + counted_by = 0;
283 + for (size_t j = 0; j < ATF_TOTAL_ENTRIES; j++) {
284 + if (i == j || selected[j])
285 + counted_by++;
286 + }
287 + }
288 +
289 + if (counted_by == ATF_TOTAL_ENTRIES) {
290 + // we need to count it on this facet
291 + struct facet_entry *x = dictionary_get(d->facets[i].dict, facets[i]);
292 + internal_fatal(!x, "facet is not found");
293 + if(x)
294 + x->count++;
295 + }
296 + }
297 + }
298 +}
299 +
300 +void contexts_v2_alert_transitions_to_json(BUFFER *wb, struct rrdcontext_to_json_v2_data *ctl, bool debug) {
301 + struct alert_transitions_callback_data data = {
302 + .wb = wb,
303 + .ctl = ctl,
304 + .debug = debug,
305 + .only_one_config = true,
306 + .max_items_to_return = ctl->request->alerts.last,
307 + .items_to_return = 0,
308 + .base = NULL,
309 + };
310 +
311 + for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
312 + data.facets[i].dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, sizeof(struct facet_entry));
313 + if(ctl->request->alerts.facets[i])
314 + data.facets[i].pattern = simple_pattern_create(ctl->request->alerts.facets[i], ",|", SIMPLE_PATTERN_EXACT, false);
315 + }
316 +
317 + sql_alert_transitions(
318 + ctl->nodes.dict,
319 + ctl->window.after,
320 + ctl->window.before,
321 + ctl->request->contexts,
322 + ctl->request->alerts.alert,
323 + ctl->request->alerts.transition,
324 + contexts_v2_alert_transition_callback,
325 + &data,
326 + debug);
327 +
328 + buffer_json_member_add_array(wb, "facets");
329 + for (size_t i = 0; i < ATF_TOTAL_ENTRIES; i++) {
330 + buffer_json_add_array_item_object(wb);
331 + {
332 + buffer_json_member_add_string(wb, "id", alert_transition_facets[i].id);
333 + buffer_json_member_add_string(wb, "name", alert_transition_facets[i].name);
334 + buffer_json_member_add_uint64(wb, "order", alert_transition_facets[i].order);
335 + buffer_json_member_add_array(wb, "options");
336 + {
337 + struct facet_entry *x;
338 + dfe_start_read(data.facets[i].dict, x) {
339 + buffer_json_add_array_item_object(wb);
340 + {
341 + buffer_json_member_add_string(wb, "id", x_dfe.name);
342 + if (i == ATF_NODE) {
343 + RRDHOST *host = rrdhost_find_by_guid(x_dfe.name);
344 + if (host)
345 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(host));
346 + else
347 + buffer_json_member_add_string(wb, "name", x_dfe.name);
348 + } else
349 + buffer_json_member_add_string(wb, "name", x_dfe.name);
350 + buffer_json_member_add_uint64(wb, "count", x->count);
351 + }
352 + buffer_json_object_close(wb);
353 + }
354 + dfe_done(x);
355 + }
356 + buffer_json_array_close(wb); // options
357 + }
358 + buffer_json_object_close(wb); // facet
359 + }
360 + buffer_json_array_close(wb); // facets
361 +
362 + buffer_json_member_add_array(wb, "transitions");
363 + for(struct sql_alert_transition_fixed_size *t = data.base; t ; t = t->next) {
364 + buffer_json_add_array_item_object(wb);
365 + {
366 + RRDHOST *host = rrdhost_find_by_guid(t->machine_guid);
367 +
368 + buffer_json_member_add_uint64(wb, "gi", t->global_id);
369 + buffer_json_member_add_uuid(wb, "transition_id", t->transition_id);
370 + buffer_json_member_add_uuid(wb, "config_hash_id", t->config_hash_id);
371 + buffer_json_member_add_string(wb, "machine_guid", t->machine_guid);
372 +
373 + if(host) {
374 + buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
375 +
376 + if(!uuid_is_null(host->node_id))
377 + buffer_json_member_add_uuid(wb, "node_id", host->node_id);
378 + }
379 +
380 + buffer_json_member_add_string(wb, "alert", *t->alert_name ? t->alert_name : NULL);
381 + buffer_json_member_add_string(wb, "instance", *t->chart ? t->chart : NULL);
382 + buffer_json_member_add_string(wb, "instance_n", *t->chart_name ? t->chart_name : NULL);
383 + buffer_json_member_add_string(wb, "context", *t->chart_context ? t->chart_context : NULL);
384 + // buffer_json_member_add_string(wb, "family", *t->family ? t->family : NULL);
385 + buffer_json_member_add_string(wb, "component", *t->component ? t->component : NULL);
386 + buffer_json_member_add_string(wb, "classification", *t->classification ? t->classification : NULL);
387 + buffer_json_member_add_string(wb, "type", *t->type ? t->type : NULL);
388 +
389 + buffer_json_member_add_time_t(wb, "when", t->when_key);
390 + buffer_json_member_add_string(wb, "info", *t->info ? t->info : "");
391 + buffer_json_member_add_string(wb, "summary", *t->summary ? t->summary : "");
392 + buffer_json_member_add_string(wb, "units", *t->units ? t->units : NULL);
393 + buffer_json_member_add_object(wb, "new");
394 + {
395 + buffer_json_member_add_string(wb, "status", rrdcalc_status2string(t->new_status));
396 + buffer_json_member_add_double(wb, "value", t->new_value);
397 + }
398 + buffer_json_object_close(wb); // new
399 + buffer_json_member_add_object(wb, "old");
400 + {
401 + buffer_json_member_add_string(wb, "status", rrdcalc_status2string(t->old_status));
402 + buffer_json_member_add_double(wb, "value", t->old_value);
403 + buffer_json_member_add_time_t(wb, "duration", t->duration);
404 + buffer_json_member_add_time_t(wb, "raised_duration", t->non_clear_duration);
405 + }
406 + buffer_json_object_close(wb); // old
407 +
408 + buffer_json_member_add_object(wb, "notification");
409 + {
410 + buffer_json_member_add_time_t(wb, "when", t->exec_run_timestamp);
411 + buffer_json_member_add_time_t(wb, "delay", t->delay);
412 + buffer_json_member_add_time_t(wb, "delay_up_to_time", t->delay_up_to_timestamp);
413 + health_entry_flags_to_json_array(wb, "flags", t->flags);
414 + buffer_json_member_add_string(wb, "exec", *t->exec ? t->exec : string2str(localhost->health.health_default_exec));
415 + buffer_json_member_add_uint64(wb, "exec_code", t->exec_code);
416 + buffer_json_member_add_string(wb, "to", *t->recipient ? t->recipient : string2str(localhost->health.health_default_recipient));
417 + }
418 + buffer_json_object_close(wb); // notification
419 + }
420 + buffer_json_object_close(wb); // a transition
421 + }
422 + buffer_json_array_close(wb); // all transitions
423 +
424 + if(ctl->options & CONTEXTS_OPTION_ALERTS_WITH_CONFIGURATIONS) {
425 + DICTIONARY *configs = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
426 +
427 + for(struct sql_alert_transition_fixed_size *t = data.base; t ; t = t->next) {
428 + char guid[UUID_STR_LEN];
429 + uuid_unparse_lower(t->config_hash_id, guid);
430 + dictionary_set(configs, guid, NULL, 0);
431 + }
432 +
433 + buffer_json_member_add_array(wb, "configurations");
434 + sql_get_alert_configuration(configs, contexts_v2_alert_config_to_json_from_sql_alert_config_data, &data, debug);
435 + buffer_json_array_close(wb);
436 +
437 + dictionary_destroy(configs);
438 + }
439 +
440 + while(data.base) {
441 + struct sql_alert_transition_fixed_size *t = data.base;
442 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(data.base, t, prev, next);
443 + contexts_v2_alert_transition_free(t);
444 + }
445 +
446 + for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
447 + dictionary_destroy(data.facets[i].dict);
448 + simple_pattern_free(data.facets[i].pattern);
449 + }
450 +
451 + buffer_json_member_add_object(wb, "items");
452 + {
453 + // all the items in the window, under the scope_nodes, ignoring the facets (filters)
454 + buffer_json_member_add_uint64(wb, "evaluated", data.items_evaluated);
455 +
456 + // all the items matching the query (if you didn't put anchor_gi and last, these are all the items you would get back)
457 + buffer_json_member_add_uint64(wb, "matched", data.items_matched);
458 +
459 + // the items included in this response
460 + buffer_json_member_add_uint64(wb, "returned", data.items_to_return);
461 +
462 + // same as last=X parameter
463 + buffer_json_member_add_uint64(wb, "max_to_return", data.max_items_to_return);
464 +
465 + // items before the first returned, this should be 0 if anchor_gi is not set
466 + buffer_json_member_add_uint64(wb, "before", data.operations.skips_before);
467 +
468 + // items after the last returned, when this is zero there aren't any items after the current list
469 + buffer_json_member_add_uint64(wb, "after", data.operations.skips_after + data.operations.shifts);
470 + }
471 + buffer_json_object_close(wb); // items
472 +
473 + if(debug) {
474 + buffer_json_member_add_object(wb, "stats");
475 + {
476 + buffer_json_member_add_uint64(wb, "first", data.operations.first);
477 + buffer_json_member_add_uint64(wb, "prepend", data.operations.prepend);
478 + buffer_json_member_add_uint64(wb, "append", data.operations.append);
479 + buffer_json_member_add_uint64(wb, "backwards", data.operations.backwards);
480 + buffer_json_member_add_uint64(wb, "forwards", data.operations.forwards);
481 + buffer_json_member_add_uint64(wb, "shifts", data.operations.shifts);
482 + buffer_json_member_add_uint64(wb, "skips_before", data.operations.skips_before);
483 + buffer_json_member_add_uint64(wb, "skips_after", data.operations.skips_after);
484 + }
485 + buffer_json_object_close(wb);
486 + }
487 +}
src/database/contexts/api_v2_contexts_alerts.c new
+604
@@ -0,0 +1,604 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_contexts.h"
4 +
5 +struct alert_counts {
6 + size_t critical;
7 + size_t warning;
8 + size_t clear;
9 + size_t error;
10 +};
11 +
12 +struct alert_v2_entry {
13 + RRDCALC *tmp;
14 +
15 + STRING *name;
16 + STRING *summary;
17 + RRDLABELS *recipient;
18 + RRDLABELS *classification;
19 + RRDLABELS *context;
20 + RRDLABELS *component;
21 + RRDLABELS *type;
22 +
23 + size_t ati;
24 +
25 + struct alert_counts counts;
26 +
27 + size_t instances;
28 + DICTIONARY *nodes;
29 + DICTIONARY *configs;
30 +};
31 +
32 +struct alert_by_x_entry {
33 + struct {
34 + struct alert_counts counts;
35 + size_t silent;
36 + size_t total;
37 + } running;
38 +
39 + struct {
40 + size_t available;
41 + } prototypes;
42 +};
43 +
44 +bool rrdcontext_matches_alert(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc) {
45 + size_t matches = 0;
46 + RRDINSTANCE *ri;
47 + dfe_start_read(rc->rrdinstances, ri) {
48 + if(ri->rrdset) {
49 + RRDSET *st = ri->rrdset;
50 + rw_spinlock_read_lock(&st->alerts.spinlock);
51 + for (RRDCALC *rcl = st->alerts.base; rcl; rcl = rcl->next) {
52 + if(ctl->alerts.alert_name_pattern && !simple_pattern_matches_string(ctl->alerts.alert_name_pattern, rcl->config.name))
53 + continue;
54 +
55 + if(ctl->alerts.alarm_id_filter && ctl->alerts.alarm_id_filter != rcl->id)
56 + continue;
57 +
58 + size_t m = ctl->request->alerts.status & CONTEXTS_ALERT_STATUSES ? 0 : 1;
59 +
60 + if (!m) {
61 + if ((ctl->request->alerts.status & CONTEXT_ALERT_UNINITIALIZED) &&
62 + rcl->status == RRDCALC_STATUS_UNINITIALIZED)
63 + m++;
64 +
65 + if ((ctl->request->alerts.status & CONTEXT_ALERT_UNDEFINED) &&
66 + rcl->status == RRDCALC_STATUS_UNDEFINED)
67 + m++;
68 +
69 + if ((ctl->request->alerts.status & CONTEXT_ALERT_CLEAR) &&
70 + rcl->status == RRDCALC_STATUS_CLEAR)
71 + m++;
72 +
73 + if ((ctl->request->alerts.status & CONTEXT_ALERT_RAISED) &&
74 + rcl->status >= RRDCALC_STATUS_RAISED)
75 + m++;
76 +
77 + if ((ctl->request->alerts.status & CONTEXT_ALERT_WARNING) &&
78 + rcl->status == RRDCALC_STATUS_WARNING)
79 + m++;
80 +
81 + if ((ctl->request->alerts.status & CONTEXT_ALERT_CRITICAL) &&
82 + rcl->status == RRDCALC_STATUS_CRITICAL)
83 + m++;
84 +
85 + if(!m)
86 + continue;
87 + }
88 +
89 + struct alert_v2_entry t = {
90 + .tmp = rcl,
91 + };
92 + struct alert_v2_entry *a2e =
93 + dictionary_set(ctl->alerts.summary, string2str(rcl->config.name),
94 + &t, sizeof(struct alert_v2_entry));
95 + size_t ati = a2e->ati;
96 + matches++;
97 +
98 + dictionary_set_advanced(ctl->alerts.by_type,
99 + string2str(rcl->config.type),
100 + (ssize_t)string_strlen(rcl->config.type),
101 + NULL,
102 + sizeof(struct alert_by_x_entry),
103 + rcl);
104 +
105 + dictionary_set_advanced(ctl->alerts.by_component,
106 + string2str(rcl->config.component),
107 + (ssize_t)string_strlen(rcl->config.component),
108 + NULL,
109 + sizeof(struct alert_by_x_entry),
110 + rcl);
111 +
112 + dictionary_set_advanced(ctl->alerts.by_classification,
113 + string2str(rcl->config.classification),
114 + (ssize_t)string_strlen(rcl->config.classification),
115 + NULL,
116 + sizeof(struct alert_by_x_entry),
117 + rcl);
118 +
119 + dictionary_set_advanced(ctl->alerts.by_recipient,
120 + string2str(rcl->config.recipient),
121 + (ssize_t)string_strlen(rcl->config.recipient),
122 + NULL,
123 + sizeof(struct alert_by_x_entry),
124 + rcl);
125 +
126 + char *module = NULL;
127 + rrdlabels_get_value_strdup_or_null(st->rrdlabels, &module, "_collect_module");
128 + if(!module || !*module) module = "[unset]";
129 +
130 + dictionary_set_advanced(ctl->alerts.by_module,
131 + module,
132 + -1,
133 + NULL,
134 + sizeof(struct alert_by_x_entry),
135 + rcl);
136 +
137 + if (ctl->options & (CONTEXTS_OPTION_ALERTS_WITH_INSTANCES | CONTEXTS_OPTION_ALERTS_WITH_VALUES)) {
138 + char key[20 + 1];
139 + snprintfz(key, sizeof(key) - 1, "%p", rcl);
140 +
141 + struct sql_alert_instance_v2_entry z = {
142 + .ati = ati,
143 + .tmp = rcl,
144 + };
145 + dictionary_set(ctl->alerts.alert_instances, key, &z, sizeof(z));
146 + }
147 + }
148 + rw_spinlock_read_unlock(&st->alerts.spinlock);
149 + }
150 + }
151 + dfe_done(ri);
152 +
153 + return matches != 0;
154 +}
155 +
156 +static void alert_counts_add(struct alert_counts *t, RRDCALC *rc) {
157 + switch(rc->status) {
158 + case RRDCALC_STATUS_CRITICAL:
159 + t->critical++;
160 + break;
161 +
162 + case RRDCALC_STATUS_WARNING:
163 + t->warning++;
164 + break;
165 +
166 + case RRDCALC_STATUS_CLEAR:
167 + t->clear++;
168 + break;
169 +
170 + case RRDCALC_STATUS_REMOVED:
171 + case RRDCALC_STATUS_UNINITIALIZED:
172 + break;
173 +
174 + case RRDCALC_STATUS_UNDEFINED:
175 + default:
176 + if(!netdata_double_isnumber(rc->value))
177 + t->error++;
178 +
179 + break;
180 + }
181 +}
182 +
183 +static void alerts_v2_add(struct alert_v2_entry *t, RRDCALC *rc) {
184 + t->instances++;
185 +
186 + alert_counts_add(&t->counts, rc);
187 +
188 + dictionary_set(t->nodes, rc->rrdset->rrdhost->machine_guid, NULL, 0);
189 +
190 + char key[UUID_STR_LEN + 1];
191 + uuid_unparse_lower(rc->config.hash_id, key);
192 + dictionary_set(t->configs, key, NULL, 0);
193 +}
194 +
195 +static void alerts_by_x_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
196 + static STRING *silent = NULL;
197 + if(unlikely(!silent)) silent = string_strdupz("silent");
198 +
199 + struct alert_by_x_entry *b = value;
200 + RRDCALC *rc = data;
201 + if(!rc) {
202 + // prototype
203 + b->prototypes.available++;
204 + }
205 + else {
206 + alert_counts_add(&b->running.counts, rc);
207 +
208 + b->running.total++;
209 +
210 + if (rc->config.recipient == silent)
211 + b->running.silent++;
212 + }
213 +}
214 +
215 +static bool alerts_by_x_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value __maybe_unused, void *data __maybe_unused) {
216 + alerts_by_x_insert_callback(item, old_value, data);
217 + return false;
218 +}
219 +
220 +static void alerts_v2_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
221 + struct rrdcontext_to_json_v2_data *ctl = data;
222 + struct alert_v2_entry *t = value;
223 + RRDCALC *rc = t->tmp;
224 + t->name = rc->config.name;
225 + t->summary = rc->config.summary; // the original summary
226 + t->context = rrdlabels_create();
227 + t->recipient = rrdlabels_create();
228 + t->classification = rrdlabels_create();
229 + t->component = rrdlabels_create();
230 + t->type = rrdlabels_create();
231 + if (string_strlen(rc->rrdset->context))
232 + rrdlabels_add(t->context, string2str(rc->rrdset->context), "yes", RRDLABEL_SRC_AUTO);
233 + if (string_strlen(rc->config.recipient))
234 + rrdlabels_add(t->recipient, string2str(rc->config.recipient), "yes", RRDLABEL_SRC_AUTO);
235 + if (string_strlen(rc->config.classification))
236 + rrdlabels_add(t->classification, string2str(rc->config.classification), "yes", RRDLABEL_SRC_AUTO);
237 + if (string_strlen(rc->config.component))
238 + rrdlabels_add(t->component, string2str(rc->config.component), "yes", RRDLABEL_SRC_AUTO);
239 + if (string_strlen(rc->config.type))
240 + rrdlabels_add(t->type, string2str(rc->config.type), "yes", RRDLABEL_SRC_AUTO);
241 + t->ati = ctl->alerts.ati++;
242 +
243 + t->nodes = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_VALUE_LINK_DONT_CLONE|DICT_OPTION_NAME_LINK_DONT_CLONE);
244 + t->configs = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_VALUE_LINK_DONT_CLONE|DICT_OPTION_NAME_LINK_DONT_CLONE);
245 +
246 + alerts_v2_add(t, rc);
247 +}
248 +
249 +static bool alerts_v2_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
250 + struct alert_v2_entry *t = old_value, *n = new_value;
251 + RRDCALC *rc = n->tmp;
252 + if (string_strlen(rc->rrdset->context))
253 + rrdlabels_add(t->context, string2str(rc->rrdset->context), "yes", RRDLABEL_SRC_AUTO);
254 + if (string_strlen(rc->config.recipient))
255 + rrdlabels_add(t->recipient, string2str(rc->config.recipient), "yes", RRDLABEL_SRC_AUTO);
256 + if (string_strlen(rc->config.classification))
257 + rrdlabels_add(t->classification, string2str(rc->config.classification), "yes", RRDLABEL_SRC_AUTO);
258 + if (string_strlen(rc->config.component))
259 + rrdlabels_add(t->component, string2str(rc->config.component), "yes", RRDLABEL_SRC_AUTO);
260 + if (string_strlen(rc->config.type))
261 + rrdlabels_add(t->type, string2str(rc->config.type), "yes", RRDLABEL_SRC_AUTO);
262 + alerts_v2_add(t, rc);
263 + return true;
264 +}
265 +
266 +static void alerts_v2_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
267 + struct alert_v2_entry *t = value;
268 +
269 + rrdlabels_destroy(t->context);
270 + rrdlabels_destroy(t->recipient);
271 + rrdlabels_destroy(t->classification);
272 + rrdlabels_destroy(t->component);
273 + rrdlabels_destroy(t->type);
274 +
275 + dictionary_destroy(t->nodes);
276 + dictionary_destroy(t->configs);
277 +}
278 +
279 +struct alert_instances_callback_data {
280 + BUFFER *wb;
281 + struct rrdcontext_to_json_v2_data *ctl;
282 + bool debug;
283 +};
284 +
285 +static int contexts_v2_alert_instance_to_json_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
286 + struct sql_alert_instance_v2_entry *t = value;
287 + struct alert_instances_callback_data *d = data;
288 + struct rrdcontext_to_json_v2_data *ctl = d->ctl; (void)ctl;
289 + bool debug = d->debug; (void)debug;
290 + BUFFER *wb = d->wb;
291 +
292 + buffer_json_add_array_item_object(wb);
293 + {
294 + buffer_json_member_add_uint64(wb, "ni", t->ni);
295 +
296 + buffer_json_member_add_string(wb, "nm", string2str(t->name));
297 + buffer_json_member_add_string(wb, "ch", string2str(t->chart_id));
298 + buffer_json_member_add_string(wb, "ch_n", string2str(t->chart_name));
299 +
300 + if(ctl->request->options & CONTEXTS_OPTION_ALERTS_WITH_SUMMARY)
301 + buffer_json_member_add_uint64(wb, "ati", t->ati);
302 +
303 + if(ctl->request->options & CONTEXTS_OPTION_ALERTS_WITH_INSTANCES) {
304 + buffer_json_member_add_string(wb, "units", string2str(t->units));
305 + buffer_json_member_add_string(wb, "fami", string2str(t->family));
306 + buffer_json_member_add_string(wb, "info", string2str(t->info));
307 + buffer_json_member_add_string(wb, "sum", string2str(t->summary));
308 + buffer_json_member_add_string(wb, "ctx", string2str(t->context));
309 + buffer_json_member_add_string(wb, "st", rrdcalc_status2string(t->status));
310 + buffer_json_member_add_uuid(wb, "tr_i", t->last_transition_id);
311 + buffer_json_member_add_double(wb, "tr_v", t->last_status_change_value);
312 + buffer_json_member_add_time_t(wb, "tr_t", t->last_status_change);
313 + buffer_json_member_add_uuid(wb, "cfg", t->config_hash_id);
314 + buffer_json_member_add_string(wb, "src", string2str(t->source));
315 +
316 + buffer_json_member_add_string(wb, "to", string2str(t->recipient));
317 + buffer_json_member_add_string(wb, "tp", string2str(t->type));
318 + buffer_json_member_add_string(wb, "cm", string2str(t->component));
319 + buffer_json_member_add_string(wb, "cl", string2str(t->classification));
320 +
321 + // Agent specific fields
322 + buffer_json_member_add_uint64(wb, "gi", t->global_id);
323 + // rrdcalc_flags_to_json_array (wb, "flags", t->flags);
324 + }
325 +
326 + if(ctl->request->options & CONTEXTS_OPTION_ALERTS_WITH_VALUES) {
327 + // Netdata Cloud fetched these by querying the agents
328 + buffer_json_member_add_double(wb, "v", t->value);
329 + buffer_json_member_add_time_t(wb, "t", t->last_updated);
330 + }
331 + }
332 + buffer_json_object_close(wb); // alert instance
333 +
334 + return 1;
335 +}
336 +
337 +static void contexts_v2_alerts_by_x_update_prototypes(void *data, STRING *type, STRING *component, STRING *classification, STRING *recipient) {
338 + struct rrdcontext_to_json_v2_data *ctl = data;
339 +
340 + dictionary_set_advanced(ctl->alerts.by_type, string2str(type), (ssize_t)string_strlen(type), NULL, sizeof(struct alert_by_x_entry), NULL);
341 + dictionary_set_advanced(ctl->alerts.by_component, string2str(component), (ssize_t)string_strlen(component), NULL, sizeof(struct alert_by_x_entry), NULL);
342 + dictionary_set_advanced(ctl->alerts.by_classification, string2str(classification), (ssize_t)string_strlen(classification), NULL, sizeof(struct alert_by_x_entry), NULL);
343 + dictionary_set_advanced(ctl->alerts.by_recipient, string2str(recipient), (ssize_t)string_strlen(recipient), NULL, sizeof(struct alert_by_x_entry), NULL);
344 +}
345 +
346 +static void contexts_v2_alerts_by_x_to_json(BUFFER *wb, DICTIONARY *dict, const char *key) {
347 + buffer_json_member_add_array(wb, key);
348 + {
349 + struct alert_by_x_entry *b;
350 + dfe_start_read(dict, b) {
351 + buffer_json_add_array_item_object(wb);
352 + {
353 + buffer_json_member_add_string(wb, "name", b_dfe.name);
354 + buffer_json_member_add_uint64(wb, "cr", b->running.counts.critical);
355 + buffer_json_member_add_uint64(wb, "wr", b->running.counts.warning);
356 + buffer_json_member_add_uint64(wb, "cl", b->running.counts.clear);
357 + buffer_json_member_add_uint64(wb, "er", b->running.counts.error);
358 + buffer_json_member_add_uint64(wb, "running", b->running.total);
359 +
360 + buffer_json_member_add_uint64(wb, "running_silent", b->running.silent);
361 +
362 + if(b->prototypes.available)
363 + buffer_json_member_add_uint64(wb, "available", b->prototypes.available);
364 + }
365 + buffer_json_object_close(wb);
366 + }
367 + dfe_done(b);
368 + }
369 + buffer_json_array_close(wb);
370 +}
371 +
372 +static void contexts_v2_alert_instances_to_json(BUFFER *wb, const char *key, struct rrdcontext_to_json_v2_data *ctl, bool debug) {
373 + buffer_json_member_add_array(wb, key);
374 + {
375 + struct alert_instances_callback_data data = {
376 + .wb = wb,
377 + .ctl = ctl,
378 + .debug = debug,
379 + };
380 + dictionary_walkthrough_rw(ctl->alerts.alert_instances, DICTIONARY_LOCK_READ,
381 + contexts_v2_alert_instance_to_json_callback, &data);
382 + }
383 + buffer_json_array_close(wb); // alerts_instances
384 +}
385 +
386 +void contexts_v2_alerts_to_json(BUFFER *wb, struct rrdcontext_to_json_v2_data *ctl, bool debug) {
387 + if(ctl->request->options & CONTEXTS_OPTION_ALERTS_WITH_SUMMARY) {
388 + buffer_json_member_add_array(wb, "alerts");
389 + {
390 + struct alert_v2_entry *t;
391 + dfe_start_read(ctl->alerts.summary, t)
392 + {
393 + buffer_json_add_array_item_object(wb);
394 + {
395 + buffer_json_member_add_uint64(wb, "ati", t->ati);
396 +
397 + buffer_json_member_add_array(wb, "ni");
398 + void *host_guid;
399 + dfe_start_read(t->nodes, host_guid) {
400 + struct contexts_v2_node *cn = dictionary_get(ctl->nodes.dict,host_guid_dfe.name);
401 + buffer_json_add_array_item_int64(wb, (int64_t) cn->ni);
402 + }
403 + dfe_done(host_guid);
404 + buffer_json_array_close(wb);
405 +
406 + buffer_json_member_add_string(wb, "nm", string2str(t->name));
407 + buffer_json_member_add_string(wb, "sum", string2str(t->summary));
408 +
409 + buffer_json_member_add_uint64(wb, "cr", t->counts.critical);
410 + buffer_json_member_add_uint64(wb, "wr", t->counts.warning);
411 + buffer_json_member_add_uint64(wb, "cl", t->counts.clear);
412 + buffer_json_member_add_uint64(wb, "er", t->counts.error);
413 +
414 + buffer_json_member_add_uint64(wb, "in", t->instances);
415 + buffer_json_member_add_uint64(wb, "nd", dictionary_entries(t->nodes));
416 + buffer_json_member_add_uint64(wb, "cfg", dictionary_entries(t->configs));
417 +
418 + buffer_json_member_add_array(wb, "ctx");
419 + rrdlabels_key_to_buffer_array_item(t->context, wb);
420 + buffer_json_array_close(wb); // ctx
421 +
422 + buffer_json_member_add_array(wb, "cls");
423 + rrdlabels_key_to_buffer_array_item(t->classification, wb);
424 + buffer_json_array_close(wb); // classification
425 +
426 +
427 + buffer_json_member_add_array(wb, "cp");
428 + rrdlabels_key_to_buffer_array_item(t->component, wb);
429 + buffer_json_array_close(wb); // component
430 +
431 + buffer_json_member_add_array(wb, "ty");
432 + rrdlabels_key_to_buffer_array_item(t->type, wb);
433 + buffer_json_array_close(wb); // type
434 +
435 + buffer_json_member_add_array(wb, "to");
436 + rrdlabels_key_to_buffer_array_item(t->recipient, wb);
437 + buffer_json_array_close(wb); // recipient
438 + }
439 + buffer_json_object_close(wb); // alert name
440 + }
441 + dfe_done(t);
442 + }
443 + buffer_json_array_close(wb); // alerts
444 +
445 + health_prototype_metadata_foreach(ctl, contexts_v2_alerts_by_x_update_prototypes);
446 + contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_type, "alerts_by_type");
447 + contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_component, "alerts_by_component");
448 + contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_classification, "alerts_by_classification");
449 + contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_recipient, "alerts_by_recipient");
450 + contexts_v2_alerts_by_x_to_json(wb, ctl->alerts.by_module, "alerts_by_module");
451 + }
452 +
453 + if(ctl->request->options & (CONTEXTS_OPTION_ALERTS_WITH_INSTANCES | CONTEXTS_OPTION_ALERTS_WITH_VALUES)) {
454 + contexts_v2_alert_instances_to_json(wb, "alert_instances", ctl, debug);
455 + }
456 +}
457 +
458 +static void alert_instances_v2_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
459 + struct rrdcontext_to_json_v2_data *ctl = data;
460 + struct sql_alert_instance_v2_entry *t = value;
461 + RRDCALC *rc = t->tmp;
462 +
463 + t->context = rc->rrdset->context;
464 + t->chart_id = rc->rrdset->id;
465 + t->chart_name = rc->rrdset->name;
466 + t->family = rc->rrdset->family;
467 + t->units = rc->config.units;
468 + t->classification = rc->config.classification;
469 + t->type = rc->config.type;
470 + t->recipient = rc->config.recipient;
471 + t->component = rc->config.component;
472 + t->name = rc->config.name;
473 + t->source = rc->config.source;
474 + t->status = rc->status;
475 + t->flags = rc->run_flags;
476 + t->info = rc->config.info;
477 + t->summary = rc->summary;
478 + t->value = rc->value;
479 + t->last_updated = rc->last_updated;
480 + t->last_status_change = rc->last_status_change;
481 + t->last_status_change_value = rc->last_status_change_value;
482 + t->host = rc->rrdset->rrdhost;
483 + t->alarm_id = rc->id;
484 + t->ni = ctl->nodes.ni;
485 +
486 + uuid_copy(t->config_hash_id, rc->config.hash_id);
487 + health_alarm_log_get_global_id_and_transition_id_for_rrdcalc(rc, &t->global_id, &t->last_transition_id);
488 +}
489 +
490 +static bool alert_instances_v2_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value __maybe_unused, void *new_value __maybe_unused, void *data __maybe_unused) {
491 + internal_fatal(true, "This should never happen!");
492 + return true;
493 +}
494 +
495 +static void alert_instances_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
496 + ;
497 +}
498 +
499 +static void rrdcontext_v2_set_transition_filter(const char *machine_guid, const char *context, time_t alarm_id, void *data) {
500 + struct rrdcontext_to_json_v2_data *ctl = data;
501 +
502 + if(machine_guid && *machine_guid) {
503 + if(ctl->nodes.scope_pattern)
504 + simple_pattern_free(ctl->nodes.scope_pattern);
505 +
506 + if(ctl->nodes.pattern)
507 + simple_pattern_free(ctl->nodes.pattern);
508 +
509 + ctl->nodes.scope_pattern = string_to_simple_pattern(machine_guid);
510 + ctl->nodes.pattern = NULL;
511 + }
512 +
513 + if(context && *context) {
514 + if(ctl->contexts.scope_pattern)
515 + simple_pattern_free(ctl->contexts.scope_pattern);
516 +
517 + if(ctl->contexts.pattern)
518 + simple_pattern_free(ctl->contexts.pattern);
519 +
520 + ctl->contexts.scope_pattern = string_to_simple_pattern(context);
521 + ctl->contexts.pattern = NULL;
522 + }
523 +
524 + ctl->alerts.alarm_id_filter = alarm_id;
525 +}
526 +
527 +bool rrdcontexts_v2_init_alert_dictionaries(struct rrdcontext_to_json_v2_data *ctl, struct api_v2_contexts_request *req) {
528 + if(req->alerts.transition) {
529 + ctl->options |= CONTEXTS_OPTION_ALERTS_WITH_INSTANCES | CONTEXTS_OPTION_ALERTS_WITH_VALUES;
530 + if(!sql_find_alert_transition(req->alerts.transition, rrdcontext_v2_set_transition_filter, &ctl))
531 + return false;
532 + }
533 +
534 + ctl->alerts.summary = dictionary_create_advanced(
535 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
536 + NULL,
537 + sizeof(struct alert_v2_entry));
538 +
539 + dictionary_register_insert_callback(ctl->alerts.summary, alerts_v2_insert_callback, &ctl);
540 + dictionary_register_conflict_callback(ctl->alerts.summary, alerts_v2_conflict_callback, &ctl);
541 + dictionary_register_delete_callback(ctl->alerts.summary, alerts_v2_delete_callback, &ctl);
542 +
543 + ctl->alerts.by_type = dictionary_create_advanced(
544 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
545 + NULL,
546 + sizeof(struct alert_by_x_entry));
547 +
548 + dictionary_register_insert_callback(ctl->alerts.by_type, alerts_by_x_insert_callback, NULL);
549 + dictionary_register_conflict_callback(ctl->alerts.by_type, alerts_by_x_conflict_callback, NULL);
550 +
551 + ctl->alerts.by_component = dictionary_create_advanced(
552 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
553 + NULL,
554 + sizeof(struct alert_by_x_entry));
555 +
556 + dictionary_register_insert_callback(ctl->alerts.by_component, alerts_by_x_insert_callback, NULL);
557 + dictionary_register_conflict_callback(ctl->alerts.by_component, alerts_by_x_conflict_callback, NULL);
558 +
559 + ctl->alerts.by_classification = dictionary_create_advanced(
560 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
561 + NULL,
562 + sizeof(struct alert_by_x_entry));
563 +
564 + dictionary_register_insert_callback(ctl->alerts.by_classification, alerts_by_x_insert_callback, NULL);
565 + dictionary_register_conflict_callback(ctl->alerts.by_classification, alerts_by_x_conflict_callback, NULL);
566 +
567 + ctl->alerts.by_recipient = dictionary_create_advanced(
568 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
569 + NULL,
570 + sizeof(struct alert_by_x_entry));
571 +
572 + dictionary_register_insert_callback(ctl->alerts.by_recipient, alerts_by_x_insert_callback, NULL);
573 + dictionary_register_conflict_callback(ctl->alerts.by_recipient, alerts_by_x_conflict_callback, NULL);
574 +
575 + ctl->alerts.by_module = dictionary_create_advanced(
576 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
577 + NULL,
578 + sizeof(struct alert_by_x_entry));
579 +
580 + dictionary_register_insert_callback(ctl->alerts.by_module, alerts_by_x_insert_callback, NULL);
581 + dictionary_register_conflict_callback(ctl->alerts.by_module, alerts_by_x_conflict_callback, NULL);
582 +
583 + if(ctl->options & (CONTEXTS_OPTION_ALERTS_WITH_INSTANCES | CONTEXTS_OPTION_ALERTS_WITH_VALUES)) {
584 + ctl->alerts.alert_instances = dictionary_create_advanced(
585 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
586 + NULL, sizeof(struct sql_alert_instance_v2_entry));
587 +
588 + dictionary_register_insert_callback(ctl->alerts.alert_instances, alert_instances_v2_insert_callback, &ctl);
589 + dictionary_register_conflict_callback(ctl->alerts.alert_instances, alert_instances_v2_conflict_callback, &ctl);
590 + dictionary_register_delete_callback(ctl->alerts.alert_instances, alert_instances_delete_callback, &ctl);
591 + }
592 +
593 + return true;
594 +}
595 +
596 +void rrdcontexts_v2_alerts_cleanup(struct rrdcontext_to_json_v2_data *ctl) {
597 + dictionary_destroy(ctl->alerts.summary);
598 + dictionary_destroy(ctl->alerts.alert_instances);
599 + dictionary_destroy(ctl->alerts.by_type);
600 + dictionary_destroy(ctl->alerts.by_component);
601 + dictionary_destroy(ctl->alerts.by_classification);
602 + dictionary_destroy(ctl->alerts.by_recipient);
603 + dictionary_destroy(ctl->alerts.by_module);
604 +}
src/database/contexts/api_v2_contexts_alerts.h new
+52
@@ -0,0 +1,52 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_API_V2_CONTEXTS_ALERTS_H
4 +#define NETDATA_API_V2_CONTEXTS_ALERTS_H
5 +
6 +#include "internal.h"
7 +#include "api_v2_contexts.h"
8 +
9 +struct alert_transitions_callback_data {
10 + struct rrdcontext_to_json_v2_data *ctl;
11 + BUFFER *wb;
12 + bool debug;
13 + bool only_one_config;
14 +
15 + struct {
16 + SIMPLE_PATTERN *pattern;
17 + DICTIONARY *dict;
18 + } facets[ATF_TOTAL_ENTRIES];
19 +
20 + uint32_t max_items_to_return;
21 + uint32_t items_to_return;
22 +
23 + uint32_t items_evaluated;
24 + uint32_t items_matched;
25 +
26 +
27 + struct sql_alert_transition_fixed_size *base; // double linked list - last item is base->prev
28 + struct sql_alert_transition_fixed_size *last_added; // the last item added, not the last of the list
29 +
30 + struct {
31 + size_t first;
32 + size_t skips_before;
33 + size_t skips_after;
34 + size_t backwards;
35 + size_t forwards;
36 + size_t prepend;
37 + size_t append;
38 + size_t shifts;
39 + } operations;
40 +
41 + uint32_t configs_added;
42 +};
43 +
44 +void contexts_v2_alerts_to_json(BUFFER *wb, struct rrdcontext_to_json_v2_data *ctl, bool debug);
45 +bool rrdcontext_matches_alert(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc);
46 +void contexts_v2_alert_config_to_json_from_sql_alert_config_data(struct sql_alert_config_data *t, void *data);
47 +void contexts_v2_alert_transitions_to_json(BUFFER *wb, struct rrdcontext_to_json_v2_data *ctl, bool debug);
48 +
49 +bool rrdcontexts_v2_init_alert_dictionaries(struct rrdcontext_to_json_v2_data *ctl, struct api_v2_contexts_request *req);
50 +void rrdcontexts_v2_alerts_cleanup(struct rrdcontext_to_json_v2_data *ctl);
51 +
52 +#endif //NETDATA_API_V2_CONTEXTS_ALERTS_H
src/database/contexts/query_scope.c
+2 -2
@@ -18,8 +18,8 @@ ssize_t query_scope_foreach_host(SIMPLE_PATTERN *scope_hosts_sp, SIMPLE_PATTERN
18 uint64_t t_hash = 0;
19
20 dfe_start_read(rrdhost_root_index, host) {
21 - if(host->node_id)
22 - uuid_unparse_lower(*host->node_id, host_node_id_str);
21 + if(!uuid_is_null(host->node_id))
22 + uuid_unparse_lower(host->node_id, host_node_id_str);
23 else
24 host_node_id_str[0] = '\0';
25
src/database/contexts/query_target.c
+5 -5
@@ -897,9 +897,9 @@ static ssize_t query_node_add(void *data, RRDHOST *host, bool queryable_host) {
897 QUERY_TARGET *qt = qtl->qt;
898 QUERY_NODE *qn = query_node_allocate(qt, host);
899
900 - if(host->node_id) {
900 + if(!uuid_is_null(host->node_id)) {
901 if(!qtl->host_node_id_str[0])
902 - uuid_unparse_lower(*host->node_id, qn->node_id);
902 + uuid_unparse_lower(host->node_id, qn->node_id);
903 else
904 memcpy(qn->node_id, qtl->host_node_id_str, sizeof(qn->node_id));
905 }
@@ -958,7 +958,7 @@ static ssize_t query_node_add(void *data, RRDHOST *host, bool queryable_host) {
958
959 void query_target_generate_name(QUERY_TARGET *qt) {
960 char options_buffer[100 + 1];
961 - web_client_api_request_v1_data_options_to_string(options_buffer, 100, qt->request.options);
961 + web_client_api_request_data_vX_options_to_string(options_buffer, 100, qt->request.options);
962
963 char resampling_buffer[20 + 1] = "";
964 if(qt->request.resampling_time > 1)
@@ -1120,8 +1120,8 @@ QUERY_TARGET *query_target_create(QUERY_TARGET_REQUEST *qtr) {
1120 }
1121
1122 if(host) {
1123 - if(host->node_id)
1124 - uuid_unparse_lower(*host->node_id, qtl.host_node_id_str);
1123 + if(!uuid_is_null(host->node_id))
1124 + uuid_unparse_lower(host->node_id, qtl.host_node_id_str);
1125 else
1126 qtl.host_node_id_str[0] = '\0';
1127
src/database/contexts/rrdcontext.c
+8 -15
@@ -198,21 +198,16 @@ int rrdcontext_foreach_instance_with_rrdset_in_context(RRDHOST *host, const char
198 // ----------------------------------------------------------------------------
199 // ACLK interface
200
201 -static bool rrdhost_check_our_claim_id(const char *claim_id) {
202 - if(!localhost->aclk_state.claimed_id) return false;
203 - return (strcasecmp(claim_id, localhost->aclk_state.claimed_id) == 0) ? true : false;
204 -}
205 -
201 void rrdcontext_hub_checkpoint_command(void *ptr) {
202 struct ctxs_checkpoint *cmd = ptr;
203
209 - if(!rrdhost_check_our_claim_id(cmd->claim_id)) {
204 + if(!claim_id_matches(cmd->claim_id)) {
205 + CLAIM_ID claim_id = claim_id_get();
206 nd_log(NDLS_DAEMON, NDLP_WARNING,
207 "RRDCONTEXT: received checkpoint command for claim_id '%s', node id '%s', "
208 "but this is not our claim id. Ours '%s', received '%s'. Ignoring command.",
209 cmd->claim_id, cmd->node_id,
214 - localhost->aclk_state.claimed_id?localhost->aclk_state.claimed_id:"NOT SET",
215 - cmd->claim_id);
210 + claim_id.str, cmd->claim_id);
211
212 return;
213 }
@@ -245,10 +240,9 @@ void rrdcontext_hub_checkpoint_command(void *ptr) {
240 "Sending snapshot of all contexts.",
241 cmd->version_hash, rrdhost_hostname(host), our_version_hash);
242
248 -#ifdef ENABLE_ACLK
243 // prepare the snapshot
244 char uuid[UUID_STR_LEN];
251 - uuid_unparse_lower(*host->node_id, uuid);
245 + uuid_unparse_lower(host->node_id, uuid);
246 contexts_snapshot_t bundle = contexts_snapshot_new(cmd->claim_id, uuid, our_version_hash);
247
248 // do a deep scan on every metric of the host to make sure all our data are updated
@@ -262,7 +256,6 @@ void rrdcontext_hub_checkpoint_command(void *ptr) {
256
257 // send it
258 aclk_send_contexts_snapshot(bundle);
265 -#endif
259 }
260
261 nd_log(NDLS_DAEMON, NDLP_DEBUG,
@@ -271,7 +264,7 @@ void rrdcontext_hub_checkpoint_command(void *ptr) {
264
265 rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS);
266 char node_str[UUID_STR_LEN];
274 - uuid_unparse_lower(*host->node_id, node_str);
267 + uuid_unparse_lower(host->node_id, node_str);
268 nd_log(NDLS_ACCESS, NDLP_DEBUG,
269 "ACLK REQ [%s (%s)]: STREAM CONTEXTS ENABLED",
270 node_str, rrdhost_hostname(host));
@@ -280,13 +273,13 @@ void rrdcontext_hub_checkpoint_command(void *ptr) {
273 void rrdcontext_hub_stop_streaming_command(void *ptr) {
274 struct stop_streaming_ctxs *cmd = ptr;
275
283 - if(!rrdhost_check_our_claim_id(cmd->claim_id)) {
276 + if(!claim_id_matches(cmd->claim_id)) {
277 + CLAIM_ID claim_id = claim_id_get();
278 nd_log(NDLS_DAEMON, NDLP_WARNING,
279 "RRDCONTEXT: received stop streaming command for claim_id '%s', node id '%s', "
280 "but this is not our claim id. Ours '%s', received '%s'. Ignoring command.",
281 cmd->claim_id, cmd->node_id,
288 - localhost->aclk_state.claimed_id?localhost->aclk_state.claimed_id:"NOT SET",
289 - cmd->claim_id);
282 + claim_id.str, cmd->claim_id);
283
284 return;
285 }
src/database/contexts/rrdcontext.h
+2 -2
@@ -623,10 +623,10 @@ struct api_v2_contexts_request {
623 char *contexts;
624 char *q;
625
626 - CONTEXTS_V2_OPTIONS options;
626 + CONTEXTS_OPTIONS options;
627
628 struct {
629 - CONTEXTS_V2_ALERT_STATUS status;
629 + CONTEXTS_ALERT_STATUS status;
630 char *alert;
631 char *transition;
632 uint32_t last;
src/database/contexts/worker.c
+5 -12
@@ -818,7 +818,6 @@ void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe_unused
818 rc->hub.last_time_s = rrd_flag_is_collected(rc) ? 0 : rc->last_time_s;
819 rc->hub.deleted = rrd_flag_is_deleted(rc) ? true : false;
820
821 -#ifdef ENABLE_ACLK
821 struct context_updated message = {
822 .id = rc->hub.id,
823 .version = rc->hub.version,
@@ -840,7 +839,6 @@ void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe_unused
839 else
840 contexts_updated_add_ctx_update(bundle, &message);
841 }
843 -#endif
842
843 // store it to SQL
844
@@ -956,7 +954,7 @@ static void rrdcontext_dequeue_from_hub_queue(RRDCONTEXT *rc) {
954 static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now_ut) {
955
956 // check if we have received a streaming command for this host
959 - if(!host->node_id || !rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS) || !aclk_connected || !host->rrdctx.hub_queue)
957 + if(uuid_is_null(host->node_id) || !rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS) || !aclk_online_for_contexts() || !host->rrdctx.hub_queue)
958 return;
959
960 // check if there are queued items to send
@@ -975,9 +973,9 @@ static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now
973
974 worker_is_busy(WORKER_JOB_QUEUED);
975 usec_t dispatch_ut = rrdcontext_calculate_queued_dispatch_time_ut(rc, now_ut);
978 - char *claim_id = get_agent_claimid();
976 + CLAIM_ID claim_id = claim_id_get();
977
980 - if(unlikely(now_ut >= dispatch_ut) && claim_id) {
978 + if(unlikely(now_ut >= dispatch_ut) && claim_id_is_set(claim_id)) {
979 worker_is_busy(WORKER_JOB_CHECK);
980
981 rrdcontext_lock(rc);
@@ -985,15 +983,13 @@ static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now
983 if(check_if_cloud_version_changed_unsafe(rc, true)) {
984 worker_is_busy(WORKER_JOB_SEND);
985
988 -#ifdef ENABLE_ACLK
986 if(!bundle) {
987 // prepare the bundle to send the messages
988 char uuid[UUID_STR_LEN];
992 - uuid_unparse_lower(*host->node_id, uuid);
989 + uuid_unparse_lower(host->node_id, uuid);
990
994 - bundle = contexts_updated_new(claim_id, uuid, 0, now_ut);
991 + bundle = contexts_updated_new(claim_id.str, uuid, 0, now_ut);
992 }
996 -#endif
993 // update the hub data of the context, give a new version, pack the message
994 // and save an update to SQL
995 rrdcontext_message_send_unsafe(rc, false, bundle);
@@ -1030,11 +1026,9 @@ static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now
1026 else
1027 rrdcontext_unlock(rc);
1028 }
1033 - freez(claim_id);
1029 }
1030 dfe_done(rc);
1031
1037 -#ifdef ENABLE_ACLK
1032 if(service_running(SERVICE_CONTEXT) && bundle) {
1033 // we have a bundle to send messages
1034
@@ -1046,7 +1040,6 @@ static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now
1040 }
1041 else if(bundle)
1042 contexts_updated_delete(bundle);
1049 -#endif
1043
1044 }
1045
src/database/rrd.h
+11 -11
@@ -1188,7 +1188,7 @@ struct rrdhost {
1188 struct rrdhost_system_info *system_info; // information collected from the host environment
1189
1190 // ------------------------------------------------------------------------
1191 - // streaming of data to remote hosts - rrdpush sender
1191 + // streaming of data to remote hosts - rrdpush
1192
1193 struct {
1194 struct {
@@ -1204,6 +1204,10 @@ struct rrdhost {
1204
1205 uint32_t last_used; // the last slot we used for a chart (increments only)
1206 } pluginsd_chart_slots;
1207 +
1208 + char *destination; // where to send metrics to
1209 + char *api_key; // the api key at the receiving netdata
1210 + SIMPLE_PATTERN *charts_matching; // pattern to match the charts to be sent
1211 } send;
1212
1213 struct {
@@ -1215,11 +1219,8 @@ struct rrdhost {
1219 } receive;
1220 } rrdpush;
1221
1218 - char *rrdpush_send_destination; // where to send metrics to
1219 - char *rrdpush_send_api_key; // the api key at the receiving netdata
1222 struct rrdpush_destinations *destinations; // a linked list of possible destinations
1223 struct rrdpush_destinations *destination; // the current destination from the above list
1222 - SIMPLE_PATTERN *rrdpush_send_charts_matching; // pattern to match the charts to be sent
1224
1225 int32_t rrdpush_last_receiver_exit_reason;
1226 time_t rrdpush_seconds_to_replicate; // max time we want to replicate from the child
@@ -1247,7 +1248,7 @@ struct rrdhost {
1248 int connected_children_count; // number of senders currently streaming
1249
1250 struct receiver_state *receiver;
1250 - netdata_mutex_t receiver_lock;
1251 + SPINLOCK receiver_lock;
1252 int trigger_chart_obsoletion_check; // set when child connects, will instruct parent to
1253 // trigger a check for obsoleted charts since previous connect
1254
@@ -1307,10 +1308,12 @@ struct rrdhost {
1308 } retention;
1309
1310 nd_uuid_t host_uuid; // Global GUID for this host
1310 - nd_uuid_t *node_id; // Cloud node_id
1311 + nd_uuid_t node_id; // Cloud node_id
1312
1312 - netdata_mutex_t aclk_state_lock;
1313 - aclk_rrdhost_state aclk_state;
1313 + struct {
1314 + ND_UUID claim_id_of_origin;
1315 + ND_UUID claim_id_of_parent;
1316 + } aclk;
1317
1318 struct rrdhost *next;
1319 struct rrdhost *prev;
@@ -1325,9 +1328,6 @@ extern RRDHOST *localhost;
1328 #define rrdhost_program_name(host) string2str((host)->program_name)
1329 #define rrdhost_program_version(host) string2str((host)->program_version)
1330
1328 -#define rrdhost_aclk_state_lock(host) netdata_mutex_lock(&((host)->aclk_state_lock))
1329 -#define rrdhost_aclk_state_unlock(host) netdata_mutex_unlock(&((host)->aclk_state_lock))
1330 -
1331 #define rrdhost_receiver_replicating_charts(host) (__atomic_load_n(&((host)->rrdpush_receiver_replicating_charts), __ATOMIC_RELAXED))
1332 #define rrdhost_receiver_replicating_charts_plus_one(host) (__atomic_add_fetch(&((host)->rrdpush_receiver_replicating_charts), 1, __ATOMIC_RELAXED))
1333 #define rrdhost_receiver_replicating_charts_minus_one(host) (__atomic_sub_fetch(&((host)->rrdpush_receiver_replicating_charts), 1, __ATOMIC_RELAXED))
src/database/rrdfunctions-exporters.c
+4 -4
@@ -60,7 +60,7 @@ static void functions2json(DICTIONARY *functions, BUFFER *wb) {
60 struct rrd_host_function *t;
61 dfe_start_read(functions, t) {
62 if (!rrd_collector_running(t->collector)) continue;
63 - if(t->options & RRD_FUNCTION_DYNCFG) continue;
63 + if(t->options & (RRD_FUNCTION_DYNCFG|RRD_FUNCTION_HIDDEN)) continue;
64
65 buffer_json_member_add_object(wb, t_dfe.name);
66 {
@@ -99,7 +99,7 @@ void host_functions2json(RRDHOST *host, BUFFER *wb) {
99 struct rrd_host_function *t;
100 dfe_start_read(host->functions, t) {
101 if(!rrd_collector_running(t->collector)) continue;
102 - if(t->options & RRD_FUNCTION_DYNCFG) continue;
102 + if(t->options & (RRD_FUNCTION_DYNCFG|RRD_FUNCTION_HIDDEN)) continue;
103
104 buffer_json_member_add_object(wb, t_dfe.name);
105 {
@@ -130,7 +130,7 @@ void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst,
130 struct rrd_host_function *t;
131 dfe_start_read(rrdset_functions_view, t) {
132 if(!rrd_collector_running(t->collector)) continue;
133 - if(t->options & RRD_FUNCTION_DYNCFG) continue;
133 + if(t->options & (RRD_FUNCTION_DYNCFG|RRD_FUNCTION_HIDDEN)) continue;
134
135 dictionary_set(dst, t_dfe.name, value, value_size);
136 }
@@ -144,7 +144,7 @@ void host_functions_to_dict(RRDHOST *host, DICTIONARY *dst, void *value, size_t
144 struct rrd_host_function *t;
145 dfe_start_read(host->functions, t) {
146 if(!rrd_collector_running(t->collector)) continue;
147 - if(t->options & RRD_FUNCTION_DYNCFG) continue;
147 + if(t->options & (RRD_FUNCTION_DYNCFG|RRD_FUNCTION_HIDDEN)) continue;
148
149 if(help)
150 *help = t->help;
src/database/rrdfunctions-inflight.c
+14 -8
@@ -398,7 +398,7 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s,
398 rrd_function_result_callback_t result_cb, void *result_cb_data,
399 rrd_function_progress_cb_t progress_cb, void *progress_cb_data,
400 rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data,
401 - BUFFER *payload, const char *source) {
401 + BUFFER *payload, const char *source, bool hidden) {
402
403 int code;
404 char sanitized_cmd[PLUGINSD_LINE_MAX + 1];
@@ -436,15 +436,21 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s,
436
437 struct rrd_host_function *rdcf = dictionary_acquired_item_value(host_function_acquired);
438
439 - if(!http_access_user_has_enough_access_level_for_endpoint(user_access, rdcf->access)) {
439 + if((rdcf->options & RRD_FUNCTION_HIDDEN) && !hidden) {
440 + code = rrd_call_function_error(result_wb,
441 + "You cannot access a hidden function like this. ",
442 + HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(user_access));
443 + dictionary_acquired_item_release(host->functions, host_function_acquired);
444
441 - if(!aclk_connected)
442 - code = rrd_call_function_error(result_wb,
443 - "This Netdata must be connected to Netdata Cloud for Single-Sign-On (SSO) "
444 - "access this feature. Claim this Netdata to Netdata Cloud to enable access.",
445 - HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(user_access));
445 + if(result_cb)
446 + result_cb(result_wb, code, result_cb_data);
447 +
448 + return code;
449 + }
450 +
451 + if(!http_access_user_has_enough_access_level_for_endpoint(user_access, rdcf->access)) {
452
447 - else if((rdcf->access & HTTP_ACCESS_SIGNED_ID) && !(user_access & HTTP_ACCESS_SIGNED_ID))
453 + if((rdcf->access & HTTP_ACCESS_SIGNED_ID) && !(user_access & HTTP_ACCESS_SIGNED_ID))
454 code = rrd_call_function_error(result_wb,
455 "You need to be authenticated via Netdata Cloud Single-Sign-On (SSO) "
456 "to access this feature. Sign-in on this dashboard, "
src/database/rrdfunctions-inline.c
+1 -1
@@ -17,7 +17,7 @@ static int rrd_function_run_inline(struct rrd_function_execute *rfe, void *data)
17 if(rfe->is_cancelled.cb && rfe->is_cancelled.cb(rfe->is_cancelled.data))
18 code = HTTP_RESP_CLIENT_CLOSED_REQUEST;
19 else
20 - code = fi->cb(rfe->result.wb, rfe->function);
20 + code = fi->cb(rfe->result.wb, rfe->function, rfe->payload, rfe->source);
21
22 if(code == HTTP_RESP_CLIENT_CLOSED_REQUEST || (rfe->is_cancelled.cb && rfe->is_cancelled.cb(rfe->is_cancelled.data))) {
23 buffer_flush(rfe->result.wb);
src/database/rrdfunctions-inline.h
+1 -1
@@ -5,7 +5,7 @@
5
6 #include "rrd.h"
7
8 -typedef int (*rrd_function_execute_inline_cb_t)(BUFFER *wb, const char *function);
8 +typedef int (*rrd_function_execute_inline_cb_t)(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
9
10 void rrd_function_add_inline(RRDHOST *host, RRDSET *st, const char *name, int timeout, int priority,
11 const char *help, const char *tags,
src/database/rrdfunctions-internals.h
+1
@@ -11,6 +11,7 @@ typedef enum __attribute__((packed)) {
11 RRD_FUNCTION_LOCAL = (1 << 0),
12 RRD_FUNCTION_GLOBAL = (1 << 1),
13 RRD_FUNCTION_DYNCFG = (1 << 2),
14 + RRD_FUNCTION_HIDDEN = (1 << 3),
15
16 // this is 8-bit
17 } RRD_FUNCTION_OPTIONS;
src/database/rrdfunctions-progress.c deleted
-8
@@ -1,8 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#include "rrdfunctions-progress.h"
4 -
5 -int rrdhost_function_progress(BUFFER *wb, const char *function __maybe_unused) {
6 - return progress_function_result(wb, rrdhost_hostname(localhost));
7 -}
8 -
src/database/rrdfunctions-progress.h deleted
-10
@@ -1,10 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#ifndef NETDATA_RRDFUNCTIONS_PROGRESS_H
4 -#define NETDATA_RRDFUNCTIONS_PROGRESS_H
5 -
6 -#include "rrd.h"
7 -
8 -int rrdhost_function_progress(BUFFER *wb, const char *function __maybe_unused);
9 -
10 -#endif //NETDATA_RRDFUNCTIONS_PROGRESS_H
src/database/rrdfunctions-streaming.h deleted
-12
@@ -1,12 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#ifndef NETDATA_RRDFUNCTIONS_STREAMING_H
4 -#define NETDATA_RRDFUNCTIONS_STREAMING_H
5 -
6 -#include "rrd.h"
7 -
8 -#define RRDFUNCTIONS_STREAMING_HELP "Streaming status for parents and children."
9 -
10 -int rrdhost_function_streaming(BUFFER *wb, const char *function);
11 -
12 -#endif //NETDATA_RRDFUNCTIONS_STREAMING_H
src/database/rrdfunctions.c
+14 -12
@@ -225,6 +225,10 @@ void rrd_functions_host_destroy(RRDHOST *host) {
225
226 // ----------------------------------------------------------------------------
227
228 +static inline bool is_function_hidden(const char *name, const char *tags) {
229 + return (name && name[0] == '_' && name[1] == '_') || (tags && strstr(tags, RRDFUNCTIONS_TAG_HIDDEN) != NULL);
230 +}
231 +
232 static inline bool is_function_dyncfg(const char *name) {
233 if(!name || !*name)
234 return false;
@@ -239,6 +243,15 @@ static inline bool is_function_dyncfg(const char *name) {
243 return false;
244 }
245
246 +static inline RRD_FUNCTION_OPTIONS get_function_options(RRDSET *st, const char *name, const char *tags) {
247 + if(is_function_dyncfg(name))
248 + return RRD_FUNCTION_DYNCFG;
249 +
250 + RRD_FUNCTION_OPTIONS options = st ? RRD_FUNCTION_LOCAL : RRD_FUNCTION_GLOBAL;
251 +
252 + return options | (is_function_hidden(name, tags) ? RRD_FUNCTION_HIDDEN : 0);
253 +}
254 +
255 void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout, int priority,
256 const char *help, const char *tags,
257 HTTP_ACCESS access, bool sync,
@@ -263,7 +276,7 @@ void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout,
276 struct rrd_host_function tmp = {
277 .sync = sync,
278 .timeout = timeout,
266 - .options = st ? RRD_FUNCTION_LOCAL: (is_function_dyncfg(name) ? RRD_FUNCTION_DYNCFG : RRD_FUNCTION_GLOBAL),
279 + .options = get_function_options(st, name, tags),
280 .access = access,
281 .execute_cb = execute_cb,
282 .execute_cb_data = execute_cb_data,
@@ -294,17 +307,6 @@ void rrd_function_del(RRDHOST *host, RRDSET *st, const char *name) {
307 dictionary_garbage_collect(host->functions);
308 }
309
297 -int rrd_call_function_error(BUFFER *wb, const char *msg, int code) {
298 - char buffer[PLUGINSD_LINE_MAX];
299 - json_escape_string(buffer, msg, PLUGINSD_LINE_MAX);
300 -
301 - buffer_flush(wb);
302 - buffer_sprintf(wb, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer);
303 - wb->content_type = CT_APPLICATION_JSON;
304 - buffer_no_cacheable(wb);
305 - return code;
306 -}
307 -
310 int rrd_functions_find_by_name(RRDHOST *host, BUFFER *wb, const char *name, size_t key_length, const DICTIONARY_ITEM **item) {
311 char buffer[MAX_FUNCTION_LENGTH + 1];
312 strncpyz(buffer, name, sizeof(buffer) - 1);
src/database/rrdfunctions.h
+2 -5
@@ -7,6 +7,7 @@
7 #include "libnetdata/libnetdata.h"
8
9 #define RRDFUNCTIONS_PRIORITY_DEFAULT 100
10 +#define RRDFUNCTIONS_TAG_HIDDEN "hidden"
11
12 #define RRDFUNCTIONS_TIMEOUT_EXTENSION_UT (1 * USEC_PER_SEC)
13
@@ -79,9 +80,7 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s,
80 rrd_function_result_callback_t result_cb, void *result_cb_data,
81 rrd_function_progress_cb_t progress_cb, void *progress_cb_data,
82 rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data,
82 - BUFFER *payload, const char *source);
83 -
84 -int rrd_call_function_error(BUFFER *wb, const char *msg, int code);
83 + BUFFER *payload, const char *source, bool hidden);
84
85 bool rrd_function_available(RRDHOST *host, const char *function);
86
@@ -90,7 +89,5 @@ bool rrd_function_has_this_original_result_callback(nd_uuid_t *transaction, rrd_
89 #include "rrdfunctions-inline.h"
90 #include "rrdfunctions-inflight.h"
91 #include "rrdfunctions-exporters.h"
93 -#include "rrdfunctions-streaming.h"
94 -#include "rrdfunctions-progress.h"
92
93 #endif // NETDATA_RRDFUNCTIONS_H
src/database/rrdhost.c
+22 -46
@@ -42,7 +42,7 @@ RRDHOST *find_host_by_node_id(char *node_id) {
42
43 RRDHOST *host, *ret = NULL;
44 dfe_start_read(rrdhost_root_index, host) {
45 - if (host->node_id && uuid_eq(*host->node_id, node_uuid)) {
45 + if (uuid_eq(host->node_id, node_uuid)) {
46 ret = host;
47 break;
48 }
@@ -244,15 +244,13 @@ static void rrdhost_initialize_rrdpush_sender(RRDHOST *host,
244
245 rrdhost_streaming_sender_structures_init(host);
246
247 -#ifdef ENABLE_HTTPS
247 host->sender->ssl = NETDATA_SSL_UNSET_CONNECTION;
249 -#endif
248
251 - host->rrdpush_send_destination = strdupz(rrdpush_destination);
249 + host->rrdpush.send.destination = strdupz(rrdpush_destination);
250 rrdpush_destinations_init(host);
251
254 - host->rrdpush_send_api_key = strdupz(rrdpush_api_key);
255 - host->rrdpush_send_charts_matching = simple_pattern_create(rrdpush_send_charts_matching, NULL,
252 + host->rrdpush.send.api_key = strdupz(rrdpush_api_key);
253 + host->rrdpush.send.charts_matching = simple_pattern_create(rrdpush_send_charts_matching, NULL,
254 SIMPLE_PATTERN_EXACT, true);
255
256 rrdhost_option_set(host, RRDHOST_OPTION_SENDER_ENABLED);
@@ -383,8 +381,7 @@ static RRDHOST *rrdhost_create(
381 host->rrd_history_entries = align_entries_to_pagesize(memory_mode, entries);
382 host->health.health_enabled = ((memory_mode == RRD_MEMORY_MODE_NONE)) ? 0 : health_enabled;
383
386 - netdata_mutex_init(&host->aclk_state_lock);
387 - netdata_mutex_init(&host->receiver_lock);
384 + spinlock_init(&host->receiver_lock);
385
386 if (likely(!archived)) {
387 rrd_functions_host_init(host);
@@ -535,8 +532,8 @@ static RRDHOST *rrdhost_create(
532 , rrd_memory_mode_name(host->rrd_memory_mode)
533 , host->rrd_history_entries
534 , rrdhost_has_rrdpush_sender_enabled(host)?"enabled":"disabled"
538 - , host->rrdpush_send_destination?host->rrdpush_send_destination:""
539 - , host->rrdpush_send_api_key?host->rrdpush_send_api_key:""
535 + , host->rrdpush.send.destination?host->rrdpush.send.destination:""
536 + , host->rrdpush.send.api_key?host->rrdpush.send.api_key:""
537 , host->health.health_enabled?"enabled":"disabled"
538 , host->cache_dir
539 , string2str(host->health.health_default_exec)
@@ -1027,12 +1024,14 @@ void dbengine_init(char *hostname) {
1024 #endif
1025 }
1026
1027 +void api_v1_management_init(void);
1028 +
1029 int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unittest) {
1030 rrdhost_init();
1031
1032 if (unlikely(sql_init_meta_database(DB_CHECK_NONE, system_info ? 0 : 1))) {
1033 if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) {
1035 - set_late_global_environment(system_info);
1034 + set_late_analytics_variables(system_info);
1035 fatal("Failed to initialize SQLite");
1036 }
1037
@@ -1112,26 +1111,15 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unitt
1111
1112 dyncfg_host_init(localhost);
1113
1115 - if(!unittest) {
1114 + if(!unittest)
1115 health_plugin_init();
1117 - }
1118 -
1119 - // we register this only on localhost
1120 - // for the other nodes, the origin server should register it
1121 - rrd_function_add_inline(localhost, NULL, "streaming", 10,
1122 - RRDFUNCTIONS_PRIORITY_DEFAULT + 1, RRDFUNCTIONS_STREAMING_HELP, "top",
1123 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
1124 - rrdhost_function_streaming);
1116
1126 - rrd_function_add_inline(localhost, NULL, "netdata-api-calls", 10,
1127 - RRDFUNCTIONS_PRIORITY_DEFAULT + 2, RRDFUNCTIONS_PROGRESS_HELP, "top",
1128 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
1129 - rrdhost_function_progress);
1117 + global_functions_add();
1118
1119 if (likely(system_info)) {
1120 detect_machine_guid_change(&localhost->host_uuid);
1121 sql_aclk_sync_init();
1134 - web_client_api_v1_management_init();
1122 + api_v1_management_init();
1123 }
1124
1125 return 0;
@@ -1284,9 +1272,6 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) {
1272 // ------------------------------------------------------------------------
1273 // free it
1274
1287 - pthread_mutex_destroy(&host->aclk_state_lock);
1288 - freez(host->aclk_state.claimed_id);
1289 - freez(host->aclk_state.prev_claimed_id);
1275 rrdlabels_destroy(host->rrdlabels);
1276 string_freez(host->os);
1277 string_freez(host->timezone);
@@ -1295,14 +1280,13 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) {
1280 string_freez(host->program_version);
1281 rrdhost_system_info_free(host->system_info);
1282 freez(host->cache_dir);
1298 - freez(host->rrdpush_send_api_key);
1299 - freez(host->rrdpush_send_destination);
1283 + freez(host->rrdpush.send.api_key);
1284 + freez(host->rrdpush.send.destination);
1285 rrdpush_destinations_free(host);
1286 string_freez(host->health.health_default_exec);
1287 string_freez(host->health.health_default_recipient);
1288 string_freez(host->registry_hostname);
1304 - simple_pattern_free(host->rrdpush_send_charts_matching);
1305 - freez(host->node_id);
1289 + simple_pattern_free(host->rrdpush.send.charts_matching);
1290
1291 rrd_functions_host_destroy(host);
1292 rrdvariables_destroy(host->rrdvars);
@@ -1441,8 +1425,8 @@ static void rrdhost_load_auto_labels(void) {
1425 rrdlabels_add(labels, "_hostname", string2str(localhost->hostname), RRDLABEL_SRC_AUTO);
1426 rrdlabels_add(labels, "_os", string2str(localhost->os), RRDLABEL_SRC_AUTO);
1427
1444 - if (localhost->rrdpush_send_destination)
1445 - rrdlabels_add(labels, "_streams_to", localhost->rrdpush_send_destination, RRDLABEL_SRC_AUTO);
1428 + if (localhost->rrdpush.send.destination)
1429 + rrdlabels_add(labels, "_streams_to", localhost->rrdpush.send.destination, RRDLABEL_SRC_AUTO);
1430 }
1431
1432 void rrdhost_set_is_parent_label(void) {
@@ -1453,11 +1437,7 @@ void rrdhost_set_is_parent_label(void) {
1437 rrdlabels_add(labels, "_is_parent", (count) ? "true" : "false", RRDLABEL_SRC_AUTO);
1438
1439 //queue a node info
1456 -#ifdef ENABLE_ACLK
1457 - if (netdata_cloud_enabled) {
1458 - aclk_queue_node_info(localhost, false);
1459 - }
1460 -#endif
1440 + aclk_queue_node_info(localhost, false);
1441 }
1442 }
1443
@@ -1498,7 +1478,7 @@ static void rrdhost_load_kubernetes_labels(void) {
1478 if(!instance) return;
1479
1480 char buffer[1000 + 1];
1501 - while (fgets(buffer, 1000, instance->child_stdout_fp) != NULL)
1481 + while (fgets(buffer, 1000, spawn_popen_stdout(instance)) != NULL)
1482 rrdlabels_add_pair(localhost->rrdlabels, buffer, RRDLABEL_SRC_AUTO|RRDLABEL_SRC_K8S);
1483
1484 // Non-zero exit code means that all the script output is error messages. We've shown already any message that didn't include a ':'
@@ -1740,7 +1720,7 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) {
1720 s->ingest.since = MAX(host->child_connect_time, host->child_disconnected_time);
1721 s->ingest.reason = (online) ? STREAM_HANDSHAKE_NEVER : host->rrdpush_last_receiver_exit_reason;
1722
1743 - netdata_mutex_lock(&host->receiver_lock);
1723 + spinlock_lock(&host->receiver_lock);
1724 s->ingest.hops = (host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1);
1725 bool has_receiver = false;
1726 if (host->receiver) {
@@ -1751,11 +1731,9 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) {
1731
1732 s->ingest.capabilities = host->receiver->capabilities;
1733 s->ingest.peers = socket_peers(host->receiver->fd);
1754 -#ifdef ENABLE_HTTPS
1734 s->ingest.ssl = SSL_connection(&host->receiver->ssl);
1756 -#endif
1735 }
1758 - netdata_mutex_unlock(&host->receiver_lock);
1736 + spinlock_unlock(&host->receiver_lock);
1737
1738 if (online) {
1739 if(s->db.status == RRDHOST_DB_STATUS_INITIALIZING)
@@ -1812,9 +1790,7 @@ void rrdhost_status(RRDHOST *host, time_t now, RRDHOST_STATUS *s) {
1790
1791 s->stream.since = host->sender->last_state_since_t;
1792 s->stream.peers = socket_peers(host->sender->rrdpush_sender_socket);
1815 -#ifdef ENABLE_HTTPS
1793 s->stream.ssl = SSL_connection(&host->sender->ssl);
1817 -#endif
1794
1795 memcpy(s->stream.sent_bytes_on_this_connection_per_type,
1796 host->sender->sent_bytes_on_this_connection_per_type,
src/database/sqlite/sqlite_aclk.c
+8 -20
@@ -20,7 +20,6 @@ void sanity_check(void) {
20 BUILD_BUG_ON(WORKER_UTILIZATION_MAX_JOB_TYPES < ACLK_MAX_ENUMERATIONS_DEFINED);
21 }
22
23 -#ifdef ENABLE_ACLK
23 static struct aclk_database_cmd aclk_database_deq_cmd(void)
24 {
25 struct aclk_database_cmd ret = { 0 };
@@ -39,7 +38,6 @@ static struct aclk_database_cmd aclk_database_deq_cmd(void)
38
39 return ret;
40 }
42 -#endif
41
42 static void aclk_database_enq_cmd(struct aclk_database_cmd *cmd)
43 {
@@ -165,14 +163,14 @@ static int create_host_callback(void *data, int argc, char **argv, char **column
163
164 #ifdef NETDATA_INTERNAL_CHECKS
165 char node_str[UUID_STR_LEN] = "<none>";
168 - if (likely(host->node_id))
169 - uuid_unparse_lower(*host->node_id, node_str);
170 - internal_error(true, "Adding archived host \"%s\" with GUID \"%s\" node id = \"%s\" ephemeral=%d", rrdhost_hostname(host), host->machine_guid, node_str, is_ephemeral);
166 + if (likely(!uuid_is_null(host->node_id)))
167 + uuid_unparse_lower(host->node_id, node_str);
168 + internal_error(true, "Adding archived host \"%s\" with GUID \"%s\" node id = \"%s\" ephemeral=%d",
169 + rrdhost_hostname(host), host->machine_guid, node_str, is_ephemeral);
170 #endif
171 return 0;
172 }
173
175 -#ifdef ENABLE_ACLK
174
175 #define SQL_SELECT_ACLK_ALERT_TABLES \
176 "SELECT 'DROP '||type||' IF EXISTS '||name||';' FROM sqlite_schema WHERE name LIKE 'aclk_alert_%' AND type IN ('table', 'trigger', 'index')"
@@ -262,7 +260,7 @@ static void timer_cb(uv_timer_t *handle)
260 uv_update_time(handle->loop);
261
262 struct aclk_database_cmd cmd = { 0 };
265 - if (aclk_connected) {
263 + if (aclk_online_for_alerts()) {
264 cmd.opcode = ACLK_DATABASE_PUSH_ALERT;
265 aclk_database_enq_cmd(&cmd);
266 aclk_check_node_info_and_collectors();
@@ -324,7 +322,7 @@ static void aclk_synchronization(void *arg)
322 int live = (host == localhost || host->receiver || !(rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) ? 1 : 0;
323 struct aclk_sync_cfg_t *ahc = host->aclk_config;
324 if (unlikely(!ahc))
327 - create_aclk_config(host, &host->host_uuid, host->node_id);
325 + create_aclk_config(host, &host->host_uuid, &host->node_id);
326 aclk_host_state_update(host, live, 1);
327 break;
328 case ACLK_DATABASE_NODE_UNREGISTER:
@@ -358,13 +356,11 @@ static void aclk_synchronization_init(void)
356 memset(&aclk_sync_config, 0, sizeof(aclk_sync_config));
357 fatal_assert(0 == uv_thread_create(&aclk_sync_config.thread, aclk_synchronization, &aclk_sync_config));
358 }
361 -#endif
359
360 // -------------------------------------------------------------
361
362 void create_aclk_config(RRDHOST *host __maybe_unused, nd_uuid_t *host_uuid __maybe_unused, nd_uuid_t *node_id __maybe_unused)
363 {
367 -#ifdef ENABLE_ACLK
364
365 if (!host || host->aclk_config)
366 return;
@@ -374,16 +370,14 @@ void create_aclk_config(RRDHOST *host __maybe_unused, nd_uuid_t *host_uuid __may
370 uuid_unparse_lower(*node_id, wc->node_id);
371
372 host->aclk_config = wc;
377 - if (node_id && !host->node_id) {
378 - host->node_id = mallocz(sizeof(*host->node_id));
379 - uuid_copy(*host->node_id, *node_id);
373 + if (node_id && uuid_is_null(host->node_id)) {
374 + uuid_copy(host->node_id, *node_id);
375 }
376
377 wc->host = host;
378 wc->stream_alerts = false;
379 time_t now = now_realtime_sec();
380 wc->node_info_send_time = (host == localhost || NULL == localhost) ? now - 25 : now;
386 -#endif
381 }
382
383 #define SQL_FETCH_ALL_HOSTS \
@@ -419,7 +413,6 @@ void sql_aclk_sync_init(void)
413 // Trigger host context load for hosts that have been created
414 metadata_queue_load_host_context(NULL);
415
422 -#ifdef ENABLE_ACLK
416 if (!number_of_children)
417 aclk_queue_node_info(localhost, true);
418
@@ -432,7 +425,6 @@ void sql_aclk_sync_init(void)
425 aclk_synchronization_init();
426
427 netdata_log_info("ACLK sync initialization completed");
435 -#endif
428 }
429
430 static inline void queue_aclk_sync_cmd(enum aclk_database_opcode opcode, const void *param0, const void *param1)
@@ -455,18 +447,14 @@ void aclk_push_alert_config(const char *node_id, const char *config_hash)
447
448 void schedule_node_info_update(RRDHOST *host __maybe_unused)
449 {
458 -#ifdef ENABLE_ACLK
450 if (unlikely(!host))
451 return;
452 queue_aclk_sync_cmd(ACLK_DATABASE_NODE_STATE, host, NULL);
462 -#endif
453 }
454
465 -#ifdef ENABLE_ACLK
455 void unregister_node(const char *machine_guid)
456 {
457 if (unlikely(!machine_guid))
458 return;
459 queue_aclk_sync_cmd(ACLK_DATABASE_NODE_UNREGISTER, strdupz(machine_guid), NULL);
460 }
472 -#endif
src/database/sqlite/sqlite_aclk.h
-7
@@ -15,11 +15,6 @@ static inline int uuid_parse_fix(char *in, nd_uuid_t uuid)
15 return uuid_parse(in, uuid);
16 }
17
18 -static inline int claimed()
19 -{
20 - return localhost->aclk_state.claimed_id != NULL;
21 -}
22 -
18 enum aclk_database_opcode {
19 ACLK_DATABASE_NOOP = 0,
20 ACLK_DATABASE_NODE_STATE,
@@ -56,8 +51,6 @@ void create_aclk_config(RRDHOST *host, nd_uuid_t *host_uuid, nd_uuid_t *node_id)
51 void sql_aclk_sync_init(void);
52 void aclk_push_alert_config(const char *node_id, const char *config_hash);
53 void schedule_node_info_update(RRDHOST *host);
59 -#ifdef ENABLE_ACLK
54 void unregister_node(const char *machine_guid);
61 -#endif
55
56 #endif //NETDATA_SQLITE_ACLK_H
src/database/sqlite/sqlite_aclk_alert.c
+22 -32
@@ -3,7 +3,6 @@
3 #include "sqlite_functions.h"
4 #include "sqlite_aclk_alert.h"
5
6 -#ifdef ENABLE_ACLK
6 #include "../../aclk/aclk_alarm_api.h"
7
8 #define SQLITE3_COLUMN_STRDUPZ_OR_NULL(res, param) \
@@ -424,27 +423,25 @@ void health_alarm_log_populate(
423
424 static void aclk_push_alert_event(RRDHOST *host __maybe_unused)
425 {
426 + CLAIM_ID claim_id = claim_id_get();
427
428 - char *claim_id = get_agent_claimid();
429 - if (!claim_id || !host->node_id)
428 + if (!claim_id_is_set(claim_id) || uuid_is_null(host->node_id))
429 return;
430
431 sqlite3_stmt *res = NULL;
432
434 - if (!PREPARE_STATEMENT(db_meta, SQL_SELECT_ALERT_TO_PUSH, &res)) {
435 - freez(claim_id);
433 + if (!PREPARE_STATEMENT(db_meta, SQL_SELECT_ALERT_TO_PUSH, &res))
434 return;
437 - }
435
436 int param = 0;
437 SQLITE_BIND_FAIL(done, sqlite3_bind_blob(res, ++param, &host->host_uuid, sizeof(host->host_uuid), SQLITE_STATIC));
438
439 char node_id_str[UUID_STR_LEN];
443 - uuid_unparse_lower(*host->node_id, node_id_str);
440 + uuid_unparse_lower(host->node_id, node_id_str);
441
442 struct alarm_log_entry alarm_log;
443 alarm_log.node_id = node_id_str;
447 - alarm_log.claim_id = claim_id;
444 + alarm_log.claim_id = claim_id.str;
445
446 int64_t first_id = 0;
447 int64_t last_id = 0;
@@ -484,8 +481,6 @@ static void aclk_push_alert_event(RRDHOST *host __maybe_unused)
481 done:
482 REPORT_BIND_FAIL(res, param);
483 SQLITE_FINALIZE(res);
487 -
488 - freez(claim_id);
484 }
485
486 #define SQL_DELETE_PROCESSED_ROWS \
@@ -908,16 +903,14 @@ void send_alert_snapshot_to_cloud(RRDHOST *host __maybe_unused)
903 return;
904 }
905
911 - char *claim_id = get_agent_claimid();
912 - if (unlikely(!claim_id))
906 + CLAIM_ID claim_id = claim_id_get();
907 + if (unlikely(!claim_id_is_set(claim_id)))
908 return;
909
915 - // Check database for this node to see how many alerts we will need to put in the snapshot
910 + // Check the database for this node to see how many alerts we will need to put in the snapshot
911 int cnt = calculate_alert_snapshot_entries(&host->host_uuid);
917 - if (!cnt) {
918 - freez(claim_id);
912 + if (!cnt)
913 return;
920 - }
914
915 sqlite3_stmt *res = NULL;
916 if (!PREPARE_STATEMENT(db_meta, SQL_GET_SNAPSHOT_ENTRIES, &res))
@@ -944,13 +937,13 @@ void send_alert_snapshot_to_cloud(RRDHOST *host __maybe_unused)
937 struct alarm_log_entry alarm_log;
938
939 alarm_snap.node_id = wc->node_id;
947 - alarm_snap.claim_id = claim_id;
940 + alarm_snap.claim_id = claim_id.str;
941 alarm_snap.snapshot_uuid = snapshot_uuid;
942 alarm_snap.chunks = chunks;
943 alarm_snap.chunk = 1;
944
945 alarm_log.node_id = wc->node_id;
953 - alarm_log.claim_id = claim_id;
946 + alarm_log.claim_id = claim_id.str;
947
948 cnt = 0;
949 param = 0;
@@ -969,7 +962,7 @@ void send_alert_snapshot_to_cloud(RRDHOST *host __maybe_unused)
962 version += alarm_log.version;
963
964 if (cnt == ALARM_EVENTS_PER_CHUNK) {
972 - if (aclk_connected)
965 + if (aclk_online_for_alerts())
966 aclk_send_alarm_snapshot(snapshot_proto);
967 cnt = 0;
968 if (alarm_snap.chunk < chunks) {
@@ -995,8 +988,6 @@ void send_alert_snapshot_to_cloud(RRDHOST *host __maybe_unused)
988 done:
989 REPORT_BIND_FAIL(res, param);
990 SQLITE_FINALIZE(res);
998 -
999 - freez(claim_id);
991 }
992
993 // Start streaming alerts
@@ -1030,25 +1021,24 @@ void aclk_alert_version_check(char *node_id, char *claim_id, uint64_t cloud_vers
1021 {
1022 nd_uuid_t node_uuid;
1023
1033 - if (unlikely(!node_id || !claim_id || !claimed() || uuid_parse(node_id, node_uuid)))
1024 + if (unlikely(!node_id || !claim_id || !is_agent_claimed() || uuid_parse(node_id, node_uuid)))
1025 return;
1026
1036 - char *agent_claim_id = get_agent_claimid();
1037 - if (claim_id && agent_claim_id && strcmp(agent_claim_id, claim_id) != 0) {
1038 - nd_log(NDLS_ACCESS, NDLP_NOTICE, "ACLK REQ [%s (N/A)]: ALERTS CHECKPOINT VALIDATION REQUEST RECEIVED WITH INVALID CLAIM ID", node_id);
1039 - goto done;
1027 + CLAIM_ID agent_claim_id = claim_id_get();
1028 + if (claim_id && claim_id_is_set(agent_claim_id) && strcmp(agent_claim_id.str, claim_id) != 0) {
1029 + nd_log(NDLS_ACCESS, NDLP_NOTICE,
1030 + "ACLK REQ [%s (N/A)]: ALERTS CHECKPOINT VALIDATION REQUEST RECEIVED WITH INVALID CLAIM ID",
1031 + node_id);
1032 + return;
1033 }
1034
1035 struct aclk_sync_cfg_t *wc;
1036 RRDHOST *host = find_host_by_node_id(node_id);
1037
1038 if ((!host || !(wc = host->aclk_config)))
1046 - nd_log(NDLS_ACCESS, NDLP_NOTICE, "ACLK REQ [%s (N/A)]: ALERTS CHECKPOINT VALIDATION REQUEST RECEIVED FOR INVALID NODE", node_id);
1039 + nd_log(NDLS_ACCESS, NDLP_NOTICE,
1040 + "ACLK REQ [%s (N/A)]: ALERTS CHECKPOINT VALIDATION REQUEST RECEIVED FOR INVALID NODE",
1041 + node_id);
1042 else
1043 schedule_alert_snapshot_if_needed(wc, cloud_version);
1049 -
1050 -done:
1051 - freez(agent_claim_id);
1044 }
1053 -
1054 -#endif
src/database/sqlite/sqlite_aclk_node.c
+11 -12
@@ -6,8 +6,6 @@
6 #include "../../aclk/aclk_contexts_api.h"
7 #include "../../aclk/aclk_capas.h"
8
9 -#ifdef ENABLE_ACLK
10 -
9 DICTIONARY *collectors_from_charts(RRDHOST *host, DICTIONARY *dict) {
10 RRDSET *st;
11 char name[500];
@@ -32,16 +30,18 @@ static void build_node_collectors(RRDHOST *host)
30 struct update_node_collectors upd_node_collectors;
31 DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
32
33 + CLAIM_ID claim_id = claim_id_get();
34 upd_node_collectors.node_id = wc->node_id;
36 - upd_node_collectors.claim_id = get_agent_claimid();
35 + upd_node_collectors.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL;
36
37 upd_node_collectors.node_collectors = collectors_from_charts(host, dict);
38 aclk_update_node_collectors(&upd_node_collectors);
39
40 dictionary_destroy(dict);
42 - freez(upd_node_collectors.claim_id);
41
44 - nd_log(NDLS_ACCESS, NDLP_DEBUG, "ACLK RES [%s (%s)]: NODE COLLECTORS SENT", wc->node_id, rrdhost_hostname(host));
42 + nd_log(NDLS_ACCESS, NDLP_DEBUG,
43 + "ACLK RES [%s (%s)]: NODE COLLECTORS SENT",
44 + wc->node_id, rrdhost_hostname(host));
45 }
46
47 static void build_node_info(RRDHOST *host)
@@ -50,9 +50,11 @@ static void build_node_info(RRDHOST *host)
50
51 struct aclk_sync_cfg_t *wc = host->aclk_config;
52
53 + CLAIM_ID claim_id = claim_id_get();
54 +
55 rrd_rdlock();
56 node_info.node_id = wc->node_id;
55 - node_info.claim_id = get_agent_claimid();
57 + node_info.claim_id = claim_id_is_set(claim_id) ? claim_id.str : NULL;
58 node_info.machine_guid = host->machine_guid;
59 node_info.child = (host != localhost);
60 node_info.ml_info.ml_capable = ml_capable();
@@ -64,11 +66,11 @@ static void build_node_info(RRDHOST *host)
66
67 char *host_version = NULL;
68 if (host != localhost) {
67 - netdata_mutex_lock(&host->receiver_lock);
69 + spinlock_lock(&host->receiver_lock);
70 host_version = strdupz(
71 host->receiver && host->receiver->program_version ? host->receiver->program_version :
72 rrdhost_program_version(host));
71 - netdata_mutex_unlock(&host->receiver_lock);
73 + spinlock_unlock(&host->receiver_lock);
74 }
75
76 node_info.data.name = rrdhost_hostname(host);
@@ -108,7 +110,6 @@ static void build_node_info(RRDHOST *host)
110 host == localhost ? "parent" : "child");
111
112 rrd_rdunlock();
111 - freez(node_info.claim_id);
113 freez(node_info.node_instance_capabilities);
114 freez(host_version);
115
@@ -133,7 +134,7 @@ void aclk_check_node_info_and_collectors(void)
134 {
135 RRDHOST *host;
136
136 - if (unlikely(!aclk_connected))
137 + if (unlikely(!aclk_online_for_nodes()))
138 return;
139
140 size_t context_loading = 0;
@@ -190,5 +191,3 @@ void aclk_check_node_info_and_collectors(void)
191 context_pp);
192 }
193 }
193 -
194 -#endif
src/database/sqlite/sqlite_health.c
+6 -15
@@ -134,7 +134,6 @@ int calculate_delay(RRDCALC_STATUS old_status, RRDCALC_STATUS new_status)
134 return delay;
135 }
136
137 -#ifdef ENABLE_ACLK
137 #define SQL_INSERT_ALERT_PENDING_QUEUE \
138 "INSERT INTO alert_queue (host_id, health_log_id, unique_id, alarm_id, status, date_scheduled)" \
139 " VALUES (@host_id, @health_log_id, @unique_id, @alarm_id, @new_status, UNIXEPOCH() + @delay)" \
@@ -179,7 +178,6 @@ done:
178 REPORT_BIND_FAIL(res, param);
179 SQLITE_RESET(res);
180 }
182 -#endif
181
182 #define SQL_INSERT_HEALTH_LOG_DETAIL \
183 "INSERT INTO health_log_detail (health_log_id, unique_id, alarm_id, alarm_event_id, " \
@@ -272,11 +270,8 @@ static void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae)
270 if (rc == SQLITE_ROW) {
271 health_log_id = (size_t)sqlite3_column_int64(res, 0);
272 sql_health_alarm_log_insert_detail(host, health_log_id, ae);
275 -#ifdef ENABLE_ACLK
276 - if (netdata_cloud_enabled)
277 - insert_alert_queue(
278 - host, health_log_id, (int64_t)ae->unique_id, (int64_t)ae->alarm_id, ae->old_status, ae->new_status);
279 -#endif
273 + insert_alert_queue(
274 + host, health_log_id, (int64_t)ae->unique_id, (int64_t)ae->alarm_id, ae->old_status, ae->new_status);
275 } else
276 error_report("HEALTH [%s]: Failed to execute SQL_INSERT_HEALTH_LOG, rc = %d", rrdhost_hostname(host), rc);
277
@@ -432,14 +427,10 @@ static void sql_inject_removed_status(
427 //update the old entry in health_log
428 sql_update_transition_in_health_log(host, alarm_id, &transition_id, last_transition);
429
435 -#ifdef ENABLE_ACLK
436 - if (netdata_cloud_enabled) {
437 - int64_t health_log_id = sqlite3_column_int64(res, 0);
438 - RRDCALC_STATUS old_status = (RRDCALC_STATUS)sqlite3_column_double(res, 1);
439 - insert_alert_queue(
440 - host, health_log_id, (int64_t)unique_id, (int64_t)alarm_id, old_status, RRDCALC_STATUS_REMOVED);
441 - }
442 -#endif
430 + int64_t health_log_id = sqlite3_column_int64(res, 0);
431 + RRDCALC_STATUS old_status = (RRDCALC_STATUS)sqlite3_column_double(res, 1);
432 + insert_alert_queue(
433 + host, health_log_id, (int64_t)unique_id, (int64_t)alarm_id, old_status, RRDCALC_STATUS_REMOVED);
434 }
435 //else
436 // error_report("HEALTH [N/A]: Failed to execute SQL_INJECT_REMOVED, rc = %d", rc);
src/database/sqlite/sqlite_metadata.c
+8 -20
@@ -78,7 +78,6 @@ const char *database_config[] = {
78 "CREATE INDEX IF NOT EXISTS health_log_d_ind_7 on health_log_detail (alarm_id)",
79 "CREATE INDEX IF NOT EXISTS health_log_d_ind_8 on health_log_detail (new_status, updated_by_id)",
80
81 -#ifdef ENABLE_ACLK
81 "CREATE TABLE IF NOT EXISTS alert_queue "
82 " (host_id BLOB, health_log_id INT, unique_id INT, alarm_id INT, status INT, date_scheduled INT, "
83 " UNIQUE(host_id, health_log_id, alarm_id))",
@@ -88,7 +87,6 @@ const char *database_config[] = {
87
88 "CREATE TABLE IF NOT EXISTS aclk_queue (sequence_id INTEGER PRIMARY KEY, host_id blob, health_log_id INT, "
89 "unique_id INT, date_created INT, UNIQUE(host_id, health_log_id))",
91 -#endif
90
91 NULL
92 };
@@ -257,26 +255,20 @@ static inline void set_host_node_id(RRDHOST *host, nd_uuid_t *node_id)
255 return;
256
257 if (unlikely(!node_id)) {
260 - freez(host->node_id);
261 - __atomic_store_n(&host->node_id, NULL, __ATOMIC_RELAXED);
258 + uuid_clear(host->node_id);
259 return;
260 }
261
262 struct aclk_sync_cfg_t *wc = host->aclk_config;
263
267 - if (unlikely(!host->node_id)) {
268 - nd_uuid_t *t = mallocz(sizeof(*host->node_id));
269 - uuid_copy(*t, *node_id);
270 - __atomic_store_n(&host->node_id, t, __ATOMIC_RELAXED);
271 - }
272 - else {
273 - uuid_copy(*(host->node_id), *node_id);
274 - }
264 + uuid_copy(host->node_id, *node_id);
265
266 if (unlikely(!wc))
267 create_aclk_config(host, &host->host_uuid, node_id);
268 else
269 uuid_unparse_lower(*node_id, wc->node_id);
270 +
271 + rrdpush_receiver_send_node_and_claim_id_to_child(host);
272 }
273
274 #define SQL_SET_HOST_LABEL \
@@ -315,7 +307,7 @@ done:
307
308 #define SQL_UPDATE_NODE_ID "UPDATE node_instance SET node_id = @node_id WHERE host_id = @host_id"
309
318 -int update_node_id(nd_uuid_t *host_id, nd_uuid_t *node_id)
310 +int sql_update_node_id(nd_uuid_t *host_id, nd_uuid_t *node_id)
311 {
312 sqlite3_stmt *res = NULL;
313 RRDHOST *host = NULL;
@@ -1479,9 +1471,7 @@ static void cleanup_health_log(struct metadata_wc *wc)
1471
1472 (void) db_execute(db_meta,"DELETE FROM health_log WHERE host_id NOT IN (SELECT host_id FROM host)");
1473 (void) db_execute(db_meta,"DELETE FROM health_log_detail WHERE health_log_id NOT IN (SELECT health_log_id FROM health_log)");
1482 -#ifdef ENABLE_ACLK
1474 (void) db_execute(db_meta,"DELETE FROM alert_version WHERE health_log_id NOT IN (SELECT health_log_id FROM health_log)");
1484 -#endif
1475 }
1476
1477 //
@@ -1631,9 +1621,7 @@ static void restore_host_context(void *arg)
1621
1622 rrdhost_flag_clear(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD);
1623
1634 -#ifdef ENABLE_ACLK
1624 aclk_queue_node_info(host, false);
1636 -#endif
1625
1626 nd_log(
1627 NDLS_DAEMON,
@@ -1952,10 +1940,10 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused)
1940
1941 if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_CLAIMID))) {
1942 rrdhost_flag_clear(host, RRDHOST_FLAG_METADATA_CLAIMID);
1955 - nd_uuid_t uuid;
1943 int rc;
1957 - if (likely(host->aclk_state.claimed_id && !uuid_parse(host->aclk_state.claimed_id, uuid)))
1958 - rc = store_claim_id(&host->host_uuid, &uuid);
1944 + ND_UUID uuid = claim_id_get_uuid();
1945 + if(!UUIDiszero(uuid))
1946 + rc = store_claim_id(&host->host_uuid, &uuid.uuid);
1947 else
1948 rc = store_claim_id(&host->host_uuid, NULL);
1949
src/database/sqlite/sqlite_metadata.h
+1 -1
@@ -41,7 +41,7 @@ void vacuum_database(sqlite3 *database, const char *db_alias, int threshold, int
41 int sql_metadata_cache_stats(int op);
42
43 int get_node_id(nd_uuid_t *host_id, nd_uuid_t *node_id);
44 -int update_node_id(nd_uuid_t *host_id, nd_uuid_t *node_id);
44 +int sql_update_node_id(nd_uuid_t *host_id, nd_uuid_t *node_id);
45 struct node_instance_list *get_node_list(void);
46 void sql_load_node_id(RRDHOST *host);
47
src/exporting/clean_connectors.c
-2
@@ -67,9 +67,7 @@ void simple_connector_cleanup(struct instance *instance)
67 freez(current_buffer);
68 }
69
70 -#ifdef ENABLE_HTTPS
70 netdata_ssl_close(&simple_connector_data->ssl);
72 -#endif
71
72 freez(simple_connector_data);
73
src/exporting/exporting_engine.c
-2
@@ -6,7 +6,6 @@ static struct engine *engine = NULL;
6
7 void analytics_exporting_connectors_ssl(BUFFER *b)
8 {
9 -#ifdef ENABLE_HTTPS
9 if (netdata_ssl_exporting_ctx) {
10 for (struct instance *instance = engine->instance_root; instance; instance = instance->next) {
11 struct simple_connector_data *connector_specific_data = instance->connector_specific_data;
@@ -16,7 +15,6 @@ void analytics_exporting_connectors_ssl(BUFFER *b)
15 }
16 }
17 }
19 -#endif
18 buffer_strcat(b, "|");
19 }
20
src/exporting/exporting_engine.h
-2
@@ -124,9 +124,7 @@ struct simple_connector_data {
124 struct simple_connector_buffer *first_buffer;
125 struct simple_connector_buffer *last_buffer;
126
127 -#ifdef ENABLE_HTTPS
127 NETDATA_SSL ssl;
129 -#endif
128 };
129
130 struct prometheus_remote_write_specific_config {
src/exporting/graphite/graphite.c
-2
@@ -19,12 +19,10 @@ int init_graphite_instance(struct instance *instance)
19 struct simple_connector_data *connector_specific_data = callocz(1, sizeof(struct simple_connector_data));
20 instance->connector_specific_data = connector_specific_data;
21
22 -#ifdef ENABLE_HTTPS
22 connector_specific_data->ssl = NETDATA_SSL_UNSET_CONNECTION;
23 if (instance->config.options & EXPORTING_OPTION_USE_TLS) {
24 netdata_ssl_initialize_ctx(NETDATA_SSL_EXPORTING_CTX);
25 }
27 -#endif
26
27 instance->start_batch_formatting = NULL;
28 instance->start_host_formatting = format_host_labels_graphite_plaintext;
src/exporting/json/json.c
-2
@@ -70,12 +70,10 @@ int init_json_http_instance(struct instance *instance)
70 struct simple_connector_data *connector_specific_data = callocz(1, sizeof(struct simple_connector_data));
71 instance->connector_specific_data = connector_specific_data;
72
73 -#ifdef ENABLE_HTTPS
73 connector_specific_data->ssl = NETDATA_SSL_UNSET_CONNECTION;
74 if (instance->config.options & EXPORTING_OPTION_USE_TLS) {
75 netdata_ssl_initialize_ctx(NETDATA_SSL_EXPORTING_CTX);
76 }
78 -#endif
77
78 instance->start_batch_formatting = open_batch_json_http;
79 instance->start_host_formatting = format_host_labels_json_plaintext;
src/exporting/opentsdb/opentsdb.c
-4
@@ -20,12 +20,10 @@ int init_opentsdb_telnet_instance(struct instance *instance)
20 struct simple_connector_data *connector_specific_data = callocz(1, sizeof(struct simple_connector_data));
21 instance->connector_specific_data = connector_specific_data;
22
23 -#ifdef ENABLE_HTTPS
23 connector_specific_data->ssl = NETDATA_SSL_UNSET_CONNECTION;
24 if (instance->config.options & EXPORTING_OPTION_USE_TLS) {
25 netdata_ssl_initialize_ctx(NETDATA_SSL_EXPORTING_CTX);
26 }
28 -#endif
27
28 instance->start_batch_formatting = NULL;
29 instance->start_host_formatting = format_host_labels_opentsdb_telnet;
@@ -75,12 +73,10 @@ int init_opentsdb_http_instance(struct instance *instance)
73 connector_specific_config->default_port = 4242;
74
75 struct simple_connector_data *connector_specific_data = callocz(1, sizeof(struct simple_connector_data));
78 -#ifdef ENABLE_HTTPS
76 connector_specific_data->ssl = NETDATA_SSL_UNSET_CONNECTION;
77 if (instance->config.options & EXPORTING_OPTION_USE_TLS) {
78 netdata_ssl_initialize_ctx(NETDATA_SSL_EXPORTING_CTX);
79 }
83 -#endif
80 instance->connector_specific_data = connector_specific_data;
81
82 instance->start_batch_formatting = open_batch_json_http;
src/exporting/prometheus/remote_write/remote_write.c
-2
@@ -114,12 +114,10 @@ int init_prometheus_remote_write_instance(struct instance *instance)
114 struct simple_connector_data *simple_connector_data = callocz(1, sizeof(struct simple_connector_data));
115 instance->connector_specific_data = simple_connector_data;
116
117 -#ifdef ENABLE_HTTPS
117 simple_connector_data->ssl = NETDATA_SSL_UNSET_CONNECTION;
118 if (instance->config.options & EXPORTING_OPTION_USE_TLS) {
119 netdata_ssl_initialize_ctx(NETDATA_SSL_EXPORTING_CTX);
120 }
122 -#endif
121
122 struct prometheus_remote_write_specific_data *connector_specific_data =
123 callocz(1, sizeof(struct prometheus_remote_write_specific_data));
src/exporting/read_config.c
+2 -5
@@ -207,14 +207,14 @@ struct engine *read_exporting_config()
207 if (unlikely(engine))
208 return engine;
209
210 - char *filename = strdupz_path_subpath(netdata_configured_user_config_dir, EXPORTING_CONF);
210 + char *filename = filename_from_path_entry_strdupz(netdata_configured_user_config_dir, EXPORTING_CONF);
211
212 exporting_config_exists = appconfig_load(&exporting_config, filename, 0, NULL);
213 if (!exporting_config_exists) {
214 netdata_log_info("CONFIG: cannot load user exporting config '%s'. Will try the stock version.", filename);
215 freez(filename);
216
217 - filename = strdupz_path_subpath(netdata_configured_stock_config_dir, EXPORTING_CONF);
217 + filename = filename_from_path_entry_strdupz(netdata_configured_stock_config_dir, EXPORTING_CONF);
218 exporting_config_exists = appconfig_load(&exporting_config, filename, 0, NULL);
219 if (!exporting_config_exists)
220 netdata_log_info("CONFIG: cannot load stock exporting config '%s'. Running with internal defaults.", filename);
@@ -468,8 +468,6 @@ struct engine *read_exporting_config()
468
469 tmp_instance->config.hostname = strdupz(exporter_get(instance_name, "hostname", engine->config.hostname));
470
471 -#ifdef ENABLE_HTTPS
472 -
471 #define STR_GRAPHITE_HTTPS "graphite:https"
472 #define STR_JSON_HTTPS "json:https"
473 #define STR_OPENTSDB_HTTPS "opentsdb:https"
@@ -487,7 +485,6 @@ struct engine *read_exporting_config()
485 strlen(STR_PROMETHEUS_REMOTE_WRITE_HTTPS)))) {
486 tmp_instance->config.options |= EXPORTING_OPTION_USE_TLS;
487 }
490 -#endif
488
489 #ifdef NETDATA_INTERNAL_CHECKS
490 netdata_log_info(
src/exporting/send_data.c
+3 -21
@@ -2,7 +2,6 @@
2
3 #include "exporting_engine.h"
4
5 -#ifdef ENABLE_HTTPS
5 /**
6 * Check if TLS is enabled in the configuration
7 *
@@ -19,7 +18,6 @@ static int exporting_tls_is_enabled(EXPORTING_CONNECTOR_TYPE type __maybe_unused
18 type == EXPORTING_CONNECTOR_TYPE_PROMETHEUS_REMOTE_WRITE) &&
19 options & EXPORTING_OPTION_USE_TLS;
20 }
22 -#endif
21
22 /**
23 * Discard response
@@ -69,28 +67,23 @@ void simple_connector_receive_response(int *sock, struct instance *instance)
67 response = buffer_create(4096, &netdata_buffers_statistics.buffers_exporters);
68
69 struct stats *stats = &instance->stats;
72 -#ifdef ENABLE_HTTPS
70 uint32_t options = (uint32_t)instance->config.options;
71 struct simple_connector_data *connector_specific_data = instance->connector_specific_data;
72
73 if (options & EXPORTING_OPTION_USE_TLS)
74 ERR_clear_error();
78 -#endif
75
76 errno_clear();
77
78 // loop through to collect all data
79 while (*sock != -1 && errno != EWOULDBLOCK) {
80 ssize_t r;
85 -#ifdef ENABLE_HTTPS
81 if (SSL_connection(&connector_specific_data->ssl))
82 r = netdata_ssl_read(&connector_specific_data->ssl, &response->buffer[response->len],
83 (int) (response->size - response->len));
84 else
85 r = recv(*sock, &response->buffer[response->len], response->size - response->len, MSG_DONTWAIT);
91 -#else
92 - r = recv(*sock, &response->buffer[response->len], response->size - response->len, MSG_DONTWAIT);
93 -#endif
86 +
87 if (likely(r > 0)) {
88 // we received some data
89 response->len += r;
@@ -136,13 +129,11 @@ void simple_connector_send_buffer(
129 flags += MSG_NOSIGNAL;
130 #endif
131
139 -#ifdef ENABLE_HTTPS
132 uint32_t options = (uint32_t)instance->config.options;
133 struct simple_connector_data *connector_specific_data = instance->connector_specific_data;
134
135 if (options & EXPORTING_OPTION_USE_TLS)
136 ERR_clear_error();
145 -#endif
137
138 struct stats *stats = &instance->stats;
139 ssize_t header_sent_bytes = 0;
@@ -150,7 +141,6 @@ void simple_connector_send_buffer(
141 size_t header_len = buffer_strlen(header);
142 size_t buffer_len = buffer_strlen(buffer);
143
153 -#ifdef ENABLE_HTTPS
144 if (SSL_connection(&connector_specific_data->ssl)) {
145
146 if (header_len)
@@ -166,12 +156,6 @@ void simple_connector_send_buffer(
156 if ((size_t)header_sent_bytes == header_len)
157 buffer_sent_bytes = send(*sock, buffer_tostring(buffer), buffer_len, flags);
158 }
169 -#else
170 - if (header_len)
171 - header_sent_bytes = send(*sock, buffer_tostring(header), header_len, flags);
172 - if ((size_t)header_sent_bytes == header_len)
173 - buffer_sent_bytes = send(*sock, buffer_tostring(buffer), buffer_len, flags);
174 -#endif
159
160 if ((size_t)buffer_sent_bytes == buffer_len) {
161 // we sent the data successfully
@@ -221,12 +205,11 @@ void simple_connector_worker(void *instance_p)
205 snprintfz(threadname, ND_THREAD_TAG_MAX, "EXPSMPL[%zu]", instance->index);
206 uv_thread_set_name_np(threadname);
207
224 -#ifdef ENABLE_HTTPS
208 uint32_t options = (uint32_t)instance->config.options;
209
210 if (options & EXPORTING_OPTION_USE_TLS)
211 ERR_clear_error();
229 -#endif
212 +
213 struct simple_connector_config *connector_specific_config = instance->config.connector_specific_config;
214
215 int sock = -1;
@@ -303,7 +286,7 @@ void simple_connector_worker(void *instance_p)
286 &reconnects,
287 connector_specific_data->connected_to,
288 CONNECTED_TO_MAX);
306 -#ifdef ENABLE_HTTPS
289 +
290 if (exporting_tls_is_enabled(instance->config.type, options) && sock != -1) {
291 if (netdata_ssl_exporting_ctx) {
292 if (sock_delnonblock(sock) < 0)
@@ -326,7 +309,6 @@ void simple_connector_worker(void *instance_p)
309 }
310 }
311 }
329 -#endif
312
313 stats->reconnects += reconnects;
314 }
src/health/health.h
+1 -1
@@ -51,7 +51,7 @@ void health_plugin_reload(void);
51
52 void health_aggregate_alarms(RRDHOST *host, BUFFER *wb, BUFFER* context, RRDCALC_STATUS status);
53 void health_alarms2json(RRDHOST *host, BUFFER *wb, int all);
54 -void health_alert2json_conf(RRDHOST *host, BUFFER *wb, CONTEXTS_V2_OPTIONS all);
54 +void health_alert2json_conf(RRDHOST *host, BUFFER *wb, CONTEXTS_OPTIONS all);
55 void health_alarms_values2json(RRDHOST *host, BUFFER *wb, int all);
56
57 void health_api_v1_chart_variables2json(RRDSET *st, BUFFER *wb);
src/health/health_dyncfg.c
+15 -10
@@ -68,8 +68,8 @@ static bool parse_match(json_object *jobj, const char *path, struct rrd_alert_ma
68 }
69
70 static bool parse_config_value_database_lookup(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error, bool strict) {
71 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "after", config->after, error, strict);
72 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "before", config->before, error, strict);
71 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "after", config->after, error, strict);
72 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "before", config->before, error, strict);
73 JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "time_group", time_grouping_txt2id, config->time_group, error, strict);
74 JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "dims_group", alerts_dims_grouping2id, config->dims_group, error, strict);
75 JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "data_source", alerts_data_sources2id, config->data_source, error, strict);
@@ -98,7 +98,7 @@ static bool parse_config_value(json_object *jobj, const char *path, struct rrd_a
98 JSONC_PARSE_SUBOBJECT(jobj, path, "database_lookup", config, parse_config_value_database_lookup, error, strict);
99 JSONC_PARSE_TXT2EXPRESSION_OR_ERROR_AND_RETURN(jobj, path, "calculation", config->calculation, error, false);
100 JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "units", config->units, error, false);
101 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "update_every", config->update_every, error, strict);
101 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "update_every", config->update_every, error, strict);
102 return true;
103 }
104
@@ -109,17 +109,17 @@ static bool parse_config_conditions(json_object *jobj, const char *path, struct
109 }
110
111 static bool parse_config_action_delay(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error, bool strict) {
112 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "up", config->delay_up_duration, error, strict);
113 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "down", config->delay_down_duration, error, strict);
114 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "max", config->delay_max_duration, error, strict);
112 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "up", config->delay_up_duration, error, strict);
113 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "down", config->delay_down_duration, error, strict);
114 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "max", config->delay_max_duration, error, strict);
115 JSONC_PARSE_DOUBLE_OR_ERROR_AND_RETURN(jobj, path, "multiplier", config->delay_multiplier, error, strict);
116 return true;
117 }
118
119 static bool parse_config_action_repeat(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error, bool strict) {
120 JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, "enabled", config->has_custom_repeat_config, error, strict);
121 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "warning", config->warn_repeat_every, error, strict);
122 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "critical", config->crit_repeat_every, error, strict);
121 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "warning", config->warn_repeat_every, error, strict);
122 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, "critical", config->crit_repeat_every, error, strict);
123 return true;
124 }
125
@@ -153,7 +153,7 @@ static bool parse_config(json_object *jobj, const char *path, RRD_ALERT_PROTOTYP
153
154 static bool parse_prototype(json_object *jobj, const char *path, RRD_ALERT_PROTOTYPE *base, BUFFER *error, const char *name, bool strict) {
155 int64_t version = 0;
156 - JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "format_version", version, error, strict);
156 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "format_version", version, error, strict);
157
158 if(version != 1) {
159 buffer_sprintf(error, "unsupported document version");
@@ -164,6 +164,11 @@ static bool parse_prototype(json_object *jobj, const char *path, RRD_ALERT_PROTO
164
165 json_object *rules;
166 if (json_object_object_get_ex(jobj, "rules", &rules)) {
167 + if (json_object_get_type(rules) != json_type_array) {
168 + buffer_sprintf(error, "member 'rules' is not an array");
169 + return false;
170 + }
171 +
172 size_t rules_len = json_object_array_length(rules);
173
174 RRD_ALERT_PROTOTYPE *ap = base; // fill the first entry
@@ -270,7 +275,7 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
275 buffer_json_member_add_object(wb, "config");
276 {
277 if(!for_hashing) {
273 - buffer_json_member_add_uuid(wb, "hash", &ap->config.hash_id);
278 + buffer_json_member_add_uuid(wb, "hash", ap->config.hash_id);
279 buffer_json_member_add_string(wb, "source_type", dyncfg_id2source_type(ap->config.source_type));
280 buffer_json_member_add_string(wb, "source", string2str(ap->config.source));
281 }
src/health/health_event_loop.c
+1 -5
@@ -295,13 +295,11 @@ static void health_event_loop(void) {
295 }
296
297 worker_is_busy(WORKER_HEALTH_JOB_HOST_LOCK);
298 -#ifdef ENABLE_ACLK
299 - if (netdata_cloud_enabled) {
298 + {
299 struct aclk_sync_cfg_t *wc = host->aclk_config;
300 if (wc && wc->send_snapshot == 2)
301 continue;
302 }
304 -#endif
303
304 // the first loop is to lookup values from the db
305 foreach_rrdcalc_in_rrdhost_read(host, rc) {
@@ -648,7 +646,6 @@ static void health_event_loop(void) {
646 break;
647 }
648 }
651 -#ifdef ENABLE_ACLK
649 struct aclk_sync_cfg_t *wc = host->aclk_config;
650 if (wc && wc->send_snapshot == 1) {
651 wc->send_snapshot = 2;
@@ -657,7 +654,6 @@ static void health_event_loop(void) {
654 else
655 if (process_alert_pending_queue(host))
656 rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS);
660 -#endif
657
658 dfe_done(host);
659
src/libnetdata/buffer/buffer.h
+17 -1
@@ -775,7 +775,7 @@ static inline void buffer_json_member_add_quoted_string(BUFFER *wb, const char *
775 wb->json.stack[wb->json.depth].count++;
776 }
777
778 -static inline void buffer_json_member_add_uuid(BUFFER *wb, const char *key, nd_uuid_t *value) {
778 +static inline void buffer_json_member_add_uuid_ptr(BUFFER *wb, const char *key, nd_uuid_t *value) {
779 buffer_print_json_comma_newline_spacing(wb);
780 buffer_print_json_key(wb, key);
781 buffer_fast_strcat(wb, ":", 1);
@@ -791,6 +791,22 @@ static inline void buffer_json_member_add_uuid(BUFFER *wb, const char *key, nd_u
791 wb->json.stack[wb->json.depth].count++;
792 }
793
794 +static inline void buffer_json_member_add_uuid(BUFFER *wb, const char *key, nd_uuid_t value) {
795 + buffer_print_json_comma_newline_spacing(wb);
796 + buffer_print_json_key(wb, key);
797 + buffer_fast_strcat(wb, ":", 1);
798 +
799 + if(!uuid_is_null(value)) {
800 + char uuid[GUID_LEN + 1];
801 + uuid_unparse_lower(value, uuid);
802 + buffer_json_add_string_value(wb, uuid);
803 + }
804 + else
805 + buffer_json_add_string_value(wb, NULL);
806 +
807 + wb->json.stack[wb->json.depth].count++;
808 +}
809 +
810 static inline void buffer_json_member_add_boolean(BUFFER *wb, const char *key, bool value) {
811 buffer_print_json_comma_newline_spacing(wb);
812 buffer_print_json_key(wb, key);
src/libnetdata/buffered_reader/buffered_reader.h
-2
@@ -55,9 +55,7 @@ static inline buffered_reader_ret_t buffered_reader_read(struct buffered_reader
55 static inline buffered_reader_ret_t buffered_reader_read_timeout(struct buffered_reader *reader, int fd, int timeout_ms, bool log_error) {
56 short int revents = 0;
57 switch(wait_on_socket_or_cancel_with_timeout(
58 -#ifdef ENABLE_HTTPS
58 NULL,
60 -#endif
59 fd, timeout_ms, POLLIN, &revents)) {
60
61 case 0: // data are waiting
src/libnetdata/config/appconfig.c
+13 -4
@@ -653,7 +653,9 @@ int appconfig_load(struct config *root, char *filename, int overwrite_used, cons
653
654 FILE *fp = fopen(filename, "r");
655 if(!fp) {
656 - // netdata_log_info("CONFIG: cannot open file '%s'. Using internal defaults.", filename);
656 + if(errno != ENOENT)
657 + netdata_log_info("CONFIG: cannot open file '%s'. Using internal defaults.", filename);
658 +
659 return 0;
660 }
661
@@ -799,7 +801,7 @@ int appconfig_load(struct config *root, char *filename, int overwrite_used, cons
801 return 1;
802 }
803
802 -void appconfig_generate(struct config *root, BUFFER *wb, int only_changed)
804 +void appconfig_generate(struct config *root, BUFFER *wb, int only_changed, bool netdata_conf)
805 {
806 int i, pri;
807 struct section *co;
@@ -811,12 +813,13 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed)
813 if(!strcmp(co->name, CONFIG_SECTION_HOST_LABEL))
814 found_host_labels = 1;
815
814 - if(!found_host_labels) {
816 + if(netdata_conf && !found_host_labels) {
817 appconfig_section_create(root, CONFIG_SECTION_HOST_LABEL);
818 appconfig_get(root, CONFIG_SECTION_HOST_LABEL, "name", "value");
819 }
820 }
821
822 + if(netdata_conf) {
823 buffer_strcat(wb,
824 "# netdata configuration\n"
825 "#\n"
@@ -830,6 +833,7 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed)
833 "# The value shown in the commented settings, is the default value.\n"
834 "#\n"
835 "\n# global netdata configuration\n");
836 + }
837
838 for(i = 0; i <= 17 ;i++) {
839 appconfig_wrlock(root);
@@ -884,7 +888,12 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed)
888 if(used && !(cv->flags & CONFIG_VALUE_USED)) {
889 buffer_sprintf(wb, "\n\t# option '%s' is not used.\n", cv->name);
890 }
887 - buffer_sprintf(wb, "\t%s%s = %s\n", ((!(cv->flags & CONFIG_VALUE_LOADED)) && (!(cv->flags & CONFIG_VALUE_CHANGED)) && (cv->flags & CONFIG_VALUE_USED))?"# ":"", cv->name, cv->value);
891 + buffer_sprintf(wb, "\t%s%s = %s\n",
892 + (
893 + !(cv->flags & CONFIG_VALUE_LOADED) &&
894 + !(cv->flags & CONFIG_VALUE_CHANGED) &&
895 + (cv->flags & CONFIG_VALUE_USED)
896 + )?"# ":"", cv->name, cv->value);
897 }
898 config_section_unlock(co);
899 }
src/libnetdata/config/appconfig.h
+1 -1
@@ -186,7 +186,7 @@ int appconfig_set_boolean(struct config *root, const char *section, const char *
186 int appconfig_exists(struct config *root, const char *section, const char *name);
187 int appconfig_move(struct config *root, const char *section_old, const char *name_old, const char *section_new, const char *name_new);
188
189 -void appconfig_generate(struct config *root, BUFFER *wb, int only_changed);
189 +void appconfig_generate(struct config *root, BUFFER *wb, int only_changed, bool netdata_conf);
190
191 int appconfig_section_compare(void *a, void *b);
192
src/libnetdata/dictionary/dictionary.h
+2 -2
@@ -299,7 +299,8 @@ typedef DICTFE_CONST struct dictionary_foreach {
299
300 #define dfe_start_rw(dict, value, mode) \
301 do { \
302 - DICTFE value ## _dfe = {}; \
302 + /* automatically cleanup DFE, to allow using return from within the loop */ \
303 + DICTFE _cleanup_(dictionary_foreach_done) value ## _dfe = {}; \
304 (void)(value); /* needed to avoid warning when looping without using this */ \
305 for((value) = dictionary_foreach_start_rw(&value ## _dfe, (dict), (mode)); \
306 (value ## _dfe.item) || (value) ; \
@@ -308,7 +309,6 @@ typedef DICTFE_CONST struct dictionary_foreach {
309
310 #define dfe_done(value) \
311 } \
311 - dictionary_foreach_done(&value ## _dfe); \
312 } while(0)
313
314 #define dfe_unlock(value) dictionary_foreach_unlock(&value ## _dfe)
src/libnetdata/facets/facets.c
+60 -12
@@ -222,6 +222,7 @@ struct facets {
222 SIMPLE_PATTERN *visible_keys;
223 SIMPLE_PATTERN *excluded_keys;
224 SIMPLE_PATTERN *included_keys;
225 + bool all_keys_included_by_default;
226
227 FACETS_OPTIONS options;
228
@@ -570,11 +571,11 @@ static inline void FACET_VALUE_ADD_CURRENT_VALUE_TO_INDEX(FACET_KEY *k) {
571 k->facets->operations.values.indexed++;
572 }
573
573 -static inline void FACET_VALUE_ADD_OR_UPDATE_SELECTED(FACET_KEY *k, FACETS_HASH hash) {
574 +static inline void FACET_VALUE_ADD_OR_UPDATE_SELECTED(FACET_KEY *k, const char *name, FACETS_HASH hash) {
575 FACET_VALUE tv = {
576 .hash = hash,
577 .selected = true,
577 - .name = NULL,
578 + .name = name,
579 .name_len = 0,
580 };
581 FACET_VALUE_ADD_TO_INDEX(k, &tv);
@@ -1547,7 +1548,7 @@ static inline void facet_value_is_used(FACET_KEY *k, FACET_VALUE *v) {
1548 }
1549
1550 static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k) {
1550 - bool included = true, excluded = false, never = false;
1551 + bool included = facets->all_keys_included_by_default, excluded = false, never = false;
1552
1553 if(k->options & (FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_NO_FACET | FACET_KEY_OPTION_NEVER_FACET)) {
1554 if(k->options & FACET_KEY_OPTION_FACET) {
@@ -1594,6 +1595,7 @@ static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k) {
1595
1596 FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const char *visible_keys, const char *facet_keys, const char *non_facet_keys) {
1597 FACETS *facets = callocz(1, sizeof(FACETS));
1598 + facets->all_keys_included_by_default = true;
1599 facets->options = options;
1600 FACETS_KEYS_INDEX_CREATE(facets);
1601
@@ -1691,6 +1693,40 @@ void facets_enable_slice_mode(FACETS *facets) {
1693 facets->options |= FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS | FACETS_OPTION_SORT_FACETS_ALPHABETICALLY;
1694 }
1695
1696 +void facets_reset_and_disable_all_facets(FACETS *facets) {
1697 + facets->all_keys_included_by_default = false;
1698 +
1699 + simple_pattern_free(facets->included_keys);
1700 + facets->included_keys = NULL;
1701 +
1702 +// We need this, because the exclusions are good for controlling which key can become a facet.
1703 +// The excluded ones are not offered for facets at all.
1704 +// simple_pattern_free(facets->excluded_keys);
1705 +// facets->excluded_keys = NULL;
1706 +
1707 + simple_pattern_free(facets->visible_keys);
1708 + facets->visible_keys = NULL;
1709 +
1710 + FACET_KEY *k;
1711 + foreach_key_in_facets(facets, k) {
1712 + k->options |= FACET_KEY_OPTION_NO_FACET;
1713 + k->options &= ~FACET_KEY_OPTION_FACET;
1714 + }
1715 + foreach_key_in_facets_done(k);
1716 +}
1717 +
1718 +inline FACET_KEY *facets_register_facet(FACETS *facets, const char *name, FACET_KEY_OPTIONS options) {
1719 + size_t name_length = strlen(name);
1720 + FACETS_HASH hash = FACETS_HASH_FUNCTION(name, name_length);
1721 +
1722 + FACET_KEY *k = FACETS_KEY_ADD_TO_INDEX(facets, hash, name, name_length, options);
1723 + k->options |= FACET_KEY_OPTION_FACET;
1724 + k->options &= ~FACET_KEY_OPTION_NO_FACET;
1725 + facet_key_late_init(facets, k);
1726 +
1727 + return k;
1728 +}
1729 +
1730 inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options) {
1731 if(!is_valid_string_hash(key_id))
1732 return NULL;
@@ -1708,16 +1744,25 @@ inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, F
1744 return k;
1745 }
1746
1711 -void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_id, FACET_KEY_OPTIONS options) {
1747 +void facets_register_facet_filter_id(FACETS *facets, const char *key_id, const char *value_id, FACET_KEY_OPTIONS options) {
1748 FACET_KEY *k = facets_register_facet_id(facets, key_id, options);
1749 if(k) {
1750 if(is_valid_string_hash(value_id)) {
1751 k->default_selected_for_values = false;
1716 - FACET_VALUE_ADD_OR_UPDATE_SELECTED(k, str_to_facets_hash(value_id));
1752 + FACET_VALUE_ADD_OR_UPDATE_SELECTED(k, NULL, str_to_facets_hash(value_id));
1753 }
1754 }
1755 }
1756
1757 +void facets_register_facet_filter(FACETS *facets, const char *key, const char *value, FACET_KEY_OPTIONS options) {
1758 + FACET_KEY *k = facets_register_facet(facets, key, options);
1759 + if(k) {
1760 + FACETS_HASH hash = FACETS_HASH_FUNCTION(value, strlen(value));
1761 + k->default_selected_for_values = false;
1762 + FACET_VALUE_ADD_OR_UPDATE_SELECTED(k, value, hash);
1763 + }
1764 +}
1765 +
1766 void facets_set_current_row_severity(FACETS *facets, FACET_ROW_SEVERITY severity) {
1767 facets->current_row.severity = severity;
1768 }
@@ -2437,16 +2482,18 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
2482 if(!k->values.enabled)
2483 continue;
2484
2440 - if(!facets_sort_and_reorder_values(k))
2441 - // no values for this key
2442 - continue;
2485 + facets_sort_and_reorder_values(k);
2486
2487 buffer_json_add_array_item_object(wb); // key
2488 {
2446 - buffer_json_member_add_string(wb, "id", hash_to_static_string(k->hash));
2447 - buffer_json_member_add_string(wb, "name", facets_key_name_cached(k
2448 - , facets->report.used_hashes_registry
2449 - ));
2489 + buffer_json_member_add_string(
2490 + wb, "id", hash_to_static_string(k->hash));
2491 +
2492 + buffer_json_member_add_string(
2493 + wb, "name",
2494 + facets_key_name_cached(k, facets->report.used_hashes_registry));
2495 +
2496 + buffer_json_member_add_string(wb, "raw", k->name);
2497
2498 if(!k->order) k->order = facets->order++;
2499 buffer_json_member_add_uint64(wb, "order", k->order);
@@ -2467,6 +2514,7 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
2514
2515 facets_key_value_transformed(facets, k, v, tb, FACETS_TRANSFORM_FACET);
2516 buffer_json_member_add_string(wb, "name", buffer_tostring(tb));
2517 + buffer_json_member_add_string(wb, "raw", v->name);
2518 buffer_json_member_add_uint64(wb, "count", v->final_facet_value_counter);
2519 buffer_json_member_add_uint64(wb, "order", v->order);
2520 }
src/libnetdata/facets/facets.h
+8 -1
@@ -23,6 +23,7 @@ typedef enum __attribute__((packed)) {
23 } FACETS_TRANSFORMATION_SCOPE;
24
25 typedef enum __attribute__((packed)) {
26 + FACET_KEY_OPTION_NONE = 0,
27 FACET_KEY_OPTION_FACET = (1 << 0), // filterable values
28 FACET_KEY_OPTION_NO_FACET = (1 << 1), // non-filterable value
29 FACET_KEY_OPTION_NEVER_FACET = (1 << 2), // never enable this field as facet
@@ -98,8 +99,14 @@ void facets_set_anchor(FACETS *facets, usec_t start_ut, usec_t stop_ut, FACETS_A
99 void facets_enable_slice_mode(FACETS *facets);
100 bool facets_row_candidate_to_keep(FACETS *facets, usec_t usec);
101
102 +void facets_reset_and_disable_all_facets(FACETS *facets);
103 +
104 +FACET_KEY *facets_register_facet(FACETS *facets, const char *name, FACET_KEY_OPTIONS options);
105 FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options);
102 -void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_id, FACET_KEY_OPTIONS options);
106 +
107 +void facets_register_facet_filter(FACETS *facets, const char *key, const char *value, FACET_KEY_OPTIONS options);
108 +void facets_register_facet_filter_id(FACETS *facets, const char *key_id, const char *value_id, FACET_KEY_OPTIONS options);
109 +
110 void facets_set_timeframe_and_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut);
111 void facets_set_timeframe_and_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut);
112
src/libnetdata/functions_evloop/functions_evloop.h
+4
@@ -71,6 +71,10 @@
71 #define PLUGINSD_KEYWORD_CONFIG_ACTION_STATUS "status"
72 #define PLUGINSD_FUNCTION_CONFIG "config"
73
74 +// claiming
75 +#define PLUGINSD_KEYWORD_NODE_ID "NODE_ID"
76 +#define PLUGINSD_KEYWORD_CLAIMED_ID "CLAIMED_ID"
77 +
78 typedef void (*functions_evloop_worker_execute_t)(const char *transaction, char *function, usec_t *stop_monotonic_ut,
79 bool *cancelled, BUFFER *payload, HTTP_ACCESS access,
80 const char *source, void *data);
src/libnetdata/http/http_access.c
+16 -16
@@ -3,24 +3,24 @@
3 #include "../libnetdata.h"
4
5 static struct {
6 - HTTP_USER_ROLE access;
6 + HTTP_USER_ROLE role;
7 const char *name;
8 } user_roles[] = {
9 - { .access = HTTP_USER_ROLE_NONE, .name = "none" },
10 - { .access = HTTP_USER_ROLE_ADMIN, .name = "admin" },
11 - { .access = HTTP_USER_ROLE_MANAGER, .name = "manager" },
12 - { .access = HTTP_USER_ROLE_TROUBLESHOOTER, .name = "troubleshooter" },
13 - { .access = HTTP_USER_ROLE_OBSERVER, .name = "observer" },
14 - { .access = HTTP_USER_ROLE_MEMBER, .name = "member" },
15 - { .access = HTTP_USER_ROLE_BILLING, .name = "billing" },
16 - { .access = HTTP_USER_ROLE_ANY, .name = "any" },
17 -
18 - { .access = HTTP_USER_ROLE_MEMBER, .name = "members" },
19 - { .access = HTTP_USER_ROLE_ADMIN, .name = "admins" },
20 - { .access = HTTP_USER_ROLE_ANY, .name = "all" },
9 + { .role = HTTP_USER_ROLE_NONE, .name = "none" },
10 + { .role = HTTP_USER_ROLE_ADMIN, .name = "admin" },
11 + { .role = HTTP_USER_ROLE_MANAGER, .name = "manager" },
12 + { .role = HTTP_USER_ROLE_TROUBLESHOOTER, .name = "troubleshooter" },
13 + { .role = HTTP_USER_ROLE_OBSERVER, .name = "observer" },
14 + { .role = HTTP_USER_ROLE_MEMBER, .name = "member" },
15 + { .role = HTTP_USER_ROLE_BILLING, .name = "billing" },
16 + { .role = HTTP_USER_ROLE_ANY, .name = "any" },
17 +
18 + { .role = HTTP_USER_ROLE_MEMBER, .name = "members" },
19 + { .role = HTTP_USER_ROLE_ADMIN, .name = "admins" },
20 + { .role = HTTP_USER_ROLE_ANY, .name = "all" },
21
22 // terminator
23 - { .access = 0, .name = NULL },
23 + { .role = 0, .name = NULL },
24 };
25
26 HTTP_USER_ROLE http_user_role2id(const char *role) {
@@ -29,7 +29,7 @@ HTTP_USER_ROLE http_user_role2id(const char *role) {
29
30 for(size_t i = 0; user_roles[i].name ;i++) {
31 if(strcmp(user_roles[i].name, role) == 0)
32 - return user_roles[i].access;
32 + return user_roles[i].role;
33 }
34
35 nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP user role '%s' is not valid", role);
@@ -38,7 +38,7 @@ HTTP_USER_ROLE http_user_role2id(const char *role) {
38
39 const char *http_id2user_role(HTTP_USER_ROLE role) {
40 for(size_t i = 0; user_roles[i].name ;i++) {
41 - if(role == user_roles[i].access)
41 + if(role == user_roles[i].role)
42 return user_roles[i].name;
43 }
44
src/libnetdata/http/http_access.h
+36 -16
@@ -93,12 +93,16 @@ typedef enum __attribute__((packed)) {
93 HTTP_ACL_WEBRTC = (1 << 6), // from WebRTC
94
95 // HTTP_ACL_API takes the following additional ACLs, based on pattern matching of the client IP
96 - HTTP_ACL_DASHBOARD = (1 << 10),
97 - HTTP_ACL_REGISTRY = (1 << 11),
98 - HTTP_ACL_BADGES = (1 << 12),
99 - HTTP_ACL_MANAGEMENT = (1 << 13),
100 - HTTP_ACL_STREAMING = (1 << 14),
101 - HTTP_ACL_NETDATACONF = (1 << 15),
96 + HTTP_ACL_METRICS = (1 << 10),
97 + HTTP_ACL_FUNCTIONS = (1 << 11),
98 + HTTP_ACL_NODES = (1 << 12),
99 + HTTP_ACL_ALERTS = (1 << 13),
100 + HTTP_ACL_DYNCFG = (1 << 14),
101 + HTTP_ACL_REGISTRY = (1 << 15),
102 + HTTP_ACL_BADGES = (1 << 16),
103 + HTTP_ACL_MANAGEMENT = (1 << 17),
104 + HTTP_ACL_STREAMING = (1 << 18),
105 + HTTP_ACL_NETDATACONF = (1 << 19),
106
107 // SSL related
108 HTTP_ACL_SSL_OPTIONAL = (1 << 28),
@@ -106,6 +110,14 @@ typedef enum __attribute__((packed)) {
110 HTTP_ACL_SSL_DEFAULT = (1 << 30),
111 } HTTP_ACL;
112
113 +#define HTTP_ACL_DASHBOARD (HTTP_ACL)( \
114 + HTTP_ACL_METRICS \
115 + | HTTP_ACL_FUNCTIONS \
116 + | HTTP_ACL_ALERTS \
117 + | HTTP_ACL_NODES \
118 + | HTTP_ACL_DYNCFG \
119 + )
120 +
121 #define HTTP_ACL_TRANSPORTS (HTTP_ACL)( \
122 HTTP_ACL_API \
123 | HTTP_ACL_API_UDP \
@@ -121,7 +133,11 @@ typedef enum __attribute__((packed)) {
133 )
134
135 #define HTTP_ACL_ALL_FEATURES (HTTP_ACL)( \
124 - HTTP_ACL_DASHBOARD \
136 + HTTP_ACL_METRICS \
137 + | HTTP_ACL_FUNCTIONS \
138 + | HTTP_ACL_NODES \
139 + | HTTP_ACL_ALERTS \
140 + | HTTP_ACL_DYNCFG \
141 | HTTP_ACL_REGISTRY \
142 | HTTP_ACL_BADGES \
143 | HTTP_ACL_MANAGEMENT \
@@ -129,20 +145,24 @@ typedef enum __attribute__((packed)) {
145 | HTTP_ACL_NETDATACONF \
146 )
147
148 +#define HTTP_ACL_ACLK_LICENSE_MANAGER (HTTP_ACL)( \
149 + HTTP_ACL_NODES \
150 +)
151 +
152 #ifdef NETDATA_DEV_MODE
153 #define ACL_DEV_OPEN_ACCESS HTTP_ACL_NOCHECK
154 #else
155 #define ACL_DEV_OPEN_ACCESS 0
156 #endif
157
138 -#define http_can_access_dashboard(w) ((w)->acl & HTTP_ACL_DASHBOARD)
139 -#define http_can_access_registry(w) ((w)->acl & HTTP_ACL_REGISTRY)
140 -#define http_can_access_badges(w) ((w)->acl & HTTP_ACL_BADGES)
141 -#define http_can_access_mgmt(w) ((w)->acl & HTTP_ACL_MANAGEMENT)
142 -#define http_can_access_stream(w) ((w)->acl & HTTP_ACL_STREAMING)
143 -#define http_can_access_netdataconf(w) ((w)->acl & HTTP_ACL_NETDATACONF)
144 -#define http_is_using_ssl_optional(w) ((w)->port_acl & HTTP_ACL_SSL_OPTIONAL)
145 -#define http_is_using_ssl_force(w) ((w)->port_acl & HTTP_ACL_SSL_FORCE)
146 -#define http_is_using_ssl_default(w) ((w)->port_acl & HTTP_ACL_SSL_DEFAULT)
158 +#define http_can_access_dashboard(w) (((w)->acl & HTTP_ACL_DASHBOARD) == HTTP_ACL_DASHBOARD)
159 +#define http_can_access_registry(w) (((w)->acl & HTTP_ACL_REGISTRY) == HTTP_ACL_REGISTRY)
160 +#define http_can_access_badges(w) (((w)->acl & HTTP_ACL_BADGES) == HTTP_ACL_BADGES)
161 +#define http_can_access_mgmt(w) (((w)->acl & HTTP_ACL_MANAGEMENT) == HTTP_ACL_MANAGEMENT)
162 +#define http_can_access_stream(w) (((w)->acl & HTTP_ACL_STREAMING) == HTTP_ACL_STREAMING)
163 +#define http_can_access_netdataconf(w) (((w)->acl & HTTP_ACL_NETDATACONF) == HTTP_ACL_NETDATACONF)
164 +#define http_is_using_ssl_optional(w) (((w)->port_acl & HTTP_ACL_SSL_OPTIONAL) == HTTP_ACL_SSL_OPTIONAL)
165 +#define http_is_using_ssl_force(w) (((w)->port_acl & HTTP_ACL_SSL_FORCE) == HTTP_ACL_SSL_FORCE)
166 +#define http_is_using_ssl_default(w) (((w)->port_acl & HTTP_ACL_SSL_DEFAULT) == HTTP_ACL_SSL_DEFAULT)
167
168 #endif //NETDATA_HTTP_ACCESS_H
src/libnetdata/inlined.h
+37
@@ -506,6 +506,43 @@ static inline int read_txt_file(const char *filename, char *buffer, size_t size)
506 return 0;
507 }
508
509 +static inline bool read_txt_file_to_buffer(const char *filename, BUFFER *wb, size_t max_size) {
510 + // Open the file
511 + int fd = open(filename, O_RDONLY | O_CLOEXEC);
512 + if (fd == -1)
513 + return false;
514 +
515 + // Get the file size
516 + struct stat st;
517 + if (fstat(fd, &st) == -1) {
518 + close(fd);
519 + return false;
520 + }
521 +
522 + size_t file_size = st.st_size;
523 +
524 + // Check if the file size exceeds the maximum allowed size
525 + if (file_size > max_size) {
526 + close(fd);
527 + return false; // File size too large
528 + }
529 +
530 + buffer_need_bytes(wb, file_size + 1);
531 +
532 + // Read the file contents into the buffer
533 + ssize_t r = read(fd, &wb->buffer[wb->len], file_size);
534 + if (r != (ssize_t)file_size) {
535 + close(fd);
536 + return false; // Read error
537 + }
538 + wb->len = r;
539 +
540 + // Close the file descriptor
541 + close(fd);
542 +
543 + return true; // Success
544 +}
545 +
546 static inline int read_proc_cmdline(const char *filename, char *buffer, size_t size) {
547 if (unlikely(!size)) return 3;
548
src/libnetdata/json/json-c-parser-inline.c new
+49
@@ -0,0 +1,49 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "../libnetdata.h"
4 +
5 +int rrd_call_function_error(BUFFER *wb, const char *msg, int code) {
6 + buffer_reset(wb);
7 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
8 + buffer_json_member_add_int64(wb, "status", code);
9 + buffer_json_member_add_string(wb, "error_message", msg);
10 + buffer_json_finalize(wb);
11 + return code;
12 +}
13 +
14 +struct json_object *json_parse_function_payload_or_error(BUFFER *output, BUFFER *payload, int *code, json_parse_function_payload_t cb, void *cb_data) {
15 + if(!payload || !buffer_strlen(payload)) {
16 + *code = rrd_call_function_error(output, "No payload given", HTTP_RESP_BAD_REQUEST);
17 + return NULL;
18 + }
19 +
20 + struct json_tokener *tokener = json_tokener_new();
21 + if (!tokener) {
22 + *code = rrd_call_function_error(output, "Cannot initialize json parser", HTTP_RESP_INTERNAL_SERVER_ERROR);
23 + return NULL;
24 + }
25 +
26 + struct json_object *jobj = json_tokener_parse_ex(tokener, buffer_tostring(payload), (int)buffer_strlen(payload));
27 + if (json_tokener_get_error(tokener) != json_tokener_success) {
28 + const char *error_msg = json_tokener_error_desc(json_tokener_get_error(tokener));
29 + char tmp[strlen(error_msg) + 100];
30 + snprintf(tmp, sizeof(tmp), "JSON parser failed: %s", error_msg);
31 + json_tokener_free(tokener);
32 + *code = rrd_call_function_error(output, tmp, HTTP_RESP_INTERNAL_SERVER_ERROR);
33 + return NULL;
34 + }
35 + json_tokener_free(tokener);
36 +
37 + CLEAN_BUFFER *error = buffer_create(0, NULL);
38 + if(!cb(jobj, "", cb_data, error)) {
39 + char tmp[buffer_strlen(error) + 100];
40 + snprintfz(tmp, sizeof(tmp), "JSON parser failed: %s", buffer_tostring(error));
41 + *code = rrd_call_function_error(output, tmp, HTTP_RESP_BAD_REQUEST);
42 + json_object_put(jobj);
43 + return NULL;
44 + }
45 +
46 + *code = HTTP_RESP_OK;
47 +
48 + return jobj;
49 +}
src/libnetdata/json/json-c-parser-inline.h
+52 -3
@@ -25,6 +25,32 @@
25 } \
26 } while(0)
27
28 +#define JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
29 + json_object *_j; \
30 + if (json_object_object_get_ex(jobj, member, &_j) && json_object_is_type(_j, json_type_string)) { \
31 + freez((void *)dst); \
32 + dst = strdupz(json_object_get_string(_j)); \
33 + } \
34 + else if(required) { \
35 + buffer_sprintf(error, "missing or invalid type for '%s.%s' string", path, member); \
36 + return false; \
37 + } \
38 +} while(0)
39 +
40 +#define JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
41 + json_object *_j; \
42 + if (json_object_object_get_ex(jobj, member, &_j) && json_object_is_type(_j, json_type_string)) { \
43 + if(uuid_parse(json_object_get_string(_j), dst) != 0 && required) { \
44 + buffer_sprintf(error, "invalid UUID '%s.%s'", path, member); \
45 + return false; \
46 + } \
47 + } \
48 + else if(required) { \
49 + buffer_sprintf(error, "missing UUID '%s.%s'", path, member); \
50 + return false; \
51 + } \
52 +} while(0)
53 +
54 #define JSONC_PARSE_TXT2BUFFER_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
55 json_object *_j; \
56 if (json_object_object_get_ex(jobj, member, &_j) && json_object_is_type(_j, json_type_string)) { \
@@ -122,11 +148,30 @@
148 } \
149 } while(0)
150
125 -#define JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
151 +#define JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
152 + json_object *_j; \
153 + if (json_object_object_get_ex(jobj, member, &_j)) { \
154 + if (_j != NULL && json_object_is_type(_j, json_type_int)) \
155 + dst = json_object_get_int64(_j); \
156 + else if (_j != NULL && json_object_is_type(_j, json_type_double)) \
157 + dst = (typeof(dst))json_object_get_double(_j); \
158 + else if (_j == NULL) \
159 + dst = 0; \
160 + else { \
161 + buffer_sprintf(error, "not supported type (expected int) for '%s.%s'", path, member); \
162 + return false; \
163 + } \
164 + } else if(required) { \
165 + buffer_sprintf(error, "missing or invalid type (expected int value or null) for '%s.%s'", path, member);\
166 + return false; \
167 + } \
168 +} while(0)
169 +
170 +#define JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
171 json_object *_j; \
172 if (json_object_object_get_ex(jobj, member, &_j)) { \
173 if (_j != NULL && json_object_is_type(_j, json_type_int)) \
129 - dst = json_object_get_int(_j); \
174 + dst = json_object_get_uint64(_j); \
175 else if (_j != NULL && json_object_is_type(_j, json_type_double)) \
176 dst = (typeof(dst))json_object_get_double(_j); \
177 else if (_j == NULL) \
@@ -136,7 +181,7 @@
181 return false; \
182 } \
183 } else if(required) { \
139 - buffer_sprintf(error, "missing or invalid type (expected int value or null) for '%s.%s'", path, member); \
184 + buffer_sprintf(error, "missing or invalid type (expected int value or null) for '%s.%s'", path, member);\
185 return false; \
186 } \
187 } while(0)
@@ -174,4 +219,8 @@
219 } \
220 } while(0)
221
222 +typedef bool (*json_parse_function_payload_t)(json_object *jobj, const char *path, void *data, BUFFER *error);
223 +int rrd_call_function_error(BUFFER *wb, const char *msg, int code);
224 +struct json_object *json_parse_function_payload_or_error(BUFFER *output, BUFFER *payload, int *code, json_parse_function_payload_t cb, void *cb_data);
225 +
226 #endif //NETDATA_JSON_C_PARSER_INLINE_H
src/libnetdata/libnetdata.c
+9 -317
@@ -1302,315 +1302,6 @@ int snprintfz(char *dst, size_t n, const char *fmt, ...) {
1302 return ret;
1303 }
1304
1305 -static int is_procfs(const char *path, char **reason) {
1306 -#if defined(__APPLE__) || defined(__FreeBSD__)
1307 - (void)path;
1308 - (void)reason;
1309 -#else
1310 - struct statfs stat;
1311 -
1312 - if (statfs(path, &stat) == -1) {
1313 - if (reason)
1314 - *reason = "failed to statfs()";
1315 - return -1;
1316 - }
1317 -
1318 -#if defined PROC_SUPER_MAGIC
1319 - if (stat.f_type != PROC_SUPER_MAGIC) {
1320 - if (reason)
1321 - *reason = "type is not procfs";
1322 - return -1;
1323 - }
1324 -#endif
1325 -
1326 -#endif
1327 -
1328 - return 0;
1329 -}
1330 -
1331 -static int is_sysfs(const char *path, char **reason) {
1332 -#if defined(__APPLE__) || defined(__FreeBSD__)
1333 - (void)path;
1334 - (void)reason;
1335 -#else
1336 - struct statfs stat;
1337 -
1338 - if (statfs(path, &stat) == -1) {
1339 - if (reason)
1340 - *reason = "failed to statfs()";
1341 - return -1;
1342 - }
1343 -
1344 -#if defined SYSFS_MAGIC
1345 - if (stat.f_type != SYSFS_MAGIC) {
1346 - if (reason)
1347 - *reason = "type is not sysfs";
1348 - return -1;
1349 - }
1350 -#endif
1351 -
1352 -#endif
1353 -
1354 - return 0;
1355 -}
1356 -
1357 -int verify_netdata_host_prefix(bool log_msg) {
1358 - if(!netdata_configured_host_prefix)
1359 - netdata_configured_host_prefix = "";
1360 -
1361 - if(!*netdata_configured_host_prefix)
1362 - return 0;
1363 -
1364 - char buffer[FILENAME_MAX + 1];
1365 - char *path = netdata_configured_host_prefix;
1366 - char *reason = "unknown reason";
1367 - errno_clear();
1368 -
1369 - struct stat sb;
1370 - if (stat(path, &sb) == -1) {
1371 - reason = "failed to stat()";
1372 - goto failed;
1373 - }
1374 -
1375 - if((sb.st_mode & S_IFMT) != S_IFDIR) {
1376 - errno = EINVAL;
1377 - reason = "is not a directory";
1378 - goto failed;
1379 - }
1380 -
1381 - path = buffer;
1382 - snprintfz(path, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
1383 - if(is_procfs(path, &reason) == -1)
1384 - goto failed;
1385 -
1386 - snprintfz(path, FILENAME_MAX, "%s/sys", netdata_configured_host_prefix);
1387 - if(is_sysfs(path, &reason) == -1)
1388 - goto failed;
1389 -
1390 - if (netdata_configured_host_prefix && *netdata_configured_host_prefix) {
1391 - if (log_msg)
1392 - netdata_log_info("Using host prefix directory '%s'", netdata_configured_host_prefix);
1393 - }
1394 -
1395 - return 0;
1396 -
1397 -failed:
1398 - if (log_msg)
1399 - netdata_log_error("Ignoring host prefix '%s': path '%s' %s", netdata_configured_host_prefix, path, reason);
1400 - netdata_configured_host_prefix = "";
1401 - return -1;
1402 -}
1403 -
1404 -char *strdupz_path_subpath(const char *path, const char *subpath) {
1405 - if(unlikely(!path || !*path)) path = ".";
1406 - if(unlikely(!subpath)) subpath = "";
1407 -
1408 - // skip trailing slashes in path
1409 - size_t len = strlen(path);
1410 - while(len > 0 && path[len - 1] == '/') len--;
1411 -
1412 - // skip leading slashes in subpath
1413 - while(subpath[0] == '/') subpath++;
1414 -
1415 - // if the last character in path is / and (there is a subpath or path is now empty)
1416 - // keep the trailing slash in path and remove the additional slash
1417 - char *slash = "/";
1418 - if(path[len] == '/' && (*subpath || len == 0)) {
1419 - slash = "";
1420 - len++;
1421 - }
1422 - else if(!*subpath) {
1423 - // there is no subpath
1424 - // no need for trailing slash
1425 - slash = "";
1426 - }
1427 -
1428 - char buffer[FILENAME_MAX + 1];
1429 - snprintfz(buffer, FILENAME_MAX, "%.*s%s%s", (int)len, path, slash, subpath);
1430 - return strdupz(buffer);
1431 -}
1432 -
1433 -int path_is_dir(const char *path, const char *subpath) {
1434 - char *s = strdupz_path_subpath(path, subpath);
1435 -
1436 - size_t max_links = 100;
1437 -
1438 - int is_dir = 0;
1439 - struct stat statbuf;
1440 - while(max_links-- && stat(s, &statbuf) == 0) {
1441 - if((statbuf.st_mode & S_IFMT) == S_IFDIR) {
1442 - is_dir = 1;
1443 - break;
1444 - }
1445 - else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
1446 - char buffer[FILENAME_MAX + 1];
1447 - ssize_t l = readlink(s, buffer, FILENAME_MAX);
1448 - if(l > 0) {
1449 - buffer[l] = '\0';
1450 - freez(s);
1451 - s = strdupz(buffer);
1452 - continue;
1453 - }
1454 - else {
1455 - is_dir = 0;
1456 - break;
1457 - }
1458 - }
1459 - else {
1460 - is_dir = 0;
1461 - break;
1462 - }
1463 - }
1464 -
1465 - freez(s);
1466 - return is_dir;
1467 -}
1468 -
1469 -int path_is_file(const char *path, const char *subpath) {
1470 - char *s = strdupz_path_subpath(path, subpath);
1471 -
1472 - size_t max_links = 100;
1473 -
1474 - int is_file = 0;
1475 - struct stat statbuf;
1476 - while(max_links-- && stat(s, &statbuf) == 0) {
1477 - if((statbuf.st_mode & S_IFMT) == S_IFREG) {
1478 - is_file = 1;
1479 - break;
1480 - }
1481 - else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
1482 - char buffer[FILENAME_MAX + 1];
1483 - ssize_t l = readlink(s, buffer, FILENAME_MAX);
1484 - if(l > 0) {
1485 - buffer[l] = '\0';
1486 - freez(s);
1487 - s = strdupz(buffer);
1488 - continue;
1489 - }
1490 - else {
1491 - is_file = 0;
1492 - break;
1493 - }
1494 - }
1495 - else {
1496 - is_file = 0;
1497 - break;
1498 - }
1499 - }
1500 -
1501 - freez(s);
1502 - return is_file;
1503 -}
1504 -
1505 -void recursive_config_double_dir_load(const char *user_path, const char *stock_path, const char *subpath, int (*callback)(const char *filename, void *data, bool stock_config), void *data, size_t depth) {
1506 - if(depth > 3) {
1507 - netdata_log_error("CONFIG: Max directory depth reached while reading user path '%s', stock path '%s', subpath '%s'", user_path, stock_path, subpath);
1508 - return;
1509 - }
1510 -
1511 - if(!stock_path)
1512 - stock_path = user_path;
1513 -
1514 - char *udir = strdupz_path_subpath(user_path, subpath);
1515 - char *sdir = strdupz_path_subpath(stock_path, subpath);
1516 -
1517 - netdata_log_debug(D_HEALTH, "CONFIG traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
1518 -
1519 - DIR *dir = opendir(udir);
1520 - if (!dir) {
1521 - netdata_log_error("CONFIG cannot open user-config directory '%s'.", udir);
1522 - }
1523 - else {
1524 - struct dirent *de = NULL;
1525 - while((de = readdir(dir))) {
1526 - if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
1527 - if( !de->d_name[0] ||
1528 - (de->d_name[0] == '.' && de->d_name[1] == '\0') ||
1529 - (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
1530 - ) {
1531 - netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config directory '%s/%s'", udir, de->d_name);
1532 - continue;
1533 - }
1534 -
1535 - if(path_is_dir(udir, de->d_name)) {
1536 - recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
1537 - continue;
1538 - }
1539 - }
1540 -
1541 - if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
1542 - size_t len = strlen(de->d_name);
1543 - if(path_is_file(udir, de->d_name) &&
1544 - len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
1545 - char *filename = strdupz_path_subpath(udir, de->d_name);
1546 - netdata_log_debug(D_HEALTH, "CONFIG calling callback for user file '%s'", filename);
1547 - callback(filename, data, false);
1548 - freez(filename);
1549 - continue;
1550 - }
1551 - }
1552 -
1553 - netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
1554 - }
1555 -
1556 - closedir(dir);
1557 - }
1558 -
1559 - netdata_log_debug(D_HEALTH, "CONFIG traversing stock config directory '%s', user config directory '%s'", sdir, udir);
1560 -
1561 - dir = opendir(sdir);
1562 - if (!dir) {
1563 - netdata_log_error("CONFIG cannot open stock config directory '%s'.", sdir);
1564 - }
1565 - else {
1566 - if (strcmp(udir, sdir)) {
1567 - struct dirent *de = NULL;
1568 - while((de = readdir(dir))) {
1569 - if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
1570 - if( !de->d_name[0] ||
1571 - (de->d_name[0] == '.' && de->d_name[1] == '\0') ||
1572 - (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
1573 - ) {
1574 - netdata_log_debug(D_HEALTH, "CONFIG ignoring stock config directory '%s/%s'", sdir, de->d_name);
1575 - continue;
1576 - }
1577 -
1578 - if(path_is_dir(sdir, de->d_name)) {
1579 - // we recurse in stock subdirectory, only when there is no corresponding
1580 - // user subdirectory - to avoid reading the files twice
1581 -
1582 - if(!path_is_dir(udir, de->d_name))
1583 - recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
1584 -
1585 - continue;
1586 - }
1587 - }
1588 -
1589 - if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
1590 - size_t len = strlen(de->d_name);
1591 - if(path_is_file(sdir, de->d_name) && !path_is_file(udir, de->d_name) &&
1592 - len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
1593 - char *filename = strdupz_path_subpath(sdir, de->d_name);
1594 - netdata_log_debug(D_HEALTH, "CONFIG calling callback for stock file '%s'", filename);
1595 - callback(filename, data, true);
1596 - freez(filename);
1597 - continue;
1598 - }
1599 -
1600 - }
1601 -
1602 - netdata_log_debug(D_HEALTH, "CONFIG ignoring stock-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
1603 - }
1604 - }
1605 - closedir(dir);
1606 - }
1607 -
1608 - netdata_log_debug(D_HEALTH, "CONFIG done traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
1609 -
1610 - freez(udir);
1611 - freez(sdir);
1612 -}
1613 -
1305 // Returns the number of bytes read from the file if file_size is not NULL.
1306 // The actual buffer has an extra byte set to zero (not included in the count).
1307 char *read_by_filename(const char *filename, long *file_size)
@@ -1618,34 +1309,37 @@ char *read_by_filename(const char *filename, long *file_size)
1309 FILE *f = fopen(filename, "r");
1310 if (!f)
1311 return NULL;
1312 +
1313 if (fseek(f, 0, SEEK_END) < 0) {
1314 fclose(f);
1315 return NULL;
1316 }
1317 +
1318 long size = ftell(f);
1319 if (size <= 0 || fseek(f, 0, SEEK_END) < 0) {
1320 fclose(f);
1321 return NULL;
1322 }
1323 +
1324 char *contents = callocz(size + 1, 1);
1631 - if (!contents) {
1632 - fclose(f);
1633 - return NULL;
1634 - }
1325 if (fseek(f, 0, SEEK_SET) < 0) {
1326 fclose(f);
1327 freez(contents);
1328 return NULL;
1329 }
1330 +
1331 size_t res = fread(contents, 1, size, f);
1332 if ( res != (size_t)size) {
1333 freez(contents);
1334 fclose(f);
1335 return NULL;
1336 }
1337 +
1338 fclose(f);
1339 +
1340 if (file_size)
1341 *file_size = size;
1342 +
1343 return contents;
1344 }
1345
@@ -1685,7 +1379,7 @@ BUFFER *run_command_and_get_output_to_buffer(const char *command, int max_line_l
1379 POPEN_INSTANCE *pi = spawn_popen_run(command);
1380 if(pi) {
1381 char buffer[max_line_length + 1];
1688 - while (fgets(buffer, max_line_length, pi->child_stdout_fp)) {
1382 + while (fgets(buffer, max_line_length, spawn_popen_stdout(pi))) {
1383 buffer[max_line_length] = '\0';
1384 buffer_strcat(wb, buffer);
1385 }
@@ -1705,7 +1399,7 @@ bool run_command_and_copy_output_to_stdout(const char *command, int max_line_len
1399 if(pi) {
1400 char buffer[max_line_length + 1];
1401
1708 - while (fgets(buffer, max_line_length, pi->child_stdout_fp))
1402 + while (fgets(buffer, max_line_length, spawn_popen_stdout(pi)))
1403 fprintf(stdout, "%s", buffer);
1404
1405 spawn_popen_kill(pi);
@@ -1831,7 +1525,6 @@ void timing_action(TIMING_ACTION action, TIMING_STEP step) {
1525 }
1526 }
1527
1834 -#ifdef ENABLE_HTTPS
1528 int hash256_string(const unsigned char *string, size_t size, char *hash) {
1529 EVP_MD_CTX *ctx;
1530 ctx = EVP_MD_CTX_create();
@@ -1856,7 +1549,6 @@ int hash256_string(const unsigned char *string, size_t size, char *hash) {
1549 EVP_MD_CTX_destroy(ctx);
1550 return 1;
1551 }
1859 -#endif
1552
1553
1554 bool rrdr_relative_window_to_absolute(time_t *after, time_t *before, time_t now) {
src/libnetdata/libnetdata.h
+2 -18
@@ -9,10 +9,6 @@ extern "C" {
9
10 #include "config.h"
11
12 -#ifdef ENABLE_OPENSSL
13 -#define ENABLE_HTTPS 1
14 -#endif
15 -
12 #ifdef HAVE_LIBDATACHANNEL
13 #define ENABLE_WEBRTC 1
14 #endif
@@ -331,6 +327,7 @@ size_t judy_aral_structures(void);
327
328 #include "linked-lists.h"
329 #include "storage-point.h"
330 +#include "paths/paths.h"
331
332 void netdata_fix_chart_id(char *s);
333 void netdata_fix_chart_name(char *s);
@@ -394,17 +391,6 @@ int verify_netdata_host_prefix(bool log_msg);
391
392 extern volatile sig_atomic_t netdata_exit;
393
397 -char *strdupz_path_subpath(const char *path, const char *subpath);
398 -int path_is_dir(const char *path, const char *subpath);
399 -int path_is_file(const char *path, const char *subpath);
400 -void recursive_config_double_dir_load(
401 - const char *user_path
402 - , const char *stock_path
403 - , const char *subpath
404 - , int (*callback)(const char *filename, void *data, bool stock_config)
405 - , void *data
406 - , size_t depth
407 -);
394 char *read_by_filename(const char *filename, long *file_size);
395 char *find_and_replace(const char *src, const char *find, const char *replace, const char *where);
396
@@ -479,9 +465,7 @@ extern char *netdata_configured_host_prefix;
465 #include "spawn_server/spawn_server.h"
466 #include "spawn_server/spawn_popen.h"
467 #include "simple_pattern/simple_pattern.h"
482 -#ifdef ENABLE_HTTPS
483 -# include "socket/security.h"
484 -#endif
468 +#include "socket/security.h"
469 #include "socket/socket.h"
470 #include "config/appconfig.h"
471 #include "log/journal.h"
src/libnetdata/locks/README.md
+7 -7
@@ -58,13 +58,13 @@ If any call is expected to pause the caller (ie the caller is attempting a read
58 ```
59 RW_LOCK ON LOCK 0x0x5651c9fcce20: 4190039 'HEALTH' (function health_execute_pending_updates() 661@health/health.c) WANTS a 'W' lock (while holding 1 rwlocks and 1 mutexes).
60 There are 7 readers and 0 writers are holding the lock:
61 - => 1: RW_LOCK: process 4190091 'WEB_SERVER[static14]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709847 usec.
62 - => 2: RW_LOCK: process 4190079 'WEB_SERVER[static6]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709869 usec.
63 - => 3: RW_LOCK: process 4190084 'WEB_SERVER[static10]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709948 usec.
64 - => 4: RW_LOCK: process 4190076 'WEB_SERVER[static3]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710190 usec.
65 - => 5: RW_LOCK: process 4190092 'WEB_SERVER[static15]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710195 usec.
66 - => 6: RW_LOCK: process 4190077 'WEB_SERVER[static4]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710208 usec.
67 - => 7: RW_LOCK: process 4190044 'WEB_SERVER[static1]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710221 usec.
61 + => 1: RW_LOCK: process 4190091 'WEB_SERVER[static14]' (function api_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709847 usec.
62 + => 2: RW_LOCK: process 4190079 'WEB_SERVER[static6]' (function api_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709869 usec.
63 + => 3: RW_LOCK: process 4190084 'WEB_SERVER[static10]' (function api_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709948 usec.
64 + => 4: RW_LOCK: process 4190076 'WEB_SERVER[static3]' (function api_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710190 usec.
65 + => 5: RW_LOCK: process 4190092 'WEB_SERVER[static15]' (function api_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710195 usec.
66 + => 6: RW_LOCK: process 4190077 'WEB_SERVER[static4]' (function api_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710208 usec.
67 + => 7: RW_LOCK: process 4190044 'WEB_SERVER[static1]' (function api_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710221 usec.
68 ```
69
70 And each of the above is paired with a `GOT` log, like this:
src/libnetdata/log/log.c
+12 -16
@@ -27,12 +27,8 @@
27 #endif
28
29 const char *program_name = "";
30 -
30 uint64_t debug_flags = 0;
32 -
33 -#ifdef ENABLE_ACLK
31 int aclklog_enabled = 0;
35 -#endif
32
33 // ----------------------------------------------------------------------------
34
@@ -467,7 +463,7 @@ static struct {
463 .spinlock = NETDATA_SPINLOCK_INITIALIZER,
464 .method = NDLM_DEFAULT,
465 .format = NDLF_LOGFMT,
470 - .filename = LOG_DIR "/collectors.log",
466 + .filename = LOG_DIR "/collector.log",
467 .fd = STDERR_FILENO,
468 .fp = NULL,
469 .min_priority = NDLP_INFO,
@@ -518,7 +514,7 @@ __attribute__((constructor)) void initialize_invocation_id(void) {
514
515 char uuid[UUID_COMPACT_STR_LEN];
516 uuid_unparse_lower_compact(nd_log.invocation_id, uuid);
521 - setenv("NETDATA_INVOCATION_ID", uuid, 1);
517 + nd_setenv("NETDATA_INVOCATION_ID", uuid, 1);
518 }
519
520 int nd_log_health_fd(void) {
@@ -658,9 +654,9 @@ void nd_log_set_user_settings(ND_LOG_SOURCES source, const char *setting) {
654 else
655 method = NDLM_STDERR;
656
661 - setenv("NETDATA_LOG_METHOD", nd_log_id2method(method), 1);
662 - setenv("NETDATA_LOG_FORMAT", nd_log_id2format(format), 1);
663 - setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
657 + nd_setenv("NETDATA_LOG_METHOD", nd_log_id2method(method), 1);
658 + nd_setenv("NETDATA_LOG_FORMAT", nd_log_id2format(format), 1);
659 + nd_setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
660 }
661 }
662
@@ -680,7 +676,7 @@ void nd_log_set_priority_level(const char *setting) {
676 }
677
678 // the right one
683 - setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
679 + nd_setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
680 }
681
682 void nd_log_set_facility(const char *facility) {
@@ -688,7 +684,7 @@ void nd_log_set_facility(const char *facility) {
684 facility = "daemon";
685
686 nd_log.syslog.facility = nd_log_facility2id(facility);
691 - setenv("NETDATA_SYSLOG_FACILITY", nd_log_id2facility(nd_log.syslog.facility), 1);
687 + nd_setenv("NETDATA_SYSLOG_FACILITY", nd_log_id2facility(nd_log.syslog.facility), 1);
688 }
689
690 void nd_log_set_flood_protection(size_t logs, time_t period) {
@@ -702,9 +698,9 @@ void nd_log_set_flood_protection(size_t logs, time_t period) {
698
699 char buf[100];
700 snprintfz(buf, sizeof(buf), "%" PRIu64, (uint64_t )period);
705 - setenv("NETDATA_ERRORS_THROTTLE_PERIOD", buf, 1);
701 + nd_setenv("NETDATA_ERRORS_THROTTLE_PERIOD", buf, 1);
702 snprintfz(buf, sizeof(buf), "%" PRIu64, (uint64_t )logs);
707 - setenv("NETDATA_ERRORS_PER_PERIOD", buf, 1);
703 + nd_setenv("NETDATA_ERRORS_PER_PERIOD", buf, 1);
704 }
705
706 static bool nd_log_journal_systemd_init(void) {
@@ -719,7 +715,7 @@ static bool nd_log_journal_systemd_init(void) {
715
716 static void nd_log_journal_direct_set_env(void) {
717 if(nd_log.sources[NDLS_COLLECTORS].method == NDLM_JOURNAL)
722 - setenv("NETDATA_SYSTEMD_JOURNAL_PATH", nd_log.journal_direct.filename, 1);
718 + nd_setenv("NETDATA_SYSTEMD_JOURNAL_PATH", nd_log.journal_direct.filename, 1);
719 }
720
721 static bool nd_log_journal_direct_init(const char *path) {
@@ -776,8 +772,8 @@ static void nd_log_syslog_init() {
772 void nd_log_initialize_for_external_plugins(const char *name) {
773 // if we don't run under Netdata, log to stderr,
774 // otherwise, use the logging method Netdata wants us to use.
779 - setenv("NETDATA_LOG_METHOD", "stderr", 0);
780 - setenv("NETDATA_LOG_FORMAT", "logfmt", 0);
775 + nd_setenv("NETDATA_LOG_METHOD", "stderr", 0);
776 + nd_setenv("NETDATA_LOG_FORMAT", "logfmt", 0);
777
778 nd_log.overwrite_process_source = NDLS_COLLECTORS;
779 program_name = name;
src/libnetdata/log/log.h
+1 -5
@@ -16,7 +16,7 @@ typedef enum __attribute__((__packed__)) {
16 NDLS_UNSET = 0, // internal use only
17 NDLS_ACCESS, // access.log
18 NDLS_ACLK, // aclk.log
19 - NDLS_COLLECTORS, // collectors.log
19 + NDLS_COLLECTORS, // collector.log
20 NDLS_DAEMON, // error.log
21 NDLS_HEALTH, // health.log
22 NDLS_DEBUG, // debug.log
@@ -238,12 +238,8 @@ void log_stack_push(struct log_stack_entry *lgs);
238 #define D_SYSTEM 0x8000000000000000
239
240 extern uint64_t debug_flags;
241 -
241 extern const char *program_name;
243 -
244 -#ifdef ENABLE_ACLK
242 extern int aclklog_enabled;
246 -#endif
243
244 #define LOG_DATE_LENGTH 26
245 void log_date(char *buffer, size_t len, time_t now);
src/libnetdata/maps/local-sockets.h
+6 -4
@@ -223,7 +223,7 @@ typedef struct local_socket {
223 #endif
224 } LOCAL_SOCKET;
225
226 -static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request);
226 +static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request);
227
228 // --------------------------------------------------------------------------------------------------------------------
229
@@ -1145,7 +1145,7 @@ static inline void local_sockets_send_to_parent(struct local_socket_state *ls __
1145 local_sockets_log(ls, "failed to write cmdline to pipe");
1146 }
1147
1148 -static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
1148 +static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
1149 LS_STATE ls = { 0 };
1150 ls.config = *((struct local_sockets_config *)request->data);
1151
@@ -1172,7 +1172,7 @@ static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
1172 // switch namespace using the custom fd passed via the spawn server
1173 if (setns(request->fds[3], CLONE_NEWNET) == -1) {
1174 local_sockets_log(&ls, "failed to switch network namespace at child process using fd %d", request->fds[3]);
1175 - exit(EXIT_FAILURE);
1175 + return EXIT_FAILURE;
1176 }
1177
1178 // read all sockets from /proc
@@ -1187,7 +1187,9 @@ static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
1187 };
1188 local_sockets_send_to_parent(&ls, &zero, &cw);
1189
1190 - exit(EXIT_SUCCESS);
1190 + local_sockets_cleanup(&ls);
1191 +
1192 + return EXIT_SUCCESS;
1193 }
1194
1195 static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, struct pid_socket *ps) {
src/libnetdata/os/close_range.c
+24 -9
@@ -7,6 +7,12 @@ static int fd_is_valid(int fd) {
7 return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
8 }
9
10 +static void setcloexec(int fd) {
11 + int flags = fcntl(fd, F_GETFD);
12 + if (flags != -1)
13 + (void) fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
14 +}
15 +
16 int os_get_fd_open_max(void) {
17 static int fd_open_max = CLOSE_RANGE_FD_MAX;
18
@@ -33,9 +39,9 @@ int os_get_fd_open_max(void) {
39 return fd_open_max;
40 }
41
36 -void os_close_range(int first, int last) {
42 +void os_close_range(int first, int last, int flags) {
43 #if defined(HAVE_CLOSE_RANGE)
38 - if(close_range(first, last, 0) == 0) return;
44 + if(close_range(first, last, flags) == 0) return;
45 #endif
46
47 #if defined(OS_LINUX)
@@ -44,8 +50,12 @@ void os_close_range(int first, int last) {
50 struct dirent *entry;
51 while ((entry = readdir(dir)) != NULL) {
52 int fd = str2i(entry->d_name);
47 - if (fd >= first && (last == CLOSE_RANGE_FD_MAX || fd <= last) && fd_is_valid(fd))
48 - (void)close(fd);
53 + if (fd >= first && (last == CLOSE_RANGE_FD_MAX || fd <= last) && fd_is_valid(fd)) {
54 + if(flags & CLOSE_RANGE_CLOEXEC)
55 + setcloexec(fd);
56 + else
57 + (void)close(fd);
58 + }
59 }
60 closedir(dir);
61 return;
@@ -57,7 +67,12 @@ void os_close_range(int first, int last) {
67 last = os_get_fd_open_max();
68
69 for (int fd = first; fd <= last; fd++) {
60 - if (fd_is_valid(fd)) (void)close(fd);
70 + if (fd_is_valid(fd)) {
71 + if(flags & CLOSE_RANGE_CLOEXEC)
72 + setcloexec(fd);
73 + else
74 + (void)close(fd);
75 + }
76 }
77 }
78
@@ -67,9 +82,9 @@ static int compare_ints(const void *a, const void *b) {
82 return (int_a > int_b) - (int_a < int_b);
83 }
84
70 -void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num) {
85 +void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num, int flags) {
86 if (fds_num == 0 || fds == NULL) {
72 - os_close_range(STDERR_FILENO + 1, CLOSE_RANGE_FD_MAX);
87 + os_close_range(STDERR_FILENO + 1, CLOSE_RANGE_FD_MAX, flags);
88 return;
89 }
90
@@ -89,10 +104,10 @@ void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num) {
104 // call os_close_range() as many times as needed
105 for (; i < fds_num; i++) {
106 if (fds_copy[i] > start)
92 - os_close_range(start, fds_copy[i] - 1);
107 + os_close_range(start, fds_copy[i] - 1, flags);
108
109 start = fds_copy[i] + 1;
110 }
111
97 - os_close_range(start, CLOSE_RANGE_FD_MAX);
112 + os_close_range(start, CLOSE_RANGE_FD_MAX, flags);
113 }
src/libnetdata/os/close_range.h
+10 -2
@@ -5,8 +5,16 @@
5
6 #define CLOSE_RANGE_FD_MAX (int)(~0U)
7
8 +#ifndef CLOSE_RANGE_UNSHARE
9 +#define CLOSE_RANGE_UNSHARE (1U << 1)
10 +#endif
11 +
12 +#ifndef CLOSE_RANGE_CLOEXEC
13 +#define CLOSE_RANGE_CLOEXEC (1U << 2)
14 +#endif
15 +
16 int os_get_fd_open_max(void);
9 -void os_close_range(int first, int last);
10 -void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num);
17 +void os_close_range(int first, int last, int flags);
18 +void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num, int flags);
19
20 #endif //CLOSE_RANGE_H
src/libnetdata/os/setenv.c
+23 -2
@@ -2,12 +2,15 @@
2
3 #include "config.h"
4
5 -#ifndef HAVE_SETENV
6 -
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8
9 +#if defined(OS_WINDOWS)
10 +#include <windows.h>
11 +#endif
12 +
13 +#ifndef HAVE_SETENV
14 int os_setenv(const char *name, const char *value, int overwrite) {
15 char *env_var;
16 int result;
@@ -28,3 +31,21 @@ int os_setenv(const char *name, const char *value, int overwrite) {
31 }
32
33 #endif
34 +
35 +void nd_setenv(const char *name, const char *value, int overwrite) {
36 +#if defined(OS_WINDOWS)
37 + if(overwrite)
38 + SetEnvironmentVariable(name, value);
39 + else {
40 + char buf[1024];
41 + if(GetEnvironmentVariable(name, buf, sizeof(buf)) == 0)
42 + SetEnvironmentVariable(name, value);
43 + }
44 +#endif
45 +
46 +#ifdef HAVE_SETENV
47 + setenv(name, value, overwrite);
48 +#else
49 + os_setenv(name, value, overwite);
50 +#endif
51 +}
src/libnetdata/os/setenv.h
+2
@@ -10,4 +10,6 @@ int os_setenv(const char *name, const char *value, int overwrite);
10 #define setenv(name, value, overwrite) os_setenv(name, value, overwrite)
11 #endif
12
13 +void nd_setenv(const char *name, const char *value, int overwrite);
14 +
15 #endif //NETDATA_SETENV_H
src/libnetdata/paths/paths.c new
+327
@@ -0,0 +1,327 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "paths.h"
4 +
5 +static int is_procfs(const char *path, char **reason) {
6 +#if defined(__APPLE__) || defined(__FreeBSD__)
7 + (void)path;
8 + (void)reason;
9 +#else
10 + struct statfs stat;
11 +
12 + if (statfs(path, &stat) == -1) {
13 + if (reason)
14 + *reason = "failed to statfs()";
15 + return -1;
16 + }
17 +
18 +#if defined PROC_SUPER_MAGIC
19 + if (stat.f_type != PROC_SUPER_MAGIC) {
20 + if (reason)
21 + *reason = "type is not procfs";
22 + return -1;
23 + }
24 +#endif
25 +
26 +#endif
27 +
28 + return 0;
29 +}
30 +
31 +static int is_sysfs(const char *path, char **reason) {
32 +#if defined(__APPLE__) || defined(__FreeBSD__)
33 + (void)path;
34 + (void)reason;
35 +#else
36 + struct statfs stat;
37 +
38 + if (statfs(path, &stat) == -1) {
39 + if (reason)
40 + *reason = "failed to statfs()";
41 + return -1;
42 + }
43 +
44 +#if defined SYSFS_MAGIC
45 + if (stat.f_type != SYSFS_MAGIC) {
46 + if (reason)
47 + *reason = "type is not sysfs";
48 + return -1;
49 + }
50 +#endif
51 +
52 +#endif
53 +
54 + return 0;
55 +}
56 +
57 +int verify_netdata_host_prefix(bool log_msg) {
58 + if(!netdata_configured_host_prefix)
59 + netdata_configured_host_prefix = "";
60 +
61 + if(!*netdata_configured_host_prefix)
62 + return 0;
63 +
64 + char path[FILENAME_MAX];
65 + char *reason = "unknown reason";
66 + errno_clear();
67 +
68 + strncpyz(path, netdata_configured_host_prefix, sizeof(path) - 1);
69 +
70 + struct stat sb;
71 + if (stat(path, &sb) == -1) {
72 + reason = "failed to stat()";
73 + goto failed;
74 + }
75 +
76 + if((sb.st_mode & S_IFMT) != S_IFDIR) {
77 + errno = EINVAL;
78 + reason = "is not a directory";
79 + goto failed;
80 + }
81 +
82 + snprintfz(path, sizeof(path), "%s/proc", netdata_configured_host_prefix);
83 + if(is_procfs(path, &reason) == -1)
84 + goto failed;
85 +
86 + snprintfz(path, sizeof(path), "%s/sys", netdata_configured_host_prefix);
87 + if(is_sysfs(path, &reason) == -1)
88 + goto failed;
89 +
90 + if (netdata_configured_host_prefix && *netdata_configured_host_prefix) {
91 + if (log_msg)
92 + netdata_log_info("Using host prefix directory '%s'", netdata_configured_host_prefix);
93 + }
94 +
95 + return 0;
96 +
97 +failed:
98 + if (log_msg)
99 + netdata_log_error("Ignoring host prefix '%s': path '%s' %s", netdata_configured_host_prefix, path, reason);
100 +
101 + netdata_configured_host_prefix = "";
102 + return -1;
103 +}
104 +
105 +size_t filename_from_path_entry(char out[FILENAME_MAX], const char *path, const char *entry, const char *extension) {
106 + if(unlikely(!path || !*path)) path = ".";
107 + if(unlikely(!entry)) entry = "";
108 +
109 + // skip trailing slashes in path
110 + size_t len = strlen(path);
111 + while(len > 0 && path[len - 1] == '/') len--;
112 +
113 + // skip leading slashes in subpath
114 + while(entry[0] == '/') entry++;
115 +
116 + // if the last character in path is / and (there is a subpath or path is now empty)
117 + // keep the trailing slash in path and remove the additional slash
118 + char *slash = "/";
119 + if(path[len] == '/' && (*entry || len == 0)) {
120 + slash = "";
121 + len++;
122 + }
123 + else if(!*entry) {
124 + // there is no entry
125 + // no need for trailing slash
126 + slash = "";
127 + }
128 +
129 + return snprintfz(out, FILENAME_MAX, "%.*s%s%s%s%s", (int)len, path, slash, entry,
130 + extension && *extension ? "." : "",
131 + extension && *extension ? extension : "");
132 +}
133 +
134 +char *filename_from_path_entry_strdupz(const char *path, const char *entry) {
135 + char filename[FILENAME_MAX];
136 + filename_from_path_entry(filename, path, entry, NULL);
137 + return strdupz(filename);
138 +}
139 +
140 +bool filename_is_dir(const char *filename, bool create_it) {
141 + CLEAN_CHAR_P *buffer = NULL;
142 +
143 + size_t max_links = 100;
144 +
145 + bool is_dir = false;
146 + struct stat st;
147 + while(max_links && stat(filename, &st) == 0) {
148 + if ((st.st_mode & S_IFMT) == S_IFDIR)
149 + is_dir = true;
150 + else if ((st.st_mode & S_IFMT) == S_IFLNK) {
151 + max_links--;
152 +
153 + if(!buffer)
154 + buffer = mallocz(FILENAME_MAX);
155 +
156 + char link_dst[FILENAME_MAX];
157 + ssize_t l = readlink(filename, link_dst, FILENAME_MAX - 1);
158 + if (l > 0) {
159 + link_dst[l] = '\0';
160 + strncpyz(buffer, link_dst, FILENAME_MAX - 1);
161 + filename = buffer;
162 + continue;
163 + }
164 + }
165 +
166 + break;
167 + }
168 +
169 + if(!is_dir && create_it && max_links == 100 && mkdir(filename, 0750) == 0)
170 + is_dir = true;
171 +
172 + return is_dir;
173 +}
174 +
175 +bool path_entry_is_dir(const char *path, const char *entry, bool create_it) {
176 + char filename[FILENAME_MAX];
177 + filename_from_path_entry(filename, path, entry, NULL);
178 + return filename_is_dir(filename, create_it);
179 +}
180 +
181 +bool filename_is_file(const char *filename) {
182 + CLEAN_CHAR_P *buffer = NULL;
183 +
184 + size_t max_links = 100;
185 +
186 + bool is_file = false;
187 + struct stat st;
188 + while(max_links && stat(filename, &st) == 0) {
189 + if((st.st_mode & S_IFMT) == S_IFREG)
190 + is_file = true;
191 + else if((st.st_mode & S_IFMT) == S_IFLNK) {
192 + max_links--;
193 +
194 + if(!buffer)
195 + buffer = mallocz(FILENAME_MAX);
196 +
197 + char link_dst[FILENAME_MAX];
198 + ssize_t l = readlink(filename, link_dst, FILENAME_MAX - 1);
199 + if(l > 0) {
200 + link_dst[l] = '\0';
201 + strncpyz(buffer, link_dst, FILENAME_MAX - 1);
202 + filename = buffer;
203 + continue;
204 + }
205 + }
206 +
207 + break;
208 + }
209 +
210 + return is_file;
211 +}
212 +
213 +bool path_entry_is_file(const char *path, const char *entry) {
214 + char filename[FILENAME_MAX];
215 + filename_from_path_entry(filename, path, entry, NULL);
216 + return filename_is_file(filename);
217 +}
218 +
219 +void recursive_config_double_dir_load(const char *user_path, const char *stock_path, const char *entry, int (*callback)(const char *filename, void *data, bool stock_config), void *data, size_t depth) {
220 + if(depth > 3) {
221 + netdata_log_error("CONFIG: Max directory depth reached while reading user path '%s', stock path '%s', subpath '%s'", user_path, stock_path,
222 + entry);
223 + return;
224 + }
225 +
226 + if(!stock_path)
227 + stock_path = user_path;
228 +
229 + char *udir = filename_from_path_entry_strdupz(user_path, entry);
230 + char *sdir = filename_from_path_entry_strdupz(stock_path, entry);
231 +
232 + netdata_log_debug(D_HEALTH, "CONFIG traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
233 +
234 + DIR *dir = opendir(udir);
235 + if (!dir) {
236 + netdata_log_error("CONFIG cannot open user-config directory '%s'.", udir);
237 + }
238 + else {
239 + struct dirent *de = NULL;
240 + while((de = readdir(dir))) {
241 + if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
242 + if( !de->d_name[0] ||
243 + (de->d_name[0] == '.' && de->d_name[1] == '\0') ||
244 + (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
245 + ) {
246 + netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config directory '%s/%s'", udir, de->d_name);
247 + continue;
248 + }
249 +
250 + if(path_entry_is_dir(udir, de->d_name, false)) {
251 + recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
252 + continue;
253 + }
254 + }
255 +
256 + if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
257 + size_t len = strlen(de->d_name);
258 + if(path_entry_is_file(udir, de->d_name) &&
259 + len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
260 + char *filename = filename_from_path_entry_strdupz(udir, de->d_name);
261 + netdata_log_debug(D_HEALTH, "CONFIG calling callback for user file '%s'", filename);
262 + callback(filename, data, false);
263 + freez(filename);
264 + continue;
265 + }
266 + }
267 +
268 + netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
269 + }
270 +
271 + closedir(dir);
272 + }
273 +
274 + netdata_log_debug(D_HEALTH, "CONFIG traversing stock config directory '%s', user config directory '%s'", sdir, udir);
275 +
276 + dir = opendir(sdir);
277 + if (!dir) {
278 + netdata_log_error("CONFIG cannot open stock config directory '%s'.", sdir);
279 + }
280 + else {
281 + if (strcmp(udir, sdir)) {
282 + struct dirent *de = NULL;
283 + while((de = readdir(dir))) {
284 + if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
285 + if( !de->d_name[0] ||
286 + (de->d_name[0] == '.' && de->d_name[1] == '\0') ||
287 + (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
288 + ) {
289 + netdata_log_debug(D_HEALTH, "CONFIG ignoring stock config directory '%s/%s'", sdir, de->d_name);
290 + continue;
291 + }
292 +
293 + if(path_entry_is_dir(sdir, de->d_name, false)) {
294 + // we recurse in stock subdirectory, only when there is no corresponding
295 + // user subdirectory - to avoid reading the files twice
296 +
297 + if(!path_entry_is_dir(udir, de->d_name, false))
298 + recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
299 +
300 + continue;
301 + }
302 + }
303 +
304 + if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
305 + size_t len = strlen(de->d_name);
306 + if(path_entry_is_file(sdir, de->d_name) && !path_entry_is_file(udir, de->d_name) &&
307 + len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
308 + char *filename = filename_from_path_entry_strdupz(sdir, de->d_name);
309 + netdata_log_debug(D_HEALTH, "CONFIG calling callback for stock file '%s'", filename);
310 + callback(filename, data, true);
311 + freez(filename);
312 + continue;
313 + }
314 +
315 + }
316 +
317 + netdata_log_debug(D_HEALTH, "CONFIG ignoring stock-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
318 + }
319 + }
320 + closedir(dir);
321 + }
322 +
323 + netdata_log_debug(D_HEALTH, "CONFIG done traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
324 +
325 + freez(udir);
326 + freez(sdir);
327 +}
src/libnetdata/paths/paths.h new
+26
@@ -0,0 +1,26 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_PATHS_H
4 +#define NETDATA_PATHS_H
5 +
6 +#include "../libnetdata.h"
7 +
8 +size_t filename_from_path_entry(char out[FILENAME_MAX], const char *path, const char *entry, const char *extension);
9 +char *filename_from_path_entry_strdupz(const char *path, const char *entry);
10 +
11 +bool filename_is_file(const char *filename);
12 +bool filename_is_dir(const char *filename, bool create_it);
13 +
14 +bool path_entry_is_file(const char *path, const char *entry);
15 +bool path_entry_is_dir(const char *path, const char *entry, bool create_it);
16 +
17 +void recursive_config_double_dir_load(
18 + const char *user_path
19 + , const char *stock_path
20 + , const char *entry
21 + , int (*callback)(const char *filename, void *data, bool stock_config)
22 + , void *data
23 + , size_t depth
24 +);
25 +
26 +#endif //NETDATA_PATHS_H
src/libnetdata/required_dummies.h
-5
@@ -13,11 +13,6 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re
13 exit(ret);
14 }
15
16 -// callbacks required by popen()
17 -void signals_block(void){}
18 -void signals_unblock(void){}
19 -void signals_reset(void){}
20 -
16 void rrdset_thread_rda_free(void){}
17 void sender_thread_buffer_free(void){}
18 void query_target_free(void){}
src/libnetdata/socket/security.c
-3
@@ -1,7 +1,5 @@
1 #include "../libnetdata.h"
2
3 -#ifdef ENABLE_HTTPS
4 -
3 SSL_CTX *netdata_ssl_exporting_ctx =NULL;
4 SSL_CTX *netdata_ssl_streaming_sender_ctx =NULL;
5 SSL_CTX *netdata_ssl_web_server_ctx =NULL;
@@ -751,4 +749,3 @@ int ssl_security_location_for_context(SSL_CTX *ctx, char *file, char *path) {
749
750 return 0;
751 }
754 -#endif
src/libnetdata/socket/security.h
-3
@@ -12,8 +12,6 @@ typedef enum __attribute__((packed)) {
12 #define NETDATA_SSL_STREAMING_SENDER_CTX 1
13 #define NETDATA_SSL_EXPORTING_CTX 2
14
15 -# ifdef ENABLE_HTTPS
16 -
15 #define OPENSSL_VERSION_095 0x00905100L
16 #define OPENSSL_VERSION_097 0x0907000L
17 #define OPENSSL_VERSION_110 0x10100000L
@@ -73,5 +71,4 @@ ssize_t netdata_ssl_write(NETDATA_SSL *ssl, const void *buf, size_t num);
71 ssize_t netdata_ssl_pending(NETDATA_SSL *ssl);
72 bool netdata_ssl_has_pending(NETDATA_SSL *ssl);
73
76 -# endif //ENABLE_HTTPS
74 #endif //NETDATA_SECURITY_H
src/libnetdata/socket/socket.c
+7 -31
@@ -515,7 +515,6 @@ HTTP_ACL socket_ssl_acl(char *acl) {
515 //Due the format of the SSL command it is always the last command,
516 //we finish it here to avoid problems with the ACLs
517 *ssl = '\0';
518 -#ifdef ENABLE_HTTPS
518 ssl++;
519 if (!strncmp("SSL=",ssl,4)) {
520 ssl += 4;
@@ -526,7 +525,6 @@ HTTP_ACL socket_ssl_acl(char *acl) {
525 return HTTP_ACL_SSL_FORCE;
526 }
527 }
529 -#endif
528 }
529
530 return HTTP_ACL_NONE;
@@ -927,9 +925,7 @@ int connect_to_this_ip46(int protocol, int socktype, const char *host, uint32_t
925 int timeout_ms = timeout->tv_sec * 1000 + timeout->tv_usec / 1000;
926
927 switch(wait_on_socket_or_cancel_with_timeout(
930 -#ifdef ENABLE_HTTPS
931 - NULL,
932 -#endif
928 + NULL,
929 fd, timeout_ms, POLLOUT, NULL)) {
930 case 0: // proceed
931 nd_log(NDLS_DAEMON, NDLP_DEBUG,
@@ -1172,9 +1168,7 @@ int connect_to_one_of_urls(const char *destination, int default_port, struct tim
1168 // returns: -1 = thread cancelled, 0 = proceed to read/write, 1 = time exceeded, 2 = error on fd
1169 // timeout parameter can be zero to wait forever
1170 inline int wait_on_socket_or_cancel_with_timeout(
1175 -#ifdef ENABLE_HTTPS
1171 NETDATA_SSL *ssl,
1177 -#endif
1172 int fd, int timeout_ms, short int poll_events, short int *revents) {
1173 struct pollfd pfd = {
1174 .fd = fd,
@@ -1190,10 +1184,8 @@ inline int wait_on_socket_or_cancel_with_timeout(
1184 return -1;
1185 }
1186
1193 -#ifdef ENABLE_HTTPS
1187 if(poll_events == POLLIN && ssl && SSL_connection(ssl) && netdata_ssl_has_pending(ssl))
1188 return 0;
1196 -#endif
1189
1190 const int wait_ms = (timeout_ms >= ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS || forever) ?
1191 ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS : timeout_ms;
@@ -1233,16 +1225,10 @@ inline int wait_on_socket_or_cancel_with_timeout(
1225 return 1;
1226 }
1227
1236 -ssize_t recv_timeout(
1237 -#ifdef ENABLE_HTTPS
1238 - NETDATA_SSL *ssl,
1239 -#endif
1240 - int sockfd, void *buf, size_t len, int flags, int timeout) {
1228 +ssize_t recv_timeout(NETDATA_SSL *ssl, int sockfd, void *buf, size_t len, int flags, int timeout) {
1229
1230 switch(wait_on_socket_or_cancel_with_timeout(
1243 -#ifdef ENABLE_HTTPS
1244 - ssl,
1245 -#endif
1231 + ssl,
1232 sockfd, timeout * 1000, POLLIN, NULL)) {
1233 case 0: // data are waiting
1234 break;
@@ -1256,25 +1242,16 @@ ssize_t recv_timeout(
1242 return -1;
1243 }
1244
1259 -#ifdef ENABLE_HTTPS
1260 - if (SSL_connection(ssl)) {
1245 + if (SSL_connection(ssl))
1246 return netdata_ssl_read(ssl, buf, len);
1262 - }
1263 -#endif
1247
1248 return recv(sockfd, buf, len, flags);
1249 }
1250
1268 -ssize_t send_timeout(
1269 -#ifdef ENABLE_HTTPS
1270 - NETDATA_SSL *ssl,
1271 -#endif
1272 - int sockfd, void *buf, size_t len, int flags, int timeout) {
1251 +ssize_t send_timeout(NETDATA_SSL *ssl, int sockfd, void *buf, size_t len, int flags, int timeout) {
1252
1253 switch(wait_on_socket_or_cancel_with_timeout(
1275 -#ifdef ENABLE_HTTPS
1276 - ssl,
1277 -#endif
1254 + ssl,
1255 sockfd, timeout * 1000, POLLOUT, NULL)) {
1256 case 0: // data are waiting
1257 break;
@@ -1288,7 +1265,6 @@ ssize_t send_timeout(
1265 return -1;
1266 }
1267
1291 -#ifdef ENABLE_HTTPS
1268 if(ssl->conn) {
1269 if (SSL_connection(ssl)) {
1270 return netdata_ssl_write(ssl, buf, len);
@@ -1300,7 +1276,7 @@ ssize_t send_timeout(
1276 return -1;
1277 }
1278 }
1303 -#endif
1279 +
1280 return send(sockfd, buf, len, flags);
1281 }
1282
src/libnetdata/socket/socket.h
-6
@@ -39,15 +39,9 @@ int connect_to_one_of(const char *destination, int default_port, struct timeval
39 int connect_to_one_of_urls(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size);
40
41
42 -#ifdef ENABLE_HTTPS
42 ssize_t recv_timeout(NETDATA_SSL *ssl,int sockfd, void *buf, size_t len, int flags, int timeout);
43 ssize_t send_timeout(NETDATA_SSL *ssl,int sockfd, void *buf, size_t len, int flags, int timeout);
44 int wait_on_socket_or_cancel_with_timeout(NETDATA_SSL *ssl, int fd, int timeout_ms, short int poll_events, short int *revents);
46 -#else
47 -ssize_t recv_timeout(int sockfd, void *buf, size_t len, int flags, int timeout);
48 -ssize_t send_timeout(int sockfd, void *buf, size_t len, int flags, int timeout);
49 -int wait_on_socket_or_cancel_with_timeout(int fd, int timeout_ms, short int poll_events, short int *revents);
50 -#endif
45
46 bool fd_is_socket(int fd);
47 bool sock_has_output_error(int fd);
src/libnetdata/spawn_server/spawn-tester.c new
+493
@@ -0,0 +1,493 @@
1 +#include "libnetdata/libnetdata.h"
2 +#include "libnetdata/required_dummies.h"
3 +
4 +#define ENV_VAR_KEY "SPAWN_TESTER"
5 +#define ENV_VAR_VALUE "1234567890"
6 +
7 +size_t warnings = 0;
8 +
9 +void child_check_environment(void) {
10 + const char *s = getenv(ENV_VAR_KEY);
11 + if(!s || !*s || strcmp(s, ENV_VAR_VALUE) != 0) {
12 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
13 + "Wrong environment. Variable '%s' should have value '%s' but it has '%s'",
14 + ENV_VAR_KEY, ENV_VAR_VALUE, s ? s : "(unset)");
15 +
16 + exit(1);
17 + }
18 +}
19 +
20 +static bool is_valid_fd(int fd) {
21 + errno_clear();
22 + return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
23 +}
24 +
25 +void child_check_fds(void) {
26 + for(int fd = 0; fd < 3; fd++) {
27 + if(!is_valid_fd(fd)) {
28 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
29 + "fd No %d should be a valid file descriptor - but it isn't.", fd);
30 +
31 + exit(1);
32 + }
33 + }
34 +
35 + for(int fd = 3; fd < /* os_get_fd_open_max() */ 1024; fd++) {
36 + if(is_valid_fd(fd)) {
37 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
38 + "fd No %d is a valid file descriptor - it shouldn't.", fd);
39 +
40 + exit(1);
41 + }
42 + }
43 +
44 + errno_clear();
45 +}
46 +
47 +// --------------------------------------------------------------------------------------------------------------------
48 +// kill to stop
49 +
50 +int plugin_kill_to_stop() {
51 + child_check_fds();
52 + child_check_environment();
53 +
54 + char buffer[1024];
55 + while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
56 + fprintf(stderr, "+");
57 + printf("%s", buffer);
58 + fflush(stdout);
59 + }
60 +
61 + return 0;
62 +}
63 +
64 +void test_int_fds_plugin_kill_to_stop(SPAWN_SERVER *server, const char *argv0) {
65 + const char *params[] = {
66 + argv0,
67 + "plugin-kill-to-stop",
68 + NULL,
69 + };
70 +
71 + SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
72 + if(!si) {
73 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
74 + exit(1);
75 + }
76 +
77 + const char *msg = "Hello World!\n";
78 + ssize_t len = strlen(msg);
79 + char buffer[len * 2];
80 +
81 + for(size_t j = 0; j < 30 ;j++) {
82 + fprintf(stderr, "-");
83 + memset(buffer, 0, sizeof(buffer));
84 +
85 + ssize_t rc = write(spawn_server_instance_write_fd(si), msg, len);
86 +
87 + if (rc != len) {
88 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
89 + "Cannot write to plugin. Expected to write %zd bytes, wrote %zd bytes",
90 + len, rc);
91 + exit(1);
92 + }
93 +
94 + rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
95 + if (rc != len) {
96 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
97 + "Cannot read from plugin. Expected to read %zd bytes, read %zd bytes",
98 + len, rc);
99 + exit(1);
100 + }
101 +
102 + if (memcmp(msg, buffer, len) != 0) {
103 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
104 + "Read corrupted data. Expected '%s', Read '%s'",
105 + msg, buffer);
106 + exit(1);
107 + }
108 + }
109 + fprintf(stderr, "\n");
110 +
111 + int code = spawn_server_exec_kill(server, si);
112 +
113 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
114 + "child exited with code %d",
115 + code);
116 +
117 + if(code != 15 && code != 0) {
118 + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0 or 15, but exited with code %d", code);
119 + warnings++;
120 + }
121 +}
122 +
123 +void test_popen_plugin_kill_to_stop(const char *argv0) {
124 + char cmd[FILENAME_MAX + 100];
125 + snprintfz(cmd, sizeof(cmd), "exec %s plugin-kill-to-stop", argv0);
126 + POPEN_INSTANCE *pi = spawn_popen_run(cmd);
127 + if(!pi) {
128 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
129 + exit(1);
130 + }
131 +
132 + const char *msg = "Hello World!\n";
133 + size_t len = strlen(msg);
134 + char buffer[len * 2];
135 +
136 + for(size_t j = 0; j < 30 ;j++) {
137 + fprintf(stderr, "-");
138 + memset(buffer, 0, sizeof(buffer));
139 +
140 + size_t rc = fwrite(msg, 1, len, spawn_popen_stdin(pi));
141 + if (rc != len) {
142 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
143 + "Cannot write to plugin. Expected to write %zu bytes, wrote %zu bytes",
144 + len, rc);
145 + exit(1);
146 + }
147 + fflush(spawn_popen_stdin(pi));
148 +
149 + char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
150 + if (!s || strlen(s) != len) {
151 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
152 + "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
153 + len, (size_t)(s ? strlen(s) : 0));
154 + exit(1);
155 + }
156 + if (memcmp(msg, buffer, len) != 0) {
157 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
158 + "Read corrupted data. Expected '%s', Read '%s'",
159 + msg, buffer);
160 + exit(1);
161 + }
162 + }
163 + fprintf(stderr, "\n");
164 +
165 + int code = spawn_popen_kill(pi);
166 +
167 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
168 + "child exited with code %d",
169 + code);
170 +
171 + if(code != 0) {
172 + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
173 + warnings++;
174 + }
175 +}
176 +
177 +// --------------------------------------------------------------------------------------------------------------------
178 +// close to stop
179 +
180 +int plugin_close_to_stop() {
181 + child_check_fds();
182 + child_check_environment();
183 +
184 + char buffer[1024];
185 + while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
186 + fprintf(stderr, "+");
187 + printf("%s", buffer);
188 + fflush(stdout);
189 + }
190 +
191 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "child detected a closed pipe.");
192 + exit(1);
193 +}
194 +
195 +void test_int_fds_plugin_close_to_stop(SPAWN_SERVER *server, const char *argv0) {
196 + const char *params[] = {
197 + argv0,
198 + "plugin-close-to-stop",
199 + NULL,
200 + };
201 +
202 + SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
203 + if(!si) {
204 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
205 + exit(1);
206 + }
207 +
208 + const char *msg = "Hello World!\n";
209 + ssize_t len = strlen(msg);
210 + char buffer[len * 2];
211 +
212 + for(size_t j = 0; j < 30 ;j++) {
213 + fprintf(stderr, "-");
214 + memset(buffer, 0, sizeof(buffer));
215 +
216 + ssize_t rc = write(spawn_server_instance_write_fd(si), msg, len);
217 + if (rc != len) {
218 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
219 + "Cannot write to plugin. Expected to write %zd bytes, wrote %zd bytes",
220 + len, rc);
221 + exit(1);
222 + }
223 +
224 + rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
225 + if (rc != len) {
226 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
227 + "Cannot read from plugin. Expected to read %zd bytes, read %zd bytes",
228 + len, rc);
229 + exit(1);
230 + }
231 + if (memcmp(msg, buffer, len) != 0) {
232 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
233 + "Read corrupted data. Expected '%s', Read '%s'",
234 + msg, buffer);
235 + exit(1);
236 + }
237 +
238 + break;
239 + }
240 + fprintf(stderr, "\n");
241 +
242 + int code = spawn_server_exec_wait(server, si);
243 +
244 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
245 + "child exited with code %d",
246 + code);
247 +
248 + if(!WIFEXITED(code) || WEXITSTATUS(code) != 1) {
249 + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 1, but exited with code %d", code);
250 + warnings++;
251 + }
252 +}
253 +
254 +void test_popen_plugin_close_to_stop(const char *argv0) {
255 + char cmd[FILENAME_MAX + 100];
256 + snprintfz(cmd, sizeof(cmd), "exec %s plugin-close-to-stop", argv0);
257 + POPEN_INSTANCE *pi = spawn_popen_run(cmd);
258 + if(!pi) {
259 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
260 + exit(1);
261 + }
262 +
263 + const char *msg = "Hello World!\n";
264 + size_t len = strlen(msg);
265 + char buffer[len * 2];
266 +
267 + for(size_t j = 0; j < 30 ;j++) {
268 + fprintf(stderr, "-");
269 + memset(buffer, 0, sizeof(buffer));
270 +
271 + size_t rc = fwrite(msg, 1, len, spawn_popen_stdin(pi));
272 + if (rc != len) {
273 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
274 + "Cannot write to plugin. Expected to write %zu bytes, wrote %zu bytes",
275 + len, rc);
276 + exit(1);
277 + }
278 + fflush(spawn_popen_stdin(pi));
279 +
280 + char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
281 + if (!s || strlen(s) != len) {
282 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
283 + "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
284 + len, (size_t)(s ? strlen(s) : 0));
285 + exit(1);
286 + }
287 + if (memcmp(msg, buffer, len) != 0) {
288 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
289 + "Read corrupted data. Expected '%s', Read '%s'",
290 + msg, buffer);
291 + exit(1);
292 + }
293 +
294 + break;
295 + }
296 + fprintf(stderr, "\n");
297 +
298 + int code = spawn_popen_wait(pi);
299 +
300 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
301 + "child exited with code %d",
302 + code);
303 +
304 + if(code != 1) {
305 + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 1, but exited with code %d", code);
306 + warnings++;
307 + }
308 +}
309 +
310 +// --------------------------------------------------------------------------------------------------------------------
311 +// echo and exit
312 +
313 +#define ECHO_AND_EXIT_MSG "GOODBYE\n"
314 +
315 +int plugin_echo_and_exit() {
316 + child_check_fds();
317 + child_check_environment();
318 +
319 + printf(ECHO_AND_EXIT_MSG);
320 + exit(0);
321 +}
322 +
323 +void test_int_fds_plugin_echo_and_exit(SPAWN_SERVER *server, const char *argv0) {
324 + const char *params[] = {
325 + argv0,
326 + "plugin-echo-and-exit",
327 + NULL,
328 + };
329 +
330 + SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
331 + if(!si) {
332 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
333 + exit(1);
334 + }
335 +
336 + char buffer[1024];
337 + size_t reads = 0;
338 +
339 + for(size_t j = 0; j < 30 ;j++) {
340 + fprintf(stderr, "-");
341 + memset(buffer, 0, sizeof(buffer));
342 +
343 + ssize_t rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
344 + if(rc <= 0)
345 + break;
346 +
347 + reads++;
348 +
349 + if (rc != strlen(ECHO_AND_EXIT_MSG)) {
350 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
351 + "Cannot read from plugin. Expected to read %zu bytes, read %zd bytes",
352 + strlen(ECHO_AND_EXIT_MSG), rc);
353 + exit(1);
354 + }
355 + if (memcmp(ECHO_AND_EXIT_MSG, buffer, strlen(ECHO_AND_EXIT_MSG)) != 0) {
356 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
357 + "Read corrupted data. Expected '%s', Read '%s'",
358 + ECHO_AND_EXIT_MSG, buffer);
359 + exit(1);
360 + }
361 + }
362 + fprintf(stderr, "\n");
363 +
364 + if(reads != 1) {
365 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
366 + "Cannot read from plugin. Expected to read %d times, but read %zu",
367 + 1, reads);
368 + exit(1);
369 + }
370 +
371 + int code = spawn_server_exec_wait(server, si);
372 +
373 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
374 + "child exited with code %d",
375 + code);
376 +
377 + if(code != 0) {
378 + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
379 + warnings++;
380 + }
381 +}
382 +
383 +void test_popen_plugin_echo_and_exit(const char *argv0) {
384 + char cmd[FILENAME_MAX + 100];
385 + snprintfz(cmd, sizeof(cmd), "exec %s plugin-echo-and-exit", argv0);
386 + POPEN_INSTANCE *pi = spawn_popen_run(cmd);
387 + if(!pi) {
388 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
389 + exit(1);
390 + }
391 +
392 + char buffer[1024];
393 + size_t reads = 0;
394 + for(size_t j = 0; j < 30 ;j++) {
395 + fprintf(stderr, "-");
396 + memset(buffer, 0, sizeof(buffer));
397 +
398 + char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
399 + if(!s) break;
400 + reads++;
401 + if (strlen(s) != strlen(ECHO_AND_EXIT_MSG)) {
402 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
403 + "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
404 + strlen(ECHO_AND_EXIT_MSG), (size_t)(s ? strlen(s) : 0));
405 + exit(1);
406 + }
407 + if (memcmp(ECHO_AND_EXIT_MSG, buffer, strlen(ECHO_AND_EXIT_MSG)) != 0) {
408 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
409 + "Read corrupted data. Expected '%s', Read '%s'",
410 + ECHO_AND_EXIT_MSG, buffer);
411 + exit(1);
412 + }
413 + }
414 + fprintf(stderr, "\n");
415 +
416 + if(reads != 1) {
417 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
418 + "Cannot read from plugin. Expected to read %d times, but read %zu",
419 + 1, reads);
420 + exit(1);
421 + }
422 +
423 + int code = spawn_popen_wait(pi);
424 +
425 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
426 + "child exited with code %d",
427 + code);
428 +
429 + if(code != 0) {
430 + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
431 + warnings++;
432 + }
433 +}
434 +
435 +// --------------------------------------------------------------------------------------------------------------------
436 +
437 +int main(int argc, const char **argv) {
438 + if(argc > 1 && strcmp(argv[1], "plugin-kill-to-stop") == 0)
439 + return plugin_kill_to_stop();
440 +
441 + if(argc > 1 && strcmp(argv[1], "plugin-echo-and-exit") == 0)
442 + return plugin_echo_and_exit();
443 +
444 + if(argc > 1 && strcmp(argv[1], "plugin-close-to-stop") == 0)
445 + return plugin_close_to_stop();
446 +
447 + if(argc <= 1 || strcmp(argv[1], "test") != 0) {
448 + fprintf(stderr, "Run me with 'test' parameter!\n");
449 + exit(1);
450 + }
451 +
452 + nd_setenv(ENV_VAR_KEY, ENV_VAR_VALUE, 1);
453 +
454 + fprintf(stderr, "\n\nTESTING fds\n\n");
455 + SPAWN_SERVER *server = spawn_server_create(SPAWN_SERVER_OPTION_EXEC, "test", NULL, argc, argv);
456 + if(!server) {
457 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot create spawn server");
458 + exit(1);
459 + }
460 + for(size_t i = 0; i < 5; i++) {
461 + fprintf(stderr, "\n\nTESTING fds No %zu (kill to stop)\n\n", i + 1);
462 + test_int_fds_plugin_kill_to_stop(server, argv[0]);
463 + }
464 + for(size_t i = 0; i < 5; i++) {
465 + fprintf(stderr, "\n\nTESTING fds No %zu (echo and exit)\n\n", i + 1);
466 + test_int_fds_plugin_echo_and_exit(server, argv[0]);
467 + }
468 + for(size_t i = 0; i < 5; i++) {
469 + fprintf(stderr, "\n\nTESTING fds No %zu (close to stop)\n\n", i + 1);
470 + test_int_fds_plugin_close_to_stop(server, argv[0]);
471 + }
472 + spawn_server_destroy(server);
473 +
474 + fprintf(stderr, "\n\nTESTING popen\n\n");
475 + netdata_main_spawn_server_init("test", argc, argv);
476 + for(size_t i = 0; i < 5; i++) {
477 + fprintf(stderr, "\n\nTESTING popen No %zu (kill to stop)\n\n", i + 1);
478 + test_popen_plugin_kill_to_stop(argv[0]);
479 + }
480 + for(size_t i = 0; i < 5; i++) {
481 + fprintf(stderr, "\n\nTESTING popen No %zu (echo and exit)\n\n", i + 1);
482 + test_popen_plugin_echo_and_exit(argv[0]);
483 + }
484 + for(size_t i = 0; i < 5; i++) {
485 + fprintf(stderr, "\n\nTESTING popen No %zu (close to stop)\n\n", i + 1);
486 + test_popen_plugin_close_to_stop(argv[0]);
487 + }
488 + netdata_main_spawn_server_cleanup();
489 +
490 + fprintf(stderr, "\n\nTests passed! (%zu warnings)\n\n", warnings);
491 +
492 + exit(0);
493 +}
src/libnetdata/spawn_server/spawn_library.c new
+51
@@ -0,0 +1,51 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "spawn_library.h"
4 +
5 +BUFFER *argv_to_cmdline_buffer(const char **argv) {
6 + BUFFER *wb = buffer_create(0, NULL);
7 +
8 + for(size_t i = 0; argv[i] ;i++) {
9 + const char *s = argv[i];
10 + size_t len = strlen(s);
11 + buffer_need_bytes(wb, len * 2 + 1);
12 +
13 + bool needs_quotes = false;
14 + for(const char *c = s; !needs_quotes && *c ; c++) {
15 + switch(*c) {
16 + case ' ':
17 + case '\v':
18 + case '\t':
19 + case '\n':
20 + case '"':
21 + needs_quotes = true;
22 + break;
23 +
24 + default:
25 + break;
26 + }
27 + }
28 +
29 + if(needs_quotes && buffer_strlen(wb))
30 + buffer_strcat(wb, " \"");
31 + else if(buffer_strlen(wb))
32 + buffer_putc(wb, ' ');
33 +
34 + for(const char *c = s; *c ; c++) {
35 + switch(*c) {
36 + case '"':
37 + buffer_putc(wb, '\\');
38 + // fall through
39 +
40 + default:
41 + buffer_putc(wb, *c);
42 + break;
43 + }
44 + }
45 +
46 + if(needs_quotes)
47 + buffer_strcat(wb, "\"");
48 + }
49 +
50 + return wb;
51 +}
src/libnetdata/spawn_server/spawn_library.h new
+10
@@ -0,0 +1,10 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_SPAWN_LIBRARY_H
4 +#define NETDATA_SPAWN_LIBRARY_H
5 +
6 +#include "../libnetdata.h"
7 +
8 +BUFFER *argv_to_cmdline_buffer(const char **argv);
9 +
10 +#endif //NETDATA_SPAWN_LIBRARY_H
src/libnetdata/spawn_server/spawn_popen.c
+93 -26
@@ -2,6 +2,16 @@
2
3 #include "spawn_popen.h"
4
5 +#if defined(OS_WINDOWS)
6 +#include <windows.h>
7 +#endif
8 +
9 +struct popen_instance {
10 + SPAWN_INSTANCE *si;
11 + FILE *child_stdin_fp;
12 + FILE *child_stdout_fp;
13 +};
14 +
15 SPAWN_SERVER *netdata_main_spawn_server = NULL;
16 static SPINLOCK netdata_main_spawn_server_spinlock = NETDATA_SPINLOCK_INITIALIZER;
17
@@ -27,6 +37,30 @@ void netdata_main_spawn_server_cleanup(void) {
37 }
38 }
39
40 +FILE *spawn_popen_stdin(POPEN_INSTANCE *pi) {
41 + if(!pi->child_stdin_fp)
42 + pi->child_stdin_fp = fdopen(spawn_server_instance_write_fd(pi->si), "w");
43 +
44 + if(!pi->child_stdin_fp)
45 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
46 + "Cannot open FILE on child's stdin on fd %d.",
47 + spawn_server_instance_write_fd(pi->si));
48 +
49 + return pi->child_stdin_fp;
50 +}
51 +
52 +FILE *spawn_popen_stdout(POPEN_INSTANCE *pi) {
53 + if(!pi->child_stdout_fp)
54 + pi->child_stdout_fp = fdopen(spawn_server_instance_read_fd(pi->si), "r");
55 +
56 + if(!pi->child_stdout_fp)
57 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
58 + "Cannot open FILE on child's stdout on fd %d.",
59 + spawn_server_instance_read_fd(pi->si));
60 +
61 + return pi->child_stdout_fp;
62 +}
63 +
64 POPEN_INSTANCE *spawn_popen_run_argv(const char **argv) {
65 netdata_main_spawn_server_init(NULL, 0, NULL);
66
@@ -35,29 +69,9 @@ POPEN_INSTANCE *spawn_popen_run_argv(const char **argv) {
69
70 if(si == NULL) return NULL;
71
38 - POPEN_INSTANCE *pi = mallocz(sizeof(*pi));
72 + POPEN_INSTANCE *pi = callocz(1, sizeof(*pi));
73 pi->si = si;
40 - pi->child_stdin_fp = fdopen(spawn_server_instance_write_fd(si), "w");
41 - pi->child_stdout_fp = fdopen(spawn_server_instance_read_fd(si), "r");
42 -
43 - if(!pi->child_stdin_fp) {
44 - nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open FILE on child's stdin on fd %d.", spawn_server_instance_write_fd(si));
45 - goto cleanup;
46 - }
47 -
48 - if(!pi->child_stdout_fp) {
49 - nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open FILE on child's stdout on fd %d.", spawn_server_instance_read_fd(si));
50 - goto cleanup;
51 - }
52 -
74 return pi;
54 -
55 -cleanup:
56 - if(pi->child_stdin_fp) { fclose(pi->child_stdin_fp); spawn_server_instance_write_fd(si); }
57 - if(pi->child_stdout_fp) { fclose(pi->child_stdout_fp); spawn_server_instance_read_fd_unset(si); }
58 - spawn_server_exec_kill(netdata_main_spawn_server, si);
59 - freez(pi);
60 - return NULL;
75 }
76
77 POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...) {
@@ -92,7 +106,33 @@ POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...) {
106
107 POPEN_INSTANCE *spawn_popen_run(const char *cmd) {
108 if(!cmd || !*cmd) return NULL;
95 -
109 +
110 +//#if defined(OS_WINDOWS)
111 +// if(strncmp(cmd, "exec ", 5) == 0) {
112 +// size_t len = strlen(cmd);
113 +// char cmd_copy[strlen(cmd) + 1];
114 +// memcpy(cmd_copy, cmd, len + 1);
115 +// char *words[100];
116 +// size_t num_words = quoted_strings_splitter(cmd_copy, words, 100, isspace_map_pluginsd);
117 +// char *exec = get_word(words, num_words, 0);
118 +// char *prog = get_word(words, num_words, 1);
119 +// if (strcmp(exec, "exec") == 0 &&
120 +// prog &&
121 +// strendswith(prog, ".plugin") &&
122 +// !strendswith(prog, "charts.d.plugin") &&
123 +// !strendswith(prog, "ioping.plugin")) {
124 +// const char *argv[num_words - 1 + 1]; // remove exec, add terminator
125 +//
126 +// size_t dst = 0;
127 +// for (size_t i = 1; i < num_words; i++)
128 +// argv[dst++] = get_word(words, num_words, i);
129 +//
130 +// argv[dst] = NULL;
131 +// return spawn_popen_run_argv(argv);
132 +// }
133 +// }
134 +//#endif
135 +
136 const char *argv[] = {
137 "/bin/sh",
138 "-c",
@@ -121,11 +161,24 @@ static int spawn_popen_status_rc(int status) {
161 return -1;
162 }
163
164 +static void spawn_popen_close_files(POPEN_INSTANCE *pi) {
165 + if(pi->child_stdin_fp) {
166 + fclose(pi->child_stdin_fp);
167 + pi->child_stdin_fp = NULL;
168 + spawn_server_instance_write_fd_unset(pi->si);
169 + }
170 +
171 + if(pi->child_stdout_fp) {
172 + fclose(pi->child_stdout_fp);
173 + pi->child_stdout_fp = NULL;
174 + spawn_server_instance_read_fd_unset(pi->si);
175 + }
176 +}
177 +
178 int spawn_popen_wait(POPEN_INSTANCE *pi) {
179 if(!pi) return -1;
180
127 - fclose(pi->child_stdin_fp); pi->child_stdin_fp = NULL; spawn_server_instance_write_fd_unset(pi->si);
128 - fclose(pi->child_stdout_fp); pi->child_stdout_fp = NULL; spawn_server_instance_read_fd_unset(pi->si);
181 + spawn_popen_close_files(pi);
182 int status = spawn_server_exec_wait(netdata_main_spawn_server, pi->si);
183 freez(pi);
184 return spawn_popen_status_rc(status);
@@ -134,9 +187,23 @@ int spawn_popen_wait(POPEN_INSTANCE *pi) {
187 int spawn_popen_kill(POPEN_INSTANCE *pi) {
188 if(!pi) return -1;
189
137 - fclose(pi->child_stdin_fp); pi->child_stdin_fp = NULL; spawn_server_instance_write_fd_unset(pi->si);
138 - fclose(pi->child_stdout_fp); pi->child_stdout_fp = NULL; spawn_server_instance_read_fd_unset(pi->si);
190 + spawn_popen_close_files(pi);
191 int status = spawn_server_exec_kill(netdata_main_spawn_server, pi->si);
192 freez(pi);
193 return spawn_popen_status_rc(status);
194 }
195 +
196 +pid_t spawn_popen_pid(POPEN_INSTANCE *pi) {
197 + if(!pi) return -1;
198 + return spawn_server_instance_pid(pi->si);
199 +}
200 +
201 +int spawn_popen_read_fd(POPEN_INSTANCE *pi) {
202 + if(!pi) return -1;
203 + return spawn_server_instance_read_fd(pi->si);
204 +}
205 +
206 +int spawn_popen_write_fd(POPEN_INSTANCE *pi) {
207 + if(!pi) return -1;
208 + return spawn_server_instance_write_fd(pi->si);
209 +}
src/libnetdata/spawn_server/spawn_popen.h
+7 -5
@@ -9,11 +9,7 @@ extern SPAWN_SERVER *netdata_main_spawn_server;
9 bool netdata_main_spawn_server_init(const char *name, int argc, const char **argv);
10 void netdata_main_spawn_server_cleanup(void);
11
12 -typedef struct {
13 - SPAWN_INSTANCE *si;
14 - FILE *child_stdin_fp;
15 - FILE *child_stdout_fp;
16 -} POPEN_INSTANCE;
12 +typedef struct popen_instance POPEN_INSTANCE;
13
14 POPEN_INSTANCE *spawn_popen_run(const char *cmd);
15 POPEN_INSTANCE *spawn_popen_run_argv(const char **argv);
@@ -21,4 +17,10 @@ POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...);
17 int spawn_popen_wait(POPEN_INSTANCE *pi);
18 int spawn_popen_kill(POPEN_INSTANCE *pi);
19
20 +pid_t spawn_popen_pid(POPEN_INSTANCE *pi);
21 +int spawn_popen_read_fd(POPEN_INSTANCE *pi);
22 +int spawn_popen_write_fd(POPEN_INSTANCE *pi);
23 +FILE *spawn_popen_stdin(POPEN_INSTANCE *pi);
24 +FILE *spawn_popen_stdout(POPEN_INSTANCE *pi);
25 +
26 #endif //SPAWN_POPEN_H
src/libnetdata/spawn_server/spawn_server.h
+5 -9
@@ -7,16 +7,12 @@
7
8 typedef enum __attribute__((packed)) {
9 SPAWN_INSTANCE_TYPE_EXEC = 0,
10 -#if !defined(OS_WINDOWS)
10 SPAWN_INSTANCE_TYPE_CALLBACK = 1
12 -#endif
11 } SPAWN_INSTANCE_TYPE;
12
13 typedef enum __attribute__((packed)) {
14 SPAWN_SERVER_OPTION_EXEC = (1 << 0),
17 -#if !defined(OS_WINDOWS)
15 SPAWN_SERVER_OPTION_CALLBACK = (1 << 1),
19 -#endif
16 } SPAWN_SERVER_OPTIONS;
17
18 // this is only used publicly for SPAWN_INSTANCE_TYPE_CALLBACK
@@ -27,7 +23,7 @@ typedef struct spawn_request {
23 pid_t pid; // the pid of the child
24 int sock; // the socket for this request
25 int fds[SPAWN_SERVER_TRANSFER_FDS]; // 0 = stdin, 1 = stdout, 2 = stderr, 3 = custom
30 - const char **environment; // the environment of the parent process
26 + const char **envp; // the environment of the parent process
27 const char **argv; // the command line and its parameters
28 const void *data; // the data structure for the callback
29 size_t data_size; // the data structure size
@@ -36,17 +32,17 @@ typedef struct spawn_request {
32 struct spawn_request *prev, *next; // linking of active requests at the spawn server
33 } SPAWN_REQUEST;
34
39 -typedef void (*spawn_request_callback_t)(SPAWN_REQUEST *request);
35 +typedef int (*spawn_request_callback_t)(SPAWN_REQUEST *request);
36
41 -typedef struct spawm_instance SPAWN_INSTANCE;
37 +typedef struct spawn_instance SPAWN_INSTANCE;
38 typedef struct spawn_server SPAWN_SERVER;
39
40 SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options, const char *name, spawn_request_callback_t child_callback, int argc, const char **argv);
41 void spawn_server_destroy(SPAWN_SERVER *server);
42
43 SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd, const char **argv, const void *data, size_t data_size, SPAWN_INSTANCE_TYPE type);
48 -int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *instance);
49 -int spawn_server_exec_wait(SPAWN_SERVER *server, SPAWN_INSTANCE *instance);
44 +int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *si);
45 +int spawn_server_exec_wait(SPAWN_SERVER *server, SPAWN_INSTANCE *si);
46
47 int spawn_server_instance_read_fd(SPAWN_INSTANCE *si);
48 int spawn_server_instance_write_fd(SPAWN_INSTANCE *si);
src/libnetdata/spawn_server/spawn_server_internals.h new
+97
@@ -0,0 +1,97 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_SPAWN_SERVER_INTERNALS_H
4 +#define NETDATA_SPAWN_SERVER_INTERNALS_H
5 +
6 +#include "../libnetdata.h"
7 +#include "spawn_server.h"
8 +#include "spawn_library.h"
9 +
10 +#if defined(OS_WINDOWS)
11 +// #define SPAWN_SERVER_VERSION_WINDOWS 1
12 +// #define SPAWN_SERVER_VERSION_UV 1
13 +#define SPAWN_SERVER_VERSION_POSIX_SPAWN 1
14 +#else
15 +#define SPAWN_SERVER_VERSION_NOFORK 1
16 +// #define SPAWN_SERVER_VERSION_UV 1
17 +// #define SPAWN_SERVER_VERSION_POSIX_SPAWN 1
18 +#endif
19 +
20 +#if defined(SPAWN_SERVER_VERSION_WINDOWS)
21 +#include <windows.h>
22 +#include <io.h>
23 +#include <fcntl.h>
24 +#include <process.h>
25 +#include <tlhelp32.h>
26 +#include <tchar.h>
27 +#include <sys/cygwin.h>
28 +#endif
29 +
30 +struct spawn_server {
31 + size_t id;
32 + size_t request_id;
33 + const char *name;
34 +
35 +#if defined(SPAWN_SERVER_VERSION_UV)
36 + uv_loop_t *loop;
37 + uv_thread_t thread;
38 + uv_async_t async;
39 + bool stopping;
40 +
41 + SPINLOCK spinlock;
42 + struct work_item *work_queue;
43 +#endif
44 +
45 +#if defined(SPAWN_SERVER_VERSION_NOFORK)
46 + SPAWN_SERVER_OPTIONS options;
47 +
48 + ND_UUID magic; // for authorizing requests, the client needs to know our random UUID
49 + // it is ignored for PING requests
50 +
51 + int pipe[2];
52 + int sock; // the listening socket of the server
53 + pid_t server_pid;
54 + char *path;
55 + spawn_request_callback_t cb;
56 +
57 + int argc;
58 + const char **argv;
59 +#endif
60 +
61 +#if defined(SPAWN_SERVER_VERSION_POSIX_SPAWN)
62 +#endif
63 +
64 +#if defined(SPAWN_SERVER_VERSION_WINDOWS)
65 +#endif
66 +};
67 +
68 +struct spawn_instance {
69 + size_t request_id;
70 + int sock;
71 + int write_fd;
72 + int read_fd;
73 + pid_t child_pid;
74 +
75 +#if defined(SPAWN_SERVER_VERSION_UV)
76 + uv_process_t process;
77 + int exit_code;
78 + uv_sem_t sem;
79 +#endif
80 +
81 +#if defined(SPAWN_SERVER_VERSION_NOFORK)
82 +#endif
83 +
84 +#if defined(SPAWN_SERVER_VERSION_POSIX_SPAWN)
85 + const char *cmdline;
86 + bool exited;
87 + int waitpid_status;
88 + struct spawn_instance *prev, *next;
89 +#endif
90 +
91 +#if defined(SPAWN_SERVER_VERSION_WINDOWS)
92 + HANDLE process_handle;
93 + DWORD dwProcessId;
94 +#endif
95 +};
96 +
97 +#endif //NETDATA_SPAWN_SERVER_INTERNALS_H
src/libnetdata/spawn_server/spawn_server_libuv.c new
+395
@@ -0,0 +1,395 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "spawn_server_internals.h"
4 +
5 +#if defined(SPAWN_SERVER_VERSION_UV)
6 +
7 +int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
8 +int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
9 +void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
10 +void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
11 +pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return uv_process_get_pid(&si->process); }
12 +
13 +typedef struct work_item {
14 + int stderr_fd;
15 + const char **argv;
16 + uv_sem_t sem;
17 + SPAWN_INSTANCE *instance;
18 + struct work_item *prev;
19 + struct work_item *next;
20 +} work_item;
21 +
22 +int uv_errno_to_errno(int uv_err) {
23 + switch (uv_err) {
24 + case 0: return 0;
25 + case UV_E2BIG: return E2BIG;
26 + case UV_EACCES: return EACCES;
27 + case UV_EADDRINUSE: return EADDRINUSE;
28 + case UV_EADDRNOTAVAIL: return EADDRNOTAVAIL;
29 + case UV_EAFNOSUPPORT: return EAFNOSUPPORT;
30 + case UV_EAGAIN: return EAGAIN;
31 + case UV_EAI_ADDRFAMILY: return EAI_ADDRFAMILY;
32 + case UV_EAI_AGAIN: return EAI_AGAIN;
33 + case UV_EAI_BADFLAGS: return EAI_BADFLAGS;
34 +#if defined(EAI_CANCELED)
35 + case UV_EAI_CANCELED: return EAI_CANCELED;
36 +#endif
37 + case UV_EAI_FAIL: return EAI_FAIL;
38 + case UV_EAI_FAMILY: return EAI_FAMILY;
39 + case UV_EAI_MEMORY: return EAI_MEMORY;
40 + case UV_EAI_NODATA: return EAI_NODATA;
41 + case UV_EAI_NONAME: return EAI_NONAME;
42 + case UV_EAI_OVERFLOW: return EAI_OVERFLOW;
43 + case UV_EAI_SERVICE: return EAI_SERVICE;
44 + case UV_EAI_SOCKTYPE: return EAI_SOCKTYPE;
45 + case UV_EALREADY: return EALREADY;
46 + case UV_EBADF: return EBADF;
47 + case UV_EBUSY: return EBUSY;
48 + case UV_ECANCELED: return ECANCELED;
49 + case UV_ECHARSET: return EILSEQ; // No direct mapping, using EILSEQ
50 + case UV_ECONNABORTED: return ECONNABORTED;
51 + case UV_ECONNREFUSED: return ECONNREFUSED;
52 + case UV_ECONNRESET: return ECONNRESET;
53 + case UV_EDESTADDRREQ: return EDESTADDRREQ;
54 + case UV_EEXIST: return EEXIST;
55 + case UV_EFAULT: return EFAULT;
56 + case UV_EFBIG: return EFBIG;
57 + case UV_EHOSTUNREACH: return EHOSTUNREACH;
58 + case UV_EINTR: return EINTR;
59 + case UV_EINVAL: return EINVAL;
60 + case UV_EIO: return EIO;
61 + case UV_EISCONN: return EISCONN;
62 + case UV_EISDIR: return EISDIR;
63 + case UV_ELOOP: return ELOOP;
64 + case UV_EMFILE: return EMFILE;
65 + case UV_EMSGSIZE: return EMSGSIZE;
66 + case UV_ENAMETOOLONG: return ENAMETOOLONG;
67 + case UV_ENETDOWN: return ENETDOWN;
68 + case UV_ENETUNREACH: return ENETUNREACH;
69 + case UV_ENFILE: return ENFILE;
70 + case UV_ENOBUFS: return ENOBUFS;
71 + case UV_ENODEV: return ENODEV;
72 + case UV_ENOENT: return ENOENT;
73 + case UV_ENOMEM: return ENOMEM;
74 + case UV_ENONET: return ENONET;
75 + case UV_ENOSPC: return ENOSPC;
76 + case UV_ENOSYS: return ENOSYS;
77 + case UV_ENOTCONN: return ENOTCONN;
78 + case UV_ENOTDIR: return ENOTDIR;
79 + case UV_ENOTEMPTY: return ENOTEMPTY;
80 + case UV_ENOTSOCK: return ENOTSOCK;
81 + case UV_ENOTSUP: return ENOTSUP;
82 + case UV_ENOTTY: return ENOTTY;
83 + case UV_ENXIO: return ENXIO;
84 + case UV_EPERM: return EPERM;
85 + case UV_EPIPE: return EPIPE;
86 + case UV_EPROTO: return EPROTO;
87 + case UV_EPROTONOSUPPORT: return EPROTONOSUPPORT;
88 + case UV_EPROTOTYPE: return EPROTOTYPE;
89 + case UV_ERANGE: return ERANGE;
90 + case UV_EROFS: return EROFS;
91 + case UV_ESHUTDOWN: return ESHUTDOWN;
92 + case UV_ESPIPE: return ESPIPE;
93 + case UV_ESRCH: return ESRCH;
94 + case UV_ETIMEDOUT: return ETIMEDOUT;
95 + case UV_ETXTBSY: return ETXTBSY;
96 + case UV_EXDEV: return EXDEV;
97 + default: return EINVAL; // Use EINVAL for unknown libuv errors
98 + }
99 +}
100 +
101 +static void posix_unmask_sigchld_on_thread(void) {
102 + sigset_t sigset;
103 + sigemptyset(&sigset); // Initialize the signal set to empty
104 + sigaddset(&sigset, SIGCHLD); // Add SIGCHLD to the set
105 +
106 + if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) != 0)
107 + netdata_log_error("SPAWN SERVER: cannot unmask SIGCHLD");
108 +}
109 +
110 +static void server_thread(void *arg) {
111 + SPAWN_SERVER *server = (SPAWN_SERVER *)arg;
112 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
113 + "SPAWN SERVER: started");
114 +
115 + // this thread needs to process SIGCHLD (by libuv)
116 + // otherwise the on_exit() callback is never run
117 + posix_unmask_sigchld_on_thread();
118 +
119 + // run the event loop
120 + uv_run(server->loop, UV_RUN_DEFAULT);
121 +
122 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
123 + "SPAWN SERVER: ended");
124 +}
125 +
126 +static void on_process_exit(uv_process_t *req, int64_t exit_status, int term_signal) {
127 + SPAWN_INSTANCE *si = (SPAWN_INSTANCE *)req->data;
128 + si->exit_code = (int)(term_signal ? term_signal : exit_status << 8);
129 + uv_close((uv_handle_t *)req, NULL); // Properly close the process handle
130 +
131 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
132 + "SPAWN SERVER: process with pid %d exited with code %d and term_signal %d",
133 + si->child_pid, (int)exit_status, term_signal);
134 +
135 + uv_sem_post(&si->sem); // Signal that the process has exited
136 +}
137 +
138 +static SPAWN_INSTANCE *spawn_process_with_libuv(uv_loop_t *loop, int stderr_fd, const char **argv) {
139 + SPAWN_INSTANCE *si = NULL;
140 + bool si_sem_init = false;
141 +
142 + int stdin_pipe[2] = { -1, -1 };
143 + int stdout_pipe[2] = { -1, -1 };
144 +
145 + if (pipe(stdin_pipe) == -1) {
146 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: stdin pipe() failed");
147 + goto cleanup;
148 + }
149 +
150 + if (pipe(stdout_pipe) == -1) {
151 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: stdout pipe() failed");
152 + goto cleanup;
153 + }
154 +
155 + si = callocz(1, sizeof(SPAWN_INSTANCE));
156 + si->exit_code = -1;
157 +
158 + if (uv_sem_init(&si->sem, 0)) {
159 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: uv_sem_init() failed");
160 + goto cleanup;
161 + }
162 + si_sem_init = true;
163 +
164 + uv_stdio_container_t stdio[3] = { 0 };
165 + stdio[0].flags = UV_INHERIT_FD;
166 + stdio[0].data.fd = stdin_pipe[PIPE_READ];
167 + stdio[1].flags = UV_INHERIT_FD;
168 + stdio[1].data.fd = stdout_pipe[PIPE_WRITE];
169 + stdio[2].flags = UV_INHERIT_FD;
170 + stdio[2].data.fd = stderr_fd;
171 +
172 + uv_process_options_t options = { 0 };
173 + options.stdio_count = 3;
174 + options.stdio = stdio;
175 + options.exit_cb = on_process_exit;
176 + options.file = argv[0];
177 + options.args = (char **)argv;
178 + options.env = (char **)environ;
179 +
180 + // uv_spawn() does not close all other open file descriptors
181 + // we have to close them manually
182 + int fds[3] = { stdio[0].data.fd, stdio[1].data.fd, stdio[2].data.fd };
183 + os_close_all_non_std_open_fds_except(fds, 3, CLOSE_RANGE_CLOEXEC);
184 +
185 + int rc = uv_spawn(loop, &si->process, &options);
186 + if (rc) {
187 + errno = uv_errno_to_errno(rc);
188 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
189 + "SPAWN SERVER: uv_spawn() failed with error %s, %s",
190 + uv_err_name(rc), uv_strerror(rc));
191 + goto cleanup;
192 + }
193 +
194 + // Successfully spawned
195 +
196 + // get the pid of the process spawned
197 + si->child_pid = uv_process_get_pid(&si->process);
198 +
199 + // on_process_exit() needs this to find the si
200 + si->process.data = si;
201 +
202 + nd_log(NDLS_COLLECTORS, NDLP_INFO,
203 + "SPAWN SERVER: process created with pid %d", si->child_pid);
204 +
205 + // close the child sides of the pipes
206 + close(stdin_pipe[PIPE_READ]);
207 + si->write_fd = stdin_pipe[PIPE_WRITE];
208 + si->read_fd = stdout_pipe[PIPE_READ];
209 + close(stdout_pipe[PIPE_WRITE]);
210 +
211 + return si;
212 +
213 +cleanup:
214 + if(stdin_pipe[PIPE_READ] != -1) close(stdin_pipe[PIPE_READ]);
215 + if(stdin_pipe[PIPE_WRITE] != -1) close(stdin_pipe[PIPE_WRITE]);
216 + if(stdout_pipe[PIPE_READ] != -1) close(stdout_pipe[PIPE_READ]);
217 + if(stdout_pipe[PIPE_WRITE] != -1) close(stdout_pipe[PIPE_WRITE]);
218 + if(si) {
219 + if(si_sem_init)
220 + uv_sem_destroy(&si->sem);
221 +
222 + freez(si);
223 + }
224 + return NULL;
225 +}
226 +
227 +static void async_callback(uv_async_t *handle) {
228 + nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: dequeue commands started");
229 + SPAWN_SERVER *server = (SPAWN_SERVER *)handle->data;
230 +
231 + // Check if the server is stopping
232 + if (__atomic_load_n(&server->stopping, __ATOMIC_RELAXED)) {
233 + nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: stopping...");
234 + uv_stop(server->loop);
235 + return;
236 + }
237 +
238 + work_item *item;
239 + spinlock_lock(&server->spinlock);
240 + while (server->work_queue) {
241 + item = server->work_queue;
242 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(server->work_queue, item, prev, next);
243 + spinlock_unlock(&server->spinlock);
244 +
245 + item->instance = spawn_process_with_libuv(server->loop, item->stderr_fd, item->argv);
246 + uv_sem_post(&item->sem);
247 +
248 + spinlock_lock(&server->spinlock);
249 + }
250 + spinlock_unlock(&server->spinlock);
251 +
252 + nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: dequeue commands done");
253 +}
254 +
255 +
256 +SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
257 + SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
258 + spinlock_init(&server->spinlock);
259 +
260 + if (name)
261 + server->name = strdupz(name);
262 + else
263 + server->name = strdupz("unnamed");
264 +
265 + server->loop = callocz(1, sizeof(uv_loop_t));
266 + if (uv_loop_init(server->loop)) {
267 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_loop_init() failed");
268 + freez(server->loop);
269 + freez((void *)server->name);
270 + freez(server);
271 + return NULL;
272 + }
273 +
274 + if (uv_async_init(server->loop, &server->async, async_callback)) {
275 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_async_init() failed");
276 + uv_loop_close(server->loop);
277 + freez(server->loop);
278 + freez((void *)server->name);
279 + freez(server);
280 + return NULL;
281 + }
282 + server->async.data = server;
283 +
284 + if (uv_thread_create(&server->thread, server_thread, server)) {
285 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_thread_create() failed");
286 + uv_close((uv_handle_t*)&server->async, NULL);
287 + uv_loop_close(server->loop);
288 + freez(server->loop);
289 + freez((void *)server->name);
290 + freez(server);
291 + return NULL;
292 + }
293 +
294 + return server;
295 +}
296 +
297 +static void close_handle(uv_handle_t* handle, void* arg __maybe_unused) {
298 + if (!uv_is_closing(handle)) {
299 + uv_close(handle, NULL);
300 + }
301 +}
302 +
303 +void spawn_server_destroy(SPAWN_SERVER *server) {
304 + if (!server) return;
305 +
306 + __atomic_store_n(&server->stopping, true, __ATOMIC_RELAXED);
307 +
308 + // Trigger the async callback to stop the event loop
309 + uv_async_send(&server->async);
310 +
311 + // Wait for the server thread to finish
312 + uv_thread_join(&server->thread);
313 +
314 + uv_stop(server->loop);
315 + uv_close((uv_handle_t*)&server->async, NULL);
316 +
317 + // Walk through and close any remaining handles
318 + uv_walk(server->loop, close_handle, NULL);
319 +
320 + uv_loop_close(server->loop);
321 + freez(server->loop);
322 + freez((void *)server->name);
323 + freez(server);
324 +}
325 +
326 +SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd __maybe_unused, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
327 + if (type != SPAWN_INSTANCE_TYPE_EXEC)
328 + return NULL;
329 +
330 + work_item item = { 0 };
331 + item.stderr_fd = stderr_fd;
332 + item.argv = argv;
333 +
334 + if (uv_sem_init(&item.sem, 0)) {
335 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_sem_init() failed");
336 + return NULL;
337 + }
338 +
339 + spinlock_lock(&server->spinlock);
340 + // item is in the stack, but the server will remove it before sending to us
341 + // the semaphore, so it is safe to have the item in the stack.
342 + work_item *item_ptr = &item;
343 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(server->work_queue, item_ptr, prev, next);
344 + spinlock_unlock(&server->spinlock);
345 +
346 + uv_async_send(&server->async);
347 +
348 + nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: queued command");
349 +
350 + // Wait for the command to be executed
351 + uv_sem_wait(&item.sem);
352 + uv_sem_destroy(&item.sem);
353 +
354 + if (!item.instance) {
355 + nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: process failed to be started");
356 + return NULL;
357 + }
358 +
359 + nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: process started");
360 +
361 + return item.instance;
362 +}
363 +
364 +int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
365 + if(!si) return -1;
366 +
367 + // close all pipe descriptors to force the child to exit
368 + if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
369 + if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
370 +
371 + if (uv_process_kill(&si->process, SIGTERM)) {
372 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_process_kill() failed");
373 + return -1;
374 + }
375 +
376 + return spawn_server_exec_wait(server, si);
377 +}
378 +
379 +int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
380 + if (!si) return -1;
381 +
382 + // close all pipe descriptors to force the child to exit
383 + if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
384 + if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
385 +
386 + // Wait for the process to exit
387 + uv_sem_wait(&si->sem);
388 + int exit_code = si->exit_code;
389 +
390 + uv_sem_destroy(&si->sem);
391 + freez(si);
392 + return exit_code;
393 +}
394 +
395 +#endif
src/libnetdata/spawn_server/spawn_server_nofork.c renamed
+133 -396
@@ -1,287 +1,14 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#include "../libnetdata.h"
3 +#include "spawn_server_internals.h"
4
5 -#include "spawn_server.h"
6 -
7 -#if defined(OS_WINDOWS)
8 -#include <windows.h>
9 -#include <io.h>
10 -#include <fcntl.h>
11 -#include <process.h>
12 -#include <sys/cygwin.h>
13 -#endif
14 -
15 -struct spawn_server {
16 - size_t id;
17 - size_t request_id;
18 - const char *name;
19 -#if !defined(OS_WINDOWS)
20 - SPAWN_SERVER_OPTIONS options;
21 -
22 - ND_UUID magic; // for authorizing requests, the client needs to know our random UUID
23 - // it is ignored for PING requests
24 -
25 - int pipe[2];
26 - int sock; // the listening socket of the server
27 - pid_t server_pid;
28 - char *path;
29 - spawn_request_callback_t cb;
30 -
31 - int argc;
32 - const char **argv;
33 -#endif
34 -};
35 -
36 -struct spawm_instance {
37 - size_t request_id;
38 - int sock;
39 - int write_fd;
40 - int read_fd;
41 - pid_t child_pid;
42 -
43 -#if defined(OS_WINDOWS)
44 - HANDLE process_handle;
45 - HANDLE read_handle;
46 - HANDLE write_handle;
47 -#endif
48 -};
5 +#if defined(SPAWN_SERVER_VERSION_NOFORK)
6
7 int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
8 int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
52 -pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
9 void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
10 void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
55 -
56 -#if defined(OS_WINDOWS)
57 -
58 -SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
59 - SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
60 - if(name)
61 - server->name = strdupz(name);
62 - else
63 - server->name = strdupz("unnamed");
64 - return server;
65 -}
66 -
67 -void spawn_server_destroy(SPAWN_SERVER *server) {
68 - if (server) {
69 - freez((void *)server->name);
70 - freez(server);
71 - }
72 -}
73 -
74 -static BUFFER *argv_to_windows(const char **argv) {
75 - BUFFER *wb = buffer_create(0, NULL);
76 -
77 - // argv[0] is the path
78 - char b[strlen(argv[0]) * 2 + 1024];
79 - cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
80 -
81 - buffer_strcat(wb, "cmd.exe /C ");
82 -
83 - for(size_t i = 0; argv[i] ;i++) {
84 - const char *s = (i == 0) ? b : argv[i];
85 - size_t len = strlen(s);
86 - buffer_need_bytes(wb, len * 2 + 1);
87 -
88 - bool needs_quotes = false;
89 - for(const char *c = s; !needs_quotes && *c ; c++) {
90 - switch(*c) {
91 - case ' ':
92 - case '\v':
93 - case '\t':
94 - case '\n':
95 - case '"':
96 - needs_quotes = true;
97 - break;
98 -
99 - default:
100 - break;
101 - }
102 - }
103 -
104 - if(needs_quotes && buffer_strlen(wb))
105 - buffer_strcat(wb, " \"");
106 - else
107 - buffer_putc(wb, ' ');
108 -
109 - for(const char *c = s; *c ; c++) {
110 - switch(*c) {
111 - case '"':
112 - buffer_putc(wb, '\\');
113 - // fall through
114 -
115 - default:
116 - buffer_putc(wb, *c);
117 - break;
118 - }
119 - }
120 -
121 - if(needs_quotes)
122 - buffer_strcat(wb, "\"");
123 - }
124 -
125 - return wb;
126 -}
127 -
128 -SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
129 - static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
130 -
131 - if (type != SPAWN_INSTANCE_TYPE_EXEC)
132 - return NULL;
133 -
134 - int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 };
135 -
136 - errno_clear();
137 -
138 - SPAWN_INSTANCE *instance = callocz(1, sizeof(*instance));
139 - instance->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
140 -
141 - CLEAN_BUFFER *wb = argv_to_windows(argv);
142 - char *command = (char *)buffer_tostring(wb);
143 -
144 - if (pipe(pipe_stdin) == -1) {
145 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
146 - "SPAWN PARENT: Cannot create stdin pipe() for request No %zu, command: %s",
147 - instance->request_id, command);
148 - goto cleanup;
149 - }
150 -
151 - if (pipe(pipe_stdout) == -1) {
152 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
153 - "SPAWN PARENT: Cannot create stdout pipe() for request No %zu, command: %s",
154 - instance->request_id, command);
155 - goto cleanup;
156 - }
157 -
158 - // do not run multiple times this section
159 - // to prevent handles leaking
160 - spinlock_lock(&spinlock);
161 -
162 - // Convert POSIX file descriptors to Windows handles
163 - HANDLE stdin_read_handle = (HANDLE)_get_osfhandle(pipe_stdin[0]);
164 - HANDLE stdout_write_handle = (HANDLE)_get_osfhandle(pipe_stdout[1]);
165 - HANDLE stderr_handle = (HANDLE)_get_osfhandle(stderr_fd);
166 -
167 - if (stdin_read_handle == INVALID_HANDLE_VALUE || stdout_write_handle == INVALID_HANDLE_VALUE || stderr_handle == INVALID_HANDLE_VALUE) {
168 - spinlock_unlock(&spinlock);
169 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
170 - "SPAWN PARENT: Invalid handle value(s) for request No %zu, command: %s",
171 - instance->request_id, command);
172 - goto cleanup;
173 - }
174 -
175 - // Set handle inheritance
176 - if (!SetHandleInformation(stdin_read_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
177 - !SetHandleInformation(stdout_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
178 - !SetHandleInformation(stderr_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
179 - spinlock_unlock(&spinlock);
180 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
181 - "SPAWN PARENT: Cannot set handle(s) inheritance for request No %zu, command: %s",
182 - instance->request_id, command);
183 - goto cleanup;
184 - }
185 -
186 - // Set up the STARTUPINFO structure
187 - STARTUPINFO si;
188 - PROCESS_INFORMATION pi;
189 - ZeroMemory(&si, sizeof(si));
190 - si.cb = sizeof(si);
191 - si.dwFlags = STARTF_USESTDHANDLES;
192 - si.hStdInput = stdin_read_handle;
193 - si.hStdOutput = stdout_write_handle;
194 - si.hStdError = stderr_handle;
195 -
196 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
197 - "SPAWN PARENT: Running request No %zu, command: %s",
198 - instance->request_id, command);
199 -
200 - // Spawn the process
201 - if (!CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
202 - spinlock_unlock(&spinlock);
203 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
204 - "SPAWN PARENT: cannot CreateProcess() for request No %zu, command: %s",
205 - instance->request_id, command);
206 - goto cleanup;
207 - }
208 -
209 - CloseHandle(pi.hThread);
210 -
211 - // end of the critical section
212 - spinlock_unlock(&spinlock);
213 -
214 - // Close unused pipe ends
215 - close(pipe_stdin[0]); pipe_stdin[0] = -1;
216 - close(pipe_stdout[1]); pipe_stdout[1] = -1;
217 -
218 - // Store process information in instance
219 - instance->child_pid = cygwin_winpid_to_pid(pi.dwProcessId);
220 - if(instance->child_pid == -1) instance->child_pid = pi.dwProcessId;
221 -
222 - instance->process_handle = pi.hProcess;
223 -
224 - // Convert handles to POSIX file descriptors
225 - instance->write_fd = pipe_stdin[1];
226 - instance->read_fd = pipe_stdout[0];
227 -
228 - errno_clear();
229 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
230 - "SPAWN PARENT: created process for request No %zu, pid %d, command: %s",
231 - instance->request_id, (int)instance->child_pid, command);
232 -
233 - return instance;
234 -
235 -cleanup:
236 - if (pipe_stdin[0] >= 0) close(pipe_stdin[0]);
237 - if (pipe_stdin[1] >= 0) close(pipe_stdin[1]);
238 - if (pipe_stdout[0] >= 0) close(pipe_stdout[0]);
239 - if (pipe_stdout[1] >= 0) close(pipe_stdout[1]);
240 - freez(instance);
241 - return NULL;
242 -}
243 -
244 -int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
245 - if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; }
246 - if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; }
247 - CloseHandle(instance->read_handle); instance->read_handle = NULL;
248 - CloseHandle(instance->write_handle); instance->write_handle = NULL;
249 -
250 - TerminateProcess(instance->process_handle, 0);
251 -
252 - DWORD exit_code;
253 - GetExitCodeProcess(instance->process_handle, &exit_code);
254 - CloseHandle(instance->process_handle);
255 -
256 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
257 - "SPAWN PARENT: child of request No %zu, pid %d, killed and exited with code %d",
258 - instance->request_id, (int)instance->child_pid, (int)exit_code);
259 -
260 - freez(instance);
261 - return (int)exit_code;
262 -}
263 -
264 -int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
265 - if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; }
266 - if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; }
267 - CloseHandle(instance->read_handle); instance->read_handle = NULL;
268 - CloseHandle(instance->write_handle); instance->write_handle = NULL;
269 -
270 - WaitForSingleObject(instance->process_handle, INFINITE);
271 -
272 - DWORD exit_code = -1;
273 - GetExitCodeProcess(instance->process_handle, &exit_code);
274 - CloseHandle(instance->process_handle);
275 -
276 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
277 - "SPAWN PARENT: child of request No %zu, pid %d, waited and exited with code %d",
278 - instance->request_id, (int)instance->child_pid, (int)exit_code);
279 -
280 - freez(instance);
281 - return (int)exit_code;
282 -}
283 -
284 -#else // !OS_WINDOWS
11 +pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
12
13 #ifdef __APPLE__
14 #include <crt_externs.h>
@@ -344,19 +71,19 @@ static void spawn_server_run_child(SPAWN_SERVER *server, SPAWN_REQUEST *rq) {
71 nd_log(NDLS_COLLECTORS, NDLP_ERR,
72 "SPAWN SERVER: cannot dup2(%d) stdin of request No %zu: %s",
73 stdin_fd, rq->request_id, rq->cmdline);
347 - exit(1);
74 + exit(EXIT_FAILURE);
75 }
76 if (dup2(stdout_fd, STDOUT_FILENO) == -1) {
77 nd_log(NDLS_COLLECTORS, NDLP_ERR,
78 "SPAWN SERVER: cannot dup2(%d) stdin of request No %zu: %s",
79 stdout_fd, rq->request_id, rq->cmdline);
353 - exit(1);
80 + exit(EXIT_FAILURE);
81 }
82 if (dup2(stderr_fd, STDERR_FILENO) == -1) {
83 nd_log(NDLS_COLLECTORS, NDLP_ERR,
84 "SPAWN SERVER: cannot dup2(%d) stderr of request No %zu: %s",
85 stderr_fd, rq->request_id, rq->cmdline);
359 - exit(1);
86 + exit(EXIT_FAILURE);
87 }
88
89 // close the excess fds
@@ -365,34 +92,10 @@ static void spawn_server_run_child(SPAWN_SERVER *server, SPAWN_REQUEST *rq) {
92 close(stderr_fd); stderr_fd = rq->fds[2] = STDERR_FILENO;
93
94 // overwrite the process environment
368 - environ = (char **)rq->environment;
369 -
370 - // Perform different actions based on the type
371 - switch (rq->type) {
372 -
373 - case SPAWN_INSTANCE_TYPE_EXEC:
374 - // close all fds except the ones we need
375 - os_close_all_non_std_open_fds_except(NULL, 0);
376 -
377 - // run the command
378 - execvp(rq->argv[0], (char **)rq->argv);
379 -
380 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
381 - "SPAWN SERVER: Failed to execute command of request No %zu: %s",
382 - rq->request_id, rq->cmdline);
383 -
384 - exit(1);
385 - break;
95 + environ = (char **)rq->envp;
96
387 - case SPAWN_INSTANCE_TYPE_CALLBACK:
388 - server->cb(rq);
389 - exit(0);
390 - break;
391 -
392 - default:
393 - nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: unknown request type %u", rq->type);
394 - exit(1);
395 - }
97 + // run the callback and return its code
98 + exit(server->cb(rq));
99 }
100
101 // --------------------------------------------------------------------------------------------------------------------
@@ -457,54 +160,6 @@ static const char** argv_decode(const char *buffer, size_t size) {
160 return argv;
161 }
162
460 -static BUFFER *argv_to_cmdline_buffer(const char **argv) {
461 - BUFFER *wb = buffer_create(0, NULL);
462 -
463 - for(size_t i = 0; argv[i] ;i++) {
464 - const char *s = argv[i];
465 - size_t len = strlen(s);
466 - buffer_need_bytes(wb, len * 2 + 1);
467 -
468 - bool needs_quotes = false;
469 - for(const char *c = s; !needs_quotes && *c ; c++) {
470 - switch(*c) {
471 - case ' ':
472 - case '\v':
473 - case '\t':
474 - case '\n':
475 - case '"':
476 - needs_quotes = true;
477 - break;
478 -
479 - default:
480 - break;
481 - }
482 - }
483 -
484 - if(needs_quotes && buffer_strlen(wb))
485 - buffer_strcat(wb, " \"");
486 - else
487 - buffer_putc(wb, ' ');
488 -
489 - for(const char *c = s; *c ; c++) {
490 - switch(*c) {
491 - case '"':
492 - buffer_putc(wb, '\\');
493 - // fall through
494 -
495 - default:
496 - buffer_putc(wb, *c);
497 - break;
498 - }
499 - }
500 -
501 - if(needs_quotes)
502 - buffer_strcat(wb, "\"");
503 - }
504 -
505 - return wb;
506 -}
507 -
163 // --------------------------------------------------------------------------------------------------------------------
164 // status reports
165
@@ -602,50 +257,105 @@ static void request_free(SPAWN_REQUEST *rq) {
257 if(rq->fds[3] != -1) close(rq->fds[3]);
258 if(rq->sock != -1) close(rq->sock);
259 freez((void *)rq->argv);
605 - freez((void *)rq->environment);
260 + freez((void *)rq->envp);
261 freez((void *)rq->data);
262 freez((void *)rq->cmdline);
263 freez((void *)rq);
264 }
265
611 -static void spawn_server_execute_request(SPAWN_SERVER *server, SPAWN_REQUEST *rq) {
612 - switch(rq->type) {
613 - case SPAWN_INSTANCE_TYPE_EXEC:
614 - // close custom_fd - it is not needed for exec mode
615 - if(rq->fds[3] != -1) { close(rq->fds[3]); rq->fds[3] = -1; }
266 +static bool spawn_external_command(SPAWN_SERVER *server __maybe_unused, SPAWN_REQUEST *rq) {
267 + // Close custom_fd - it is not needed for exec mode
268 + if(rq->fds[3] != -1) { close(rq->fds[3]); rq->fds[3] = -1; }
269
617 - // create the cmdline for logs
618 - if(rq->argv) {
619 - CLEAN_BUFFER *wb = argv_to_cmdline_buffer(rq->argv);
620 - rq->cmdline = strdupz(buffer_tostring(wb));
621 - }
622 - break;
270 + if(!rq->argv) {
271 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: there is no argv pointer to exec");
272 + return false;
273 + }
274
624 - case SPAWN_INSTANCE_TYPE_CALLBACK:
625 - if(server->cb == NULL) {
626 - errno = ENOSYS;
627 - spawn_server_send_status_failure(rq);
628 - request_free(rq);
629 - return;
630 - }
631 - rq->cmdline = strdupz("callback() function");
632 - break;
275 + if(rq->fds[0] == -1 || rq->fds[1] == -1 || rq->fds[2] == -1) {
276 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdio fds are missing from the request");
277 + return false;
278 + }
279
634 - default:
635 - errno = EINVAL;
636 - spawn_server_send_status_failure(rq);
637 - request_free(rq);
638 - return;
280 + CLEAN_BUFFER *wb = argv_to_cmdline_buffer(rq->argv);
281 + rq->cmdline = strdupz(buffer_tostring(wb));
282 +
283 + posix_spawn_file_actions_t file_actions;
284 + if (posix_spawn_file_actions_init(&file_actions) != 0) {
285 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawn_file_actions_init() failed: %s", rq->cmdline);
286 + return false;
287 + }
288 +
289 + posix_spawn_file_actions_adddup2(&file_actions, rq->fds[0], STDIN_FILENO);
290 + posix_spawn_file_actions_adddup2(&file_actions, rq->fds[1], STDOUT_FILENO);
291 + posix_spawn_file_actions_adddup2(&file_actions, rq->fds[2], STDERR_FILENO);
292 + posix_spawn_file_actions_addclose(&file_actions, rq->fds[0]);
293 + posix_spawn_file_actions_addclose(&file_actions, rq->fds[1]);
294 + posix_spawn_file_actions_addclose(&file_actions, rq->fds[2]);
295 +
296 + posix_spawnattr_t attr;
297 + if (posix_spawnattr_init(&attr) != 0) {
298 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_init() failed: %s", rq->cmdline);
299 + posix_spawn_file_actions_destroy(&file_actions);
300 + return false;
301 + }
302 +
303 + // Set the flags to reset the signal mask and signal actions
304 + sigset_t empty_mask;
305 + sigemptyset(&empty_mask);
306 + if (posix_spawnattr_setsigmask(&attr, &empty_mask) != 0) {
307 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setsigmask() failed: %s", rq->cmdline);
308 + posix_spawn_file_actions_destroy(&file_actions);
309 + posix_spawnattr_destroy(&attr);
310 + return false;
311 + }
312 +
313 + short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
314 + if (posix_spawnattr_setflags(&attr, flags) != 0) {
315 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setflags() failed: %s", rq->cmdline);
316 + posix_spawn_file_actions_destroy(&file_actions);
317 + posix_spawnattr_destroy(&attr);
318 + return false;
319 + }
320 +
321 + os_close_all_non_std_open_fds_except(rq->fds, 3, CLOSE_RANGE_CLOEXEC);
322 +
323 + errno_clear();
324 + if (posix_spawn(&rq->pid, rq->argv[0], &file_actions, &attr, (char * const *)rq->argv, (char * const *)rq->envp) != 0) {
325 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: posix_spawn() failed: %s", rq->cmdline);
326 +
327 + posix_spawnattr_destroy(&attr);
328 + posix_spawn_file_actions_destroy(&file_actions);
329 + return false;
330 + }
331 +
332 + // Destroy the posix_spawnattr_t and posix_spawn_file_actions_t structures
333 + posix_spawnattr_destroy(&attr);
334 + posix_spawn_file_actions_destroy(&file_actions);
335 +
336 + // Close the read end of the stdin pipe and the write end of the stdout pipe in the parent process
337 + close(rq->fds[0]); rq->fds[0] = -1;
338 + close(rq->fds[1]); rq->fds[1] = -1;
339 + close(rq->fds[2]); rq->fds[2] = -1;
340 +
341 + nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "SPAWN SERVER: process created with pid %d: %s", rq->pid, rq->cmdline);
342 + return true;
343 +}
344 +
345 +static bool spawn_server_run_callback(SPAWN_SERVER *server __maybe_unused, SPAWN_REQUEST *rq) {
346 + rq->cmdline = strdupz("callback() function");
347 +
348 + if(server->cb == NULL) {
349 + errno = ENOSYS;
350 + return false;
351 }
352
353 pid_t pid = fork();
354 if (pid < 0) {
355 // fork failed
356
645 - nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to fork() child.");
646 - spawn_server_send_status_failure(rq);
647 - request_free(rq);
648 - return;
357 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to fork() child for callback.");
358 + return false;
359 }
360 else if (pid == 0) {
361 // the child
@@ -657,11 +367,37 @@ static void spawn_server_execute_request(SPAWN_SERVER *server, SPAWN_REQUEST *rq
367 // the parent
368 rq->pid = pid;
369
370 + return true;
371 +}
372 +
373 +static void spawn_server_execute_request(SPAWN_SERVER *server, SPAWN_REQUEST *rq) {
374 + bool done;
375 + switch(rq->type) {
376 + case SPAWN_INSTANCE_TYPE_EXEC:
377 + done = spawn_external_command(server, rq);
378 + break;
379 +
380 + case SPAWN_INSTANCE_TYPE_CALLBACK:
381 + done = spawn_server_run_callback(server, rq);
382 + break;
383 +
384 + default:
385 + errno = EINVAL;
386 + done = false;
387 + break;
388 + }
389 +
390 + if(!done) {
391 + spawn_server_send_status_failure(rq);
392 + request_free(rq);
393 + return;
394 + }
395 +
396 // let the parent know
397 spawn_server_send_status_success(rq);
398
399 // do not keep data we don't need at the parent
664 - freez((void *)rq->environment); rq->environment = NULL;
400 + freez((void *)rq->envp); rq->envp = NULL;
401 freez((void *)rq->argv); rq->argv = NULL;
402 freez((void *)rq->data); rq->data = NULL;
403 rq->data_size = 0;
@@ -747,7 +483,7 @@ static bool spawn_server_send_request(ND_UUID *magic, SPAWN_REQUEST *request) {
483 bool ret = false;
484
485 size_t env_size = 0;
750 - void *encoded_env = argv_encode(request->environment, &env_size);
486 + void *encoded_env = argv_encode(request->envp, &env_size);
487 if (!encoded_env)
488 goto cleanup;
489
@@ -974,7 +710,7 @@ static void spawn_server_receive_request(int sock, SPAWN_SERVER *server) {
710 [2] = stderr_fd,
711 [3] = custom_fd,
712 },
977 - .environment = argv_decode(envp_encoded, env_size),
713 + .envp = argv_decode(envp_encoded, env_size),
714 .argv = argv_decode(argv_encoded, argv_size),
715 .data = data,
716 .data_size = data_size,
@@ -1082,20 +818,21 @@ static void spawn_server_process_sigchld(void) {
818 }
819 }
820
1085 -static void signals_unblock(void) {
821 +static void posix_unmask_sigchld_on_thread(void) {
822 sigset_t sigset;
1087 - sigfillset(&sigset);
823 + sigemptyset(&sigset); // Initialize the signal set to empty
824 + sigaddset(&sigset, SIGCHLD); // Add SIGCHLD to the set
825
1089 - if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
1090 - netdata_log_error("SPAWN SERVER: Could not unblock signals for threads");
1091 - }
826 + if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) != 0)
827 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
828 + "SPAWN SERVER: cannot unmask SIGCHLD");
829 }
830
831 static void spawn_server_event_loop(SPAWN_SERVER *server) {
832 int pipe_fd = server->pipe[1];
833 close(server->pipe[0]); server->pipe[0] = -1;
834
1098 - signals_unblock();
835 + posix_unmask_sigchld_on_thread();
836
837 // Set up the signal handler for SIGCHLD and SIGTERM
838 struct sigaction sa;
@@ -1336,7 +1073,7 @@ SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options, const char *name
1073 }
1074
1075 replace_stdio_with_dev_null();
1339 - os_close_all_non_std_open_fds_except((int[]){ server->sock, server->pipe[1] }, 2);
1076 + os_close_all_non_std_open_fds_except((int[]){ server->sock, server->pipe[1] }, 2, 0);
1077 nd_log_reopen_log_files_for_spawn_server();
1078 spawn_server_event_loop(server);
1079 }
@@ -1458,7 +1195,7 @@ SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custo
1195 [2] = stderr_fd,
1196 [3] = custom_fd,
1197 },
1461 - .environment = (const char **)environ,
1198 + .envp = (const char **)environ,
1199 .argv = argv,
1200 .data = data,
1201 .data_size = data_size,
@@ -1530,4 +1267,4 @@ cleanup:
1267 return NULL;
1268 }
1269
1533 -#endif // !OS_WINDOWS
1270 +#endif
src/libnetdata/spawn_server/spawn_server_posix.c new
+259
@@ -0,0 +1,259 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "spawn_server_internals.h"
4 +
5 +#if defined(SPAWN_SERVER_VERSION_POSIX_SPAWN)
6 +
7 +#ifdef __APPLE__
8 +#include <crt_externs.h>
9 +#define environ (*_NSGetEnviron())
10 +#else
11 +extern char **environ;
12 +#endif
13 +
14 +int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
15 +int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
16 +void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
17 +void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
18 +pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
19 +
20 +static struct {
21 + bool sigchld_initialized;
22 + SPINLOCK spinlock;
23 + SPAWN_INSTANCE *instances;
24 +} spawn_globals = {
25 + .spinlock = NETDATA_SPINLOCK_INITIALIZER,
26 + .instances = NULL,
27 +};
28 +
29 +//static void sigchld_handler(int signum __maybe_unused) {
30 +// pid_t pid;
31 +// int status;
32 +//
33 +// while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
34 +// // Find the SPAWN_INSTANCE corresponding to this pid
35 +// spinlock_lock(&spawn_globals.spinlock);
36 +// for(SPAWN_INSTANCE *si = spawn_globals.instances; si ;si = si->next) {
37 +// if (si->child_pid == pid) {
38 +// __atomic_store_n(&si->waitpid_status, status, __ATOMIC_RELAXED);
39 +// __atomic_store_n(&si->exited, true, __ATOMIC_RELAXED);
40 +// break;
41 +// }
42 +// }
43 +// spinlock_unlock(&spawn_globals.spinlock);
44 +// }
45 +//}
46 +
47 +SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
48 + SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
49 +
50 + if (name)
51 + server->name = strdupz(name);
52 + else
53 + server->name = strdupz("unnamed");
54 +
55 + if(!spawn_globals.sigchld_initialized) {
56 + spawn_globals.sigchld_initialized = true;
57 +
58 +// struct sigaction sa;
59 +// sa.sa_handler = sigchld_handler;
60 +// sigemptyset(&sa.sa_mask);
61 +// sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
62 +// if (sigaction(SIGCHLD, &sa, NULL) == -1) {
63 +// nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to set SIGCHLD handler");
64 +// freez((void *)server->name);
65 +// freez(server);
66 +// return NULL;
67 +// }
68 + }
69 +
70 + return server;
71 +}
72 +
73 +void spawn_server_destroy(SPAWN_SERVER *server) {
74 + if (!server) return;
75 + freez((void *)server->name);
76 + freez(server);
77 +}
78 +
79 +SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
80 + if (type != SPAWN_INSTANCE_TYPE_EXEC)
81 + return NULL;
82 +
83 + CLEAN_BUFFER *cmdline_wb = argv_to_cmdline_buffer(argv);
84 + const char *cmdline = buffer_tostring(cmdline_wb);
85 +
86 + SPAWN_INSTANCE *si = callocz(1, sizeof(SPAWN_INSTANCE));
87 + si->child_pid = -1;
88 + si->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
89 +
90 + int stdin_pipe[2] = { -1, -1 };
91 + int stdout_pipe[2] = { -1, -1 };
92 +
93 + if (pipe(stdin_pipe) == -1) {
94 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdin pipe() failed: %s", cmdline);
95 + freez(si);
96 + return NULL;
97 + }
98 +
99 + if (pipe(stdout_pipe) == -1) {
100 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdout pipe() failed: %s", cmdline);
101 + close(stdin_pipe[PIPE_READ]);
102 + close(stdin_pipe[PIPE_WRITE]);
103 + freez(si);
104 + return NULL;
105 + }
106 +
107 + posix_spawn_file_actions_t file_actions;
108 + posix_spawnattr_t attr;
109 +
110 + if (posix_spawn_file_actions_init(&file_actions) != 0) {
111 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawn_file_actions_init() failed: %s", cmdline);
112 + close(stdin_pipe[PIPE_READ]);
113 + close(stdin_pipe[PIPE_WRITE]);
114 + close(stdout_pipe[PIPE_READ]);
115 + close(stdout_pipe[PIPE_WRITE]);
116 + freez(si);
117 + return NULL;
118 + }
119 +
120 + posix_spawn_file_actions_adddup2(&file_actions, stdin_pipe[PIPE_READ], STDIN_FILENO);
121 + posix_spawn_file_actions_adddup2(&file_actions, stdout_pipe[PIPE_WRITE], STDOUT_FILENO);
122 + posix_spawn_file_actions_addclose(&file_actions, stdin_pipe[PIPE_READ]);
123 + posix_spawn_file_actions_addclose(&file_actions, stdin_pipe[PIPE_WRITE]);
124 + posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[PIPE_READ]);
125 + posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[PIPE_WRITE]);
126 + if(stderr_fd != STDERR_FILENO) {
127 + posix_spawn_file_actions_adddup2(&file_actions, stderr_fd, STDERR_FILENO);
128 + posix_spawn_file_actions_addclose(&file_actions, stderr_fd);
129 + }
130 +
131 + if (posix_spawnattr_init(&attr) != 0) {
132 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_init() failed: %s", cmdline);
133 + posix_spawn_file_actions_destroy(&file_actions);
134 + close(stdin_pipe[PIPE_READ]);
135 + close(stdin_pipe[PIPE_WRITE]);
136 + close(stdout_pipe[PIPE_READ]);
137 + close(stdout_pipe[PIPE_WRITE]);
138 + freez(si);
139 + return NULL;
140 + }
141 +
142 + // Set the flags to reset the signal mask and signal actions
143 + sigset_t empty_mask;
144 + sigemptyset(&empty_mask);
145 + if (posix_spawnattr_setsigmask(&attr, &empty_mask) != 0) {
146 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setsigmask() failed: %s", cmdline);
147 + posix_spawn_file_actions_destroy(&file_actions);
148 + posix_spawnattr_destroy(&attr);
149 + return false;
150 + }
151 +
152 + short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
153 + if (posix_spawnattr_setflags(&attr, flags) != 0) {
154 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setflags() failed: %s", cmdline);
155 + posix_spawn_file_actions_destroy(&file_actions);
156 + posix_spawnattr_destroy(&attr);
157 + return false;
158 + }
159 +
160 + spinlock_lock(&spawn_globals.spinlock);
161 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
162 + spinlock_unlock(&spawn_globals.spinlock);
163 +
164 + // unfortunately, on CYGWIN/MSYS posix_spawn() is not thread safe
165 + // so, we run it one by one.
166 + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
167 + spinlock_lock(&spinlock);
168 +
169 + int fds[3] = { stdin_pipe[PIPE_READ], stdout_pipe[PIPE_WRITE], stderr_fd };
170 + os_close_all_non_std_open_fds_except(fds, 3, CLOSE_RANGE_CLOEXEC);
171 +
172 + errno_clear();
173 + if (posix_spawn(&si->child_pid, argv[0], &file_actions, &attr, (char * const *)argv, environ) != 0) {
174 + spinlock_unlock(&spinlock);
175 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: posix_spawn() failed: %s", cmdline);
176 +
177 + spinlock_lock(&spawn_globals.spinlock);
178 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
179 + spinlock_unlock(&spawn_globals.spinlock);
180 +
181 + posix_spawnattr_destroy(&attr);
182 + posix_spawn_file_actions_destroy(&file_actions);
183 +
184 + close(stdin_pipe[PIPE_READ]);
185 + close(stdin_pipe[PIPE_WRITE]);
186 + close(stdout_pipe[PIPE_READ]);
187 + close(stdout_pipe[PIPE_WRITE]);
188 + freez(si);
189 + return NULL;
190 + }
191 + spinlock_unlock(&spinlock);
192 +
193 + // Destroy the posix_spawnattr_t and posix_spawn_file_actions_t structures
194 + posix_spawnattr_destroy(&attr);
195 + posix_spawn_file_actions_destroy(&file_actions);
196 +
197 + // Close the read end of the stdin pipe and the write end of the stdout pipe in the parent process
198 + close(stdin_pipe[PIPE_READ]);
199 + close(stdout_pipe[PIPE_WRITE]);
200 +
201 + si->write_fd = stdin_pipe[PIPE_WRITE];
202 + si->read_fd = stdout_pipe[PIPE_READ];
203 + si->cmdline = strdupz(cmdline);
204 +
205 + nd_log(NDLS_COLLECTORS, NDLP_INFO,
206 + "SPAWN SERVER: process created with pid %d: %s",
207 + si->child_pid, cmdline);
208 + return si;
209 +}
210 +
211 +int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *si) {
212 + if (!si) return -1;
213 +
214 + if (kill(si->child_pid, SIGTERM))
215 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
216 + "SPAWN PARENT: kill() of pid %d failed: %s",
217 + si->child_pid, si->cmdline);
218 +
219 + return spawn_server_exec_wait(server, si);
220 +}
221 +
222 +int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
223 + if (!si) return -1;
224 +
225 + // Close all pipe descriptors to force the child to exit
226 + if (si->read_fd != -1) close(si->read_fd);
227 + if (si->write_fd != -1) close(si->write_fd);
228 +
229 + // Wait for the process to exit
230 + int status = __atomic_load_n(&si->waitpid_status, __ATOMIC_RELAXED);
231 + bool exited = __atomic_load_n(&si->exited, __ATOMIC_RELAXED);
232 + if(!exited) {
233 + if(waitpid(si->child_pid, &status, 0) != si->child_pid) {
234 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
235 + "SPAWN PARENT: failed to wait for pid %d: %s",
236 + si->child_pid, si->cmdline);
237 + status = -1;
238 + }
239 + else {
240 + nd_log(NDLS_COLLECTORS, NDLP_INFO,
241 + "SPAWN PARENT: child with pid %d exited with status %d (waitpid): %s",
242 + si->child_pid, status, si->cmdline);
243 + }
244 + }
245 + else
246 + nd_log(NDLS_COLLECTORS, NDLP_INFO,
247 + "SPAWN PARENT: child with pid %d exited with status %d (sighandler): %s",
248 + si->child_pid, status, si->cmdline);
249 +
250 + spinlock_lock(&spawn_globals.spinlock);
251 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
252 + spinlock_unlock(&spawn_globals.spinlock);
253 +
254 + freez((void *)si->cmdline);
255 + freez(si);
256 + return status;
257 +}
258 +
259 +#endif
src/libnetdata/spawn_server/spawn_server_windows.c new
+417
@@ -0,0 +1,417 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "spawn_server_internals.h"
4 +
5 +#if defined(SPAWN_SERVER_VERSION_WINDOWS)
6 +
7 +int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
8 +int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
9 +void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
10 +void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
11 +
12 +pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) {
13 + if(si->child_pid != -1)
14 + return si->child_pid;
15 +
16 + return (pid_t)si->dwProcessId;
17 +}
18 +
19 +static void update_cygpath_env(void) {
20 + static volatile bool done = false;
21 +
22 + if(done) return;
23 + done = true;
24 +
25 + char win_path[MAX_PATH];
26 +
27 + // Convert Cygwin root path to Windows path
28 + cygwin_conv_path(CCP_POSIX_TO_WIN_A, "/", win_path, sizeof(win_path));
29 +
30 + nd_setenv("NETDATA_CYGWIN_BASE_PATH", win_path, 1);
31 +
32 + nd_log(NDLS_COLLECTORS, NDLP_INFO, "Cygwin/MSYS2 base path set to '%s'", win_path);
33 +}
34 +
35 +SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
36 + update_cygpath_env();
37 +
38 + SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
39 + if(name)
40 + server->name = strdupz(name);
41 + else
42 + server->name = strdupz("unnamed");
43 + return server;
44 +}
45 +
46 +void spawn_server_destroy(SPAWN_SERVER *server) {
47 + if (server) {
48 + freez((void *)server->name);
49 + freez(server);
50 + }
51 +}
52 +
53 +static BUFFER *argv_to_windows(const char **argv) {
54 + BUFFER *wb = buffer_create(0, NULL);
55 +
56 + // argv[0] is the path
57 + char b[strlen(argv[0]) * 2 + 1024];
58 + cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
59 +
60 + for(size_t i = 0; argv[i] ;i++) {
61 + const char *s = (i == 0) ? b : argv[i];
62 + size_t len = strlen(s);
63 + buffer_need_bytes(wb, len * 2 + 1);
64 +
65 + bool needs_quotes = false;
66 + for(const char *c = s; !needs_quotes && *c ; c++) {
67 + switch(*c) {
68 + case ' ':
69 + case '\v':
70 + case '\t':
71 + case '\n':
72 + case '"':
73 + needs_quotes = true;
74 + break;
75 +
76 + default:
77 + break;
78 + }
79 + }
80 +
81 + if(buffer_strlen(wb)) {
82 + if (needs_quotes)
83 + buffer_strcat(wb, " \"");
84 + else
85 + buffer_putc(wb, ' ');
86 + }
87 +
88 + for(const char *c = s; *c ; c++) {
89 + switch(*c) {
90 + case '"':
91 + buffer_putc(wb, '\\');
92 + // fall through
93 +
94 + default:
95 + buffer_putc(wb, *c);
96 + break;
97 + }
98 + }
99 +
100 + if(needs_quotes)
101 + buffer_strcat(wb, "\"");
102 + }
103 +
104 + return wb;
105 +}
106 +
107 +int set_fd_blocking(int fd) {
108 + int flags = fcntl(fd, F_GETFL, 0);
109 + if (flags == -1) {
110 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: fcntl(F_GETFL) failed");
111 + return -1;
112 + }
113 +
114 + flags &= ~O_NONBLOCK;
115 + if (fcntl(fd, F_SETFL, flags) == -1) {
116 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: fcntl(F_SETFL) failed");
117 + return -1;
118 + }
119 +
120 + return 0;
121 +}
122 +
123 +//static void print_environment_block(char *env_block) {
124 +// if (env_block == NULL) {
125 +// fprintf(stderr, "Environment block is NULL\n");
126 +// return;
127 +// }
128 +//
129 +// char *env = env_block;
130 +// while (*env) {
131 +// fprintf(stderr, "ENVIRONMENT: %s\n", env);
132 +// // Move to the next string in the block
133 +// env += strlen(env) + 1;
134 +// }
135 +//}
136 +
137 +SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
138 + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
139 +
140 + if (type != SPAWN_INSTANCE_TYPE_EXEC)
141 + return NULL;
142 +
143 + int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 };
144 +
145 + errno_clear();
146 +
147 + SPAWN_INSTANCE *instance = callocz(1, sizeof(*instance));
148 + instance->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
149 +
150 + CLEAN_BUFFER *wb = argv_to_windows(argv);
151 + char *command = (char *)buffer_tostring(wb);
152 +
153 + if (pipe(pipe_stdin) == -1) {
154 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
155 + "SPAWN PARENT: Cannot create stdin pipe() for request No %zu, command: %s",
156 + instance->request_id, command);
157 + goto cleanup;
158 + }
159 +
160 + if (pipe(pipe_stdout) == -1) {
161 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
162 + "SPAWN PARENT: Cannot create stdout pipe() for request No %zu, command: %s",
163 + instance->request_id, command);
164 + goto cleanup;
165 + }
166 +
167 + // Ensure pipes are in blocking mode
168 + if (set_fd_blocking(pipe_stdin[PIPE_READ]) == -1 || set_fd_blocking(pipe_stdin[PIPE_WRITE]) == -1 ||
169 + set_fd_blocking(pipe_stdout[PIPE_READ]) == -1 || set_fd_blocking(pipe_stdout[PIPE_WRITE]) == -1) {
170 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
171 + "SPAWN PARENT: Failed to set blocking I/O on pipes for request No %zu, command: %s",
172 + instance->request_id, command);
173 + }
174 +
175 + // do not run multiple times this section
176 + // to prevent handles leaking
177 + spinlock_lock(&spinlock);
178 +
179 + // Convert POSIX file descriptors to Windows handles
180 + HANDLE stdin_read_handle = (HANDLE)_get_osfhandle(pipe_stdin[PIPE_READ]);
181 + HANDLE stdout_write_handle = (HANDLE)_get_osfhandle(pipe_stdout[PIPE_WRITE]);
182 + HANDLE stderr_handle = (HANDLE)_get_osfhandle(stderr_fd);
183 +
184 + if (stdin_read_handle == INVALID_HANDLE_VALUE || stdout_write_handle == INVALID_HANDLE_VALUE || stderr_handle == INVALID_HANDLE_VALUE) {
185 + spinlock_unlock(&spinlock);
186 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
187 + "SPAWN PARENT: Invalid handle value(s) for request No %zu, command: %s",
188 + instance->request_id, command);
189 + goto cleanup;
190 + }
191 +
192 + // Set handle inheritance
193 + if (!SetHandleInformation(stdin_read_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
194 + !SetHandleInformation(stdout_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
195 + !SetHandleInformation(stderr_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
196 + spinlock_unlock(&spinlock);
197 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
198 + "SPAWN PARENT: Cannot set handle(s) inheritance for request No %zu, command: %s",
199 + instance->request_id, command);
200 + goto cleanup;
201 + }
202 +
203 + // Set up the STARTUPINFO structure
204 + STARTUPINFO si;
205 + PROCESS_INFORMATION pi;
206 + ZeroMemory(&si, sizeof(si));
207 + si.cb = sizeof(si);
208 + si.dwFlags = STARTF_USESTDHANDLES;
209 + si.hStdInput = stdin_read_handle;
210 + si.hStdOutput = stdout_write_handle;
211 + si.hStdError = stderr_handle;
212 +
213 + // Retrieve the current environment block
214 + char* env_block = GetEnvironmentStrings();
215 +// print_environment_block(env_block);
216 +
217 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
218 + "SPAWN PARENT: Running request No %zu, command: '%s'",
219 + instance->request_id, command);
220 +
221 + int fds[3] = { pipe_stdin[PIPE_READ], pipe_stdout[PIPE_WRITE], stderr_fd };
222 + os_close_all_non_std_open_fds_except(fds, 3, CLOSE_RANGE_CLOEXEC);
223 +
224 + // Spawn the process
225 + errno_clear();
226 + if (!CreateProcess(NULL, command, NULL, NULL, TRUE, 0, env_block, NULL, &si, &pi)) {
227 + spinlock_unlock(&spinlock);
228 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
229 + "SPAWN PARENT: cannot CreateProcess() for request No %zu, command: %s",
230 + instance->request_id, command);
231 + goto cleanup;
232 + }
233 +
234 + FreeEnvironmentStrings(env_block);
235 +
236 + // When we create a process with the CreateProcess function, it returns two handles:
237 + // - one for the process (pi.hProcess) and
238 + // - one for the primary thread of the new process (pi.hThread).
239 + // Both of these handles need to be explicitly closed when they are no longer needed.
240 + CloseHandle(pi.hThread);
241 +
242 + // end of the critical section
243 + spinlock_unlock(&spinlock);
244 +
245 + // Close unused pipe ends
246 + close(pipe_stdin[PIPE_READ]); pipe_stdin[PIPE_READ] = -1;
247 + close(pipe_stdout[PIPE_WRITE]); pipe_stdout[PIPE_WRITE] = -1;
248 +
249 + // Store process information in instance
250 + instance->dwProcessId = pi.dwProcessId;
251 + instance->child_pid = cygwin_winpid_to_pid((pid_t)pi.dwProcessId);
252 + instance->process_handle = pi.hProcess;
253 +
254 + // Convert handles to POSIX file descriptors
255 + instance->write_fd = pipe_stdin[PIPE_WRITE];
256 + instance->read_fd = pipe_stdout[PIPE_READ];
257 +
258 + errno_clear();
259 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
260 + "SPAWN PARENT: created process for request No %zu, pid %d (winpid %d), command: %s",
261 + instance->request_id, (int)instance->child_pid, (int)pi.dwProcessId, command);
262 +
263 + return instance;
264 +
265 + cleanup:
266 + if (pipe_stdin[PIPE_READ] >= 0) close(pipe_stdin[PIPE_READ]);
267 + if (pipe_stdin[PIPE_WRITE] >= 0) close(pipe_stdin[PIPE_WRITE]);
268 + if (pipe_stdout[PIPE_READ] >= 0) close(pipe_stdout[PIPE_READ]);
269 + if (pipe_stdout[PIPE_WRITE] >= 0) close(pipe_stdout[PIPE_WRITE]);
270 + freez(instance);
271 + return NULL;
272 +}
273 +
274 +static char* GetErrorString(DWORD errorCode) {
275 + DWORD lastError = GetLastError();
276 +
277 + LPVOID lpMsgBuf;
278 + DWORD bufLen = FormatMessage(
279 + FORMAT_MESSAGE_ALLOCATE_BUFFER |
280 + FORMAT_MESSAGE_FROM_SYSTEM |
281 + FORMAT_MESSAGE_IGNORE_INSERTS,
282 + NULL,
283 + errorCode,
284 + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
285 + (LPTSTR) &lpMsgBuf,
286 + 0, NULL );
287 +
288 + SetLastError(lastError);
289 +
290 + if (bufLen) {
291 + char* errorString = (char*)LocalAlloc(LMEM_FIXED, bufLen + 1);
292 + if (errorString) {
293 + strcpy(errorString, (char*)lpMsgBuf);
294 + }
295 + LocalFree(lpMsgBuf);
296 + return errorString;
297 + }
298 +
299 + return NULL;
300 +}
301 +
302 +static void TerminateChildProcesses(SPAWN_INSTANCE *si) {
303 + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
304 + if (hSnapshot == INVALID_HANDLE_VALUE)
305 + return;
306 +
307 + PROCESSENTRY32 pe;
308 + pe.dwSize = sizeof(PROCESSENTRY32);
309 +
310 + if (Process32First(hSnapshot, &pe)) {
311 + do {
312 + if (pe.th32ParentProcessID == si->dwProcessId) {
313 + HANDLE hChildProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
314 + if (hChildProcess) {
315 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
316 + "SPAWN PARENT: killing subprocess %u of request No %zu, pid %d (winpid %u)",
317 + pe.th32ProcessID, si->request_id, (int)si->child_pid, si->dwProcessId);
318 +
319 + TerminateProcess(hChildProcess, STATUS_CONTROL_C_EXIT);
320 + CloseHandle(hChildProcess);
321 + }
322 + }
323 + } while (Process32Next(hSnapshot, &pe));
324 + }
325 +
326 + CloseHandle(hSnapshot);
327 +}
328 +
329 +int map_status_code_to_signal(DWORD status_code) {
330 + switch (status_code) {
331 + case STATUS_ACCESS_VIOLATION:
332 + return SIGSEGV;
333 + case STATUS_ILLEGAL_INSTRUCTION:
334 + return SIGILL;
335 + case STATUS_FLOAT_DIVIDE_BY_ZERO:
336 + case STATUS_INTEGER_DIVIDE_BY_ZERO:
337 + case STATUS_ARRAY_BOUNDS_EXCEEDED:
338 + case STATUS_FLOAT_OVERFLOW:
339 + case STATUS_FLOAT_UNDERFLOW:
340 + case STATUS_FLOAT_INVALID_OPERATION:
341 + return SIGFPE;
342 + case STATUS_BREAKPOINT:
343 + case STATUS_SINGLE_STEP:
344 + return SIGTRAP;
345 + case STATUS_STACK_OVERFLOW:
346 + case STATUS_INVALID_HANDLE:
347 + case STATUS_INVALID_PARAMETER:
348 + case STATUS_NO_MEMORY:
349 + case STATUS_PRIVILEGED_INSTRUCTION:
350 + case STATUS_DLL_NOT_FOUND:
351 + case STATUS_DLL_INIT_FAILED:
352 + case STATUS_ORDINAL_NOT_FOUND:
353 + case STATUS_ENTRYPOINT_NOT_FOUND:
354 + case STATUS_CONTROL_STACK_VIOLATION:
355 + case STATUS_STACK_BUFFER_OVERRUN:
356 + case STATUS_ASSERTION_FAILURE:
357 + case STATUS_INVALID_CRUNTIME_PARAMETER:
358 + case STATUS_HEAP_CORRUPTION:
359 + return SIGABRT;
360 + case STATUS_CONTROL_C_EXIT:
361 + return SIGTERM; // we use this internally as such
362 + case STATUS_FATAL_APP_EXIT:
363 + return SIGTERM;
364 + default:
365 + return (status_code & 0xFF) << 8;
366 + }
367 +}
368 +
369 +int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
370 + if(si->child_pid != -1 && kill(si->child_pid, SIGTERM) != 0)
371 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
372 + "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), failed to be killed",
373 + si->request_id, (int)si->child_pid, si->dwProcessId);
374 +
375 + // this gives some warnings at the spawn-tester, but it is generally better
376 + // to have them, to avoid abnormal shutdown of the plugins
377 + if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
378 + if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
379 +
380 + errno_clear();
381 + if(TerminateProcess(si->process_handle, STATUS_CONTROL_C_EXIT) == 0)
382 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
383 + "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), failed to be terminated",
384 + si->request_id, (int)si->child_pid, si->dwProcessId);
385 +
386 + errno_clear();
387 + TerminateChildProcesses(si);
388 +
389 + return spawn_server_exec_wait(server, si);
390 +}
391 +
392 +int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
393 + if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
394 + if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
395 +
396 + // wait for the process to end
397 + WaitForSingleObject(si->process_handle, INFINITE);
398 +
399 + DWORD exit_code = -1;
400 + GetExitCodeProcess(si->process_handle, &exit_code);
401 + CloseHandle(si->process_handle);
402 +
403 + char *err = GetErrorString(exit_code);
404 +
405 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
406 + "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), exited with code %u (0x%x): %s",
407 + si->request_id, (int)si->child_pid, si->dwProcessId,
408 + (unsigned)exit_code, (unsigned)exit_code, err ? err : "(no reason text)");
409 +
410 + if(err)
411 + LocalFree(err);
412 +
413 + freez(si);
414 + return map_status_code_to_signal(exit_code);
415 +}
416 +
417 +#endif
src/libnetdata/string/string.h
+7
@@ -36,4 +36,11 @@ int string_unittest(size_t entries);
36
37 void string_init(void);
38
39 +static inline void cleanup_string_pp(STRING **stringpp) {
40 + if(stringpp)
41 + string_freez(*stringpp);
42 +}
43 +
44 +#define CLEAN_STRING _cleanup_(cleanup_string_pp) STRING
45 +
46 #endif
src/libnetdata/uuid/uuid.h
+2
@@ -37,6 +37,8 @@ ND_UUID UUID_generate_from_hash(const void *payload, size_t payload_len);
37
38 #define UUIDeq(a, b) ((a).parts.hig64 == (b).parts.hig64 && (a).parts.low64 == (b).parts.low64)
39
40 +#define UUIDiszero(a) (UUIDeq(a, UUID_ZERO))
41 +
42 static inline ND_UUID uuid2UUID(const nd_uuid_t uu1) {
43 // uu1 may not be aligned, so copy it to the output
44 ND_UUID copy;
src/registry/registry.c
+8 -10
@@ -154,8 +154,8 @@ static inline int registry_person_url_callback_verify_machine_exists(REGISTRY_PE
154 // that could make this safe, so try to be as atomic as possible.
155
156 void registry_update_cloud_base_url() {
157 - registry.cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", DEFAULT_CLOUD_BASE_URL);
158 - setenv("NETDATA_REGISTRY_CLOUD_BASE_URL", registry.cloud_base_url, 1);
157 + registry.cloud_base_url = cloud_config_url_get();
158 + nd_setenv("NETDATA_REGISTRY_CLOUD_BASE_URL", registry.cloud_base_url, 1);
159 }
160
161 // ----------------------------------------------------------------------------
@@ -164,21 +164,19 @@ void registry_update_cloud_base_url() {
164 int registry_request_hello_json(RRDHOST *host, struct web_client *w, bool do_not_track) {
165 registry_json_header(host, w, "hello", REGISTRY_STATUS_OK);
166
167 - if(host->node_id)
167 + if(!uuid_is_null(host->node_id))
168 buffer_json_member_add_uuid(w->response.data, "node_id", host->node_id);
169
170 buffer_json_member_add_object(w->response.data, "agent");
171 {
172 buffer_json_member_add_string(w->response.data, "machine_guid", localhost->machine_guid);
173
174 - if(localhost->node_id)
174 + if(!uuid_is_null(localhost->node_id))
175 buffer_json_member_add_uuid(w->response.data, "node_id", localhost->node_id);
176
177 - char *claim_id = get_agent_claimid();
178 - if (claim_id) {
179 - buffer_json_member_add_string(w->response.data, "claim_id", claim_id);
180 - freez(claim_id);
181 - }
177 + CLAIM_ID claim_id = claim_id_get();
178 + if (claim_id_is_set(claim_id))
179 + buffer_json_member_add_string(w->response.data, "claim_id", claim_id.str);
180
181 buffer_json_member_add_boolean(w->response.data, "bearer_protection", netdata_is_protected_by_bearer);
182 }
@@ -198,7 +196,7 @@ int registry_request_hello_json(RRDHOST *host, struct web_client *w, bool do_not
196 buffer_json_add_array_item_object(w->response.data);
197 buffer_json_member_add_string(w->response.data, "machine_guid", h->machine_guid);
198
201 - if(h->node_id)
199 + if(!uuid_is_null(h->node_id))
200 buffer_json_member_add_uuid(w->response.data, "node_id", h->node_id);
201
202 buffer_json_member_add_string(w->response.data, "hostname", rrdhost_registry_hostname(h));
src/registry/registry_init.c
+2 -2
@@ -101,8 +101,8 @@ int registry_init(void) {
101 registry.enable_cookies_samesite_secure = config_get_boolean(CONFIG_SECTION_REGISTRY, "enable cookies SameSite and Secure", 1);
102
103 registry_update_cloud_base_url();
104 - setenv("NETDATA_REGISTRY_HOSTNAME", registry.hostname, 1);
105 - setenv("NETDATA_REGISTRY_URL", registry.registry_to_announce, 1);
104 + nd_setenv("NETDATA_REGISTRY_HOSTNAME", registry.hostname, 1);
105 + nd_setenv("NETDATA_REGISTRY_URL", registry.registry_to_announce, 1);
106
107 registry.max_url_length = (size_t)config_get_number(CONFIG_SECTION_REGISTRY, "max URL length", 1024);
108 if(registry.max_url_length < 10) {
src/registry/registry_internals.c
+1 -1
@@ -315,7 +315,7 @@ const char *registry_get_this_machine_guid(void) {
315 close(fd);
316 }
317
318 - setenv("NETDATA_REGISTRY_UNIQUE_ID", guid, 1);
318 + nd_setenv("NETDATA_REGISTRY_UNIQUE_ID", guid, 1);
319
320 return guid;
321 }
src/registry/registry_internals.h
+1 -1
@@ -33,7 +33,7 @@ struct registry {
33 char *registry_domain;
34 char *hostname;
35 char *registry_to_announce;
36 - char *cloud_base_url;
36 + const char *cloud_base_url;
37 time_t persons_expiration; // seconds to expire idle persons
38 int verify_cookies_redirects;
39 int enable_cookies_samesite_secure;
src/streaming/protocol/command-claimed_id.c new
+78
@@ -0,0 +1,78 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "commands.h"
4 +#include "collectors/plugins.d/pluginsd_internals.h"
5 +
6 +PARSER_RC rrdpush_receiver_pluginsd_claimed_id(char **words, size_t num_words, PARSER *parser) {
7 + const char *machine_guid_str = get_word(words, num_words, 1);
8 + const char *claim_id_str = get_word(words, num_words, 2);
9 +
10 + if (!machine_guid_str || !claim_id_str) {
11 + netdata_log_error("PLUGINSD: command CLAIMED_ID came malformed, machine_guid '%s', claim_id '%s'",
12 + machine_guid_str ? machine_guid_str : "[unset]",
13 + claim_id_str ? claim_id_str : "[unset]");
14 + return PARSER_RC_ERROR;
15 + }
16 +
17 + RRDHOST *host = parser->user.host;
18 +
19 + nd_uuid_t machine_uuid;
20 + if(uuid_parse(machine_guid_str, machine_uuid)) {
21 + netdata_log_error("PLUGINSD: parameter machine guid to CLAIMED_ID command is not valid UUID. "
22 + "Received: '%s'.", machine_guid_str);
23 + return PARSER_RC_ERROR;
24 + }
25 +
26 + nd_uuid_t claim_uuid;
27 + if(strcmp(claim_id_str, "NULL") == 0)
28 + uuid_clear(claim_uuid);
29 +
30 + else if(uuid_parse(claim_id_str, claim_uuid) != 0) {
31 + netdata_log_error("PLUGINSD: parameter claim id to CLAIMED_ID command is not valid UUID. "
32 + "Received: '%s'.", claim_id_str);
33 + return PARSER_RC_ERROR;
34 + }
35 +
36 + if(strcmp(machine_guid_str, host->machine_guid) != 0) {
37 + netdata_log_error("PLUGINSD: received claim id for host '%s' but it came over the connection of '%s'",
38 + machine_guid_str, host->machine_guid);
39 + return PARSER_RC_OK; //the message is OK problem must be somewhere else
40 + }
41 +
42 + if(host == localhost) {
43 + netdata_log_error("PLUGINSD: CLAIMED_ID command cannot be used to set the claimed id of localhost. "
44 + "Received: '%s'.", claim_id_str);
45 + return PARSER_RC_OK;
46 + }
47 +
48 + if(!uuid_is_null(claim_uuid)) {
49 + uuid_copy(host->aclk.claim_id_of_origin.uuid, claim_uuid);
50 + rrdpush_sender_send_claimed_id(host);
51 + }
52 +
53 + return PARSER_RC_OK;
54 +}
55 +
56 +void rrdpush_sender_send_claimed_id(RRDHOST *host) {
57 + if(!stream_has_capability(host->sender, STREAM_CAP_CLAIM))
58 + return;
59 +
60 + if(unlikely(!rrdhost_can_send_definitions_to_parent(host)))
61 + return;
62 +
63 + BUFFER *wb = sender_start(host->sender);
64 +
65 + char str[UUID_STR_LEN] = "";
66 + ND_UUID uuid = host->aclk.claim_id_of_origin;
67 + if(!UUIDiszero(uuid))
68 + uuid_unparse_lower(uuid.uuid, str);
69 + else
70 + strncpyz(str, "NULL", sizeof(str) - 1);
71 +
72 + buffer_sprintf(wb, PLUGINSD_KEYWORD_CLAIMED_ID " '%s' '%s'\n",
73 + host->machine_guid, str);
74 +
75 + sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA);
76 +
77 + sender_thread_buffer_free();
78 +}
src/streaming/protocol/command-nodeid.c new
+116
@@ -0,0 +1,116 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "commands.h"
4 +#include "collectors/plugins.d/pluginsd_internals.h"
5 +
6 +// the child disconnected from the parent, and it has to clear the parent's claim id
7 +void rrdpush_sender_clear_child_claim_id(RRDHOST *host) {
8 + host->aclk.claim_id_of_parent = UUID_ZERO;
9 +}
10 +
11 +// the parent sends to the child its claim id, node id and cloud url
12 +void rrdpush_receiver_send_node_and_claim_id_to_child(RRDHOST *host) {
13 + if(host == localhost || uuid_is_null(host->node_id)) return;
14 +
15 + spinlock_lock(&host->receiver_lock);
16 + if(host->receiver && stream_has_capability(host->receiver, STREAM_CAP_NODE_ID)) {
17 + char node_id_str[UUID_STR_LEN] = "";
18 + uuid_unparse_lower(host->node_id, node_id_str);
19 +
20 + CLAIM_ID claim_id = claim_id_get();
21 +
22 + if((!claim_id_is_set(claim_id) || !aclk_online())) {
23 + // the agent is not claimed or not connected, just use parent claim id
24 + // to allow the connection flow.
25 + // this may be zero and it is ok.
26 + claim_id.uuid = host->aclk.claim_id_of_parent;
27 + uuid_unparse_lower(claim_id.uuid.uuid, claim_id.str);
28 + }
29 +
30 + char buf[4096];
31 + snprintfz(buf, sizeof(buf),
32 + PLUGINSD_KEYWORD_NODE_ID " '%s' '%s' '%s'\n",
33 + claim_id.str, node_id_str, cloud_config_url_get());
34 +
35 + send_to_plugin(buf, __atomic_load_n(&host->receiver->parser, __ATOMIC_RELAXED));
36 + }
37 + spinlock_unlock(&host->receiver_lock);
38 +}
39 +
40 +// the sender of the child receives node id, claim id and cloud url from the receiver of the parent
41 +void rrdpush_sender_get_node_and_claim_id_from_parent(struct sender_state *s) {
42 + char *claim_id = get_word(s->line.words, s->line.num_words, 1);
43 + char *node_id = get_word(s->line.words, s->line.num_words, 2);
44 + char *url = get_word(s->line.words, s->line.num_words, 3);
45 +
46 + bool claimed = is_agent_claimed();
47 +
48 + ND_UUID claim_uuid;
49 + if (uuid_parse(claim_id ? claim_id : "", claim_uuid.uuid) != 0) {
50 + nd_log(NDLS_DAEMON, NDLP_ERR,
51 + "STREAM %s [send to %s] received invalid claim id '%s'",
52 + rrdhost_hostname(s->host), s->connected_to,
53 + claim_id ? claim_id : "(unset)");
54 + return;
55 + }
56 +
57 + ND_UUID node_uuid;
58 + if(uuid_parse(node_id ? node_id : "", node_uuid.uuid) != 0) {
59 + nd_log(NDLS_DAEMON, NDLP_ERR,
60 + "STREAM %s [send to %s] received an invalid node id '%s'",
61 + rrdhost_hostname(s->host), s->connected_to,
62 + node_id ? node_id : "(unset)");
63 + return;
64 + }
65 +
66 + if (!UUIDiszero(s->host->aclk.claim_id_of_parent) && !UUIDeq(s->host->aclk.claim_id_of_parent, claim_uuid))
67 + nd_log(NDLS_DAEMON, NDLP_INFO,
68 + "STREAM %s [send to %s] changed parent's claim id to %s",
69 + rrdhost_hostname(s->host), s->connected_to, claim_id ? claim_id : "(unset)");
70 +
71 + if(!uuid_is_null(s->host->node_id) && uuid_compare(s->host->node_id, node_uuid.uuid) != 0) {
72 + if(claimed) {
73 + nd_log(NDLS_DAEMON, NDLP_ERR,
74 + "STREAM %s [send to %s] parent reports different node id '%s', but we are claimed. Ignoring it.",
75 + rrdhost_hostname(s->host), s->connected_to, node_id ? node_id : "(unset)");
76 + return;
77 + }
78 + else
79 + nd_log(NDLS_DAEMON, NDLP_WARNING,
80 + "STREAM %s [send to %s] changed node id to %s",
81 + rrdhost_hostname(s->host), s->connected_to, node_id ? node_id : "(unset)");
82 + }
83 +
84 + if(!url || !*url) {
85 + nd_log(NDLS_DAEMON, NDLP_ERR,
86 + "STREAM %s [send to %s] received an invalid cloud URL '%s'",
87 + rrdhost_hostname(s->host), s->connected_to,
88 + url ? url : "(unset)");
89 + return;
90 + }
91 +
92 + s->host->aclk.claim_id_of_parent = claim_uuid;
93 +
94 + // There are some very strange corner cases here:
95 + //
96 + // - Agent is claimed but offline, and it receives node_id and cloud_url from a different Netdata Cloud.
97 + // - Agent is configured to talk to an on-prem Netdata Cloud, it is offline, but the parent is connected
98 + // to a different Netdata Cloud.
99 + //
100 + // The solution below, tries to get the agent online, using the latest information.
101 + // So, if the agent is not claimed or not connected, we inherit whatever information sent from the parent,
102 + // to allow the user to work with it.
103 +
104 + if(claimed && aclk_online())
105 + // we are directly claimed and connected, ignore node id and cloud url
106 + return;
107 +
108 + if(uuid_is_null(s->host->node_id))
109 + uuid_copy(s->host->node_id, node_uuid.uuid);
110 +
111 + // we change the URL, to allow the agent dashboard to work with Netdata Cloud on-prem, if any.
112 + cloud_config_url_set(url);
113 +
114 + // send it down the line (to children)
115 + rrdpush_receiver_send_node_and_claim_id_to_child(s->host);
116 +}
src/streaming/protocol/commands.c new
+3
@@ -0,0 +1,3 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "commands.h"
src/streaming/protocol/commands.h new
+14
@@ -0,0 +1,14 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_STREAMING_PROTCOL_COMMANDS_H
4 +#define NETDATA_STREAMING_PROTCOL_COMMANDS_H
5 +
6 +#include "../rrdpush.h"
7 +
8 +void rrdpush_sender_get_node_and_claim_id_from_parent(struct sender_state *s);
9 +void rrdpush_receiver_send_node_and_claim_id_to_child(RRDHOST *host);
10 +void rrdpush_sender_clear_child_claim_id(RRDHOST *host);
11 +
12 +void rrdpush_sender_send_claimed_id(RRDHOST *host);
13 +
14 +#endif //NETDATA_STREAMING_PROTCOL_COMMANDS_H
src/streaming/receiver.c
+27 -48
@@ -19,9 +19,7 @@ void receiver_state_free(struct receiver_state *rpt) {
19 freez(rpt->program_name);
20 freez(rpt->program_version);
21
22 -#ifdef ENABLE_HTTPS
22 netdata_ssl_close(&rpt->ssl);
24 -#endif
23
24 if(rpt->fd != -1) {
25 internal_error(true, "closing socket...");
@@ -73,9 +71,7 @@ static inline int read_stream(struct receiver_state *r, char* buffer, size_t siz
71 errno_clear();
72
73 switch(wait_on_socket_or_cancel_with_timeout(
76 -#ifdef ENABLE_HTTPS
74 &r->ssl,
78 -#endif
75 r->fd, 0, POLLIN, NULL))
76 {
77 case 0: // data are waiting
@@ -95,14 +91,10 @@ static inline int read_stream(struct receiver_state *r, char* buffer, size_t siz
91 return -2;
92 }
93
98 -#ifdef ENABLE_HTTPS
94 if (SSL_connection(&r->ssl))
95 bytes_read = netdata_ssl_read(&r->ssl, buffer, size);
96 else
97 bytes_read = read(r->fd, buffer, size);
103 -#else
104 - bytes_read = read(r->fd, buffer, size);
105 -#endif
98
99 } while(bytes_read < 0 && errno == EINTR && tries--);
100
@@ -327,7 +319,7 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i
319 .capabilities = rpt->capabilities,
320 };
321
330 - parser = parser_init(&user, NULL, NULL, fd, PARSER_INPUT_SPLIT, ssl);
322 + parser = parser_init(&user, fd, fd, PARSER_INPUT_SPLIT, ssl);
323 }
324
325 #ifdef ENABLE_H2O
@@ -338,10 +330,6 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i
330
331 rrd_collector_started();
332
341 - // this keeps the parser with its current value
342 - // so, parser needs to be allocated before pushing it
343 - CLEANUP_FUNCTION_REGISTER(pluginsd_process_thread_cleanup) parser_ptr = parser;
344 -
333 bool compressed_connection = rrdpush_decompression_initialize(rpt);
334 buffered_reader_init(&rpt->reader);
335
@@ -367,6 +355,9 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i
355 };
356 ND_LOG_STACK_PUSH(lgs);
357
358 + __atomic_store_n(&rpt->parser, parser, __ATOMIC_RELAXED);
359 + rrdpush_receiver_send_node_and_claim_id_to_child(rpt->host);
360 +
361 while(!receiver_should_stop(rpt)) {
362
363 if(!buffered_reader_next_line(&rpt->reader, buffer)) {
@@ -391,6 +382,13 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i
382 buffer->len = 0;
383 buffer->buffer[0] = '\0';
384 }
385 +
386 + // make sure send_to_plugin() will not write any data to the socket
387 + spinlock_lock(&parser->writer.spinlock);
388 + parser->fd_output = -1;
389 + parser->ssl_output = NULL;
390 + spinlock_unlock(&parser->writer.spinlock);
391 +
392 result = parser->user.data_collections_count;
393 return result;
394 }
@@ -409,7 +407,7 @@ static bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) {
407 bool signal_rrdcontext = false;
408 bool set_this = false;
409
412 - netdata_mutex_lock(&host->receiver_lock);
410 + spinlock_lock(&host->receiver_lock);
411
412 if (!host->receiver) {
413 rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN);
@@ -452,7 +450,7 @@ static bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) {
450 set_this = true;
451 }
452
455 - netdata_mutex_unlock(&host->receiver_lock);
453 + spinlock_unlock(&host->receiver_lock);
454
455 if(signal_rrdcontext)
456 rrdcontext_host_child_connected(host);
@@ -464,13 +462,16 @@ static void rrdhost_clear_receiver(struct receiver_state *rpt) {
462 RRDHOST *host = rpt->host;
463 if(host) {
464 bool signal_rrdcontext = false;
467 - netdata_mutex_lock(&host->receiver_lock);
465 + spinlock_lock(&host->receiver_lock);
466
467 // Make sure that we detach this thread and don't kill a freshly arriving receiver
468 if(host->receiver == rpt) {
469 __atomic_sub_fetch(&localhost->connected_children_count, 1, __ATOMIC_RELAXED);
470 rrdhost_flag_set(rpt->host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED);
471
472 + pluginsd_process_cleanup(rpt->parser);
473 + __atomic_store_n(&rpt->parser, NULL, __ATOMIC_RELAXED);
474 +
475 host->trigger_chart_obsoletion_check = 0;
476 host->child_connect_time = 0;
477 host->child_disconnected_time = now_realtime_sec();
@@ -490,7 +491,7 @@ static void rrdhost_clear_receiver(struct receiver_state *rpt) {
491 rrdcalc_child_disconnected(host);
492 }
493
493 - netdata_mutex_unlock(&host->receiver_lock);
494 + spinlock_unlock(&host->receiver_lock);
495
496 if(signal_rrdcontext)
497 rrdcontext_host_child_disconnected(host);
@@ -502,7 +503,7 @@ static void rrdhost_clear_receiver(struct receiver_state *rpt) {
503 bool stop_streaming_receiver(RRDHOST *host, STREAM_HANDSHAKE reason) {
504 bool ret = false;
505
505 - netdata_mutex_lock(&host->receiver_lock);
506 + spinlock_lock(&host->receiver_lock);
507
508 if(host->receiver) {
509 if(!host->receiver->exit.shutdown) {
@@ -516,12 +517,12 @@ bool stop_streaming_receiver(RRDHOST *host, STREAM_HANDSHAKE reason) {
517
518 int count = 2000;
519 while (host->receiver && count-- > 0) {
519 - netdata_mutex_unlock(&host->receiver_lock);
520 + spinlock_unlock(&host->receiver_lock);
521
522 // let the lock for the receiver thread to exit
523 sleep_usec(1 * USEC_PER_MS);
524
524 - netdata_mutex_lock(&host->receiver_lock);
525 + spinlock_lock(&host->receiver_lock);
526 }
527
528 if(host->receiver)
@@ -533,16 +534,14 @@ bool stop_streaming_receiver(RRDHOST *host, STREAM_HANDSHAKE reason) {
534 else
535 ret = true;
536
536 - netdata_mutex_unlock(&host->receiver_lock);
537 + spinlock_unlock(&host->receiver_lock);
538
539 return ret;
540 }
541
542 static void rrdpush_send_error_on_taken_over_connection(struct receiver_state *rpt, const char *msg) {
543 (void) send_timeout(
543 -#ifdef ENABLE_HTTPS
544 &rpt->ssl,
545 -#endif
545 rpt->fd,
546 (char *)msg,
547 strlen(msg),
@@ -732,11 +731,7 @@ static void rrdpush_receive(struct receiver_state *rpt)
731 , rpt->host->rrd_history_entries
732 , rrd_memory_mode_name(rpt->host->rrd_memory_mode)
733 , (rpt->config.health_enabled == CONFIG_BOOLEAN_NO)?"disabled":((rpt->config.health_enabled == CONFIG_BOOLEAN_YES)?"enabled":"auto")
735 -#ifdef ENABLE_HTTPS
734 , (rpt->ssl.conn != NULL) ? " SSL," : ""
737 -#else
738 - , ""
739 -#endif
735 );
736 #endif // NETDATA_INTERNAL_CHECKS
737
@@ -786,9 +781,7 @@ static void rrdpush_receive(struct receiver_state *rpt)
781 } else {
782 #endif
783 ssize_t bytes_sent = send_timeout(
789 -#ifdef ENABLE_HTTPS
784 &rpt->ssl,
791 -#endif
785 rpt->fd, initial_response, strlen(initial_response), 0, 60);
786
787 if(bytes_sent != (ssize_t)strlen(initial_response)) {
@@ -830,12 +823,9 @@ static void rrdpush_receive(struct receiver_state *rpt)
823 rpt, "connected and ready to receive data",
824 RRDPUSH_STATUS_CONNECTED, NDLP_INFO);
825
833 -#ifdef ENABLE_ACLK
826 // in case we have cloud connection we inform cloud
827 // new child connected
836 - if (netdata_cloud_enabled)
837 - aclk_host_state_update(rpt->host, 1, 1);
838 -#endif
828 + aclk_host_state_update(rpt->host, 1, 1);
829
830 rrdhost_set_is_parent_label();
831
@@ -845,14 +835,10 @@ static void rrdpush_receive(struct receiver_state *rpt)
835 // let it reconnect to parent immediately
836 rrdpush_reset_destinations_postpone_time(rpt->host);
837
848 - size_t count = streaming_parser(rpt, &cd, rpt->fd,
849 -#ifdef ENABLE_HTTPS
850 - (rpt->ssl.conn) ? &rpt->ssl : NULL
851 -#else
852 - NULL
853 -#endif
854 - );
838 + // receive data
839 + size_t count = streaming_parser(rpt, &cd, rpt->fd, (rpt->ssl.conn) ? &rpt->ssl : NULL);
840
841 + // the parser stopped
842 receiver_set_exit_reason(rpt, STREAM_HANDSHAKE_DISCONNECT_PARSER_EXIT, false);
843
844 {
@@ -863,12 +849,9 @@ static void rrdpush_receive(struct receiver_state *rpt)
849 RRDPUSH_STATUS_DISCONNECTED, NDLP_WARNING);
850 }
851
866 -#ifdef ENABLE_ACLK
852 // in case we have cloud connection we inform cloud
853 // a child disconnected
869 - if (netdata_cloud_enabled)
870 - aclk_host_state_update(rpt->host, 0, 1);
871 -#endif
854 + aclk_host_state_update(rpt->host, 0, 1);
855
856 cleanup:
857 ;
@@ -903,11 +886,7 @@ static bool stream_receiver_log_transport(BUFFER *wb, void *ptr) {
886 if(!rpt)
887 return false;
888
906 -#ifdef ENABLE_HTTPS
889 buffer_strcat(wb, SSL_connection(&rpt->ssl) ? "https" : "http");
908 -#else
909 - buffer_strcat(wb, "http");
910 -#endif
890 return true;
891 }
892
src/streaming/replication.c
+4 -4
@@ -718,7 +718,7 @@ bool replication_response_execute_and_finalize(struct replication_query *q, size
718 struct replication_request_details {
719 struct {
720 send_command callback;
721 - void *data;
721 + struct parser *parser;
722 } caller;
723
724 RRDHOST *host;
@@ -826,7 +826,7 @@ static bool send_replay_chart_cmd(struct replication_request_details *r, const c
826 rrdset_id(st), r->wanted.start_streaming ? "true" : "false",
827 (unsigned long long)r->wanted.after, (unsigned long long)r->wanted.before);
828
829 - ssize_t ret = r->caller.callback(buffer, r->caller.data);
829 + ssize_t ret = r->caller.callback(buffer, r->caller.parser);
830 if (ret < 0) {
831 netdata_log_error("REPLAY ERROR: 'host:%s/chart:%s' failed to send replication request to child (error %zd)",
832 rrdhost_hostname(r->host), rrdset_id(r->st), ret);
@@ -836,14 +836,14 @@ static bool send_replay_chart_cmd(struct replication_request_details *r, const c
836 return true;
837 }
838
839 -bool replicate_chart_request(send_command callback, void *callback_data, RRDHOST *host, RRDSET *st,
839 +bool replicate_chart_request(send_command callback, struct parser *parser, RRDHOST *host, RRDSET *st,
840 time_t child_first_entry, time_t child_last_entry, time_t child_wall_clock_time,
841 time_t prev_first_entry_wanted, time_t prev_last_entry_wanted)
842 {
843 struct replication_request_details r = {
844 .caller = {
845 .callback = callback,
846 - .data = callback_data,
846 + .parser = parser,
847 },
848
849 .host = host,
src/streaming/replication.h
+2 -2
@@ -17,9 +17,9 @@ struct replication_query_statistics replication_get_query_statistics(void);
17
18 bool replicate_chart_response(RRDHOST *rh, RRDSET *rs, bool start_streaming, time_t after, time_t before);
19
20 -typedef ssize_t (*send_command)(const char *txt, void *data);
20 +typedef ssize_t (*send_command)(const char *txt, struct parser *parser);
21
22 -bool replicate_chart_request(send_command callback, void *callback_data,
22 +bool replicate_chart_request(send_command callback, struct parser *parser,
23 RRDHOST *rh, RRDSET *rs,
24 time_t child_first_entry, time_t child_last_entry, time_t child_wall_clock_time,
25 time_t response_first_start_time, time_t response_last_end_time);
src/streaming/rrdpush.c
+36 -43
@@ -44,23 +44,21 @@ STREAM_CAPABILITIES globally_disabled_capabilities = STREAM_CAP_NONE;
44 unsigned int default_rrdpush_compression_enabled = 1;
45 char *default_rrdpush_destination = NULL;
46 char *default_rrdpush_api_key = NULL;
47 -char *default_rrdpush_send_charts_matching = NULL;
47 +char *default_rrdpush_send_charts_matching = "*";
48 bool default_rrdpush_enable_replication = true;
49 time_t default_rrdpush_seconds_to_replicate = 86400;
50 time_t default_rrdpush_replication_step = 600;
51 -#ifdef ENABLE_HTTPS
51 char *netdata_ssl_ca_path = NULL;
52 char *netdata_ssl_ca_file = NULL;
54 -#endif
53
54 static void load_stream_conf() {
55 errno_clear();
58 - char *filename = strdupz_path_subpath(netdata_configured_user_config_dir, "stream.conf");
56 + char *filename = filename_from_path_entry_strdupz(netdata_configured_user_config_dir, "stream.conf");
57 if(!appconfig_load(&stream_config, filename, 0, NULL)) {
58 nd_log_daemon(NDLP_NOTICE, "CONFIG: cannot load user config '%s'. Will try stock config.", filename);
59 freez(filename);
60
63 - filename = strdupz_path_subpath(netdata_configured_stock_config_dir, "stream.conf");
61 + filename = filename_from_path_entry_strdupz(netdata_configured_stock_config_dir, "stream.conf");
62 if(!appconfig_load(&stream_config, filename, 0, NULL))
63 nd_log_daemon(NDLP_NOTICE, "CONFIG: cannot load stock config '%s'. Running with internal defaults.", filename);
64 }
@@ -100,7 +98,7 @@ int rrdpush_init() {
98 default_rrdpush_enabled = (unsigned int)appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM, "enabled", default_rrdpush_enabled);
99 default_rrdpush_destination = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "destination", "");
100 default_rrdpush_api_key = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "api key", "");
103 - default_rrdpush_send_charts_matching = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "send charts matching", "*");
101 + default_rrdpush_send_charts_matching = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "send charts matching", default_rrdpush_send_charts_matching);
102
103 default_rrdpush_enable_replication = config_get_boolean(CONFIG_SECTION_DB, "enable replication", default_rrdpush_enable_replication);
104 default_rrdpush_seconds_to_replicate = config_get_number(CONFIG_SECTION_DB, "seconds to replicate", default_rrdpush_seconds_to_replicate);
@@ -132,7 +130,6 @@ int rrdpush_init() {
130 default_rrdpush_enabled = 0;
131 }
132
135 -#ifdef ENABLE_HTTPS
133 netdata_ssl_validate_certificate_sender = !appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM, "ssl skip certificate verification", !netdata_ssl_validate_certificate);
134
135 if(!netdata_ssl_validate_certificate_sender)
@@ -140,7 +137,6 @@ int rrdpush_init() {
137
138 netdata_ssl_ca_path = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "CApath", NULL);
139 netdata_ssl_ca_file = appconfig_get(&stream_config, CONFIG_SECTION_STREAM, "CAfile", NULL);
143 -#endif
140
141 return default_rrdpush_enabled;
142 }
@@ -172,12 +168,31 @@ static inline bool should_send_chart_matching(RRDSET *st, RRDSET_FLAGS flags) {
168 else
169 rrdset_flag_set(st, RRDSET_FLAG_UPSTREAM_IGNORE);
170 }
175 - else if(simple_pattern_matches_string(host->rrdpush_send_charts_matching, st->id) ||
176 - simple_pattern_matches_string(host->rrdpush_send_charts_matching, st->name))
171 + else {
172 + int negative = 0, positive = 0;
173 + SIMPLE_PATTERN_RESULT r;
174
178 - rrdset_flag_set(st, RRDSET_FLAG_UPSTREAM_SEND);
179 - else
180 - rrdset_flag_set(st, RRDSET_FLAG_UPSTREAM_IGNORE);
175 + r = simple_pattern_matches_string_extract(host->rrdpush.send.charts_matching, st->context, NULL, 0);
176 + if(r == SP_MATCHED_POSITIVE) positive++;
177 + else if(r == SP_MATCHED_NEGATIVE) negative++;
178 +
179 + if(!negative) {
180 + r = simple_pattern_matches_string_extract(host->rrdpush.send.charts_matching, st->name, NULL, 0);
181 + if (r == SP_MATCHED_POSITIVE) positive++;
182 + else if (r == SP_MATCHED_NEGATIVE) negative++;
183 + }
184 +
185 + if(!negative) {
186 + r = simple_pattern_matches_string_extract(host->rrdpush.send.charts_matching, st->id, NULL, 0);
187 + if (r == SP_MATCHED_POSITIVE) positive++;
188 + else if (r == SP_MATCHED_NEGATIVE) negative++;
189 + }
190 +
191 + if(!negative && positive)
192 + rrdset_flag_set(st, RRDSET_FLAG_UPSTREAM_SEND);
193 + else
194 + rrdset_flag_set(st, RRDSET_FLAG_UPSTREAM_IGNORE);
195 + }
196
197 // get the flags again, to know how to respond
198 flags = rrdset_flag_check(st, RRDSET_FLAG_UPSTREAM_SEND|RRDSET_FLAG_UPSTREAM_IGNORE);
@@ -578,24 +593,6 @@ void rrdpush_send_global_functions(RRDHOST *host) {
593 sender_thread_buffer_free();
594 }
595
581 -void rrdpush_send_claimed_id(RRDHOST *host) {
582 - if(!stream_has_capability(host->sender, STREAM_CAP_CLAIM))
583 - return;
584 -
585 - if(unlikely(!rrdhost_can_send_definitions_to_parent(host)))
586 - return;
587 -
588 - BUFFER *wb = sender_start(host->sender);
589 - rrdhost_aclk_state_lock(host);
590 -
591 - buffer_sprintf(wb, "CLAIMED_ID %s %s\n", host->machine_guid, (host->aclk_state.claimed_id ? host->aclk_state.claimed_id : "NULL") );
592 -
593 - rrdhost_aclk_state_unlock(host);
594 - sender_commit(host->sender, wb, STREAM_TRAFFIC_TYPE_METADATA);
595 -
596 - sender_thread_buffer_free();
597 -}
598 -
596 int connect_to_one_of_destinations(
597 RRDHOST *host,
598 int default_port,
@@ -677,7 +674,7 @@ bool destinations_init_add_one(char *entry, void *data) {
674 }
675
676 void rrdpush_destinations_init(RRDHOST *host) {
680 - if(!host->rrdpush_send_destination) return;
677 + if(!host->rrdpush.send.destination) return;
678
679 rrdpush_destinations_free(host);
680
@@ -687,7 +684,7 @@ void rrdpush_destinations_init(RRDHOST *host) {
684 .count = 0,
685 };
686
690 - foreach_entry_in_connection_string(host->rrdpush_send_destination, destinations_init_add_one, &t);
687 + foreach_entry_in_connection_string(host->rrdpush.send.destination, destinations_init_add_one, &t);
688
689 host->destinations = t.list;
690 }
@@ -777,12 +774,10 @@ int rrdpush_receiver_too_busy_now(struct web_client *w) {
774 static void rrdpush_receiver_takeover_web_connection(struct web_client *w, struct receiver_state *rpt) {
775 rpt->fd = w->ifd;
776
780 -#ifdef ENABLE_HTTPS
777 rpt->ssl.conn = w->ssl.conn;
778 rpt->ssl.state = w->ssl.state;
779
780 w->ssl = NETDATA_SSL_UNSET_CONNECTION;
785 -#endif
781
782 WEB_CLIENT_IS_DEAD(w);
783
@@ -825,9 +820,7 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_stri
820 rpt->client_ip = strdupz(w->client_ip);
821 rpt->client_port = strdupz(w->client_port);
822
828 -#ifdef ENABLE_HTTPS
823 rpt->ssl = NETDATA_SSL_UNSET_CONNECTION;
830 -#endif
824
825 rpt->config.update_every = default_rrd_update_every;
826
@@ -1083,9 +1076,7 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_stri
1076 snprintfz(initial_response, HTTP_HEADER_SIZE, "%s", START_STREAMING_ERROR_SAME_LOCALHOST);
1077
1078 if(send_timeout(
1086 -#ifdef ENABLE_HTTPS
1079 &rpt->ssl,
1088 -#endif
1080 rpt->fd, initial_response, strlen(initial_response), 0, 60) != (ssize_t)strlen(initial_response)) {
1081
1082 nd_log_daemon(NDLP_ERR, "STREAM '%s' [receive from [%s]:%s]: "
@@ -1149,7 +1140,7 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_stri
1140 host = NULL;
1141
1142 if (host) {
1152 - netdata_mutex_lock(&host->receiver_lock);
1143 + spinlock_lock(&host->receiver_lock);
1144 if (host->receiver) {
1145 age = now_monotonic_sec() - host->receiver->last_msg_t;
1146
@@ -1158,7 +1149,7 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_stri
1149 else
1150 receiver_stale = true;
1151 }
1161 - netdata_mutex_unlock(&host->receiver_lock);
1152 + spinlock_unlock(&host->receiver_lock);
1153 }
1154 rrd_rdunlock();
1155
@@ -1294,6 +1285,7 @@ static struct {
1285 {STREAM_CAP_IEEE754, "IEEE754" },
1286 {STREAM_CAP_DATA_WITH_ML, "ML" },
1287 {STREAM_CAP_DYNCFG, "DYNCFG" },
1288 + {STREAM_CAP_NODE_ID, "NODEID" },
1289 {STREAM_CAP_SLOTS, "SLOTS" },
1290 {STREAM_CAP_ZSTD, "ZSTD" },
1291 {STREAM_CAP_GZIP, "GZIP" },
@@ -1352,12 +1344,12 @@ STREAM_CAPABILITIES stream_our_capabilities(RRDHOST *host, bool sender) {
1344 // we have DATA_WITH_ML capability
1345 // we should remove the DATA_WITH_ML capability if our database does not have anomaly info
1346 // this can happen under these conditions: 1. we don't run ML, and 2. we don't receive ML
1355 - netdata_mutex_lock(&host->receiver_lock);
1347 + spinlock_lock(&host->receiver_lock);
1348
1349 if(!ml_host_running(host) && !stream_has_capability(host->receiver, STREAM_CAP_DATA_WITH_ML))
1350 disabled_capabilities |= STREAM_CAP_DATA_WITH_ML;
1351
1360 - netdata_mutex_unlock(&host->receiver_lock);
1352 + spinlock_unlock(&host->receiver_lock);
1353
1354 if(host->sender)
1355 disabled_capabilities |= host->sender->disabled_capabilities;
@@ -1378,6 +1370,7 @@ STREAM_CAPABILITIES stream_our_capabilities(RRDHOST *host, bool sender) {
1370 STREAM_CAP_PROGRESS |
1371 STREAM_CAP_COMPRESSIONS_AVAILABLE |
1372 STREAM_CAP_DYNCFG |
1373 + STREAM_CAP_NODE_ID |
1374 STREAM_CAP_IEEE754 |
1375 STREAM_CAP_DATA_WITH_ML |
1376 0) & ~disabled_capabilities;
src/streaming/rrdpush.h
+11 -5
@@ -54,6 +54,7 @@ typedef enum {
54 STREAM_CAP_BROTLI = (1 << 21), // BROTLI compression supported
55 STREAM_CAP_PROGRESS = (1 << 22), // Functions PROGRESS support
56 STREAM_CAP_DYNCFG = (1 << 23), // support for DYNCFG
57 + STREAM_CAP_NODE_ID = (1 << 24), // support for sending NODE_ID back to the child
58
59 STREAM_CAP_INVALID = (1 << 30), // used as an invalid value for capabilities when this is set
60 // this must be signed int, so don't use the last bit
@@ -238,9 +239,7 @@ struct sender_state {
239 FILE *stream_log_fp;
240 #endif
241
241 -#ifdef ENABLE_HTTPS
242 NETDATA_SSL ssl; // structure used to encrypt the connection
243 -#endif
243
244 struct {
245 bool shutdown;
@@ -334,6 +333,8 @@ typedef struct stream_node_instance {
333 } STREAM_NODE_INSTANCE;
334 */
335
336 +struct parser;
337 +
338 struct receiver_state {
339 RRDHOST *host;
340 pid_t tid;
@@ -382,9 +383,7 @@ struct receiver_state {
383 STREAM_CAPABILITIES compression_priorities[COMPRESSION_ALGORITHM_MAX];
384 } config;
385
385 -#ifdef ENABLE_HTTPS
386 NETDATA_SSL ssl;
387 -#endif
387
388 time_t replication_first_time_t;
389
@@ -396,6 +395,12 @@ struct receiver_state {
395 } instances;
396 */
397
398 + // The parser pointer is safe to read and use, only when having the host receiver lock.
399 + // Without this lock, the data pointed by the pointer may vanish randomly.
400 + // Also, since the receiver sets it when it starts, it should be read with
401 + // an atomic read.
402 + struct parser *parser;
403 +
404 #ifdef ENABLE_H2O
405 void *h2o_ctx;
406 #endif
@@ -455,7 +460,6 @@ void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_
460 bool rrdset_push_chart_definition_now(RRDSET *st);
461 void *rrdpush_sender_thread(void *ptr);
462 void rrdpush_send_host_labels(RRDHOST *host);
458 -void rrdpush_send_claimed_id(RRDHOST *host);
463 void rrdpush_send_global_functions(RRDHOST *host);
464
465 int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_string, void *h2o_ctx);
@@ -758,4 +762,6 @@ void rrdpush_parse_compression_order(struct receiver_state *rpt, const char *ord
762 void rrdpush_select_receiver_compression_algorithm(struct receiver_state *rpt);
763 void rrdpush_compression_deactivate(struct sender_state *s);
764
765 +#include "protocol/commands.h"
766 +
767 #endif //NETDATA_RRDPUSH_H
src/streaming/sender.c
+16 -42
@@ -334,9 +334,7 @@ static void rrdpush_sender_after_connect(RRDHOST *host) {
334 }
335
336 static inline void rrdpush_sender_thread_close_socket(RRDHOST *host) {
337 -#ifdef ENABLE_HTTPS
337 netdata_ssl_close(&host->sender->ssl);
339 -#endif
338
339 if(host->sender->rrdpush_sender_socket != -1) {
340 close(host->sender->rrdpush_sender_socket);
@@ -349,6 +347,10 @@ static inline void rrdpush_sender_thread_close_socket(RRDHOST *host) {
347 // do not flush the circular buffer here
348 // this function is called sometimes with the mutex lock, sometimes without the lock
349 rrdpush_sender_charts_and_replication_reset(host);
350 +
351 + // clear the parent's claim id
352 + rrdpush_sender_clear_child_claim_id(host);
353 + rrdpush_receiver_send_node_and_claim_id_to_child(host);
354 }
355
356 void rrdpush_encode_variable(stream_encoded_t *se, RRDHOST *host) {
@@ -572,7 +574,6 @@ unsigned char alpn_proto_list[] = {
574 #define CONN_UPGRADE_VAL "upgrade"
575
576 static bool rrdpush_sender_connect_ssl(struct sender_state *s __maybe_unused) {
575 -#ifdef ENABLE_HTTPS
577 RRDHOST *host = s->host;
578 bool ssl_required = host->destination && host->destination->ssl;
579
@@ -627,11 +628,6 @@ static bool rrdpush_sender_connect_ssl(struct sender_state *s __maybe_unused) {
628
629 netdata_log_error("SSL: failed to establish connection.");
630 return false;
630 -
631 -#else
632 - // SSL is not enabled
633 - return true;
634 -#endif
631 }
632
633 static int rrdpush_http_upgrade_prelude(RRDHOST *host, struct sender_state *s) {
@@ -644,9 +640,7 @@ static int rrdpush_http_upgrade_prelude(RRDHOST *host, struct sender_state *s) {
640 HTTP_HDR_END);
641
642 ssize_t bytes = send_timeout(
647 -#ifdef ENABLE_HTTPS
643 &host->sender->ssl,
649 -#endif
644 s->rrdpush_sender_socket,
645 http,
646 strlen(http),
@@ -654,9 +648,7 @@ static int rrdpush_http_upgrade_prelude(RRDHOST *host, struct sender_state *s) {
648 1000);
649
650 bytes = recv_timeout(
657 -#ifdef ENABLE_HTTPS
651 &host->sender->ssl,
659 -#endif
652 s->rrdpush_sender_socket,
653 http,
654 HTTP_HEADER_SIZE,
@@ -818,7 +810,7 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
810 HTTP_1_1 HTTP_ENDL
811 "User-Agent: %s/%s\r\n"
812 "Accept: */*\r\n\r\n"
821 - , host->rrdpush_send_api_key
813 + , host->rrdpush.send.api_key
814 , rrdhost_hostname(host)
815 , rrdhost_registry_hostname(host)
816 , host->machine_guid
@@ -885,9 +877,7 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
877
878 ssize_t len = (ssize_t)strlen(http);
879 ssize_t bytes = send_timeout(
888 -#ifdef ENABLE_HTTPS
880 &host->sender->ssl,
890 -#endif
881 s->rrdpush_sender_socket,
882 http,
883 len,
@@ -914,9 +904,7 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
904 }
905
906 bytes = recv_timeout(
917 -#ifdef ENABLE_HTTPS
907 &host->sender->ssl,
919 -#endif
908 s->rrdpush_sender_socket,
909 http,
910 HTTP_HEADER_SIZE,
@@ -1037,14 +1025,10 @@ static ssize_t attempt_to_send(struct sender_state *s) {
1025 size_t outstanding = cbuffer_next_unsafe(s->buffer, &chunk);
1026 netdata_log_debug(D_STREAM, "STREAM: Sending data. Buffer r=%zu w=%zu s=%zu, next chunk=%zu", cb->read, cb->write, cb->size, outstanding);
1027
1040 -#ifdef ENABLE_HTTPS
1028 if(SSL_connection(&s->ssl))
1029 ret = netdata_ssl_write(&s->ssl, chunk, outstanding);
1030 else
1031 ret = send(s->rrdpush_sender_socket, chunk, outstanding, MSG_DONTWAIT);
1045 -#else
1046 - ret = send(s->rrdpush_sender_socket, chunk, outstanding, MSG_DONTWAIT);
1047 -#endif
1032
1033 if (likely(ret > 0)) {
1034 cbuffer_remove_unsafe(s->buffer, ret);
@@ -1072,14 +1056,10 @@ static ssize_t attempt_to_send(struct sender_state *s) {
1056 static ssize_t attempt_read(struct sender_state *s) {
1057 ssize_t ret;
1058
1075 -#ifdef ENABLE_HTTPS
1059 if (SSL_connection(&s->ssl))
1060 ret = netdata_ssl_read(&s->ssl, s->read_buffer + s->read_len, sizeof(s->read_buffer) - s->read_len - 1);
1061 else
1062 ret = recv(s->rrdpush_sender_socket, s->read_buffer + s->read_len, sizeof(s->read_buffer) - s->read_len - 1,MSG_DONTWAIT);
1080 -#else
1081 - ret = recv(s->rrdpush_sender_socket, s->read_buffer + s->read_len, sizeof(s->read_buffer) - s->read_len - 1,MSG_DONTWAIT);
1082 -#endif
1063
1064 if (ret > 0) {
1065 s->read_len += ret;
@@ -1089,13 +1069,9 @@ static ssize_t attempt_read(struct sender_state *s) {
1069 if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
1070 return ret;
1071
1092 -#ifdef ENABLE_HTTPS
1072 if (SSL_connection(&s->ssl))
1073 worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR);
1095 - else
1096 -#endif
1097 -
1098 - if (ret == 0 || errno == ECONNRESET) {
1074 + else if (ret == 0 || errno == ECONNRESET) {
1075 worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_PARENT_CLOSED);
1076 netdata_log_error("STREAM %s [send to %s]: connection closed by far end.", rrdhost_hostname(s->host), s->connected_to);
1077 }
@@ -1187,7 +1163,7 @@ static void execute_commands_function(struct sender_state *s, const char *comman
1163 stream_execute_function_callback, tmp,
1164 stream_has_capability(s, STREAM_CAP_PROGRESS) ? stream_execute_function_progress_callback : NULL,
1165 stream_has_capability(s, STREAM_CAP_PROGRESS) ? tmp : NULL,
1190 - NULL, NULL, payload, source);
1166 + NULL, NULL, payload, source, true);
1167
1168 if(code != HTTP_RESP_OK) {
1169 if (!buffer_strlen(wb))
@@ -1333,8 +1309,12 @@ void execute_commands(struct sender_state *s) {
1309 );
1310 }
1311 }
1312 + else if(command && strcmp(command, PLUGINSD_KEYWORD_NODE_ID) == 0) {
1313 + rrdpush_sender_get_node_and_claim_id_from_parent(s);
1314 + }
1315 else {
1337 - netdata_log_error("STREAM %s [send to %s] received unknown command over connection: %s", rrdhost_hostname(s->host), s->connected_to, s->line.words[0]?s->line.words[0]:"(unset)");
1316 + netdata_log_error("STREAM %s [send to %s] received unknown command over connection: %s",
1317 + rrdhost_hostname(s->host), s->connected_to, s->line.words[0]?s->line.words[0]:"(unset)");
1318 }
1319
1320 line_splitter_reset(&s->line);
@@ -1516,7 +1496,6 @@ static void rrdpush_sender_thread_cleanup_callback(void *pptr) {
1496 }
1497
1498 void rrdpush_initialize_ssl_ctx(RRDHOST *host __maybe_unused) {
1519 -#ifdef ENABLE_HTTPS
1499 static SPINLOCK sp = NETDATA_SPINLOCK_INITIALIZER;
1500 spinlock_lock(&sp);
1501
@@ -1538,7 +1517,6 @@ void rrdpush_initialize_ssl_ctx(RRDHOST *host __maybe_unused) {
1517 }
1518
1519 spinlock_unlock(&sp);
1541 -#endif
1520 }
1521
1522 static bool stream_sender_log_capabilities(BUFFER *wb, void *ptr) {
@@ -1555,11 +1533,7 @@ static bool stream_sender_log_transport(BUFFER *wb, void *ptr) {
1533 if(!state)
1534 return false;
1535
1558 -#ifdef ENABLE_HTTPS
1536 buffer_strcat(wb, SSL_connection(&state->ssl) ? "https" : "http");
1560 -#else
1561 - buffer_strcat(wb, "http");
1562 -#endif
1537 return true;
1538 }
1539
@@ -1627,9 +1601,9 @@ void *rrdpush_sender_thread(void *ptr) {
1601 worker_register_job_custom_metric(WORKER_SENDER_JOB_BYTES_COMPRESSION_RATIO, "cumulative compression savings ratio", "%", WORKER_METRIC_ABSOLUTE);
1602 worker_register_job_custom_metric(WORKER_SENDER_JOB_REPLAY_DICT_SIZE, "replication dict entries", "entries", WORKER_METRIC_ABSOLUTE);
1603
1630 - if(!rrdhost_has_rrdpush_sender_enabled(s->host) || !s->host->rrdpush_send_destination ||
1631 - !*s->host->rrdpush_send_destination || !s->host->rrdpush_send_api_key ||
1632 - !*s->host->rrdpush_send_api_key) {
1604 + if(!rrdhost_has_rrdpush_sender_enabled(s->host) || !s->host->rrdpush.send.destination ||
1605 + !*s->host->rrdpush.send.destination || !s->host->rrdpush.send.api_key ||
1606 + !*s->host->rrdpush.send.api_key) {
1607 netdata_log_error("STREAM %s [send]: thread created (task id %d), but host has streaming disabled.",
1608 rrdhost_hostname(s->host), gettid_cached());
1609 return NULL;
@@ -1714,7 +1688,7 @@ void *rrdpush_sender_thread(void *ptr) {
1688 break;
1689
1690 now_s = s->last_traffic_seen_t = now_monotonic_sec();
1717 - rrdpush_send_claimed_id(s->host);
1691 + rrdpush_sender_send_claimed_id(s->host);
1692 rrdpush_send_host_labels(s->host);
1693 rrdpush_send_global_functions(s->host);
1694 s->replication.oldest_request_after_t = 0;
src/streaming/stream.conf
+12 -11
@@ -62,32 +62,33 @@
62 #enable compression = yes
63
64 # The timeout to connect and send metrics
65 - timeout seconds = 60
65 + #timeout seconds = 60
66
67 # If the destination line above does not specify a port, use this
68 - default port = 19999
68 + #default port = 19999
69
70 - # filter the charts to be streamed
70 + # filter the charts and contexts to be streamed
71 # netdata SIMPLE PATTERN:
72 # - space separated list of patterns (use \ to include spaces in patterns)
73 # - use * as wildcard, any number of times within each pattern
74 # - prefix a pattern with ! for a negative match (ie not stream the charts it matches)
75 # - the order of patterns is important (left to right)
76 # To send all except a few, use: !this !that * (ie append a wildcard pattern)
77 - send charts matching = *
77 + # The pattern is matched against the context, the chart name and the chart id.
78 + #send charts matching = *
79
80 # The buffer to use for sending metrics.
81 # 10MB is good for 60 seconds of data, so increase this if you expect latencies.
82 # The buffer is flushed on reconnects (this will not prevent gaps at the charts).
82 - buffer size bytes = 10485760
83 + #buffer size bytes = 10485760
84
85 # If the connection fails, or it disconnects,
86 # retry after that many seconds.
86 - reconnect delay seconds = 5
87 + #reconnect delay seconds = 5
88
89 # Sync the clock of the charts for that many iterations, when starting.
90 # It is ignored when replication is enabled
90 - initial clock resync iterations = 60
91 + #initial clock resync iterations = 60
92
93 # -----------------------------------------------------------------------------
94 # 2. ON PARENT NETDATA - THE ONE THAT WILL BE RECEIVING METRICS
@@ -124,7 +125,7 @@
125 # will be pushing metrics using this API key.
126 # The metrics are received via the API port, so the same IPs
127 # should also be matched at netdata.conf [web].allow connections from
127 - allow from = *
128 + #allow from = *
129
130 # The default history in entries, for all hosts using this API key.
131 # You can also set it per host below.
@@ -151,7 +152,7 @@
152 #health enabled by default = auto
153
154 # postpone alarms for a short period after the sender is connected
154 - default postpone alarms on connect seconds = 60
155 + #default postpone alarms on connect seconds = 60
156
157 # seconds of health log events to keep
158 #default health log history = 432000
@@ -217,7 +218,7 @@
218 # The metrics are received via the API port, so the same IPs
219 # should also be matched at netdata.conf [web].allow connections from
220 # and at stream.conf [API_KEY].allow from
220 - allow from = *
221 + #allow from = *
222
223 # The number of entries in the database.
224 # This is ignored for db mode dbengine.
@@ -230,7 +231,7 @@
231 #health enabled = auto
232
233 # postpone alarms when the sender connects
233 - postpone alarms on connect seconds = 60
234 + #postpone alarms on connect seconds = 60
235
236 # seconds of health log events to keep
237 #health log history = 432000
src/web/api/badges/web_buffer_svg.h deleted
-18
@@ -1,18 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#ifndef NETDATA_WEB_BUFFER_SVG_H
4 -#define NETDATA_WEB_BUFFER_SVG_H 1
5 -
6 -#include "libnetdata/libnetdata.h"
7 -#include "web/server/web_client.h"
8 -
9 -void buffer_svg(BUFFER *wb, const char *label,
10 - NETDATA_DOUBLE value, const char *units, const char *label_color, const char *value_color, int precision, int scale, uint32_t options, int fixed_width_lbl, int fixed_width_val, const char* text_color_lbl, const char* text_color_val);
11 -char *format_value_and_unit(char *value_string, size_t value_string_len,
12 - NETDATA_DOUBLE value, const char *units, int precision);
13 -
14 -int web_client_api_request_v1_badge(struct rrdhost *host, struct web_client *w, char *url);
15 -
16 -#include "web/api/web_api_v1.h"
17 -
18 -#endif /* NETDATA_WEB_BUFFER_SVG_H */
src/web/api/exporters/allmetrics.c deleted
-132
@@ -1,132 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#include "allmetrics.h"
4 -
5 -struct prometheus_output_options {
6 - char *name;
7 - PROMETHEUS_OUTPUT_OPTIONS flag;
8 -} prometheus_output_flags_root[] = {
9 - { "names", PROMETHEUS_OUTPUT_NAMES },
10 - { "timestamps", PROMETHEUS_OUTPUT_TIMESTAMPS },
11 - { "variables", PROMETHEUS_OUTPUT_VARIABLES },
12 - { "oldunits", PROMETHEUS_OUTPUT_OLDUNITS },
13 - { "hideunits", PROMETHEUS_OUTPUT_HIDEUNITS },
14 - // terminator
15 - { NULL, PROMETHEUS_OUTPUT_NONE },
16 -};
17 -
18 -inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) {
19 - int format = ALLMETRICS_SHELL;
20 - const char *filter = NULL;
21 - const char *prometheus_server = w->client_ip;
22 -
23 - uint32_t prometheus_exporting_options;
24 - if (prometheus_exporter_instance)
25 - prometheus_exporting_options = prometheus_exporter_instance->config.options;
26 - else
27 - prometheus_exporting_options = global_exporting_options;
28 -
29 - PROMETHEUS_OUTPUT_OPTIONS prometheus_output_options =
30 - PROMETHEUS_OUTPUT_TIMESTAMPS |
31 - ((prometheus_exporting_options & EXPORTING_OPTION_SEND_NAMES) ? PROMETHEUS_OUTPUT_NAMES : 0);
32 -
33 - const char *prometheus_prefix;
34 - if (prometheus_exporter_instance)
35 - prometheus_prefix = prometheus_exporter_instance->config.prefix;
36 - else
37 - prometheus_prefix = global_exporting_prefix;
38 -
39 - while(url) {
40 - char *value = strsep_skip_consecutive_separators(&url, "&");
41 - if (!value || !*value) continue;
42 -
43 - char *name = strsep_skip_consecutive_separators(&value, "=");
44 - if(!name || !*name) continue;
45 - if(!value || !*value) continue;
46 -
47 - if(!strcmp(name, "format")) {
48 - if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
49 - format = ALLMETRICS_SHELL;
50 - else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS))
51 - format = ALLMETRICS_PROMETHEUS;
52 - else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS))
53 - format = ALLMETRICS_PROMETHEUS_ALL_HOSTS;
54 - else if(!strcmp(value, ALLMETRICS_FORMAT_JSON))
55 - format = ALLMETRICS_JSON;
56 - else
57 - format = 0;
58 - }
59 - else if(!strcmp(name, "filter")) {
60 - filter = value;
61 - }
62 - else if(!strcmp(name, "server")) {
63 - prometheus_server = value;
64 - }
65 - else if(!strcmp(name, "prefix")) {
66 - prometheus_prefix = value;
67 - }
68 - else if(!strcmp(name, "data") || !strcmp(name, "source") || !strcmp(name, "data source") || !strcmp(name, "data-source") || !strcmp(name, "data_source") || !strcmp(name, "datasource")) {
69 - prometheus_exporting_options = exporting_parse_data_source(value, prometheus_exporting_options);
70 - }
71 - else {
72 - int i;
73 - for(i = 0; prometheus_output_flags_root[i].name ; i++) {
74 - if(!strcmp(name, prometheus_output_flags_root[i].name)) {
75 - if(!strcmp(value, "yes") || !strcmp(value, "1") || !strcmp(value, "true"))
76 - prometheus_output_options |= prometheus_output_flags_root[i].flag;
77 - else {
78 - prometheus_output_options &= ~prometheus_output_flags_root[i].flag;
79 - }
80 -
81 - break;
82 - }
83 - }
84 - }
85 - }
86 -
87 - buffer_flush(w->response.data);
88 - buffer_no_cacheable(w->response.data);
89 -
90 - switch(format) {
91 - case ALLMETRICS_JSON:
92 - w->response.data->content_type = CT_APPLICATION_JSON;
93 - rrd_stats_api_v1_charts_allmetrics_json(host, filter, w->response.data);
94 - return HTTP_RESP_OK;
95 -
96 - case ALLMETRICS_SHELL:
97 - w->response.data->content_type = CT_TEXT_PLAIN;
98 - rrd_stats_api_v1_charts_allmetrics_shell(host, filter, w->response.data);
99 - return HTTP_RESP_OK;
100 -
101 - case ALLMETRICS_PROMETHEUS:
102 - w->response.data->content_type = CT_PROMETHEUS;
103 - rrd_stats_api_v1_charts_allmetrics_prometheus_single_host(
104 - host
105 - , filter
106 - , w->response.data
107 - , prometheus_server
108 - , prometheus_prefix
109 - , prometheus_exporting_options
110 - , prometheus_output_options
111 - );
112 - return HTTP_RESP_OK;
113 -
114 - case ALLMETRICS_PROMETHEUS_ALL_HOSTS:
115 - w->response.data->content_type = CT_PROMETHEUS;
116 - rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(
117 - host
118 - , filter
119 - , w->response.data
120 - , prometheus_server
121 - , prometheus_prefix
122 - , prometheus_exporting_options
123 - , prometheus_output_options
124 - );
125 - return HTTP_RESP_OK;
126 -
127 - default:
128 - w->response.data->content_type = CT_TEXT_PLAIN;
129 - buffer_strcat(w->response.data, "Which format? '" ALLMETRICS_FORMAT_SHELL "', '" ALLMETRICS_FORMAT_PROMETHEUS "', '" ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "' and '" ALLMETRICS_FORMAT_JSON "' are currently supported.");
130 - return HTTP_RESP_BAD_REQUEST;
131 - }
132 -}
src/web/api/exporters/allmetrics.h deleted
-12
@@ -1,12 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#ifndef NETDATA_API_ALLMETRICS_H
4 -#define NETDATA_API_ALLMETRICS_H
5 -
6 -#include "web/api/formatters/rrd2json.h"
7 -#include "shell/allmetrics_shell.h"
8 -#include "web/server/web_client.h"
9 -
10 -int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url);
11 -
12 -#endif //NETDATA_API_ALLMETRICS_H
src/web/api/exporters/shell/README.md
+3 -3
@@ -12,14 +12,14 @@ learn_rel_path: "Developers/Web/Api/Exporters"
12 Shell scripts can now query Netdata:
13
14 ```sh
15 -eval "$(curl -s 'http://localhost:19999/api/v1/allmetrics')"
15 +eval "$(curl -s 'http://localhost:19999/api/v3/allmetrics')"
16 ```
17
18 after this command, all the Netdata metrics are exposed to shell. Check:
19
20 ```sh
21 # source the metrics
22 -eval "$(curl -s 'http://localhost:19999/api/v1/allmetrics')"
22 +eval "$(curl -s 'http://localhost:19999/api/v3/allmetrics')"
23
24 # let's see if there are variables exposed by Netdata for system.cpu
25 set | grep "^NETDATA_SYSTEM_CPU"
@@ -50,7 +50,7 @@ echo ${NETDATA_ALARM_SYSTEM_SWAP_USED_SWAP_STATUS}
50 CLEAR
51
52 # is it fast?
53 -time curl -s 'http://localhost:19999/api/v1/allmetrics' >/dev/null
53 +time curl -s 'http://localhost:19999/api/v3/allmetrics' >/dev/null
54
55 real 0m0,070s
56 user 0m0,000s
src/web/api/exporters/shell/allmetrics_shell.h deleted
-21
@@ -1,21 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#ifndef NETDATA_API_ALLMETRICS_SHELL_H
4 -#define NETDATA_API_ALLMETRICS_SHELL_H
5 -
6 -#include "../allmetrics.h"
7 -
8 -#define ALLMETRICS_FORMAT_SHELL "shell"
9 -#define ALLMETRICS_FORMAT_PROMETHEUS "prometheus"
10 -#define ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "prometheus_all_hosts"
11 -#define ALLMETRICS_FORMAT_JSON "json"
12 -
13 -#define ALLMETRICS_SHELL 1
14 -#define ALLMETRICS_PROMETHEUS 2
15 -#define ALLMETRICS_JSON 3
16 -#define ALLMETRICS_PROMETHEUS_ALL_HOSTS 4
17 -
18 -void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_string, BUFFER *wb);
19 -void rrd_stats_api_v1_charts_allmetrics_shell(RRDHOST *host, const char *filter_string, BUFFER *wb);
20 -
21 -#endif //NETDATA_API_ALLMETRICS_SHELL_H
src/web/api/formatters/rrd2json.c
-40
@@ -10,46 +10,6 @@ void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb)
10 buffer_json_finalize(wb);
11 }
12
13 -const char *rrdr_format_to_string(DATASOURCE_FORMAT format) {
14 - switch(format) {
15 - case DATASOURCE_JSON:
16 - return DATASOURCE_FORMAT_JSON;
17 -
18 - case DATASOURCE_JSON2:
19 - return DATASOURCE_FORMAT_JSON2;
20 -
21 - case DATASOURCE_DATATABLE_JSON:
22 - return DATASOURCE_FORMAT_DATATABLE_JSON;
23 -
24 - case DATASOURCE_DATATABLE_JSONP:
25 - return DATASOURCE_FORMAT_DATATABLE_JSONP;
26 -
27 - case DATASOURCE_JSONP:
28 - return DATASOURCE_FORMAT_JSONP;
29 -
30 - case DATASOURCE_SSV:
31 - return DATASOURCE_FORMAT_SSV;
32 -
33 - case DATASOURCE_CSV:
34 - return DATASOURCE_FORMAT_CSV;
35 -
36 - case DATASOURCE_TSV:
37 - return DATASOURCE_FORMAT_TSV;
38 -
39 - case DATASOURCE_HTML:
40 - return DATASOURCE_FORMAT_HTML;
41 -
42 - case DATASOURCE_JS_ARRAY:
43 - return DATASOURCE_FORMAT_JS_ARRAY;
44 -
45 - case DATASOURCE_SSV_COMMA:
46 - return DATASOURCE_FORMAT_SSV_COMMA;
47 -
48 - default:
49 - return "unknown";
50 - }
51 -}
52 -
13 int rrdset2value_api_v1(
14 RRDSET *st
15 , BUFFER *wb
src/web/api/formatters/rrd2json.h
+1 -34
@@ -3,26 +3,8 @@
3 #ifndef NETDATA_RRD2JSON_H
4 #define NETDATA_RRD2JSON_H 1
5
6 -// type of JSON generations
7 -typedef enum {
8 - DATASOURCE_JSON = 0,
9 - DATASOURCE_DATATABLE_JSON = 1,
10 - DATASOURCE_DATATABLE_JSONP = 2,
11 - DATASOURCE_SSV = 3,
12 - DATASOURCE_CSV = 4,
13 - DATASOURCE_JSONP = 5,
14 - DATASOURCE_TSV = 6,
15 - DATASOURCE_HTML = 7,
16 - DATASOURCE_JS_ARRAY = 8,
17 - DATASOURCE_SSV_COMMA = 9,
18 - DATASOURCE_CSV_JSON_ARRAY = 10,
19 - DATASOURCE_CSV_MARKDOWN = 11,
20 - DATASOURCE_JSON2 = 12,
21 -} DATASOURCE_FORMAT;
6 +#include "web/api/web_api.h"
7
23 -#include "web/api/web_api_v1.h"
24 -
25 -#include "web/api/exporters/allmetrics.h"
8 #include "web/api/queries/rrdr.h"
9
10 #include "web/api/formatters/csv/csv.h"
@@ -38,22 +20,7 @@ typedef enum {
20
21 #define HOSTNAME_MAX 1024
22
41 -#define DATASOURCE_FORMAT_JSON "json"
42 -#define DATASOURCE_FORMAT_JSON2 "json2"
43 -#define DATASOURCE_FORMAT_DATATABLE_JSON "datatable"
44 -#define DATASOURCE_FORMAT_DATATABLE_JSONP "datasource"
45 -#define DATASOURCE_FORMAT_JSONP "jsonp"
46 -#define DATASOURCE_FORMAT_SSV "ssv"
47 -#define DATASOURCE_FORMAT_CSV "csv"
48 -#define DATASOURCE_FORMAT_TSV "tsv"
49 -#define DATASOURCE_FORMAT_HTML "html"
50 -#define DATASOURCE_FORMAT_JS_ARRAY "array"
51 -#define DATASOURCE_FORMAT_SSV_COMMA "ssvcomma"
52 -#define DATASOURCE_FORMAT_CSV_JSON_ARRAY "csvjsonarray"
53 -#define DATASOURCE_FORMAT_CSV_MARKDOWN "markdown"
54 -
23 void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb);
56 -const char *rrdr_format_to_string(DATASOURCE_FORMAT format);
24
25 int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, struct query_target *qt, time_t *latest_timestamp);
26
src/web/api/functions/function-bearer_get_token.c new
+81
@@ -0,0 +1,81 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "function-bearer_get_token.h"
4 +#include "../v2/api_v2_calls.h"
5 +
6 +struct bearer_token_request {
7 + nd_uuid_t claim_id;
8 + nd_uuid_t machine_guid;
9 + nd_uuid_t node_id;
10 + HTTP_USER_ROLE user_role;
11 + HTTP_ACCESS access;
12 + nd_uuid_t cloud_account_id;
13 + STRING *client_name;
14 +};
15 +
16 +static bool parse_json_payload(json_object *jobj, const char *path, void *data, BUFFER *error) {
17 + struct bearer_token_request *rq = data;
18 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "claim_id", rq->claim_id, error, true);
19 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "machine_guid", rq->machine_guid, error, true);
20 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "node_id", rq->node_id, error, true);
21 + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "user_role", http_user_role2id, rq->user_role, error, true);
22 + JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "access", http_access2id_one, rq->access, error, true);
23 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "cloud_account_id", rq->cloud_account_id, error, true);
24 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "client_name", rq->client_name, error, true);
25 + return true;
26 +}
27 +
28 +int function_bearer_get_token(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload, const char *source) {
29 + if(!request_source_is_cloud(source))
30 + return rrd_call_function_error(
31 + wb, "You cannot access this function from outside Netdata Cloud", HTTP_RESP_BAD_REQUEST);
32 +
33 + int code;
34 + struct bearer_token_request rq = { 0 };
35 + CLEAN_JSON_OBJECT *jobj = json_parse_function_payload_or_error(wb, payload, &code, parse_json_payload, &rq);
36 + if(!jobj || code != HTTP_RESP_OK) {
37 + string_freez(rq.client_name);
38 + return code;
39 + }
40 +
41 + char claim_id[UUID_STR_LEN];
42 + uuid_unparse_lower(rq.claim_id, claim_id);
43 +
44 + char machine_guid[UUID_STR_LEN];
45 + uuid_unparse_lower(rq.machine_guid, machine_guid);
46 +
47 + char node_id[UUID_STR_LEN];
48 + uuid_unparse_lower(rq.node_id, node_id);
49 +
50 + int rc = bearer_get_token_json_response(wb, localhost, claim_id, machine_guid, node_id,
51 + rq.user_role, rq.access, rq.cloud_account_id,
52 + string2str(rq.client_name));
53 +
54 + string_freez(rq.client_name);
55 + return rc;
56 +}
57 +
58 +int call_function_bearer_get_token(RRDHOST *host, struct web_client *w, const char *claim_id, const char *machine_guid, const char *node_id) {
59 + CLEAN_BUFFER *payload = buffer_create(0, NULL);
60 + buffer_json_initialize(payload, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
61 + buffer_json_member_add_string(payload, "claim_id", claim_id);
62 + buffer_json_member_add_string(payload, "machine_guid", machine_guid);
63 + buffer_json_member_add_string(payload, "node_id", node_id);
64 + buffer_json_member_add_string(payload, "user_role", http_id2user_role(w->user_role));
65 + http_access2buffer_json_array(payload, "access", w->access);
66 + buffer_json_member_add_uuid(payload, "cloud_account_id", w->auth.cloud_account_id);
67 + buffer_json_member_add_string(payload, "client_name", w->auth.client_name);
68 + buffer_json_finalize(payload);
69 +
70 + CLEAN_BUFFER *source = buffer_create(0, NULL);
71 + web_client_api_request_vX_source_to_buffer(w, source);
72 +
73 + char transaction_str[UUID_COMPACT_STR_LEN];
74 + uuid_unparse_lower_compact(w->transaction, transaction_str);
75 + return rrd_function_run(host, w->response.data, 10,
76 + w->access, RRDFUNCTIONS_BEARER_GET_TOKEN, true,
77 + transaction_str, NULL, NULL,
78 + NULL, NULL,
79 + NULL, NULL,
80 + payload, buffer_tostring(source), true);
81 +}
src/web/api/functions/function-bearer_get_token.h new
+14
@@ -0,0 +1,14 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_FUNCTION_BEARER_GET_TOKEN_H
4 +#define NETDATA_FUNCTION_BEARER_GET_TOKEN_H
5 +
6 +#include "daemon/common.h"
7 +
8 +int function_bearer_get_token(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
9 +int call_function_bearer_get_token(RRDHOST *host, struct web_client *w, const char *claim_id, const char *machine_guid, const char *node_id);
10 +
11 +#define RRDFUNCTIONS_BEARER_GET_TOKEN "bearer_get_token"
12 +#define RRDFUNCTIONS_BEARER_GET_TOKEN_HELP "Get a bearer token for authenticated direct access to the agent"
13 +
14 +#endif //NETDATA_FUNCTION_BEARER_GET_TOKEN_H
src/web/api/functions/function-progress.c new
+8
@@ -0,0 +1,8 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "function-progress.h"
4 +
5 +int function_progress(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
6 + return progress_function_result(wb, rrdhost_hostname(localhost));
7 +}
8 +
src/web/api/functions/function-progress.h new
+10
@@ -0,0 +1,10 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_FUNCTION_PROGRESS_H
4 +#define NETDATA_FUNCTION_PROGRESS_H
5 +
6 +#include "daemon/common.h"
7 +
8 +int function_progress(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
9 +
10 +#endif //NETDATA_FUNCTION_PROGRESS_H
src/web/api/functions/function-streaming.c renamed
+2 -2
@@ -1,8 +1,8 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#include "rrdfunctions-streaming.h"
3 +#include "function-streaming.h"
4
5 -int rrdhost_function_streaming(BUFFER *wb, const char *function __maybe_unused) {
5 +int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
6
7 time_t now = now_realtime_sec();
8
src/web/api/functions/function-streaming.h new
+12
@@ -0,0 +1,12 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_FUNCTION_STREAMING_H
4 +#define NETDATA_FUNCTION_STREAMING_H
5 +
6 +#include "daemon/common.h"
7 +
8 +#define RRDFUNCTIONS_STREAMING_HELP "Streaming status for parents and children."
9 +
10 +int function_streaming(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
11 +
12 +#endif //NETDATA_FUNCTION_STREAMING_H
src/web/api/functions/functions.c new
+40
@@ -0,0 +1,40 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "functions.h"
4 +
5 +void global_functions_add(void) {
6 + // we register this only on localhost
7 + // for the other nodes, the origin server should register it
8 + rrd_function_add_inline(
9 + localhost,
10 + NULL,
11 + "streaming",
12 + 10,
13 + RRDFUNCTIONS_PRIORITY_DEFAULT + 1,
14 + RRDFUNCTIONS_STREAMING_HELP,
15 + "top",
16 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
17 + function_streaming);
18 +
19 + rrd_function_add_inline(
20 + localhost,
21 + NULL,
22 + "netdata-api-calls",
23 + 10,
24 + RRDFUNCTIONS_PRIORITY_DEFAULT + 2,
25 + RRDFUNCTIONS_PROGRESS_HELP,
26 + "top",
27 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
28 + function_progress);
29 +
30 + rrd_function_add_inline(
31 + localhost,
32 + NULL,
33 + RRDFUNCTIONS_BEARER_GET_TOKEN,
34 + 10,
35 + RRDFUNCTIONS_PRIORITY_DEFAULT + 3,
36 + RRDFUNCTIONS_BEARER_GET_TOKEN_HELP,
37 + RRDFUNCTIONS_TAG_HIDDEN,
38 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
39 + function_bearer_get_token);
40 +}
src/web/api/functions/functions.h new
+14
@@ -0,0 +1,14 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_FUNCTIONS_H
4 +#define NETDATA_FUNCTIONS_H
5 +
6 +#include "daemon/common.h"
7 +
8 +#include "function-streaming.h"
9 +#include "function-progress.h"
10 +#include "function-bearer_get_token.h"
11 +
12 +void global_functions_add(void);
13 +
14 +#endif //NETDATA_FUNCTIONS_H
src/web/api/http_auth.c
+298 -40
@@ -2,83 +2,341 @@
2
3 #include "http_auth.h"
4
5 -#define BEARER_TOKEN_EXPIRATION 86400
5 +#define BEARER_TOKEN_EXPIRATION (86400 * 1)
6
7 -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
7 +bool netdata_is_protected_by_bearer = false;
8 static DICTIONARY *netdata_authorized_bearers = NULL;
9
10 struct bearer_token {
11 nd_uuid_t cloud_account_id;
12 - char cloud_user_name[CLOUD_USER_NAME_LENGTH];
12 + char client_name[CLOUD_CLIENT_NAME_LENGTH];
13 HTTP_ACCESS access;
14 HTTP_USER_ROLE user_role;
15 time_t created_s;
16 time_t expires_s;
17 };
18
19 -bool web_client_bearer_token_auth(struct web_client *w, const char *v) {
20 - if(!uuid_parse_flexi(v, w->auth.bearer_token)) {
21 - char uuid_str[UUID_COMPACT_STR_LEN];
22 - uuid_unparse_lower_compact(w->auth.bearer_token, uuid_str);
19 +static void bearer_tokens_path(char out[FILENAME_MAX]) {
20 + filename_from_path_entry(out, netdata_configured_varlib_dir, "bearer_tokens", NULL);
21 +}
22
24 - struct bearer_token *z = dictionary_get(netdata_authorized_bearers, uuid_str);
25 - if (z && z->expires_s > now_monotonic_sec()) {
26 - strncpyz(w->auth.client_name, z->cloud_user_name, sizeof(w->auth.client_name) - 1);
27 - uuid_copy(w->auth.cloud_account_id, z->cloud_account_id);
28 - web_client_set_permissions(w, z->access, z->user_role, WEB_CLIENT_FLAG_AUTH_BEARER);
29 - return true;
30 - }
31 - }
32 - else
33 - nd_log(NDLS_DAEMON, NDLP_NOTICE, "Invalid bearer token '%s' received.", v);
23 +static void bearer_token_filename(char out[FILENAME_MAX], nd_uuid_t uuid) {
24 + char uuid_str[UUID_STR_LEN];
25 + uuid_unparse_lower(uuid, uuid_str);
26 +
27 + char path[FILENAME_MAX];
28 + bearer_tokens_path(path);
29 + filename_from_path_entry(out, path, uuid_str, NULL);
30 +}
31
35 - return false;
32 +static inline bool bearer_tokens_ensure_path_exists(void) {
33 + char path[FILENAME_MAX];
34 + bearer_tokens_path(path);
35 + return filename_is_dir(path, true);
36 }
37
38 -static void bearer_token_cleanup(void) {
38 +static void bearer_token_delete_from_disk(nd_uuid_t *token) {
39 + char filename[FILENAME_MAX];
40 + bearer_token_filename(filename, *token);
41 + if(unlink(filename) != 0)
42 + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to unlink() file '%s'", filename);
43 +}
44 +
45 +static void bearer_token_cleanup(bool force) {
46 static time_t attempts = 0;
47
41 - if(++attempts % 1000 != 0)
48 + if(++attempts % 1000 != 0 && !force)
49 return;
50
44 - time_t now_s = now_monotonic_sec();
51 + time_t now_s = now_realtime_sec();
52
53 struct bearer_token *z;
54 dfe_start_read(netdata_authorized_bearers, z) {
48 - if(z->expires_s < now_s)
55 + if(z->expires_s < now_s) {
56 + nd_uuid_t uuid;
57 + if(uuid_parse_flexi(z_dfe.name, uuid) == 0)
58 + bearer_token_delete_from_disk(&uuid);
59 +
60 dictionary_del(netdata_authorized_bearers, z_dfe.name);
61 + }
62 }
63 dfe_done(z);
64
65 dictionary_garbage_collect(netdata_authorized_bearers);
66 }
67
56 -void bearer_tokens_init(void) {
57 - netdata_authorized_bearers = dictionary_create_advanced(
58 - DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
59 - NULL, sizeof(struct bearer_token));
68 +static uint64_t bearer_token_signature(nd_uuid_t token, struct bearer_token *bt) {
69 + // we use a custom structure to make sure that changes in the other code will not affect the signature
70 +
71 + struct {
72 + nd_uuid_t host_uuid;
73 + nd_uuid_t token;
74 + nd_uuid_t cloud_account_id;
75 + char client_name[CLOUD_CLIENT_NAME_LENGTH];
76 + HTTP_ACCESS access;
77 + HTTP_USER_ROLE user_role;
78 + time_t created_s;
79 + time_t expires_s;
80 + } signature_payload = {
81 + .access = bt->access,
82 + .user_role = bt->user_role,
83 + .created_s = bt->created_s,
84 + .expires_s = bt->expires_s,
85 + };
86 + uuid_copy(signature_payload.host_uuid, localhost->host_uuid);
87 + uuid_copy(signature_payload.token, token);
88 + uuid_copy(signature_payload.cloud_account_id, bt->cloud_account_id);
89 + memset(signature_payload.client_name, 0, sizeof(signature_payload.client_name));
90 + strncpyz(signature_payload.client_name, bt->client_name, sizeof(signature_payload.client_name) - 1);
91 +
92 + return XXH3_64bits(&signature_payload, sizeof(signature_payload));
93 }
94
62 -time_t bearer_create_token(nd_uuid_t *uuid, struct web_client *w) {
95 +static bool bearer_token_save_to_file(nd_uuid_t token, struct bearer_token *bt) {
96 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
97 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
98 + buffer_json_member_add_uint64(wb, "version", 1);
99 + buffer_json_member_add_uuid(wb, "host_uuid", localhost->host_uuid);
100 + buffer_json_member_add_uuid(wb, "token", token);
101 + buffer_json_member_add_uuid(wb, "cloud_account_id", bt->cloud_account_id);
102 + buffer_json_member_add_string(wb, "client_name", bt->client_name);
103 + http_access2buffer_json_array(wb, "access", bt->access);
104 + buffer_json_member_add_string(wb, "user_role", http_id2user_role(bt->user_role));
105 + buffer_json_member_add_uint64(wb, "created_s", bt->created_s);
106 + buffer_json_member_add_uint64(wb, "expires_s", bt->expires_s);
107 + buffer_json_member_add_uint64(wb, "signature", bearer_token_signature(token, bt));
108 + buffer_json_finalize(wb);
109 +
110 + char filename[FILENAME_MAX];
111 + bearer_token_filename(filename, token);
112 +
113 + FILE *fp = fopen(filename, "w");
114 + if(!fp) {
115 + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot create file '%s'", filename);
116 + return false;
117 + }
118 +
119 + if(fwrite(buffer_tostring(wb), 1, buffer_strlen(wb), fp) != buffer_strlen(wb)) {
120 + fclose(fp);
121 + unlink(filename);
122 + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot save file '%s'", filename);
123 + return false;
124 + }
125 +
126 + fclose(fp);
127 + return true;
128 +}
129 +
130 +static time_t bearer_create_token_internal(nd_uuid_t token, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name, time_t created_s, time_t expires_s, bool save) {
131 char uuid_str[UUID_COMPACT_STR_LEN];
132 + uuid_unparse_lower_compact(token, uuid_str);
133 +
134 + struct bearer_token t = { 0 }, *bt;
135 + const DICTIONARY_ITEM *item = dictionary_set_and_acquire_item(netdata_authorized_bearers, uuid_str, &t, sizeof(t));
136 + bt = dictionary_acquired_item_value(item);
137 +
138 + if(!bt->created_s) {
139 + bt->created_s = created_s;
140 + bt->expires_s = expires_s;
141 + bt->user_role = user_role;
142 + bt->access = access;
143 +
144 + uuid_copy(bt->cloud_account_id, cloud_account_id);
145 + strncpyz(bt->client_name, client_name, sizeof(bt->cloud_account_id) - 1);
146 +
147 + if(save)
148 + bearer_token_save_to_file(token, bt);
149 + }
150 +
151 + time_t expiration = bt->expires_s;
152 +
153 + dictionary_acquired_item_release(netdata_authorized_bearers, item);
154 +
155 + return expiration;
156 +}
157 +
158 +time_t bearer_create_token(nd_uuid_t *uuid, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name) {
159 + time_t now_s = now_realtime_sec();
160 + time_t expires_s = 0;
161 +
162 + struct bearer_token *bt;
163 + dfe_start_read(netdata_authorized_bearers, bt) {
164 + if(bt->expires_s > now_s + 3600 * 2 && // expires in more than 2 hours
165 + user_role == bt->user_role && // the user_role matches
166 + access == bt->access && // the access matches
167 + uuid_eq(cloud_account_id, bt->cloud_account_id) && // the cloud_account_id matches
168 + strncmp(client_name, bt->client_name, sizeof(bt->client_name) - 1) == 0 && // the client_name matches
169 + uuid_parse_flexi(bt_dfe.name, *uuid) == 0) // the token can be parsed
170 + return expires_s; /* dfe will cleanup automatically */
171 + }
172 + dfe_done(bt);
173
174 uuid_generate_random(*uuid);
66 - uuid_unparse_lower_compact(*uuid, uuid_str);
67 -
68 - struct bearer_token t = { 0 }, *z;
69 - z = dictionary_set(netdata_authorized_bearers, uuid_str, &t, sizeof(t));
70 - if(!z->created_s) {
71 - z->created_s = now_monotonic_sec();
72 - z->expires_s = z->created_s + BEARER_TOKEN_EXPIRATION;
73 - z->user_role = w->user_role;
74 - z->access = w->access;
75 - uuid_copy(z->cloud_account_id, w->auth.cloud_account_id);
76 - strncpyz(z->cloud_user_name, w->auth.client_name, sizeof(z->cloud_account_id) - 1);
175 + expires_s = bearer_create_token_internal(
176 + *uuid, user_role, access, cloud_account_id, client_name,
177 + now_s, now_s + BEARER_TOKEN_EXPIRATION, true);
178 +
179 + bearer_token_cleanup(false);
180 +
181 + return expires_s;
182 +}
183 +
184 +static bool bearer_token_parse_json(nd_uuid_t token, struct json_object *jobj, BUFFER *error) {
185 + int64_t version;
186 + nd_uuid_t token_in_file, cloud_account_id, host_uuid;
187 + CLEAN_STRING *client_name = NULL;
188 + HTTP_USER_ROLE user_role = HTTP_USER_ROLE_NONE;
189 + HTTP_ACCESS access = HTTP_ACCESS_NONE;
190 + time_t created_s = 0, expires_s = 0;
191 + uint64_t signature = 0;
192 +
193 + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, ".", "version", version, error, true);
194 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, ".", "host_uuid", host_uuid, error, true);
195 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, ".", "token", token_in_file, error, true);
196 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, ".", "cloud_account_id", cloud_account_id, error, true);
197 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, ".", "client_name", client_name, error, true);
198 + JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, ".", "access", http_access2id_one, access, error, true);
199 + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, ".", "user_role", http_user_role2id, user_role, error, true);
200 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, ".", "created_s", created_s, error, true);
201 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, ".", "expires_s", expires_s, error, true);
202 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, ".", "signature", signature, error, true);
203 +
204 + if(uuid_compare(token, token_in_file) != 0) {
205 + buffer_flush(error);
206 + buffer_strcat(error, "token in JSON file does not match the filename");
207 + return false;
208 + }
209 +
210 + if(uuid_compare(host_uuid, localhost->host_uuid) != 0) {
211 + buffer_flush(error);
212 + buffer_strcat(error, "Host UUID in JSON file does not match our host UUID");
213 + return false;
214 + }
215 +
216 + if(!created_s || !expires_s || created_s >= expires_s) {
217 + buffer_flush(error);
218 + buffer_strcat(error, "bearer token has invalid dates");
219 + return false;
220 + }
221 +
222 + struct bearer_token bt = {
223 + .access = access,
224 + .user_role = user_role,
225 + .created_s = created_s,
226 + .expires_s = expires_s,
227 + };
228 + uuid_copy(bt.cloud_account_id, cloud_account_id);
229 + strncpyz(bt.client_name, string2str(client_name), sizeof(bt.client_name) - 1);
230 +
231 + if(signature != bearer_token_signature(token_in_file, &bt)) {
232 + buffer_flush(error);
233 + buffer_strcat(error, "bearer token has invalid signature");
234 + return false;
235 + }
236 +
237 + bearer_create_token_internal(token, user_role, access,
238 + cloud_account_id, string2str(client_name),
239 + created_s, expires_s, false);
240 +
241 + return true;
242 +}
243 +
244 +static bool bearer_token_load_token(nd_uuid_t token) {
245 + char filename[FILENAME_MAX];
246 + bearer_token_filename(filename, token);
247 +
248 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
249 + if(!read_txt_file_to_buffer(filename, wb, 1 * 1024 * 1024))
250 + return false;
251 +
252 + CLEAN_JSON_OBJECT *jobj = json_tokener_parse(buffer_tostring(wb));
253 + if (jobj == NULL) {
254 + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot parse bearer token file '%s'", filename);
255 + return false;
256 + }
257 +
258 + CLEAN_BUFFER *error = buffer_create(0, NULL);
259 + bool rc = bearer_token_parse_json(token, jobj, error);
260 + if(!rc) {
261 + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to parse bearer token file '%s': %s", filename, buffer_tostring(error));
262 + unlink(filename);
263 + return false;
264 + }
265 +
266 + bearer_token_cleanup(true);
267 +
268 + return true;
269 +}
270 +
271 +static void bearer_tokens_load_from_disk(void) {
272 + bearer_tokens_ensure_path_exists();
273 +
274 + char path[FILENAME_MAX];
275 + bearer_tokens_path(path);
276 +
277 + DIR *dir = opendir(path);
278 + if(!dir) {
279 + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot open directory '%s' to read saved bearer tokens", path);
280 + return;
281 }
282
79 - bearer_token_cleanup();
283 + struct dirent *de;
284 + while((de = readdir(dir))) {
285 + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
286 + continue;
287 +
288 + ND_UUID uuid = UUID_ZERO;
289 + if(uuid_parse_flexi(de->d_name, uuid.uuid) != 0 || UUIDiszero(uuid))
290 + continue;
291 +
292 + char filename[FILENAME_MAX];
293 + filename_from_path_entry(filename, path, de->d_name, NULL);
294 +
295 + if(de->d_type == DT_REG || (de->d_type == DT_LNK && filename_is_file(filename)))
296 + bearer_token_load_token(uuid.uuid);
297 + }
298 +
299 + closedir(dir);
300 +}
301 +
302 +bool web_client_bearer_token_auth(struct web_client *w, const char *v) {
303 + bool rc = false;
304 +
305 + if(!uuid_parse_flexi(v, w->auth.bearer_token)) {
306 + char uuid_str[UUID_COMPACT_STR_LEN];
307 + uuid_unparse_lower_compact(w->auth.bearer_token, uuid_str);
308 +
309 + const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(netdata_authorized_bearers, uuid_str);
310 + if(!item && bearer_token_load_token(w->auth.bearer_token))
311 + item = dictionary_get_and_acquire_item(netdata_authorized_bearers, uuid_str);
312 +
313 + if(item) {
314 + struct bearer_token *bt = dictionary_acquired_item_value(item);
315 + if (bt->expires_s > now_realtime_sec()) {
316 + strncpyz(w->auth.client_name, bt->client_name, sizeof(w->auth.client_name) - 1);
317 + uuid_copy(w->auth.cloud_account_id, bt->cloud_account_id);
318 + web_client_set_permissions(w, bt->access, bt->user_role, WEB_CLIENT_FLAG_AUTH_BEARER);
319 + rc = true;
320 + }
321 +
322 + dictionary_acquired_item_release(netdata_authorized_bearers, item);
323 + }
324 + }
325 + else
326 + nd_log(NDLS_DAEMON, NDLP_NOTICE, "Invalid bearer token '%s' received.", v);
327 +
328 + return rc;
329 +}
330 +
331 +void bearer_tokens_init(void) {
332 + netdata_is_protected_by_bearer =
333 + config_get_boolean(CONFIG_SECTION_WEB, "bearer token protection", netdata_is_protected_by_bearer);
334 +
335 + netdata_authorized_bearers = dictionary_create_advanced(
336 + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
337 + NULL, sizeof(struct bearer_token));
338
81 - return now_realtime_sec() + BEARER_TOKEN_EXPIRATION;
339 + bearer_tokens_load_from_disk();
340 }
341
342 bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t dst_len) {
src/web/api/http_auth.h
+1 -1
@@ -11,7 +11,7 @@ extern bool netdata_is_protected_by_bearer;
11
12 bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t dst_len);
13
14 -time_t bearer_create_token(nd_uuid_t *uuid, struct web_client *w);
14 +time_t bearer_create_token(nd_uuid_t *uuid, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name);
15 bool web_client_bearer_token_auth(struct web_client *w, const char *v);
16
17 static inline bool http_access_user_has_enough_access_level_for_endpoint(HTTP_ACCESS user, HTTP_ACCESS endpoint) {
src/web/api/ilove/ilove.h deleted
-13
@@ -1,13 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#ifndef NETDATA_WEB_API_ILOVE_H
4 -#define NETDATA_WEB_API_ILOVE_H 1
5 -
6 -#include "libnetdata/libnetdata.h"
7 -#include "web/server/web_client.h"
8 -
9 -int web_client_api_request_v2_ilove(RRDHOST *host, struct web_client *w, char *url);
10 -
11 -#include "web/api/web_api_v1.h"
12 -
13 -#endif /* NETDATA_WEB_API_ILOVE_H */
src/web/api/maps/contexts_alert_statuses.c new
+60
@@ -0,0 +1,60 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "contexts_alert_statuses.h"
4 +
5 +static struct {
6 + const char *name;
7 + uint32_t hash;
8 + CONTEXTS_ALERT_STATUS value;
9 +} contexts_alert_status[] = {
10 + {"uninitialized" , 0 , CONTEXT_ALERT_UNINITIALIZED}
11 + , {"undefined" , 0 , CONTEXT_ALERT_UNDEFINED}
12 + , {"clear" , 0 , CONTEXT_ALERT_CLEAR}
13 + , {"raised" , 0 , CONTEXT_ALERT_RAISED}
14 + , {"active" , 0 , CONTEXT_ALERT_RAISED}
15 + , {"warning" , 0 , CONTEXT_ALERT_WARNING}
16 + , {"critical" , 0 , CONTEXT_ALERT_CRITICAL}
17 + , {NULL , 0 , 0}
18 +};
19 +
20 +CONTEXTS_ALERT_STATUS contexts_alert_status_str_to_id(char *o) {
21 + CONTEXTS_ALERT_STATUS ret = 0;
22 + char *tok;
23 +
24 + while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
25 + if(!*tok) continue;
26 +
27 + uint32_t hash = simple_hash(tok);
28 + int i;
29 + for(i = 0; contexts_alert_status[i].name ; i++) {
30 + if (unlikely(hash == contexts_alert_status[i].hash && !strcmp(tok, contexts_alert_status[i].name))) {
31 + ret |= contexts_alert_status[i].value;
32 + break;
33 + }
34 + }
35 + }
36 +
37 + return ret;
38 +}
39 +
40 +void contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key,
41 + CONTEXTS_ALERT_STATUS options) {
42 + buffer_json_member_add_array(wb, key);
43 +
44 + CONTEXTS_ALERT_STATUS used = 0; // to prevent adding duplicates
45 + for(int i = 0; contexts_alert_status[i].name ; i++) {
46 + if (unlikely((contexts_alert_status[i].value & options) && !(contexts_alert_status[i].value & used))) {
47 + const char *name = contexts_alert_status[i].name;
48 + used |= contexts_alert_status[i].value;
49 +
50 + buffer_json_add_array_item_string(wb, name);
51 + }
52 + }
53 +
54 + buffer_json_array_close(wb);
55 +}
56 +
57 +void contexts_alert_statuses_init(void) {
58 + for(size_t i = 0; contexts_alert_status[i].name ; i++)
59 + contexts_alert_status[i].hash = simple_hash(contexts_alert_status[i].name);
60 +}
src/web/api/maps/contexts_alert_statuses.h new
+26
@@ -0,0 +1,26 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_CONTEXTS_ALERT_STATUSES_H
4 +#define NETDATA_CONTEXTS_ALERT_STATUSES_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +
8 +typedef enum contexts_alert_status {
9 + CONTEXT_ALERT_UNINITIALIZED = (1 << 6), // include UNINITIALIZED alerts
10 + CONTEXT_ALERT_UNDEFINED = (1 << 7), // include UNDEFINED alerts
11 + CONTEXT_ALERT_CLEAR = (1 << 8), // include CLEAR alerts
12 + CONTEXT_ALERT_RAISED = (1 << 9), // include WARNING & CRITICAL alerts
13 + CONTEXT_ALERT_WARNING = (1 << 10), // include WARNING alerts
14 + CONTEXT_ALERT_CRITICAL = (1 << 11), // include CRITICAL alerts
15 +} CONTEXTS_ALERT_STATUS;
16 +
17 +#define CONTEXTS_ALERT_STATUSES (CONTEXT_ALERT_UNINITIALIZED | CONTEXT_ALERT_UNDEFINED | CONTEXT_ALERT_CLEAR | \
18 + CONTEXT_ALERT_RAISED | CONTEXT_ALERT_WARNING | CONTEXT_ALERT_CRITICAL)
19 +
20 +CONTEXTS_ALERT_STATUS contexts_alert_status_str_to_id(char *o);
21 +void contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key,
22 + CONTEXTS_ALERT_STATUS options);
23 +
24 +void contexts_alert_statuses_init(void);
25 +
26 +#endif //NETDATA_CONTEXTS_ALERT_STATUSES_H
src/web/api/maps/contexts_options.c new
+58
@@ -0,0 +1,58 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "contexts_options.h"
4 +
5 +static struct {
6 + const char *name;
7 + uint32_t hash;
8 + CONTEXTS_OPTIONS value;
9 +} contexts_options[] = {
10 + {"minify" , 0 , CONTEXTS_OPTION_MINIFY}
11 + , {"debug" , 0 , CONTEXTS_OPTION_DEBUG}
12 + , {"config" , 0 , CONTEXTS_OPTION_ALERTS_WITH_CONFIGURATIONS}
13 + , {"instances" , 0 , CONTEXTS_OPTION_ALERTS_WITH_INSTANCES}
14 + , {"values" , 0 , CONTEXTS_OPTION_ALERTS_WITH_VALUES}
15 + , {"summary" , 0 , CONTEXTS_OPTION_ALERTS_WITH_SUMMARY}
16 + , {NULL , 0 , 0}
17 +};
18 +
19 +CONTEXTS_OPTIONS contexts_options_str_to_id(char *o) {
20 + CONTEXTS_OPTIONS ret = 0;
21 + char *tok;
22 +
23 + while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
24 + if(!*tok) continue;
25 +
26 + uint32_t hash = simple_hash(tok);
27 + int i;
28 + for(i = 0; contexts_options[i].name ; i++) {
29 + if (unlikely(hash == contexts_options[i].hash && !strcmp(tok, contexts_options[i].name))) {
30 + ret |= contexts_options[i].value;
31 + break;
32 + }
33 + }
34 + }
35 +
36 + return ret;
37 +}
38 +
39 +void contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_OPTIONS options) {
40 + buffer_json_member_add_array(wb, key);
41 +
42 + CONTEXTS_OPTIONS used = 0; // to prevent adding duplicates
43 + for(int i = 0; contexts_options[i].name ; i++) {
44 + if (unlikely((contexts_options[i].value & options) && !(contexts_options[i].value & used))) {
45 + const char *name = contexts_options[i].name;
46 + used |= contexts_options[i].value;
47 +
48 + buffer_json_add_array_item_string(wb, name);
49 + }
50 + }
51 +
52 + buffer_json_array_close(wb);
53 +}
54 +
55 +void contexts_options_init(void) {
56 + for(size_t i = 0; contexts_options[i].name ; i++)
57 + contexts_options[i].hash = simple_hash(contexts_options[i].name);
58 +}
src/web/api/maps/contexts_options.h new
+22
@@ -0,0 +1,22 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_CONTEXTS_OPTIONS_H
4 +#define NETDATA_CONTEXTS_OPTIONS_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +
8 +typedef enum contexts_options {
9 + CONTEXTS_OPTION_MINIFY = (1 << 0), // remove JSON spaces and newlines from JSON output
10 + CONTEXTS_OPTION_DEBUG = (1 << 1), // show the request
11 + CONTEXTS_OPTION_ALERTS_WITH_CONFIGURATIONS = (1 << 2), // include alert configurations (used by /api/v2/alert_transitions)
12 + CONTEXTS_OPTION_ALERTS_WITH_INSTANCES = (1 << 3), // include alert instances (used by /api/v2/alerts)
13 + CONTEXTS_OPTION_ALERTS_WITH_VALUES = (1 << 4), // include alert latest values (used by /api/v2/alerts)
14 + CONTEXTS_OPTION_ALERTS_WITH_SUMMARY = (1 << 5), // include alerts summary counters (used by /api/v2/alerts)
15 +} CONTEXTS_OPTIONS;
16 +
17 +CONTEXTS_OPTIONS contexts_options_str_to_id(char *o);
18 +void contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_OPTIONS options);
19 +
20 +void contexts_options_init(void);
21 +
22 +#endif //NETDATA_CONTEXTS_OPTIONS_H
src/web/api/maps/datasource_formats.c new
+89
@@ -0,0 +1,89 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "datasource_formats.h"
4 +
5 +static struct {
6 + const char *name;
7 + uint32_t hash;
8 + DATASOURCE_FORMAT value;
9 +} google_data_formats[] = {
10 + // this is not an error - when Google requests json, it expects javascript
11 + // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source#responseformat
12 + {"json", 0, DATASOURCE_DATATABLE_JSONP}
13 + , {"html", 0, DATASOURCE_HTML}
14 + , {"csv", 0, DATASOURCE_CSV}
15 + , {"tsv-excel", 0, DATASOURCE_TSV}
16 +
17 + // terminator
18 + , {NULL, 0, 0}
19 +};
20 +
21 +inline DATASOURCE_FORMAT google_data_format_str_to_id(char *name) {
22 + uint32_t hash = simple_hash(name);
23 + int i;
24 +
25 + for(i = 0; google_data_formats[i].name ; i++) {
26 + if (unlikely(hash == google_data_formats[i].hash && !strcmp(name, google_data_formats[i].name))) {
27 + return google_data_formats[i].value;
28 + }
29 + }
30 +
31 + return DATASOURCE_JSON;
32 +}
33 +
34 +// --------------------------------------------------------------------------------------------------------------------
35 +
36 +static struct {
37 + const char *name;
38 + uint32_t hash;
39 + DATASOURCE_FORMAT value;
40 +} datasource_formats[] = {
41 + { "datatable" , 0 , DATASOURCE_DATATABLE_JSON}
42 + , {"datasource" , 0 , DATASOURCE_DATATABLE_JSONP}
43 + , {"json" , 0 , DATASOURCE_JSON}
44 + , {"json2" , 0 , DATASOURCE_JSON2}
45 + , {"jsonp" , 0 , DATASOURCE_JSONP}
46 + , {"ssv" , 0 , DATASOURCE_SSV}
47 + , {"csv" , 0 , DATASOURCE_CSV}
48 + , {"tsv" , 0 , DATASOURCE_TSV}
49 + , {"tsv-excel" , 0 , DATASOURCE_TSV}
50 + , {"html" , 0 , DATASOURCE_HTML}
51 + , {"array" , 0 , DATASOURCE_JS_ARRAY}
52 + , {"ssvcomma" , 0 , DATASOURCE_SSV_COMMA}
53 + , {"csvjsonarray" , 0 , DATASOURCE_CSV_JSON_ARRAY}
54 + , {"markdown" , 0 , DATASOURCE_CSV_MARKDOWN}
55 +
56 + // terminator
57 + , {NULL, 0, 0}
58 +};
59 +
60 +DATASOURCE_FORMAT datasource_format_str_to_id(char *name) {
61 + uint32_t hash = simple_hash(name);
62 + int i;
63 +
64 + for(i = 0; datasource_formats[i].name ; i++) {
65 + if (unlikely(hash == datasource_formats[i].hash && !strcmp(name, datasource_formats[i].name))) {
66 + return datasource_formats[i].value;
67 + }
68 + }
69 +
70 + return DATASOURCE_JSON;
71 +}
72 +
73 +const char *rrdr_format_to_string(DATASOURCE_FORMAT format) {
74 + for(size_t i = 0; datasource_formats[i].name ;i++)
75 + if(unlikely(datasource_formats[i].value == format))
76 + return datasource_formats[i].name;
77 +
78 + return "unknown";
79 +}
80 +
81 +// --------------------------------------------------------------------------------------------------------------------
82 +
83 +void datasource_formats_init(void) {
84 + for(size_t i = 0; datasource_formats[i].name ; i++)
85 + datasource_formats[i].hash = simple_hash(datasource_formats[i].name);
86 +
87 + for(size_t i = 0; google_data_formats[i].name ; i++)
88 + google_data_formats[i].hash = simple_hash(google_data_formats[i].name);
89 +}
src/web/api/maps/datasource_formats.h new
+32
@@ -0,0 +1,32 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DATASOURCE_FORMATS_H
4 +#define NETDATA_DATASOURCE_FORMATS_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +
8 +// type of JSON generations
9 +typedef enum {
10 + DATASOURCE_JSON = 0,
11 + DATASOURCE_DATATABLE_JSON,
12 + DATASOURCE_DATATABLE_JSONP,
13 + DATASOURCE_SSV,
14 + DATASOURCE_CSV,
15 + DATASOURCE_JSONP,
16 + DATASOURCE_TSV,
17 + DATASOURCE_HTML,
18 + DATASOURCE_JS_ARRAY,
19 + DATASOURCE_SSV_COMMA,
20 + DATASOURCE_CSV_JSON_ARRAY,
21 + DATASOURCE_CSV_MARKDOWN,
22 + DATASOURCE_JSON2,
23 +} DATASOURCE_FORMAT;
24 +
25 +DATASOURCE_FORMAT datasource_format_str_to_id(char *name);
26 +const char *rrdr_format_to_string(DATASOURCE_FORMAT format);
27 +
28 +DATASOURCE_FORMAT google_data_format_str_to_id(char *name);
29 +
30 +void datasource_formats_init(void);
31 +
32 +#endif //NETDATA_DATASOURCE_FORMATS_H
src/web/api/maps/maps.h new
+12
@@ -0,0 +1,12 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_MAPS_H
4 +#define NETDATA_MAPS_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +#include "datasource_formats.h"
8 +#include "contexts_options.h"
9 +#include "rrdr_options.h"
10 +#include "contexts_alert_statuses.h"
11 +
12 +#endif //NETDATA_MAPS_H
src/web/api/maps/rrdr_options.c new
+139
@@ -0,0 +1,139 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "rrdr_options.h"
4 +
5 +static struct {
6 + const char *name;
7 + uint32_t hash;
8 + RRDR_OPTIONS value;
9 +} rrdr_options[] = {
10 + { "nonzero" , 0 , RRDR_OPTION_NONZERO}
11 + , {"flip" , 0 , RRDR_OPTION_REVERSED}
12 + , {"reversed" , 0 , RRDR_OPTION_REVERSED}
13 + , {"reverse" , 0 , RRDR_OPTION_REVERSED}
14 + , {"jsonwrap" , 0 , RRDR_OPTION_JSON_WRAP}
15 + , {"min2max" , 0 , RRDR_OPTION_DIMS_MIN2MAX} // rrdr2value() only
16 + , {"average" , 0 , RRDR_OPTION_DIMS_AVERAGE} // rrdr2value() only
17 + , {"min" , 0 , RRDR_OPTION_DIMS_MIN} // rrdr2value() only
18 + , {"max" , 0 , RRDR_OPTION_DIMS_MAX} // rrdr2value() only
19 + , {"ms" , 0 , RRDR_OPTION_MILLISECONDS}
20 + , {"milliseconds" , 0 , RRDR_OPTION_MILLISECONDS}
21 + , {"absolute" , 0 , RRDR_OPTION_ABSOLUTE}
22 + , {"abs" , 0 , RRDR_OPTION_ABSOLUTE}
23 + , {"absolute_sum" , 0 , RRDR_OPTION_ABSOLUTE}
24 + , {"absolute-sum" , 0 , RRDR_OPTION_ABSOLUTE}
25 + , {"display_absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
26 + , {"display-absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
27 + , {"seconds" , 0 , RRDR_OPTION_SECONDS}
28 + , {"null2zero" , 0 , RRDR_OPTION_NULL2ZERO}
29 + , {"objectrows" , 0 , RRDR_OPTION_OBJECTSROWS}
30 + , {"google_json" , 0 , RRDR_OPTION_GOOGLE_JSON}
31 + , {"google-json" , 0 , RRDR_OPTION_GOOGLE_JSON}
32 + , {"percentage" , 0 , RRDR_OPTION_PERCENTAGE}
33 + , {"unaligned" , 0 , RRDR_OPTION_NOT_ALIGNED}
34 + , {"match_ids" , 0 , RRDR_OPTION_MATCH_IDS}
35 + , {"match-ids" , 0 , RRDR_OPTION_MATCH_IDS}
36 + , {"match_names" , 0 , RRDR_OPTION_MATCH_NAMES}
37 + , {"match-names" , 0 , RRDR_OPTION_MATCH_NAMES}
38 + , {"anomaly-bit" , 0 , RRDR_OPTION_ANOMALY_BIT}
39 + , {"selected-tier" , 0 , RRDR_OPTION_SELECTED_TIER}
40 + , {"raw" , 0 , RRDR_OPTION_RETURN_RAW}
41 + , {"jw-anomaly-rates" , 0 , RRDR_OPTION_RETURN_JWAR}
42 + , {"natural-points" , 0 , RRDR_OPTION_NATURAL_POINTS}
43 + , {"virtual-points" , 0 , RRDR_OPTION_VIRTUAL_POINTS}
44 + , {"all-dimensions" , 0 , RRDR_OPTION_ALL_DIMENSIONS}
45 + , {"details" , 0 , RRDR_OPTION_SHOW_DETAILS}
46 + , {"debug" , 0 , RRDR_OPTION_DEBUG}
47 + , {"plan" , 0 , RRDR_OPTION_DEBUG}
48 + , {"minify" , 0 , RRDR_OPTION_MINIFY}
49 + , {"group-by-labels" , 0 , RRDR_OPTION_GROUP_BY_LABELS}
50 + , {"label-quotes" , 0 , RRDR_OPTION_LABEL_QUOTES}
51 + , {NULL , 0 , 0}
52 +};
53 +
54 +RRDR_OPTIONS rrdr_options_parse_one(const char *o) {
55 + RRDR_OPTIONS ret = 0;
56 +
57 + if(!o || !*o) return ret;
58 +
59 + uint32_t hash = simple_hash(o);
60 + int i;
61 + for(i = 0; rrdr_options[i].name ; i++) {
62 + if (unlikely(hash == rrdr_options[i].hash && !strcmp(o, rrdr_options[i].name))) {
63 + ret |= rrdr_options[i].value;
64 + break;
65 + }
66 + }
67 +
68 + return ret;
69 +}
70 +
71 +RRDR_OPTIONS rrdr_options_parse(char *o) {
72 + RRDR_OPTIONS ret = 0;
73 + char *tok;
74 +
75 + while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
76 + if(!*tok) continue;
77 + ret |= rrdr_options_parse_one(tok);
78 + }
79 +
80 + return ret;
81 +}
82 +
83 +void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options) {
84 + buffer_json_member_add_array(wb, key);
85 +
86 + RRDR_OPTIONS used = 0; // to prevent adding duplicates
87 + for(int i = 0; rrdr_options[i].name ; i++) {
88 + if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
89 + const char *name = rrdr_options[i].name;
90 + used |= rrdr_options[i].value;
91 +
92 + buffer_json_add_array_item_string(wb, name);
93 + }
94 + }
95 +
96 + buffer_json_array_close(wb);
97 +}
98 +
99 +void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options) {
100 + RRDR_OPTIONS used = 0; // to prevent adding duplicates
101 + size_t added = 0;
102 + for(int i = 0; rrdr_options[i].name ; i++) {
103 + if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
104 + const char *name = rrdr_options[i].name;
105 + used |= rrdr_options[i].value;
106 +
107 + if(added++) buffer_strcat(wb, " ");
108 + buffer_strcat(wb, name);
109 + }
110 + }
111 +}
112 +
113 +void web_client_api_request_data_vX_options_to_string(char *buf, size_t size, RRDR_OPTIONS options) {
114 + char *write = buf;
115 + char *end = &buf[size - 1];
116 +
117 + RRDR_OPTIONS used = 0; // to prevent adding duplicates
118 + int added = 0;
119 + for(int i = 0; rrdr_options[i].name ; i++) {
120 + if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
121 + const char *name = rrdr_options[i].name;
122 + used |= rrdr_options[i].value;
123 +
124 + if(added && write < end)
125 + *write++ = ',';
126 +
127 + while(*name && write < end)
128 + *write++ = *name++;
129 +
130 + added++;
131 + }
132 + }
133 + *write = *end = '\0';
134 +}
135 +
136 +void rrdr_options_init(void) {
137 + for(size_t i = 0; rrdr_options[i].name ; i++)
138 + rrdr_options[i].hash = simple_hash(rrdr_options[i].name);
139 +}
src/web/api/maps/rrdr_options.h new
+52
@@ -0,0 +1,52 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_RRDR_OPTIONS_H
4 +#define NETDATA_RRDR_OPTIONS_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +
8 +typedef enum rrdr_options {
9 + RRDR_OPTION_NONZERO = (1 << 0), // don't output dimensions with just zero values
10 + RRDR_OPTION_REVERSED = (1 << 1), // output the rows in reverse order (oldest to newest)
11 + RRDR_OPTION_ABSOLUTE = (1 << 2), // values positive, for DATASOURCE_SSV before summing
12 + RRDR_OPTION_DIMS_MIN2MAX = (1 << 3), // when adding dimensions, use max - min, instead of sum
13 + RRDR_OPTION_DIMS_AVERAGE = (1 << 4), // when adding dimensions, use average, instead of sum
14 + RRDR_OPTION_DIMS_MIN = (1 << 5), // when adding dimensions, use minimum, instead of sum
15 + RRDR_OPTION_DIMS_MAX = (1 << 6), // when adding dimensions, use maximum, instead of sum
16 + RRDR_OPTION_SECONDS = (1 << 7), // output seconds, instead of dates
17 + RRDR_OPTION_MILLISECONDS = (1 << 8), // output milliseconds, instead of dates
18 + RRDR_OPTION_NULL2ZERO = (1 << 9), // do not show nulls, convert them to zeros
19 + RRDR_OPTION_OBJECTSROWS = (1 << 10), // each row of values should be an object, not an array
20 + RRDR_OPTION_GOOGLE_JSON = (1 << 11), // comply with google JSON/JSONP specs
21 + RRDR_OPTION_JSON_WRAP = (1 << 12), // wrap the response in a JSON header with info about the result
22 + RRDR_OPTION_LABEL_QUOTES = (1 << 13), // in CSV output, wrap header labels in double quotes
23 + RRDR_OPTION_PERCENTAGE = (1 << 14), // give values as percentage of total
24 + RRDR_OPTION_NOT_ALIGNED = (1 << 15), // do not align charts for persistent timeframes
25 + RRDR_OPTION_DISPLAY_ABS = (1 << 16), // for badges, display the absolute value, but calculate colors with sign
26 + RRDR_OPTION_MATCH_IDS = (1 << 17), // when filtering dimensions, match only IDs
27 + RRDR_OPTION_MATCH_NAMES = (1 << 18), // when filtering dimensions, match only names
28 + RRDR_OPTION_NATURAL_POINTS = (1 << 19), // return the natural points of the database
29 + RRDR_OPTION_VIRTUAL_POINTS = (1 << 20), // return virtual points
30 + RRDR_OPTION_ANOMALY_BIT = (1 << 21), // Return the anomaly bit stored in each collected_number
31 + RRDR_OPTION_RETURN_RAW = (1 << 22), // Return raw data for aggregating across multiple nodes
32 + RRDR_OPTION_RETURN_JWAR = (1 << 23), // Return anomaly rates in jsonwrap
33 + RRDR_OPTION_SELECTED_TIER = (1 << 24), // Use the selected tier for the query
34 + RRDR_OPTION_ALL_DIMENSIONS = (1 << 25), // Return the full dimensions list
35 + RRDR_OPTION_SHOW_DETAILS = (1 << 26), // v2 returns detailed object tree
36 + RRDR_OPTION_DEBUG = (1 << 27), // v2 returns request description
37 + RRDR_OPTION_MINIFY = (1 << 28), // remove JSON spaces and newlines from JSON output
38 + RRDR_OPTION_GROUP_BY_LABELS = (1 << 29), // v2 returns flattened labels per dimension of the chart
39 +
40 + // internal ones - not to be exposed to the API
41 + RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate
42 +} RRDR_OPTIONS;
43 +
44 +void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options);
45 +void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options);
46 +void web_client_api_request_data_vX_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
47 +void rrdr_options_init(void);
48 +
49 +RRDR_OPTIONS rrdr_options_parse(char *o);
50 +RRDR_OPTIONS rrdr_options_parse_one(const char *o);
51 +
52 +#endif //NETDATA_RRDR_OPTIONS_H
src/web/api/queries/rrdr.h
-56
@@ -17,62 +17,6 @@ typedef enum tier_query_fetch {
17 TIER_QUERY_FETCH_AVERAGE
18 } TIER_QUERY_FETCH;
19
20 -typedef enum rrdr_options {
21 - RRDR_OPTION_NONZERO = (1 << 0), // don't output dimensions with just zero values
22 - RRDR_OPTION_REVERSED = (1 << 1), // output the rows in reverse order (oldest to newest)
23 - RRDR_OPTION_ABSOLUTE = (1 << 2), // values positive, for DATASOURCE_SSV before summing
24 - RRDR_OPTION_DIMS_MIN2MAX = (1 << 3), // when adding dimensions, use max - min, instead of sum
25 - RRDR_OPTION_DIMS_AVERAGE = (1 << 4), // when adding dimensions, use average, instead of sum
26 - RRDR_OPTION_DIMS_MIN = (1 << 5), // when adding dimensions, use minimum, instead of sum
27 - RRDR_OPTION_DIMS_MAX = (1 << 6), // when adding dimensions, use maximum, instead of sum
28 - RRDR_OPTION_SECONDS = (1 << 7), // output seconds, instead of dates
29 - RRDR_OPTION_MILLISECONDS = (1 << 8), // output milliseconds, instead of dates
30 - RRDR_OPTION_NULL2ZERO = (1 << 9), // do not show nulls, convert them to zeros
31 - RRDR_OPTION_OBJECTSROWS = (1 << 10), // each row of values should be an object, not an array
32 - RRDR_OPTION_GOOGLE_JSON = (1 << 11), // comply with google JSON/JSONP specs
33 - RRDR_OPTION_JSON_WRAP = (1 << 12), // wrap the response in a JSON header with info about the result
34 - RRDR_OPTION_LABEL_QUOTES = (1 << 13), // in CSV output, wrap header labels in double quotes
35 - RRDR_OPTION_PERCENTAGE = (1 << 14), // give values as percentage of total
36 - RRDR_OPTION_NOT_ALIGNED = (1 << 15), // do not align charts for persistent timeframes
37 - RRDR_OPTION_DISPLAY_ABS = (1 << 16), // for badges, display the absolute value, but calculate colors with sign
38 - RRDR_OPTION_MATCH_IDS = (1 << 17), // when filtering dimensions, match only IDs
39 - RRDR_OPTION_MATCH_NAMES = (1 << 18), // when filtering dimensions, match only names
40 - RRDR_OPTION_NATURAL_POINTS = (1 << 19), // return the natural points of the database
41 - RRDR_OPTION_VIRTUAL_POINTS = (1 << 20), // return virtual points
42 - RRDR_OPTION_ANOMALY_BIT = (1 << 21), // Return the anomaly bit stored in each collected_number
43 - RRDR_OPTION_RETURN_RAW = (1 << 22), // Return raw data for aggregating across multiple nodes
44 - RRDR_OPTION_RETURN_JWAR = (1 << 23), // Return anomaly rates in jsonwrap
45 - RRDR_OPTION_SELECTED_TIER = (1 << 24), // Use the selected tier for the query
46 - RRDR_OPTION_ALL_DIMENSIONS = (1 << 25), // Return the full dimensions list
47 - RRDR_OPTION_SHOW_DETAILS = (1 << 26), // v2 returns detailed object tree
48 - RRDR_OPTION_DEBUG = (1 << 27), // v2 returns request description
49 - RRDR_OPTION_MINIFY = (1 << 28), // remove JSON spaces and newlines from JSON output
50 - RRDR_OPTION_GROUP_BY_LABELS = (1 << 29), // v2 returns flattened labels per dimension of the chart
51 -
52 - // internal ones - not to be exposed to the API
53 - RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate
54 -} RRDR_OPTIONS;
55 -
56 -typedef enum context_v2_options {
57 - CONTEXT_V2_OPTION_MINIFY = (1 << 0), // remove JSON spaces and newlines from JSON output
58 - CONTEXT_V2_OPTION_DEBUG = (1 << 1), // show the request
59 - CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS = (1 << 2), // include alert configurations (used by /api/v2/alert_transitions)
60 - CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES = (1 << 3), // include alert instances (used by /api/v2/alerts)
61 - CONTEXT_V2_OPTION_ALERTS_WITH_VALUES = (1 << 4), // include alert latest values (used by /api/v2/alerts)
62 - CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY = (1 << 5), // include alerts summary counters (used by /api/v2/alerts)
63 -} CONTEXTS_V2_OPTIONS;
64 -
65 -typedef enum context_v2_alert_status {
66 - CONTEXT_V2_ALERT_UNINITIALIZED = (1 << 5), // include UNINITIALIZED alerts
67 - CONTEXT_V2_ALERT_UNDEFINED = (1 << 6), // include UNDEFINED alerts
68 - CONTEXT_V2_ALERT_CLEAR = (1 << 7), // include CLEAR alerts
69 - CONTEXT_V2_ALERT_RAISED = (1 << 8), // include WARNING & CRITICAL alerts
70 - CONTEXT_V2_ALERT_WARNING = (1 << 9), // include WARNING alerts
71 - CONTEXT_V2_ALERT_CRITICAL = (1 << 10), // include CRITICAL alerts
72 -} CONTEXTS_V2_ALERT_STATUS;
73 -
74 -#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)
75 -
20 typedef enum __attribute__ ((__packed__)) rrdr_value_flag {
21
22 // IMPORTANT:
src/web/api/v1/api_v1_aclk.c new
+20
@@ -0,0 +1,20 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_aclk(RRDHOST *host, struct web_client *w, char *url) {
6 + UNUSED(url);
7 + UNUSED(host);
8 + if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
9 +
10 + BUFFER *wb = w->response.data;
11 + buffer_flush(wb);
12 + char *str = aclk_state_json();
13 + buffer_strcat(wb, str);
14 + freez(str);
15 +
16 + wb->content_type = CT_APPLICATION_JSON;
17 + buffer_no_cacheable(wb);
18 + return HTTP_RESP_OK;
19 +}
20 +
src/web/api/v1/api_v1_alarms.c new
+153
@@ -0,0 +1,153 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +static int web_client_api_request_v1_alarms_select(char *url) {
6 + int all = 0;
7 + while(url) {
8 + char *value = strsep_skip_consecutive_separators(&url, "&");
9 + if (!value || !*value) continue;
10 +
11 + if(!strcmp(value, "all") || !strcmp(value, "all=true")) all = 1;
12 + else if(!strcmp(value, "active") || !strcmp(value, "active=true")) all = 0;
13 + }
14 +
15 + return all;
16 +}
17 +
18 +int api_v1_alarms(RRDHOST *host, struct web_client *w, char *url) {
19 + int all = web_client_api_request_v1_alarms_select(url);
20 +
21 + buffer_flush(w->response.data);
22 + w->response.data->content_type = CT_APPLICATION_JSON;
23 + health_alarms2json(host, w->response.data, all);
24 + buffer_no_cacheable(w->response.data);
25 + return HTTP_RESP_OK;
26 +}
27 +
28 +int api_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url) {
29 + int all = web_client_api_request_v1_alarms_select(url);
30 +
31 + buffer_flush(w->response.data);
32 + w->response.data->content_type = CT_APPLICATION_JSON;
33 + health_alarms_values2json(host, w->response.data, all);
34 + buffer_no_cacheable(w->response.data);
35 + return HTTP_RESP_OK;
36 +}
37 +
38 +int api_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url) {
39 + RRDCALC_STATUS status = RRDCALC_STATUS_RAISED;
40 + BUFFER *contexts = NULL;
41 +
42 + buffer_flush(w->response.data);
43 + buffer_sprintf(w->response.data, "[");
44 +
45 + while(url) {
46 + char *value = strsep_skip_consecutive_separators(&url, "&");
47 + if(!value || !*value) continue;
48 +
49 + char *name = strsep_skip_consecutive_separators(&value, "=");
50 + if(!name || !*name) continue;
51 + if(!value || !*value) continue;
52 +
53 + netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 alarm_count query param '%s' with value '%s'", w->id, name, value);
54 +
55 + char* p = value;
56 + if(!strcmp(name, "status")) {
57 + while ((*p = toupper(*p))) p++;
58 + if (!strcmp("CRITICAL", value)) status = RRDCALC_STATUS_CRITICAL;
59 + else if (!strcmp("WARNING", value)) status = RRDCALC_STATUS_WARNING;
60 + else if (!strcmp("UNINITIALIZED", value)) status = RRDCALC_STATUS_UNINITIALIZED;
61 + else if (!strcmp("UNDEFINED", value)) status = RRDCALC_STATUS_UNDEFINED;
62 + else if (!strcmp("REMOVED", value)) status = RRDCALC_STATUS_REMOVED;
63 + else if (!strcmp("CLEAR", value)) status = RRDCALC_STATUS_CLEAR;
64 + }
65 + else if(!strcmp(name, "context") || !strcmp(name, "ctx")) {
66 + if(!contexts) contexts = buffer_create(255, &netdata_buffers_statistics.buffers_api);
67 + buffer_strcat(contexts, "|");
68 + buffer_strcat(contexts, value);
69 + }
70 + }
71 +
72 + health_aggregate_alarms(host, w->response.data, contexts, status);
73 +
74 + buffer_sprintf(w->response.data, "]\n");
75 + w->response.data->content_type = CT_APPLICATION_JSON;
76 + buffer_no_cacheable(w->response.data);
77 +
78 + buffer_free(contexts);
79 + return 200;
80 +}
81 +
82 +int api_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
83 + time_t after = 0;
84 + char *chart = NULL;
85 +
86 + while(url) {
87 + char *value = strsep_skip_consecutive_separators(&url, "&");
88 + if (!value || !*value) continue;
89 +
90 + char *name = strsep_skip_consecutive_separators(&value, "=");
91 + if(!name || !*name) continue;
92 + if(!value || !*value) continue;
93 +
94 + if (!strcmp(name, "after")) after = (time_t) strtoul(value, NULL, 0);
95 + else if (!strcmp(name, "chart")) chart = value;
96 + }
97 +
98 + buffer_flush(w->response.data);
99 + w->response.data->content_type = CT_APPLICATION_JSON;
100 + sql_health_alarm_log2json(host, w->response.data, after, chart);
101 + return HTTP_RESP_OK;
102 +}
103 +
104 +int api_v1_variable(RRDHOST *host, struct web_client *w, char *url) {
105 + int ret = HTTP_RESP_BAD_REQUEST;
106 + char *chart = NULL;
107 + char *variable = NULL;
108 +
109 + buffer_flush(w->response.data);
110 +
111 + while(url) {
112 + char *value = strsep_skip_consecutive_separators(&url, "&");
113 + if(!value || !*value) continue;
114 +
115 + char *name = strsep_skip_consecutive_separators(&value, "=");
116 + if(!name || !*name) continue;
117 + if(!value || !*value) continue;
118 +
119 + // name and value are now the parameters
120 + // they are not null and not empty
121 +
122 + if(!strcmp(name, "chart")) chart = value;
123 + else if(!strcmp(name, "variable")) variable = value;
124 + }
125 +
126 + if(!chart || !*chart || !variable || !*variable) {
127 + buffer_sprintf(w->response.data, "A chart= and a variable= are required.");
128 + goto cleanup;
129 + }
130 +
131 + RRDSET *st = rrdset_find(host, chart);
132 + if(!st) st = rrdset_find_byname(host, chart);
133 + if(!st) {
134 + buffer_strcat(w->response.data, "Chart is not found: ");
135 + buffer_strcat_htmlescape(w->response.data, chart);
136 + ret = HTTP_RESP_NOT_FOUND;
137 + goto cleanup;
138 + }
139 +
140 + w->response.data->content_type = CT_APPLICATION_JSON;
141 + st->last_accessed_time_s = now_realtime_sec();
142 + alert_variable_lookup_trace(host, st, variable, w->response.data);
143 +
144 + return HTTP_RESP_OK;
145 +
146 +cleanup:
147 + return ret;
148 +}
149 +
150 +int api_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
151 + return api_v1_single_chart_helper(host, w, url, health_api_v1_chart_variables2json);
152 +}
153 +
src/web/api/v1/api_v1_allmetrics.c renamed
+139 -1
@@ -1,6 +1,29 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#include "allmetrics_shell.h"
3 +#include "api_v1_calls.h"
4 +
5 +#define ALLMETRICS_FORMAT_SHELL "shell"
6 +#define ALLMETRICS_FORMAT_PROMETHEUS "prometheus"
7 +#define ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "prometheus_all_hosts"
8 +#define ALLMETRICS_FORMAT_JSON "json"
9 +
10 +#define ALLMETRICS_SHELL 1
11 +#define ALLMETRICS_PROMETHEUS 2
12 +#define ALLMETRICS_JSON 3
13 +#define ALLMETRICS_PROMETHEUS_ALL_HOSTS 4
14 +
15 +struct prometheus_output_options {
16 + char *name;
17 + PROMETHEUS_OUTPUT_OPTIONS flag;
18 +} prometheus_output_flags_root[] = {
19 + { "names", PROMETHEUS_OUTPUT_NAMES },
20 + { "timestamps", PROMETHEUS_OUTPUT_TIMESTAMPS },
21 + { "variables", PROMETHEUS_OUTPUT_VARIABLES },
22 + { "oldunits", PROMETHEUS_OUTPUT_OLDUNITS },
23 + { "hideunits", PROMETHEUS_OUTPUT_HIDEUNITS },
24 + // terminator
25 + { NULL, PROMETHEUS_OUTPUT_NONE },
26 +};
27
28 // ----------------------------------------------------------------------------
29 // BASH
@@ -168,3 +191,118 @@ void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_s
191 simple_pattern_free(filter);
192 }
193
194 +int api_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) {
195 + int format = ALLMETRICS_SHELL;
196 + const char *filter = NULL;
197 + const char *prometheus_server = w->client_ip;
198 +
199 + uint32_t prometheus_exporting_options;
200 + if (prometheus_exporter_instance)
201 + prometheus_exporting_options = prometheus_exporter_instance->config.options;
202 + else
203 + prometheus_exporting_options = global_exporting_options;
204 +
205 + PROMETHEUS_OUTPUT_OPTIONS prometheus_output_options =
206 + PROMETHEUS_OUTPUT_TIMESTAMPS |
207 + ((prometheus_exporting_options & EXPORTING_OPTION_SEND_NAMES) ? PROMETHEUS_OUTPUT_NAMES : 0);
208 +
209 + const char *prometheus_prefix;
210 + if (prometheus_exporter_instance)
211 + prometheus_prefix = prometheus_exporter_instance->config.prefix;
212 + else
213 + prometheus_prefix = global_exporting_prefix;
214 +
215 + while(url) {
216 + char *value = strsep_skip_consecutive_separators(&url, "&");
217 + if (!value || !*value) continue;
218 +
219 + char *name = strsep_skip_consecutive_separators(&value, "=");
220 + if(!name || !*name) continue;
221 + if(!value || !*value) continue;
222 +
223 + if(!strcmp(name, "format")) {
224 + if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
225 + format = ALLMETRICS_SHELL;
226 + else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS))
227 + format = ALLMETRICS_PROMETHEUS;
228 + else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS))
229 + format = ALLMETRICS_PROMETHEUS_ALL_HOSTS;
230 + else if(!strcmp(value, ALLMETRICS_FORMAT_JSON))
231 + format = ALLMETRICS_JSON;
232 + else
233 + format = 0;
234 + }
235 + else if(!strcmp(name, "filter")) {
236 + filter = value;
237 + }
238 + else if(!strcmp(name, "server")) {
239 + prometheus_server = value;
240 + }
241 + else if(!strcmp(name, "prefix")) {
242 + prometheus_prefix = value;
243 + }
244 + else if(!strcmp(name, "data") || !strcmp(name, "source") || !strcmp(name, "data source") || !strcmp(name, "data-source") || !strcmp(name, "data_source") || !strcmp(name, "datasource")) {
245 + prometheus_exporting_options = exporting_parse_data_source(value, prometheus_exporting_options);
246 + }
247 + else {
248 + int i;
249 + for(i = 0; prometheus_output_flags_root[i].name ; i++) {
250 + if(!strcmp(name, prometheus_output_flags_root[i].name)) {
251 + if(!strcmp(value, "yes") || !strcmp(value, "1") || !strcmp(value, "true"))
252 + prometheus_output_options |= prometheus_output_flags_root[i].flag;
253 + else {
254 + prometheus_output_options &= ~prometheus_output_flags_root[i].flag;
255 + }
256 +
257 + break;
258 + }
259 + }
260 + }
261 + }
262 +
263 + buffer_flush(w->response.data);
264 + buffer_no_cacheable(w->response.data);
265 +
266 + switch(format) {
267 + case ALLMETRICS_JSON:
268 + w->response.data->content_type = CT_APPLICATION_JSON;
269 + rrd_stats_api_v1_charts_allmetrics_json(host, filter, w->response.data);
270 + return HTTP_RESP_OK;
271 +
272 + case ALLMETRICS_SHELL:
273 + w->response.data->content_type = CT_TEXT_PLAIN;
274 + rrd_stats_api_v1_charts_allmetrics_shell(host, filter, w->response.data);
275 + return HTTP_RESP_OK;
276 +
277 + case ALLMETRICS_PROMETHEUS:
278 + w->response.data->content_type = CT_PROMETHEUS;
279 + rrd_stats_api_v1_charts_allmetrics_prometheus_single_host(
280 + host
281 + , filter
282 + , w->response.data
283 + , prometheus_server
284 + , prometheus_prefix
285 + , prometheus_exporting_options
286 + , prometheus_output_options
287 + );
288 + return HTTP_RESP_OK;
289 +
290 + case ALLMETRICS_PROMETHEUS_ALL_HOSTS:
291 + w->response.data->content_type = CT_PROMETHEUS;
292 + rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(
293 + host
294 + , filter
295 + , w->response.data
296 + , prometheus_server
297 + , prometheus_prefix
298 + , prometheus_exporting_options
299 + , prometheus_output_options
300 + );
301 + return HTTP_RESP_OK;
302 +
303 + default:
304 + w->response.data->content_type = CT_TEXT_PLAIN;
305 + buffer_strcat(w->response.data, "Which format? '" ALLMETRICS_FORMAT_SHELL "', '" ALLMETRICS_FORMAT_PROMETHEUS "', '" ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "' and '" ALLMETRICS_FORMAT_JSON "' are currently supported.");
306 + return HTTP_RESP_BAD_REQUEST;
307 + }
308 +}
src/web/api/v1/api_v1_badge/README.md renamed
src/web/api/v1/api_v1_badge/web_buffer_svg.c renamed
+5 -4
@@ -1,6 +1,7 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#include "web_buffer_svg.h"
3 +#include "libnetdata/libnetdata.h"
4 +#include "../../../server/web_client.h"
5
6 #define BADGE_HORIZONTAL_PADDING 4
7 #define VERDANA_KERNING 0.2
@@ -360,7 +361,7 @@ static struct units_formatter {
361 { NULL, 0, UNITS_FORMAT_NONE }
362 };
363
363 -inline char *format_value_and_unit(char *value_string, size_t value_string_len,
364 +char *format_value_and_unit(char *value_string, size_t value_string_len,
365 NETDATA_DOUBLE value, const char *units, int precision) {
366 static int max = -1;
367 int i;
@@ -734,7 +735,7 @@ static const char *parse_color_argument(const char *arg, const char *def)
735 return color_map(arg, def);
736 }
737
737 -void buffer_svg(BUFFER *wb, const char *label,
738 +static void buffer_svg(BUFFER *wb, const char *label,
739 NETDATA_DOUBLE value, const char *units, const char *label_color, const char *value_color, int precision, int scale, uint32_t options, int fixed_width_lbl, int fixed_width_val, const char* text_color_lbl, const char* text_color_val) {
740 char value_color_buffer[COLOR_STRING_SIZE + 1]
741 , value_string[VALUE_STRING_SIZE + 1]
@@ -864,7 +865,7 @@ void buffer_svg(BUFFER *wb, const char *label,
865 #define BADGE_URL_ARG_LBL_COLOR "text_color_lbl"
866 #define BADGE_URL_ARG_VAL_COLOR "text_color_val"
867
867 -int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
868 +int api_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
869 int ret = HTTP_RESP_BAD_REQUEST;
870 buffer_flush(w->response.data);
871
src/web/api/v1/api_v1_calls.h new
+47
@@ -0,0 +1,47 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_API_V1_CALLS_H
4 +#define NETDATA_API_V1_CALLS_H
5 +
6 +#include "../web_api_v1.h"
7 +
8 +int api_v1_info(RRDHOST *host, struct web_client *w, char *url);
9 +
10 +int api_v1_config(RRDHOST *host, struct web_client *w, char *url);
11 +
12 +int api_v1_registry(RRDHOST *host, struct web_client *w, char *url);
13 +
14 +int api_v1_manage(RRDHOST *host, struct web_client *w, char *url);
15 +
16 +int api_v1_data(RRDHOST *host, struct web_client *w, char *url);
17 +int api_v1_chart(RRDHOST *host, struct web_client *w, char *url);
18 +int api_v1_charts(RRDHOST *host, struct web_client *w, char *url);
19 +
20 +int api_v1_context(RRDHOST *host, struct web_client *w, char *url);
21 +int api_v1_contexts(RRDHOST *host, struct web_client *w, char *url);
22 +
23 +int api_v1_alarms(RRDHOST *host, struct web_client *w, char *url);
24 +int api_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url);
25 +int api_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url);
26 +int api_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url);
27 +int api_v1_variable(RRDHOST *host, struct web_client *w, char *url);
28 +int api_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url);
29 +
30 +int api_v1_dbengine_stats(RRDHOST *host, struct web_client *w, char *url);
31 +int api_v1_ml_info(RRDHOST *host, struct web_client *w, char *url);
32 +int api_v1_aclk(RRDHOST *host, struct web_client *w, char *url);
33 +
34 +int api_v1_functions(RRDHOST *host, struct web_client *w, char *url);
35 +int api_v1_function(RRDHOST *host, struct web_client *w, char *url);
36 +
37 +int api_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url);
38 +int api_v1_weights(RRDHOST *host, struct web_client *w, char *url);
39 +
40 +int api_v1_badge(RRDHOST *host, struct web_client *w, char *url);
41 +int api_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url);
42 +
43 +// common library calls
44 +int api_v1_single_chart_helper(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf));
45 +void api_v1_management_init(void);
46 +
47 +#endif //NETDATA_API_V1_CALLS_H
src/web/api/v1/api_v1_charts.c new
+64
@@ -0,0 +1,64 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_single_chart_helper(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) {
6 + int ret = HTTP_RESP_BAD_REQUEST;
7 + char *chart = NULL;
8 +
9 + buffer_flush(w->response.data);
10 +
11 + while(url) {
12 + char *value = strsep_skip_consecutive_separators(&url, "&");
13 + if(!value || !*value) continue;
14 +
15 + char *name = strsep_skip_consecutive_separators(&value, "=");
16 + if(!name || !*name) continue;
17 + if(!value || !*value) continue;
18 +
19 + // name and value are now the parameters
20 + // they are not null and not empty
21 +
22 + if(!strcmp(name, "chart")) chart = value;
23 + //else {
24 + /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
25 + // goto cleanup;
26 + //}
27 + }
28 +
29 + if(!chart || !*chart) {
30 + buffer_sprintf(w->response.data, "No chart id is given at the request.");
31 + goto cleanup;
32 + }
33 +
34 + RRDSET *st = rrdset_find(host, chart);
35 + if(!st) st = rrdset_find_byname(host, chart);
36 + if(!st) {
37 + buffer_strcat(w->response.data, "Chart is not found: ");
38 + buffer_strcat_htmlescape(w->response.data, chart);
39 + ret = HTTP_RESP_NOT_FOUND;
40 + goto cleanup;
41 + }
42 +
43 + w->response.data->content_type = CT_APPLICATION_JSON;
44 + st->last_accessed_time_s = now_realtime_sec();
45 + callback(st, w->response.data);
46 + return HTTP_RESP_OK;
47 +
48 +cleanup:
49 + return ret;
50 +}
51 +
52 +int api_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
53 + (void)url;
54 +
55 + buffer_flush(w->response.data);
56 + w->response.data->content_type = CT_APPLICATION_JSON;
57 + charts2json(host, w->response.data);
58 + return HTTP_RESP_OK;
59 +}
60 +
61 +int api_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
62 + return api_v1_single_chart_helper(host, w, url, rrd_stats_api_v1_chart);
63 +}
64 +
src/web/api/v1/api_v1_config.c new
+92
@@ -0,0 +1,92 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "web/api/v2/api_v2_calls.h"
4 +
5 +int api_v1_config(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
6 + char *action = "tree";
7 + char *path = "/";
8 + char *id = NULL;
9 + char *add_name = NULL;
10 + int timeout = 120;
11 +
12 + while(url) {
13 + char *value = strsep_skip_consecutive_separators(&url, "&");
14 + if(!value || !*value) continue;
15 +
16 + char *name = strsep_skip_consecutive_separators(&value, "=");
17 + if(!name || !*name) continue;
18 + if(!value || !*value) continue;
19 +
20 + // name and value are now the parameters
21 + // they are not null and not empty
22 +
23 + if(!strcmp(name, "action"))
24 + action = value;
25 + else if(!strcmp(name, "path"))
26 + path = value;
27 + else if(!strcmp(name, "id"))
28 + id = value;
29 + else if(!strcmp(name, "name"))
30 + add_name = value;
31 + else if(!strcmp(name, "timeout")) {
32 + timeout = (int)strtol(value, NULL, 10);
33 + if(timeout < 10)
34 + timeout = 10;
35 + }
36 + }
37 +
38 + char transaction[UUID_COMPACT_STR_LEN];
39 + uuid_unparse_lower_compact(w->transaction, transaction);
40 +
41 + size_t len = strlen(action) + (id ? strlen(id) : 0) + strlen(path) + (add_name ? strlen(add_name) : 0) + 100;
42 +
43 + char cmd[len];
44 + if(strcmp(action, "tree") == 0)
45 + snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " tree '%s' '%s'", path, id?id:"");
46 + else {
47 + DYNCFG_CMDS c = dyncfg_cmds2id(action);
48 + if(!id || !*id || !dyncfg_is_valid_id(id)) {
49 + rrd_call_function_error(w->response.data, "invalid id given", HTTP_RESP_BAD_REQUEST);
50 + return HTTP_RESP_BAD_REQUEST;
51 + }
52 +
53 + if(c == DYNCFG_CMD_NONE) {
54 + rrd_call_function_error(w->response.data, "invalid action given", HTTP_RESP_BAD_REQUEST);
55 + return HTTP_RESP_BAD_REQUEST;
56 + }
57 +
58 + if(c == DYNCFG_CMD_ADD || c == DYNCFG_CMD_USERCONFIG || c == DYNCFG_CMD_TEST) {
59 + if(c == DYNCFG_CMD_TEST && (!add_name || !*add_name)) {
60 + // backwards compatibility for TEST without a name
61 + char *colon = strrchr(id, ':');
62 + if(colon) {
63 + *colon = '\0';
64 + add_name = ++colon;
65 + }
66 + else
67 + add_name = "test";
68 + }
69 +
70 + if(!add_name || !*add_name || !dyncfg_is_valid_id(add_name)) {
71 + rrd_call_function_error(w->response.data, "invalid name given", HTTP_RESP_BAD_REQUEST);
72 + return HTTP_RESP_BAD_REQUEST;
73 + }
74 + snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s %s", id, dyncfg_id2cmd_one(c), add_name);
75 + }
76 + else
77 + snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s", id, dyncfg_id2cmd_one(c));
78 + }
79 +
80 + CLEAN_BUFFER *source = buffer_create(100, NULL);
81 + web_client_api_request_vX_source_to_buffer(w, source);
82 +
83 + buffer_flush(w->response.data);
84 + int code = rrd_function_run(host, w->response.data, timeout, w->access, cmd,
85 + true, transaction,
86 + NULL, NULL,
87 + web_client_progress_functions_update, w,
88 + web_client_interrupt_callback, w,
89 + w->payload, buffer_tostring(source), false);
90 +
91 + return code;
92 +}
src/web/api/v1/api_v1_context.c new
+68
@@ -0,0 +1,68 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_context(RRDHOST *host, struct web_client *w, char *url) {
6 + char *context = NULL;
7 + RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
8 + time_t after = 0, before = 0;
9 + const char *chart_label_key = NULL, *chart_labels_filter = NULL;
10 + BUFFER *dimensions = NULL;
11 +
12 + buffer_flush(w->response.data);
13 +
14 + while(url) {
15 + char *value = strsep_skip_consecutive_separators(&url, "&");
16 + if(!value || !*value) continue;
17 +
18 + char *name = strsep_skip_consecutive_separators(&value, "=");
19 + if(!name || !*name) continue;
20 + if(!value || !*value) continue;
21 +
22 + // name and value are now the parameters
23 + // they are not null and not empty
24 +
25 + if(!strcmp(name, "context") || !strcmp(name, "ctx")) context = value;
26 + else if(!strcmp(name, "after")) after = str2l(value);
27 + else if(!strcmp(name, "before")) before = str2l(value);
28 + else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
29 + else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
30 + else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
31 + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
32 + if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
33 + buffer_strcat(dimensions, "|");
34 + buffer_strcat(dimensions, value);
35 + }
36 + }
37 +
38 + if(!context || !*context) {
39 + buffer_sprintf(w->response.data, "No context is given at the request.");
40 + return HTTP_RESP_BAD_REQUEST;
41 + }
42 +
43 + SIMPLE_PATTERN *chart_label_key_pattern = NULL;
44 + SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
45 + SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
46 +
47 + if(chart_label_key)
48 + chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
49 +
50 + if(chart_labels_filter)
51 + chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
52 + true);
53 +
54 + if(dimensions) {
55 + chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
56 + SIMPLE_PATTERN_EXACT, true);
57 + buffer_free(dimensions);
58 + }
59 +
60 + w->response.data->content_type = CT_APPLICATION_JSON;
61 + int ret = rrdcontext_to_json(host, w->response.data, after, before, options, context, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
62 +
63 + simple_pattern_free(chart_label_key_pattern);
64 + simple_pattern_free(chart_labels_filter_pattern);
65 + simple_pattern_free(chart_dimensions_pattern);
66 +
67 + return ret;
68 +}
src/web/api/v1/api_v1_contexts.c new
+61
@@ -0,0 +1,61 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_contexts(RRDHOST *host, struct web_client *w, char *url) {
6 + RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
7 + time_t after = 0, before = 0;
8 + const char *chart_label_key = NULL, *chart_labels_filter = NULL;
9 + BUFFER *dimensions = NULL;
10 +
11 + buffer_flush(w->response.data);
12 +
13 + while(url) {
14 + char *value = strsep_skip_consecutive_separators(&url, "&");
15 + if(!value || !*value) continue;
16 +
17 + char *name = strsep_skip_consecutive_separators(&value, "=");
18 + if(!name || !*name) continue;
19 + if(!value || !*value) continue;
20 +
21 + // name and value are now the parameters
22 + // they are not null and not empty
23 +
24 + if(!strcmp(name, "after")) after = str2l(value);
25 + else if(!strcmp(name, "before")) before = str2l(value);
26 + else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
27 + else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
28 + else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
29 + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
30 + if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
31 + buffer_strcat(dimensions, "|");
32 + buffer_strcat(dimensions, value);
33 + }
34 + }
35 +
36 + SIMPLE_PATTERN *chart_label_key_pattern = NULL;
37 + SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
38 + SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
39 +
40 + if(chart_label_key)
41 + chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
42 +
43 + if(chart_labels_filter)
44 + chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
45 + true);
46 +
47 + if(dimensions) {
48 + chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
49 + SIMPLE_PATTERN_EXACT, true);
50 + buffer_free(dimensions);
51 + }
52 +
53 + w->response.data->content_type = CT_APPLICATION_JSON;
54 + int ret = rrdcontexts_to_json(host, w->response.data, after, before, options, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
55 +
56 + simple_pattern_free(chart_label_key_pattern);
57 + simple_pattern_free(chart_labels_filter_pattern);
58 + simple_pattern_free(chart_dimensions_pattern);
59 +
60 + return ret;
61 +}
src/web/api/v1/api_v1_data.c new
+246
@@ -0,0 +1,246 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_data(RRDHOST *host, struct web_client *w, char *url) {
6 + netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
7 +
8 + int ret = HTTP_RESP_BAD_REQUEST;
9 + BUFFER *dimensions = NULL;
10 +
11 + buffer_flush(w->response.data);
12 +
13 + char *google_version = "0.6",
14 + *google_reqId = "0",
15 + *google_sig = "0",
16 + *google_out = "json",
17 + *responseHandler = NULL,
18 + *outFileName = NULL;
19 +
20 + time_t last_timestamp_in_data = 0, google_timestamp = 0;
21 +
22 + char *chart = NULL;
23 + char *before_str = NULL;
24 + char *after_str = NULL;
25 + char *group_time_str = NULL;
26 + char *points_str = NULL;
27 + char *timeout_str = NULL;
28 + char *context = NULL;
29 + char *chart_label_key = NULL;
30 + char *chart_labels_filter = NULL;
31 + char *group_options = NULL;
32 + size_t tier = 0;
33 + RRDR_TIME_GROUPING group = RRDR_GROUPING_AVERAGE;
34 + DATASOURCE_FORMAT format = DATASOURCE_JSON;
35 + RRDR_OPTIONS options = 0;
36 +
37 + while(url) {
38 + char *value = strsep_skip_consecutive_separators(&url, "&");
39 + if(!value || !*value) continue;
40 +
41 + char *name = strsep_skip_consecutive_separators(&value, "=");
42 + if(!name || !*name) continue;
43 + if(!value || !*value) continue;
44 +
45 + netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
46 +
47 + // name and value are now the parameters
48 + // they are not null and not empty
49 +
50 + if(!strcmp(name, "context")) context = value;
51 + else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
52 + else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
53 + else if(!strcmp(name, "chart")) chart = value;
54 + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
55 + if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
56 + buffer_strcat(dimensions, "|");
57 + buffer_strcat(dimensions, value);
58 + }
59 + else if(!strcmp(name, "show_dimensions")) options |= RRDR_OPTION_ALL_DIMENSIONS;
60 + else if(!strcmp(name, "after")) after_str = value;
61 + else if(!strcmp(name, "before")) before_str = value;
62 + else if(!strcmp(name, "points")) points_str = value;
63 + else if(!strcmp(name, "timeout")) timeout_str = value;
64 + else if(!strcmp(name, "gtime")) group_time_str = value;
65 + else if(!strcmp(name, "group_options")) group_options = value;
66 + else if(!strcmp(name, "group")) {
67 + group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
68 + }
69 + else if(!strcmp(name, "format")) {
70 + format = datasource_format_str_to_id(value);
71 + }
72 + else if(!strcmp(name, "options")) {
73 + options |= rrdr_options_parse(value);
74 + }
75 + else if(!strcmp(name, "callback")) {
76 + responseHandler = value;
77 + }
78 + else if(!strcmp(name, "filename")) {
79 + outFileName = value;
80 + }
81 + else if(!strcmp(name, "tqx")) {
82 + // parse Google Visualization API options
83 + // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
84 + char *tqx_name, *tqx_value;
85 +
86 + while(value) {
87 + tqx_value = strsep_skip_consecutive_separators(&value, ";");
88 + if(!tqx_value || !*tqx_value) continue;
89 +
90 + tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
91 + if(!tqx_name || !*tqx_name) continue;
92 + if(!tqx_value || !*tqx_value) continue;
93 +
94 + if(!strcmp(tqx_name, "version"))
95 + google_version = tqx_value;
96 + else if(!strcmp(tqx_name, "reqId"))
97 + google_reqId = tqx_value;
98 + else if(!strcmp(tqx_name, "sig")) {
99 + google_sig = tqx_value;
100 + google_timestamp = strtoul(google_sig, NULL, 0);
101 + }
102 + else if(!strcmp(tqx_name, "out")) {
103 + google_out = tqx_value;
104 + format = google_data_format_str_to_id(google_out);
105 + }
106 + else if(!strcmp(tqx_name, "responseHandler"))
107 + responseHandler = tqx_value;
108 + else if(!strcmp(tqx_name, "outFileName"))
109 + outFileName = tqx_value;
110 + }
111 + }
112 + else if(!strcmp(name, "tier")) {
113 + tier = str2ul(value);
114 + if(tier < storage_tiers)
115 + options |= RRDR_OPTION_SELECTED_TIER;
116 + else
117 + tier = 0;
118 + }
119 + }
120 +
121 + // validate the google parameters given
122 + fix_google_param(google_out);
123 + fix_google_param(google_sig);
124 + fix_google_param(google_reqId);
125 + fix_google_param(google_version);
126 + fix_google_param(responseHandler);
127 + fix_google_param(outFileName);
128 +
129 + RRDSET *st = NULL;
130 + ONEWAYALLOC *owa = onewayalloc_create(0);
131 + QUERY_TARGET *qt = NULL;
132 +
133 + if(!is_valid_sp(chart) && !is_valid_sp(context)) {
134 + buffer_sprintf(w->response.data, "No chart or context is given.");
135 + goto cleanup;
136 + }
137 +
138 + if(chart && !context) {
139 + // check if this is a specific chart
140 + st = rrdset_find(host, chart);
141 + if (!st) st = rrdset_find_byname(host, chart);
142 + }
143 +
144 + long long before = (before_str && *before_str)?str2l(before_str):0;
145 + long long after = (after_str && *after_str) ?str2l(after_str):-600;
146 + int points = (points_str && *points_str)?str2i(points_str):0;
147 + int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
148 + long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0;
149 +
150 + QUERY_TARGET_REQUEST qtr = {
151 + .version = 1,
152 + .after = after,
153 + .before = before,
154 + .host = host,
155 + .st = st,
156 + .nodes = NULL,
157 + .contexts = context,
158 + .instances = chart,
159 + .dimensions = (dimensions)?buffer_tostring(dimensions):NULL,
160 + .timeout_ms = timeout,
161 + .points = points,
162 + .format = format,
163 + .options = options,
164 + .time_group_method = group,
165 + .time_group_options = group_options,
166 + .resampling_time = group_time,
167 + .tier = tier,
168 + .chart_label_key = chart_label_key,
169 + .labels = chart_labels_filter,
170 + .query_source = QUERY_SOURCE_API_DATA,
171 + .priority = STORAGE_PRIORITY_NORMAL,
172 + .interrupt_callback = web_client_interrupt_callback,
173 + .interrupt_callback_data = w,
174 + .transaction = &w->transaction,
175 + };
176 + qt = query_target_create(&qtr);
177 +
178 + if(!qt || !qt->query.used) {
179 + buffer_sprintf(w->response.data, "No metrics where matched to query.");
180 + ret = HTTP_RESP_NOT_FOUND;
181 + goto cleanup;
182 + }
183 +
184 + web_client_timeout_checkpoint_set(w, timeout);
185 + if(web_client_timeout_checkpoint_and_check(w, NULL)) {
186 + ret = w->response.code;
187 + goto cleanup;
188 + }
189 +
190 + if(outFileName && *outFileName) {
191 + buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
192 + netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
193 + }
194 +
195 + if(format == DATASOURCE_DATATABLE_JSONP) {
196 + if(responseHandler == NULL)
197 + responseHandler = "google.visualization.Query.setResponse";
198 +
199 + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
200 + w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
201 + );
202 +
203 + buffer_sprintf(
204 + w->response.data,
205 + "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
206 + responseHandler,
207 + google_version,
208 + google_reqId,
209 + (int64_t)(st ? st->last_updated.tv_sec : 0));
210 + }
211 + else if(format == DATASOURCE_JSONP) {
212 + if(responseHandler == NULL)
213 + responseHandler = "callback";
214 +
215 + buffer_strcat(w->response.data, responseHandler);
216 + buffer_strcat(w->response.data, "(");
217 + }
218 +
219 + ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
220 +
221 + if(format == DATASOURCE_DATATABLE_JSONP) {
222 + if(google_timestamp < last_timestamp_in_data)
223 + buffer_strcat(w->response.data, "});");
224 +
225 + else {
226 + // the client already has the latest data
227 + buffer_flush(w->response.data);
228 + buffer_sprintf(w->response.data,
229 + "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
230 + responseHandler, google_version, google_reqId);
231 + }
232 + }
233 + else if(format == DATASOURCE_JSONP)
234 + buffer_strcat(w->response.data, ");");
235 +
236 + if(qt->internal.relative)
237 + buffer_no_cacheable(w->response.data);
238 + else
239 + buffer_cacheable(w->response.data);
240 +
241 +cleanup:
242 + query_target_release(qt);
243 + onewayalloc_destroy(owa);
244 + buffer_free(dimensions);
245 + return ret;
246 +}
src/web/api/v1/api_v1_dbengine.c new
+97
@@ -0,0 +1,97 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +#ifndef ENABLE_DBENGINE
6 +int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
7 + return HTTP_RESP_NOT_FOUND;
8 +}
9 +#else
10 +static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
11 + RRDENG_SIZE_STATS stats = rrdeng_size_statistics(multidb_ctx[tier]);
12 +
13 + buffer_sprintf(wb,
14 + "\n\t\t\"default_granularity_secs\":%zu"
15 + ",\n\t\t\"sizeof_datafile\":%zu"
16 + ",\n\t\t\"sizeof_page_in_cache\":%zu"
17 + ",\n\t\t\"sizeof_point_data\":%zu"
18 + ",\n\t\t\"sizeof_page_data\":%zu"
19 + ",\n\t\t\"pages_per_extent\":%zu"
20 + ",\n\t\t\"datafiles\":%zu"
21 + ",\n\t\t\"extents\":%zu"
22 + ",\n\t\t\"extents_pages\":%zu"
23 + ",\n\t\t\"points\":%zu"
24 + ",\n\t\t\"metrics\":%zu"
25 + ",\n\t\t\"metrics_pages\":%zu"
26 + ",\n\t\t\"extents_compressed_bytes\":%zu"
27 + ",\n\t\t\"pages_uncompressed_bytes\":%zu"
28 + ",\n\t\t\"pages_duration_secs\":%lld"
29 + ",\n\t\t\"single_point_pages\":%zu"
30 + ",\n\t\t\"first_t\":%ld"
31 + ",\n\t\t\"last_t\":%ld"
32 + ",\n\t\t\"database_retention_secs\":%lld"
33 + ",\n\t\t\"average_compression_savings\":%0.2f"
34 + ",\n\t\t\"average_point_duration_secs\":%0.2f"
35 + ",\n\t\t\"average_metric_retention_secs\":%0.2f"
36 + ",\n\t\t\"ephemeral_metrics_per_day_percent\":%0.2f"
37 + ",\n\t\t\"average_page_size_bytes\":%0.2f"
38 + ",\n\t\t\"estimated_concurrently_collected_metrics\":%zu"
39 + ",\n\t\t\"currently_collected_metrics\":%zu"
40 + ",\n\t\t\"disk_space\":%zu"
41 + ",\n\t\t\"max_disk_space\":%zu"
42 + , stats.default_granularity_secs
43 + , stats.sizeof_datafile
44 + , stats.sizeof_page_in_cache
45 + , stats.sizeof_point_data
46 + , stats.sizeof_page_data
47 + , stats.pages_per_extent
48 + , stats.datafiles
49 + , stats.extents
50 + , stats.extents_pages
51 + , stats.points
52 + , stats.metrics
53 + , stats.metrics_pages
54 + , stats.extents_compressed_bytes
55 + , stats.pages_uncompressed_bytes
56 + , (long long)stats.pages_duration_secs
57 + , stats.single_point_pages
58 + , stats.first_time_s
59 + , stats.last_time_s
60 + , (long long)stats.database_retention_secs
61 + , stats.average_compression_savings
62 + , stats.average_point_duration_secs
63 + , stats.average_metric_retention_secs
64 + , stats.ephemeral_metrics_per_day_percent
65 + , stats.average_page_size_bytes
66 + , stats.estimated_concurrently_collected_metrics
67 + , stats.currently_collected_metrics
68 + , stats.disk_space
69 + , stats.max_disk_space
70 + );
71 +}
72 +
73 +int api_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
74 + if (!netdata_ready)
75 + return HTTP_RESP_SERVICE_UNAVAILABLE;
76 +
77 + BUFFER *wb = w->response.data;
78 + buffer_flush(wb);
79 +
80 + if(!dbengine_enabled) {
81 + buffer_strcat(wb, "dbengine is not enabled");
82 + return HTTP_RESP_NOT_FOUND;
83 + }
84 +
85 + wb->content_type = CT_APPLICATION_JSON;
86 + buffer_no_cacheable(wb);
87 + buffer_strcat(wb, "{");
88 + for(size_t tier = 0; tier < storage_tiers ;tier++) {
89 + buffer_sprintf(wb, "%s\n\t\"tier%zu\": {", tier?",":"", tier);
90 + web_client_api_v1_dbengine_stats_for_tier(wb, tier);
91 + buffer_strcat(wb, "\n\t}");
92 + }
93 + buffer_strcat(wb, "\n}");
94 +
95 + return HTTP_RESP_OK;
96 +}
97 +#endif
src/web/api/v1/api_v1_function.c new
+44
@@ -0,0 +1,44 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_function(RRDHOST *host, struct web_client *w, char *url) {
6 + if (!netdata_ready)
7 + return HTTP_RESP_SERVICE_UNAVAILABLE;
8 +
9 + int timeout = 0;
10 + const char *function = NULL;
11 +
12 + while (url) {
13 + char *value = strsep_skip_consecutive_separators(&url, "&");
14 + if (!value || !*value)
15 + continue;
16 +
17 + char *name = strsep_skip_consecutive_separators(&value, "=");
18 + if (!name || !*name)
19 + continue;
20 +
21 + if (!strcmp(name, "function"))
22 + function = value;
23 +
24 + else if (!strcmp(name, "timeout"))
25 + timeout = (int) strtoul(value, NULL, 0);
26 + }
27 +
28 + BUFFER *wb = w->response.data;
29 + buffer_flush(wb);
30 + wb->content_type = CT_APPLICATION_JSON;
31 + buffer_no_cacheable(wb);
32 +
33 + char transaction[UUID_COMPACT_STR_LEN];
34 + uuid_unparse_lower_compact(w->transaction, transaction);
35 +
36 + CLEAN_BUFFER *source = buffer_create(100, NULL);
37 + web_client_api_request_vX_source_to_buffer(w, source);
38 +
39 + return rrd_function_run(host, wb, timeout, w->access, function, true, transaction,
40 + NULL, NULL,
41 + web_client_progress_functions_update, w,
42 + web_client_interrupt_callback, w, NULL,
43 + buffer_tostring(source), false);
44 +}
src/web/api/v1/api_v1_functions.c new
+19
@@ -0,0 +1,19 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_functions(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
6 + if (!netdata_ready)
7 + return HTTP_RESP_SERVICE_UNAVAILABLE;
8 +
9 + BUFFER *wb = w->response.data;
10 + buffer_flush(wb);
11 + wb->content_type = CT_APPLICATION_JSON;
12 + buffer_no_cacheable(wb);
13 +
14 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
15 + host_functions2json(host, wb);
16 + buffer_json_finalize(wb);
17 +
18 + return HTTP_RESP_OK;
19 +}
src/web/api/v1/api_v1_info.c new
+207
@@ -0,0 +1,207 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +static void host_collectors(RRDHOST *host, BUFFER *wb) {
6 + buffer_json_member_add_array(wb, "collectors");
7 +
8 + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
9 + RRDSET *st;
10 + char name[500];
11 +
12 + time_t now = now_realtime_sec();
13 +
14 + rrdset_foreach_read(st, host) {
15 + if (!rrdset_is_available_for_viewers(st))
16 + continue;
17 +
18 + sprintf(name, "%s:%s", rrdset_plugin_name(st), rrdset_module_name(st));
19 +
20 + bool old = 0;
21 + bool *set = dictionary_set(dict, name, &old, sizeof(bool));
22 + if(!*set) {
23 + *set = true;
24 + st->last_accessed_time_s = now;
25 + buffer_json_add_array_item_object(wb);
26 + buffer_json_member_add_string(wb, "plugin", rrdset_plugin_name(st));
27 + buffer_json_member_add_string(wb, "module", rrdset_module_name(st));
28 + buffer_json_object_close(wb);
29 + }
30 + }
31 + rrdset_foreach_done(st);
32 + dictionary_destroy(dict);
33 +
34 + buffer_json_array_close(wb);
35 +}
36 +
37 +static inline void web_client_api_request_v1_info_mirrored_hosts_status(BUFFER *wb, RRDHOST *host) {
38 + buffer_json_add_array_item_object(wb);
39 +
40 + buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
41 + buffer_json_member_add_uint64(wb, "hops", host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1);
42 + buffer_json_member_add_boolean(wb, "reachable", (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)));
43 +
44 + buffer_json_member_add_string(wb, "guid", host->machine_guid);
45 + buffer_json_member_add_uuid(wb, "node_id", host->node_id);
46 + CLAIM_ID claim_id = rrdhost_claim_id_get(host);
47 + buffer_json_member_add_string(wb, "claim_id", claim_id_is_set(claim_id) ? claim_id.str : NULL);
48 +
49 + buffer_json_object_close(wb);
50 +}
51 +
52 +static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) {
53 + RRDHOST *host;
54 +
55 + rrd_rdlock();
56 +
57 + buffer_json_member_add_array(wb, "mirrored_hosts");
58 + rrdhost_foreach_read(host)
59 + buffer_json_add_array_item_string(wb, rrdhost_hostname(host));
60 + buffer_json_array_close(wb);
61 +
62 + buffer_json_member_add_array(wb, "mirrored_hosts_status");
63 + rrdhost_foreach_read(host) {
64 + if ((host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
65 + web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
66 + }
67 + }
68 + rrdhost_foreach_read(host) {
69 + if ((host != localhost && rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
70 + web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
71 + }
72 + }
73 + buffer_json_array_close(wb);
74 +
75 + rrd_rdunlock();
76 +}
77 +
78 +static void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key) {
79 + buffer_json_member_add_object(wb, key);
80 +
81 + size_t normal = 0, warning = 0, critical = 0;
82 + RRDCALC *rc;
83 + foreach_rrdcalc_in_rrdhost_read(host, rc) {
84 + if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
85 + continue;
86 +
87 + switch(rc->status) {
88 + case RRDCALC_STATUS_WARNING:
89 + warning++;
90 + break;
91 + case RRDCALC_STATUS_CRITICAL:
92 + critical++;
93 + break;
94 + default:
95 + normal++;
96 + }
97 + }
98 + foreach_rrdcalc_in_rrdhost_done(rc);
99 +
100 + buffer_json_member_add_uint64(wb, "normal", normal);
101 + buffer_json_member_add_uint64(wb, "warning", warning);
102 + buffer_json_member_add_uint64(wb, "critical", critical);
103 +
104 + buffer_json_object_close(wb);
105 +}
106 +
107 +static int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) {
108 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
109 +
110 + buffer_json_member_add_string(wb, "version", rrdhost_program_version(host));
111 + buffer_json_member_add_string(wb, "uid", host->machine_guid);
112 +
113 + buffer_json_member_add_uint64(wb, "hosts-available", rrdhost_hosts_available());
114 + web_client_api_request_v1_info_mirrored_hosts(wb);
115 +
116 + web_client_api_request_v1_info_summary_alarm_statuses(host, wb, "alarms");
117 +
118 + buffer_json_member_add_string_or_empty(wb, "os_name", host->system_info->host_os_name);
119 + buffer_json_member_add_string_or_empty(wb, "os_id", host->system_info->host_os_id);
120 + buffer_json_member_add_string_or_empty(wb, "os_id_like", host->system_info->host_os_id_like);
121 + buffer_json_member_add_string_or_empty(wb, "os_version", host->system_info->host_os_version);
122 + buffer_json_member_add_string_or_empty(wb, "os_version_id", host->system_info->host_os_version_id);
123 + buffer_json_member_add_string_or_empty(wb, "os_detection", host->system_info->host_os_detection);
124 + buffer_json_member_add_string_or_empty(wb, "cores_total", host->system_info->host_cores);
125 + buffer_json_member_add_string_or_empty(wb, "total_disk_space", host->system_info->host_disk_space);
126 + buffer_json_member_add_string_or_empty(wb, "cpu_freq", host->system_info->host_cpu_freq);
127 + buffer_json_member_add_string_or_empty(wb, "ram_total", host->system_info->host_ram_total);
128 +
129 + buffer_json_member_add_string_or_omit(wb, "container_os_name", host->system_info->container_os_name);
130 + buffer_json_member_add_string_or_omit(wb, "container_os_id", host->system_info->container_os_id);
131 + buffer_json_member_add_string_or_omit(wb, "container_os_id_like", host->system_info->container_os_id_like);
132 + buffer_json_member_add_string_or_omit(wb, "container_os_version", host->system_info->container_os_version);
133 + buffer_json_member_add_string_or_omit(wb, "container_os_version_id", host->system_info->container_os_version_id);
134 + buffer_json_member_add_string_or_omit(wb, "container_os_detection", host->system_info->container_os_detection);
135 + buffer_json_member_add_string_or_omit(wb, "is_k8s_node", host->system_info->is_k8s_node);
136 +
137 + buffer_json_member_add_string_or_empty(wb, "kernel_name", host->system_info->kernel_name);
138 + buffer_json_member_add_string_or_empty(wb, "kernel_version", host->system_info->kernel_version);
139 + buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture);
140 + buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization);
141 + buffer_json_member_add_string_or_empty(wb, "virt_detection", host->system_info->virt_detection);
142 + buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container);
143 + buffer_json_member_add_string_or_empty(wb, "container_detection", host->system_info->container_detection);
144 +
145 + buffer_json_member_add_string_or_omit(wb, "cloud_provider_type", host->system_info->cloud_provider_type);
146 + buffer_json_member_add_string_or_omit(wb, "cloud_instance_type", host->system_info->cloud_instance_type);
147 + buffer_json_member_add_string_or_omit(wb, "cloud_instance_region", host->system_info->cloud_instance_region);
148 +
149 + host_labels2json(host, wb, "host_labels");
150 + host_functions2json(host, wb);
151 + host_collectors(host, wb);
152 +
153 + buffer_json_member_add_boolean(wb, "cloud-enabled", true);
154 + buffer_json_member_add_boolean(wb, "cloud-available", true);
155 + buffer_json_member_add_boolean(wb, "agent-claimed", is_agent_claimed());
156 + buffer_json_member_add_boolean(wb, "aclk-available", aclk_online());
157 +
158 + buffer_json_member_add_string(wb, "memory-mode", rrd_memory_mode_name(host->rrd_memory_mode));
159 +#ifdef ENABLE_DBENGINE
160 + buffer_json_member_add_uint64(wb, "multidb-disk-quota", default_multidb_disk_quota_mb);
161 + buffer_json_member_add_uint64(wb, "page-cache-size", default_rrdeng_page_cache_mb);
162 +#endif // ENABLE_DBENGINE
163 + buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE);
164 + buffer_json_member_add_boolean(wb, "stream-enabled", default_rrdpush_enabled);
165 +
166 + buffer_json_member_add_boolean(wb, "stream-compression",
167 + host->sender && host->sender->compressor.initialized);
168 +
169 + buffer_json_member_add_boolean(wb, "https-enabled", true);
170 +
171 + buffer_json_member_add_quoted_string(wb, "buildinfo", analytics_data.netdata_buildinfo);
172 + buffer_json_member_add_quoted_string(wb, "release-channel", analytics_data.netdata_config_release_channel);
173 + buffer_json_member_add_quoted_string(wb, "notification-methods", analytics_data.netdata_notification_methods);
174 +
175 + buffer_json_member_add_boolean(wb, "exporting-enabled", analytics_data.exporting_enabled);
176 + buffer_json_member_add_quoted_string(wb, "exporting-connectors", analytics_data.netdata_exporting_connectors);
177 +
178 + buffer_json_member_add_uint64(wb, "allmetrics-prometheus-used", analytics_data.prometheus_hits);
179 + buffer_json_member_add_uint64(wb, "allmetrics-shell-used", analytics_data.shell_hits);
180 + buffer_json_member_add_uint64(wb, "allmetrics-json-used", analytics_data.json_hits);
181 + buffer_json_member_add_uint64(wb, "dashboard-used", analytics_data.dashboard_hits);
182 +
183 + buffer_json_member_add_uint64(wb, "charts-count", analytics_data.charts_count);
184 + buffer_json_member_add_uint64(wb, "metrics-count", analytics_data.metrics_count);
185 +
186 +#if defined(ENABLE_ML)
187 + buffer_json_member_add_object(wb, "ml-info");
188 + ml_host_get_info(host, wb);
189 + buffer_json_object_close(wb);
190 +#endif
191 +
192 + buffer_json_finalize(wb);
193 + return 0;
194 +}
195 +
196 +int api_v1_info(RRDHOST *host, struct web_client *w, char *url) {
197 + (void)url;
198 + if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
199 + BUFFER *wb = w->response.data;
200 + buffer_flush(wb);
201 + wb->content_type = CT_APPLICATION_JSON;
202 +
203 + web_client_api_request_v1_info_fill_buffer(host, wb);
204 +
205 + buffer_no_cacheable(wb);
206 + return HTTP_RESP_OK;
207 +}
src/web/api/v1/api_v1_manage.c new
+86
@@ -0,0 +1,86 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +char *api_secret;
6 +
7 +static char *get_mgmt_api_key(void) {
8 + char filename[FILENAME_MAX + 1];
9 + snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir);
10 + char *api_key_filename=config_get(CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
11 + static char guid[GUID_LEN + 1] = "";
12 +
13 + if(likely(guid[0]))
14 + return guid;
15 +
16 + // read it from disk
17 + int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC);
18 + if(fd != -1) {
19 + char buf[GUID_LEN + 1];
20 + if(read(fd, buf, GUID_LEN) != GUID_LEN)
21 + netdata_log_error("Failed to read management API key from '%s'", api_key_filename);
22 + else {
23 + buf[GUID_LEN] = '\0';
24 + if(regenerate_guid(buf, guid) == -1) {
25 + netdata_log_error("Failed to validate management API key '%s' from '%s'.",
26 + buf, api_key_filename);
27 +
28 + guid[0] = '\0';
29 + }
30 + }
31 + close(fd);
32 + }
33 +
34 + // generate a new one?
35 + if(!guid[0]) {
36 + nd_uuid_t uuid;
37 +
38 + uuid_generate_time(uuid);
39 + uuid_unparse_lower(uuid, guid);
40 + guid[GUID_LEN] = '\0';
41 +
42 + // save it
43 + fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC | O_CLOEXEC, 444);
44 + if(fd == -1) {
45 + 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);
46 + goto temp_key;
47 + }
48 +
49 + if(write(fd, guid, GUID_LEN) != GUID_LEN) {
50 + 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);
51 + close(fd);
52 + goto temp_key;
53 + }
54 +
55 + close(fd);
56 + }
57 +
58 + return guid;
59 +
60 +temp_key:
61 + netdata_log_info("You can still continue to use the alarm management API using the authorization token %s during this Netdata session only.", guid);
62 + return guid;
63 +}
64 +
65 +void api_v1_management_init(void) {
66 + api_secret = get_mgmt_api_key();
67 +}
68 +
69 +#define HLT_MGM "manage/health"
70 +int api_v1_manage(RRDHOST *host, struct web_client *w, char *url) {
71 + const char *haystack = buffer_tostring(w->url_path_decoded);
72 + char *needle;
73 +
74 + buffer_flush(w->response.data);
75 +
76 + if ((needle = strstr(haystack, HLT_MGM)) == NULL) {
77 + buffer_strcat(w->response.data, "Invalid management request. Curently only 'health' is supported.");
78 + return HTTP_RESP_NOT_FOUND;
79 + }
80 + needle += strlen(HLT_MGM);
81 + if (*needle != '\0') {
82 + buffer_strcat(w->response.data, "Invalid management request. Currently only 'health' is supported.");
83 + return HTTP_RESP_NOT_FOUND;
84 + }
85 + return web_client_api_request_v1_mgmt_health(host, w, url);
86 +}
src/web/api/v1/api_v1_ml_info.c new
+27
@@ -0,0 +1,27 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_ml_info(RRDHOST *host, struct web_client *w, char *url) {
6 + (void) url;
7 +#if defined(ENABLE_ML)
8 +
9 + if (!netdata_ready)
10 + return HTTP_RESP_SERVICE_UNAVAILABLE;
11 +
12 + BUFFER *wb = w->response.data;
13 + buffer_flush(wb);
14 + wb->content_type = CT_APPLICATION_JSON;
15 +
16 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
17 + ml_host_get_detection_info(host, wb);
18 + buffer_json_finalize(wb);
19 +
20 + buffer_no_cacheable(wb);
21 +
22 + return HTTP_RESP_OK;
23 +#else
24 + return HTTP_RESP_SERVICE_UNAVAILABLE;
25 +#endif // ENABLE_ML
26 +}
27 +
src/web/api/v1/api_v1_registry.c new
+198
@@ -0,0 +1,198 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +// Pings a netdata server:
6 +// /api/v1/registry?action=hello
7 +//
8 +// Access to a netdata registry:
9 +// /api/v1/registry?action=access&machine=${machine_guid}&name=${hostname}&url=${url}
10 +//
11 +// Delete from a netdata registry:
12 +// /api/v1/registry?action=delete&machine=${machine_guid}&name=${hostname}&url=${url}&delete_url=${delete_url}
13 +//
14 +// Search for the URLs of a machine:
15 +// /api/v1/registry?action=search&for=${machine_guid}
16 +//
17 +// Impersonate:
18 +// /api/v1/registry?action=switch&machine=${machine_guid}&name=${hostname}&url=${url}&to=${new_person_guid}
19 +int api_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
20 + static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
21 + hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
22 + hash_to = 0 /*, hash_redirects = 0 */;
23 +
24 + if(unlikely(!hash_action)) {
25 + hash_action = simple_hash("action");
26 + hash_access = simple_hash("access");
27 + hash_hello = simple_hash("hello");
28 + hash_delete = simple_hash("delete");
29 + hash_search = simple_hash("search");
30 + hash_switch = simple_hash("switch");
31 + hash_machine = simple_hash("machine");
32 + hash_url = simple_hash("url");
33 + hash_name = simple_hash("name");
34 + hash_delete_url = simple_hash("delete_url");
35 + hash_for = simple_hash("for");
36 + hash_to = simple_hash("to");
37 + /*
38 + hash_redirects = simple_hash("redirects");
39 +*/
40 + }
41 +
42 + netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
43 +
44 + // TODO
45 + // The browser may send multiple cookies with our id
46 +
47 + char person_guid[UUID_STR_LEN] = "";
48 + char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
49 + if(cookie)
50 + strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], UUID_STR_LEN - 1);
51 + else if(!extract_bearer_token_from_request(w, person_guid, sizeof(person_guid)))
52 + person_guid[0] = '\0';
53 +
54 + char action = '\0';
55 + char *machine_guid = NULL,
56 + *machine_url = NULL,
57 + *url_name = NULL,
58 + *search_machine_guid = NULL,
59 + *delete_url = NULL,
60 + *to_person_guid = NULL;
61 + /*
62 + int redirects = 0;
63 +*/
64 +
65 + // Don't cache registry responses
66 + buffer_no_cacheable(w->response.data);
67 +
68 + while(url) {
69 + char *value = strsep_skip_consecutive_separators(&url, "&");
70 + if (!value || !*value) continue;
71 +
72 + char *name = strsep_skip_consecutive_separators(&value, "=");
73 + if (!name || !*name) continue;
74 + if (!value || !*value) continue;
75 +
76 + netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
77 +
78 + uint32_t hash = simple_hash(name);
79 +
80 + if(hash == hash_action && !strcmp(name, "action")) {
81 + uint32_t vhash = simple_hash(value);
82 +
83 + if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
84 + else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
85 + else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
86 + else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
87 + else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
88 +#ifdef NETDATA_INTERNAL_CHECKS
89 + else netdata_log_error("unknown registry action '%s'", value);
90 +#endif /* NETDATA_INTERNAL_CHECKS */
91 + }
92 + /*
93 + else if(hash == hash_redirects && !strcmp(name, "redirects"))
94 + redirects = atoi(value);
95 +*/
96 + else if(hash == hash_machine && !strcmp(name, "machine"))
97 + machine_guid = value;
98 +
99 + else if(hash == hash_url && !strcmp(name, "url"))
100 + machine_url = value;
101 +
102 + else if(action == 'A') {
103 + if(hash == hash_name && !strcmp(name, "name"))
104 + url_name = value;
105 + }
106 + else if(action == 'D') {
107 + if(hash == hash_delete_url && !strcmp(name, "delete_url"))
108 + delete_url = value;
109 + }
110 + else if(action == 'S') {
111 + if(hash == hash_for && !strcmp(name, "for"))
112 + search_machine_guid = value;
113 + }
114 + else if(action == 'W') {
115 + if(hash == hash_to && !strcmp(name, "to"))
116 + to_person_guid = value;
117 + }
118 +#ifdef NETDATA_INTERNAL_CHECKS
119 + else netdata_log_error("unused registry URL parameter '%s' with value '%s'", name, value);
120 +#endif /* NETDATA_INTERNAL_CHECKS */
121 + }
122 +
123 + bool do_not_track = respect_web_browser_do_not_track_policy && web_client_has_donottrack(w);
124 +
125 + if(unlikely(action == 'H')) {
126 + // HELLO request, dashboard ACL
127 + analytics_log_dashboard();
128 + if(unlikely(!http_can_access_dashboard(w)))
129 + return web_client_permission_denied_acl(w);
130 + }
131 + else {
132 + // everything else, registry ACL
133 + if(unlikely(!http_can_access_registry(w)))
134 + return web_client_permission_denied_acl(w);
135 +
136 + if(unlikely(do_not_track)) {
137 + buffer_flush(w->response.data);
138 + buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work.");
139 + return HTTP_RESP_BAD_REQUEST;
140 + }
141 + }
142 +
143 + buffer_no_cacheable(w->response.data);
144 +
145 + switch(action) {
146 + case 'A':
147 + if(unlikely(!machine_guid || !machine_url || !url_name)) {
148 + 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");
149 + buffer_flush(w->response.data);
150 + buffer_strcat(w->response.data, "Invalid registry Access request.");
151 + return HTTP_RESP_BAD_REQUEST;
152 + }
153 +
154 + web_client_enable_tracking_required(w);
155 + return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
156 +
157 + case 'D':
158 + if(unlikely(!machine_guid || !machine_url || !delete_url)) {
159 + 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");
160 + buffer_flush(w->response.data);
161 + buffer_strcat(w->response.data, "Invalid registry Delete request.");
162 + return HTTP_RESP_BAD_REQUEST;
163 + }
164 +
165 + web_client_enable_tracking_required(w);
166 + return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
167 +
168 + case 'S':
169 + if(unlikely(!search_machine_guid)) {
170 + netdata_log_error("Invalid registry request - search requires these parameters: for ('%s')", search_machine_guid?search_machine_guid:"UNSET");
171 + buffer_flush(w->response.data);
172 + buffer_strcat(w->response.data, "Invalid registry Search request.");
173 + return HTTP_RESP_BAD_REQUEST;
174 + }
175 +
176 + web_client_enable_tracking_required(w);
177 + return registry_request_search_json(host, w, person_guid, search_machine_guid);
178 +
179 + case 'W':
180 + if(unlikely(!machine_guid || !machine_url || !to_person_guid)) {
181 + 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");
182 + buffer_flush(w->response.data);
183 + buffer_strcat(w->response.data, "Invalid registry Switch request.");
184 + return HTTP_RESP_BAD_REQUEST;
185 + }
186 +
187 + web_client_enable_tracking_required(w);
188 + return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
189 +
190 + case 'H':
191 + return registry_request_hello_json(host, w, do_not_track);
192 +
193 + default:
194 + buffer_flush(w->response.data);
195 + buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
196 + return HTTP_RESP_BAD_REQUEST;
197 + }
198 +}
src/web/api/v1/api_v1_weights.c new
+11
@@ -0,0 +1,11 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v1_calls.h"
4 +
5 +int api_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url) {
6 + return web_client_api_request_weights(host, w, url, default_metric_correlations_method, WEIGHTS_FORMAT_CHARTS, 1);
7 +}
8 +
9 +int api_v1_weights(RRDHOST *host, struct web_client *w, char *url) {
10 + return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_ANOMALY_RATE, WEIGHTS_FORMAT_CONTEXTS, 1);
11 +}
src/web/api/v2/api_v2_alert_config.c new
+32
@@ -0,0 +1,32 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_alert_config(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + const char *config = NULL;
7 +
8 + while(url) {
9 + char *value = strsep_skip_consecutive_separators(&url, "&");
10 + if(!value || !*value) continue;
11 +
12 + char *name = strsep_skip_consecutive_separators(&value, "=");
13 + if(!name || !*name) continue;
14 + if(!value || !*value) continue;
15 +
16 + // name and value are now the parameters
17 + // they are not null and not empty
18 +
19 + if(!strcmp(name, "config"))
20 + config = value;
21 + }
22 +
23 + buffer_flush(w->response.data);
24 +
25 + if(!config) {
26 + w->response.data->content_type = CT_TEXT_PLAIN;
27 + buffer_strcat(w->response.data, "A config hash ID is required. Add ?config=UUID query param");
28 + return HTTP_RESP_BAD_REQUEST;
29 + }
30 +
31 + return contexts_v2_alert_config_to_json(w, config);
32 +}
src/web/api/v2/api_v2_alert_transitions.c new
+7
@@ -0,0 +1,7 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_alert_transitions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERT_TRANSITIONS | CONTEXTS_V2_NODES);
7 +}
src/web/api/v2/api_v2_alerts.c new
+7
@@ -0,0 +1,7 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_alerts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERTS | CONTEXTS_V2_NODES);
7 +}
src/web/api/v2/api_v2_bearer.c new
+139
@@ -0,0 +1,139 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +static bool verify_host_uuids(RRDHOST *host, const char *machine_guid, const char *node_id) {
6 + if(!machine_guid || !node_id)
7 + return false;
8 +
9 + if(strcmp(machine_guid, host->machine_guid) != 0)
10 + return false;
11 +
12 + if(uuid_is_null(host->node_id))
13 + return false;
14 +
15 + char buf[UUID_STR_LEN];
16 + uuid_unparse_lower(host->node_id, buf);
17 +
18 + return strcmp(node_id, buf) == 0;
19 +}
20 +
21 +int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url) {
22 + char *machine_guid = NULL;
23 + char *claim_id = NULL;
24 + char *node_id = NULL;
25 + bool protection = netdata_is_protected_by_bearer;
26 +
27 + while (url) {
28 + char *value = strsep_skip_consecutive_separators(&url, "&");
29 + if (!value || !*value) continue;
30 +
31 + char *name = strsep_skip_consecutive_separators(&value, "=");
32 + if (!name || !*name) continue;
33 + if (!value || !*value) continue;
34 +
35 + if(!strcmp(name, "bearer_protection")) {
36 + if(!strcmp(value, "on") || !strcmp(value, "true") || !strcmp(value, "yes"))
37 + protection = true;
38 + else
39 + protection = false;
40 + }
41 + else if(!strcmp(name, "machine_guid"))
42 + machine_guid = value;
43 + else if(!strcmp(name, "claim_id"))
44 + claim_id = value;
45 + else if(!strcmp(name, "node_id"))
46 + node_id = value;
47 + }
48 +
49 + if(!claim_id_matches(claim_id)) {
50 + buffer_reset(w->response.data);
51 + buffer_strcat(w->response.data, "The request is for a different claimed agent");
52 + return HTTP_RESP_BAD_REQUEST;
53 + }
54 +
55 + if(!verify_host_uuids(localhost, machine_guid, node_id)) {
56 + buffer_reset(w->response.data);
57 + buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
58 + return HTTP_RESP_BAD_REQUEST;
59 + }
60 +
61 + netdata_is_protected_by_bearer = protection;
62 +
63 + BUFFER *wb = w->response.data;
64 + buffer_reset(wb);
65 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
66 + buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
67 + buffer_json_finalize(wb);
68 +
69 + return HTTP_RESP_OK;
70 +}
71 +
72 +int bearer_get_token_json_response(BUFFER *wb, RRDHOST *host, const char *claim_id, const char *machine_guid, const char *node_id, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name) {
73 + if(!claim_id_matches_any(claim_id))
74 + return rrd_call_function_error(wb, "The request is for a different claimed agent", HTTP_RESP_BAD_REQUEST);
75 +
76 + if(!verify_host_uuids(host, machine_guid, node_id))
77 + return rrd_call_function_error(wb, "The request is missing or not matching local UUIDs", HTTP_RESP_BAD_REQUEST);
78 +
79 + nd_uuid_t uuid;
80 + time_t expires_s = bearer_create_token(&uuid, user_role, access, cloud_account_id, client_name);
81 +
82 + buffer_reset(wb);
83 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
84 + buffer_json_member_add_int64(wb, "status", HTTP_RESP_OK);
85 + buffer_json_member_add_string(wb, "mg", host->machine_guid);
86 + buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
87 + buffer_json_member_add_uuid(wb, "token", uuid);
88 + buffer_json_member_add_time_t(wb, "expiration", expires_s);
89 + buffer_json_finalize(wb);
90 + return HTTP_RESP_OK;
91 +}
92 +
93 +int api_v2_bearer_get_token(RRDHOST *host, struct web_client *w, char *url) {
94 + char *machine_guid = NULL;
95 + char *claim_id = NULL;
96 + char *node_id = NULL;
97 +
98 + while(url) {
99 + char *value = strsep_skip_consecutive_separators(&url, "&");
100 + if (!value || !*value) continue;
101 +
102 + char *name = strsep_skip_consecutive_separators(&value, "=");
103 + if (!name || !*name) continue;
104 + if (!value || !*value) continue;
105 +
106 + if(!strcmp(name, "machine_guid"))
107 + machine_guid = value;
108 + else if(!strcmp(name, "claim_id"))
109 + claim_id = value;
110 + else if(!strcmp(name, "node_id"))
111 + node_id = value;
112 + }
113 +
114 + if(!claim_id_matches(claim_id)) {
115 + buffer_reset(w->response.data);
116 + buffer_strcat(w->response.data, "The request is for a different claimed agent");
117 + return HTTP_RESP_BAD_REQUEST;
118 + }
119 +
120 + if(!verify_host_uuids(host, machine_guid, node_id)) {
121 + buffer_reset(w->response.data);
122 + buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
123 + return HTTP_RESP_BAD_REQUEST;
124 + }
125 +
126 + if(host != localhost)
127 + return call_function_bearer_get_token(host, w, claim_id, machine_guid, node_id);
128 +
129 + return bearer_get_token_json_response(
130 + w->response.data,
131 + host,
132 + claim_id,
133 + machine_guid,
134 + node_id,
135 + w->user_role,
136 + w->access,
137 + w->auth.cloud_account_id,
138 + w->auth.client_name);
139 +}
src/web/api/v2/api_v2_calls.h new
+37
@@ -0,0 +1,37 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_API_V2_CALLS_H
4 +#define NETDATA_API_V2_CALLS_H
5 +
6 +#include "../web_api_v2.h"
7 +
8 +int api_v2_info(RRDHOST *host, struct web_client *w, char *url);
9 +
10 +int api_v2_data(RRDHOST *host, struct web_client *w, char *url);
11 +int api_v2_weights(RRDHOST *host, struct web_client *w, char *url);
12 +
13 +int api_v2_alert_config(RRDHOST *host, struct web_client *w, char *url);
14 +
15 +int api_v2_contexts_internal(RRDHOST *host, struct web_client *w, char *url, CONTEXTS_V2_MODE mode);
16 +int api_v2_contexts(RRDHOST *host, struct web_client *w, char *url);
17 +int api_v2_alert_transitions(RRDHOST *host, struct web_client *w, char *url);
18 +int api_v2_alerts(RRDHOST *host, struct web_client *w, char *url);
19 +int api_v2_functions(RRDHOST *host, struct web_client *w, char *url);
20 +int api_v2_versions(RRDHOST *host, struct web_client *w, char *url);
21 +int api_v2_q(RRDHOST *host, struct web_client *w, char *url);
22 +int api_v2_nodes(RRDHOST *host, struct web_client *w, char *url);
23 +int api_v2_node_instances(RRDHOST *host, struct web_client *w, char *url);
24 +
25 +int api_v2_ilove(RRDHOST *host, struct web_client *w, char *url);
26 +
27 +int api_v2_claim(RRDHOST *host, struct web_client *w, char *url);
28 +
29 +int api_v2_webrtc(RRDHOST *host, struct web_client *w, char *url);
30 +
31 +int api_v2_progress(RRDHOST *host, struct web_client *w, char *url);
32 +
33 +int api_v2_bearer_get_token(RRDHOST *host, struct web_client *w, char *url);
34 +int bearer_get_token_json_response(BUFFER *wb, RRDHOST *host, const char *claim_id, const char *machine_guid, const char *node_id, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name);
35 +int api_v2_bearer_protection(RRDHOST *host, struct web_client *w, char *url);
36 +
37 +#endif //NETDATA_API_V2_CALLS_H
src/web/api/v2/api_v2_claim.c new
+212
@@ -0,0 +1,212 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +#include "claim/claim.h"
5 +
6 +#if defined(OS_WINDOWS)
7 +#include <windows.h>
8 +#include <sys/cygwin.h>
9 +#endif
10 +
11 +static char *netdata_random_session_id_filename = NULL;
12 +static nd_uuid_t netdata_random_session_id = { 0 };
13 +
14 +bool netdata_random_session_id_generate(void) {
15 + static char guid[UUID_STR_LEN] = "";
16 +
17 + uuid_generate_random(netdata_random_session_id);
18 + uuid_unparse_lower(netdata_random_session_id, guid);
19 +
20 + char filename[FILENAME_MAX + 1];
21 + snprintfz(filename, FILENAME_MAX, "%s/netdata_random_session_id", netdata_configured_varlib_dir);
22 +
23 + bool ret = true;
24 +
25 + (void)unlink(filename);
26 +
27 + // save it
28 + int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 640);
29 + if(fd == -1) {
30 + netdata_log_error("Cannot create random session id file '%s'.", filename);
31 + ret = false;
32 + }
33 + else {
34 + if (write(fd, guid, UUID_STR_LEN - 1) != UUID_STR_LEN - 1) {
35 + netdata_log_error("Cannot write the random session id file '%s'.", filename);
36 + ret = false;
37 + } else {
38 + ssize_t bytes = write(fd, "\n", 1);
39 + UNUSED(bytes);
40 + }
41 + close(fd);
42 + }
43 +
44 + if(ret && (!netdata_random_session_id_filename || strcmp(netdata_random_session_id_filename, filename) != 0)) {
45 + freez(netdata_random_session_id_filename);
46 + netdata_random_session_id_filename = strdupz(filename);
47 + }
48 +
49 + return ret;
50 +}
51 +
52 +static const char *netdata_random_session_id_get_filename(void) {
53 + if(!netdata_random_session_id_filename)
54 + netdata_random_session_id_generate();
55 +
56 + return netdata_random_session_id_filename;
57 +}
58 +
59 +static bool netdata_random_session_id_matches(const char *guid) {
60 + if(uuid_is_null(netdata_random_session_id))
61 + return false;
62 +
63 + nd_uuid_t uuid;
64 +
65 + if(uuid_parse(guid, uuid))
66 + return false;
67 +
68 + if(uuid_compare(netdata_random_session_id, uuid) == 0)
69 + return true;
70 +
71 + return false;
72 +}
73 +
74 +static bool check_claim_param(const char *s) {
75 + if(!s || !*s) return true;
76 +
77 + do {
78 + if(isalnum((uint8_t)*s) || *s == '.' || *s == ',' || *s == '-' || *s == ':' || *s == '/' || *s == '_')
79 + ;
80 + else
81 + return false;
82 +
83 + } while(*++s);
84 +
85 + return true;
86 +}
87 +
88 +int api_v2_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
89 + char *key = NULL;
90 + char *token = NULL;
91 + char *rooms = NULL;
92 + char *base_url = NULL;
93 +
94 + while (url) {
95 + char *value = strsep_skip_consecutive_separators(&url, "&");
96 + if (!value || !*value) continue;
97 +
98 + char *name = strsep_skip_consecutive_separators(&value, "=");
99 + if (!name || !*name) continue;
100 + if (!value || !*value) continue;
101 +
102 + if(!strcmp(name, "key"))
103 + key = value;
104 + else if(!strcmp(name, "token"))
105 + token = value;
106 + else if(!strcmp(name, "rooms"))
107 + rooms = value;
108 + else if(!strcmp(name, "url"))
109 + base_url = value;
110 + }
111 +
112 + BUFFER *wb = w->response.data;
113 + buffer_flush(wb);
114 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
115 +
116 + time_t now_s = now_realtime_sec();
117 + CLOUD_STATUS status = buffer_json_cloud_status(wb, now_s);
118 +
119 + bool can_be_claimed = false;
120 + switch(status) {
121 + case CLOUD_STATUS_AVAILABLE:
122 + case CLOUD_STATUS_OFFLINE:
123 + case CLOUD_STATUS_INDIRECT:
124 + can_be_claimed = true;
125 + break;
126 +
127 + case CLOUD_STATUS_BANNED:
128 + case CLOUD_STATUS_ONLINE:
129 + can_be_claimed = false;
130 + break;
131 + }
132 +
133 + buffer_json_member_add_boolean(wb, "can_be_claimed", can_be_claimed);
134 +
135 + if(can_be_claimed && key) {
136 + if(!netdata_random_session_id_matches(key)) {
137 + buffer_reset(wb);
138 + buffer_strcat(wb, "invalid key");
139 + netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
140 + return HTTP_RESP_FORBIDDEN;
141 + }
142 +
143 + if(!token || !base_url || !check_claim_param(token) || !check_claim_param(base_url) || (rooms && !check_claim_param(rooms))) {
144 + buffer_reset(wb);
145 + buffer_strcat(wb, "invalid parameters");
146 + netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
147 + return HTTP_RESP_BAD_REQUEST;
148 + }
149 +
150 + netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
151 +
152 + bool success = false;
153 + const char *msg;
154 + if(claim_agent(base_url, token, rooms, cloud_config_proxy_get(), cloud_config_insecure_get())) {
155 + msg = "ok";
156 + success = true;
157 + can_be_claimed = false;
158 + status = claim_reload_and_wait_online();
159 + }
160 + else
161 + msg = claim_agent_failure_reason_get();
162 +
163 + // our status may have changed
164 + // refresh the status in our output
165 + buffer_flush(wb);
166 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
167 + now_s = now_realtime_sec();
168 + buffer_json_cloud_status(wb, now_s);
169 +
170 + // and this is the status of the claiming command we run
171 + buffer_json_member_add_boolean(wb, "success", success);
172 + buffer_json_member_add_string_or_empty(wb, "message", msg);
173 + }
174 +
175 + if(can_be_claimed) {
176 + const char *filename = netdata_random_session_id_get_filename();
177 + CLEAN_BUFFER *buffer = buffer_create(0, NULL);
178 +
179 + const char *os_filename;
180 + const char *os_prefix;
181 + const char *os_quote;
182 + const char *os_message;
183 +
184 +#if defined(OS_WINDOWS)
185 + char win_path[MAX_PATH];
186 + cygwin_conv_path(CCP_POSIX_TO_WIN_A, filename, win_path, sizeof(win_path));
187 + os_filename = win_path;
188 + os_prefix = "more";
189 + os_message = "We need to verify this Windows server is yours. So, open a Command Prompt on this server to run the command. It will give you a UUID. Copy and paste this UUID to this box:";
190 +#else
191 + os_filename = filename;
192 + os_prefix = "sudo cat";
193 + os_message = "We need to verify this server is yours. SSH to this server and run this command. It will give you a UUID. Copy and paste this UUID to this box:";
194 +#endif
195 +
196 + // add quotes only when the filename has a space
197 + if(strchr(os_filename, ' '))
198 + os_quote = "\"";
199 + else
200 + os_quote = "";
201 +
202 + buffer_sprintf(buffer, "%s %s%s%s", os_prefix, os_quote, os_filename, os_quote);
203 + buffer_json_member_add_string(wb, "key_filename", os_filename);
204 + buffer_json_member_add_string(wb, "cmd", buffer_tostring(buffer));
205 + buffer_json_member_add_string(wb, "help", os_message);
206 + }
207 +
208 + buffer_json_agents_v2(wb, NULL, now_s, false, false);
209 + buffer_json_finalize(wb);
210 +
211 + return HTTP_RESP_OK;
212 +}
src/web/api/v2/api_v2_contexts.c new
+78
@@ -0,0 +1,78 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +// --------------------------------------------------------------------------------------------------------------------
6 +
7 +int api_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_MODE mode) {
8 + struct api_v2_contexts_request req = { 0 };
9 +
10 + while(url) {
11 + char *value = strsep_skip_consecutive_separators(&url, "&");
12 + if(!value || !*value) continue;
13 +
14 + char *name = strsep_skip_consecutive_separators(&value, "=");
15 + if(!name || !*name) continue;
16 + if(!value || !*value) continue;
17 +
18 + // name and value are now the parameters
19 + // they are not null and not empty
20 +
21 + if(!strcmp(name, "scope_nodes"))
22 + req.scope_nodes = value;
23 + else if(!strcmp(name, "nodes"))
24 + req.nodes = value;
25 + else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "scope_contexts"))
26 + req.scope_contexts = value;
27 + else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "contexts"))
28 + req.contexts = value;
29 + else if((mode & CONTEXTS_V2_SEARCH) && !strcmp(name, "q"))
30 + req.q = value;
31 + else if(!strcmp(name, "options"))
32 + req.options = contexts_options_str_to_id(value);
33 + else if(!strcmp(name, "after"))
34 + req.after = str2l(value);
35 + else if(!strcmp(name, "before"))
36 + req.before = str2l(value);
37 + else if(!strcmp(name, "timeout"))
38 + req.timeout_ms = str2l(value);
39 + else if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) {
40 + if (!strcmp(name, "alert"))
41 + req.alerts.alert = value;
42 + else if (!strcmp(name, "transition"))
43 + req.alerts.transition = value;
44 + else if(mode & CONTEXTS_V2_ALERTS) {
45 + if (!strcmp(name, "status"))
46 + req.alerts.status = contexts_alert_status_str_to_id(value);
47 + }
48 + else if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
49 + if (!strcmp(name, "last"))
50 + req.alerts.last = strtoul(value, NULL, 0);
51 + else if(!strcmp(name, "context"))
52 + req.contexts = value;
53 + else if (!strcmp(name, "anchor_gi")) {
54 + req.alerts.global_id_anchor = str2ull(value, NULL);
55 + }
56 + else {
57 + for(int i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
58 + if(!strcmp(name, alert_transition_facets[i].query_param))
59 + req.alerts.facets[i] = value;
60 + }
61 + }
62 + }
63 + }
64 + }
65 +
66 + if ((mode & CONTEXTS_V2_ALERT_TRANSITIONS) && !req.alerts.last)
67 + req.alerts.last = 1;
68 +
69 + buffer_flush(w->response.data);
70 + buffer_no_cacheable(w->response.data);
71 + return rrdcontext_to_json_v2(w->response.data, &req, mode);
72 +}
73 +
74 +int api_v2_contexts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
75 + return api_v2_contexts_internal(
76 + host, w, url, CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
77 +}
78 +
src/web/api/v2/api_v2_data.c new
+302
@@ -0,0 +1,302 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +#define GROUP_BY_KEY_MAX_LENGTH 30
6 +static struct {
7 + char group_by[GROUP_BY_KEY_MAX_LENGTH + 1];
8 + char aggregation[GROUP_BY_KEY_MAX_LENGTH + 1];
9 + char group_by_label[GROUP_BY_KEY_MAX_LENGTH + 1];
10 +} group_by_keys[MAX_QUERY_GROUP_BY_PASSES];
11 +
12 +__attribute__((constructor)) void initialize_group_by_keys(void) {
13 + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
14 + snprintfz(group_by_keys[g].group_by, GROUP_BY_KEY_MAX_LENGTH, "group_by[%zu]", g);
15 + snprintfz(group_by_keys[g].aggregation, GROUP_BY_KEY_MAX_LENGTH, "aggregation[%zu]", g);
16 + snprintfz(group_by_keys[g].group_by_label, GROUP_BY_KEY_MAX_LENGTH, "group_by_label[%zu]", g);
17 + }
18 +}
19 +
20 +int api_v2_data(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
21 + usec_t received_ut = now_monotonic_usec();
22 +
23 + int ret = HTTP_RESP_BAD_REQUEST;
24 +
25 + buffer_flush(w->response.data);
26 +
27 + char *google_version = "0.6",
28 + *google_reqId = "0",
29 + *google_sig = "0",
30 + *google_out = "json",
31 + *responseHandler = NULL,
32 + *outFileName = NULL;
33 +
34 + time_t last_timestamp_in_data = 0, google_timestamp = 0;
35 +
36 + char *scope_nodes = NULL;
37 + char *scope_contexts = NULL;
38 + char *nodes = NULL;
39 + char *contexts = NULL;
40 + char *instances = NULL;
41 + char *dimensions = NULL;
42 + char *before_str = NULL;
43 + char *after_str = NULL;
44 + char *resampling_time_str = NULL;
45 + char *points_str = NULL;
46 + char *timeout_str = NULL;
47 + char *labels = NULL;
48 + char *alerts = NULL;
49 + char *time_group_options = NULL;
50 + char *tier_str = NULL;
51 + size_t tier = 0;
52 + RRDR_TIME_GROUPING time_group = RRDR_GROUPING_AVERAGE;
53 + DATASOURCE_FORMAT format = DATASOURCE_JSON2;
54 + RRDR_OPTIONS options = RRDR_OPTION_VIRTUAL_POINTS | RRDR_OPTION_JSON_WRAP | RRDR_OPTION_RETURN_JWAR;
55 +
56 + struct group_by_pass group_by[MAX_QUERY_GROUP_BY_PASSES] = {
57 + {
58 + .group_by = RRDR_GROUP_BY_DIMENSION,
59 + .group_by_label = NULL,
60 + .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
61 + },
62 + };
63 +
64 + size_t group_by_idx = 0, group_by_label_idx = 0, aggregation_idx = 0;
65 +
66 + while(url) {
67 + char *value = strsep_skip_consecutive_separators(&url, "&");
68 + if(!value || !*value) continue;
69 +
70 + char *name = strsep_skip_consecutive_separators(&value, "=");
71 + if(!name || !*name) continue;
72 + if(!value || !*value) continue;
73 +
74 + // name and value are now the parameters
75 + // they are not null and not empty
76 +
77 + if(!strcmp(name, "scope_nodes")) scope_nodes = value;
78 + else if(!strcmp(name, "scope_contexts")) scope_contexts = value;
79 + else if(!strcmp(name, "nodes")) nodes = value;
80 + else if(!strcmp(name, "contexts")) contexts = value;
81 + else if(!strcmp(name, "instances")) instances = value;
82 + else if(!strcmp(name, "dimensions")) dimensions = value;
83 + else if(!strcmp(name, "labels")) labels = value;
84 + else if(!strcmp(name, "alerts")) alerts = value;
85 + else if(!strcmp(name, "after")) after_str = value;
86 + else if(!strcmp(name, "before")) before_str = value;
87 + else if(!strcmp(name, "points")) points_str = value;
88 + else if(!strcmp(name, "timeout")) timeout_str = value;
89 + else if(!strcmp(name, "group_by")) {
90 + group_by[group_by_idx++].group_by = group_by_parse(value);
91 + if(group_by_idx >= MAX_QUERY_GROUP_BY_PASSES)
92 + group_by_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
93 + }
94 + else if(!strcmp(name, "group_by_label")) {
95 + group_by[group_by_label_idx++].group_by_label = value;
96 + if(group_by_label_idx >= MAX_QUERY_GROUP_BY_PASSES)
97 + group_by_label_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
98 + }
99 + else if(!strcmp(name, "aggregation")) {
100 + group_by[aggregation_idx++].aggregation = group_by_aggregate_function_parse(value);
101 + if(aggregation_idx >= MAX_QUERY_GROUP_BY_PASSES)
102 + aggregation_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
103 + }
104 + else if(!strcmp(name, "format")) format = datasource_format_str_to_id(value);
105 + else if(!strcmp(name, "options")) options |= rrdr_options_parse(value);
106 + else if(!strcmp(name, "time_group")) time_group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
107 + else if(!strcmp(name, "time_group_options")) time_group_options = value;
108 + else if(!strcmp(name, "time_resampling")) resampling_time_str = value;
109 + else if(!strcmp(name, "tier")) tier_str = value;
110 + else if(!strcmp(name, "callback")) responseHandler = value;
111 + else if(!strcmp(name, "filename")) outFileName = value;
112 + else if(!strcmp(name, "tqx")) {
113 + // parse Google Visualization API options
114 + // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
115 + char *tqx_name, *tqx_value;
116 +
117 + while(value) {
118 + tqx_value = strsep_skip_consecutive_separators(&value, ";");
119 + if(!tqx_value || !*tqx_value) continue;
120 +
121 + tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
122 + if(!tqx_name || !*tqx_name) continue;
123 + if(!tqx_value || !*tqx_value) continue;
124 +
125 + if(!strcmp(tqx_name, "version"))
126 + google_version = tqx_value;
127 + else if(!strcmp(tqx_name, "reqId"))
128 + google_reqId = tqx_value;
129 + else if(!strcmp(tqx_name, "sig")) {
130 + google_sig = tqx_value;
131 + google_timestamp = strtoul(google_sig, NULL, 0);
132 + }
133 + else if(!strcmp(tqx_name, "out")) {
134 + google_out = tqx_value;
135 + format = google_data_format_str_to_id(google_out);
136 + }
137 + else if(!strcmp(tqx_name, "responseHandler"))
138 + responseHandler = tqx_value;
139 + else if(!strcmp(tqx_name, "outFileName"))
140 + outFileName = tqx_value;
141 + }
142 + }
143 + else {
144 + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
145 + if(!strcmp(name, group_by_keys[g].group_by))
146 + group_by[g].group_by = group_by_parse(value);
147 + else if(!strcmp(name, group_by_keys[g].group_by_label))
148 + group_by[g].group_by_label = value;
149 + else if(!strcmp(name, group_by_keys[g].aggregation))
150 + group_by[g].aggregation = group_by_aggregate_function_parse(value);
151 + }
152 + }
153 + }
154 +
155 + // validate the google parameters given
156 + fix_google_param(google_out);
157 + fix_google_param(google_sig);
158 + fix_google_param(google_reqId);
159 + fix_google_param(google_version);
160 + fix_google_param(responseHandler);
161 + fix_google_param(outFileName);
162 +
163 + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
164 + if (group_by[g].group_by_label && *group_by[g].group_by_label)
165 + group_by[g].group_by |= RRDR_GROUP_BY_LABEL;
166 + }
167 +
168 + if(group_by[0].group_by == RRDR_GROUP_BY_NONE)
169 + group_by[0].group_by = RRDR_GROUP_BY_DIMENSION;
170 +
171 + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
172 + if ((group_by[g].group_by & ~(RRDR_GROUP_BY_DIMENSION)) || (options & RRDR_OPTION_PERCENTAGE)) {
173 + options |= RRDR_OPTION_ABSOLUTE;
174 + break;
175 + }
176 + }
177 +
178 + if(options & RRDR_OPTION_DEBUG)
179 + options &= ~RRDR_OPTION_MINIFY;
180 +
181 + if(tier_str && *tier_str) {
182 + tier = str2ul(tier_str);
183 + if(tier < storage_tiers)
184 + options |= RRDR_OPTION_SELECTED_TIER;
185 + else
186 + tier = 0;
187 + }
188 +
189 + time_t before = (before_str && *before_str)?str2l(before_str):0;
190 + time_t after = (after_str && *after_str) ?str2l(after_str):-600;
191 + size_t points = (points_str && *points_str)?str2u(points_str):0;
192 + int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
193 + time_t resampling_time = (resampling_time_str && *resampling_time_str) ? str2l(resampling_time_str) : 0;
194 +
195 + QUERY_TARGET_REQUEST qtr = {
196 + .version = 2,
197 + .scope_nodes = scope_nodes,
198 + .scope_contexts = scope_contexts,
199 + .after = after,
200 + .before = before,
201 + .host = NULL,
202 + .st = NULL,
203 + .nodes = nodes,
204 + .contexts = contexts,
205 + .instances = instances,
206 + .dimensions = dimensions,
207 + .alerts = alerts,
208 + .timeout_ms = timeout,
209 + .points = points,
210 + .format = format,
211 + .options = options,
212 + .time_group_method = time_group,
213 + .time_group_options = time_group_options,
214 + .resampling_time = resampling_time,
215 + .tier = tier,
216 + .chart_label_key = NULL,
217 + .labels = labels,
218 + .query_source = QUERY_SOURCE_API_DATA,
219 + .priority = STORAGE_PRIORITY_NORMAL,
220 + .received_ut = received_ut,
221 +
222 + .interrupt_callback = web_client_interrupt_callback,
223 + .interrupt_callback_data = w,
224 +
225 + .transaction = &w->transaction,
226 + };
227 +
228 + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++)
229 + qtr.group_by[g] = group_by[g];
230 +
231 + QUERY_TARGET *qt = query_target_create(&qtr);
232 + ONEWAYALLOC *owa = NULL;
233 +
234 + if(!qt) {
235 + buffer_sprintf(w->response.data, "Failed to prepare the query.");
236 + ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
237 + goto cleanup;
238 + }
239 +
240 + web_client_timeout_checkpoint_set(w, timeout);
241 + if(web_client_timeout_checkpoint_and_check(w, NULL)) {
242 + ret = w->response.code;
243 + goto cleanup;
244 + }
245 +
246 + if(outFileName && *outFileName) {
247 + buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
248 + netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
249 + }
250 +
251 + if(format == DATASOURCE_DATATABLE_JSONP) {
252 + if(responseHandler == NULL)
253 + responseHandler = "google.visualization.Query.setResponse";
254 +
255 + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
256 + w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
257 + );
258 +
259 + buffer_sprintf(
260 + w->response.data,
261 + "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
262 + responseHandler,
263 + google_version,
264 + google_reqId,
265 + (int64_t)now_realtime_sec());
266 + }
267 + else if(format == DATASOURCE_JSONP) {
268 + if(responseHandler == NULL)
269 + responseHandler = "callback";
270 +
271 + buffer_strcat(w->response.data, responseHandler);
272 + buffer_strcat(w->response.data, "(");
273 + }
274 +
275 + owa = onewayalloc_create(0);
276 + ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
277 +
278 + if(format == DATASOURCE_DATATABLE_JSONP) {
279 + if(google_timestamp < last_timestamp_in_data)
280 + buffer_strcat(w->response.data, "});");
281 +
282 + else {
283 + // the client already has the latest data
284 + buffer_flush(w->response.data);
285 + buffer_sprintf(w->response.data,
286 + "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
287 + responseHandler, google_version, google_reqId);
288 + }
289 + }
290 + else if(format == DATASOURCE_JSONP)
291 + buffer_strcat(w->response.data, ");");
292 +
293 + if(qt->internal.relative)
294 + buffer_no_cacheable(w->response.data);
295 + else
296 + buffer_cacheable(w->response.data);
297 +
298 +cleanup:
299 + query_target_release(qt);
300 + onewayalloc_destroy(owa);
301 + return ret;
302 +}
src/web/api/v2/api_v2_functions.c new
+8
@@ -0,0 +1,8 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_functions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(
7 + host, w, url, CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
8 +}
src/web/api/v2/api_v2_ilove/README.md renamed
src/web/api/v2/api_v2_ilove/ilove.c renamed
+2 -2
@@ -1,6 +1,6 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#include "ilove.h"
3 +#include "../api_v2_calls.h"
4
5 static const unsigned short int ibm_plex_sans_bold_250[128][128] = {
6 {0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* ! */, 0 /* " */, 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, 0 /* ( */, 0 /* ) */, 0 /* * */, 0 /* + */, 0 /* , */, 0 /* - */, 0 /* . */, 0 /* / */, 0 /* 0 */, 0 /* 1 */, 0 /* 2 */, 0 /* 3 */, 0 /* 4 */, 0 /* 5 */, 0 /* 6 */, 0 /* 7 */, 0 /* 8 */, 0 /* 9 */, 0 /* : */, 0 /* ; */, 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, 0 /* @ */, 0 /* A */, 0 /* B */, 0 /* C */, 0 /* D */, 0 /* E */, 0 /* F */, 0 /* G */, 0 /* H */, 0 /* I */, 0 /* J */, 0 /* K */, 0 /* L */, 0 /* M */, 0 /* N */, 0 /* O */, 0 /* P */, 0 /* Q */, 0 /* R */, 0 /* S */, 0 /* T */, 0 /* U */, 0 /* V */, 0 /* W */, 0 /* X */, 0 /* Y */, 0 /* Z */, 0 /* [ */, 0 /* \ */, 0 /* ] */, 0 /* ^ */, 0 /* _ */, 0 /* ` */, 0 /* a */, 0 /* b */, 0 /* c */, 0 /* d */, 0 /* e */, 0 /* f */, 0 /* g */, 0 /* h */, 0 /* i */, 0 /* j */, 0 /* k */, 0 /* l */, 0 /* m */, 0 /* n */, 0 /* o */, 0 /* p */, 0 /* q */, 0 /* r */, 0 /* s */, 0 /* t */, 0 /* u */, 0 /* v */, 0 /* w */, 0 /* x */, 0 /* y */, 0 /* z */, 0 /* { */, 0 /* | */, 0 /* } */, 0 /* ~ */},
@@ -277,7 +277,7 @@ static void generate_ilove_svg(BUFFER *wb, const char *love) {
277 wb->content_type = CT_IMAGE_SVG_XML;
278 }
279
280 -int web_client_api_request_v2_ilove(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
280 +int api_v2_ilove(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
281 char *love = "TROUBLE";
282
283 while(url) {
src/web/api/v2/api_v2_ilove/measure-text.js renamed
src/web/api/v2/api_v2_info.c new
+7
@@ -0,0 +1,7 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO);
7 +}
src/web/api/v2/api_v2_node_instances.c new
+10
@@ -0,0 +1,10 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_node_instances(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(
7 + host, w, url,
8 + CONTEXTS_V2_NODES | CONTEXTS_V2_NODE_INSTANCES | CONTEXTS_V2_AGENTS |
9 + CONTEXTS_V2_AGENTS_INFO | CONTEXTS_V2_VERSIONS);
10 +}
src/web/api/v2/api_v2_nodes.c new
+7
@@ -0,0 +1,7 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_nodes(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_INFO);
7 +}
src/web/api/v2/api_v2_progress.c new
+28
@@ -0,0 +1,28 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_progress(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + char *transaction = NULL;
7 +
8 + while(url) {
9 + char *value = strsep_skip_consecutive_separators(&url, "&");
10 + if(!value || !*value) continue;
11 +
12 + char *name = strsep_skip_consecutive_separators(&value, "=");
13 + if(!name || !*name) continue;
14 + if(!value || !*value) continue;
15 +
16 + // name and value are now the parameters
17 + // they are not null and not empty
18 +
19 + if(!strcmp(name, "transaction")) transaction = value;
20 + }
21 +
22 + nd_uuid_t tr;
23 + uuid_parse_flexi(transaction, tr);
24 +
25 + rrd_function_call_progresser(&tr);
26 +
27 + return web_api_v2_report_progress(&tr, w->response.data);
28 +}
src/web/api/v2/api_v2_q.c new
+9
@@ -0,0 +1,9 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_q(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(
7 + host, w, url,
8 + CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
9 +}
src/web/api/v2/api_v2_versions.c new
+7
@@ -0,0 +1,7 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int api_v2_versions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
6 + return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_VERSIONS);
7 +}
src/web/api/v2/api_v2_webrtc.c new
+8
@@ -0,0 +1,8 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +#include "../../rtc/webrtc.h"
5 +
6 +int api_v2_webrtc(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
7 + return webrtc_new_connection(buffer_tostring(w->payload), w->response.data);
8 +}
src/web/api/v2/api_v2_weights.c new
+152
@@ -0,0 +1,152 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v2_calls.h"
4 +
5 +int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, size_t api_version) {
6 + if (!netdata_ready)
7 + return HTTP_RESP_SERVICE_UNAVAILABLE;
8 +
9 + time_t baseline_after = 0, baseline_before = 0, after = 0, before = 0;
10 + size_t points = 0;
11 + RRDR_OPTIONS options = 0;
12 + RRDR_TIME_GROUPING time_group_method = RRDR_GROUPING_AVERAGE;
13 + time_t timeout_ms = 0;
14 + size_t tier = 0;
15 + const char *time_group_options = NULL, *scope_contexts = NULL, *scope_nodes = NULL, *contexts = NULL, *nodes = NULL,
16 + *instances = NULL, *dimensions = NULL, *labels = NULL, *alerts = NULL;
17 +
18 + struct group_by_pass group_by = {
19 + .group_by = RRDR_GROUP_BY_NONE,
20 + .group_by_label = NULL,
21 + .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
22 + };
23 +
24 + while (url) {
25 + char *value = strsep_skip_consecutive_separators(&url, "&");
26 + if (!value || !*value)
27 + continue;
28 +
29 + char *name = strsep_skip_consecutive_separators(&value, "=");
30 + if (!name || !*name)
31 + continue;
32 + if (!value || !*value)
33 + continue;
34 +
35 + if (!strcmp(name, "baseline_after"))
36 + baseline_after = str2l(value);
37 +
38 + else if (!strcmp(name, "baseline_before"))
39 + baseline_before = str2l(value);
40 +
41 + else if (!strcmp(name, "after") || !strcmp(name, "highlight_after"))
42 + after = str2l(value);
43 +
44 + else if (!strcmp(name, "before") || !strcmp(name, "highlight_before"))
45 + before = str2l(value);
46 +
47 + else if (!strcmp(name, "points") || !strcmp(name, "max_points"))
48 + points = str2ul(value);
49 +
50 + else if (!strcmp(name, "timeout"))
51 + timeout_ms = str2l(value);
52 +
53 + else if((api_version == 1 && !strcmp(name, "group")) || (api_version >= 2 && !strcmp(name, "time_group")))
54 + time_group_method = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
55 +
56 + else if((api_version == 1 && !strcmp(name, "group_options")) || (api_version >= 2 && !strcmp(name, "time_group_options")))
57 + time_group_options = value;
58 +
59 + else if(!strcmp(name, "options"))
60 + options |= rrdr_options_parse(value);
61 +
62 + else if(!strcmp(name, "method"))
63 + method = weights_string_to_method(value);
64 +
65 + else if(api_version == 1 && (!strcmp(name, "context") || !strcmp(name, "contexts")))
66 + scope_contexts = value;
67 +
68 + else if(api_version >= 2 && !strcmp(name, "scope_nodes")) scope_nodes = value;
69 + else if(api_version >= 2 && !strcmp(name, "scope_contexts")) scope_contexts = value;
70 + else if(api_version >= 2 && !strcmp(name, "nodes")) nodes = value;
71 + else if(api_version >= 2 && !strcmp(name, "contexts")) contexts = value;
72 + else if(api_version >= 2 && !strcmp(name, "instances")) instances = value;
73 + else if(api_version >= 2 && !strcmp(name, "dimensions")) dimensions = value;
74 + else if(api_version >= 2 && !strcmp(name, "labels")) labels = value;
75 + else if(api_version >= 2 && !strcmp(name, "alerts")) alerts = value;
76 + else if(api_version >= 2 && (!strcmp(name, "group_by") || !strcmp(name, "group_by[0]"))) {
77 + group_by.group_by = group_by_parse(value);
78 + }
79 + else if(api_version >= 2 && (!strcmp(name, "group_by_label") || !strcmp(name, "group_by_label[0]"))) {
80 + group_by.group_by_label = value;
81 + }
82 + else if(api_version >= 2 && (!strcmp(name, "aggregation") || !strcmp(name, "aggregation[0]"))) {
83 + group_by.aggregation = group_by_aggregate_function_parse(value);
84 + }
85 +
86 + else if(!strcmp(name, "tier")) {
87 + tier = str2ul(value);
88 + if(tier < storage_tiers)
89 + options |= RRDR_OPTION_SELECTED_TIER;
90 + else
91 + tier = 0;
92 + }
93 + }
94 +
95 + if(options == 0)
96 + // the user did not set any options
97 + options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO | RRDR_OPTION_NONZERO;
98 + else
99 + // the user set some options, add also these
100 + options |= RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO;
101 +
102 + if(options & RRDR_OPTION_PERCENTAGE)
103 + options |= RRDR_OPTION_ABSOLUTE;
104 +
105 + if(options & RRDR_OPTION_DEBUG)
106 + options &= ~RRDR_OPTION_MINIFY;
107 +
108 + BUFFER *wb = w->response.data;
109 + buffer_flush(wb);
110 + wb->content_type = CT_APPLICATION_JSON;
111 +
112 + QUERY_WEIGHTS_REQUEST qwr = {
113 + .version = api_version,
114 + .host = (api_version == 1) ? NULL : host,
115 + .scope_nodes = scope_nodes,
116 + .scope_contexts = scope_contexts,
117 + .nodes = nodes,
118 + .contexts = contexts,
119 + .instances = instances,
120 + .dimensions = dimensions,
121 + .labels = labels,
122 + .alerts = alerts,
123 + .group_by = {
124 + .group_by = group_by.group_by,
125 + .group_by_label = group_by.group_by_label,
126 + .aggregation = group_by.aggregation,
127 + },
128 + .method = method,
129 + .format = format,
130 + .time_group_method = time_group_method,
131 + .time_group_options = time_group_options,
132 + .baseline_after = baseline_after,
133 + .baseline_before = baseline_before,
134 + .after = after,
135 + .before = before,
136 + .points = points,
137 + .options = options,
138 + .tier = tier,
139 + .timeout_ms = timeout_ms,
140 +
141 + .interrupt_callback = web_client_interrupt_callback,
142 + .interrupt_callback_data = w,
143 +
144 + .transaction = &w->transaction,
145 + };
146 +
147 + return web_api_v12_weights(wb, &qwr);
148 +}
149 +
150 +int api_v2_weights(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
151 + return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, WEIGHTS_FORMAT_MULTINODE, 2);
152 +}
src/web/api/v3/api_v3_calls.h new
+11
@@ -0,0 +1,11 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_API_V3_CALLS_H
4 +#define NETDATA_API_V3_CALLS_H
5 +
6 +#include "../web_api_v3.h"
7 +
8 +int api_v3_settings(RRDHOST *host, struct web_client *w, char *url);
9 +int api_v3_me(RRDHOST *host, struct web_client *w, char *url);
10 +
11 +#endif //NETDATA_API_V3_CALLS_H
src/web/api/v3/api_v3_me.c new
+37
@@ -0,0 +1,37 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "api_v3_calls.h"
4 +
5 +int api_v3_me(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
6 + BUFFER *wb = w->response.data;
7 + buffer_reset(wb);
8 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
9 +
10 + const char *auth;
11 + switch(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD|WEB_CLIENT_FLAG_AUTH_BEARER|WEB_CLIENT_FLAG_AUTH_GOD)) {
12 + case WEB_CLIENT_FLAG_AUTH_CLOUD:
13 + auth = "cloud";
14 + break;
15 +
16 + case WEB_CLIENT_FLAG_AUTH_BEARER:
17 + auth = "bearer";
18 + break;
19 +
20 + case WEB_CLIENT_FLAG_AUTH_GOD:
21 + auth = "god";
22 + break;
23 +
24 + default:
25 + auth = "none";
26 + break;
27 + }
28 + buffer_json_member_add_string(wb, "auth", auth);
29 +
30 + buffer_json_member_add_uuid(wb, "cloud_account_id", w->auth.cloud_account_id);
31 + buffer_json_member_add_string(wb, "client_name", w->auth.client_name);
32 + http_access2buffer_json_array(wb, "access", w->access);
33 + buffer_json_member_add_string(wb, "user_role", http_id2user_role(w->user_role));
34 +
35 + buffer_json_finalize(wb);
36 + return HTTP_RESP_OK;
37 +}
src/web/api/v3/api_v3_settings.c new
+285
@@ -0,0 +1,285 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +/*
4 + * /api/v3/settings
5 + *
6 + * QUERY STRING PARAMETERS:
7 + * - file=a file name (alphanumerics, dashes, underscores)
8 + * When the user is not authenticated with a bearer token
9 + * only the 'default' file is allowed.
10 + * Authenticated users can create, store and update any
11 + * settings file.
12 + *
13 + * HTTP METHODS
14 + * - GET to retrieve a file
15 + * - PUT to create or update a file
16 + *
17 + * PAYLOAD
18 + * - The payload MUST have the member 'version'.
19 + * - The payload MAY have anything else.
20 + * - The maximum payload size in JSON is 20MiB.
21 + * - When updating the payload, the caller must specify the
22 + * version of the existing file. If this check fails,
23 + * Netdata will return 409 (conflict).
24 + * When the caller receives 409, it means there are updates
25 + * in the payload outside its control and the object MUST
26 + * be loaded again to find its current version to update it.
27 + * After loading it, the caller must reapply the changes and
28 + * PUT it again.
29 + * - Netdata will increase the version on every PUT action.
30 + * So, the payload MUST specify the version found on disk
31 + * but, Netdata will increment the version before saving it.
32 + */
33 +
34 +#include "api_v3_calls.h"
35 +
36 +#define MAX_SETTINGS_SIZE_BYTES (20 * 1024 * 1024)
37 +
38 +// we need an r/w spinlock to ensure that reads and write do not happen
39 +// concurrently for settings files
40 +static RW_SPINLOCK settings_spinlock = NETDATA_RW_SPINLOCK_INITIALIZER;
41 +
42 +static inline void settings_path(char out[FILENAME_MAX]) {
43 + filename_from_path_entry(out, netdata_configured_varlib_dir, "settings", NULL);
44 +}
45 +
46 +static inline void settings_filename(char out[FILENAME_MAX], const char *file, const char *extension) {
47 + char path[FILENAME_MAX];
48 + settings_path(path);
49 + filename_from_path_entry(out, path, file, extension);
50 +}
51 +
52 +static inline bool settings_ensure_path_exists(void) {
53 + char path[FILENAME_MAX];
54 + settings_path(path);
55 + return filename_is_dir(path, true);
56 +}
57 +
58 +static inline size_t settings_extract_json_version(const char *json) {
59 + if(!json || !*json) return 0;
60 +
61 + // Parse the JSON string into a JSON-C object
62 + CLEAN_JSON_OBJECT *jobj = json_tokener_parse(json);
63 + if (jobj == NULL)
64 + return 0;
65 +
66 + // Access the "version" field
67 + struct json_object *version_obj;
68 + if (json_object_object_get_ex(jobj, "version", &version_obj))
69 + // Extract the integer value of the version
70 + return (size_t)json_object_get_int(version_obj);
71 +
72 + return 0;
73 +}
74 +
75 +static inline void settings_initial_version(BUFFER *wb) {
76 + buffer_reset(wb);
77 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
78 + buffer_json_member_add_uint64(wb, "version", 1);
79 + buffer_json_finalize(wb);
80 +}
81 +
82 +static inline void settings_get(BUFFER *wb, const char *file, bool have_lock) {
83 + char filename[FILENAME_MAX];
84 + settings_filename(filename, file, NULL);
85 +
86 + buffer_reset(wb);
87 +
88 + if(!have_lock)
89 + rw_spinlock_read_lock(&settings_spinlock);
90 +
91 + bool rc = read_txt_file_to_buffer(filename, wb, MAX_SETTINGS_SIZE_BYTES);
92 +
93 + if(!have_lock)
94 + rw_spinlock_read_unlock(&settings_spinlock);
95 +
96 + if(rc) {
97 + size_t version = settings_extract_json_version(buffer_tostring(wb));
98 + if (!version) {
99 + nd_log(NDLS_DAEMON, NDLP_ERR, "file '%s' cannot be parsed to extract version", filename);
100 + settings_initial_version(wb);
101 + }
102 + else {
103 + wb->content_type = CT_APPLICATION_JSON;
104 + buffer_no_cacheable(wb);
105 + }
106 + }
107 + else
108 + settings_initial_version(wb);
109 +}
110 +
111 +static inline size_t settings_get_version(const char *path, bool have_lock) {
112 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
113 + settings_get(wb, path, have_lock);
114 +
115 + return settings_extract_json_version(buffer_tostring(wb));
116 +}
117 +
118 +static inline int settings_put(struct web_client *w, char *file) {
119 + rw_spinlock_write_lock(&settings_spinlock);
120 +
121 + if(!settings_ensure_path_exists()) {
122 + rw_spinlock_write_unlock(&settings_spinlock);
123 + return rrd_call_function_error(
124 + w->response.data,
125 + "Settings path cannot be created or accessed.",
126 + HTTP_RESP_BAD_REQUEST);
127 + }
128 +
129 + size_t old_version = settings_get_version(file, true);
130 +
131 + // Parse the JSON string into a JSON-C object
132 + CLEAN_JSON_OBJECT *jobj = json_tokener_parse(buffer_tostring(w->payload));
133 + if (jobj == NULL) {
134 + rw_spinlock_write_unlock(&settings_spinlock);
135 + return rrd_call_function_error(
136 + w->response.data,
137 + "Payload cannot be parsed as a JSON object",
138 + HTTP_RESP_BAD_REQUEST);
139 + }
140 +
141 + // Access the "version" field
142 + struct json_object *version_obj;
143 + if (!json_object_object_get_ex(jobj, "version", &version_obj)) {
144 + rw_spinlock_write_unlock(&settings_spinlock);
145 + return rrd_call_function_error(
146 + w->response.data,
147 + "Field version is not found in payload",
148 + HTTP_RESP_BAD_REQUEST);
149 + }
150 +
151 + size_t new_version = (size_t)json_object_get_int(version_obj);
152 +
153 + if (old_version != new_version) {
154 + rw_spinlock_write_unlock(&settings_spinlock);
155 + return rrd_call_function_error(
156 + w->response.data,
157 + "Payload version does not match the version of the stored object",
158 + HTTP_RESP_CONFLICT);
159 + }
160 +
161 + new_version++;
162 + // Set the new version back into the JSON object
163 + json_object_object_add(jobj, "version", json_object_new_int((int)new_version));
164 +
165 + // Convert the updated JSON object back to a string
166 + const char *updated_json_str = json_object_to_json_string(jobj);
167 +
168 + char tmp_filename[FILENAME_MAX];
169 + settings_filename(tmp_filename, file, "new");
170 +
171 + // Save the updated JSON string to a file
172 + FILE *fp = fopen(tmp_filename, "w");
173 + if (fp == NULL) {
174 + rw_spinlock_write_unlock(&settings_spinlock);
175 + nd_log(NDLS_DAEMON, NDLP_ERR, "cannot open/create settings file '%s'", tmp_filename);
176 + return rrd_call_function_error(
177 + w->response.data,
178 + "Cannot create payload file '%s'",
179 + HTTP_RESP_INTERNAL_SERVER_ERROR);
180 + }
181 + size_t len = strlen(updated_json_str);
182 + if(fwrite(updated_json_str, 1, len, fp) != len) {
183 + fclose(fp);
184 + unlink(tmp_filename);
185 + rw_spinlock_write_unlock(&settings_spinlock);
186 + nd_log(NDLS_DAEMON, NDLP_ERR, "cannot save settings to file '%s'", tmp_filename);
187 + return rrd_call_function_error(
188 + w->response.data,
189 + "Cannot save payload to file '%s'",
190 + HTTP_RESP_INTERNAL_SERVER_ERROR);
191 + }
192 + fclose(fp);
193 +
194 + char filename[FILENAME_MAX];
195 + settings_filename(filename, file, NULL);
196 +
197 + bool renamed = rename(tmp_filename, filename) == 0;
198 +
199 + rw_spinlock_write_unlock(&settings_spinlock);
200 +
201 + if(!renamed) {
202 + nd_log(NDLS_DAEMON, NDLP_ERR, "cannot rename file '%s' to '%s'", tmp_filename, filename);
203 + return rrd_call_function_error(
204 + w->response.data,
205 + "Failed to move the payload file to its final location",
206 + HTTP_RESP_INTERNAL_SERVER_ERROR);
207 + }
208 +
209 + return rrd_call_function_error(
210 + w->response.data,
211 + "OK",
212 + HTTP_RESP_OK);
213 +}
214 +
215 +static inline bool is_settings_file_valid(char *file) {
216 + char *s = file;
217 +
218 + if(!s || !*s)
219 + return false;
220 +
221 + while(*s) {
222 + if(!isalnum(*s) && *s != '-' && *s != '_')
223 + return false;
224 + s++;
225 + }
226 +
227 + return true;
228 +}
229 +
230 +int api_v3_settings(RRDHOST *host, struct web_client *w, char *url) {
231 + char *file = NULL;
232 +
233 + while(url) {
234 + char *value = strsep_skip_consecutive_separators(&url, "&");
235 + if(!value || !*value) continue;
236 +
237 + char *name = strsep_skip_consecutive_separators(&value, "=");
238 + if(!name || !*name) continue;
239 + if(!value || !*value) continue;
240 +
241 + // name and value are now the parameters
242 + // they are not null and not empty
243 +
244 + if(!strcmp(name, "file"))
245 + file = value;
246 + }
247 +
248 + if(!is_settings_file_valid(file))
249 + return rrd_call_function_error(
250 + w->response.data,
251 + "Invalid settings file given.",
252 + HTTP_RESP_BAD_REQUEST);
253 +
254 + if(host != localhost)
255 + return rrd_call_function_error(
256 + w->response.data,
257 + "Settings API is only allowed for the agent node.",
258 + HTTP_RESP_BAD_REQUEST);
259 +
260 + if(web_client_flags_check_auth(w) != WEB_CLIENT_FLAG_AUTH_BEARER && strcmp(file, "default") != 0)
261 + return rrd_call_function_error(
262 + w->response.data,
263 + "Only the 'default' settings file is allowed for unauthenticated users",
264 + HTTP_RESP_BAD_REQUEST);
265 +
266 + switch(w->mode) {
267 + case HTTP_REQUEST_MODE_GET:
268 + settings_get(w->response.data, file, false);
269 + return HTTP_RESP_OK;
270 +
271 + case HTTP_REQUEST_MODE_PUT:
272 + if(!w->payload || !buffer_strlen(w->payload))
273 + return rrd_call_function_error(
274 + w->response.data,
275 + "Settings API PUT action requires a payload.",
276 + HTTP_RESP_BAD_REQUEST);
277 +
278 + return settings_put(w, file);
279 +
280 + default:
281 + return rrd_call_function_error(w->response.data,
282 + "Invalid HTTP mode. HTTP modes GET and PUT are supported.",
283 + HTTP_RESP_BAD_REQUEST);
284 + }
285 +}
src/web/api/web_api.c
+65 -151
@@ -2,6 +2,12 @@
2
3 #include "web_api.h"
4
5 +void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key) {
6 + buffer_json_member_add_object(wb, key);
7 + rrdlabels_to_buffer_json_members(host->rrdlabels, wb);
8 + buffer_json_object_close(wb);
9 +}
10 +
11 int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_path_endpoint, struct web_api_command *api_commands) {
12 buffer_no_cacheable(w->response.data);
13
@@ -11,15 +17,16 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
17 internal_fatal(!web_client_flags_check_auth(w) && (w->access & HTTP_ACCESS_SIGNED_ID),
18 "signed-in permission is set, but it shouldn't");
19
20 +#ifdef NETDATA_GOD_MODE
21 + web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, WEB_CLIENT_FLAG_AUTH_GOD);
22 +#else
23 if(!web_client_flags_check_auth(w)) {
15 - w->user_role = (netdata_is_protected_by_bearer) ? HTTP_USER_ROLE_NONE : HTTP_USER_ROLE_ANY;
16 - w->access = (netdata_is_protected_by_bearer) ? HTTP_ACCESS_NONE : HTTP_ACCESS_ANONYMOUS_DATA;
24 + web_client_set_permissions(
25 + w,
26 + (netdata_is_protected_by_bearer) ? HTTP_ACCESS_NONE : HTTP_ACCESS_ANONYMOUS_DATA,
27 + (netdata_is_protected_by_bearer) ? HTTP_USER_ROLE_NONE : HTTP_USER_ROLE_ANY,
28 + 0);
29 }
18 -
19 -#ifdef NETDATA_GOD_MODE
20 - web_client_flag_set(w, WEB_CLIENT_FLAG_AUTH_GOD);
21 - w->user_role = HTTP_USER_ROLE_ADMIN;
22 - w->access = HTTP_ACCESS_ALL;
30 #endif
31
32 if(unlikely(!url_path_endpoint || !*url_path_endpoint)) {
@@ -110,150 +117,6 @@ RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o) {
117 return options;
118 }
119
113 -int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, size_t api_version) {
114 - if (!netdata_ready)
115 - return HTTP_RESP_SERVICE_UNAVAILABLE;
116 -
117 - time_t baseline_after = 0, baseline_before = 0, after = 0, before = 0;
118 - size_t points = 0;
119 - RRDR_OPTIONS options = 0;
120 - RRDR_TIME_GROUPING time_group_method = RRDR_GROUPING_AVERAGE;
121 - time_t timeout_ms = 0;
122 - size_t tier = 0;
123 - const char *time_group_options = NULL, *scope_contexts = NULL, *scope_nodes = NULL, *contexts = NULL, *nodes = NULL,
124 - *instances = NULL, *dimensions = NULL, *labels = NULL, *alerts = NULL;
125 -
126 - struct group_by_pass group_by = {
127 - .group_by = RRDR_GROUP_BY_NONE,
128 - .group_by_label = NULL,
129 - .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
130 - };
131 -
132 - while (url) {
133 - char *value = strsep_skip_consecutive_separators(&url, "&");
134 - if (!value || !*value)
135 - continue;
136 -
137 - char *name = strsep_skip_consecutive_separators(&value, "=");
138 - if (!name || !*name)
139 - continue;
140 - if (!value || !*value)
141 - continue;
142 -
143 - if (!strcmp(name, "baseline_after"))
144 - baseline_after = str2l(value);
145 -
146 - else if (!strcmp(name, "baseline_before"))
147 - baseline_before = str2l(value);
148 -
149 - else if (!strcmp(name, "after") || !strcmp(name, "highlight_after"))
150 - after = str2l(value);
151 -
152 - else if (!strcmp(name, "before") || !strcmp(name, "highlight_before"))
153 - before = str2l(value);
154 -
155 - else if (!strcmp(name, "points") || !strcmp(name, "max_points"))
156 - points = str2ul(value);
157 -
158 - else if (!strcmp(name, "timeout"))
159 - timeout_ms = str2l(value);
160 -
161 - else if((api_version == 1 && !strcmp(name, "group")) || (api_version >= 2 && !strcmp(name, "time_group")))
162 - time_group_method = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
163 -
164 - else if((api_version == 1 && !strcmp(name, "group_options")) || (api_version >= 2 && !strcmp(name, "time_group_options")))
165 - time_group_options = value;
166 -
167 - else if(!strcmp(name, "options"))
168 - options |= rrdr_options_parse(value);
169 -
170 - else if(!strcmp(name, "method"))
171 - method = weights_string_to_method(value);
172 -
173 - else if(api_version == 1 && (!strcmp(name, "context") || !strcmp(name, "contexts")))
174 - scope_contexts = value;
175 -
176 - else if(api_version >= 2 && !strcmp(name, "scope_nodes")) scope_nodes = value;
177 - else if(api_version >= 2 && !strcmp(name, "scope_contexts")) scope_contexts = value;
178 - else if(api_version >= 2 && !strcmp(name, "nodes")) nodes = value;
179 - else if(api_version >= 2 && !strcmp(name, "contexts")) contexts = value;
180 - else if(api_version >= 2 && !strcmp(name, "instances")) instances = value;
181 - else if(api_version >= 2 && !strcmp(name, "dimensions")) dimensions = value;
182 - else if(api_version >= 2 && !strcmp(name, "labels")) labels = value;
183 - else if(api_version >= 2 && !strcmp(name, "alerts")) alerts = value;
184 - else if(api_version >= 2 && (!strcmp(name, "group_by") || !strcmp(name, "group_by[0]"))) {
185 - group_by.group_by = group_by_parse(value);
186 - }
187 - else if(api_version >= 2 && (!strcmp(name, "group_by_label") || !strcmp(name, "group_by_label[0]"))) {
188 - group_by.group_by_label = value;
189 - }
190 - else if(api_version >= 2 && (!strcmp(name, "aggregation") || !strcmp(name, "aggregation[0]"))) {
191 - group_by.aggregation = group_by_aggregate_function_parse(value);
192 - }
193 -
194 - else if(!strcmp(name, "tier")) {
195 - tier = str2ul(value);
196 - if(tier < storage_tiers)
197 - options |= RRDR_OPTION_SELECTED_TIER;
198 - else
199 - tier = 0;
200 - }
201 - }
202 -
203 - if(options == 0)
204 - // the user did not set any options
205 - options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO | RRDR_OPTION_NONZERO;
206 - else
207 - // the user set some options, add also these
208 - options |= RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO;
209 -
210 - if(options & RRDR_OPTION_PERCENTAGE)
211 - options |= RRDR_OPTION_ABSOLUTE;
212 -
213 - if(options & RRDR_OPTION_DEBUG)
214 - options &= ~RRDR_OPTION_MINIFY;
215 -
216 - BUFFER *wb = w->response.data;
217 - buffer_flush(wb);
218 - wb->content_type = CT_APPLICATION_JSON;
219 -
220 - QUERY_WEIGHTS_REQUEST qwr = {
221 - .version = api_version,
222 - .host = (api_version == 1) ? NULL : host,
223 - .scope_nodes = scope_nodes,
224 - .scope_contexts = scope_contexts,
225 - .nodes = nodes,
226 - .contexts = contexts,
227 - .instances = instances,
228 - .dimensions = dimensions,
229 - .labels = labels,
230 - .alerts = alerts,
231 - .group_by = {
232 - .group_by = group_by.group_by,
233 - .group_by_label = group_by.group_by_label,
234 - .aggregation = group_by.aggregation,
235 - },
236 - .method = method,
237 - .format = format,
238 - .time_group_method = time_group_method,
239 - .time_group_options = time_group_options,
240 - .baseline_after = baseline_after,
241 - .baseline_before = baseline_before,
242 - .after = after,
243 - .before = before,
244 - .points = points,
245 - .options = options,
246 - .tier = tier,
247 - .timeout_ms = timeout_ms,
248 -
249 - .interrupt_callback = web_client_interrupt_callback,
250 - .interrupt_callback_data = w,
251 -
252 - .transaction = &w->transaction,
253 - };
254 -
255 - return web_api_v12_weights(wb, &qwr);
256 -}
120
121 bool web_client_interrupt_callback(void *data) {
122 struct web_client *w = data;
@@ -263,3 +126,54 @@ bool web_client_interrupt_callback(void *data) {
126
127 return sock_has_output_error(w->ofd);
128 }
129 +
130 +void nd_web_api_init(void) {
131 + contexts_alert_statuses_init();
132 + rrdr_options_init();
133 + contexts_options_init();
134 + datasource_formats_init();
135 + time_grouping_init();
136 +}
137 +
138 +
139 +bool request_source_is_cloud(const char *source) {
140 + return source && *source && strstartswith(source, "method=NC,");
141 +}
142 +
143 +void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source) {
144 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD))
145 + buffer_sprintf(source, "method=NC");
146 + else if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_BEARER))
147 + buffer_sprintf(source, "method=api-bearer");
148 + else
149 + buffer_sprintf(source, "method=api");
150 +
151 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
152 + buffer_strcat(source, ",role=god");
153 + else
154 + buffer_sprintf(source, ",role=%s", http_id2user_role(w->user_role));
155 +
156 + buffer_sprintf(source, ",permissions="HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)w->access);
157 +
158 + if(w->auth.client_name[0])
159 + buffer_sprintf(source, ",user=%s", w->auth.client_name);
160 +
161 + if(!uuid_is_null(w->auth.cloud_account_id)) {
162 + char uuid_str[UUID_COMPACT_STR_LEN];
163 + uuid_unparse_lower_compact(w->auth.cloud_account_id, uuid_str);
164 + buffer_sprintf(source, ",account=%s", uuid_str);
165 + }
166 +
167 + if(w->client_ip[0])
168 + buffer_sprintf(source, ",ip=%s", w->client_ip);
169 +
170 + if(w->forwarded_for)
171 + buffer_sprintf(source, ",forwarded_for=%s", w->forwarded_for);
172 +}
173 +
174 +void web_client_progress_functions_update(void *data, size_t done, size_t all) {
175 + // handle progress updates from the plugin
176 + struct web_client *w = data;
177 + query_progress_functions_update(&w->transaction, done, all);
178 +}
179 +
src/web/api/web_api.h
+20 -2
@@ -3,14 +3,28 @@
3 #ifndef NETDATA_WEB_API_H
4 #define NETDATA_WEB_API_H 1
5
6 +#define ENABLE_API_V1 1
7 +#define ENABLE_API_v2 1
8 +
9 +struct web_client;
10 +
11 #include "daemon/common.h"
12 +#include "maps/maps.h"
13 +#include "functions/functions.h"
14 +
15 #include "web/api/http_header.h"
16 #include "web/api/http_auth.h"
9 -#include "web/api/badges/web_buffer_svg.h"
10 -#include "web/api/ilove/ilove.h"
17 #include "web/api/formatters/rrd2json.h"
18 #include "web/api/queries/weights.h"
19
20 +void nd_web_api_init(void);
21 +
22 +bool request_source_is_cloud(const char *source);
23 +void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source);
24 +void web_client_progress_functions_update(void *data, size_t done, size_t all);
25 +
26 +void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key);
27 +
28 struct web_api_command {
29 const char *api;
30 uint32_t hash;
@@ -37,7 +51,11 @@ int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *ur
51
52 bool web_client_interrupt_callback(void *data);
53
54 +char *format_value_and_unit(char *value_string, size_t value_string_len,
55 + NETDATA_DOUBLE value, const char *units, int precision);
56 +
57 #include "web_api_v1.h"
58 #include "web_api_v2.h"
59 +#include "web_api_v3.h"
60
61 #endif //NETDATA_WEB_API_H
src/web/api/web_api_v1.c
+69 -1803
@@ -1,1958 +1,224 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "web_api_v1.h"
4 -
5 -char *api_secret;
6 -
7 -static struct {
8 - const char *name;
9 - uint32_t hash;
10 - RRDR_OPTIONS value;
11 -} rrdr_options[] = {
12 - { "nonzero" , 0 , RRDR_OPTION_NONZERO}
13 - , {"flip" , 0 , RRDR_OPTION_REVERSED}
14 - , {"reversed" , 0 , RRDR_OPTION_REVERSED}
15 - , {"reverse" , 0 , RRDR_OPTION_REVERSED}
16 - , {"jsonwrap" , 0 , RRDR_OPTION_JSON_WRAP}
17 - , {"min2max" , 0 , RRDR_OPTION_DIMS_MIN2MAX} // rrdr2value() only
18 - , {"average" , 0 , RRDR_OPTION_DIMS_AVERAGE} // rrdr2value() only
19 - , {"min" , 0 , RRDR_OPTION_DIMS_MIN} // rrdr2value() only
20 - , {"max" , 0 , RRDR_OPTION_DIMS_MAX} // rrdr2value() only
21 - , {"ms" , 0 , RRDR_OPTION_MILLISECONDS}
22 - , {"milliseconds" , 0 , RRDR_OPTION_MILLISECONDS}
23 - , {"absolute" , 0 , RRDR_OPTION_ABSOLUTE}
24 - , {"abs" , 0 , RRDR_OPTION_ABSOLUTE}
25 - , {"absolute_sum" , 0 , RRDR_OPTION_ABSOLUTE}
26 - , {"absolute-sum" , 0 , RRDR_OPTION_ABSOLUTE}
27 - , {"display_absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
28 - , {"display-absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
29 - , {"seconds" , 0 , RRDR_OPTION_SECONDS}
30 - , {"null2zero" , 0 , RRDR_OPTION_NULL2ZERO}
31 - , {"objectrows" , 0 , RRDR_OPTION_OBJECTSROWS}
32 - , {"google_json" , 0 , RRDR_OPTION_GOOGLE_JSON}
33 - , {"google-json" , 0 , RRDR_OPTION_GOOGLE_JSON}
34 - , {"percentage" , 0 , RRDR_OPTION_PERCENTAGE}
35 - , {"unaligned" , 0 , RRDR_OPTION_NOT_ALIGNED}
36 - , {"match_ids" , 0 , RRDR_OPTION_MATCH_IDS}
37 - , {"match-ids" , 0 , RRDR_OPTION_MATCH_IDS}
38 - , {"match_names" , 0 , RRDR_OPTION_MATCH_NAMES}
39 - , {"match-names" , 0 , RRDR_OPTION_MATCH_NAMES}
40 - , {"anomaly-bit" , 0 , RRDR_OPTION_ANOMALY_BIT}
41 - , {"selected-tier" , 0 , RRDR_OPTION_SELECTED_TIER}
42 - , {"raw" , 0 , RRDR_OPTION_RETURN_RAW}
43 - , {"jw-anomaly-rates" , 0 , RRDR_OPTION_RETURN_JWAR}
44 - , {"natural-points" , 0 , RRDR_OPTION_NATURAL_POINTS}
45 - , {"virtual-points" , 0 , RRDR_OPTION_VIRTUAL_POINTS}
46 - , {"all-dimensions" , 0 , RRDR_OPTION_ALL_DIMENSIONS}
47 - , {"details" , 0 , RRDR_OPTION_SHOW_DETAILS}
48 - , {"debug" , 0 , RRDR_OPTION_DEBUG}
49 - , {"plan" , 0 , RRDR_OPTION_DEBUG}
50 - , {"minify" , 0 , RRDR_OPTION_MINIFY}
51 - , {"group-by-labels" , 0 , RRDR_OPTION_GROUP_BY_LABELS}
52 - , {"label-quotes" , 0 , RRDR_OPTION_LABEL_QUOTES}
53 - , {NULL , 0 , 0}
54 -};
55 -
56 -static struct {
57 - const char *name;
58 - uint32_t hash;
59 - CONTEXTS_V2_OPTIONS value;
60 -} contexts_v2_options[] = {
61 - {"minify" , 0 , CONTEXT_V2_OPTION_MINIFY}
62 - , {"debug" , 0 , CONTEXT_V2_OPTION_DEBUG}
63 - , {"config" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS}
64 - , {"instances" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES}
65 - , {"values" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_VALUES}
66 - , {"summary" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY}
67 - , {NULL , 0 , 0}
68 -};
69 -
70 -static struct {
71 - const char *name;
72 - uint32_t hash;
73 - CONTEXTS_V2_ALERT_STATUS value;
74 -} contexts_v2_alert_status[] = {
75 - {"uninitialized" , 0 , CONTEXT_V2_ALERT_UNINITIALIZED}
76 - , {"undefined" , 0 , CONTEXT_V2_ALERT_UNDEFINED}
77 - , {"clear" , 0 , CONTEXT_V2_ALERT_CLEAR}
78 - , {"raised" , 0 , CONTEXT_V2_ALERT_RAISED}
79 - , {"active" , 0 , CONTEXT_V2_ALERT_RAISED}
80 - , {"warning" , 0 , CONTEXT_V2_ALERT_WARNING}
81 - , {"critical" , 0 , CONTEXT_V2_ALERT_CRITICAL}
82 - , {NULL , 0 , 0}
83 -};
84 -
85 -static struct {
86 - const char *name;
87 - uint32_t hash;
88 - DATASOURCE_FORMAT value;
89 -} api_v1_data_formats[] = {
90 - { DATASOURCE_FORMAT_DATATABLE_JSON , 0 , DATASOURCE_DATATABLE_JSON}
91 - , {DATASOURCE_FORMAT_DATATABLE_JSONP, 0 , DATASOURCE_DATATABLE_JSONP}
92 - , {DATASOURCE_FORMAT_JSON , 0 , DATASOURCE_JSON}
93 - , {DATASOURCE_FORMAT_JSON2 , 0 , DATASOURCE_JSON2}
94 - , {DATASOURCE_FORMAT_JSONP , 0 , DATASOURCE_JSONP}
95 - , {DATASOURCE_FORMAT_SSV , 0 , DATASOURCE_SSV}
96 - , {DATASOURCE_FORMAT_CSV , 0 , DATASOURCE_CSV}
97 - , {DATASOURCE_FORMAT_TSV , 0 , DATASOURCE_TSV}
98 - , {"tsv-excel" , 0 , DATASOURCE_TSV}
99 - , {DATASOURCE_FORMAT_HTML , 0 , DATASOURCE_HTML}
100 - , {DATASOURCE_FORMAT_JS_ARRAY , 0 , DATASOURCE_JS_ARRAY}
101 - , {DATASOURCE_FORMAT_SSV_COMMA , 0 , DATASOURCE_SSV_COMMA}
102 - , {DATASOURCE_FORMAT_CSV_JSON_ARRAY , 0 , DATASOURCE_CSV_JSON_ARRAY}
103 - , {DATASOURCE_FORMAT_CSV_MARKDOWN , 0 , DATASOURCE_CSV_MARKDOWN}
104 -
105 - // terminator
106 - , {NULL, 0, 0}
107 -};
108 -
109 -static struct {
110 - const char *name;
111 - uint32_t hash;
112 - DATASOURCE_FORMAT value;
113 -} api_v1_data_google_formats[] = {
114 - // this is not an error - when Google requests json, it expects javascript
115 - // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source#responseformat
116 - {"json", 0, DATASOURCE_DATATABLE_JSONP}
117 - , {"html", 0, DATASOURCE_HTML}
118 - , {"csv", 0, DATASOURCE_CSV}
119 - , {"tsv-excel", 0, DATASOURCE_TSV}
120 -
121 - // terminator
122 - , {NULL, 0, 0}
123 -};
124 -
125 -void web_client_api_v1_init(void) {
126 - int i;
127 -
128 - for(i = 0; contexts_v2_alert_status[i].name ; i++)
129 - contexts_v2_alert_status[i].hash = simple_hash(contexts_v2_alert_status[i].name);
130 -
131 - for(i = 0; rrdr_options[i].name ; i++)
132 - rrdr_options[i].hash = simple_hash(rrdr_options[i].name);
133 -
134 - for(i = 0; contexts_v2_options[i].name ; i++)
135 - contexts_v2_options[i].hash = simple_hash(contexts_v2_options[i].name);
136 -
137 - for(i = 0; api_v1_data_formats[i].name ; i++)
138 - api_v1_data_formats[i].hash = simple_hash(api_v1_data_formats[i].name);
139 -
140 - for(i = 0; api_v1_data_google_formats[i].name ; i++)
141 - api_v1_data_google_formats[i].hash = simple_hash(api_v1_data_google_formats[i].name);
142 -
143 - time_grouping_init();
144 -
145 - nd_uuid_t uuid;
146 -
147 - // generate
148 - uuid_generate(uuid);
149 -
150 - // unparse (to string)
151 - char uuid_str[37];
152 - uuid_unparse_lower(uuid, uuid_str);
153 -}
154 -
155 -char *get_mgmt_api_key(void) {
156 - char filename[FILENAME_MAX + 1];
157 - snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir);
158 - char *api_key_filename=config_get(CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
159 - static char guid[GUID_LEN + 1] = "";
160 -
161 - if(likely(guid[0]))
162 - return guid;
163 -
164 - // read it from disk
165 - int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC);
166 - if(fd != -1) {
167 - char buf[GUID_LEN + 1];
168 - if(read(fd, buf, GUID_LEN) != GUID_LEN)
169 - netdata_log_error("Failed to read management API key from '%s'", api_key_filename);
170 - else {
171 - buf[GUID_LEN] = '\0';
172 - if(regenerate_guid(buf, guid) == -1) {
173 - netdata_log_error("Failed to validate management API key '%s' from '%s'.",
174 - buf, api_key_filename);
175 -
176 - guid[0] = '\0';
177 - }
178 - }
179 - close(fd);
180 - }
181 -
182 - // generate a new one?
183 - if(!guid[0]) {
184 - nd_uuid_t uuid;
185 -
186 - uuid_generate_time(uuid);
187 - uuid_unparse_lower(uuid, guid);
188 - guid[GUID_LEN] = '\0';
189 -
190 - // save it
191 - fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC | O_CLOEXEC, 444);
192 - if(fd == -1) {
193 - 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);
194 - goto temp_key;
195 - }
196 -
197 - if(write(fd, guid, GUID_LEN) != GUID_LEN) {
198 - 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);
199 - close(fd);
200 - goto temp_key;
201 - }
202 -
203 - close(fd);
204 - }
205 -
206 - return guid;
207 -
208 -temp_key:
209 - netdata_log_info("You can still continue to use the alarm management API using the authorization token %s during this Netdata session only.", guid);
210 - return guid;
211 -}
212 -
213 -void web_client_api_v1_management_init(void) {
214 - api_secret = get_mgmt_api_key();
215 -}
216 -
217 -inline RRDR_OPTIONS rrdr_options_parse_one(const char *o) {
218 - RRDR_OPTIONS ret = 0;
219 -
220 - if(!o || !*o) return ret;
221 -
222 - uint32_t hash = simple_hash(o);
223 - int i;
224 - for(i = 0; rrdr_options[i].name ; i++) {
225 - if (unlikely(hash == rrdr_options[i].hash && !strcmp(o, rrdr_options[i].name))) {
226 - ret |= rrdr_options[i].value;
227 - break;
228 - }
229 - }
230 -
231 - return ret;
232 -}
233 -
234 -inline RRDR_OPTIONS rrdr_options_parse(char *o) {
235 - RRDR_OPTIONS ret = 0;
236 - char *tok;
237 -
238 - while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
239 - if(!*tok) continue;
240 - ret |= rrdr_options_parse_one(tok);
241 - }
242 -
243 - return ret;
244 -}
245 -
246 -inline CONTEXTS_V2_OPTIONS web_client_api_request_v2_context_options(char *o) {
247 - CONTEXTS_V2_OPTIONS ret = 0;
248 - char *tok;
249 -
250 - while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
251 - if(!*tok) continue;
252 -
253 - uint32_t hash = simple_hash(tok);
254 - int i;
255 - for(i = 0; contexts_v2_options[i].name ; i++) {
256 - if (unlikely(hash == contexts_v2_options[i].hash && !strcmp(tok, contexts_v2_options[i].name))) {
257 - ret |= contexts_v2_options[i].value;
258 - break;
259 - }
260 - }
261 - }
262 -
263 - return ret;
264 -}
265 -
266 -inline CONTEXTS_V2_ALERT_STATUS web_client_api_request_v2_alert_status(char *o) {
267 - CONTEXTS_V2_ALERT_STATUS ret = 0;
268 - char *tok;
269 -
270 - while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
271 - if(!*tok) continue;
272 -
273 - uint32_t hash = simple_hash(tok);
274 - int i;
275 - for(i = 0; contexts_v2_alert_status[i].name ; i++) {
276 - if (unlikely(hash == contexts_v2_alert_status[i].hash && !strcmp(tok, contexts_v2_alert_status[i].name))) {
277 - ret |= contexts_v2_alert_status[i].value;
278 - break;
279 - }
280 - }
281 - }
282 -
283 - return ret;
284 -}
285 -
286 -void web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_ALERT_STATUS options) {
287 - buffer_json_member_add_array(wb, key);
288 -
289 - RRDR_OPTIONS used = 0; // to prevent adding duplicates
290 - for(int i = 0; contexts_v2_alert_status[i].name ; i++) {
291 - if (unlikely((contexts_v2_alert_status[i].value & options) && !(contexts_v2_alert_status[i].value & used))) {
292 - const char *name = contexts_v2_alert_status[i].name;
293 - used |= contexts_v2_alert_status[i].value;
294 -
295 - buffer_json_add_array_item_string(wb, name);
296 - }
297 - }
298 -
299 - buffer_json_array_close(wb);
300 -}
301 -
302 -void web_client_api_request_v2_contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_OPTIONS options) {
303 - buffer_json_member_add_array(wb, key);
304 -
305 - RRDR_OPTIONS used = 0; // to prevent adding duplicates
306 - for(int i = 0; contexts_v2_options[i].name ; i++) {
307 - if (unlikely((contexts_v2_options[i].value & options) && !(contexts_v2_options[i].value & used))) {
308 - const char *name = contexts_v2_options[i].name;
309 - used |= contexts_v2_options[i].value;
310 -
311 - buffer_json_add_array_item_string(wb, name);
312 - }
313 - }
314 -
315 - buffer_json_array_close(wb);
316 -}
317 -
318 -void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options) {
319 - buffer_json_member_add_array(wb, key);
320 -
321 - RRDR_OPTIONS used = 0; // to prevent adding duplicates
322 - for(int i = 0; rrdr_options[i].name ; i++) {
323 - if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
324 - const char *name = rrdr_options[i].name;
325 - used |= rrdr_options[i].value;
326 -
327 - buffer_json_add_array_item_string(wb, name);
328 - }
329 - }
330 -
331 - buffer_json_array_close(wb);
332 -}
333 -
334 -void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options) {
335 - RRDR_OPTIONS used = 0; // to prevent adding duplicates
336 - size_t added = 0;
337 - for(int i = 0; rrdr_options[i].name ; i++) {
338 - if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
339 - const char *name = rrdr_options[i].name;
340 - used |= rrdr_options[i].value;
341 -
342 - if(added++) buffer_strcat(wb, " ");
343 - buffer_strcat(wb, name);
344 - }
345 - }
346 -}
347 -
348 -void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options) {
349 - char *write = buf;
350 - char *end = &buf[size - 1];
351 -
352 - RRDR_OPTIONS used = 0; // to prevent adding duplicates
353 - int added = 0;
354 - for(int i = 0; rrdr_options[i].name ; i++) {
355 - if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
356 - const char *name = rrdr_options[i].name;
357 - used |= rrdr_options[i].value;
358 -
359 - if(added && write < end)
360 - *write++ = ',';
361 -
362 - while(*name && write < end)
363 - *write++ = *name++;
364 -
365 - added++;
366 - }
367 - }
368 - *write = *end = '\0';
369 -}
370 -
371 -inline uint32_t web_client_api_request_v1_data_format(char *name) {
372 - uint32_t hash = simple_hash(name);
373 - int i;
374 -
375 - for(i = 0; api_v1_data_formats[i].name ; i++) {
376 - if (unlikely(hash == api_v1_data_formats[i].hash && !strcmp(name, api_v1_data_formats[i].name))) {
377 - return api_v1_data_formats[i].value;
378 - }
379 - }
380 -
381 - return DATASOURCE_JSON;
382 -}
383 -
384 -inline uint32_t web_client_api_request_v1_data_google_format(char *name) {
385 - uint32_t hash = simple_hash(name);
386 - int i;
387 -
388 - for(i = 0; api_v1_data_google_formats[i].name ; i++) {
389 - if (unlikely(hash == api_v1_data_google_formats[i].hash && !strcmp(name, api_v1_data_google_formats[i].name))) {
390 - return api_v1_data_google_formats[i].value;
391 - }
392 - }
393 -
394 - return DATASOURCE_JSON;
395 -}
396 -
397 -int web_client_api_request_v1_alarms_select (char *url) {
398 - int all = 0;
399 - while(url) {
400 - char *value = strsep_skip_consecutive_separators(&url, "&");
401 - if (!value || !*value) continue;
402 -
403 - if(!strcmp(value, "all") || !strcmp(value, "all=true")) all = 1;
404 - else if(!strcmp(value, "active") || !strcmp(value, "active=true")) all = 0;
405 - }
406 -
407 - return all;
408 -}
409 -
410 -inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url) {
411 - int all = web_client_api_request_v1_alarms_select(url);
412 -
413 - buffer_flush(w->response.data);
414 - w->response.data->content_type = CT_APPLICATION_JSON;
415 - health_alarms2json(host, w->response.data, all);
416 - buffer_no_cacheable(w->response.data);
417 - return HTTP_RESP_OK;
418 -}
419 -
420 -inline int web_client_api_request_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url) {
421 - int all = web_client_api_request_v1_alarms_select(url);
422 -
423 - buffer_flush(w->response.data);
424 - w->response.data->content_type = CT_APPLICATION_JSON;
425 - health_alarms_values2json(host, w->response.data, all);
426 - buffer_no_cacheable(w->response.data);
427 - return HTTP_RESP_OK;
428 -}
429 -
430 -inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url) {
431 - RRDCALC_STATUS status = RRDCALC_STATUS_RAISED;
432 - BUFFER *contexts = NULL;
433 -
434 - buffer_flush(w->response.data);
435 - buffer_sprintf(w->response.data, "[");
436 -
437 - while(url) {
438 - char *value = strsep_skip_consecutive_separators(&url, "&");
439 - if(!value || !*value) continue;
440 -
441 - char *name = strsep_skip_consecutive_separators(&value, "=");
442 - if(!name || !*name) continue;
443 - if(!value || !*value) continue;
444 -
445 - netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 alarm_count query param '%s' with value '%s'", w->id, name, value);
446 -
447 - char* p = value;
448 - if(!strcmp(name, "status")) {
449 - while ((*p = toupper(*p))) p++;
450 - if (!strcmp("CRITICAL", value)) status = RRDCALC_STATUS_CRITICAL;
451 - else if (!strcmp("WARNING", value)) status = RRDCALC_STATUS_WARNING;
452 - else if (!strcmp("UNINITIALIZED", value)) status = RRDCALC_STATUS_UNINITIALIZED;
453 - else if (!strcmp("UNDEFINED", value)) status = RRDCALC_STATUS_UNDEFINED;
454 - else if (!strcmp("REMOVED", value)) status = RRDCALC_STATUS_REMOVED;
455 - else if (!strcmp("CLEAR", value)) status = RRDCALC_STATUS_CLEAR;
456 - }
457 - else if(!strcmp(name, "context") || !strcmp(name, "ctx")) {
458 - if(!contexts) contexts = buffer_create(255, &netdata_buffers_statistics.buffers_api);
459 - buffer_strcat(contexts, "|");
460 - buffer_strcat(contexts, value);
461 - }
462 - }
463 -
464 - health_aggregate_alarms(host, w->response.data, contexts, status);
465 -
466 - buffer_sprintf(w->response.data, "]\n");
467 - w->response.data->content_type = CT_APPLICATION_JSON;
468 - buffer_no_cacheable(w->response.data);
469 -
470 - buffer_free(contexts);
471 - return 200;
472 -}
473 -
474 -inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
475 - time_t after = 0;
476 - char *chart = NULL;
477 -
478 - while(url) {
479 - char *value = strsep_skip_consecutive_separators(&url, "&");
480 - if (!value || !*value) continue;
481 -
482 - char *name = strsep_skip_consecutive_separators(&value, "=");
483 - if(!name || !*name) continue;
484 - if(!value || !*value) continue;
485 -
486 - if (!strcmp(name, "after")) after = (time_t) strtoul(value, NULL, 0);
487 - else if (!strcmp(name, "chart")) chart = value;
488 - }
489 -
490 - buffer_flush(w->response.data);
491 - w->response.data->content_type = CT_APPLICATION_JSON;
492 - sql_health_alarm_log2json(host, w->response.data, after, chart);
493 - return HTTP_RESP_OK;
494 -}
495 -
496 -inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) {
497 - int ret = HTTP_RESP_BAD_REQUEST;
498 - char *chart = NULL;
499 -
500 - buffer_flush(w->response.data);
501 -
502 - while(url) {
503 - char *value = strsep_skip_consecutive_separators(&url, "&");
504 - if(!value || !*value) continue;
505 -
506 - char *name = strsep_skip_consecutive_separators(&value, "=");
507 - if(!name || !*name) continue;
508 - if(!value || !*value) continue;
509 -
510 - // name and value are now the parameters
511 - // they are not null and not empty
512 -
513 - if(!strcmp(name, "chart")) chart = value;
514 - //else {
515 - /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
516 - // goto cleanup;
517 - //}
518 - }
519 -
520 - if(!chart || !*chart) {
521 - buffer_sprintf(w->response.data, "No chart id is given at the request.");
522 - goto cleanup;
523 - }
524 -
525 - RRDSET *st = rrdset_find(host, chart);
526 - if(!st) st = rrdset_find_byname(host, chart);
527 - if(!st) {
528 - buffer_strcat(w->response.data, "Chart is not found: ");
529 - buffer_strcat_htmlescape(w->response.data, chart);
530 - ret = HTTP_RESP_NOT_FOUND;
531 - goto cleanup;
532 - }
533 -
534 - w->response.data->content_type = CT_APPLICATION_JSON;
535 - st->last_accessed_time_s = now_realtime_sec();
536 - callback(st, w->response.data);
537 - return HTTP_RESP_OK;
538 -
539 - cleanup:
540 - return ret;
541 -}
542 -
543 -static inline int web_client_api_request_variable(RRDHOST *host, struct web_client *w, char *url) {
544 - int ret = HTTP_RESP_BAD_REQUEST;
545 - char *chart = NULL;
546 - char *variable = NULL;
547 -
548 - buffer_flush(w->response.data);
549 -
550 - while(url) {
551 - char *value = strsep_skip_consecutive_separators(&url, "&");
552 - if(!value || !*value) continue;
553 -
554 - char *name = strsep_skip_consecutive_separators(&value, "=");
555 - if(!name || !*name) continue;
556 - if(!value || !*value) continue;
557 -
558 - // name and value are now the parameters
559 - // they are not null and not empty
560 -
561 - if(!strcmp(name, "chart")) chart = value;
562 - else if(!strcmp(name, "variable")) variable = value;
563 - }
564 -
565 - if(!chart || !*chart || !variable || !*variable) {
566 - buffer_sprintf(w->response.data, "A chart= and a variable= are required.");
567 - goto cleanup;
568 - }
569 -
570 - RRDSET *st = rrdset_find(host, chart);
571 - if(!st) st = rrdset_find_byname(host, chart);
572 - if(!st) {
573 - buffer_strcat(w->response.data, "Chart is not found: ");
574 - buffer_strcat_htmlescape(w->response.data, chart);
575 - ret = HTTP_RESP_NOT_FOUND;
576 - goto cleanup;
577 - }
578 -
579 - w->response.data->content_type = CT_APPLICATION_JSON;
580 - st->last_accessed_time_s = now_realtime_sec();
581 - alert_variable_lookup_trace(host, st, variable, w->response.data);
582 -
583 - return HTTP_RESP_OK;
584 -
585 -cleanup:
586 - return ret;
587 -}
588 -
589 -inline int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
590 - return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json);
591 -}
592 -
593 -static int web_client_api_request_v1_context(RRDHOST *host, struct web_client *w, char *url) {
594 - char *context = NULL;
595 - RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
596 - time_t after = 0, before = 0;
597 - const char *chart_label_key = NULL, *chart_labels_filter = NULL;
598 - BUFFER *dimensions = NULL;
599 -
600 - buffer_flush(w->response.data);
601 -
602 - while(url) {
603 - char *value = strsep_skip_consecutive_separators(&url, "&");
604 - if(!value || !*value) continue;
605 -
606 - char *name = strsep_skip_consecutive_separators(&value, "=");
607 - if(!name || !*name) continue;
608 - if(!value || !*value) continue;
609 -
610 - // name and value are now the parameters
611 - // they are not null and not empty
612 -
613 - if(!strcmp(name, "context") || !strcmp(name, "ctx")) context = value;
614 - else if(!strcmp(name, "after")) after = str2l(value);
615 - else if(!strcmp(name, "before")) before = str2l(value);
616 - else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
617 - else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
618 - else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
619 - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
620 - if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
621 - buffer_strcat(dimensions, "|");
622 - buffer_strcat(dimensions, value);
623 - }
624 - }
625 -
626 - if(!context || !*context) {
627 - buffer_sprintf(w->response.data, "No context is given at the request.");
628 - return HTTP_RESP_BAD_REQUEST;
629 - }
630 -
631 - SIMPLE_PATTERN *chart_label_key_pattern = NULL;
632 - SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
633 - SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
634 -
635 - if(chart_label_key)
636 - chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
637 -
638 - if(chart_labels_filter)
639 - chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
640 - true);
641 -
642 - if(dimensions) {
643 - chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
644 - SIMPLE_PATTERN_EXACT, true);
645 - buffer_free(dimensions);
646 - }
647 -
648 - w->response.data->content_type = CT_APPLICATION_JSON;
649 - int ret = rrdcontext_to_json(host, w->response.data, after, before, options, context, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
650 -
651 - simple_pattern_free(chart_label_key_pattern);
652 - simple_pattern_free(chart_labels_filter_pattern);
653 - simple_pattern_free(chart_dimensions_pattern);
654 -
655 - return ret;
656 -}
657 -
658 -static int web_client_api_request_v1_contexts(RRDHOST *host, struct web_client *w, char *url) {
659 - RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
660 - time_t after = 0, before = 0;
661 - const char *chart_label_key = NULL, *chart_labels_filter = NULL;
662 - BUFFER *dimensions = NULL;
663 -
664 - buffer_flush(w->response.data);
665 -
666 - while(url) {
667 - char *value = strsep_skip_consecutive_separators(&url, "&");
668 - if(!value || !*value) continue;
669 -
670 - char *name = strsep_skip_consecutive_separators(&value, "=");
671 - if(!name || !*name) continue;
672 - if(!value || !*value) continue;
673 -
674 - // name and value are now the parameters
675 - // they are not null and not empty
676 -
677 - if(!strcmp(name, "after")) after = str2l(value);
678 - else if(!strcmp(name, "before")) before = str2l(value);
679 - else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
680 - else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
681 - else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
682 - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
683 - if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
684 - buffer_strcat(dimensions, "|");
685 - buffer_strcat(dimensions, value);
686 - }
687 - }
688 -
689 - SIMPLE_PATTERN *chart_label_key_pattern = NULL;
690 - SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
691 - SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
692 -
693 - if(chart_label_key)
694 - chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
695 -
696 - if(chart_labels_filter)
697 - chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
698 - true);
699 -
700 - if(dimensions) {
701 - chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
702 - SIMPLE_PATTERN_EXACT, true);
703 - buffer_free(dimensions);
704 - }
705 -
706 - w->response.data->content_type = CT_APPLICATION_JSON;
707 - int ret = rrdcontexts_to_json(host, w->response.data, after, before, options, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
708 -
709 - simple_pattern_free(chart_label_key_pattern);
710 - simple_pattern_free(chart_labels_filter_pattern);
711 - simple_pattern_free(chart_dimensions_pattern);
712 -
713 - return ret;
714 -}
715 -
716 -inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
717 - (void)url;
718 -
719 - buffer_flush(w->response.data);
720 - w->response.data->content_type = CT_APPLICATION_JSON;
721 - charts2json(host, w->response.data);
722 - return HTTP_RESP_OK;
723 -}
724 -
725 -inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
726 - return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
727 -}
728 -
729 -// returns the HTTP code
730 -static inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) {
731 - netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
732 -
733 - int ret = HTTP_RESP_BAD_REQUEST;
734 - BUFFER *dimensions = NULL;
735 -
736 - buffer_flush(w->response.data);
737 -
738 - char *google_version = "0.6",
739 - *google_reqId = "0",
740 - *google_sig = "0",
741 - *google_out = "json",
742 - *responseHandler = NULL,
743 - *outFileName = NULL;
744 -
745 - time_t last_timestamp_in_data = 0, google_timestamp = 0;
746 -
747 - char *chart = NULL;
748 - char *before_str = NULL;
749 - char *after_str = NULL;
750 - char *group_time_str = NULL;
751 - char *points_str = NULL;
752 - char *timeout_str = NULL;
753 - char *context = NULL;
754 - char *chart_label_key = NULL;
755 - char *chart_labels_filter = NULL;
756 - char *group_options = NULL;
757 - size_t tier = 0;
758 - RRDR_TIME_GROUPING group = RRDR_GROUPING_AVERAGE;
759 - DATASOURCE_FORMAT format = DATASOURCE_JSON;
760 - RRDR_OPTIONS options = 0;
761 -
762 - while(url) {
763 - char *value = strsep_skip_consecutive_separators(&url, "&");
764 - if(!value || !*value) continue;
765 -
766 - char *name = strsep_skip_consecutive_separators(&value, "=");
767 - if(!name || !*name) continue;
768 - if(!value || !*value) continue;
769 -
770 - netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
771 -
772 - // name and value are now the parameters
773 - // they are not null and not empty
774 -
775 - if(!strcmp(name, "context")) context = value;
776 - else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
777 - else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
778 - else if(!strcmp(name, "chart")) chart = value;
779 - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
780 - if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
781 - buffer_strcat(dimensions, "|");
782 - buffer_strcat(dimensions, value);
783 - }
784 - else if(!strcmp(name, "show_dimensions")) options |= RRDR_OPTION_ALL_DIMENSIONS;
785 - else if(!strcmp(name, "after")) after_str = value;
786 - else if(!strcmp(name, "before")) before_str = value;
787 - else if(!strcmp(name, "points")) points_str = value;
788 - else if(!strcmp(name, "timeout")) timeout_str = value;
789 - else if(!strcmp(name, "gtime")) group_time_str = value;
790 - else if(!strcmp(name, "group_options")) group_options = value;
791 - else if(!strcmp(name, "group")) {
792 - group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
793 - }
794 - else if(!strcmp(name, "format")) {
795 - format = web_client_api_request_v1_data_format(value);
796 - }
797 - else if(!strcmp(name, "options")) {
798 - options |= rrdr_options_parse(value);
799 - }
800 - else if(!strcmp(name, "callback")) {
801 - responseHandler = value;
802 - }
803 - else if(!strcmp(name, "filename")) {
804 - outFileName = value;
805 - }
806 - else if(!strcmp(name, "tqx")) {
807 - // parse Google Visualization API options
808 - // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
809 - char *tqx_name, *tqx_value;
810 -
811 - while(value) {
812 - tqx_value = strsep_skip_consecutive_separators(&value, ";");
813 - if(!tqx_value || !*tqx_value) continue;
814 -
815 - tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
816 - if(!tqx_name || !*tqx_name) continue;
817 - if(!tqx_value || !*tqx_value) continue;
818 -
819 - if(!strcmp(tqx_name, "version"))
820 - google_version = tqx_value;
821 - else if(!strcmp(tqx_name, "reqId"))
822 - google_reqId = tqx_value;
823 - else if(!strcmp(tqx_name, "sig")) {
824 - google_sig = tqx_value;
825 - google_timestamp = strtoul(google_sig, NULL, 0);
826 - }
827 - else if(!strcmp(tqx_name, "out")) {
828 - google_out = tqx_value;
829 - format = web_client_api_request_v1_data_google_format(google_out);
830 - }
831 - else if(!strcmp(tqx_name, "responseHandler"))
832 - responseHandler = tqx_value;
833 - else if(!strcmp(tqx_name, "outFileName"))
834 - outFileName = tqx_value;
835 - }
836 - }
837 - else if(!strcmp(name, "tier")) {
838 - tier = str2ul(value);
839 - if(tier < storage_tiers)
840 - options |= RRDR_OPTION_SELECTED_TIER;
841 - else
842 - tier = 0;
843 - }
844 - }
845 -
846 - // validate the google parameters given
847 - fix_google_param(google_out);
848 - fix_google_param(google_sig);
849 - fix_google_param(google_reqId);
850 - fix_google_param(google_version);
851 - fix_google_param(responseHandler);
852 - fix_google_param(outFileName);
853 -
854 - RRDSET *st = NULL;
855 - ONEWAYALLOC *owa = onewayalloc_create(0);
856 - QUERY_TARGET *qt = NULL;
857 -
858 - if(!is_valid_sp(chart) && !is_valid_sp(context)) {
859 - buffer_sprintf(w->response.data, "No chart or context is given.");
860 - goto cleanup;
861 - }
862 -
863 - if(chart && !context) {
864 - // check if this is a specific chart
865 - st = rrdset_find(host, chart);
866 - if (!st) st = rrdset_find_byname(host, chart);
867 - }
868 -
869 - long long before = (before_str && *before_str)?str2l(before_str):0;
870 - long long after = (after_str && *after_str) ?str2l(after_str):-600;
871 - int points = (points_str && *points_str)?str2i(points_str):0;
872 - int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
873 - long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0;
874 -
875 - QUERY_TARGET_REQUEST qtr = {
876 - .version = 1,
877 - .after = after,
878 - .before = before,
879 - .host = host,
880 - .st = st,
881 - .nodes = NULL,
882 - .contexts = context,
883 - .instances = chart,
884 - .dimensions = (dimensions)?buffer_tostring(dimensions):NULL,
885 - .timeout_ms = timeout,
886 - .points = points,
887 - .format = format,
888 - .options = options,
889 - .time_group_method = group,
890 - .time_group_options = group_options,
891 - .resampling_time = group_time,
892 - .tier = tier,
893 - .chart_label_key = chart_label_key,
894 - .labels = chart_labels_filter,
895 - .query_source = QUERY_SOURCE_API_DATA,
896 - .priority = STORAGE_PRIORITY_NORMAL,
897 - .interrupt_callback = web_client_interrupt_callback,
898 - .interrupt_callback_data = w,
899 - .transaction = &w->transaction,
900 - };
901 - qt = query_target_create(&qtr);
902 -
903 - if(!qt || !qt->query.used) {
904 - buffer_sprintf(w->response.data, "No metrics where matched to query.");
905 - ret = HTTP_RESP_NOT_FOUND;
906 - goto cleanup;
907 - }
908 -
909 - web_client_timeout_checkpoint_set(w, timeout);
910 - if(web_client_timeout_checkpoint_and_check(w, NULL)) {
911 - ret = w->response.code;
912 - goto cleanup;
913 - }
914 -
915 - if(outFileName && *outFileName) {
916 - buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
917 - netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
918 - }
919 -
920 - if(format == DATASOURCE_DATATABLE_JSONP) {
921 - if(responseHandler == NULL)
922 - responseHandler = "google.visualization.Query.setResponse";
923 -
924 - netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
925 - w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
926 - );
927 -
928 - buffer_sprintf(
929 - w->response.data,
930 - "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
931 - responseHandler,
932 - google_version,
933 - google_reqId,
934 - (int64_t)(st ? st->last_updated.tv_sec : 0));
935 - }
936 - else if(format == DATASOURCE_JSONP) {
937 - if(responseHandler == NULL)
938 - responseHandler = "callback";
939 -
940 - buffer_strcat(w->response.data, responseHandler);
941 - buffer_strcat(w->response.data, "(");
942 - }
943 -
944 - ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
945 -
946 - if(format == DATASOURCE_DATATABLE_JSONP) {
947 - if(google_timestamp < last_timestamp_in_data)
948 - buffer_strcat(w->response.data, "});");
949 -
950 - else {
951 - // the client already has the latest data
952 - buffer_flush(w->response.data);
953 - buffer_sprintf(w->response.data,
954 - "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
955 - responseHandler, google_version, google_reqId);
956 - }
957 - }
958 - else if(format == DATASOURCE_JSONP)
959 - buffer_strcat(w->response.data, ");");
960 -
961 - if(qt->internal.relative)
962 - buffer_no_cacheable(w->response.data);
963 - else
964 - buffer_cacheable(w->response.data);
965 -
966 -cleanup:
967 - query_target_release(qt);
968 - onewayalloc_destroy(owa);
969 - buffer_free(dimensions);
970 - return ret;
971 -}
972 -
973 -// Pings a netdata server:
974 -// /api/v1/registry?action=hello
975 -//
976 -// Access to a netdata registry:
977 -// /api/v1/registry?action=access&machine=${machine_guid}&name=${hostname}&url=${url}
978 -//
979 -// Delete from a netdata registry:
980 -// /api/v1/registry?action=delete&machine=${machine_guid}&name=${hostname}&url=${url}&delete_url=${delete_url}
981 -//
982 -// Search for the URLs of a machine:
983 -// /api/v1/registry?action=search&for=${machine_guid}
984 -//
985 -// Impersonate:
986 -// /api/v1/registry?action=switch&machine=${machine_guid}&name=${hostname}&url=${url}&to=${new_person_guid}
987 -inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
988 - static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
989 - hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
990 - hash_to = 0 /*, hash_redirects = 0 */;
991 -
992 - if(unlikely(!hash_action)) {
993 - hash_action = simple_hash("action");
994 - hash_access = simple_hash("access");
995 - hash_hello = simple_hash("hello");
996 - hash_delete = simple_hash("delete");
997 - hash_search = simple_hash("search");
998 - hash_switch = simple_hash("switch");
999 - hash_machine = simple_hash("machine");
1000 - hash_url = simple_hash("url");
1001 - hash_name = simple_hash("name");
1002 - hash_delete_url = simple_hash("delete_url");
1003 - hash_for = simple_hash("for");
1004 - hash_to = simple_hash("to");
1005 -/*
1006 - hash_redirects = simple_hash("redirects");
1007 -*/
1008 - }
1009 -
1010 - netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
1011 -
1012 - // TODO
1013 - // The browser may send multiple cookies with our id
1014 -
1015 - char person_guid[UUID_STR_LEN] = "";
1016 - char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
1017 - if(cookie)
1018 - strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], UUID_STR_LEN - 1);
1019 - else if(!extract_bearer_token_from_request(w, person_guid, sizeof(person_guid)))
1020 - person_guid[0] = '\0';
1021 -
1022 - char action = '\0';
1023 - char *machine_guid = NULL,
1024 - *machine_url = NULL,
1025 - *url_name = NULL,
1026 - *search_machine_guid = NULL,
1027 - *delete_url = NULL,
1028 - *to_person_guid = NULL;
1029 -/*
1030 - int redirects = 0;
1031 -*/
1032 -
1033 - // Don't cache registry responses
1034 - buffer_no_cacheable(w->response.data);
1035 -
1036 - while(url) {
1037 - char *value = strsep_skip_consecutive_separators(&url, "&");
1038 - if (!value || !*value) continue;
1039 -
1040 - char *name = strsep_skip_consecutive_separators(&value, "=");
1041 - if (!name || !*name) continue;
1042 - if (!value || !*value) continue;
1043 -
1044 - netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
1045 -
1046 - uint32_t hash = simple_hash(name);
1047 -
1048 - if(hash == hash_action && !strcmp(name, "action")) {
1049 - uint32_t vhash = simple_hash(value);
1050 -
1051 - if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
1052 - else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
1053 - else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
1054 - else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
1055 - else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
1056 -#ifdef NETDATA_INTERNAL_CHECKS
1057 - else netdata_log_error("unknown registry action '%s'", value);
1058 -#endif /* NETDATA_INTERNAL_CHECKS */
1059 - }
1060 -/*
1061 - else if(hash == hash_redirects && !strcmp(name, "redirects"))
1062 - redirects = atoi(value);
1063 -*/
1064 - else if(hash == hash_machine && !strcmp(name, "machine"))
1065 - machine_guid = value;
1066 -
1067 - else if(hash == hash_url && !strcmp(name, "url"))
1068 - machine_url = value;
1069 -
1070 - else if(action == 'A') {
1071 - if(hash == hash_name && !strcmp(name, "name"))
1072 - url_name = value;
1073 - }
1074 - else if(action == 'D') {
1075 - if(hash == hash_delete_url && !strcmp(name, "delete_url"))
1076 - delete_url = value;
1077 - }
1078 - else if(action == 'S') {
1079 - if(hash == hash_for && !strcmp(name, "for"))
1080 - search_machine_guid = value;
1081 - }
1082 - else if(action == 'W') {
1083 - if(hash == hash_to && !strcmp(name, "to"))
1084 - to_person_guid = value;
1085 - }
1086 -#ifdef NETDATA_INTERNAL_CHECKS
1087 - else netdata_log_error("unused registry URL parameter '%s' with value '%s'", name, value);
1088 -#endif /* NETDATA_INTERNAL_CHECKS */
1089 - }
1090 -
1091 - bool do_not_track = respect_web_browser_do_not_track_policy && web_client_has_donottrack(w);
1092 -
1093 - if(unlikely(action == 'H')) {
1094 - // HELLO request, dashboard ACL
1095 - analytics_log_dashboard();
1096 - if(unlikely(!http_can_access_dashboard(w)))
1097 - return web_client_permission_denied_acl(w);
1098 - }
1099 - else {
1100 - // everything else, registry ACL
1101 - if(unlikely(!http_can_access_registry(w)))
1102 - return web_client_permission_denied_acl(w);
1103 -
1104 - if(unlikely(do_not_track)) {
1105 - buffer_flush(w->response.data);
1106 - buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work.");
1107 - return HTTP_RESP_BAD_REQUEST;
1108 - }
1109 - }
1110 -
1111 - buffer_no_cacheable(w->response.data);
1112 -
1113 - switch(action) {
1114 - case 'A':
1115 - if(unlikely(!machine_guid || !machine_url || !url_name)) {
1116 - 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");
1117 - buffer_flush(w->response.data);
1118 - buffer_strcat(w->response.data, "Invalid registry Access request.");
1119 - return HTTP_RESP_BAD_REQUEST;
1120 - }
1121 -
1122 - web_client_enable_tracking_required(w);
1123 - return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
1124 -
1125 - case 'D':
1126 - if(unlikely(!machine_guid || !machine_url || !delete_url)) {
1127 - 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");
1128 - buffer_flush(w->response.data);
1129 - buffer_strcat(w->response.data, "Invalid registry Delete request.");
1130 - return HTTP_RESP_BAD_REQUEST;
1131 - }
1132 -
1133 - web_client_enable_tracking_required(w);
1134 - return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
1135 -
1136 - case 'S':
1137 - if(unlikely(!search_machine_guid)) {
1138 - netdata_log_error("Invalid registry request - search requires these parameters: for ('%s')", search_machine_guid?search_machine_guid:"UNSET");
1139 - buffer_flush(w->response.data);
1140 - buffer_strcat(w->response.data, "Invalid registry Search request.");
1141 - return HTTP_RESP_BAD_REQUEST;
1142 - }
1143 -
1144 - web_client_enable_tracking_required(w);
1145 - return registry_request_search_json(host, w, person_guid, search_machine_guid);
1146 -
1147 - case 'W':
1148 - if(unlikely(!machine_guid || !machine_url || !to_person_guid)) {
1149 - 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");
1150 - buffer_flush(w->response.data);
1151 - buffer_strcat(w->response.data, "Invalid registry Switch request.");
1152 - return HTTP_RESP_BAD_REQUEST;
1153 - }
1154 -
1155 - web_client_enable_tracking_required(w);
1156 - return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
1157 -
1158 - case 'H':
1159 - return registry_request_hello_json(host, w, do_not_track);
1160 -
1161 - default:
1162 - buffer_flush(w->response.data);
1163 - buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
1164 - return HTTP_RESP_BAD_REQUEST;
1165 - }
1166 -}
1167 -
1168 -void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key) {
1169 - buffer_json_member_add_object(wb, key);
1170 -
1171 - size_t normal = 0, warning = 0, critical = 0;
1172 - RRDCALC *rc;
1173 - foreach_rrdcalc_in_rrdhost_read(host, rc) {
1174 - if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
1175 - continue;
1176 -
1177 - switch(rc->status) {
1178 - case RRDCALC_STATUS_WARNING:
1179 - warning++;
1180 - break;
1181 - case RRDCALC_STATUS_CRITICAL:
1182 - critical++;
1183 - break;
1184 - default:
1185 - normal++;
1186 - }
1187 - }
1188 - foreach_rrdcalc_in_rrdhost_done(rc);
1189 -
1190 - buffer_json_member_add_uint64(wb, "normal", normal);
1191 - buffer_json_member_add_uint64(wb, "warning", warning);
1192 - buffer_json_member_add_uint64(wb, "critical", critical);
1193 -
1194 - buffer_json_object_close(wb);
1195 -}
1196 -
1197 -static inline void web_client_api_request_v1_info_mirrored_hosts_status(BUFFER *wb, RRDHOST *host) {
1198 - buffer_json_add_array_item_object(wb);
1199 -
1200 - buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
1201 - buffer_json_member_add_uint64(wb, "hops", host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1);
1202 - buffer_json_member_add_boolean(wb, "reachable", (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)));
1203 -
1204 - buffer_json_member_add_string(wb, "guid", host->machine_guid);
1205 - buffer_json_member_add_uuid(wb, "node_id", host->node_id);
1206 - rrdhost_aclk_state_lock(host);
1207 - buffer_json_member_add_string(wb, "claim_id", host->aclk_state.claimed_id);
1208 - rrdhost_aclk_state_unlock(host);
1209 -
1210 - buffer_json_object_close(wb);
1211 -}
1212 -
1213 -static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) {
1214 - RRDHOST *host;
1215 -
1216 - rrd_rdlock();
1217 -
1218 - buffer_json_member_add_array(wb, "mirrored_hosts");
1219 - rrdhost_foreach_read(host)
1220 - buffer_json_add_array_item_string(wb, rrdhost_hostname(host));
1221 - buffer_json_array_close(wb);
1222 -
1223 - buffer_json_member_add_array(wb, "mirrored_hosts_status");
1224 - rrdhost_foreach_read(host) {
1225 - if ((host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
1226 - web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
1227 - }
1228 - }
1229 - rrdhost_foreach_read(host) {
1230 - if ((host != localhost && rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
1231 - web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
1232 - }
1233 - }
1234 - buffer_json_array_close(wb);
1235 -
1236 - rrd_rdunlock();
1237 -}
1238 -
1239 -void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key) {
1240 - buffer_json_member_add_object(wb, key);
1241 - rrdlabels_to_buffer_json_members(host->rrdlabels, wb);
1242 - buffer_json_object_close(wb);
1243 -}
1244 -
1245 -static void host_collectors(RRDHOST *host, BUFFER *wb) {
1246 - buffer_json_member_add_array(wb, "collectors");
1247 -
1248 - DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
1249 - RRDSET *st;
1250 - char name[500];
1251 -
1252 - time_t now = now_realtime_sec();
1253 -
1254 - rrdset_foreach_read(st, host) {
1255 - if (!rrdset_is_available_for_viewers(st))
1256 - continue;
1257 -
1258 - sprintf(name, "%s:%s", rrdset_plugin_name(st), rrdset_module_name(st));
1259 -
1260 - bool old = 0;
1261 - bool *set = dictionary_set(dict, name, &old, sizeof(bool));
1262 - if(!*set) {
1263 - *set = true;
1264 - st->last_accessed_time_s = now;
1265 - buffer_json_add_array_item_object(wb);
1266 - buffer_json_member_add_string(wb, "plugin", rrdset_plugin_name(st));
1267 - buffer_json_member_add_string(wb, "module", rrdset_module_name(st));
1268 - buffer_json_object_close(wb);
1269 - }
1270 - }
1271 - rrdset_foreach_done(st);
1272 - dictionary_destroy(dict);
1273 -
1274 - buffer_json_array_close(wb);
1275 -}
1276 -
1277 -extern int aclk_connected;
1278 -inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) {
1279 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
1280 -
1281 - buffer_json_member_add_string(wb, "version", rrdhost_program_version(host));
1282 - buffer_json_member_add_string(wb, "uid", host->machine_guid);
1283 -
1284 - buffer_json_member_add_uint64(wb, "hosts-available", rrdhost_hosts_available());
1285 - web_client_api_request_v1_info_mirrored_hosts(wb);
1286 -
1287 - web_client_api_request_v1_info_summary_alarm_statuses(host, wb, "alarms");
1288 -
1289 - buffer_json_member_add_string_or_empty(wb, "os_name", host->system_info->host_os_name);
1290 - buffer_json_member_add_string_or_empty(wb, "os_id", host->system_info->host_os_id);
1291 - buffer_json_member_add_string_or_empty(wb, "os_id_like", host->system_info->host_os_id_like);
1292 - buffer_json_member_add_string_or_empty(wb, "os_version", host->system_info->host_os_version);
1293 - buffer_json_member_add_string_or_empty(wb, "os_version_id", host->system_info->host_os_version_id);
1294 - buffer_json_member_add_string_or_empty(wb, "os_detection", host->system_info->host_os_detection);
1295 - buffer_json_member_add_string_or_empty(wb, "cores_total", host->system_info->host_cores);
1296 - buffer_json_member_add_string_or_empty(wb, "total_disk_space", host->system_info->host_disk_space);
1297 - buffer_json_member_add_string_or_empty(wb, "cpu_freq", host->system_info->host_cpu_freq);
1298 - buffer_json_member_add_string_or_empty(wb, "ram_total", host->system_info->host_ram_total);
1299 -
1300 - buffer_json_member_add_string_or_omit(wb, "container_os_name", host->system_info->container_os_name);
1301 - buffer_json_member_add_string_or_omit(wb, "container_os_id", host->system_info->container_os_id);
1302 - buffer_json_member_add_string_or_omit(wb, "container_os_id_like", host->system_info->container_os_id_like);
1303 - buffer_json_member_add_string_or_omit(wb, "container_os_version", host->system_info->container_os_version);
1304 - buffer_json_member_add_string_or_omit(wb, "container_os_version_id", host->system_info->container_os_version_id);
1305 - buffer_json_member_add_string_or_omit(wb, "container_os_detection", host->system_info->container_os_detection);
1306 - buffer_json_member_add_string_or_omit(wb, "is_k8s_node", host->system_info->is_k8s_node);
1307 -
1308 - buffer_json_member_add_string_or_empty(wb, "kernel_name", host->system_info->kernel_name);
1309 - buffer_json_member_add_string_or_empty(wb, "kernel_version", host->system_info->kernel_version);
1310 - buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture);
1311 - buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization);
1312 - buffer_json_member_add_string_or_empty(wb, "virt_detection", host->system_info->virt_detection);
1313 - buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container);
1314 - buffer_json_member_add_string_or_empty(wb, "container_detection", host->system_info->container_detection);
1315 -
1316 - buffer_json_member_add_string_or_omit(wb, "cloud_provider_type", host->system_info->cloud_provider_type);
1317 - buffer_json_member_add_string_or_omit(wb, "cloud_instance_type", host->system_info->cloud_instance_type);
1318 - buffer_json_member_add_string_or_omit(wb, "cloud_instance_region", host->system_info->cloud_instance_region);
1319 -
1320 - host_labels2json(host, wb, "host_labels");
1321 - host_functions2json(host, wb);
1322 - host_collectors(host, wb);
1323 -
1324 - buffer_json_member_add_boolean(wb, "cloud-enabled", netdata_cloud_enabled);
1325 -
1326 -#ifdef ENABLE_ACLK
1327 - buffer_json_member_add_boolean(wb, "cloud-available", true);
1328 -#else
1329 - buffer_json_member_add_boolean(wb, "cloud-available", false);
1330 -#endif
1331 -
1332 - char *agent_id = get_agent_claimid();
1333 - buffer_json_member_add_boolean(wb, "agent-claimed", agent_id != NULL);
1334 - freez(agent_id);
1335 -
1336 -#ifdef ENABLE_ACLK
1337 - buffer_json_member_add_boolean(wb, "aclk-available", aclk_connected);
1338 -#else
1339 - buffer_json_member_add_boolean(wb, "aclk-available", false);
1340 -#endif
1341 -
1342 - buffer_json_member_add_string(wb, "memory-mode", rrd_memory_mode_name(host->rrd_memory_mode));
1343 -#ifdef ENABLE_DBENGINE
1344 - buffer_json_member_add_uint64(wb, "multidb-disk-quota", default_multidb_disk_quota_mb);
1345 - buffer_json_member_add_uint64(wb, "page-cache-size", default_rrdeng_page_cache_mb);
1346 -#endif // ENABLE_DBENGINE
1347 - buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE);
1348 - buffer_json_member_add_boolean(wb, "stream-enabled", default_rrdpush_enabled);
1349 -
1350 - buffer_json_member_add_boolean(wb, "stream-compression",
1351 - host->sender && host->sender->compressor.initialized);
1352 -
1353 -#ifdef ENABLE_HTTPS
1354 - buffer_json_member_add_boolean(wb, "https-enabled", true);
1355 -#else
1356 - buffer_json_member_add_boolean(wb, "https-enabled", false);
1357 -#endif
1358 -
1359 - buffer_json_member_add_quoted_string(wb, "buildinfo", analytics_data.netdata_buildinfo);
1360 - buffer_json_member_add_quoted_string(wb, "release-channel", analytics_data.netdata_config_release_channel);
1361 - buffer_json_member_add_quoted_string(wb, "notification-methods", analytics_data.netdata_notification_methods);
1362 -
1363 - buffer_json_member_add_boolean(wb, "exporting-enabled", analytics_data.exporting_enabled);
1364 - buffer_json_member_add_quoted_string(wb, "exporting-connectors", analytics_data.netdata_exporting_connectors);
1365 -
1366 - buffer_json_member_add_uint64(wb, "allmetrics-prometheus-used", analytics_data.prometheus_hits);
1367 - buffer_json_member_add_uint64(wb, "allmetrics-shell-used", analytics_data.shell_hits);
1368 - buffer_json_member_add_uint64(wb, "allmetrics-json-used", analytics_data.json_hits);
1369 - buffer_json_member_add_uint64(wb, "dashboard-used", analytics_data.dashboard_hits);
1370 -
1371 - buffer_json_member_add_uint64(wb, "charts-count", analytics_data.charts_count);
1372 - buffer_json_member_add_uint64(wb, "metrics-count", analytics_data.metrics_count);
1373 -
1374 -#if defined(ENABLE_ML)
1375 - buffer_json_member_add_object(wb, "ml-info");
1376 - ml_host_get_info(host, wb);
1377 - buffer_json_object_close(wb);
1378 -#endif
1379 -
1380 - buffer_json_finalize(wb);
1381 - return 0;
1382 -}
1383 -
1384 -#if defined(ENABLE_ML)
1385 -int web_client_api_request_v1_ml_info(RRDHOST *host, struct web_client *w, char *url) {
1386 - (void) url;
1387 -
1388 - if (!netdata_ready)
1389 - return HTTP_RESP_SERVICE_UNAVAILABLE;
1390 -
1391 - BUFFER *wb = w->response.data;
1392 - buffer_flush(wb);
1393 - wb->content_type = CT_APPLICATION_JSON;
1394 -
1395 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
1396 - ml_host_get_detection_info(host, wb);
1397 - buffer_json_finalize(wb);
1398 -
1399 - buffer_no_cacheable(wb);
1400 -
1401 - return HTTP_RESP_OK;
1402 -}
1403 -#endif // ENABLE_ML
1404 -
1405 -inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) {
1406 - (void)url;
1407 - if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
1408 - BUFFER *wb = w->response.data;
1409 - buffer_flush(wb);
1410 - wb->content_type = CT_APPLICATION_JSON;
1411 -
1412 - web_client_api_request_v1_info_fill_buffer(host, wb);
1413 -
1414 - buffer_no_cacheable(wb);
1415 - return HTTP_RESP_OK;
1416 -}
1417 -
1418 -static int web_client_api_request_v1_aclk_state(RRDHOST *host, struct web_client *w, char *url) {
1419 - UNUSED(url);
1420 - UNUSED(host);
1421 - if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
1422 -
1423 - BUFFER *wb = w->response.data;
1424 - buffer_flush(wb);
1425 -#ifdef ENABLE_ACLK
1426 - char *str = aclk_state_json();
1427 - buffer_strcat(wb, str);
1428 - freez(str);
1429 -#else
1430 - buffer_strcat(wb, "{\"aclk-available\":false}");
1431 -#endif
1432 -
1433 - wb->content_type = CT_APPLICATION_JSON;
1434 - buffer_no_cacheable(wb);
1435 - return HTTP_RESP_OK;
1436 -}
1437 -
1438 -int web_client_api_request_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url) {
1439 - return web_client_api_request_weights(host, w, url, default_metric_correlations_method, WEIGHTS_FORMAT_CHARTS, 1);
1440 -}
1441 -
1442 -int web_client_api_request_v1_weights(RRDHOST *host, struct web_client *w, char *url) {
1443 - return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_ANOMALY_RATE, WEIGHTS_FORMAT_CONTEXTS, 1);
1444 -}
1445 -
1446 -static void web_client_progress_functions_update(void *data, size_t done, size_t all) {
1447 - // handle progress updates from the plugin
1448 - struct web_client *w = data;
1449 - query_progress_functions_update(&w->transaction, done, all);
1450 -}
1451 -
1452 -int web_client_api_request_v1_function(RRDHOST *host, struct web_client *w, char *url) {
1453 - if (!netdata_ready)
1454 - return HTTP_RESP_SERVICE_UNAVAILABLE;
1455 -
1456 - int timeout = 0;
1457 - const char *function = NULL;
1458 -
1459 - while (url) {
1460 - char *value = strsep_skip_consecutive_separators(&url, "&");
1461 - if (!value || !*value)
1462 - continue;
1463 -
1464 - char *name = strsep_skip_consecutive_separators(&value, "=");
1465 - if (!name || !*name)
1466 - continue;
1467 -
1468 - if (!strcmp(name, "function"))
1469 - function = value;
1470 -
1471 - else if (!strcmp(name, "timeout"))
1472 - timeout = (int) strtoul(value, NULL, 0);
1473 - }
1474 -
1475 - BUFFER *wb = w->response.data;
1476 - buffer_flush(wb);
1477 - wb->content_type = CT_APPLICATION_JSON;
1478 - buffer_no_cacheable(wb);
1479 -
1480 - char transaction[UUID_COMPACT_STR_LEN];
1481 - uuid_unparse_lower_compact(w->transaction, transaction);
1482 -
1483 - CLEAN_BUFFER *source = buffer_create(100, NULL);
1484 - web_client_source2buffer(w, source);
1485 -
1486 - return rrd_function_run(host, wb, timeout, w->access, function, true, transaction,
1487 - NULL, NULL,
1488 - web_client_progress_functions_update, w,
1489 - web_client_interrupt_callback, w, NULL,
1490 - buffer_tostring(source));
1491 -}
1492 -
1493 -int web_client_api_request_v1_functions(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
1494 - if (!netdata_ready)
1495 - return HTTP_RESP_SERVICE_UNAVAILABLE;
1496 -
1497 - BUFFER *wb = w->response.data;
1498 - buffer_flush(wb);
1499 - wb->content_type = CT_APPLICATION_JSON;
1500 - buffer_no_cacheable(wb);
1501 -
1502 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
1503 - host_functions2json(host, wb);
1504 - buffer_json_finalize(wb);
1505 -
1506 - return HTTP_RESP_OK;
1507 -}
1508 -
1509 -void web_client_source2buffer(struct web_client *w, BUFFER *source) {
1510 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD))
1511 - buffer_sprintf(source, "method=NC");
1512 - else if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_BEARER))
1513 - buffer_sprintf(source, "method=api-bearer");
1514 - else
1515 - buffer_sprintf(source, "method=api");
1516 -
1517 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
1518 - buffer_strcat(source, ",role=god");
1519 - else
1520 - buffer_sprintf(source, ",role=%s", http_id2user_role(w->user_role));
1521 -
1522 - buffer_sprintf(source, ",permissions="HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)w->access);
1523 -
1524 - if(w->auth.client_name[0])
1525 - buffer_sprintf(source, ",user=%s", w->auth.client_name);
1526 -
1527 - if(!uuid_is_null(w->auth.cloud_account_id)) {
1528 - char uuid_str[UUID_COMPACT_STR_LEN];
1529 - uuid_unparse_lower_compact(w->auth.cloud_account_id, uuid_str);
1530 - buffer_sprintf(source, ",account=%s", uuid_str);
1531 - }
1532 -
1533 - if(w->client_ip[0])
1534 - buffer_sprintf(source, ",ip=%s", w->client_ip);
1535 -
1536 - if(w->forwarded_for)
1537 - buffer_sprintf(source, ",forwarded_for=%s", w->forwarded_for);
1538 -}
1539 -
1540 -static int web_client_api_request_v1_config(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
1541 - char *action = "tree";
1542 - char *path = "/";
1543 - char *id = NULL;
1544 - char *add_name = NULL;
1545 - int timeout = 120;
1546 -
1547 - while(url) {
1548 - char *value = strsep_skip_consecutive_separators(&url, "&");
1549 - if(!value || !*value) continue;
1550 -
1551 - char *name = strsep_skip_consecutive_separators(&value, "=");
1552 - if(!name || !*name) continue;
1553 - if(!value || !*value) continue;
1554 -
1555 - // name and value are now the parameters
1556 - // they are not null and not empty
1557 -
1558 - if(!strcmp(name, "action"))
1559 - action = value;
1560 - else if(!strcmp(name, "path"))
1561 - path = value;
1562 - else if(!strcmp(name, "id"))
1563 - id = value;
1564 - else if(!strcmp(name, "name"))
1565 - add_name = value;
1566 - else if(!strcmp(name, "timeout")) {
1567 - timeout = (int)strtol(value, NULL, 10);
1568 - if(timeout < 10)
1569 - timeout = 10;
1570 - }
1571 - }
1572 -
1573 - char transaction[UUID_COMPACT_STR_LEN];
1574 - uuid_unparse_lower_compact(w->transaction, transaction);
1575 -
1576 - size_t len = strlen(action) + (id ? strlen(id) : 0) + strlen(path) + (add_name ? strlen(add_name) : 0) + 100;
1577 -
1578 - char cmd[len];
1579 - if(strcmp(action, "tree") == 0)
1580 - snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " tree '%s' '%s'", path, id?id:"");
1581 - else {
1582 - DYNCFG_CMDS c = dyncfg_cmds2id(action);
1583 - if(!id || !*id || !dyncfg_is_valid_id(id)) {
1584 - rrd_call_function_error(w->response.data, "invalid id given", HTTP_RESP_BAD_REQUEST);
1585 - return HTTP_RESP_BAD_REQUEST;
1586 - }
1587 -
1588 - if(c == DYNCFG_CMD_NONE) {
1589 - rrd_call_function_error(w->response.data, "invalid action given", HTTP_RESP_BAD_REQUEST);
1590 - return HTTP_RESP_BAD_REQUEST;
1591 - }
1592 -
1593 - if(c == DYNCFG_CMD_ADD || c == DYNCFG_CMD_USERCONFIG || c == DYNCFG_CMD_TEST) {
1594 - if(c == DYNCFG_CMD_TEST && (!add_name || !*add_name)) {
1595 - // backwards compatibility for TEST without a name
1596 - char *colon = strrchr(id, ':');
1597 - if(colon) {
1598 - *colon = '\0';
1599 - add_name = ++colon;
1600 - }
1601 - else
1602 - add_name = "test";
1603 - }
1604 -
1605 - if(!add_name || !*add_name || !dyncfg_is_valid_id(add_name)) {
1606 - rrd_call_function_error(w->response.data, "invalid name given", HTTP_RESP_BAD_REQUEST);
1607 - return HTTP_RESP_BAD_REQUEST;
1608 - }
1609 - snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s %s", id, dyncfg_id2cmd_one(c), add_name);
1610 - }
1611 - else
1612 - snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s", id, dyncfg_id2cmd_one(c));
1613 - }
1614 -
1615 - CLEAN_BUFFER *source = buffer_create(100, NULL);
1616 - web_client_source2buffer(w, source);
1617 -
1618 - buffer_flush(w->response.data);
1619 - int code = rrd_function_run(host, w->response.data, timeout, w->access, cmd,
1620 - true, transaction,
1621 - NULL, NULL,
1622 - web_client_progress_functions_update, w,
1623 - web_client_interrupt_callback, w,
1624 - w->payload, buffer_tostring(source));
1625 -
1626 - return code;
1627 -}
1628 -
1629 -#ifndef ENABLE_DBENGINE
1630 -int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
1631 - return HTTP_RESP_NOT_FOUND;
1632 -}
1633 -#else
1634 -static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
1635 - RRDENG_SIZE_STATS stats = rrdeng_size_statistics(multidb_ctx[tier]);
1636 -
1637 - buffer_sprintf(wb,
1638 - "\n\t\t\"default_granularity_secs\":%zu"
1639 - ",\n\t\t\"sizeof_datafile\":%zu"
1640 - ",\n\t\t\"sizeof_page_in_cache\":%zu"
1641 - ",\n\t\t\"sizeof_point_data\":%zu"
1642 - ",\n\t\t\"sizeof_page_data\":%zu"
1643 - ",\n\t\t\"pages_per_extent\":%zu"
1644 - ",\n\t\t\"datafiles\":%zu"
1645 - ",\n\t\t\"extents\":%zu"
1646 - ",\n\t\t\"extents_pages\":%zu"
1647 - ",\n\t\t\"points\":%zu"
1648 - ",\n\t\t\"metrics\":%zu"
1649 - ",\n\t\t\"metrics_pages\":%zu"
1650 - ",\n\t\t\"extents_compressed_bytes\":%zu"
1651 - ",\n\t\t\"pages_uncompressed_bytes\":%zu"
1652 - ",\n\t\t\"pages_duration_secs\":%lld"
1653 - ",\n\t\t\"single_point_pages\":%zu"
1654 - ",\n\t\t\"first_t\":%ld"
1655 - ",\n\t\t\"last_t\":%ld"
1656 - ",\n\t\t\"database_retention_secs\":%lld"
1657 - ",\n\t\t\"average_compression_savings\":%0.2f"
1658 - ",\n\t\t\"average_point_duration_secs\":%0.2f"
1659 - ",\n\t\t\"average_metric_retention_secs\":%0.2f"
1660 - ",\n\t\t\"ephemeral_metrics_per_day_percent\":%0.2f"
1661 - ",\n\t\t\"average_page_size_bytes\":%0.2f"
1662 - ",\n\t\t\"estimated_concurrently_collected_metrics\":%zu"
1663 - ",\n\t\t\"currently_collected_metrics\":%zu"
1664 - ",\n\t\t\"disk_space\":%zu"
1665 - ",\n\t\t\"max_disk_space\":%zu"
1666 - , stats.default_granularity_secs
1667 - , stats.sizeof_datafile
1668 - , stats.sizeof_page_in_cache
1669 - , stats.sizeof_point_data
1670 - , stats.sizeof_page_data
1671 - , stats.pages_per_extent
1672 - , stats.datafiles
1673 - , stats.extents
1674 - , stats.extents_pages
1675 - , stats.points
1676 - , stats.metrics
1677 - , stats.metrics_pages
1678 - , stats.extents_compressed_bytes
1679 - , stats.pages_uncompressed_bytes
1680 - , (long long)stats.pages_duration_secs
1681 - , stats.single_point_pages
1682 - , stats.first_time_s
1683 - , stats.last_time_s
1684 - , (long long)stats.database_retention_secs
1685 - , stats.average_compression_savings
1686 - , stats.average_point_duration_secs
1687 - , stats.average_metric_retention_secs
1688 - , stats.ephemeral_metrics_per_day_percent
1689 - , stats.average_page_size_bytes
1690 - , stats.estimated_concurrently_collected_metrics
1691 - , stats.currently_collected_metrics
1692 - , stats.disk_space
1693 - , stats.max_disk_space
1694 - );
1695 -}
1696 -int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
1697 - if (!netdata_ready)
1698 - return HTTP_RESP_SERVICE_UNAVAILABLE;
1699 -
1700 - BUFFER *wb = w->response.data;
1701 - buffer_flush(wb);
1702 -
1703 - if(!dbengine_enabled) {
1704 - buffer_strcat(wb, "dbengine is not enabled");
1705 - return HTTP_RESP_NOT_FOUND;
1706 - }
1707 -
1708 - wb->content_type = CT_APPLICATION_JSON;
1709 - buffer_no_cacheable(wb);
1710 - buffer_strcat(wb, "{");
1711 - for(size_t tier = 0; tier < storage_tiers ;tier++) {
1712 - buffer_sprintf(wb, "%s\n\t\"tier%zu\": {", tier?",":"", tier);
1713 - web_client_api_v1_dbengine_stats_for_tier(wb, tier);
1714 - buffer_strcat(wb, "\n\t}");
1715 - }
1716 - buffer_strcat(wb, "\n}");
1717 -
1718 - return HTTP_RESP_OK;
1719 -}
1720 -#endif
1721 -
1722 -#define HLT_MGM "manage/health"
1723 -int web_client_api_request_v1_mgmt(RRDHOST *host, struct web_client *w, char *url) {
1724 - const char *haystack = buffer_tostring(w->url_path_decoded);
1725 - char *needle;
1726 -
1727 - buffer_flush(w->response.data);
1728 -
1729 - if ((needle = strstr(haystack, HLT_MGM)) == NULL) {
1730 - buffer_strcat(w->response.data, "Invalid management request. Curently only 'health' is supported.");
1731 - return HTTP_RESP_NOT_FOUND;
1732 - }
1733 - needle += strlen(HLT_MGM);
1734 - if (*needle != '\0') {
1735 - buffer_strcat(w->response.data, "Invalid management request. Currently only 'health' is supported.");
1736 - return HTTP_RESP_NOT_FOUND;
1737 - }
1738 - return web_client_api_request_v1_mgmt_health(host, w, url);
1739 -}
4 +#include "v1/api_v1_calls.h"
5 +#include "v2/api_v2_calls.h"
6 +#include "v3/api_v3_calls.h"
7
8 static struct web_api_command api_commands_v1[] = {
9 // time-series data APIs
10 {
11 .api = "data",
12 .hash = 0,
1746 - .acl = HTTP_ACL_DASHBOARD,
13 + .acl = HTTP_ACL_METRICS,
14 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1748 - .callback = web_client_api_request_v1_data,
15 + .callback = api_v1_data,
16 .allow_subpaths = 0
17 },
18 +#if defined(ENABLE_API_V1)
19 {
20 .api = "weights",
21 .hash = 0,
1754 - .acl = HTTP_ACL_DASHBOARD,
22 + .acl = HTTP_ACL_METRICS,
23 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1756 - .callback = web_client_api_request_v1_weights,
24 + .callback = api_v1_weights,
25 .allow_subpaths = 0
26 },
27 {
28 // deprecated - do not use anymore - use "weights"
29 .api = "metric_correlations",
30 .hash = 0,
1763 - .acl = HTTP_ACL_DASHBOARD,
31 + .acl = HTTP_ACL_METRICS,
32 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1765 - .callback = web_client_api_request_v1_metric_correlations,
33 + .callback = api_v1_metric_correlations,
34 .allow_subpaths = 0
35 },
36 +#endif
37 {
38 // exporting API
39 .api = "allmetrics",
40 .hash = 0,
1772 - .acl = HTTP_ACL_DASHBOARD,
1773 - .access = HTTP_ACCESS_ANONYMOUS_DATA,
1774 - .callback = web_client_api_request_v1_allmetrics,
1775 - .allow_subpaths = 0
1776 - },
1777 - {
1778 - // badges can be fetched with both dashboard and badge ACL
1779 - .api = "badge.svg",
1780 - .hash = 0,
1781 - .acl = HTTP_ACL_BADGES,
41 + .acl = HTTP_ACL_METRICS,
42 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1783 - .callback = web_client_api_request_v1_badge,
43 + .callback = api_v1_allmetrics,
44 .allow_subpaths = 0
45 },
46
47 // alerts APIs
48 +#if defined(ENABLE_API_V1)
49 {
50 .api = "alarms",
51 .hash = 0,
1791 - .acl = HTTP_ACL_DASHBOARD,
52 + .acl = HTTP_ACL_ALERTS,
53 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1793 - .callback = web_client_api_request_v1_alarms,
54 + .callback = api_v1_alarms,
55 .allow_subpaths = 0
56 },
57 {
58 .api = "alarms_values",
59 .hash = 0,
1799 - .acl = HTTP_ACL_DASHBOARD,
60 + .acl = HTTP_ACL_ALERTS,
61 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1801 - .callback = web_client_api_request_v1_alarms_values,
62 + .callback = api_v1_alarms_values,
63 .allow_subpaths = 0
64 },
65 {
66 .api = "alarm_log",
67 .hash = 0,
1807 - .acl = HTTP_ACL_DASHBOARD,
68 + .acl = HTTP_ACL_ALERTS,
69 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1809 - .callback = web_client_api_request_v1_alarm_log,
70 + .callback = api_v1_alarm_log,
71 .allow_subpaths = 0
72 },
73 {
74 .api = "alarm_variables",
75 .hash = 0,
1815 - .acl = HTTP_ACL_DASHBOARD,
76 + .acl = HTTP_ACL_ALERTS,
77 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1817 - .callback = web_client_api_request_v1_alarm_variables,
78 + .callback = api_v1_alarm_variables,
79 .allow_subpaths = 0
80 },
81 {
82 .api = "variable",
83 .hash = 0,
1823 - .acl = HTTP_ACL_DASHBOARD,
84 + .acl = HTTP_ACL_ALERTS,
85 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1825 - .callback = web_client_api_request_variable,
86 + .callback = api_v1_variable,
87 .allow_subpaths = 0
88 },
89 {
90 .api = "alarm_count",
91 .hash = 0,
1831 - .acl = HTTP_ACL_DASHBOARD,
92 + .acl = HTTP_ACL_ALERTS,
93 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1833 - .callback = web_client_api_request_v1_alarm_count,
94 + .callback = api_v1_alarm_count,
95 .allow_subpaths = 0
96 },
97 +#endif
98
99 // functions APIs - they check permissions per function call
100 {
101 .api = "function",
102 .hash = 0,
1841 - .acl = HTTP_ACL_DASHBOARD,
103 + .acl = HTTP_ACL_FUNCTIONS,
104 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1843 - .callback = web_client_api_request_v1_function,
105 + .callback = api_v1_function,
106 .allow_subpaths = 0
107 },
108 +
109 +#if defined(ENABLE_API_V1)
110 {
111 .api = "functions",
112 .hash = 0,
1849 - .acl = HTTP_ACL_DASHBOARD,
113 + .acl = HTTP_ACL_FUNCTIONS,
114 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1851 - .callback = web_client_api_request_v1_functions,
115 + .callback = api_v1_functions,
116 .allow_subpaths = 0
117 },
118 +#endif
119
120 // time-series metadata APIs
121 +#if defined(ENABLE_API_V1)
122 {
123 .api = "chart",
124 .hash = 0,
1859 - .acl = HTTP_ACL_DASHBOARD,
125 + .acl = HTTP_ACL_METRICS,
126 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1861 - .callback = web_client_api_request_v1_chart,
127 + .callback = api_v1_chart,
128 .allow_subpaths = 0
129 },
130 +#endif
131 {
132 .api = "charts",
133 .hash = 0,
1867 - .acl = HTTP_ACL_DASHBOARD,
134 + .acl = HTTP_ACL_METRICS,
135 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1869 - .callback = web_client_api_request_v1_charts,
136 + .callback = api_v1_charts,
137 .allow_subpaths = 0
138 },
139 {
140 .api = "context",
141 .hash = 0,
1875 - .acl = HTTP_ACL_DASHBOARD,
142 + .acl = HTTP_ACL_METRICS,
143 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1877 - .callback = web_client_api_request_v1_context,
144 + .callback = api_v1_context,
145 .allow_subpaths = 0
146 },
147 {
148 .api = "contexts",
149 .hash = 0,
1883 - .acl = HTTP_ACL_DASHBOARD,
150 + .acl = HTTP_ACL_METRICS,
151 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1885 - .callback = web_client_api_request_v1_contexts,
152 + .callback = api_v1_contexts,
153 .allow_subpaths = 0
154 },
155
156 // registry APIs
157 +#if defined(ENABLE_API_V1)
158 {
159 // registry checks the ACL by itself, so we allow everything
160 .api = "registry",
161 .hash = 0,
162 .acl = HTTP_ACL_NONE, // it manages acl by itself
163 .access = HTTP_ACCESS_NONE, // it manages access by itself
1896 - .callback = web_client_api_request_v1_registry,
164 + .callback = api_v1_registry,
165 .allow_subpaths = 0
166 },
167 +#endif
168
169 // agent information APIs
170 +#if defined(ENABLE_API_V1)
171 {
172 .api = "info",
173 .hash = 0,
1904 - .acl = HTTP_ACL_DASHBOARD,
174 + .acl = HTTP_ACL_NODES,
175 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1906 - .callback = web_client_api_request_v1_info,
176 + .callback = api_v1_info,
177 .allow_subpaths = 0
178 },
179 {
180 .api = "aclk",
181 .hash = 0,
1912 - .acl = HTTP_ACL_DASHBOARD,
182 + .acl = HTTP_ACL_NODES,
183 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1914 - .callback = web_client_api_request_v1_aclk_state,
184 + .callback = api_v1_aclk,
185 .allow_subpaths = 0
186 },
187 {
188 // deprecated - use /api/v2/info
189 .api = "dbengine_stats",
190 .hash = 0,
1921 - .acl = HTTP_ACL_DASHBOARD,
191 + .acl = HTTP_ACL_NODES,
192 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1923 - .callback = web_client_api_request_v1_dbengine_stats,
193 + .callback = api_v1_dbengine_stats,
194 .allow_subpaths = 0
195 },
1926 -
1927 - // dyncfg APIs
196 {
1929 - .api = "config",
197 + .api = "ml_info",
198 .hash = 0,
1931 - .acl = HTTP_ACL_DASHBOARD,
199 + .acl = HTTP_ACL_NODES,
200 .access = HTTP_ACCESS_ANONYMOUS_DATA,
1933 - .callback = web_client_api_request_v1_config,
201 + .callback = api_v1_ml_info,
202 .allow_subpaths = 0
203 },
1936 -
1937 -#if defined(ENABLE_ML)
204 {
1939 - .api = "ml_info",
1940 - .hash = 0,
1941 - .acl = HTTP_ACL_DASHBOARD,
1942 - .access = HTTP_ACCESS_ANONYMOUS_DATA,
1943 - .callback = web_client_api_request_v1_ml_info,
1944 - .allow_subpaths = 0
205 + .api = "manage",
206 + .hash = 0,
207 + .acl = HTTP_ACL_MANAGEMENT,
208 + .access = HTTP_ACCESS_NONE, // it manages access by itself
209 + .callback = api_v1_manage,
210 + .allow_subpaths = 1
211 },
212 #endif
213
214 + // dyncfg APIs
215 {
1949 - // deprecated
1950 - .api = "manage",
216 + .api = "config",
217 .hash = 0,
1952 - .acl = HTTP_ACL_MANAGEMENT,
1953 - .access = HTTP_ACCESS_NONE, // it manages access by itself
1954 - .callback = web_client_api_request_v1_mgmt,
1955 - .allow_subpaths = 1
218 + .acl = HTTP_ACL_DYNCFG,
219 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
220 + .callback = api_v1_config,
221 + .allow_subpaths = 0
222 },
223
224 {
src/web/api/web_api_v1.h
-36
@@ -5,43 +5,7 @@
5
6 #include "web_api.h"
7
8 -struct web_client;
9 -
10 -CONTEXTS_V2_OPTIONS web_client_api_request_v2_context_options(char *o);
11 -CONTEXTS_V2_ALERT_STATUS web_client_api_request_v2_alert_status(char *o);
12 -void web_client_api_request_v2_contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_OPTIONS options);
13 -void web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_ALERT_STATUS options);
14 -
15 -RRDR_OPTIONS rrdr_options_parse(char *o);
16 -RRDR_OPTIONS rrdr_options_parse_one(const char *o);
17 -
18 -void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options);
19 -void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options);
20 -void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
21 -
22 -uint32_t web_client_api_request_v1_data_format(char *name);
23 -uint32_t web_client_api_request_v1_data_google_format(char *name);
24 -
25 -int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url);
26 -int web_client_api_request_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url);
27 -int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url);
28 -int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf));
29 -int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url);
30 -int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url);
31 -int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url);
32 -int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url);
33 -int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url);
34 -int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url);
8 int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint);
36 -int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb);
37 -
38 -void web_client_api_v1_init(void);
39 -void web_client_api_v1_management_init(void);
40 -
41 -void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key);
42 -void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key);
43 -
44 -void web_client_source2buffer(struct web_client *w, BUFFER *source);
9
10 extern char *api_secret;
11
src/web/api/web_api_v2.c
+36 -612
@@ -1,604 +1,27 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "web_api_v2.h"
4 -#include "../rtc/webrtc.h"
5 -
6 -static bool verify_agent_uuids(const char *machine_guid, const char *node_id, const char *claim_id) {
7 - if(!machine_guid || !node_id || !claim_id)
8 - return false;
9 -
10 - if(strcmp(machine_guid, localhost->machine_guid) != 0)
11 - return false;
12 -
13 - char *agent_claim_id = get_agent_claimid();
14 -
15 - bool not_verified = (!agent_claim_id || strcmp(claim_id, agent_claim_id) != 0);
16 - freez(agent_claim_id);
17 -
18 - if(not_verified || !localhost->node_id)
19 - return false;
20 -
21 - char buf[UUID_STR_LEN];
22 - uuid_unparse_lower(*localhost->node_id, buf);
23 -
24 - if(strcmp(node_id, buf) != 0)
25 - return false;
26 -
27 - return true;
28 -}
29 -
30 -int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url) {
31 - char *machine_guid = NULL;
32 - char *claim_id = NULL;
33 - char *node_id = NULL;
34 - bool protection = netdata_is_protected_by_bearer;
35 -
36 - while (url) {
37 - char *value = strsep_skip_consecutive_separators(&url, "&");
38 - if (!value || !*value) continue;
39 -
40 - char *name = strsep_skip_consecutive_separators(&value, "=");
41 - if (!name || !*name) continue;
42 - if (!value || !*value) continue;
43 -
44 - if(!strcmp(name, "bearer_protection")) {
45 - if(!strcmp(value, "on") || !strcmp(value, "true") || !strcmp(value, "yes"))
46 - protection = true;
47 - else
48 - protection = false;
49 - }
50 - else if(!strcmp(name, "machine_guid"))
51 - machine_guid = value;
52 - else if(!strcmp(name, "claim_id"))
53 - claim_id = value;
54 - else if(!strcmp(name, "node_id"))
55 - node_id = value;
56 - }
57 -
58 - if(!verify_agent_uuids(machine_guid, node_id, claim_id)) {
59 - buffer_flush(w->response.data);
60 - buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
61 - return HTTP_RESP_BAD_REQUEST;
62 - }
63 -
64 - netdata_is_protected_by_bearer = protection;
65 -
66 - BUFFER *wb = w->response.data;
67 - buffer_flush(wb);
68 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
69 - buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
70 - buffer_json_finalize(wb);
71 -
72 - return HTTP_RESP_OK;
73 -}
74 -
75 -int api_v2_bearer_token(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
76 - char *machine_guid = NULL;
77 - char *claim_id = NULL;
78 - char *node_id = NULL;
79 -
80 - while(url) {
81 - char *value = strsep_skip_consecutive_separators(&url, "&");
82 - if (!value || !*value) continue;
83 -
84 - char *name = strsep_skip_consecutive_separators(&value, "=");
85 - if (!name || !*name) continue;
86 - if (!value || !*value) continue;
87 -
88 - if(!strcmp(name, "machine_guid"))
89 - machine_guid = value;
90 - else if(!strcmp(name, "claim_id"))
91 - claim_id = value;
92 - else if(!strcmp(name, "node_id"))
93 - node_id = value;
94 - }
95 -
96 - if(!verify_agent_uuids(machine_guid, node_id, claim_id)) {
97 - buffer_flush(w->response.data);
98 - buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
99 - return HTTP_RESP_BAD_REQUEST;
100 - }
101 -
102 - nd_uuid_t uuid;
103 - time_t expires_s = bearer_create_token(&uuid, w);
104 -
105 - BUFFER *wb = w->response.data;
106 - buffer_flush(wb);
107 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
108 - buffer_json_member_add_string(wb, "mg", localhost->machine_guid);
109 - buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
110 - buffer_json_member_add_uuid(wb, "token", &uuid);
111 - buffer_json_member_add_time_t(wb, "expiration", expires_s);
112 - buffer_json_finalize(wb);
113 -
114 - return HTTP_RESP_OK;
115 -}
116 -
117 -static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_MODE mode) {
118 - struct api_v2_contexts_request req = { 0 };
119 -
120 - while(url) {
121 - char *value = strsep_skip_consecutive_separators(&url, "&");
122 - if(!value || !*value) continue;
123 -
124 - char *name = strsep_skip_consecutive_separators(&value, "=");
125 - if(!name || !*name) continue;
126 - if(!value || !*value) continue;
127 -
128 - // name and value are now the parameters
129 - // they are not null and not empty
130 -
131 - if(!strcmp(name, "scope_nodes"))
132 - req.scope_nodes = value;
133 - else if(!strcmp(name, "nodes"))
134 - req.nodes = value;
135 - else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "scope_contexts"))
136 - req.scope_contexts = value;
137 - else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "contexts"))
138 - req.contexts = value;
139 - else if((mode & CONTEXTS_V2_SEARCH) && !strcmp(name, "q"))
140 - req.q = value;
141 - else if(!strcmp(name, "options"))
142 - req.options = web_client_api_request_v2_context_options(value);
143 - else if(!strcmp(name, "after"))
144 - req.after = str2l(value);
145 - else if(!strcmp(name, "before"))
146 - req.before = str2l(value);
147 - else if(!strcmp(name, "timeout"))
148 - req.timeout_ms = str2l(value);
149 - else if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) {
150 - if (!strcmp(name, "alert"))
151 - req.alerts.alert = value;
152 - else if (!strcmp(name, "transition"))
153 - req.alerts.transition = value;
154 - else if(mode & CONTEXTS_V2_ALERTS) {
155 - if (!strcmp(name, "status"))
156 - req.alerts.status = web_client_api_request_v2_alert_status(value);
157 - }
158 - else if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
159 - if (!strcmp(name, "last"))
160 - req.alerts.last = strtoul(value, NULL, 0);
161 - else if(!strcmp(name, "context"))
162 - req.contexts = value;
163 - else if (!strcmp(name, "anchor_gi")) {
164 - req.alerts.global_id_anchor = str2ull(value, NULL);
165 - }
166 - else {
167 - for(int i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
168 - if(!strcmp(name, alert_transition_facets[i].query_param))
169 - req.alerts.facets[i] = value;
170 - }
171 - }
172 - }
173 - }
174 - }
175 -
176 - if ((mode & CONTEXTS_V2_ALERT_TRANSITIONS) && !req.alerts.last)
177 - req.alerts.last = 1;
178 -
179 - buffer_flush(w->response.data);
180 - buffer_no_cacheable(w->response.data);
181 - return rrdcontext_to_json_v2(w->response.data, &req, mode);
182 -}
183 -
184 -static int web_client_api_request_v2_alert_transitions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
185 - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERT_TRANSITIONS | CONTEXTS_V2_NODES);
186 -}
187 -
188 -static int web_client_api_request_v2_alerts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
189 - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERTS | CONTEXTS_V2_NODES);
190 -}
191 -
192 -static int web_client_api_request_v2_functions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
193 - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
194 -}
195 -
196 -static int web_client_api_request_v2_versions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
197 - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_VERSIONS);
198 -}
199 -
200 -static int web_client_api_request_v2_q(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
201 - 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);
202 -}
203 -
204 -static int web_client_api_request_v2_contexts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
205 - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
206 -}
207 -
208 -static int web_client_api_request_v2_nodes(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
209 - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_INFO);
210 -}
211 -
212 -static int web_client_api_request_v2_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
213 - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO);
214 -}
215 -
216 -static int web_client_api_request_v2_node_instances(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
217 - 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);
218 -}
219 -
220 -static int web_client_api_request_v2_weights(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
221 - return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, WEIGHTS_FORMAT_MULTINODE, 2);
222 -}
223 -
224 -static int web_client_api_request_v2_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
225 - return api_v2_claim(w, url);
226 -}
227 -
228 -static int web_client_api_request_v2_alert_config(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
229 - const char *config = NULL;
230 -
231 - while(url) {
232 - char *value = strsep_skip_consecutive_separators(&url, "&");
233 - if(!value || !*value) continue;
234 -
235 - char *name = strsep_skip_consecutive_separators(&value, "=");
236 - if(!name || !*name) continue;
237 - if(!value || !*value) continue;
238 -
239 - // name and value are now the parameters
240 - // they are not null and not empty
241 -
242 - if(!strcmp(name, "config"))
243 - config = value;
244 - }
245 -
246 - buffer_flush(w->response.data);
247 -
248 - if(!config) {
249 - w->response.data->content_type = CT_TEXT_PLAIN;
250 - buffer_strcat(w->response.data, "A config hash ID is required. Add ?config=UUID query param");
251 - return HTTP_RESP_BAD_REQUEST;
252 - }
253 -
254 - return contexts_v2_alert_config_to_json(w, config);
255 -}
256 -
257 -
258 -#define GROUP_BY_KEY_MAX_LENGTH 30
259 -static struct {
260 - char group_by[GROUP_BY_KEY_MAX_LENGTH + 1];
261 - char aggregation[GROUP_BY_KEY_MAX_LENGTH + 1];
262 - char group_by_label[GROUP_BY_KEY_MAX_LENGTH + 1];
263 -} group_by_keys[MAX_QUERY_GROUP_BY_PASSES];
264 -
265 -__attribute__((constructor)) void initialize_group_by_keys(void) {
266 - for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
267 - snprintfz(group_by_keys[g].group_by, GROUP_BY_KEY_MAX_LENGTH, "group_by[%zu]", g);
268 - snprintfz(group_by_keys[g].aggregation, GROUP_BY_KEY_MAX_LENGTH, "aggregation[%zu]", g);
269 - snprintfz(group_by_keys[g].group_by_label, GROUP_BY_KEY_MAX_LENGTH, "group_by_label[%zu]", g);
270 - }
271 -}
272 -
273 -static int web_client_api_request_v2_data(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
274 - usec_t received_ut = now_monotonic_usec();
275 -
276 - int ret = HTTP_RESP_BAD_REQUEST;
277 -
278 - buffer_flush(w->response.data);
279 -
280 - char *google_version = "0.6",
281 - *google_reqId = "0",
282 - *google_sig = "0",
283 - *google_out = "json",
284 - *responseHandler = NULL,
285 - *outFileName = NULL;
286 -
287 - time_t last_timestamp_in_data = 0, google_timestamp = 0;
288 -
289 - char *scope_nodes = NULL;
290 - char *scope_contexts = NULL;
291 - char *nodes = NULL;
292 - char *contexts = NULL;
293 - char *instances = NULL;
294 - char *dimensions = NULL;
295 - char *before_str = NULL;
296 - char *after_str = NULL;
297 - char *resampling_time_str = NULL;
298 - char *points_str = NULL;
299 - char *timeout_str = NULL;
300 - char *labels = NULL;
301 - char *alerts = NULL;
302 - char *time_group_options = NULL;
303 - char *tier_str = NULL;
304 - size_t tier = 0;
305 - RRDR_TIME_GROUPING time_group = RRDR_GROUPING_AVERAGE;
306 - DATASOURCE_FORMAT format = DATASOURCE_JSON2;
307 - RRDR_OPTIONS options = RRDR_OPTION_VIRTUAL_POINTS | RRDR_OPTION_JSON_WRAP | RRDR_OPTION_RETURN_JWAR;
308 -
309 - struct group_by_pass group_by[MAX_QUERY_GROUP_BY_PASSES] = {
310 - {
311 - .group_by = RRDR_GROUP_BY_DIMENSION,
312 - .group_by_label = NULL,
313 - .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
314 - },
315 - };
316 -
317 - size_t group_by_idx = 0, group_by_label_idx = 0, aggregation_idx = 0;
318 -
319 - while(url) {
320 - char *value = strsep_skip_consecutive_separators(&url, "&");
321 - if(!value || !*value) continue;
322 -
323 - char *name = strsep_skip_consecutive_separators(&value, "=");
324 - if(!name || !*name) continue;
325 - if(!value || !*value) continue;
326 -
327 - // name and value are now the parameters
328 - // they are not null and not empty
329 -
330 - if(!strcmp(name, "scope_nodes")) scope_nodes = value;
331 - else if(!strcmp(name, "scope_contexts")) scope_contexts = value;
332 - else if(!strcmp(name, "nodes")) nodes = value;
333 - else if(!strcmp(name, "contexts")) contexts = value;
334 - else if(!strcmp(name, "instances")) instances = value;
335 - else if(!strcmp(name, "dimensions")) dimensions = value;
336 - else if(!strcmp(name, "labels")) labels = value;
337 - else if(!strcmp(name, "alerts")) alerts = value;
338 - else if(!strcmp(name, "after")) after_str = value;
339 - else if(!strcmp(name, "before")) before_str = value;
340 - else if(!strcmp(name, "points")) points_str = value;
341 - else if(!strcmp(name, "timeout")) timeout_str = value;
342 - else if(!strcmp(name, "group_by")) {
343 - group_by[group_by_idx++].group_by = group_by_parse(value);
344 - if(group_by_idx >= MAX_QUERY_GROUP_BY_PASSES)
345 - group_by_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
346 - }
347 - else if(!strcmp(name, "group_by_label")) {
348 - group_by[group_by_label_idx++].group_by_label = value;
349 - if(group_by_label_idx >= MAX_QUERY_GROUP_BY_PASSES)
350 - group_by_label_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
351 - }
352 - else if(!strcmp(name, "aggregation")) {
353 - group_by[aggregation_idx++].aggregation = group_by_aggregate_function_parse(value);
354 - if(aggregation_idx >= MAX_QUERY_GROUP_BY_PASSES)
355 - aggregation_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
356 - }
357 - else if(!strcmp(name, "format")) format = web_client_api_request_v1_data_format(value);
358 - else if(!strcmp(name, "options")) options |= rrdr_options_parse(value);
359 - else if(!strcmp(name, "time_group")) time_group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
360 - else if(!strcmp(name, "time_group_options")) time_group_options = value;
361 - else if(!strcmp(name, "time_resampling")) resampling_time_str = value;
362 - else if(!strcmp(name, "tier")) tier_str = value;
363 - else if(!strcmp(name, "callback")) responseHandler = value;
364 - else if(!strcmp(name, "filename")) outFileName = value;
365 - else if(!strcmp(name, "tqx")) {
366 - // parse Google Visualization API options
367 - // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
368 - char *tqx_name, *tqx_value;
369 -
370 - while(value) {
371 - tqx_value = strsep_skip_consecutive_separators(&value, ";");
372 - if(!tqx_value || !*tqx_value) continue;
373 -
374 - tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
375 - if(!tqx_name || !*tqx_name) continue;
376 - if(!tqx_value || !*tqx_value) continue;
377 -
378 - if(!strcmp(tqx_name, "version"))
379 - google_version = tqx_value;
380 - else if(!strcmp(tqx_name, "reqId"))
381 - google_reqId = tqx_value;
382 - else if(!strcmp(tqx_name, "sig")) {
383 - google_sig = tqx_value;
384 - google_timestamp = strtoul(google_sig, NULL, 0);
385 - }
386 - else if(!strcmp(tqx_name, "out")) {
387 - google_out = tqx_value;
388 - format = web_client_api_request_v1_data_google_format(google_out);
389 - }
390 - else if(!strcmp(tqx_name, "responseHandler"))
391 - responseHandler = tqx_value;
392 - else if(!strcmp(tqx_name, "outFileName"))
393 - outFileName = tqx_value;
394 - }
395 - }
396 - else {
397 - for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
398 - if(!strcmp(name, group_by_keys[g].group_by))
399 - group_by[g].group_by = group_by_parse(value);
400 - else if(!strcmp(name, group_by_keys[g].group_by_label))
401 - group_by[g].group_by_label = value;
402 - else if(!strcmp(name, group_by_keys[g].aggregation))
403 - group_by[g].aggregation = group_by_aggregate_function_parse(value);
404 - }
405 - }
406 - }
407 -
408 - // validate the google parameters given
409 - fix_google_param(google_out);
410 - fix_google_param(google_sig);
411 - fix_google_param(google_reqId);
412 - fix_google_param(google_version);
413 - fix_google_param(responseHandler);
414 - fix_google_param(outFileName);
415 -
416 - for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
417 - if (group_by[g].group_by_label && *group_by[g].group_by_label)
418 - group_by[g].group_by |= RRDR_GROUP_BY_LABEL;
419 - }
420 -
421 - if(group_by[0].group_by == RRDR_GROUP_BY_NONE)
422 - group_by[0].group_by = RRDR_GROUP_BY_DIMENSION;
423 -
424 - for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
425 - if ((group_by[g].group_by & ~(RRDR_GROUP_BY_DIMENSION)) || (options & RRDR_OPTION_PERCENTAGE)) {
426 - options |= RRDR_OPTION_ABSOLUTE;
427 - break;
428 - }
429 - }
430 -
431 - if(options & RRDR_OPTION_DEBUG)
432 - options &= ~RRDR_OPTION_MINIFY;
433 -
434 - if(tier_str && *tier_str) {
435 - tier = str2ul(tier_str);
436 - if(tier < storage_tiers)
437 - options |= RRDR_OPTION_SELECTED_TIER;
438 - else
439 - tier = 0;
440 - }
441 -
442 - time_t before = (before_str && *before_str)?str2l(before_str):0;
443 - time_t after = (after_str && *after_str) ?str2l(after_str):-600;
444 - size_t points = (points_str && *points_str)?str2u(points_str):0;
445 - int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
446 - time_t resampling_time = (resampling_time_str && *resampling_time_str) ? str2l(resampling_time_str) : 0;
447 -
448 - QUERY_TARGET_REQUEST qtr = {
449 - .version = 2,
450 - .scope_nodes = scope_nodes,
451 - .scope_contexts = scope_contexts,
452 - .after = after,
453 - .before = before,
454 - .host = NULL,
455 - .st = NULL,
456 - .nodes = nodes,
457 - .contexts = contexts,
458 - .instances = instances,
459 - .dimensions = dimensions,
460 - .alerts = alerts,
461 - .timeout_ms = timeout,
462 - .points = points,
463 - .format = format,
464 - .options = options,
465 - .time_group_method = time_group,
466 - .time_group_options = time_group_options,
467 - .resampling_time = resampling_time,
468 - .tier = tier,
469 - .chart_label_key = NULL,
470 - .labels = labels,
471 - .query_source = QUERY_SOURCE_API_DATA,
472 - .priority = STORAGE_PRIORITY_NORMAL,
473 - .received_ut = received_ut,
474 -
475 - .interrupt_callback = web_client_interrupt_callback,
476 - .interrupt_callback_data = w,
477 -
478 - .transaction = &w->transaction,
479 - };
480 -
481 - for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++)
482 - qtr.group_by[g] = group_by[g];
483 -
484 - QUERY_TARGET *qt = query_target_create(&qtr);
485 - ONEWAYALLOC *owa = NULL;
486 -
487 - if(!qt) {
488 - buffer_sprintf(w->response.data, "Failed to prepare the query.");
489 - ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
490 - goto cleanup;
491 - }
492 -
493 - web_client_timeout_checkpoint_set(w, timeout);
494 - if(web_client_timeout_checkpoint_and_check(w, NULL)) {
495 - ret = w->response.code;
496 - goto cleanup;
497 - }
498 -
499 - if(outFileName && *outFileName) {
500 - buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
501 - netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
502 - }
503 -
504 - if(format == DATASOURCE_DATATABLE_JSONP) {
505 - if(responseHandler == NULL)
506 - responseHandler = "google.visualization.Query.setResponse";
507 -
508 - netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
509 - w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
510 - );
511 -
512 - buffer_sprintf(
513 - w->response.data,
514 - "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
515 - responseHandler,
516 - google_version,
517 - google_reqId,
518 - (int64_t)now_realtime_sec());
519 - }
520 - else if(format == DATASOURCE_JSONP) {
521 - if(responseHandler == NULL)
522 - responseHandler = "callback";
523 -
524 - buffer_strcat(w->response.data, responseHandler);
525 - buffer_strcat(w->response.data, "(");
526 - }
527 -
528 - owa = onewayalloc_create(0);
529 - ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
530 -
531 - if(format == DATASOURCE_DATATABLE_JSONP) {
532 - if(google_timestamp < last_timestamp_in_data)
533 - buffer_strcat(w->response.data, "});");
534 -
535 - else {
536 - // the client already has the latest data
537 - buffer_flush(w->response.data);
538 - buffer_sprintf(w->response.data,
539 - "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
540 - responseHandler, google_version, google_reqId);
541 - }
542 - }
543 - else if(format == DATASOURCE_JSONP)
544 - buffer_strcat(w->response.data, ");");
545 -
546 - if(qt->internal.relative)
547 - buffer_no_cacheable(w->response.data);
548 - else
549 - buffer_cacheable(w->response.data);
550 -
551 -cleanup:
552 - query_target_release(qt);
553 - onewayalloc_destroy(owa);
554 - return ret;
555 -}
556 -
557 -static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
558 - return webrtc_new_connection(buffer_tostring(w->payload), w->response.data);
559 -}
560 -
561 -static int web_client_api_request_v2_progress(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
562 - char *transaction = NULL;
563 -
564 - while(url) {
565 - char *value = strsep_skip_consecutive_separators(&url, "&");
566 - if(!value || !*value) continue;
567 -
568 - char *name = strsep_skip_consecutive_separators(&value, "=");
569 - if(!name || !*name) continue;
570 - if(!value || !*value) continue;
571 -
572 - // name and value are now the parameters
573 - // they are not null and not empty
574 -
575 - if(!strcmp(name, "transaction")) transaction = value;
576 - }
577 -
578 - nd_uuid_t tr;
579 - uuid_parse_flexi(transaction, tr);
580 -
581 - rrd_function_call_progresser(&tr);
582 -
583 - return web_api_v2_report_progress(&tr, w->response.data);
584 -}
4 +#include "v1/api_v1_calls.h"
5 +#include "v2/api_v2_calls.h"
6 +#include "v3/api_v3_calls.h"
7
8 static struct web_api_command api_commands_v2[] = {
9 +#if defined(ENABLE_API_v2)
10 // time-series multi-node multi-instance data APIs
11 {
12 .api = "data",
13 .hash = 0,
591 - .acl = HTTP_ACL_DASHBOARD,
14 + .acl = HTTP_ACL_METRICS,
15 .access = HTTP_ACCESS_ANONYMOUS_DATA,
593 - .callback = web_client_api_request_v2_data,
16 + .callback = api_v2_data,
17 .allow_subpaths = 0
18 },
19 {
20 .api = "weights",
21 .hash = 0,
599 - .acl = HTTP_ACL_DASHBOARD,
22 + .acl = HTTP_ACL_METRICS,
23 .access = HTTP_ACCESS_ANONYMOUS_DATA,
601 - .callback = web_client_api_request_v2_weights,
24 + .callback = api_v2_weights,
25 .allow_subpaths = 0
26 },
27
@@ -606,18 +29,18 @@ static struct web_api_command api_commands_v2[] = {
29 {
30 .api = "contexts",
31 .hash = 0,
609 - .acl = HTTP_ACL_DASHBOARD,
32 + .acl = HTTP_ACL_METRICS,
33 .access = HTTP_ACCESS_ANONYMOUS_DATA,
611 - .callback = web_client_api_request_v2_contexts,
34 + .callback = api_v2_contexts,
35 .allow_subpaths = 0
36 },
37 {
38 // full text search
39 .api = "q",
40 .hash = 0,
618 - .acl = HTTP_ACL_DASHBOARD,
41 + .acl = HTTP_ACL_METRICS,
42 .access = HTTP_ACCESS_ANONYMOUS_DATA,
620 - .callback = web_client_api_request_v2_q,
43 + .callback = api_v2_q,
44 .allow_subpaths = 0
45 },
46
@@ -625,25 +48,25 @@ static struct web_api_command api_commands_v2[] = {
48 {
49 .api = "alerts",
50 .hash = 0,
628 - .acl = HTTP_ACL_DASHBOARD,
51 + .acl = HTTP_ACL_ALERTS,
52 .access = HTTP_ACCESS_ANONYMOUS_DATA,
630 - .callback = web_client_api_request_v2_alerts,
53 + .callback = api_v2_alerts,
54 .allow_subpaths = 0
55 },
56 {
57 .api = "alert_transitions",
58 .hash = 0,
636 - .acl = HTTP_ACL_DASHBOARD,
59 + .acl = HTTP_ACL_ALERTS,
60 .access = HTTP_ACCESS_ANONYMOUS_DATA,
638 - .callback = web_client_api_request_v2_alert_transitions,
61 + .callback = api_v2_alert_transitions,
62 .allow_subpaths = 0
63 },
64 {
65 .api = "alert_config",
66 .hash = 0,
644 - .acl = HTTP_ACL_DASHBOARD,
67 + .acl = HTTP_ACL_ALERTS,
68 .access = HTTP_ACCESS_ANONYMOUS_DATA,
646 - .callback = web_client_api_request_v2_alert_config,
69 + .callback = api_v2_alert_config,
70 .allow_subpaths = 0
71 },
72
@@ -651,41 +74,41 @@ static struct web_api_command api_commands_v2[] = {
74 {
75 .api = "info",
76 .hash = 0,
654 - .acl = HTTP_ACL_DASHBOARD,
77 + .acl = HTTP_ACL_NOCHECK,
78 .access = HTTP_ACCESS_ANONYMOUS_DATA,
656 - .callback = web_client_api_request_v2_info,
79 + .callback = api_v2_info,
80 .allow_subpaths = 0
81 },
82 {
83 .api = "nodes",
84 .hash = 0,
662 - .acl = HTTP_ACL_DASHBOARD,
85 + .acl = HTTP_ACL_NODES,
86 .access = HTTP_ACCESS_ANONYMOUS_DATA,
664 - .callback = web_client_api_request_v2_nodes,
87 + .callback = api_v2_nodes,
88 .allow_subpaths = 0
89 },
90 {
91 .api = "node_instances",
92 .hash = 0,
670 - .acl = HTTP_ACL_DASHBOARD,
93 + .acl = HTTP_ACL_NODES,
94 .access = HTTP_ACCESS_ANONYMOUS_DATA,
672 - .callback = web_client_api_request_v2_node_instances,
95 + .callback = api_v2_node_instances,
96 .allow_subpaths = 0
97 },
98 {
99 .api = "versions",
100 .hash = 0,
678 - .acl = HTTP_ACL_DASHBOARD,
101 + .acl = HTTP_ACL_NODES,
102 .access = HTTP_ACCESS_ANONYMOUS_DATA,
680 - .callback = web_client_api_request_v2_versions,
103 + .callback = api_v2_versions,
104 .allow_subpaths = 0
105 },
106 {
107 .api = "progress",
108 .hash = 0,
686 - .acl = HTTP_ACL_DASHBOARD,
109 + .acl = HTTP_ACL_NOCHECK,
110 .access = HTTP_ACCESS_ANONYMOUS_DATA,
688 - .callback = web_client_api_request_v2_progress,
111 + .callback = api_v2_progress,
112 .allow_subpaths = 0
113 },
114
@@ -693,9 +116,9 @@ static struct web_api_command api_commands_v2[] = {
116 {
117 .api = "functions",
118 .hash = 0,
696 - .acl = HTTP_ACL_DASHBOARD,
119 + .acl = HTTP_ACL_FUNCTIONS,
120 .access = HTTP_ACCESS_ANONYMOUS_DATA,
698 - .callback = web_client_api_request_v2_functions,
121 + .callback = api_v2_functions,
122 .allow_subpaths = 0
123 },
124
@@ -705,7 +128,7 @@ static struct web_api_command api_commands_v2[] = {
128 .hash = 0,
129 .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
130 .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
708 - .callback = web_client_api_request_v2_webrtc,
131 + .callback = api_v2_webrtc,
132 .allow_subpaths = 0
133 },
134
@@ -715,7 +138,7 @@ static struct web_api_command api_commands_v2[] = {
138 .hash = 0,
139 .acl = HTTP_ACL_NOCHECK,
140 .access = HTTP_ACCESS_NONE,
718 - .callback = web_client_api_request_v2_claim,
141 + .callback = api_v2_claim,
142 .allow_subpaths = 0
143 },
144 {
@@ -731,17 +154,18 @@ static struct web_api_command api_commands_v2[] = {
154 .hash = 0,
155 .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
156 .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
734 - .callback = api_v2_bearer_token,
157 + .callback = api_v2_bearer_get_token,
158 .allow_subpaths = 0
159 },
160 +#endif
161
162 // Netdata branding APIs
163 {
164 .api = "ilove.svg",
165 .hash = 0,
742 - .acl = HTTP_ACL_DASHBOARD,
166 + .acl = HTTP_ACL_NOCHECK,
167 .access = HTTP_ACCESS_ANONYMOUS_DATA,
744 - .callback = web_client_api_request_v2_ilove,
168 + .callback = api_v2_ilove,
169 .allow_subpaths = 0
170 },
171
src/web/api/web_api_v3.c new
+263
@@ -0,0 +1,263 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "web_api_v3.h"
4 +#include "v1/api_v1_calls.h"
5 +#include "v2/api_v2_calls.h"
6 +#include "v3/api_v3_calls.h"
7 +
8 +static struct web_api_command api_commands_v3[] = {
9 + // time-series multi-node multi-instance data APIs
10 + {
11 + .api = "data",
12 + .hash = 0,
13 + .acl = HTTP_ACL_METRICS,
14 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
15 + .callback = api_v2_data,
16 + .allow_subpaths = 0
17 + },
18 + // badges can be fetched with both dashboard and badge ACL
19 + {
20 + .api = "badge.svg",
21 + .hash = 0,
22 + .acl = HTTP_ACL_BADGES,
23 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
24 + .callback = api_v1_badge,
25 + .allow_subpaths = 0
26 + },
27 + // scoring engine
28 + {
29 + .api = "weights",
30 + .hash = 0,
31 + .acl = HTTP_ACL_METRICS,
32 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
33 + .callback = api_v2_weights,
34 + .allow_subpaths = 0
35 + },
36 + // exporting API
37 + {
38 + .api = "allmetrics",
39 + .hash = 0,
40 + .acl = HTTP_ACL_METRICS,
41 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
42 + .callback = api_v1_allmetrics,
43 + .allow_subpaths = 0
44 + },
45 +
46 + // time-series multi-node multi-instance metadata APIs
47 + {
48 + .api = "context",
49 + .hash = 0,
50 + .acl = HTTP_ACL_METRICS,
51 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
52 + .callback = api_v1_context,
53 + .allow_subpaths = 0
54 + },
55 + {
56 + .api = "contexts",
57 + .hash = 0,
58 + .acl = HTTP_ACL_METRICS,
59 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
60 + .callback = api_v2_contexts,
61 + .allow_subpaths = 0
62 + },
63 +
64 + // fulltext search
65 + {
66 + .api = "q",
67 + .hash = 0,
68 + .acl = HTTP_ACL_METRICS,
69 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
70 + .callback = api_v2_q,
71 + .allow_subpaths = 0
72 + },
73 +
74 + // multi-node multi-instance alerts APIs
75 + {
76 + .api = "alerts",
77 + .hash = 0,
78 + .acl = HTTP_ACL_ALERTS,
79 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
80 + .callback = api_v2_alerts,
81 + .allow_subpaths = 0
82 + },
83 + {
84 + .api = "alert_transitions",
85 + .hash = 0,
86 + .acl = HTTP_ACL_ALERTS,
87 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
88 + .callback = api_v2_alert_transitions,
89 + .allow_subpaths = 0
90 + },
91 + {
92 + .api = "alert_config",
93 + .hash = 0,
94 + .acl = HTTP_ACL_ALERTS,
95 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
96 + .callback = api_v2_alert_config,
97 + .allow_subpaths = 0
98 + },
99 + {
100 + .api = "variable",
101 + .hash = 0,
102 + .acl = HTTP_ACL_ALERTS,
103 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
104 + .callback = api_v1_variable,
105 + .allow_subpaths = 0
106 + },
107 +
108 + // agent information APIs
109 + {
110 + .api = "info",
111 + .hash = 0,
112 + .acl = HTTP_ACL_NOCHECK,
113 + .access = HTTP_ACCESS_NONE,
114 + .callback = api_v2_info,
115 + .allow_subpaths = 0
116 + },
117 + {
118 + .api = "nodes",
119 + .hash = 0,
120 + .acl = HTTP_ACL_NODES,
121 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
122 + .callback = api_v2_nodes,
123 + .allow_subpaths = 0
124 + },
125 + {
126 + .api = "node_instances",
127 + .hash = 0,
128 + .acl = HTTP_ACL_NODES,
129 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
130 + .callback = api_v2_node_instances,
131 + .allow_subpaths = 0
132 + },
133 + {
134 + .api = "versions",
135 + .hash = 0,
136 + .acl = HTTP_ACL_NOCHECK,
137 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
138 + .callback = api_v2_versions,
139 + .allow_subpaths = 0
140 + },
141 + {
142 + .api = "progress",
143 + .hash = 0,
144 + .acl = HTTP_ACL_NOCHECK,
145 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
146 + .callback = api_v2_progress,
147 + .allow_subpaths = 0
148 + },
149 +
150 + // functions APIs
151 + {
152 + .api = "function",
153 + .hash = 0,
154 + .acl = HTTP_ACL_FUNCTIONS,
155 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
156 + .callback = api_v1_function,
157 + .allow_subpaths = 0
158 + },
159 + {
160 + .api = "functions",
161 + .hash = 0,
162 + .acl = HTTP_ACL_FUNCTIONS,
163 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
164 + .callback = api_v2_functions,
165 + .allow_subpaths = 0
166 + },
167 +
168 + // dyncfg APIs
169 + {
170 + .api = "config",
171 + .hash = 0,
172 + .acl = HTTP_ACL_DYNCFG,
173 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
174 + .callback = api_v1_config,
175 + .allow_subpaths = 0
176 + },
177 +
178 + // settings APIs
179 + {
180 + .api = "settings",
181 + .hash = 0,
182 + .acl = HTTP_ACL_NOCHECK,
183 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
184 + .callback = api_v3_settings,
185 + .allow_subpaths = 0
186 + },
187 +
188 + // WebRTC APIs
189 + {
190 + .api = "rtc_offer",
191 + .hash = 0,
192 + .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
193 + .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
194 + .callback = api_v2_webrtc,
195 + .allow_subpaths = 0
196 + },
197 +
198 + // management APIs
199 + {
200 + .api = "claim",
201 + .hash = 0,
202 + .acl = HTTP_ACL_NOCHECK,
203 + .access = HTTP_ACCESS_NONE,
204 + .callback = api_v2_claim,
205 + .allow_subpaths = 0
206 + },
207 + {
208 + .api = "bearer_protection",
209 + .hash = 0,
210 + .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
211 + .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG | HTTP_ACCESS_EDIT_AGENT_CONFIG,
212 + .callback = api_v2_bearer_protection,
213 + .allow_subpaths = 0
214 + },
215 + {
216 + .api = "bearer_get_token",
217 + .hash = 0,
218 + .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
219 + .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
220 + .callback = api_v2_bearer_get_token,
221 + .allow_subpaths = 0
222 + },
223 + {
224 + .api = "me",
225 + .hash = 0,
226 + .acl = HTTP_ACL_NOCHECK,
227 + .access = HTTP_ACCESS_NONE,
228 + .callback = api_v3_me,
229 + .allow_subpaths = 0
230 + },
231 +
232 + // Netdata branding APIs
233 + {
234 + .api = "ilove.svg",
235 + .hash = 0,
236 + .acl = HTTP_ACL_NOCHECK,
237 + .access = HTTP_ACCESS_NONE,
238 + .callback = api_v2_ilove,
239 + .allow_subpaths = 0
240 + },
241 +
242 + {// terminator
243 + .api = NULL,
244 + .hash = 0,
245 + .acl = HTTP_ACL_NONE,
246 + .access = HTTP_ACCESS_NONE,
247 + .callback = NULL,
248 + .allow_subpaths = 0
249 + },
250 +};
251 +
252 +inline int web_client_api_request_v3(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
253 + static int initialized = 0;
254 +
255 + if(unlikely(initialized == 0)) {
256 + initialized = 1;
257 +
258 + for(int i = 0; api_commands_v3[i].api ; i++)
259 + api_commands_v3[i].hash = simple_hash(api_commands_v3[i].api);
260 + }
261 +
262 + return web_client_api_request_vX(host, w, url_path_endpoint, api_commands_v3);
263 +}
src/web/api/web_api_v3.h new
+12
@@ -0,0 +1,12 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_WEB_API_V3_H
4 +#define NETDATA_WEB_API_V3_H
5 +
6 +#include "web_api.h"
7 +
8 +struct web_client;
9 +
10 +int web_client_api_request_v3(RRDHOST *host, struct web_client *w, char *url_path_endpoint);
11 +
12 +#endif //NETDATA_WEB_API_V3_H
src/web/server/h2o/http_server.c
+15 -8
@@ -24,6 +24,7 @@ static h2o_accept_ctx_t accept_ctx;
24 #define NBUF_INITIAL_SIZE_RESP (4096)
25 #define API_V1_PREFIX "/api/v1/"
26 #define API_V2_PREFIX "/api/v2/"
27 +#define API_V3_PREFIX "/api/v3/"
28 #define HOST_SELECT_PREFIX "/host/"
29
30 #define HTTPD_CONFIG_SECTION "httpd"
@@ -182,13 +183,17 @@ static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host)
183 norm_path.len--;
184 }
185
185 - unsigned int api_version = 2;
186 - size_t api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V2_PREFIX));
186 + unsigned int api_version = 3;
187 + size_t api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V3_PREFIX));
188 if (api_loc == SIZE_MAX) {
188 - api_version = 1;
189 - api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V1_PREFIX));
190 - if (api_loc == SIZE_MAX)
191 - return 1;
189 + api_version = 2;
190 + api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V2_PREFIX));
191 + if (api_loc == SIZE_MAX) {
192 + api_version = 1;
193 + api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V1_PREFIX));
194 + if (api_loc == SIZE_MAX)
195 + return 1;
196 + }
197 }
198
199 // API_V1_PREFIX and API_V2_PREFIX are the same length
@@ -235,7 +240,9 @@ static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host)
240 }
241
242 //inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
238 - if (api_version == 2)
243 + if (api_version == 3)
244 + web_client_api_request_v3(*host, &w, path_unescaped);
245 + else if (api_version == 2)
246 web_client_api_request_v2(*host, &w, path_unescaped);
247 else
248 web_client_api_request_v1(*host, &w, path_unescaped);
@@ -314,7 +321,7 @@ static int hdl_netdata_conf(h2o_handler_t *self, h2o_req_t *req)
321 return -1;
322
323 BUFFER *buf = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
317 - config_generate(buf, 0);
324 + netdata_conf_generate(buf, 0);
325
326 void *managed = h2o_mem_alloc_shared(&req->pool, buf->len, NULL);
327 memcpy(managed, buf->buffer, buf->len);
src/web/server/static/static-threaded.c
-8
@@ -211,7 +211,6 @@ static void *web_server_add_callback(POLLINFO *pi, short int *events, void *data
211 web_client_set_conn_tcp(w);
212 }
213
214 -#ifdef ENABLE_HTTPS
214 if ((web_client_check_conn_tcp(w)) && (netdata_ssl_web_server_ctx)) {
215 sock_delnonblock(w->ifd);
216
@@ -239,13 +238,10 @@ static void *web_server_add_callback(POLLINFO *pi, short int *events, void *data
238
239 sock_setnonblock(w->ifd);
240 }
242 -#endif
241
242 netdata_log_debug(D_WEB_CLIENT, "%llu: ADDED CLIENT FD %d", w->id, pi->fd);
243
246 -#ifdef ENABLE_HTTPS
244 cleanup:
248 -#endif
245 worker_is_idle();
246 return w;
247 }
@@ -503,14 +499,12 @@ void *socket_listen_main_static_threaded(void *ptr) {
499 if(!api_sockets.opened)
500 fatal("LISTENER: no listen sockets available.");
501
506 -#ifdef ENABLE_HTTPS
502 netdata_ssl_validate_certificate = !config_get_boolean(CONFIG_SECTION_WEB, "ssl skip certificate verification", !netdata_ssl_validate_certificate);
503
504 if(!netdata_ssl_validate_certificate_sender)
505 netdata_log_info("SSL: web server will skip SSL certificates verification.");
506
507 netdata_ssl_initialize_ctx(NETDATA_SSL_WEB_SERVER_CTX);
513 -#endif
508
509 // 6 threads is the optimal value
510 // since 6 are the parallel connections browsers will do
@@ -526,13 +520,11 @@ void *socket_listen_main_static_threaded(void *ptr) {
520
521 if (static_threaded_workers_count < 1) static_threaded_workers_count = 1;
522
529 -#ifdef ENABLE_HTTPS
523 // See https://github.com/netdata/netdata/issues/11081#issuecomment-831998240 for more details
524 if (OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110) {
525 static_threaded_workers_count = 1;
526 netdata_log_info("You are running an OpenSSL older than 1.1.0, web server will not enable multithreading.");
527 }
535 -#endif
528
529 size_t max_sockets = (size_t)config_get_number(CONFIG_SECTION_WEB, "web server max sockets",
530 (long long int)(rlimit_nofile.rlim_cur / 4));
src/web/server/web_client.c
+10 -51
@@ -33,6 +33,7 @@ void web_client_set_conn_webrtc(struct web_client *w) {
33 void web_client_reset_permissions(struct web_client *w) {
34 web_client_flags_clear_auth(w);
35 w->access = HTTP_ACCESS_NONE;
36 + w->user_role = HTTP_USER_ROLE_NONE;
37 }
38
39 void web_client_set_permissions(struct web_client *w, HTTP_ACCESS access, HTTP_USER_ROLE role, WEB_CLIENT_FLAGS auth) {
@@ -97,7 +98,6 @@ static inline int web_client_cork_socket(struct web_client *w __maybe_unused) {
98 return 0;
99 }
100
100 -#ifdef ENABLE_HTTPS
101 static inline void web_client_enable_wait_from_ssl(struct web_client *w) {
102 if (w->ssl.ssl_errno == SSL_ERROR_WANT_READ)
103 web_client_enable_ssl_wait_receive(w);
@@ -108,7 +108,6 @@ static inline void web_client_enable_wait_from_ssl(struct web_client *w) {
108 web_client_disable_ssl_wait_send(w);
109 }
110 }
111 -#endif
111
112 static inline int web_client_uncork_socket(struct web_client *w __maybe_unused) {
113 #ifdef TCP_CORK
@@ -211,6 +210,8 @@ static void web_client_reset_allocations(struct web_client *w, bool free_all) {
210 }
211
212 memset(w->transaction, 0, sizeof(w->transaction));
213 + memset(&w->auth, 0, sizeof(w->auth));
214 +
215 web_client_reset_permissions(w);
216 web_client_flag_clear(w, WEB_CLIENT_ENCODING_GZIP|WEB_CLIENT_ENCODING_DEFLATE);
217 web_client_reset_path_flags(w);
@@ -589,7 +590,9 @@ int web_client_api_request(RRDHOST *host, struct web_client *w, char *url_path_f
590 // get the api version
591 char *tok = strsep_skip_consecutive_separators(&url_path_fragment, "/");
592 if(tok && *tok) {
592 - if(strcmp(tok, "v2") == 0)
593 + if(strcmp(tok, "v3") == 0)
594 + return web_client_api_request_v3(host, w, url_path_fragment);
595 + else if(strcmp(tok, "v2") == 0)
596 return web_client_api_request_v2(host, w, url_path_fragment);
597 else if(strcmp(tok, "v1") == 0)
598 return web_client_api_request_v1(host, w, url_path_fragment);
@@ -644,7 +647,6 @@ static inline char *web_client_valid_method(struct web_client *w, char *s) {
647 else if(!strncmp(s, "STREAM ", 7)) {
648 s = &s[7];
649
647 -#ifdef ENABLE_HTTPS
650 if (!SSL_connection(&w->ssl) && http_is_using_ssl_force(w)) {
651 w->header_parse_tries = 0;
652 w->header_parse_last_size = 0;
@@ -672,7 +674,6 @@ static inline char *web_client_valid_method(struct web_client *w, char *s) {
674 netdata_log_error("The server is configured to always use encrypted connections, please enable the SSL on child with hostname '%s'.",hostname);
675 s = NULL;
676 }
675 -#endif
677
678 w->mode = HTTP_REQUEST_MODE_STREAM;
679 }
@@ -787,7 +788,6 @@ HTTP_VALIDATION http_request_validate(struct web_client *w) {
788 web_client_decode_path_and_query_string(w, encoded_url);
789 *ue = c;
790
790 -#ifdef ENABLE_HTTPS
791 if ( (web_client_check_conn_tcp(w)) && (netdata_ssl_web_server_ctx) ) {
792 if (!w->ssl.conn && (http_is_using_ssl_force(w) || http_is_using_ssl_default(w)) && (w->mode != HTTP_REQUEST_MODE_STREAM)) {
793 w->header_parse_tries = 0;
@@ -796,7 +796,6 @@ HTTP_VALIDATION http_request_validate(struct web_client *w) {
796 return HTTP_VALIDATION_REDIRECT;
797 }
798 }
799 -#endif
799
800 w->header_parse_tries = 0;
801 w->header_parse_last_size = 0;
@@ -817,7 +816,6 @@ HTTP_VALIDATION http_request_validate(struct web_client *w) {
816 static inline ssize_t web_client_send_data(struct web_client *w,const void *buf,size_t len, int flags)
817 {
818 ssize_t bytes;
820 -#ifdef ENABLE_HTTPS
819 if ((web_client_check_conn_tcp(w)) && (netdata_ssl_web_server_ctx)) {
820 if (SSL_connection(&w->ssl)) {
821 bytes = netdata_ssl_write(&w->ssl, buf, len) ;
@@ -830,12 +828,6 @@ static inline ssize_t web_client_send_data(struct web_client *w,const void *buf,
828 bytes = send(w->ofd,buf, len , flags);
829 else
830 bytes = -999;
833 -#else
834 - if(web_client_check_conn_tcp(w) || web_client_check_conn_unix(w))
835 - bytes = send(w->ofd, buf, len, flags);
836 - else
837 - bytes = -999;
838 -#endif
831
832 return bytes;
833 }
@@ -967,7 +959,7 @@ static inline void web_client_send_http_header(struct web_client *w) {
959
960 size_t count = 0;
961 ssize_t bytes;
970 -#ifdef ENABLE_HTTPS
962 +
963 if ( (web_client_check_conn_tcp(w)) && (netdata_ssl_web_server_ctx) ) {
964 if (SSL_connection(&w->ssl)) {
965 bytes = netdata_ssl_write(&w->ssl, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output));
@@ -996,20 +988,6 @@ static inline void web_client_send_http_header(struct web_client *w) {
988 }
989 else
990 bytes = -999;
999 -#else
1000 - if(web_client_check_conn_tcp(w) || web_client_check_conn_unix(w)) {
1001 - while ((bytes = send(w->ofd, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output), 0)) == -1) {
1002 - count++;
1003 -
1004 - if (count > 100 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
1005 - netdata_log_error("Cannot send HTTP headers to web client.");
1006 - break;
1007 - }
1008 - }
1009 - }
1010 - else
1011 - bytes = -999;
1012 -#endif
991
992 if(bytes != (ssize_t) buffer_strlen(w->response.header_output)) {
993 if(bytes > 0)
@@ -1224,7 +1202,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1202 netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: generating netdata.conf ...", w->id);
1203 w->response.data->content_type = CT_TEXT_PLAIN;
1204 buffer_flush(w->response.data);
1227 - config_generate(w->response.data, 0);
1205 + netdata_conf_generate(w->response.data, 0);
1206 return HTTP_RESP_OK;
1207 }
1208 #ifdef NETDATA_INTERNAL_CHECKS
@@ -1310,11 +1288,7 @@ static bool web_server_log_transport(BUFFER *wb, void *ptr) {
1288 if(!w)
1289 return false;
1290
1313 -#ifdef ENABLE_HTTPS
1291 buffer_strcat(wb, SSL_connection(&w->ssl) ? "https" : "http");
1315 -#else
1316 - buffer_strcat(wb, "http");
1317 -#endif
1292 return true;
1293 }
1294
@@ -1457,7 +1431,7 @@ void web_client_process_request_from_web_server(struct web_client *w) {
1431 return;
1432 }
1433 break;
1460 -#ifdef ENABLE_HTTPS
1434 +
1435 case HTTP_VALIDATION_REDIRECT:
1436 {
1437 buffer_flush(w->response.data);
@@ -1473,7 +1447,7 @@ void web_client_process_request_from_web_server(struct web_client *w) {
1447 w->response.code = HTTP_RESP_HTTPS_UPGRADE;
1448 break;
1449 }
1476 -#endif
1450 +
1451 case HTTP_VALIDATION_MALFORMED_URL:
1452 netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Malformed URL '%s'.", w->id, w->response.data->buffer);
1453
@@ -1857,7 +1831,6 @@ ssize_t web_client_receive(struct web_client *w)
1831
1832 errno_clear();
1833
1860 -#ifdef ENABLE_HTTPS
1834 if ( (web_client_check_conn_tcp(w)) && (netdata_ssl_web_server_ctx) ) {
1835 if (SSL_connection(&w->ssl)) {
1836 bytes = netdata_ssl_read(&w->ssl, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1));
@@ -1872,12 +1845,6 @@ ssize_t web_client_receive(struct web_client *w)
1845 }
1846 else // other connection methods
1847 bytes = -1;
1875 -#else
1876 - if(web_client_check_conn_tcp(w) || web_client_check_conn_unix(w))
1877 - bytes = recv(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1), MSG_DONTWAIT);
1878 - else
1879 - bytes = -1;
1880 -#endif
1848
1849 if(likely(bytes > 0)) {
1850 w->statistics.received_bytes += bytes;
@@ -1960,9 +1927,7 @@ void web_client_reuse_from_cache(struct web_client *w) {
1927 BUFFER *b6 = w->url_query_string_decoded;
1928 BUFFER *b7 = w->payload;
1929
1963 -#ifdef ENABLE_HTTPS
1930 NETDATA_SSL ssl = w->ssl;
1965 -#endif
1931
1932 size_t use_count = w->use_count;
1933 size_t *statistics_memory_accounting = w->statistics.memory_accounting;
@@ -1974,9 +1939,7 @@ void web_client_reuse_from_cache(struct web_client *w) {
1939 w->statistics.memory_accounting = statistics_memory_accounting;
1940 w->use_count = use_count;
1941
1977 -#ifdef ENABLE_HTTPS
1942 w->ssl = ssl;
1979 -#endif
1943
1944 // restore the pointers of the buffers
1945 w->response.data = b1;
@@ -1991,9 +1954,7 @@ void web_client_reuse_from_cache(struct web_client *w) {
1954 struct web_client *web_client_create(size_t *statistics_memory_accounting) {
1955 struct web_client *w = (struct web_client *)callocz(1, sizeof(struct web_client));
1956
1994 -#ifdef ENABLE_HTTPS
1957 w->ssl = NETDATA_SSL_UNSET_CONNECTION;
1996 -#endif
1958
1959 w->use_count = 1;
1960 w->statistics.memory_accounting = statistics_memory_accounting;
@@ -2011,9 +1972,7 @@ struct web_client *web_client_create(size_t *statistics_memory_accounting) {
1972 }
1973
1974 void web_client_free(struct web_client *w) {
2014 -#ifdef ENABLE_HTTPS
1975 netdata_ssl_close(&w->ssl);
2016 -#endif
1976
1977 web_client_reset_allocations(w, true);
1978
src/web/server/web_client.h
+4 -8
@@ -21,9 +21,7 @@ typedef enum __attribute__((packed)) {
21 HTTP_VALIDATION_EXCESS_REQUEST_DATA,
22 HTTP_VALIDATION_MALFORMED_URL,
23 HTTP_VALIDATION_INCOMPLETE,
24 -#ifdef ENABLE_HTTPS
24 HTTP_VALIDATION_REDIRECT
26 -#endif
25 } HTTP_VALIDATION;
26
27 typedef enum __attribute__((packed)) {
@@ -112,9 +110,9 @@ typedef enum __attribute__((packed)) {
110 #define web_client_check_conn_tcp(w) web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_TCP)
111 #define web_client_check_conn_cloud(w) web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD)
112 #define web_client_check_conn_webrtc(w) web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_WEBRTC)
115 -
116 -#define WEB_CLIENT_FLAG_ALL_AUTHS (WEB_CLIENT_FLAG_AUTH_CLOUD | WEB_CLIENT_FLAG_AUTH_BEARER)
113 #define web_client_flags_clear_conn(w) web_client_flag_clear(w, WEB_CLIENT_FLAG_CONN_TCP | WEB_CLIENT_FLAG_CONN_UNIX | WEB_CLIENT_FLAG_CONN_CLOUD | WEB_CLIENT_FLAG_CONN_WEBRTC)
114 +
115 +#define WEB_CLIENT_FLAG_ALL_AUTHS (WEB_CLIENT_FLAG_AUTH_CLOUD | WEB_CLIENT_FLAG_AUTH_BEARER | WEB_CLIENT_FLAG_AUTH_GOD)
116 #define web_client_flags_check_auth(w) web_client_flag_check(w, WEB_CLIENT_FLAG_ALL_AUTHS)
117 #define web_client_flags_clear_auth(w) web_client_flag_clear(w, WEB_CLIENT_FLAG_ALL_AUTHS)
118
@@ -136,7 +134,7 @@ void web_client_set_conn_webrtc(struct web_client *w);
134 #define NETDATA_WEB_REQUEST_MAX_SIZE 65536
135 #define NETDATA_WEB_DECODED_URL_INITIAL_SIZE 512
136
139 -#define CLOUD_USER_NAME_LENGTH 64
137 +#define CLOUD_CLIENT_NAME_LENGTH 64
138
139 struct response {
140 BUFFER *header; // our response header
@@ -202,14 +200,12 @@ struct web_client {
200 size_t pollinfo_slot; // POLLINFO slot of the web client
201 size_t pollinfo_filecopy_slot; // POLLINFO slot of the file read
202
205 -#ifdef ENABLE_HTTPS
203 NETDATA_SSL ssl;
207 -#endif
204
205 struct {
206 nd_uuid_t bearer_token;
207 nd_uuid_t cloud_account_id;
212 - char client_name[CLOUD_USER_NAME_LENGTH];
208 + char client_name[CLOUD_CLIENT_NAME_LENGTH];
209 } auth;
210
211 struct { // A callback to check if the query should be interrupted / stopped
src/web/server/web_client_cache.c
+1 -2
@@ -119,15 +119,14 @@ struct web_client *web_client_get_from_cache(void) {
119 w->mode = HTTP_REQUEST_MODE_GET;
120 web_client_reset_permissions(w);
121 memset(w->transaction, 0, sizeof(w->transaction));
122 + memset(&w->auth, 0, sizeof(w->auth));
123
124 return w;
125 }
126
127 void web_client_release_to_cache(struct web_client *w) {
128
128 -#ifdef ENABLE_HTTPS
129 netdata_ssl_close(&w->ssl);
130 -#endif
130
131 // unlink it from the used
132 spinlock_lock(&web_clients_cache.used.spinlock);
src/web/server/web_server.c
-4
@@ -134,11 +134,7 @@ void web_client_update_acl_matches(struct web_client *w) {
134 void web_server_log_connection(struct web_client *w, const char *msg) {
135 ND_LOG_STACK lgs[] = {
136 ND_LOG_FIELD_U64(NDF_CONNECTION_ID, w->id),
137 -#ifdef ENABLE_HTTPS
137 ND_LOG_FIELD_TXT(NDF_SRC_TRANSPORT, SSL_connection(&w->ssl) ? "https" : "http"),
139 -#else
140 - ND_LOG_FIELD_TXT(NDF_SRC_TRANSPORT, "http"),
141 -#endif
138 ND_LOG_FIELD_TXT(NDF_SRC_IP, w->client_ip),
139 ND_LOG_FIELD_TXT(NDF_SRC_PORT, w->client_port),
140 ND_LOG_FIELD_TXT(NDF_SRC_FORWARDED_HOST, w->forwarded_host),