From 9326ccda4f440e3b3a842ed196041f054377d23c Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:24:29 +0100 Subject: [PATCH] Mon 147936 native windows drivesize (#1866) (#1898) * implement abstract test * implement windows test * fix agent test memory corruption, add drive_size ut * pass test variables in a json * add robot test * fix tests * fix agent installer test * agent accepts empty filters --- .github/scripts/agent_installer_test.ps1 | 19 +- .github/scripts/agent_robot_test.ps1 | 23 +- .github/scripts/wsl-collect-test-robot.sh | 12 +- .../workflows/windows-agent-robot-test.yml | 1 + agent/CMakeLists.txt | 2 + agent/doc/agent-doc.md | 11 +- agent/inc/com/centreon/agent/drive_size.hh | 280 ++++++++ agent/native_windows/src/check_cpu.cc | 93 +-- agent/native_windows/src/check_drive_size.cc | 151 +++++ agent/native_windows/src/check_uptime.cc | 24 +- agent/precomp_inc/precomp.hh | 3 + agent/src/check.cc | 6 +- agent/src/config.cc | 1 - agent/src/config_win.cc | 2 - agent/src/drive_size.cc | 585 +++++++++++++++++ agent/src/main.cc | 4 + agent/src/main_win.cc | 15 + agent/src/scheduler.cc | 5 + agent/test/CMakeLists.txt | 1 + agent/test/check_linux_cpu_test.cc | 74 ++- agent/test/check_uptime_test.cc | 29 +- agent/test/check_windows_cpu_test.cc | 25 +- agent/test/drive_size_test.cc | 601 ++++++++++++++++++ broker/CMakeLists.txt | 2 +- common/inc/com/centreon/common/perfdata.hh | 4 +- .../com/centreon/common/rapidjson_helper.hh | 1 + common/src/perfdata.cc | 4 + common/src/rapidjson_helper.cc | 17 + tests/broker-engine/cma.robot | 144 ++++- tests/resources/Agent.py | 35 + tests/resources/Common.py | 44 ++ 31 files changed, 2087 insertions(+), 131 deletions(-) create mode 100644 agent/inc/com/centreon/agent/drive_size.hh create mode 100644 agent/native_windows/src/check_drive_size.cc create mode 100644 agent/src/drive_size.cc create mode 100644 agent/test/drive_size_test.cc diff --git a/.github/scripts/agent_installer_test.ps1 b/.github/scripts/agent_installer_test.ps1 index 275681a7641..c2d91f3b044 100644 --- a/.github/scripts/agent_installer_test.ps1 +++ b/.github/scripts/agent_installer_test.ps1 @@ -49,8 +49,12 @@ function test_args_to_registry { exit 1 } - #let time to windows to flush registry - Start-Sleep -Seconds 2 + for (($i = 0); $i -lt 10; $i++) { + Start-Sleep -Seconds 1 + if (Get-ItemProperty -Path HKLM:\Software\Centreon\CentreonMonitoringAgent) { + break + } + } foreach ($value_name in $expected_registry_values.Keys) { $expected_value = $($expected_registry_values[$value_name]) @@ -95,12 +99,15 @@ if ($process_info.ExitCode -ne 0) { exit 1 } -Start-Sleep -Seconds 5 -Get-Process | Select-Object -Property ProcessName | Select-String centagent +for (($i = 0); $i -lt 10; $i++) { + Start-Sleep -Seconds 1 + $info = Get-Process | Select-Object -Property ProcessName | Select-String centagent + if (! $info) { + break + } +} -$info = Get-Process | Select-Object -Property ProcessName | Select-String centagent -#$info = Get-Process centagent 2>$null if ($info) { Write-Host "centagent.exe running" exit 1 diff --git a/.github/scripts/agent_robot_test.ps1 b/.github/scripts/agent_robot_test.ps1 index ba6fd13381c..0190b0dff23 100644 --- a/.github/scripts/agent_robot_test.ps1 +++ b/.github/scripts/agent_robot_test.ps1 @@ -100,7 +100,28 @@ Set-ItemProperty -Path HKLM:\SOFTWARE\Centreon\CentreonMonitoringAgent -Name lo Start-Process -FilePath build_windows\agent\Release\centagent.exe -ArgumentList "--standalone" -RedirectStandardOutput reports\encrypted_reversed_centagent_stdout.log -RedirectStandardError reports\encrypted_reversed_centagent_stderr.log -wsl cd $wsl_path `&`& .github/scripts/wsl-collect-test-robot.sh broker-engine/cma.robot $my_host_name $my_ip $pwsh_path ${current_dir}.replace('\','/') +$uptime = (Get-WmiObject -Class Win32_OperatingSystem).LastBootUpTime #dtmf format +$d_uptime = [Management.ManagementDateTimeConverter]::ToDateTime($uptime) #datetime format +$ts_uptime = ([DateTimeOffset]$d_uptime).ToUnixTimeSeconds() #timestamp format + +$test_param = @{ + 'host'= $my_host_name + 'ip'= $my_ip + 'wsl_path'= $wsl_path + 'pwsh_path'= $pwsh_path + 'drive' = @() + 'current_dir' = $current_dir.replace('\','/') + 'uptime' = $ts_uptime +} + +Get-PSDrive -PSProvider FileSystem | Select Name, Used, Free | ForEach-Object -Process {$test_param.drive += $_} + +$json_test_param = $test_param | ConvertTo-Json -Compress + +Write-Host "json_test_param" $json_test_param +$quoted_json_test_param = "'" + $json_test_param + "'" + +wsl cd $wsl_path `&`& .github/scripts/wsl-collect-test-robot.sh broker-engine/cma.robot $quoted_json_test_param #something wrong in robot test => exit 1 => failure if (Test-Path -Path 'reports\windows-cma-failed' -PathType Container) { diff --git a/.github/scripts/wsl-collect-test-robot.sh b/.github/scripts/wsl-collect-test-robot.sh index ba54ad04fb0..6f2376c6828 100755 --- a/.github/scripts/wsl-collect-test-robot.sh +++ b/.github/scripts/wsl-collect-test-robot.sh @@ -4,10 +4,12 @@ set -x test_file=$1 export RUN_ENV=WSL -export HOST_NAME=$2 -export USED_ADDRESS=$3 -export PWSH_PATH=$4 -export WINDOWS_PROJECT_PATH=$5 +export JSON_TEST_PARAMS=$2 +export USED_ADDRESS=`echo $JSON_TEST_PARAMS | jq -r .ip` +export HOST_NAME=`echo $JSON_TEST_PARAMS | jq -r .host` +export PWSH_PATH=`echo $JSON_TEST_PARAMS | jq -r .pwsh_path` +export WINDOWS_PROJECT_PATH=`echo $JSON_TEST_PARAMS | jq -r .current_dir` + #in order to connect to windows we neeed to use windows ip @@ -17,7 +19,7 @@ echo "${USED_ADDRESS} ${HOST_NAME}" >> /etc/hosts echo "##### /etc/hosts: ######" cat /etc/hosts -echo "##### Starting tests #####" +echo "##### Starting tests ##### with params: $JSON_TEST_PARAMS" cd tests ./init-proto.sh diff --git a/.github/workflows/windows-agent-robot-test.yml b/.github/workflows/windows-agent-robot-test.yml index 31cd5c7d5ff..42f072f31e0 100644 --- a/.github/workflows/windows-agent-robot-test.yml +++ b/.github/workflows/windows-agent-robot-test.yml @@ -67,6 +67,7 @@ jobs: python3 python3-pip rrdtool + jq - name: IP info run: | diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index dea664c00c1..c690932e2ad 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -113,6 +113,7 @@ set( SRC_COMMON ${SRC_DIR}/bireactor.cc ${SRC_DIR}/check.cc ${SRC_DIR}/check_exec.cc + ${SRC_DIR}/drive_size.cc ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.cc ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.pb.cc ${SRC_DIR}/opentelemetry/proto/metrics/v1/metrics.pb.cc @@ -126,6 +127,7 @@ set( SRC_COMMON set( SRC_WINDOWS ${SRC_DIR}/config_win.cc ${NATIVE_SRC}/check_uptime.cc + ${NATIVE_SRC}/check_drive_size.cc ) set( SRC_LINUX diff --git a/agent/doc/agent-doc.md b/agent/doc/agent-doc.md index cc6ea0a4eb6..7d051131b16 100644 --- a/agent/doc/agent-doc.md +++ b/agent/doc/agent-doc.md @@ -114,4 +114,13 @@ metrics aren't the same as linux version. We collect user, idle, kernel , interr There are two methods, you can use internal microsoft function NtQuerySystemInformation. Yes Microsoft says that they can change signature or data format at any moment, but it's quite stable for many years. A trick, idle time is included un kernel time, so we subtract first from the second. Dpc time is yet included in interrupt time, so we don't sum it to calculate total time. The second one relies on performance data counters (pdh API), it gives us percentage despite that sum of percentage is not quite 100%. That's why the default method is the first one. -The choice between the two methods is done by 'use-nt-query-system-information' boolean parameter. \ No newline at end of file +The choice between the two methods is done by 'use-nt-query-system-information' boolean parameter. + +### check_drive_size +we have to get free space on server drives. In case of network drives, this call can block in case of network failure. Unfortunately, there is no asynchronous API to do that. So a dedicated thread (drive_size_thread) computes these statistics. In order to be os independent and to test it, drive_size_thread relies on a functor that do the job: drive_size_thread::os_fs_stats. This functor is initialized in main function. drive_size thread is stopped at the end of main function. + +So it works like that: +* check_drive_size post query in drive_size_thread queue +* drive_size_thread call os_fs_stats +* drive_size_thread post result in io_context +* io_context calls check_drive_size::_completion_handler \ No newline at end of file diff --git a/agent/inc/com/centreon/agent/drive_size.hh b/agent/inc/com/centreon/agent/drive_size.hh new file mode 100644 index 00000000000..c47e4ad0359 --- /dev/null +++ b/agent/inc/com/centreon/agent/drive_size.hh @@ -0,0 +1,280 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CENTREON_AGENT_NATIVE_DRIVE_SIZE_BASE_HH +#define CENTREON_AGENT_NATIVE_DRIVE_SIZE_BASE_HH + +#include +#include +#include "absl/base/thread_annotations.h" +#include "absl/container/btree_set.h" +#include "absl/synchronization/mutex.h" +#include "boost/asio/io_context.hpp" +#include "check.hh" +#include "re2/re2.h" + +namespace com::centreon::agent { +namespace check_drive_size_detail { + +/** + * @brief these flags are passed in check parameter:filter-storage-type and + * filter-type + * + */ +enum e_drive_fs_type : uint64_t { + hr_unknown = 0, + hr_storage_ram = 1 << 0, + hr_storage_virtual_memory = 1 << 1, + hr_storage_fixed_disk = 1 << 2, + hr_storage_removable_disk = 1 << 3, + hr_storage_floppy_disk = 1 << 4, + hr_storage_compact_disc = 1 << 5, + hr_storage_ram_disk = 1 << 6, + hr_storage_flash_memory = 1 << 7, + hr_storage_network_disk = 1 << 8, + hr_fs_other = 1 << 9, + hr_fs_unknown = 1 << 10, + hr_fs_berkeley_ffs = 1 << 11, + hr_fs_sys5_fs = 1 << 12, + hr_fs_fat = 1 << 13, + hr_fs_hpfs = 1 << 14, + hr_fs_hfs = 1 << 15, + hr_fs_mfs = 1 << 16, + hr_fs_ntfs = 1 << 17, + hr_fs_vnode = 1 << 18, + hr_fs_journaled = 1 << 19, + hr_fs_iso9660 = 1 << 20, + hr_fs_rock_ridge = 1 << 21, + hr_fs_nfs = 1 << 22, + hr_fs_netware = 1 << 23, + hr_fs_afs = 1 << 24, + hr_fs_dfs = 1 << 25, + hr_fs_appleshare = 1 << 26, + hr_fs_rfs = 1 << 27, + hr_fs_dgcfs = 1 << 28, + hr_fs_bfs = 1 << 29, + hr_fs_fat32 = 1 << 30, + hr_fs_linux_ext2 = 1U << 31, + hr_fs_linux_ext4 = 1ULL << 32, + hr_fs_exfat = 1ULL << 33 +}; + +/** + * @brief user can check only some fs by using filters + * This is the goal of this class + * In order to improve perf, results of previous tests are saved + * in cache sets. That's why is_allowed is not const + * + */ +class filter { + using string_set = absl::flat_hash_set; + + string_set _cache_allowed_fs ABSL_GUARDED_BY(_protect); + string_set _cache_excluded_fs ABSL_GUARDED_BY(_protect); + string_set _cache_allowed_mountpoint ABSL_GUARDED_BY(_protect); + string_set _cache_excluded_mountpoint ABSL_GUARDED_BY(_protect); + + mutable absl::Mutex _protect; + + unsigned _fs_type_filter; + + std::unique_ptr _filter_fs, _filter_exclude_fs; + std::unique_ptr _filter_mountpoint, _filter_exclude_mountpoint; + + public: + filter(const rapidjson::Value& args); + + bool is_allowed(const std::string_view& fs, + const std::string_view& mount_point, + e_drive_fs_type fs_type); + + bool is_fs_yet_allowed(const std::string_view& fs) const; + + bool is_fs_yet_excluded(const std::string_view& fs) const; +}; + +/** + * @brief tupple where we store statistics of a fs + * + */ +struct fs_stat { + fs_stat() = default; + fs_stat(std::string&& fs_in, uint64_t used_in, uint64_t total_in) + : fs(fs_in), mount_point(fs), used(used_in), total(total_in) {} + + fs_stat(std::string&& fs_in, + std::string&& mount_point_in, + uint64_t used_in, + uint64_t total_in) + : fs(fs_in), + mount_point(mount_point_in), + used(used_in), + total(total_in) {} + + fs_stat(const std::string_view& fs_in, + const std::string_view& mount_point_in, + uint64_t used_in, + uint64_t total_in) + : fs(fs_in), + mount_point(mount_point_in), + used(used_in), + total(total_in) {} + + std::string fs; + std::string mount_point; + uint64_t used; + uint64_t total; + + bool is_used_more_than_threshold(uint64_t threshold) const { + return used >= threshold; + } + + bool is_free_less_than_threshold(uint64_t threshold) const { + return total - used < threshold; + } + + bool is_used_more_than_prct_threshold(uint64_t percent_hundredth) const { + if (!total) { + return true; + } + return (used * 10000) / total >= percent_hundredth; + } + + bool is_free_less_than_prct_threshold(uint64_t percent_hundredth) const { + if (!total) { + return true; + } + return ((total - used) * 10000) / total < percent_hundredth; + } + + double get_used_prct() const { + if (!total) + return 0.0; + return static_cast(used * 100) / total; + } + + double get_free_prct() const { + if (!total) + return 0.0; + return static_cast((total - used) * 100) / total; + } +}; + +/** + * @brief get fs statistics can block on network drives, so we use this thread + * to do the job and not block main thread + * + */ +class drive_size_thread + : public std::enable_shared_from_this { + std::shared_ptr _io_context; + + using completion_handler = std::function)>; + + struct async_data { + std::shared_ptr request_filter; + completion_handler handler; + time_point timeout; + }; + + std::list _queue ABSL_GUARDED_BY(_queue_m); + absl::Mutex _queue_m; + + bool _active = true; + + std::shared_ptr _logger; + + bool has_to_stop_wait() const { return !_active || !_queue.empty(); } + + public: + typedef std::list ( + *get_fs_stats)(filter&, const std::shared_ptr& logger); + + static get_fs_stats os_fs_stats; + + drive_size_thread(const std::shared_ptr& io_context, + const std::shared_ptr& logger) + : _io_context(io_context), _logger(logger) {} + + void run(); + + void kill(); + + template + void async_get_fs_stats(const std::shared_ptr& request_filter, + const time_point& timeout, + handler_type&& handler); +}; + +} // namespace check_drive_size_detail + +/** + * @brief drive size check object (same for linux and windows) + * + */ +class check_drive_size : public check { + std::shared_ptr _filter; + bool _prct_threshold; + bool _free_threshold; + uint64_t _warning; // value in bytes or percent * 100 + uint64_t _critical; + + typedef e_status (check_drive_size::*fs_stat_test)( + const check_drive_size_detail::fs_stat&) const; + + fs_stat_test _fs_test; + + e_status _used_test(const check_drive_size_detail::fs_stat& fs) const; + e_status _prct_used_test(const check_drive_size_detail::fs_stat& fs) const; + + e_status _free_test(const check_drive_size_detail::fs_stat& fs) const; + e_status _prct_free_test(const check_drive_size_detail::fs_stat& fs) const; + + e_status _no_test(const check_drive_size_detail::fs_stat& fs) const; + + void _completion_handler( + unsigned start_check_index, + const std::list& result); + + public: + check_drive_size(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + time_point first_start_expected, + duration check_interval, + const std::string& serv, + const std::string& cmd_name, + const std::string& cmd_line, + const rapidjson::Value& args, + const engine_to_agent_request_ptr& cnf, + check::completion_handler&& handler); + + virtual ~check_drive_size() = default; + + std::shared_ptr shared_from_this() { + return std::static_pointer_cast( + check::shared_from_this()); + } + + void start_check(const duration& timeout) override; + + static void thread_kill(); +}; + +} // namespace com::centreon::agent + +#endif // CENTREON_AGENT_NATIVE_DRIVE_SIZE_HH \ No newline at end of file diff --git a/agent/native_windows/src/check_cpu.cc b/agent/native_windows/src/check_cpu.cc index 37c47bfe8d9..11e95510340 100644 --- a/agent/native_windows/src/check_cpu.cc +++ b/agent/native_windows/src/check_cpu.cc @@ -410,63 +410,70 @@ check_cpu::check_cpu(const std::shared_ptr& io_context, std::move(handler)) { - com::centreon::common::rapidjson_helper arg(args); - if (args.IsObject()) { - for (auto member_iter = args.MemberBegin(); member_iter != args.MemberEnd(); - ++member_iter) { - auto cpu_to_status_search = _label_to_cpu_to_status.find( - absl::AsciiStrToLower(member_iter->name.GetString())); - if (cpu_to_status_search != _label_to_cpu_to_status.end()) { - const rapidjson::Value& val = member_iter->value; - if (val.IsFloat() || val.IsInt() || val.IsUint() || val.IsInt64() || - val.IsUint64()) { - check_cpu_detail::cpu_to_status cpu_checker = - cpu_to_status_search->second(member_iter->value.GetDouble() / - 100); - _cpu_to_status.emplace( - std::make_tuple(cpu_checker.get_proc_stat_index(), - cpu_checker.is_average(), - cpu_checker.get_status()), - cpu_checker); - } else if (val.IsString()) { - auto to_conv = val.GetString(); - double dval; - if (absl::SimpleAtod(to_conv, &dval)) { + try { + com::centreon::common::rapidjson_helper arg(args); + if (args.IsObject()) { + for (auto member_iter = args.MemberBegin(); + member_iter != args.MemberEnd(); ++member_iter) { + auto cpu_to_status_search = _label_to_cpu_to_status.find( + absl::AsciiStrToLower(member_iter->name.GetString())); + if (cpu_to_status_search != _label_to_cpu_to_status.end()) { + const rapidjson::Value& val = member_iter->value; + if (val.IsFloat() || val.IsInt() || val.IsUint() || val.IsInt64() || + val.IsUint64()) { check_cpu_detail::cpu_to_status cpu_checker = - cpu_to_status_search->second(dval / 100); + cpu_to_status_search->second(member_iter->value.GetDouble() / + 100); _cpu_to_status.emplace( std::make_tuple(cpu_checker.get_proc_stat_index(), cpu_checker.is_average(), cpu_checker.get_status()), cpu_checker); + } else if (val.IsString()) { + auto to_conv = val.GetString(); + double dval; + if (absl::SimpleAtod(to_conv, &dval)) { + check_cpu_detail::cpu_to_status cpu_checker = + cpu_to_status_search->second(dval / 100); + _cpu_to_status.emplace( + std::make_tuple(cpu_checker.get_proc_stat_index(), + cpu_checker.is_average(), + cpu_checker.get_status()), + cpu_checker); + } else { + SPDLOG_LOGGER_ERROR( + logger, + "command: {}, value is not a number for parameter {}: {}", + cmd_name, member_iter->name, val); + } + + } else { + SPDLOG_LOGGER_ERROR(logger, + "command: {}, bad value for parameter {}: {}", + cmd_name, member_iter->name, val); + } + } else if (member_iter->name == "use-nt-query-system-information") { + const rapidjson::Value& val = member_iter->value; + if (val.IsBool()) { + _use_nt_query_system_information = val.GetBool(); } else { SPDLOG_LOGGER_ERROR( logger, - "command: {}, value is not a number for parameter {}: {}", - cmd_name, member_iter->name, val); + "command: {}, use-nt-query-system-information is not a boolean", + cmd_name); } - - } else { - SPDLOG_LOGGER_ERROR(logger, - "command: {}, bad value for parameter {}: {}", - cmd_name, member_iter->name, val); - } - } else if (member_iter->name == "use-nt-query-system-information") { - const rapidjson::Value& val = member_iter->value; - if (val.IsBool()) { - _use_nt_query_system_information = val.GetBool(); - } else { - SPDLOG_LOGGER_ERROR( - logger, - "command: {}, use-nt-query-system-information is not a boolean", - cmd_name); + } else if (member_iter->name != "cpu-detailed") { + SPDLOG_LOGGER_ERROR(logger, "command: {}, unknown parameter: {}", + cmd_name, member_iter->name); } - } else if (member_iter->name != "cpu-detailed") { - SPDLOG_LOGGER_ERROR(logger, "command: {}, unknown parameter: {}", - cmd_name, member_iter->name); } } + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR(_logger, "check_cpu fail to parse check params: {}", + e.what()); + throw; } + if (_use_nt_query_system_information) { _ntdll_init(); } else { diff --git a/agent/native_windows/src/check_drive_size.cc b/agent/native_windows/src/check_drive_size.cc new file mode 100644 index 00000000000..8b81b695a90 --- /dev/null +++ b/agent/native_windows/src/check_drive_size.cc @@ -0,0 +1,151 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include "drive_size.hh" + +namespace com::centreon::agent::check_drive_size_detail { + +static const absl::flat_hash_map + _sz_filesystem_map = {{"fat", e_drive_fs_type::hr_fs_fat}, + {"fat32", e_drive_fs_type::hr_fs_fat32}, + {"ntfs", e_drive_fs_type::hr_fs_ntfs}, + {"exfat", e_drive_fs_type::hr_fs_exfat}}; + +/** + * @brief Get the type of drive and type of filesystem + * + * @param fs_root like C:\ + * @param logger + * @return e_drive_fs_type + */ +static e_drive_fs_type get_fs_type( + const std::string& fs_root, + const std::shared_ptr& logger) { + // drive type + uint64_t fs_type = e_drive_fs_type::hr_unknown; + UINT drive_type = GetDriveTypeA(fs_root.c_str()); + switch (drive_type) { + case DRIVE_FIXED: + fs_type = e_drive_fs_type::hr_storage_fixed_disk; + break; + case DRIVE_REMOVABLE: + fs_type = e_drive_fs_type::hr_storage_removable_disk; + break; + case DRIVE_REMOTE: + fs_type = e_drive_fs_type::hr_storage_network_disk; + break; + case DRIVE_CDROM: + fs_type = e_drive_fs_type::hr_storage_compact_disc; + break; + case DRIVE_RAMDISK: + fs_type = e_drive_fs_type::hr_storage_ram_disk; + break; + default: + SPDLOG_LOGGER_ERROR(logger, "{} unknown drive type {}", fs_root, + drive_type); + break; + } + + // format type + char file_system_name[MAX_PATH]; // Tampon pour le nom du syst�me de + // fichiers + + BOOL result = + GetVolumeInformation(fs_root.c_str(), nullptr, 0, nullptr, nullptr, + nullptr, file_system_name, sizeof(file_system_name)); + + if (!result) { + SPDLOG_LOGGER_ERROR(logger, "{} unable to get file system type", fs_root); + } else { + std::string lower_fs_name = file_system_name; + absl::AsciiStrToLower(&lower_fs_name); + auto fs_search = _sz_filesystem_map.find(lower_fs_name); + if (fs_search != _sz_filesystem_map.end()) { + fs_type |= fs_search->second; + } else { + SPDLOG_LOGGER_ERROR(logger, "{} unknown file system type {}", fs_root, + file_system_name); + } + } + return static_cast(fs_type); +} + +/** + * @brief Get the used and total space of all drives allowed by filt + * + * @param filt fs filter (drive and fs type) + * @param logger + * @return std::list + */ +std::list os_fs_stats(filter& filt, + const std::shared_ptr& logger) { + DWORD drives = GetLogicalDrives(); + std::list result; + + std::string fs_to_test; + for (char letter = 'A'; letter <= 'Z'; ++letter) { + // test if drive bit is set + if (drives & (1 << (letter - 'A'))) { + fs_to_test.clear(); + fs_to_test.push_back(letter); + fs_to_test.push_back(':'); + fs_to_test.push_back('\\'); + + // first use cache of filter + if (filt.is_fs_yet_excluded(fs_to_test)) { + continue; + } + + if (!filt.is_fs_yet_allowed(fs_to_test)) { + // not in cache so test it + if (!filt.is_allowed(fs_to_test, "", get_fs_type(fs_to_test, logger))) { + SPDLOG_LOGGER_TRACE(logger, "{} refused by filter", fs_to_test); + continue; + } else { + SPDLOG_LOGGER_TRACE(logger, "{} allowed by filter", fs_to_test); + } + } + + ULARGE_INTEGER total_number_of_bytes; + ULARGE_INTEGER total_number_of_free_bytes; + + BOOL success = GetDiskFreeSpaceEx(fs_to_test.c_str(), nullptr, + &total_number_of_bytes, + &total_number_of_free_bytes); + + if (success) { + SPDLOG_LOGGER_TRACE(logger, "{} total: {}, free {}", fs_to_test, + total_number_of_bytes.QuadPart , + total_number_of_free_bytes.QuadPart); + + result.emplace_back(std::move(fs_to_test), + total_number_of_bytes.QuadPart - + total_number_of_free_bytes.QuadPart, + total_number_of_bytes.QuadPart); + + } else { + SPDLOG_LOGGER_ERROR(logger, "unable to get free space of {}", + fs_to_test); + } + } + } + + return result; +} + +} // namespace com::centreon::agent::check_drive_size_detail diff --git a/agent/native_windows/src/check_uptime.cc b/agent/native_windows/src/check_uptime.cc index e46aa1837b9..eb8066e4401 100644 --- a/agent/native_windows/src/check_uptime.cc +++ b/agent/native_windows/src/check_uptime.cc @@ -66,16 +66,22 @@ check_uptime::check_uptime(const std::shared_ptr& io_context, _second_warning_threshold(0), _second_critical_threshold(0) { com::centreon::common::rapidjson_helper arg(args); - if (args.IsObject()) { - _second_warning_threshold = arg.get_unsigned("warning-uptime", 0); - _second_critical_threshold = arg.get_unsigned("critical-uptime", 0); - std::string unit = arg.get_string("unit", "s"); - boost::to_lower(unit); - auto multiplier = _unit_multiplier.find(unit); - if (multiplier != _unit_multiplier.end()) { - _second_warning_threshold *= multiplier->second; - _second_critical_threshold *= multiplier->second; + try { + if (args.IsObject()) { + _second_warning_threshold = arg.get_unsigned("warning-uptime", 0); + _second_critical_threshold = arg.get_unsigned("critical-uptime", 0); + std::string unit = arg.get_string("unit", "s"); + boost::to_lower(unit); + auto multiplier = _unit_multiplier.find(unit); + if (multiplier != _unit_multiplier.end()) { + _second_warning_threshold *= multiplier->second; + _second_critical_threshold *= multiplier->second; + } } + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR(_logger, "check_uptime, fail to parse arguments: {}", + e.what()); + throw; } } diff --git a/agent/precomp_inc/precomp.hh b/agent/precomp_inc/precomp.hh index 095a128a609..df066d6fe5a 100644 --- a/agent/precomp_inc/precomp.hh +++ b/agent/precomp_inc/precomp.hh @@ -35,12 +35,15 @@ #include #include #include +#include #include #include #include #include #include +#include + #include namespace asio = boost::asio; diff --git a/agent/src/check.cc b/agent/src/check.cc index 9da93141e85..a150217d570 100644 --- a/agent/src/check.cc +++ b/agent/src/check.cc @@ -136,8 +136,10 @@ void check::on_completion( const std::list& perfdata, const std::list& outputs) { if (start_check_index == _running_check_index) { - SPDLOG_LOGGER_TRACE(_logger, "end check for service {} cmd: {}", _service, - _command_name); + SPDLOG_LOGGER_TRACE(_logger, + "end check for service {} cmd: {} status:{} output: {}", + _service, _command_name, status, + outputs.empty() ? "" : outputs.front()); _time_out_timer.cancel(); _running_check = false; ++_running_check_index; diff --git a/agent/src/config.cc b/agent/src/config.cc index 79e2c99fade..47098e0628c 100644 --- a/agent/src/config.cc +++ b/agent/src/config.cc @@ -17,7 +17,6 @@ */ #include -#include #include "com/centreon/common/rapidjson_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" diff --git a/agent/src/config_win.cc b/agent/src/config_win.cc index 424765e83e8..64d822b04dc 100644 --- a/agent/src/config_win.cc +++ b/agent/src/config_win.cc @@ -18,8 +18,6 @@ #include -#include - #include "com/centreon/exceptions/msg_fmt.hh" #include "config.hh" diff --git a/agent/src/drive_size.cc b/agent/src/drive_size.cc new file mode 100644 index 00000000000..4f7031e1c56 --- /dev/null +++ b/agent/src/drive_size.cc @@ -0,0 +1,585 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include "drive_size.hh" +#include "absl/synchronization/mutex.h" +#include "check.hh" +#include "com/centreon/common/perfdata.hh" +#include "com/centreon/common/rapidjson_helper.hh" +#include "com/centreon/exceptions/msg_fmt.hh" + +using namespace com::centreon::agent; + +static std::shared_ptr< + com::centreon::agent::check_drive_size_detail::drive_size_thread> + _worker; +static std::thread* _worker_thread = nullptr; + +namespace com::centreon::agent::check_drive_size_detail { + +/******************************************************************************** + * filter + *********************************************************************************/ + +/** + * @brief as filter parameter is a regex, we need to apply the regex on each + * line of this array + * + */ +constexpr std::array, 35> + _fs_type = { + std::make_pair("hrunknown", hr_unknown), + std::make_pair("hrstorageram", hr_storage_ram), + std::make_pair("hrstoragevirtualmemory", hr_storage_virtual_memory), + std::make_pair("hrstoragefixeddisk", hr_storage_fixed_disk), + std::make_pair("hrstorageremovabledisk", hr_storage_removable_disk), + std::make_pair("hrstoragefloppydisk", hr_storage_floppy_disk), + std::make_pair("hrstoragecompactdisc", hr_storage_compact_disc), + std::make_pair("hrstorageramdisk", hr_storage_ram_disk), + std::make_pair("hrstorageflashmemory", hr_storage_flash_memory), + std::make_pair("hrstoragenetworkdisk", hr_storage_network_disk), + std::make_pair("hrfsother", hr_fs_other), + std::make_pair("hrfsunknown", hr_fs_unknown), + std::make_pair("hrfsberkeleyffs", hr_fs_berkeley_ffs), + std::make_pair("hrfssys5fs", hr_fs_sys5_fs), + std::make_pair("hrfsfat", hr_fs_fat), + std::make_pair("hrfshpfs", hr_fs_hpfs), + std::make_pair("hrfshfs", hr_fs_hfs), + std::make_pair("hrfsmfs", hr_fs_mfs), + std::make_pair("hrfsntfs", hr_fs_ntfs), + std::make_pair("hrfsvnode", hr_fs_vnode), + std::make_pair("hrfsjournaled", hr_fs_journaled), + std::make_pair("hrfsiso9660", hr_fs_iso9660), + std::make_pair("hrfsrockridge", hr_fs_rock_ridge), + std::make_pair("hrfsnfs", hr_fs_nfs), + std::make_pair("hrfsnetware", hr_fs_netware), + std::make_pair("hrfsafs", hr_fs_afs), + std::make_pair("hrfsdfs", hr_fs_dfs), + std::make_pair("hrfsappleshare", hr_fs_appleshare), + std::make_pair("hrfsrfs", hr_fs_rfs), + std::make_pair("hrfsdgcfs", hr_fs_dgcfs), + std::make_pair("hrfsbfs", hr_fs_bfs), + std::make_pair("hrfsfat32", hr_fs_fat32), + std::make_pair("hrfslinuxext2", hr_fs_linux_ext2), + std::make_pair("hrfslinuxext4", hr_fs_linux_ext4), + std::make_pair("hrfsexfat", hr_fs_exfat)}; + +/** + * @brief Construct a new filter::filter object + * + * + * @param args json array that can contain these keys: + * filter-storage-type or filter-type + * filter-fs + * filter-exclude-fs + * filter-mountpoint + * filter-exclude-mountpoint + */ +filter::filter(const rapidjson::Value& args) : _fs_type_filter(0xFFFFFFFFU) { + if (args.IsObject()) { + for (auto member_iter = args.MemberBegin(); member_iter != args.MemberEnd(); + ++member_iter) { + if (member_iter->name == "filter-storage-type" || + member_iter->name == "filter-type") { + if (member_iter->value.IsString() && + member_iter->value.GetStringLength() > 0) { + std::string sz_regexp(member_iter->value.GetString()); + boost::to_lower(sz_regexp); + re2::RE2 filter_typ_re(sz_regexp); + if (!filter_typ_re.ok()) { + throw exceptions::msg_fmt( + "invalid regex for filter-storage-type: {}", + member_iter->value.GetString()); + } + _fs_type_filter = 0; + for (const auto& [label, flag] : _fs_type) { + if (RE2::FullMatch(label, filter_typ_re)) { + _fs_type_filter |= flag; + } + } + } + } else if (member_iter->name == "filter-fs" && + member_iter->value.IsString() && + member_iter->value.GetStringLength() > 0) { + _filter_fs = std::make_unique(member_iter->value.GetString()); + if (!_filter_fs->ok()) { + throw exceptions::msg_fmt("invalid regex for filter-fs: {}", + member_iter->value.GetString()); + } + } else if (member_iter->name == "exclude-fs" && + member_iter->value.IsString() && + member_iter->value.GetStringLength() > 0) { + _filter_exclude_fs = + std::make_unique(member_iter->value.GetString()); + if (!_filter_exclude_fs->ok()) { // NOLINT + throw exceptions::msg_fmt("invalid regex for filter-exclude-fs: {}", + member_iter->value.GetString()); + } + } else if (member_iter->name == "filter-mountpoint" && + member_iter->value.IsString() && + member_iter->value.GetStringLength() > 0) { + _filter_mountpoint = + std::make_unique(member_iter->value.GetString()); + if (!_filter_mountpoint->ok()) { + throw exceptions::msg_fmt("invalid regex for filter-mountpoint: {}", + member_iter->value.GetString()); + } + } else if (member_iter->name == "exclude-mountpoint" && + member_iter->value.IsString() && + member_iter->value.GetStringLength() > 0) { + _filter_exclude_mountpoint = + std::make_unique(member_iter->value.GetString()); + if (!_filter_exclude_mountpoint->ok()) { + throw exceptions::msg_fmt( + "invalid regex for filter-exclude-mountpoint: {}", + member_iter->value.GetString()); + } + } + } + } +} + +/** + * @brief test if fs has yet been tested and yet allowed + * + * @param fs + * @return true tested and allowed + * @return false not tested + */ +bool filter::is_fs_yet_allowed(const std::string_view& fs) const { + absl::MutexLock l(&_protect); + return _cache_allowed_fs.find(fs) != _cache_allowed_fs.end(); +} + +/** + * @brief test if fs has yet been tested and yet excluded + * + * @param fs + * @return true tested and excluded + * @return false not tested + */ +bool filter::is_fs_yet_excluded(const std::string_view& fs) const { + absl::MutexLock l(&_protect); + return _cache_excluded_fs.find(fs) != _cache_excluded_fs.end(); +} + +/** + * @brief test a fs + * + * @param fs + * @param mount_point (linux only) + * @param fs_type e_drive_fs_type mask + * @return true allowed by filter + * @return false + */ +bool filter::is_allowed(const std::string_view& fs, + const std::string_view& mount_point, + e_drive_fs_type fs_type) { + if (!(_fs_type_filter & fs_type)) { + return false; + } + + absl::MutexLock l(&_protect); + + bool yet_allowed = _cache_allowed_fs.find(fs) != _cache_allowed_fs.end(); + bool yet_excluded = _cache_excluded_fs.find(fs) != _cache_excluded_fs.end(); + if (yet_excluded) { + return false; + } + + if (!yet_allowed) { + if (_filter_exclude_fs && RE2::FullMatch(fs, *_filter_exclude_fs)) { + _cache_excluded_fs.emplace(fs); + return false; + } + + if (_filter_fs) { + if (RE2::FullMatch(fs, *_filter_fs)) { + _cache_allowed_fs.emplace(fs); + } else { + _cache_excluded_fs.emplace(fs); + return false; + } + } else { + _cache_allowed_fs.emplace(fs); + } + } + + yet_allowed = _cache_allowed_mountpoint.find(mount_point) != + _cache_allowed_mountpoint.end(); + yet_excluded = _cache_excluded_mountpoint.find(mount_point) != + _cache_excluded_mountpoint.end(); + if (yet_excluded) { + return false; + } + + if (!yet_allowed) { + if (_filter_exclude_mountpoint && + RE2::FullMatch(mount_point, *_filter_exclude_mountpoint)) { + _cache_excluded_mountpoint.emplace(mount_point); + return false; + } + + if (_filter_mountpoint) { + if (RE2::FullMatch(mount_point, *_filter_mountpoint)) { + _cache_allowed_mountpoint.emplace(mount_point); + } else { + _cache_excluded_mountpoint.emplace(mount_point); + return false; + } + } else { + _cache_allowed_mountpoint.emplace(mount_point); + } + } + + return true; +} + +/******************************************************************************** + * drive_size_thread + *********************************************************************************/ + +drive_size_thread::get_fs_stats drive_size_thread::os_fs_stats; + +/** + * @brief function run in a separate thread started by + * check_drive_size::start_check + * + */ +void drive_size_thread::run() { + auto keep_object_alive = shared_from_this(); + while (_active) { + absl::MutexLock l(&_queue_m); + _queue_m.Await(absl::Condition(this, &drive_size_thread::has_to_stop_wait)); + if (!_active) { + return; + } + time_point now = std::chrono::system_clock::now(); + while (!_queue.empty()) { + if (_queue.begin()->timeout < now) { + _queue.pop_front(); + } else { + break; + } + } + + if (!_queue.empty()) { + auto to_execute = _queue.begin(); + std::list stats = + os_fs_stats(*to_execute->request_filter, _logger); + // main code of this program is not thread safe, so we use io_context + // launched from main thread to call callback + _io_context->post( + [result = std::move(stats), + completion_handler = std::move(to_execute->handler)]() { + completion_handler(result); + }); + _queue.erase(to_execute); + } + } +} + +/** + * @brief wake up thread and tell him it's time to die + * + */ +void drive_size_thread::kill() { + absl::MutexLock l(&_queue_m); + _active = false; +} + +/** + * @brief start an asynchronous check + * + * @tparam handler_type + * @param request_filter + * @param timeout + * @param handler + */ +template +void drive_size_thread::async_get_fs_stats( + const std::shared_ptr& request_filter, + const time_point& timeout, + handler_type&& handler) { + absl::MutexLock lck(&_queue_m); + _queue.push_back( + {request_filter, std::forward(handler), timeout}); +} + +} // namespace com::centreon::agent::check_drive_size_detail + +/******************************************************************************** + * check_drive_size + *********************************************************************************/ + +check_drive_size::check_drive_size( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + time_point first_start_expected, + duration check_interval, + const std::string& serv, + const std::string& cmd_name, + const std::string& cmd_line, + const rapidjson::Value& args, + const engine_to_agent_request_ptr& cnf, + check::completion_handler&& handler) + : check(io_context, + logger, + first_start_expected, + check_interval, + serv, + cmd_name, + cmd_line, + cnf, + std::move(handler)), + _filter(std::make_shared(args)), + _prct_threshold(false), + _free_threshold(false), + _warning(0), + _critical(0), + _fs_test(&check_drive_size::_no_test) { + using namespace std::literals; + try { + if (args.IsObject()) { + common::rapidjson_helper helper(args); + + if (args.HasMember("unit")) { + _prct_threshold = helper.get_string("unit", "%") == "%"sv; + } else { + _prct_threshold = helper.get_string("units", "%") == "%"sv; + } + _free_threshold = helper.get_bool("free", false); + + _warning = helper.get_uint64_t("warning", 0); + _critical = helper.get_uint64_t("critical", 0); + if (_prct_threshold) { + if (_warning || _critical) { + _warning *= 100; + _critical *= 100; + _fs_test = _free_threshold ? &check_drive_size::_prct_free_test + : &check_drive_size::_prct_used_test; + } + } else { + if (_warning || _critical) { + _fs_test = _free_threshold ? &check_drive_size::_free_test + : &check_drive_size::_used_test; + } + } + } + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR( + _logger, "check_drive_size fail to parse check params: {}", e.what()); + throw; + } +} + +/** + * @brief used in case of no threshold + * + * @param fs + * @return e_status + */ +e_status check_drive_size::_no_test( + [[maybe_unused]] const check_drive_size_detail::fs_stat& fs) const { + return e_status::ok; +} + +/** + * @brief test used fs with fixed thresholds + * + * @param fs + * @return e_status + */ +e_status check_drive_size::_used_test( + const check_drive_size_detail::fs_stat& fs) const { + if (_critical && fs.is_used_more_than_threshold(_critical)) { + return e_status::critical; + } + if (_warning && fs.is_used_more_than_threshold(_warning)) { + return e_status::warning; + } + return e_status::ok; +} + +/** + * @brief test used fs with percent thresholds + * + * @param fs + * @return e_status + */ +e_status check_drive_size::_prct_used_test( + const check_drive_size_detail::fs_stat& fs) const { + if (_critical && fs.is_used_more_than_prct_threshold(_critical)) { + return e_status::critical; + } + if (_warning && fs.is_used_more_than_prct_threshold(_warning)) { + return e_status::warning; + } + return e_status::ok; +} + +/** + * @brief test free fs with fixed thresholds + * + * @param fs + * @return e_status + */ +e_status check_drive_size::_free_test( + const check_drive_size_detail::fs_stat& fs) const { + if (_critical && fs.is_free_less_than_threshold(_critical)) { + return e_status::critical; + } + if (_warning && fs.is_free_less_than_threshold(_warning)) { + return e_status::warning; + } + return e_status::ok; +} + +/** + * @brief test free fs with percent thresholds + * + * @param fs + * @return e_status + */ +e_status check_drive_size::_prct_free_test( + const check_drive_size_detail::fs_stat& fs) const { + if (_critical && fs.is_free_less_than_prct_threshold(_critical)) { + return e_status::critical; + } + if (_warning && fs.is_free_less_than_prct_threshold(_warning)) { + return e_status::warning; + } + return e_status::ok; +} + +/** + * @brief start a check + * start _worker thread if not yet done and pass query to it + * + * @param timeout + */ +void check_drive_size::start_check(const duration& timeout) { + if (!check::_start_check(timeout)) { + return; + } + + if (!_worker_thread) { + _worker = std::make_shared( + _io_context, _logger); + _worker_thread = new std::thread([worker = _worker] { worker->run(); }); + } + + unsigned running_check_index = _get_running_check_index(); + + _worker->async_get_fs_stats( + _filter, std::chrono::system_clock::now() + timeout, + [me = shared_from_this(), running_check_index]( + const std::list& result) { + me->_completion_handler(running_check_index, result); + }); +} + +/** + * @brief called by _worker once work is done + * As it is not thread safe, _worker use io_context to post result + * + * @param start_check_index + * @param result + */ +void check_drive_size::_completion_handler( + unsigned start_check_index, + const std::list& result) { + e_status status = e_status::ok; + + std::string output; + std::list perfs; + + for (const auto& fs : result) { + e_status fs_status = (this->*_fs_test)(fs); + if (fs_status > status) { + status = fs_status; + } + if (fs_status != e_status::ok) { + if (!output.empty()) { + output.push_back(' '); + } + output += fs_status == e_status::critical ? "CRITICAL: " : "WARNING: "; + if (_prct_threshold) { + output += fmt::format("{} Total: {}G Used: {:.2f}% Free: {:.2f}%", + fs.mount_point, fs.total / 1024 / 1024 / 1024, + fs.get_used_prct(), fs.get_free_prct()); + } else { + output += fmt::format("{} Total: {}G Used: {}G Free: {}G", + fs.mount_point, fs.total / 1024 / 1024 / 1024, + fs.used / 1024 / 1024 / 1024, + (fs.total - fs.used) / 1024 / 1024 / 1024); + } + } + + centreon::common::perfdata& perf = perfs.emplace_back(); + perf.name((_free_threshold ? "free_" : "used_") + fs.mount_point); + + if (_prct_threshold) { + perf.unit("%"); + perf.min(0); + perf.max(100.0); + if (_warning) { + perf.warning_low(0); + perf.warning(static_cast(_warning) / 100); + } + if (_critical) { + perf.critical_low(0); + perf.critical(static_cast(_critical) / 100); + } + perf.value(_free_threshold ? fs.get_free_prct() : fs.get_used_prct()); + } else { + perf.unit("B"); + perf.min(0); + perf.max(fs.total); + if (_warning) { + perf.warning_low(0); + perf.warning(_warning); + } + if (_critical) { + perf.critical_low(0); + perf.critical(_critical); + } + perf.value(_free_threshold ? (fs.total - fs.used) : fs.used); + } + } + if (output.empty()) { + using namespace std::literals; + output = perfs.empty() ? "No storage found (filters issue)"sv + : "OK: All storages are ok"sv; + } + + on_completion(start_check_index, status, perfs, {output}); +} + +/** + * @brief stop _worker + * + */ +void check_drive_size::thread_kill() { + if (_worker_thread) { + _worker->kill(); + _worker_thread->join(); + delete _worker_thread; + _worker_thread = nullptr; + } +} \ No newline at end of file diff --git a/agent/src/main.cc b/agent/src/main.cc index 1091832d011..b6f9ea65a58 100644 --- a/agent/src/main.cc +++ b/agent/src/main.cc @@ -21,6 +21,7 @@ #include #include "config.hh" +#include "drive_size.hh" #include "streaming_client.hh" #include "streaming_server.hh" @@ -196,6 +197,9 @@ int main(int argc, char* argv[]) { return -1; } + // kill check_drive_size thread if used + check_drive_size::thread_kill(); + SPDLOG_LOGGER_INFO(g_logger, "centreon-monitoring-agent end"); return 0; diff --git a/agent/src/main_win.cc b/agent/src/main_win.cc index 1609a879ab9..fa9b35eac24 100644 --- a/agent/src/main_win.cc +++ b/agent/src/main_win.cc @@ -23,9 +23,17 @@ #include #include "config.hh" +#include "drive_size.hh" #include "streaming_client.hh" #include "streaming_server.hh" +namespace com::centreon::agent::check_drive_size_detail { + +std::list os_fs_stats(filter& filter, + const std::shared_ptr& logger); + +} + using namespace com::centreon::agent; #define SERVICE_NAME "CentreonMonitoringAgent" @@ -117,6 +125,10 @@ int _main(bool service_start) { return 1; } + // init os specific drive_size getter + check_drive_size_detail::drive_size_thread::os_fs_stats = + check_drive_size_detail::os_fs_stats; + if (service_start) SPDLOG_INFO("centreon-monitoring-agent service start"); else @@ -199,6 +211,9 @@ int _main(bool service_start) { return -1; } + // kill check_drive_size thread if used + check_drive_size::thread_kill(); + SPDLOG_LOGGER_INFO(g_logger, "centreon-monitoring-agent end"); return 0; diff --git a/agent/src/scheduler.cc b/agent/src/scheduler.cc index 287e28528fd..6111cdd359e 100644 --- a/agent/src/scheduler.cc +++ b/agent/src/scheduler.cc @@ -24,6 +24,7 @@ #include "check_exec.hh" #include "com/centreon/common/rapidjson_helper.hh" #include "com/centreon/common/utf8.hh" +#include "drive_size.hh" using namespace com::centreon::agent; @@ -565,6 +566,10 @@ std::shared_ptr scheduler::default_check_builder( return std::make_shared( io_context, logger, first_start_expected, check_interval, service, cmd_name, cmd_line, *args, conf, std::move(handler)); + } else if (check_type == "storage"sv) { + return std::make_shared( + io_context, logger, first_start_expected, check_interval, service, + cmd_name, cmd_line, *args, conf, std::move(handler)); #endif } else { throw exceptions::msg_fmt("command {}, unknown native check:{}", cmd_name, diff --git a/agent/test/CMakeLists.txt b/agent/test/CMakeLists.txt index 94b89f859d8..0bdeed186fc 100644 --- a/agent/test/CMakeLists.txt +++ b/agent/test/CMakeLists.txt @@ -19,6 +19,7 @@ set( SRC_COMMON check_test.cc check_exec_test.cc + drive_size_test.cc scheduler_test.cc test_main.cc ) diff --git a/agent/test/check_linux_cpu_test.cc b/agent/test/check_linux_cpu_test.cc index 77378d5eae7..81baf20aebc 100644 --- a/agent/test/check_linux_cpu_test.cc +++ b/agent/test/check_linux_cpu_test.cc @@ -123,6 +123,8 @@ procs_blocked 0 softirq 166407220 66442046 14763247 1577070 4447556 33 0 18081353 30219191 75659 30801065 )"; +using namespace std::string_literals; + TEST(proc_stat_file_test, no_threshold) { constexpr const char* test_file_path = "/tmp/proc_stat_test"; { @@ -149,11 +151,13 @@ TEST(proc_stat_file_test, no_threshold) { rapidjson::Document check_args; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, - [](const std::shared_ptr& caller, int status, - const std::list& perfdata, - const std::list& outputs) {}); + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + []([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) {}); e_status status = checker.compute(first_measure, second_measure, &output, &perfs); @@ -224,11 +228,13 @@ TEST(proc_stat_file_test, no_threshold_detailed) { rapidjson::Document check_args = R"({"cpu-detailed":true})"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, - [](const std::shared_ptr& caller, int status, - const std::list& perfdata, - const std::list& outputs) {}); + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + []([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) {}); e_status status = checker.compute(first_measure, second_measure, &output, &perfs); @@ -354,11 +360,13 @@ TEST(proc_stat_file_test, threshold_nodetailed) { R"({"warning-core" : "24.1", "critical-core" : "24.4", "warning-average" : "10", "critical-average" : "20"})"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, - [](const std::shared_ptr& caller, int status, - const std::list& perfdata, - const std::list& outputs) {}); + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + []([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) {}); e_status status = checker.compute(first_measure, second_measure, &output, &perfs); @@ -433,11 +441,13 @@ TEST(proc_stat_file_test, threshold_nodetailed2) { R"({"warning-core-iowait" : "0.36", "critical-core-iowait" : "0.39", "warning-average-iowait" : "0.3", "critical-average-iowait" : "0.4"})"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, - [](const std::shared_ptr& caller, int status, - const std::list& perfdata, - const std::list& outputs) {}); + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + []([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) {}); e_status status = checker.compute(first_measure, second_measure, &output, &perfs); @@ -490,11 +500,13 @@ TEST(proc_stat_file_test, threshold_detailed) { R"({"cpu-detailed":true, "warning-core" : "24.1", "critical-core" : "24.4", "warning-average" : "10", "critical-average" : "20"})"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, - [](const std::shared_ptr& caller, int status, - const std::list& perfdata, - const std::list& outputs) {}); + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + []([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) {}); e_status status = checker.compute(first_measure, second_measure, &output, &perfs); @@ -562,11 +574,13 @@ TEST(proc_stat_file_test, threshold_detailed2) { R"({"cpu-detailed":"true", "warning-core-iowait" : "0.36", "critical-core-iowait" : "0.39", "warning-average-iowait" : "0.3", "critical-average-iowait" : "0.4"})"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, - [](const std::shared_ptr& caller, int status, - const std::list& perfdata, - const std::list& outputs) {}); + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + []([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) {}); e_status status = checker.compute(first_measure, second_measure, &output, &perfs); diff --git a/agent/test/check_uptime_test.cc b/agent/test/check_uptime_test.cc index 092541bb7c6..1fe8d9f5bd6 100644 --- a/agent/test/check_uptime_test.cc +++ b/agent/test/check_uptime_test.cc @@ -25,6 +25,7 @@ extern std::shared_ptr g_io_context; using namespace com::centreon::agent; +using namespace std::string_literals; TEST(native_check_uptime, ok) { using namespace com::centreon::common::literals; @@ -32,8 +33,8 @@ TEST(native_check_uptime, ok) { R"({ "warning-uptime" : "345600", "critical-uptime" : "172800"})"_json; check_uptime checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -62,8 +63,8 @@ TEST(native_check_uptime, ok_m) { R"({ "warning-uptime" : "5760", "critical-uptime" : "2880", "unit": "m"})"_json; check_uptime checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -92,8 +93,8 @@ TEST(native_check_uptime, ok_h) { R"({ "warning-uptime" : "96", "critical-uptime" : "48", "unit": "h"})"_json; check_uptime checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -122,8 +123,8 @@ TEST(native_check_uptime, ok_d) { R"({ "warning-uptime" : "4", "critical-uptime" : "2", "unit": "d"})"_json; check_uptime checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -152,8 +153,8 @@ TEST(native_check_uptime, ok_w) { R"({ "warning-uptime" : "2", "critical-uptime" : "1", "unit": "w"})"_json; check_uptime checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -182,8 +183,8 @@ TEST(native_check_uptime, warning) { R"({ "warning-uptime" : "4", "critical-uptime" : "2", "unit": "d"})"_json; check_uptime checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -212,8 +213,8 @@ TEST(native_check_uptime, critical) { R"({ "warning-uptime" : "4", "critical-uptime" : "2", "unit": "d"})"_json; check_uptime checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& diff --git a/agent/test/check_windows_cpu_test.cc b/agent/test/check_windows_cpu_test.cc index 22000b48815..1856aa4e42a 100644 --- a/agent/test/check_windows_cpu_test.cc +++ b/agent/test/check_windows_cpu_test.cc @@ -25,6 +25,7 @@ extern std::shared_ptr g_io_context; using namespace com::centreon::agent; +using namespace std::string_literals; TEST(native_check_cpu_windows, construct) { check_cpu_detail::M_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION info; @@ -76,8 +77,8 @@ TEST(native_check_cpu_windows, output_no_threshold) { rapidjson::Document check_args; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -137,8 +138,8 @@ TEST(native_check_cpu_windows, output_no_threshold_detailed) { rapidjson::Document check_args = R"({"cpu-detailed":true})"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -232,8 +233,8 @@ TEST(native_check_cpu_windows, output_threshold) { R"({"warning-core" : "39", "critical-core" : "59", "warning-average" : "49", "critical-average" : "60"})"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -305,8 +306,8 @@ TEST(native_check_cpu_windows, output_threshold_detailed) { R"({"cpu-detailed":true, "warning-core" : "39", "critical-core" : "59", "warning-average" : "49", "critical-average" : "60", "warning-core-user": "30", "critical-core-user": "40", "warning-average-user": "31", "critical-average-user": "41" })"_json; check_cpu checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -455,8 +456,8 @@ TEST(native_check_cpu_windows, compare_kernel_dph) { R"({"use-nt-query-system-information":true })"_json; check_cpu nt_checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", nt_check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, nt_check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& @@ -467,8 +468,8 @@ TEST(native_check_cpu_windows, compare_kernel_dph) { R"({"use-nt-query-system-information":false })"_json; check_cpu pdh_checker( - g_io_context, spdlog::default_logger(), {}, {}, "serv", "cmd_name", - "cmd_line", pdh_check_args, nullptr, + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, pdh_check_args, nullptr, []([[maybe_unused]] const std::shared_ptr& caller, [[maybe_unused]] int status, [[maybe_unused]] const std::list& diff --git a/agent/test/drive_size_test.cc b/agent/test/drive_size_test.cc new file mode 100644 index 00000000000..4b18fe59bbc --- /dev/null +++ b/agent/test/drive_size_test.cc @@ -0,0 +1,601 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include +#include +#include +#include +#include +#include + +#include "com/centreon/common/rapidjson_helper.hh" + +#include "drive_size.hh" + +extern std::shared_ptr g_io_context; + +using namespace com::centreon::agent; +using namespace com::centreon::agent::check_drive_size_detail; + +struct sample { + std::string_view fs; + std::string_view mount_point; + uint64_t fs_type; + uint64_t used; + uint64_t total; +}; + +std::array _samples = { + {{"udev", "/dev", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_other, + 0, 6024132000}, + + {"tmpfs", "/run", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_other, + 16760000, 1212868000}, + {"/dev/sda12", "/", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_linux_ext4, + 136830444000, 346066920000}, + {"tmpfs", "/dev/shm", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_other, + 0, 6072708000}, + {"tmpfs", "/run/lock", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_other, + 4000, 5116000}, + {"tmpfs", "/sys/fs/cgroup", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_other, + 0, 6072708000}, + {"/dev/sda11", "/boot/efi", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_fat, + 24000, 524248000}, + {"/dev/sda5", "/data", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_fat32, + 3072708000, 6072708000}, + {"tmpfs", "/run/user/1001", + check_drive_size_detail::e_drive_fs_type::hr_storage_fixed_disk | + check_drive_size_detail::e_drive_fs_type::hr_fs_other, + 100000, 1214440000}}}; + +class drive_size_test : public ::testing::Test { + public: + static std::list compute( + filter& filt, + const std::shared_ptr& logger); + + static void SetUpTestCase() { drive_size_thread::os_fs_stats = compute; } + static void TearDownTestCase() { check_drive_size::thread_kill(); } +}; + +std::list drive_size_test::compute( + filter& filt, + const std::shared_ptr&) { + std::list result; + for (const auto& s : _samples) { + if (filt.is_allowed(s.fs, s.mount_point, + static_cast(s.fs_type))) { + result.emplace_back(s.fs, s.mount_point, s.used, s.total); + } + } + return result; +} + +using namespace std::string_literals; + +TEST_F(drive_size_test, test_fs_filter1) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "1000000", "critical" : "20000000", "unit": "b", + "filter-type": "^hrfsother$"})"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, "WARNING: /run Total: 1G Used: 0G Free: 1G"); + ASSERT_EQ(perfs.size(), 6); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "B"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 1000000); + ASSERT_EQ(p.critical(), 20000000); + if (p.name() == "used_/dev") { + ASSERT_EQ(p.value(), 0); + ASSERT_EQ(p.max(), 6024132000); + } else if (p.name() == "used_/run") { + ASSERT_EQ(p.value(), 16760000); + ASSERT_EQ(p.max(), 1212868000); + } else if (p.name() == "used_/dev/shm") { + ASSERT_EQ(p.value(), 0); + ASSERT_EQ(p.max(), 6072708000); + } else if (p.name() == "used_/run/lock") { + ASSERT_EQ(p.value(), 4000); + ASSERT_EQ(p.max(), 5116000); + } else if (p.name() == "used_/sys/fs/cgroup") { + ASSERT_EQ(p.value(), 0); + ASSERT_EQ(p.max(), 6072708000); + } else if (p.name() == "used_/run/user/1001") { + ASSERT_EQ(p.value(), 100000); + ASSERT_EQ(p.max(), 1214440000); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } +} + +TEST_F(drive_size_test, test_fs_filter_percent) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "1", "critical" : "5", "unit": "%", + "filter-type": "^hrfsother$"})"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, "WARNING: /run Total: 1G Used: 1.38% Free: 98.62%"); + ASSERT_EQ(perfs.size(), 6); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "%"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 1); + ASSERT_EQ(p.critical(), 5); + if (p.name() == "used_/dev") { + ASSERT_EQ(p.value(), 0); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run") { + ASSERT_NEAR(p.value(), 1.38, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/dev/shm") { + ASSERT_NEAR(p.value(), 0, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run/lock") { + ASSERT_NEAR(p.value(), 0.08, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/sys/fs/cgroup") { + ASSERT_EQ(p.value(), 0); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run/user/1001") { + ASSERT_NEAR(p.value(), 0, 0.01); + ASSERT_EQ(p.max(), 100); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } +} + +TEST_F(drive_size_test, test_fs_filter2) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "1000000", "critical" : "20000000", "unit": "b", + "filter-type": "^(hrfsfat$|hrfsfat32)$"})"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, "CRITICAL: /data Total: 5G Used: 2G Free: 2G"); + ASSERT_EQ(perfs.size(), 2); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "B"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 1000000); + ASSERT_EQ(p.critical(), 20000000); + if (p.name() == "used_/boot/efi") { + ASSERT_EQ(p.value(), 24000); + ASSERT_EQ(p.max(), 524248000); + } else if (p.name() == "used_/data") { + ASSERT_EQ(p.value(), 3072708000); + ASSERT_EQ(p.max(), 6072708000); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } +} + +TEST_F(drive_size_test, test_fs_filter_percent_2) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "1", "critical" : "5", "unit": "%", + "filter-type": "^hrfsother$", "filter-fs": "^tmp.*$"})"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, "WARNING: /run Total: 1G Used: 1.38% Free: 98.62%"); + ASSERT_EQ(perfs.size(), 5); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "%"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 1); + ASSERT_EQ(p.critical(), 5); + if (p.name() == "used_/run") { + ASSERT_NEAR(p.value(), 1.38, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/dev/shm") { + ASSERT_NEAR(p.value(), 0, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run/lock") { + ASSERT_NEAR(p.value(), 0.08, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/sys/fs/cgroup") { + ASSERT_EQ(p.value(), 0); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run/user/1001") { + ASSERT_NEAR(p.value(), 0, 0.01); + ASSERT_EQ(p.max(), 100); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } +} + +TEST_F(drive_size_test, test_fs_filter_percent_3) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "1", "critical" : "5", "unit": "%", + "filter-type": "^hrfsother$", "filter-fs": "tmpfs", "filter-mountpoint":"^/run/.*$" })"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, "OK: All storages are ok"); + ASSERT_EQ(perfs.size(), 2); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "%"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 1); + ASSERT_EQ(p.critical(), 5); + if (p.name() == "used_/run") { + ASSERT_NEAR(p.value(), 1.38, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run/lock") { + ASSERT_NEAR(p.value(), 0.08, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run/user/1001") { + ASSERT_NEAR(p.value(), 0, 0.01); + ASSERT_EQ(p.max(), 100); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } +} + +TEST_F(drive_size_test, test_fs_filter_percent_4) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "1", "critical" : "5", "unit": "%", + "filter-type": "^hrfsother$", "filter-fs": "tmpfs", "filter-mountpoint":"^/run.*$", "exclude-mountpoint": ".*lock.*" })"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + { + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, "WARNING: /run Total: 1G Used: 1.38% Free: 98.62%"); + ASSERT_EQ(perfs.size(), 2); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "%"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 1); + ASSERT_EQ(p.critical(), 5); + if (p.name() == "used_/run") { + ASSERT_NEAR(p.value(), 1.38, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/run/user/1001") { + ASSERT_NEAR(p.value(), 0, 0.01); + ASSERT_EQ(p.max(), 100); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } + } + // recheck to validate filter cache + std::string output_save = output; + std::list perfs_save = perfs; + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, output_save); + ASSERT_EQ(perfs, perfs_save); +} + +TEST_F(drive_size_test, test_fs_filter_percent_5) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "30", "critical" : "50", "unit": "%", + "exclude-fs": "tmpfs", "exclude-mountpoint":"/dev" })"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, + "WARNING: / Total: 322G Used: 39.54% Free: 60.46% CRITICAL: /data " + "Total: 5G Used: 50.60% Free: 49.40%"); + ASSERT_EQ(perfs.size(), 3); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "%"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 30); + ASSERT_EQ(p.critical(), 50); + if (p.name() == "used_/") { + ASSERT_NEAR(p.value(), 39.54, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/data") { + ASSERT_NEAR(p.value(), 50.60, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "used_/boot/efi") { + ASSERT_NEAR(p.value(), 0.0045, 0.0001); + ASSERT_EQ(p.max(), 100); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } +} + +TEST_F(drive_size_test, test_fs_filter_free_percent) { + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "warning" : "70", "critical" : "50", "unit": "%", "free": true, + "exclude-fs": "tmpfs", "exclude-mountpoint":"/dev" })"_json; + + absl::Mutex wait_m; + std::list perfs; + std::string output; + + auto is_complete = [&]() { return !perfs.empty(); }; + + auto debug_logger = spdlog::default_logger(); + + auto checker = std::make_shared( + g_io_context, spdlog::default_logger(), std::chrono::system_clock::now(), + std::chrono::seconds(1), "serv"s, "cmd_name"s, "cmd_line"s, check_args, + nullptr, + [&]([[maybe_unused]] const std::shared_ptr& caller, + [[maybe_unused]] int status, + [[maybe_unused]] const std::list& + perfdata, + [[maybe_unused]] const std::list& outputs) { + absl::MutexLock lck(&wait_m); + perfs = perfdata; + output = outputs.front(); + }); + + checker->start_check(std::chrono::seconds(1)); + + absl::MutexLock lck(&wait_m); + wait_m.Await(absl::Condition(&is_complete)); + + ASSERT_EQ(output, + "WARNING: / Total: 322G Used: 39.54% Free: 60.46% CRITICAL: /data " + "Total: 5G Used: 50.60% Free: 49.40%"); + ASSERT_EQ(perfs.size(), 3); + + for (const auto& p : perfs) { + ASSERT_EQ(p.unit(), "%"); + ASSERT_EQ(p.min(), 0); + ASSERT_EQ(p.warning_low(), 0); + ASSERT_EQ(p.critical_low(), 0); + ASSERT_EQ(p.warning(), 70); + ASSERT_EQ(p.critical(), 50); + if (p.name() == "free_/") { + ASSERT_NEAR(p.value(), 60.46, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "free_/data") { + ASSERT_NEAR(p.value(), 49.40, 0.01); + ASSERT_EQ(p.max(), 100); + } else if (p.name() == "free_/boot/efi") { + ASSERT_NEAR(p.value(), 99.99, 0.01); + ASSERT_EQ(p.max(), 100); + } else { + FAIL() << "Unexpected perfdata name: " << p.name(); + } + } +} diff --git a/broker/CMakeLists.txt b/broker/CMakeLists.txt index a17b6afbf17..216a3b692ae 100644 --- a/broker/CMakeLists.txt +++ b/broker/CMakeLists.txt @@ -441,7 +441,7 @@ set(LIBROKER_SOURCES add_library(rokerbase STATIC ${LIBROKER_SOURCES}) set_target_properties(rokerbase PROPERTIES COMPILE_FLAGS "-fPIC") target_precompile_headers(rokerbase REUSE_FROM multiplexing) -add_dependencies(rokerbase berpc target_bbdo target_extcmd) +add_dependencies(rokerbase berpc target_bbdo target_extcmd pb_neb_lib) target_link_libraries( rokerbase sql diff --git a/common/inc/com/centreon/common/perfdata.hh b/common/inc/com/centreon/common/perfdata.hh index 8ad6e3905a7..d60f3dc2588 100644 --- a/common/inc/com/centreon/common/perfdata.hh +++ b/common/inc/com/centreon/common/perfdata.hh @@ -76,11 +76,11 @@ class perfdata { void warning_mode(bool val) { _warning_mode = val; } }; -} // namespace com::centreon::common - bool operator==(com::centreon::common::perfdata const& left, com::centreon::common::perfdata const& right); bool operator!=(com::centreon::common::perfdata const& left, com::centreon::common::perfdata const& right); +} // namespace com::centreon::common + #endif diff --git a/common/inc/com/centreon/common/rapidjson_helper.hh b/common/inc/com/centreon/common/rapidjson_helper.hh index 5cecd1ba14d..efd3bb82b7f 100644 --- a/common/inc/com/centreon/common/rapidjson_helper.hh +++ b/common/inc/com/centreon/common/rapidjson_helper.hh @@ -263,6 +263,7 @@ class rapidjson_helper { float get_float(const char* field_name) const; uint64_t get_uint64_t(const char* field_name) const; + uint64_t get_uint64_t(const char* field_name, uint64_t default_value) const; int64_t get_int64_t(const char* field_name) const; uint32_t get_uint32_t(const char* field_name) const; diff --git a/common/src/perfdata.cc b/common/src/perfdata.cc index 0d6f5b89af3..73b5306c742 100644 --- a/common/src/perfdata.cc +++ b/common/src/perfdata.cc @@ -54,6 +54,7 @@ static inline bool float_equal(float a, float b) { fabs(a - b) <= 0.01 * fabs(a)); } +namespace com::centreon::common { /** * Compare two perfdata objects. * @@ -87,6 +88,9 @@ bool operator==(perfdata const& left, perfdata const& right) { bool operator!=(perfdata const& left, perfdata const& right) { return !(left == right); } + +} // namespace com::centreon::common + /** * @brief in case of db insertions we need to ensure that name can be stored in * table With it, you can reduce name size diff --git a/common/src/rapidjson_helper.cc b/common/src/rapidjson_helper.cc index 2252cdc262f..7cd3d33f795 100644 --- a/common/src/rapidjson_helper.cc +++ b/common/src/rapidjson_helper.cc @@ -141,6 +141,23 @@ uint64_t rapidjson_helper::get_uint64_t(const char* field_name) const { &rapidjson::Value::GetUint64, &absl::SimpleAtoi); } +/** + * @brief read an uint64_t field + * + * @param field_name + * @param default_value value returned if member does not exist + * @return const char* field value + * @throw msg_fmt if field value is nor a integer nor a + * string containing a integer + */ +uint64_t rapidjson_helper::get_uint64_t(const char* field_name, + uint64_t default_value) const { + return get_or_default( + field_name, "uint64_t", + [](const rapidjson::Value& val) { return val.IsUint64(); }, + &rapidjson::Value::GetUint64, &absl::SimpleAtoi, default_value); +} + /** * @brief read a int64_t field * diff --git a/tests/broker-engine/cma.robot b/tests/broker-engine/cma.robot index f3f7294e632..879ab08736d 100644 --- a/tests/broker-engine/cma.robot +++ b/tests/broker-engine/cma.robot @@ -396,7 +396,7 @@ BEOTEL_CENTREON_AGENT_CHECK_NATIVE_CPU #a small threshold to make service_1 warning Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check2 - Ctn Engine Config Add Command ${0} otel_check2 {"check": "cpu_percentage", "args": {"warning-average" : "0.1"}} OTEL connector + Ctn Engine Config Add Command ${0} otel_check2 {"check": "cpu_percentage", "args": {"warning-average" : "0.01"}} OTEL connector Ctn Reload Engine ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 1 60 SOFT @@ -405,13 +405,153 @@ BEOTEL_CENTREON_AGENT_CHECK_NATIVE_CPU #a small threshold to make service_1 critical Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check3 - Ctn Engine Config Add Command ${0} otel_check3 {"check": "cpu_percentage", "args": {"critical-average" : "0.2", "warning-average" : "0.1"}} OTEL connector + Ctn Engine Config Add Command ${0} otel_check3 {"check": "cpu_percentage", "args": {"critical-average" : "0.02", "warning-average" : "0.01"}} OTEL connector Ctn Reload Engine ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 2 60 ANY Should Be True ${result} resources table not updated +BEOTEL_CENTREON_AGENT_CHECK_NATIVE_STORAGE + [Documentation] agent check service with native check storage and we expect to get it in check result + [Tags] broker engine opentelemetry MON-147936 + + ${run_env} Ctn Run Env + Pass Execution If "${run_env}" != "WSL" "This test is only for WSL" + + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"otel_server":{"host": "0.0.0.0","port": 4317},"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15}} + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check + Ctn Set Services Passive 0 service_1 + + Ctn Engine Config Add Command ${0} otel_check {"check": "storage", "args": { "free": true, "unit": "%"}} OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Clear Db metrics + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent + + Ctn Config BBDO3 1 + Ctn Clear Retention + + ${start} Ctn Get Round Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for the otel server start + ${content} Create List unencrypted server listening on 0.0.0.0:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "unencrypted server listening on 0.0.0.0:4317" should be available. + + ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 0 120 HARD + Should Be True ${result} resources table not updated + + ${expected_perfdata} Ctn Get Drive Statistics free_{}:\\ + ${result} Ctn Check Service Perfdata host_1 service_1 60 1 ${expected_perfdata} + Should be True ${result} data_bin not updated + + + #a small threshold to make service_1 warning + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check2 + + Ctn Engine Config Add Command ${0} otel_check2 {"check": "storage", "args": {"warning" : "10", "unit": "B"}} OTEL connector + + Ctn Reload Engine + ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 1 60 ANY + Should Be True ${result} resources table not updated + + #a small threshold to make service_1 critical + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check3 + + Ctn Engine Config Add Command ${0} otel_check3 {"check": "storage", "args": {"critical" : "10", "unit": "B"}} OTEL connector + + Ctn Reload Engine + ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 2 60 ANY + Should Be True ${result} resources table not updated + + + +BEOTEL_CENTREON_AGENT_CHECK_NATIVE_UPTIME + [Documentation] agent check service with native check uptime and we expect to get it in check result + [Tags] broker engine opentelemetry MON-147919 + + ${run_env} Ctn Run Env + Pass Execution If "${run_env}" != "WSL" "This test is only for WSL" + + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"otel_server":{"host": "0.0.0.0","port": 4317},"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15}} + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check + Ctn Set Services Passive 0 service_1 + + Ctn Engine Config Add Command ${0} otel_check {"check": "uptime"} OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Clear Db metrics + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent + + Ctn Config BBDO3 1 + Ctn Clear Retention + + ${start} Ctn Get Round Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for the otel server start + ${content} Create List unencrypted server listening on 0.0.0.0:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "unencrypted server listening on 0.0.0.0:4317" should be available. + + ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 0 120 HARD + Should Be True ${result} resources table not updated + + ${expected_perfdata} Ctn Get Uptime + ${result} Ctn Check Service Perfdata host_1 service_1 60 600 ${expected_perfdata} + Should be True ${result} data_bin not updated + + + #a small threshold to make service_1 warning + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check2 + + Ctn Engine Config Add Command ${0} otel_check2 {"check": "uptime", "args": {"warning-uptime" : "1000000000"}} OTEL connector + + Ctn Reload Engine + ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 1 60 ANY + Should Be True ${result} resources table not updated + + #a small threshold to make service_1 critical + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check3 + + Ctn Engine Config Add Command ${0} otel_check3 {"check": "uptime", "args": {"critical-uptime" : "1000000000"}} OTEL connector + + Ctn Reload Engine + ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 2 60 ANY + Should Be True ${result} resources table not updated + + + *** Keywords *** Ctn Create Cert And Init [Documentation] create key and certificates used by agent and engine on linux side diff --git a/tests/resources/Agent.py b/tests/resources/Agent.py index 219e9a8078d..db31d38ea02 100644 --- a/tests/resources/Agent.py +++ b/tests/resources/Agent.py @@ -18,9 +18,12 @@ # from os import makedirs, environ +import time from robot.libraries.BuiltIn import BuiltIn from socket import gethostname import Common +import json +from robot.api import logger ETC_ROOT = BuiltIn().get_variable_value("${EtcRoot}") VAR_ROOT = BuiltIn().get_variable_value("${VarRoot}") @@ -166,3 +169,35 @@ def ctn_check_pl_command(arg:str): else: return "/tmp/var/lib/centreon-engine/check.pl " + arg +def ctn_get_drive_statistics(drive_name_format:str): + """ + ctn_get_drive_statistics + return a dictionary of drive statistics indexed by expected perfdata names + Args: + drive_name_format: format of the drive name to search for + """ + if environ.get("RUN_ENV","") == "WSL": + drive_dict = {} + json_test_args = environ.get("JSON_TEST_PARAMS") + test_args = json.loads(json_test_args) + for drive in test_args["drive"]: + if drive['Free'] is not None: + drive_dict[drive_name_format.format(drive['Name'])] = (100 * drive['Free']) / (drive['Used'] + drive['Free']) + return drive_dict + else: + return None + +def ctn_get_uptime(): + """ + ctn_get_uptime + return a dict with only one element: uptime => uptime value + """ + if environ.get("RUN_ENV","") == "WSL": + uptime_dict = {} + json_test_args = environ.get("JSON_TEST_PARAMS") + test_args = json.loads(json_test_args) + if test_args["uptime"] is not None: + uptime_dict['uptime'] = time.time() - test_args["uptime"] + return uptime_dict + return None + \ No newline at end of file diff --git a/tests/resources/Common.py b/tests/resources/Common.py index 1325adbacad..0875f6ada49 100644 --- a/tests/resources/Common.py +++ b/tests/resources/Common.py @@ -1918,3 +1918,47 @@ def ctn_compare_string_with_file(string_to_compare:str, file_path:str): return False return True + + +def ctn_check_service_perfdata(host: str, serv: str, timeout: int, precision: float, expected: dict): + """ + Check if performance data are near as expected. + host (str): The hostname of the service to check. + serv (str): The service name to check. + timeout (int): The timeout value for the check. + precision (float): The precision required for the performance data comparison. + expected (dict): A dictionary containing the expected performance data values. + """ + limit = time.time() + timeout + query = f"""SELECT sub_query.metric_name, db.value FROM data_bin db JOIN + (SELECT m.metric_name, MAX(db.ctime) AS last_data, db.id_metric FROM data_bin db + JOIN metrics m ON db.id_metric = m.metric_id + JOIN index_data id ON id.id = m.index_id + WHERE id.host_name='{host}' AND id.service_description='{serv}' + GROUP BY m.metric_id) sub_query + ON db.ctime = sub_query.last_data AND db.id_metric = sub_query.id_metric""" + while time.time() < limit: + connection = pymysql.connect(host=DB_HOST, + user=DB_USER, + password=DB_PASS, + database=DB_NAME_STORAGE, + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) + with connection: + with connection.cursor() as cursor: + cursor.execute(query) + result = cursor.fetchall() + if len(result) == len(expected): + for res in result: + logger.console(f"metric: {res['metric_name']}, value: {res['value']}") + metric = res['metric_name'] + value = float(res['value']) + if metric not in expected: + logger.console(f"ERROR unexpected metric: {metric}") + return False + if abs(value - expected[metric]) > precision: + logger.console(f"ERROR unexpected value for {metric}: {value}") + return False + return True + time.sleep(1) + return False \ No newline at end of file