diff --git a/.github/scripts/agent_robot_test.ps1 b/.github/scripts/agent_robot_test.ps1 index 3f0fe0cd6b6..9719550041a 100644 --- a/.github/scripts/agent_robot_test.ps1 +++ b/.github/scripts/agent_robot_test.ps1 @@ -34,7 +34,7 @@ Write-Host "Work in" $pwd.ToString() $current_dir = (pwd).Path -$wsl_path = "/mnt/" + $current_dir.SubString(0,1).ToLower() + "/" + $current_dir.SubString(3).replace('\','/') +$wsl_path = "/mnt/" + $current_dir.SubString(0, 1).ToLower() + "/" + $current_dir.SubString(3).replace('\', '/') mkdir reports @@ -102,29 +102,38 @@ Start-Process -FilePath build_windows\agent\Release\centagent.exe -ArgumentList $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 +$ts_uptime = ([DateTimeOffset]$d_uptime).ToUnixTimeSeconds() #timestamp format $systeminfo_data = systeminfo /FO CSV | ConvertFrom-Csv -$memory_info = @{ - 'total' = $systeminfo_data.'Total Physical Memory' - 'free' = $systeminfo_data.'Available Physical Memory' - 'virtual_max' = $systeminfo_data.'Virtual Memory: Max Size' +$snapshot = @{ + 'total' = $systeminfo_data.'Total Physical Memory' + 'free' = $systeminfo_data.'Available Physical Memory' + 'virtual_max' = $systeminfo_data.'Virtual Memory: Max Size' 'virtual_free' = $systeminfo_data.'Virtual Memory: Available' } +$serv_list = Get-Service + +$serv_stat = @{ + 'services.running.count' = ($serv_list | Where-Object { $_.Status -eq "Running" } | measure).Count + 'services.stopped.count' = ($serv_list | Where-Object { $_.Status -eq "stopped" } | measure).Count +} + $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 - 'mem_info' = $memory_info} - -Get-PSDrive -PSProvider FileSystem | Select Name, Used, Free | ForEach-Object -Process {$test_param.drive += $_} - -$json_test_param = $test_param | ConvertTo-Json -Compress + 'host' = $my_host_name + 'ip' = $my_ip + 'wsl_path' = $wsl_path + 'pwsh_path' = $pwsh_path + 'drive' = @() + 'current_dir' = $current_dir.replace('\', '/') + 'uptime' = $ts_uptime + 'mem_info' = $snapshot + 'serv_stat' = $serv_stat +} + +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 + "'" diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index 6938b90f506..ddb71e5461b 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -129,6 +129,7 @@ set( SRC_WINDOWS ${NATIVE_SRC}/check_uptime.cc ${NATIVE_SRC}/check_drive_size.cc ${NATIVE_SRC}/check_memory.cc + ${NATIVE_SRC}/check_service.cc ) set( SRC_LINUX diff --git a/agent/native_windows/inc/com/centreon/agent/check_service.hh b/agent/native_windows/inc/com/centreon/agent/check_service.hh new file mode 100644 index 00000000000..4625ab0e906 --- /dev/null +++ b/agent/native_windows/inc/com/centreon/agent/check_service.hh @@ -0,0 +1,193 @@ +/** + * 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_CHECK_SERVICE_HH +#define CENTREON_AGENT_NATIVE_CHECK_SERVICE_HH + +#include "native_check_base.hh" + +namespace com::centreon::agent { +namespace native_check_detail { + +enum e_service_metric : unsigned { + stopped, + start_pending, + stop_pending, + running, + continue_pending, + pause_pending, + paused, + total, + nb_service_metric +}; + +/** + * @brief service filter + * it can filter services by their name and also by their start_auto + */ +class service_filter { + using string_set = absl::flat_hash_set; + + string_set _name_cache_allowed; + string_set _name_cache_excluded; + string_set _display_cache_allowed; + string_set _display_cache_excluded; + + std::unique_ptr _name_filter, _name_filter_exclude; + std::unique_ptr _display_filter, _display_filter_exclude; + + std::optional _start_auto; + + public: + service_filter(const rapidjson::Value& args); + + bool is_allowed(bool start_auto, + const std::string_view& service_name, + const std::string_view& service_display); + + bool use_start_auto_filter() const { return _start_auto.has_value(); } +}; + +/** + * @brief service enumerator + * enumerate services and call a callback on each service allowed by filter + */ +class service_enumerator { + public: + using listener = std::function; + using constructor = std::function()>; + + private: + template + void _enumerate_services(service_filter& filter, + listener&& callback, + const std::shared_ptr& logger); + + protected: + static constexpr size_t service_array_size = 512; + + SC_HANDLE _sc_manager_handler = nullptr; + DWORD _resume_handle = 0; + + using serv_array = ENUM_SERVICE_STATUSA[service_array_size]; + + virtual bool _enumerate_services(serv_array& services, + DWORD* services_returned); + + virtual bool _query_service_config( + LPCSTR service_name, + QUERY_SERVICE_CONFIGA& serv_conf, + const std::shared_ptr& logger); + + public: + service_enumerator(); + + void reset_resume_handle() { _resume_handle = 0; } + + virtual ~service_enumerator(); + + void enumerate_services(service_filter& filter, + listener&& callback, + const std::shared_ptr& logger); +}; + +/** + * snapshot of services informations, used to create output and perfdatas + */ +class w_service_info : public snapshot { + std::string _output; + unsigned _state_to_warning; + unsigned _state_to_critical; + e_status _status = e_status::ok; + + public: + w_service_info(service_enumerator& service_enumerator, + service_filter& filter, + unsigned state_to_warning, + unsigned state_to_critical, + const std::shared_ptr& logger); + + void on_service(const ENUM_SERVICE_STATUSA& service_status); + + e_status get_status() const { return _status; } + + void dump_to_output(std::string* output) const override; +}; + +} // namespace native_check_detail + +/** + * @brief native final check object + * + */ +class check_service + : public native_check_base< + native_check_detail::e_service_metric::nb_service_metric> { + /** + * @brief these enums are indexed by service states values + * https://learn.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status + */ + enum state_mask : unsigned { + stopped = 1, + start_pending = 2, + stop_pending = 4, + running = 8, + continue_pending = 16, + pause_pending = 32, + paused = 64 + }; + + static const std::array, 7> + _label_state; + + unsigned _state_to_warning = 0; + unsigned _state_to_critical = 0; + native_check_detail::service_filter _filter; + std::unique_ptr _enumerator; + + public: + /** + * in order to mock services API, this static constructor is used to replace + * service_enumarator by a mock + */ + static native_check_detail::service_enumerator::constructor + _enumerator_constructor; + + check_service(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); + + std::shared_ptr> + measure() override; + + static void help(std::ostream& help_stream); + + const std::vector& + get_metric_definitions() const override; +}; + +} // namespace com::centreon::agent +#endif diff --git a/agent/native_windows/src/check_service.cc b/agent/native_windows/src/check_service.cc new file mode 100644 index 00000000000..93f01e83e35 --- /dev/null +++ b/agent/native_windows/src/check_service.cc @@ -0,0 +1,728 @@ +/** + * 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 "check_service.hh" +#include "native_check_base.cc" + +using namespace com::centreon::agent; +using namespace com::centreon::agent::native_check_detail; + +namespace com::centreon::agent::native_check_detail { + +/*********************************************************************************************** + * service_enumerator + ***********************************************************************************************/ + +/** + * @brief Constructor + */ +service_enumerator::service_enumerator() { + _sc_manager_handler = OpenSCManager(nullptr, nullptr, GENERIC_READ); + if (!_sc_manager_handler) { + throw exceptions::msg_fmt("OpenSCManager failed"); + } +} + +/** + * @brief Destructor + */ +service_enumerator::~service_enumerator() { + CloseServiceHandle(_sc_manager_handler); +} + +/** + * @brief Enumerate services (just call a version of _enumerate_services) + */ +void service_enumerator::enumerate_services( + service_filter& filter, + service_enumerator::listener&& callback, + const std::shared_ptr& logger) { + if (filter.use_start_auto_filter()) { + _enumerate_services(filter, std::move(callback), logger); + } else { + _enumerate_services(filter, std::move(callback), logger); + } +} + +/** + * @brief Abstract layer used to enumerate services (overloaded in tests to do a + * mock) + */ +bool service_enumerator::_enumerate_services(serv_array& services, + DWORD* services_returned) { + DWORD buff_size = sizeof(services); + return EnumServicesStatusA(_sc_manager_handler, SERVICE_TYPE_ALL, + SERVICE_STATE_ALL, services, sizeof(services), + &buff_size, services_returned, &_resume_handle); +} + +/** + * @brief Query the service configuration (overloaded in tests to do a mock) + */ +bool service_enumerator::_query_service_config( + LPCSTR service_name, + QUERY_SERVICE_CONFIGA& serv_conf, + const std::shared_ptr& logger) { + SC_HANDLE serv_handle = + OpenService(_sc_manager_handler, service_name, GENERIC_READ); + if (!serv_handle) { + SPDLOG_LOGGER_ERROR(logger, " fail to open service {}", service_name); + return false; + } + DWORD bytes_needed = 0; + if (!QueryServiceConfigA(serv_handle, &serv_conf, sizeof(serv_conf), + &bytes_needed)) { + SPDLOG_LOGGER_ERROR(logger, " fail to query service config {}", + service_name); + CloseServiceHandle(serv_handle); + return false; + } + CloseServiceHandle(serv_handle); + return true; +} + +/** + * @brief Enumerate services + * @tparam start_auto if true, start_auto config parameter will be checked + * @param filter service filter + * @param callback callback to call on each service + * @param logger logger + */ +template +void service_enumerator::_enumerate_services( + service_filter& filter, + service_enumerator::listener&& callback, + const std::shared_ptr& logger) { + ENUM_SERVICE_STATUSA services[512]; + + _resume_handle = 0; + + DWORD bytes_needed = 0; + DWORD services_count = 0; + + while (true) { + BOOL success = _enumerate_services(services, &services_count); + if (success || GetLastError() == ERROR_MORE_DATA) { + LPENUM_SERVICE_STATUSA services_end = services + services_count; + for (LPENUM_SERVICE_STATUS serv = services; serv < services_end; ++serv) { + if (start_auto) { + QUERY_SERVICE_CONFIGA serv_conf; + if (!_query_service_config(serv->lpServiceName, serv_conf, logger)) { + continue; + } + + bool this_serv_auto_start = + (serv_conf.dwStartType & + (SERVICE_AUTO_START | SERVICE_BOOT_START | + SERVICE_SYSTEM_START)) != 0; + if (!filter.is_allowed(this_serv_auto_start, serv->lpServiceName, + serv->lpDisplayName)) { + continue; + } + callback(*serv); + } else { + if (!filter.is_allowed(false, serv->lpServiceName, + serv->lpDisplayName)) { + continue; + } + callback(*serv); + } + } + } + if (success) { + break; + } + } +} + +/*********************************************************************************************** + * w_service_info + **********************************************************************************************/ + +/** + * service status printed in plugin output + */ +static constexpr std::array _labels = { + "", "stopped", "starting", "stopping", + "running", "continuing", "pausing", "paused"}; + +/** + * @brief Constructor + * @param service_enumerator service enumerator + * @param filter service filter + * @param state_to_warning state to warning, if a service state is in this mask, + * output status will be at less warning + * @param state_to_critical state to critical + * @param logger logger + */ +w_service_info::w_service_info(service_enumerator& service_enumerator, + service_filter& filter, + unsigned state_to_warning, + unsigned state_to_critical, + const std::shared_ptr& logger) + : _state_to_warning(state_to_warning), + _state_to_critical(state_to_critical) { + memset(&_metrics, 0, sizeof(_metrics)); + service_enumerator.enumerate_services( + filter, + [this](const ENUM_SERVICE_STATUSA& service_status) { + on_service(service_status); + }, + logger); + if (_metrics[e_service_metric::total] == 0) { + _status = e_status::critical; + } +} + +/** + * @brief callback called by enumerator + */ +void w_service_info::on_service(const ENUM_SERVICE_STATUSA& service_status) { + unsigned state = service_status.ServiceStatus.dwCurrentState & 7; + unsigned state_flag = 1 << (state - 1); + if (state & _state_to_critical) { + _status = e_status::critical; + if (!_output.empty()) { + _output.push_back(' '); + } + _output += fmt::format("CRITICAL: {} is {}", service_status.lpServiceName, + _labels[state]); + } else if (state & _state_to_warning) { + if (_status == e_status::ok) { + _status = e_status::warning; + } + if (!_output.empty()) { + _output.push_back(' '); + } + _output += fmt::format("WARNING: {} is {}", service_status.lpServiceName, + _labels[state]); + } + ++_metrics[state - 1]; + ++_metrics[e_service_metric::total]; +} + +/** + * plugin output + */ +void w_service_info::dump_to_output(std::string* output) const { + uint64_t total = _metrics[e_service_metric::total]; + if (total == 0) { + output->append("no service found"); + } else if (total == _metrics[e_service_metric::running]) { + output->append("all services are running"); + } else { + output->append("services: "); + bool first = true; + for (unsigned i = 0; i < e_service_metric::total; ++i) { + if (_metrics[i] > 0) { + if (first) { + first = false; + } else { + output->append(", "); + } + output->append(fmt::format("{} {}", _metrics[i], _labels[i + 1])); + } + } + } + if (!_output.empty()) { + output->push_back(' '); + output->append(_output); + } +} + +/*********************************************************************************************** + * service_filter + **********************************************************************************************/ + +/** + * @brief Constructor that initializes the service filter based on the provided + * arguments. + * @param args JSON value containing the plugin config. + * @throws exceptions::msg_fmt if any of the filter parameters are invalid. + */ +service_filter::service_filter(const rapidjson::Value& args) { + if (args.IsObject()) { + for (auto member_iter = args.MemberBegin(); member_iter != args.MemberEnd(); + ++member_iter) { + std::string key = absl::AsciiStrToLower(member_iter->name.GetString()); + if (key == "start-auto") { + const rapidjson::Value& val = member_iter->value; + if (val.IsBool()) { + _start_auto = val.GetBool(); + } else { + throw exceptions::msg_fmt("start-auto must be a boolean"); + } + } else if (key == "filter-name") { + const rapidjson::Value& val = member_iter->value; + if (val.IsString()) { + std::string value = val.GetString(); + absl::AsciiStrToLower(&value); + _name_filter = std::make_unique(value); + if (!_name_filter->ok()) { + throw exceptions::msg_fmt("filter-name: {} is not a valid regex", + val.GetString()); + } + } else { + throw exceptions::msg_fmt("filter-name must be a string"); + } + } else if (key == "exclude-name") { + const rapidjson::Value& val = member_iter->value; + if (val.IsString()) { + std::string value = val.GetString(); + absl::AsciiStrToLower(&value); + _name_filter_exclude = std::make_unique(value); + if (!_name_filter_exclude->ok()) { + throw exceptions::msg_fmt("exclude-name: {} is not a valid regex", + val.GetString()); + } + } else { + throw exceptions::msg_fmt("exclude-name must be a string"); + } + } else if (key == "filter-display") { + const rapidjson::Value& val = member_iter->value; + if (val.IsString()) { + std::string value = val.GetString(); + absl::AsciiStrToLower(&value); + _display_filter = std::make_unique(value); + if (!_display_filter->ok()) { + throw exceptions::msg_fmt("filter-display: {} is not a valid regex", + val.GetString()); + } + } else { + throw exceptions::msg_fmt("filter-display must be a string"); + } + } else if (key == "exclude-display") { + const rapidjson::Value& val = member_iter->value; + if (val.IsString()) { + std::string value = val.GetString(); + absl::AsciiStrToLower(&value); + _display_filter_exclude = std::make_unique(value); + if (!_display_filter_exclude->ok()) { + throw exceptions::msg_fmt( + "exclude-display: {} is not a valid regex", val.GetString()); + } + } else { + throw exceptions::msg_fmt("exclude-display must be a string"); + } + } + } + } +} + +/** + * @brief remove all negative chars + * @param sz string to clean + */ +static void remove_accents(std::string* sz) { + for (char& chr : *sz) { + if (chr < 0) { + chr = '_'; + } + } +} + +/** + * @brief Check if a service is allowed by the filter. + * @param start_auto Whether the service is set to start automatically. + * @param service_name The name of the service. + */ +bool service_filter::is_allowed(bool start_auto, + const std::string_view& service_name, + const std::string_view& service_display) { + std::string lower_service_name(service_name.data(), service_name.length()); + absl::AsciiStrToLower(&lower_service_name); + + std::string lower_display(service_display.data(), service_display.length()); + absl::AsciiStrToLower(&lower_display); + + // accented characters are not supported by RE2 so we remove them + remove_accents(&lower_display); + + if (_start_auto && _start_auto.value() != start_auto) { + return false; + } + if (_name_cache_excluded.find(lower_service_name) != + _name_cache_excluded.end()) { + return false; + } + if (_display_cache_excluded.find(lower_display) != + _display_cache_excluded.end()) { + return false; + } + + auto check_display = [&]() { + if (_display_filter_exclude && + RE2::FullMatch(lower_display, *_display_filter_exclude)) { + _display_cache_excluded.emplace(lower_display); + return false; + } + if (_display_filter && !RE2::FullMatch(lower_display, *_display_filter)) { + _display_cache_excluded.emplace(lower_display); + return false; + } + _display_cache_allowed.emplace(lower_display); + return true; + }; + + auto check_name = [&]() { + if (_name_filter_exclude && + RE2::FullMatch(lower_service_name, *_name_filter_exclude)) { + _name_cache_excluded.emplace(lower_service_name); + return false; + } + if (_name_filter && !RE2::FullMatch(lower_service_name, *_name_filter)) { + _name_cache_excluded.emplace(lower_service_name); + return false; + } + _name_cache_allowed.emplace(lower_service_name); + return true; + }; + + if (_name_cache_allowed.find(lower_service_name) != + _name_cache_allowed.end()) { + if (_display_cache_allowed.find(lower_display) != + _display_cache_allowed.end()) { + return true; + } + return check_display(); + } + + if (_display_cache_allowed.find(lower_display) != + _display_cache_allowed.end()) { + return check_name(); + } + + return check_name() && check_display(); +} + +/*********************************************************************************************** + * w_service_info_to_status + **********************************************************************************************/ + +/** + * The goal of this class is to convert the status field of w_service_info into + * check_status. It compares nothing + * */ +class w_service_info_to_status + : public measure_to_status { + public: + w_service_info_to_status() + : measure_to_status(e_status::ok, + 0, + 0, + 0, + false, + false) {} + + void compute_status( + const snapshot& to_test, + e_status* status) const override { + e_status serv_status = + static_cast(to_test).get_status(); + if (serv_status > *status) { + *status = serv_status; + } + } +}; + +} // namespace com::centreon::agent::native_check_detail + +/*********************************************************************************************** + * check_service + **********************************************************************************************/ + +/** + * we can allow different service states, so we use check_service::state_mask to + * filter or set status to critical + */ +const std::array, 7> + check_service::_label_state = { + std::make_pair("stopped", check_service::state_mask::stopped), + std::make_pair("starting", check_service::state_mask::start_pending), + std::make_pair("stopping", check_service::state_mask::stop_pending), + std::make_pair("running", check_service::state_mask::running), + std::make_pair("continuing", + check_service::state_mask::continue_pending), + std::make_pair("pausing", check_service::state_mask::pause_pending), + std::make_pair("paused", check_service::state_mask::paused)}; + +using w_service_to_status = + measure_to_status; + +using service_to_status_constructor = + std::function(double /*threshold*/)>; + +static const absl::flat_hash_map + _label_to_service_status = { + {"warning-total-running", + [](double threshold) { + return std::make_unique( + e_status::warning, e_service_metric::running, threshold, + e_service_metric::nb_service_metric, false, true); + }}, + {"critical-total-running", + [](double threshold) { + return std::make_unique( + e_status::critical, e_service_metric::running, threshold, + e_service_metric::nb_service_metric, false, true); + }}, + {"warning-total-paused", + [](double threshold) { + return std::make_unique( + e_status::warning, e_service_metric::paused, threshold, + e_service_metric::nb_service_metric, false, false); + }}, + {"critical-total-paused", + [](double threshold) { + return std::make_unique( + e_status::critical, e_service_metric::paused, threshold, + e_service_metric::nb_service_metric, false, false); + }}, + {"warning-total-stopped", + [](double threshold) { + return std::make_unique( + e_status::warning, e_service_metric::stopped, threshold, + e_service_metric::nb_service_metric, false, false); + }}, + {"critical-total-stopped", + [](double threshold) { + return std::make_unique( + e_status::critical, e_service_metric::stopped, threshold, + e_service_metric::nb_service_metric, false, false); + }} + +}; + +/** + * default service enumerator constructor + */ +service_enumerator::constructor check_service::_enumerator_constructor = []() { + return std::make_unique(); +}; + +/** + * @brief constructor + */ +check_service::check_service( + 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) + : native_check_base(io_context, + logger, + first_start_expected, + check_interval, + serv, + cmd_name, + cmd_line, + args, + cnf, + std::move(handler)), + _filter(args), + _enumerator(_enumerator_constructor()) { + if (!args.IsObject()) { + return; + } + _measure_to_status.emplace( + std::make_tuple(e_service_metric::nb_service_metric, + e_service_metric::nb_service_metric, e_status::ok), + std::make_unique()); + + for (auto member_iter = args.MemberBegin(); member_iter != args.MemberEnd(); + ++member_iter) { + std::string key = absl::AsciiStrToLower(member_iter->name.GetString()); + + if (key == "warning-state") { + const rapidjson::Value& val = member_iter->value; + if (val.IsString()) { + re2::RE2 filter_typ_re(val.GetString()); + if (!filter_typ_re.ok()) { + throw exceptions::msg_fmt("warning-state: {} is not a valid regex", + val.GetString()); + } else { + for (const auto& [label, flag] : _label_state) { + if (RE2::FullMatch(label, filter_typ_re)) { + _state_to_warning |= flag; + } + } + } + } else { + throw exceptions::msg_fmt("warning-state must be a string"); + } + } else if (key == "critical-state") { + const rapidjson::Value& val = member_iter->value; + if (val.IsString()) { + re2::RE2 filter_typ_re(val.GetString()); + if (!filter_typ_re.ok()) { + throw exceptions::msg_fmt("critical-state: {} is not a valid regex", + val.GetString()); + } else { + for (const auto& [label, flag] : _label_state) { + if (RE2::FullMatch(label, filter_typ_re)) { + _state_to_critical |= flag; + } + } + } + } else { + throw exceptions::msg_fmt("critical-state must be a string"); + } + } else { + auto threshold = _label_to_service_status.find(key); + if (threshold != _label_to_service_status.end()) { + const rapidjson::Value& val = member_iter->value; + if (val.IsNumber()) { + std::unique_ptr to_ins = + threshold->second(val.GetDouble()); + _measure_to_status.emplace( + std::make_tuple(to_ins->get_data_index(), + e_service_metric::nb_service_metric, + to_ins->get_status()), + std::move(to_ins)); + + } else if (val.IsString()) { + const auto& to_conv = val.GetString(); + double dval; + if (absl::SimpleAtod(to_conv, &dval)) { + std::unique_ptr to_ins = + threshold->second(dval); + _measure_to_status.emplace( + std::make_tuple(to_ins->get_data_index(), + e_service_metric::nb_service_metric, + to_ins->get_status()), + std::move(to_ins)); + } else { + throw exceptions::msg_fmt( + "command: {}, {} is not a number for parameter {}", cmd_name, + key, val); + } + } else { + throw exceptions::msg_fmt( + "command: {}, {} is not a number for parameter {}", cmd_name, key, + val); + } + } + } + } +} + +/** + * @brief create a snapshot of services state + */ +std::shared_ptr> +check_service::measure() { + // used to reset service list walking + _enumerator->reset_resume_handle(); + return std::make_shared( + *_enumerator, _filter, _state_to_warning, _state_to_critical, _logger); +} + +static const std::vector + metric_definitions = { + {"services.stopped.count", e_service_metric::stopped, + e_service_metric::total, false}, + {"services.starting.count", e_service_metric::start_pending, + e_service_metric::total, false}, + {"services.stopping.count", e_service_metric::stop_pending, + e_service_metric::total, false}, + {"services.running.count", e_service_metric::running, + e_service_metric::total, false}, + {"services.continuing.count", e_service_metric::continue_pending, + e_service_metric::total, false}, + {"services.pausing.count", e_service_metric::pause_pending, + e_service_metric::total, false}, + {"services.paused.count", e_service_metric::paused, + e_service_metric::total, false}}; + +const std::vector& +check_service::get_metric_definitions() const { + return metric_definitions; +} + +/** + * @brief some help + */ +void check_service::help(std::ostream& help_stream) { + help_stream << R"( +- service params: + warning-state: regex to match service state that will trigger a warning + states are: + - stopped + - starting + - stopping + - running + - continuing + - pausing + - paused + critical-state: regex to match service state that will trigger a critical + warning-total-running: running service number threshold below which the service will pass in the warning state + critical-total-running: running service number threshold below which the service will pass in the critical state + warning-total-paused: number of services in the pause state above which the service goes into the warning state + critical-total-paused: number of services in the pause state above which the service goes into the critical state + warning-total-stopped: number of services in the stop state above which the service goes into the warning state + critical-total-stopped: number of services in the stop state above which the service goes into the critical state + start-auto: true: only services that start automatically will be counted + filter-name: regex to filter service names + exclude-name: regex to exclude service names + filter-display: regex to filter service display names as they appear in service manager + exclude-display: regex to exclude service display names + An example of a configuration file: + { + "check": "service", + "args": { + "warning-state": "stopped", + "critical-state": "running", + "warning-total-running": 20, + "critical-total-running": 150, + "start-auto": true, + "filter-name": ".*", + "exclude-name": ".*" + } + } + Examples of output: + OK: all services are running + In case of a too restricted filter: + CRITICAL: no service found + In case on some services not in running state: + OK: services: 1 stopped, 1 starting, 1 stopping + In case of a service in a critical state: + CRITICAL: services: 1 stopped, 1 starting, 1 stopping CRITICAL: logon is stopped CRITICAL: httpd is stopping + Metrics: + services.stopped.count + services.starting.count + services.stopping.count + services.running.count + services.continuing.count + services.pausing.count + services.paused.count +)"; +} + +namespace com::centreon::agent { +template class native_check_base< + native_check_detail::e_service_metric::nb_service_metric>; +} diff --git a/agent/src/main_win.cc b/agent/src/main_win.cc index 39063c4d0a4..099edcdeee1 100644 --- a/agent/src/main_win.cc +++ b/agent/src/main_win.cc @@ -19,6 +19,7 @@ #include "check_cpu.hh" #include "check_memory.hh" +#include "check_service.hh" #include "check_uptime.hh" #include "drive_size.hh" @@ -123,6 +124,7 @@ void show_help() { check_memory::help(std::cout); check_uptime::help(std::cout); check_drive_size::help(std::cout); + check_service::help(std::cout); } /** diff --git a/agent/src/scheduler.cc b/agent/src/scheduler.cc index bf89d3d480d..8718a148183 100644 --- a/agent/src/scheduler.cc +++ b/agent/src/scheduler.cc @@ -20,6 +20,7 @@ #include "check_cpu.hh" #ifdef _WIN32 #include "check_memory.hh" +#include "check_service.hh" #include "check_uptime.hh" #endif #include "check_exec.hh" @@ -575,6 +576,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 == "service"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 8afe26f26fe..6d5152ee6ec 100644 --- a/agent/test/CMakeLists.txt +++ b/agent/test/CMakeLists.txt @@ -27,7 +27,11 @@ set( SRC_COMMON if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(SRC ${SRC_COMMON} config_test.cc check_linux_cpu_test.cc) else() - set(SRC ${SRC_COMMON} check_windows_cpu_test.cc check_windows_memory_test.cc check_uptime_test.cc) + set(SRC ${SRC_COMMON} + check_windows_cpu_test.cc + check_windows_memory_test.cc + check_uptime_test.cc + check_windows_service_test.cc) endif() diff --git a/agent/test/check_windows_service_test.cc b/agent/test/check_windows_service_test.cc new file mode 100644 index 00000000000..d587bcc7753 --- /dev/null +++ b/agent/test/check_windows_service_test.cc @@ -0,0 +1,849 @@ +/** + * 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 "com/centreon/common/rapidjson_helper.hh" + +#include "check_service.hh" + +extern std::shared_ptr g_io_context; + +using namespace com::centreon::agent; +using namespace com::centreon::agent::native_check_detail; + +using namespace std::string_literals; + +class mock_service_enumerator : public service_enumerator { + public: + using enum_with_conf = std::pair; + + std::vector data; + + size_t max_enumerate = 512; + + bool _enumerate_services(serv_array& services, + DWORD* services_returned) override; + + bool _query_service_config( + LPCSTR service_name, + QUERY_SERVICE_CONFIGA& serv_conf, + const std::shared_ptr& logger) override; + + static enum_with_conf create_serv(const char* name, + const char* display, + DWORD state, + DWORD start_type) { + ENUM_SERVICE_STATUSA serv; + serv.lpServiceName = const_cast(name); + serv.lpDisplayName = const_cast(display); + serv.ServiceStatus.dwCurrentState = state; + serv.ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + serv.ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + serv.ServiceStatus.dwWin32ExitCode = 0; + serv.ServiceStatus.dwServiceSpecificExitCode = 0; + serv.ServiceStatus.dwCheckPoint = 0; + serv.ServiceStatus.dwWaitHint = 0; + + QUERY_SERVICE_CONFIGA serv_conf; + serv_conf.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + serv_conf.dwStartType = start_type; + serv_conf.dwErrorControl = SERVICE_ERROR_NORMAL; + serv_conf.lpBinaryPathName = "C:\\path\\to\\service.exe"; + serv_conf.lpLoadOrderGroup = nullptr; + serv_conf.dwTagId = 0; + serv_conf.lpDependencies = nullptr; + serv_conf.lpServiceStartName = nullptr; + serv_conf.lpDisplayName = const_cast(display); + + return {serv, serv_conf}; + } +}; + +bool mock_service_enumerator::_enumerate_services(serv_array& services, + DWORD* services_returned) { + size_t to_return = std::min(max_enumerate, data.size() - _resume_handle); + to_return = std::min(to_return, service_array_size); + *services_returned = to_return; + for (unsigned i = 0; i < to_return; ++i) { + services[i] = data[i].first; + } + _resume_handle += to_return; + return true; +} + +bool mock_service_enumerator::_query_service_config( + LPCSTR service_name, + QUERY_SERVICE_CONFIGA& serv_conf, + const std::shared_ptr& logger) { + for (const auto& service : data) { + if (strcmp(service_name, service.first.lpServiceName) == 0) { + serv_conf = service.second; + return true; + } + } + return false; +} + +constexpr std::array expected_metrics = { + "services.stopped.count", "services.starting.count", + "services.stopping.count", "services.running.count", + "services.continuing.count", "services.pausing.count", + "services.paused.count"}; + +TEST(check_service, service_no_threshold_all_running) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service1", "desc serv1", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service2", "desc serv2", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service3", "desc serv3", + SERVICE_RUNNING, SERVICE_AUTO_START), + }; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = "{ }"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::ok); + + EXPECT_EQ(output, "OK: all services are running"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.running.count") { + EXPECT_EQ(perf.value(), 3.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_no_threshold_one_by_state) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_stop_pending", "desc service_stop_pending", + SERVICE_STOP_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", " desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START), + }; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = "{ }"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::ok); + + EXPECT_EQ(output, + "OK: services: 1 stopped, 1 starting, 1 stopping, 1 running, " + "1 continuing, 1 pausing, 1 paused"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + EXPECT_EQ(perf.value(), 1.0); + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_exclude_all_service) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending ", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_stop_pending", "desc service_stop_pending", + SERVICE_STOP_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = R"({ "exclude-name": ".*" })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::critical); + + EXPECT_EQ(output, "CRITICAL: no service found"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + EXPECT_EQ(perf.value(), 0.0); + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_allow_some_service) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_stop_pending", "desc service_stop_pending", + SERVICE_STOP_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = R"({ "filter-name": "service_s.*" })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::ok); + + EXPECT_EQ(output, "OK: services: 1 stopped, 1 starting, 1 stopping"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.stopped.count" || + perf.name() == "services.starting.count" || + perf.name() == "services.stopping.count") { + EXPECT_EQ(perf.value(), 1.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_exclude_some_service) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_stop_pending", "desc service_stop_pending", + SERVICE_STOP_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = R"({ "exclude-name": "service_s.*" })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::ok); + + EXPECT_EQ(output, + "OK: services: 1 running, 1 continuing, 1 pausing, 1 paused"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.stopped.count" || + perf.name() == "services.starting.count" || + perf.name() == "services.stopping.count") { + EXPECT_EQ(perf.value(), 0.0); + } else { + EXPECT_EQ(perf.value(), 1.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_allow_some_service_warning_running) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_stop_pending", "desc service_stop_pending", + SERVICE_STOP_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "filter-name": "service_s.*", "warning-total-running": "5" })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::warning); + + EXPECT_EQ(output, "WARNING: services: 1 stopped, 1 starting, 1 stopping"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.stopped.count" || + perf.name() == "services.starting.count" || + perf.name() == "services.stopping.count") { + EXPECT_EQ(perf.value(), 1.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_allow_some_service_warning_stopped) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_stopped2", + "desc service_stopped2", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START), + }; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "filter-name": "service_s.*", "warning-total-stopped": "1" })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::warning); + + EXPECT_EQ(output, "WARNING: services: 2 stopped, 1 starting"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.stopped.count") { + EXPECT_EQ(perf.value(), 2.0); + } else if (perf.name() == "services.starting.count") { + EXPECT_EQ(perf.value(), 1.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_allow_some_service_critical_state) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_stopping", "desc service_stopping", SERVICE_STOP_PENDING, + SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "filter-name": "service_s.*", "critical-state": "stop.*" })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::critical); + + EXPECT_EQ(output, + "CRITICAL: services: 1 stopped, 1 starting, 1 stopping " + "CRITICAL: service_stopped is stopped CRITICAL: service_stopping " + "is stopping"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.stopped.count" || + perf.name() == "services.starting.count" || + perf.name() == "services.stopping.count") { + EXPECT_EQ(perf.value(), 1.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_start_auto_true) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_start_pending", + "desc service_start_pending", + SERVICE_START_PENDING, 0), + mock_service_enumerator::create_serv( + "service_stopping", "desc service_stopping", SERVICE_STOP_PENDING, + SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_running", "desc service_running", SERVICE_RUNNING, 0), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_paused", "desc service_paused", SERVICE_PAUSED, 0)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = R"({ "start-auto": true })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::ok); + + EXPECT_EQ(output, + "OK: services: 1 stopped, 1 stopping, 1 continuing, 1 pausing"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.stopped.count" || + perf.name() == "services.continuing.count" || + perf.name() == "services.pausing.count" || + perf.name() == "services.stopping.count") { + EXPECT_EQ(perf.value(), 1.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, service_filter_start_auto_false) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_start_pending", + "desc service_start_pending", + SERVICE_START_PENDING, 0), + mock_service_enumerator::create_serv( + "service_stopping", "desc service_stopping", SERVICE_STOP_PENDING, + SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_running", "desc service_running", SERVICE_RUNNING, 0), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_paused", "desc service_paused", SERVICE_PAUSED, 0)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = R"({ "start-auto": false })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::ok); + + EXPECT_EQ(output, "OK: services: 1 starting, 1 running, 1 paused"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.starting.count" || + perf.name() == "services.running.count" || + perf.name() == "services.paused.count") { + EXPECT_EQ(perf.value(), 1.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} + +TEST(check_service, + service_filter_allow_some_service_filtered_by_display_warning_running) { + mock_service_enumerator::enum_with_conf data[] = { + mock_service_enumerator::create_serv("service_stopped", + "desc service_stopped", + SERVICE_STOPPED, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_start_pending", "desc service_start_pending", + SERVICE_START_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_stop_pending", "desc service_stop_pending", + SERVICE_STOP_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_running", + "desc service_running", + SERVICE_RUNNING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_continue_pending", "desc service_continue_pending", + SERVICE_CONTINUE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv( + "service_pause_pending", "desc service_pause_pending", + SERVICE_PAUSE_PENDING, SERVICE_AUTO_START), + mock_service_enumerator::create_serv("service_paused", + "desc service_paused", + SERVICE_PAUSED, SERVICE_AUTO_START)}; + + mock_service_enumerator mock; + mock.data = {std::begin(data), std::end(data)}; + + check_service::_enumerator_constructor = [&mock]() { + return std::make_unique(mock); + }; + + using namespace com::centreon::common::literals; + rapidjson::Document check_args = + R"({ "filter-display": "desc service_s.*", "warning-total-running": "5" })"_json; + + check_service test_check( + g_io_context, spdlog::default_logger(), {}, {}, "serv"s, "cmd_name"s, + "cmd_line"s, check_args, nullptr, + [](const std::shared_ptr& caller, int status, + const std::list& perfdata, + const std::list& outputs) {}); + + auto snap = test_check.measure(); + + std::string output; + std::list perfs; + e_status status = test_check.compute(*snap, &output, &perfs); + + EXPECT_EQ(status, e_status::warning); + + EXPECT_EQ(output, "WARNING: services: 1 stopped, 1 starting, 1 stopping"); + + EXPECT_EQ(perfs.size(), 7); + + for (const com::centreon::common::perfdata& perf : perfs) { + EXPECT_NE(std::find(expected_metrics.begin(), expected_metrics.end(), + perf.name()), + expected_metrics.end()); + if (perf.name() == "services.stopped.count" || + perf.name() == "services.starting.count" || + perf.name() == "services.stopping.count") { + EXPECT_EQ(perf.value(), 1.0); + } else { + EXPECT_EQ(perf.value(), 0.0); + } + EXPECT_EQ(perf.min(), 0); + EXPECT_EQ(perf.max(), snap->get_metric(e_service_metric::total)); + } +} diff --git a/common/precomp_inc/precomp.hh b/common/precomp_inc/precomp.hh index 227f2533caa..2d6de0f85fd 100644 --- a/common/precomp_inc/precomp.hh +++ b/common/precomp_inc/precomp.hh @@ -47,7 +47,7 @@ #include #include -#ifndef _WINDOWS +#ifndef _WIN32 #include #include #endif diff --git a/common/process/src/process.cc b/common/process/src/process.cc index cd6a78c0bae..309a44e316d 100644 --- a/common/process/src/process.cc +++ b/common/process/src/process.cc @@ -150,7 +150,7 @@ process::process( const std::shared_ptr& logger, const std::string_view& cmd_line) : _io_context(io_context), _logger(logger) { -#ifdef _WINDOWS +#ifdef _WIN32 auto split_res = boost::program_options::split_winmain(std::string(cmd_line)); #else auto split_res = boost::program_options::split_unix(std::string(cmd_line)); diff --git a/common/src/utf8.cc b/common/src/utf8.cc index 7ef6ebed5ed..3aeda6c937a 100644 --- a/common/src/utf8.cc +++ b/common/src/utf8.cc @@ -32,12 +32,15 @@ std::string com::centreon::common::check_string_utf8( const std::string_view& str) noexcept { std::string_view::const_iterator it; - for (it = str.begin(); it != str.end();) { + for (it = str.begin(); it < str.end();) { uint32_t val = (*it & 0xff); if ((val & 0x80) == 0) { ++it; continue; } + if (it + 1 >= str.end()) { + break; + } val = (val << 8) | (*(it + 1) & 0xff); if ((val & 0xe0c0) == 0xc080) { val &= 0x1e00; @@ -47,6 +50,9 @@ std::string com::centreon::common::check_string_utf8( continue; } + if (it + 2 >= str.end()) { + break; + } val = (val << 8) | (*(it + 2) & 0xff); if ((val & 0xf0c0c0) == 0xe08080) { val &= 0xf2000; @@ -56,6 +62,9 @@ std::string com::centreon::common::check_string_utf8( continue; } + if (it + 3 >= str.end()) { + break; + } val = (val << 8) | (*(it + 3) & 0xff); if ((val & 0xf8c0c0c0) == 0xF0808080) { val &= 0x7300000; diff --git a/common/tests/process_test.cc b/common/tests/process_test.cc index bdf0ed82420..92d1b7d25c7 100644 --- a/common/tests/process_test.cc +++ b/common/tests/process_test.cc @@ -25,7 +25,7 @@ using namespace com::centreon::common; -#ifdef _WINDOWS +#ifdef _WIN32 #define ECHO_PATH "tests\\echo.bat" #define SLEEP_PATH "tests\\sleep.bat" #define END_OF_LINE "\r\n" @@ -147,7 +147,7 @@ TEST_F(process_test, throw_on_error) { TEST_F(process_test, script_error) { using namespace std::literals; -#ifdef _WINDOWS +#ifdef _WIN32 std::shared_ptr to_wait( new process_wait(g_io_context, _logger, "tests\\\\bad_script.bat")); #else @@ -192,7 +192,7 @@ TEST_F(process_test, call_start_several_time_no_args) { ASSERT_EQ(to_wait->get_stderr(), ""); } -#ifndef _WINDOWS +#ifndef _WIN32 TEST_F(process_test, stdin_to_stdout) { ::remove("toto.sh"); @@ -249,7 +249,7 @@ TEST_F(process_test, kill_process) { // kill process to_wait->kill(); std::this_thread::sleep_for(std::chrono::seconds(1)); -#ifdef _WINDOWS +#ifdef _WIN32 auto process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); ASSERT_NE(process_handle, nullptr); diff --git a/common/tests/rapidjson_helper_test.cc b/common/tests/rapidjson_helper_test.cc index b88afa599f9..1bbdb6fb416 100644 --- a/common/tests/rapidjson_helper_test.cc +++ b/common/tests/rapidjson_helper_test.cc @@ -30,7 +30,7 @@ using namespace com::centreon; using namespace com::centreon::common; -#ifdef _WINDOWS +#ifdef _WIN32 #define JSON_FILE_PATH "C:/Users/Public/toto.json" #else #define JSON_FILE_PATH "/tmp/toto.json" diff --git a/common/tests/utf8_test.cc b/common/tests/utf8_test.cc index 98376f390ce..77dbe2e3f31 100644 --- a/common/tests/utf8_test.cc +++ b/common/tests/utf8_test.cc @@ -48,6 +48,16 @@ TEST(string_check_utf8, cp1252) { ASSERT_EQ(check_string_utf8(txt), "Le ticket coûte 12€\n"); } +/* + * Given a string encoded in CP-1252 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, cp1252_bis) { + std::string txt("Service de plateforme des appareils connect\xe9s"); + ASSERT_EQ(check_string_utf8(txt), + "Service de plateforme des appareils connectés"); +} + /* * Given a string encoded in ISO-8859-15 * Then the check_string_utf8 function converts it to UTF-8. diff --git a/tests/broker-engine/cma.robot b/tests/broker-engine/cma.robot index 880e64ece1f..a7209a9852b 100644 --- a/tests/broker-engine/cma.robot +++ b/tests/broker-engine/cma.robot @@ -607,6 +607,74 @@ BEOTEL_CENTREON_AGENT_CHECK_NATIVE_MEMORY ${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_SERVICE + [Documentation] agent check service with native check service and we expect to get it in check result + [Tags] broker engine opentelemetry MON-147933 + + ${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": "service"} 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 + Ctn Wait For Otel Server To Be Ready ${start} + + ${result} Ctn Check Service Resource Status With Timeout host_1 service_1 0 120 HARD + Should Be True ${result} resources table not updated + + # as centagent retrieve much more services than powershell (on my computer 660 versus 263), we can't compare perfdatas + # ${expected_perfdata} Ctn Get Service + # ${result} Ctn Check Service Perfdata host_1 service_1 60 2 ${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": "service", "args": {"warning-total-running" : "1000"}} 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": "service", "args": {"critical-total-running" : "1000"}} 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 *** diff --git a/tests/broker-engine/delete-poller.robot b/tests/broker-engine/delete-poller.robot index 40db930a375..54a4756fdc0 100644 --- a/tests/broker-engine/delete-poller.robot +++ b/tests/broker-engine/delete-poller.robot @@ -359,6 +359,7 @@ EBDP5 Ctn Config Broker module ${4} Ctn Config BBDO3 ${4} Ctn Broker Config Log central sql trace + Ctn Broker Config Log central core trace ${start} Get Current Date Ctn Start Broker Ctn Start engine @@ -416,6 +417,7 @@ EBDP6 Ctn Config Broker module ${3} Ctn Config BBDO3 ${3} Ctn Broker Config Log central sql trace + Ctn Broker Config Log central core trace ${start} Get Current Date Ctn Start Broker Ctn Start Engine @@ -483,6 +485,7 @@ EBDP7 Ctn Config Broker module ${3} Ctn Config BBDO3 ${3} Ctn Broker Config Log central sql trace + Ctn Broker Config Log central core trace ${start} Get Current Date Ctn Start Broker Ctn Start engine diff --git a/tests/broker-engine/muxer_filter.robot b/tests/broker-engine/muxer_filter.robot index 9bcae04c526..6994e01d4b7 100644 --- a/tests/broker-engine/muxer_filter.robot +++ b/tests/broker-engine/muxer_filter.robot @@ -259,6 +259,7 @@ CBD_RELOAD_AND_FILTERS Ctn Config Broker central Ctn Config Broker rrd Ctn Broker Config Log central config trace + Ctn Broker Config Log central core trace Ctn Broker Config Log rrd rrd debug Ctn Config BBDO3 ${1} Ctn Config Engine ${1} @@ -356,6 +357,7 @@ CBD_RELOAD_AND_FILTERS_WITH_OPR Ctn Broker Config Output Set central centreon-broker-master-rrd one_peer_retention_mode yes Ctn Broker Config Input Set rrd central-rrd-master-input host localhost Ctn Broker Config Log central config trace + Ctn Broker Config Log central core trace Ctn Broker Config Log rrd rrd debug Ctn Config BBDO3 ${1} Ctn Config Engine ${1} diff --git a/tests/broker/sql.robot b/tests/broker/sql.robot index e7cecdd850a..84f10b4c16b 100644 --- a/tests/broker/sql.robot +++ b/tests/broker/sql.robot @@ -166,6 +166,7 @@ BDB10 Ctn Config Broker rrd Ctn Config Broker module Ctn Broker Config Log central sql debug + Ctn Broker Config Log central core debug ${start} Get Current Date Ctn Start Broker ${content} Create List sql stream initialization storage stream initialization diff --git a/tests/resources/Agent.py b/tests/resources/Agent.py index ea59e13f57b..418d1f91969 100644 --- a/tests/resources/Agent.py +++ b/tests/resources/Agent.py @@ -236,3 +236,27 @@ def ctn_get_memory(): return memory_dict return None +def ctn_get_service(): + """ + ctn_get_service statistics + return a dict with these elements (expected perfdata): + - services.stopped.count + - services.starting.count + - services.stopping.count + - services.running.count + - services.continuing.count + - services.pausing.count + - services.paused.count + """ + + if environ.get("RUN_ENV","") == "WSL": + service_dict = {'services.stopped.count': 0, 'services.starting.count': None, 'services.stopping.count': None, 'services.running.count': 0, + 'services.continuing.count': None, 'services.pausing.count': None, 'services.paused.count': None } + json_test_args = environ.get("JSON_TEST_PARAMS") + test_args = json.loads(json_test_args) + if test_args["serv_stat"] is not None: + service_dict["services.stopped.count"] = test_args["serv_stat"]["services.stopped.count"] + service_dict["services.running.count"] = test_args["serv_stat"]["services.running.count"] + return service_dict + return None +