From df2b0db352ff67274140d571b4cf54b5eb9ff10b Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:14:33 +0200 Subject: [PATCH] * centreon-agent => centreon-monitoring-agent * telegraf conf accepts only one host add scheduler, check and check_exec class to agent * otel tests checks resources table * engine accept connections from centreon monitoring agent add reverse client in opentelemetry module * no overlays, provide our own launcher move utf8 to common and some fixes (#1502) add windows-agent.yaml (#1525) * add windows-agent.yaml * fix muxer issue --- .github/workflows/windows-agent.yml | 21 + CMakeLists.txt | 1 + agent/CMakeLists.txt | 14 +- agent/inc/com/centreon/agent/bireactor.hh | 88 ++++ agent/inc/com/centreon/agent/check_exec.hh | 2 +- agent/inc/com/centreon/agent/config.hh | 4 +- agent/inc/com/centreon/agent/scheduler.hh | 6 +- .../com/centreon/agent/streaming_client.hh | 113 ++++ .../com/centreon/agent/streaming_server.hh | 77 +++ agent/precomp_inc/precomp.hh | 24 +- agent/src/bireactor.cc | 207 ++++++++ agent/src/check.cc | 6 +- agent/src/check_exec.cc | 12 +- agent/src/main.cc | 27 +- agent/src/scheduler.cc | 57 ++- agent/src/streaming_client.cc | 230 +++++++++ agent/src/streaming_server.cc | 237 +++++++++ agent/test/check_exec_test.cc | 12 +- agent/test/scheduler_test.cc | 30 +- broker/bam/src/reporting_stream.cc | 106 ++-- .../inc/com/centreon/broker/misc/string.hh | 24 +- .../com/centreon/broker/processing/feeder.hh | 9 +- .../com/centreon/broker/multiplexing/muxer.hh | 15 +- broker/core/multiplexing/src/muxer.cc | 47 +- broker/core/sql/src/mysql_stmt.cc | 6 +- broker/core/src/misc/string.cc | 258 +--------- broker/core/src/processing/feeder.cc | 27 +- broker/core/test/misc/string.cc | 198 +------ broker/lua/src/broker_utils.cc | 5 +- broker/neb/src/callbacks.cc | 291 +++++------ .../storage/src/conflict_manager_storage.cc | 9 +- broker/unified_sql/src/stream_sql.cc | 59 +-- broker/unified_sql/src/stream_storage.cc | 13 +- common/CMakeLists.txt | 4 +- common/doc/common-doc.md | 4 + .../com/centreon/common/grpc/grpc_config.hh | 31 ++ common/http/src/http_connection.cc | 2 +- common/inc/com/centreon/common/utf8.hh | 49 ++ common/process/CMakeLists.txt | 4 +- .../detail/centreon_posix_process_launcher.hh | 275 ++++++++++ .../com/centreon/common/process}/process.hh | 2 +- common/process/{ => src}/process.cc | 155 ++++-- common/src/utf8.cc | 275 ++++++++++ common/tests/CMakeLists.txt | 1 + common/tests/process_test.cc | 56 +- common/tests/utf8_test.cc | 215 ++++++++ engine/CMakeLists.txt | 2 + engine/modules/opentelemetry/CMakeLists.txt | 32 +- .../opentelemetry/doc/opentelemetry.md | 184 +++++++ .../opentelemetry/doc/otel_configuration.odg | Bin 0 -> 24096 bytes .../agent_check_result_builder.hh | 117 +++++ .../centreon_agent/agent_config.hh | 76 +++ .../centreon_agent/agent_impl.hh | 114 +++++ .../centreon_agent/agent_reverse_client.hh | 62 +++ .../centreon_agent/agent_service.hh | 75 +++ .../centreon_agent/to_agent_connector.hh | 78 +++ .../modules/opentelemetry/conf_helper.hh | 20 +- .../modules/opentelemetry/grpc_config.hh | 8 + .../modules/opentelemetry/open_telemetry.hh | 6 +- .../opentelemetry/otl_check_result_builder.hh | 5 +- .../modules/opentelemetry/otl_config.hh | 7 + .../modules/opentelemetry/otl_data_point.hh | 15 + .../engine/modules/opentelemetry/otl_fmt.hh | 66 +++ .../modules/opentelemetry/otl_server.hh | 26 +- .../opentelemetry/precomp_inc/precomp.hh | 1 + .../agent_check_result_builder.cc | 185 +++++++ .../src/centreon_agent/agent_config.cc | 154 ++++++ .../src/centreon_agent/agent_impl.cc | 446 ++++++++++++++++ .../centreon_agent/agent_reverse_client.cc | 127 +++++ .../src/centreon_agent/agent_service.cc | 162 ++++++ .../src/centreon_agent/to_agent_connector.cc | 223 ++++++++ .../opentelemetry/src/host_serv_extractor.cc | 23 +- .../opentelemetry/src/open_telemetry.cc | 46 +- .../src/otl_check_result_builder.cc | 11 + .../modules/opentelemetry/src/otl_config.cc | 59 ++- .../opentelemetry/src/otl_data_point.cc | 13 + .../modules/opentelemetry/src/otl_server.cc | 32 +- engine/precomp_inc/precomp.hh | 1 + engine/src/configuration/applier/state.cc | 8 +- engine/tests/CMakeLists.txt | 13 +- .../agent_check_result_builder_test.cc | 483 ++++++++++++++++++ .../agent_reverse_client_test.cc | 153 ++++++ .../opentelemetry/agent_to_engine_test.cc | 326 ++++++++++++ .../opentelemetry/open_telemetry_test.cc | 4 +- .../tests/opentelemetry/otl_converter_test.cc | 6 + engine/tests/opentelemetry/otl_server_test.cc | 14 +- engine/tests/test_engine.cc | 22 +- engine/tests/test_engine.hh | 11 +- packaging/centreon-monitoring-agent.yaml | 10 + tests/broker-engine/opentelemetry.robot | 402 +++++++++++++++ tests/init-sql-docker.sh | 4 +- tests/resources/Agent.py | 71 +++ tests/resources/resources.resource | 34 ++ 93 files changed, 6268 insertions(+), 977 deletions(-) create mode 100644 .github/workflows/windows-agent.yml create mode 100644 agent/inc/com/centreon/agent/bireactor.hh create mode 100644 agent/inc/com/centreon/agent/streaming_client.hh create mode 100644 agent/inc/com/centreon/agent/streaming_server.hh create mode 100644 agent/src/bireactor.cc create mode 100644 agent/src/streaming_client.cc create mode 100644 agent/src/streaming_server.cc create mode 100644 common/inc/com/centreon/common/utf8.hh create mode 100644 common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh rename common/{inc/com/centreon/common => process/inc/com/centreon/common/process}/process.hh (99%) rename common/process/{ => src}/process.cc (65%) create mode 100644 common/src/utf8.cc create mode 100644 common/tests/utf8_test.cc create mode 100644 engine/modules/opentelemetry/doc/otel_configuration.odg create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_check_result_builder.hh create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_impl.hh create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_service.hh create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh create mode 100644 engine/modules/opentelemetry/src/centreon_agent/agent_check_result_builder.cc create mode 100644 engine/modules/opentelemetry/src/centreon_agent/agent_config.cc create mode 100644 engine/modules/opentelemetry/src/centreon_agent/agent_impl.cc create mode 100644 engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc create mode 100644 engine/modules/opentelemetry/src/centreon_agent/agent_service.cc create mode 100644 engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc create mode 100644 engine/tests/opentelemetry/agent_check_result_builder_test.cc create mode 100644 engine/tests/opentelemetry/agent_reverse_client_test.cc create mode 100644 engine/tests/opentelemetry/agent_to_engine_test.cc create mode 100644 tests/resources/Agent.py diff --git a/.github/workflows/windows-agent.yml b/.github/workflows/windows-agent.yml new file mode 100644 index 00000000000..c1bbf3bb7e4 --- /dev/null +++ b/.github/workflows/windows-agent.yml @@ -0,0 +1,21 @@ +name: Centreon Monitoring Agent Windows packaging + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: ilammy/msvc-dev-cmd@v1.4.1 + + - name: install vcpkg + run: | + git clone --depth 1 https://github.com/microsoft/vcpkg $HOME/vcpkg + cd $HOME/vcpkg + bootstrap-vcpkg.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index bfdf18cce84..74bcb7aa4b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ option(WITH_MALLOC_TRACE "compile centreon-malloc-trace library." OFF) # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -stdlib=libc++") # set(CMAKE_CXX_COMPILER "clang++") add_definitions("-D_GLIBCXX_USE_CXX11_ABI=1") +add_definitions("-DBOOST_PROCESS_USE_STD_FS=1") option(DEBUG_ROBOT OFF) diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index 7a8ec1a1036..c64801646c4 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -101,6 +101,7 @@ add_custom_command( add_library(centagent_lib STATIC ${SRC_DIR}/agent.grpc.pb.cc ${SRC_DIR}/agent.pb.cc + ${SRC_DIR}/bireactor.cc ${SRC_DIR}/check.cc ${SRC_DIR}/check_exec.cc ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.cc @@ -110,6 +111,8 @@ add_library(centagent_lib STATIC ${SRC_DIR}/opentelemetry/proto/resource/v1/resource.pb.cc ${SRC_DIR}/config.cc ${SRC_DIR}/scheduler.cc + ${SRC_DIR}/streaming_client.cc + ${SRC_DIR}/streaming_server.cc ) include_directories( @@ -117,6 +120,7 @@ include_directories( ${SRC_DIR} ${CMAKE_SOURCE_DIR}/common/inc ${CMAKE_SOURCE_DIR}/common/grpc/inc + ${CMAKE_SOURCE_DIR}/common/process/inc ) target_precompile_headers(centagent_lib PRIVATE precomp_inc/precomp.hh) @@ -135,16 +139,12 @@ target_link_libraries( centreon_process -L${Boost_LIBRARY_DIR_RELEASE} boost_program_options - fmt::fmt) + fmt::fmt + stdc++fs + ) target_precompile_headers(${CENTREON_AGENT} REUSE_FROM centagent_lib) -target_include_directories(${CENTREON_AGENT} PRIVATE - ${INCLUDE_DIR} - ${SRC_DIR} - ${CMAKE_SOURCE_DIR}/common/inc -) - set(AGENT_VAR_LOG_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-monitoring-agent") diff --git a/agent/inc/com/centreon/agent/bireactor.hh b/agent/inc/com/centreon/agent/bireactor.hh new file mode 100644 index 00000000000..16af5594c81 --- /dev/null +++ b/agent/inc/com/centreon/agent/bireactor.hh @@ -0,0 +1,88 @@ +/** + * 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_BIREACTOR_HH +#define CENTREON_AGENT_BIREACTOR_HH + +#include "agent.grpc.pb.h" + +namespace com::centreon::agent { + +template +class bireactor + : public bireactor_class, + public std::enable_shared_from_this> { + private: + static std::set> _instances; + static std::mutex _instances_m; + + bool _write_pending; + std::deque> _write_queue; + std::shared_ptr _read_current; + + const std::string_view _class_name; + + const std::string _peer; + + protected: + std::shared_ptr _io_context; + std::shared_ptr _logger; + + bool _alive; + /** + * @brief All attributes of this object are protected by this mutex + * + */ + mutable std::mutex _protect; + + public: + bireactor(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::string_view& class_name, + const std::string& peer); + + virtual ~bireactor(); + + static void register_stream(const std::shared_ptr& strm); + + void start_read(); + + void start_write(); + void write(const std::shared_ptr& request); + + // bireactor part + void OnReadDone(bool ok) override; + + virtual void on_incomming_request( + const std::shared_ptr& request) = 0; + + virtual void on_error() = 0; + + void OnWriteDone(bool ok) override; + + // server version + void OnDone(); + // client version + void OnDone(const ::grpc::Status& /*s*/); + + virtual void shutdown(); +}; + +} // namespace com::centreon::agent + +#endif diff --git a/agent/inc/com/centreon/agent/check_exec.hh b/agent/inc/com/centreon/agent/check_exec.hh index 42107040c4a..8adb1a35134 100644 --- a/agent/inc/com/centreon/agent/check_exec.hh +++ b/agent/inc/com/centreon/agent/check_exec.hh @@ -20,7 +20,7 @@ #define CENTREON_AGENT_CHECK_EXEC_HH #include "check.hh" -#include "com/centreon/common/process.hh" +#include "com/centreon/common/process/process.hh" namespace com::centreon::agent { diff --git a/agent/inc/com/centreon/agent/config.hh b/agent/inc/com/centreon/agent/config.hh index d0bd774f97a..0cd7b9d4821 100644 --- a/agent/inc/com/centreon/agent/config.hh +++ b/agent/inc/com/centreon/agent/config.hh @@ -1,4 +1,3 @@ - /** * Copyright 2024 Centreon * Licensed under the Apache License, Version 2.0(the "License"); @@ -15,6 +14,7 @@ * * For more information : contact@centreon.com */ + #ifndef CENTREON_AGENT_CONFIG_HH #define CENTREON_AGENT_CONFIG_HH @@ -24,7 +24,7 @@ namespace com::centreon::agent { class config { public: - enum log_type { to_stdout, to_file }; + enum log_type { to_stdout, to_file, to_event_log }; static const std::string_view config_schema; diff --git a/agent/inc/com/centreon/agent/scheduler.hh b/agent/inc/com/centreon/agent/scheduler.hh index fcd3d71a6fa..b1ed36edfbc 100644 --- a/agent/inc/com/centreon/agent/scheduler.hh +++ b/agent/inc/com/centreon/agent/scheduler.hh @@ -57,13 +57,13 @@ class scheduler : public std::enable_shared_from_this { // pointers in this struct point to _current_request struct scope_metric_request { ::opentelemetry::proto::metrics::v1::ScopeMetrics* scope_metric; - absl::flat_hash_map + std::unordered_map metrics; }; // one serv => one scope_metric => several metrics - absl::flat_hash_map _serv_to_scope_metrics; + std::unordered_map _serv_to_scope_metrics; std::shared_ptr _io_context; std::shared_ptr _logger; diff --git a/agent/inc/com/centreon/agent/streaming_client.hh b/agent/inc/com/centreon/agent/streaming_client.hh new file mode 100644 index 00000000000..17fe24ef07b --- /dev/null +++ b/agent/inc/com/centreon/agent/streaming_client.hh @@ -0,0 +1,113 @@ +/** + * 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_STREAMING_CLIENT_HH +#define CENTREON_AGENT_STREAMING_CLIENT_HH + +#include "com/centreon/common/grpc/grpc_client.hh" + +#include "bireactor.hh" +#include "scheduler.hh" + +namespace com::centreon::agent { + +class streaming_client; + +class client_reactor + : public bireactor< + ::grpc::ClientBidiReactor> { + std::weak_ptr _parent; + ::grpc::ClientContext _context; + + public: + client_reactor(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& parent, + const std::string& peer); + + std::shared_ptr shared_from_this() { + return std::static_pointer_cast( + bireactor<::grpc::ClientBidiReactor>:: + shared_from_this()); + } + + ::grpc::ClientContext& get_context() { return _context; } + + void on_incomming_request( + const std::shared_ptr& request) override; + + void on_error() override; + + void shutdown() override; +}; + +/** + * @brief this object not only manages connection to engine, but also embed + * check scheduler + * + */ +class streaming_client : public common::grpc::grpc_client_base, + public std::enable_shared_from_this { + std::shared_ptr _io_context; + std::shared_ptr _logger; + std::string _supervised_host; + + std::unique_ptr _stub; + + std::shared_ptr _reactor; + std::shared_ptr _sched; + + /** + * @brief All attributes of this object are protected by this mutex + * + */ + std::mutex _protect; + + void _create_reactor(); + + void _start(); + + void _send(const std::shared_ptr& request); + + public: + streaming_client(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host); + + static std::shared_ptr load( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host); + + void on_incomming_request(const std::shared_ptr& caller, + const std::shared_ptr& request); + void on_error(const std::shared_ptr& caller); + + void shutdown(); + + // use only for tests + engine_to_agent_request_ptr get_last_message_to_agent() const { + return _sched->get_last_message_to_agent(); + } +}; + +} // namespace com::centreon::agent + +#endif \ No newline at end of file diff --git a/agent/inc/com/centreon/agent/streaming_server.hh b/agent/inc/com/centreon/agent/streaming_server.hh new file mode 100644 index 00000000000..b88a1cb0c3f --- /dev/null +++ b/agent/inc/com/centreon/agent/streaming_server.hh @@ -0,0 +1,77 @@ +/** + * 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_STREAMING_SERVER_HH +#define CENTREON_AGENT_STREAMING_SERVER_HH + +#include "com/centreon/common/grpc/grpc_server.hh" + +#include "bireactor.hh" +#include "scheduler.hh" + +namespace com::centreon::agent { + +class server_reactor; + +/** + * @brief grpc engine to agent server (reverse connection) + * It accept only one connection at a time + * If another connection occurs, previous connection is shutdown + * This object is both grpc server and grpc service + */ +class streaming_server : public common::grpc::grpc_server_base, + public std::enable_shared_from_this, + public ReversedAgentService::Service { + std::shared_ptr _io_context; + std::shared_ptr _logger; + const std::string _supervised_host; + + /** active engine to agent connection*/ + std::shared_ptr _incoming; + + /** + * @brief All attributes of this object are protected by this mutex + * + */ + mutable std::mutex _protect; + + void _start(); + + public: + streaming_server(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host); + + ~streaming_server(); + + static std::shared_ptr load( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host); + + ::grpc::ServerBidiReactor* Import( + ::grpc::CallbackServerContext* context); + + void shutdown(); +}; + +} // namespace com::centreon::agent + +#endif diff --git a/agent/precomp_inc/precomp.hh b/agent/precomp_inc/precomp.hh index f5384ddeb8f..8c9b04fb62a 100644 --- a/agent/precomp_inc/precomp.hh +++ b/agent/precomp_inc/precomp.hh @@ -1,20 +1,19 @@ /** * Copyright 2024 Centreon * - * This file is part of Centreon Agent. + * 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 * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. + * 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. * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * . + * For more information : contact@centreon.com */ #ifndef CA_PRECOMP_HH @@ -31,8 +30,9 @@ #include #include -#include +#include #include +#include #include diff --git a/agent/src/bireactor.cc b/agent/src/bireactor.cc new file mode 100644 index 00000000000..e26346be55c --- /dev/null +++ b/agent/src/bireactor.cc @@ -0,0 +1,207 @@ +/** + * 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 "bireactor.hh" + +using namespace com::centreon::agent; + +/** + * @brief when BiReactor::OnDone is called by grpc layers, we should delete + * this. But this object is even used by others. + * So it's stored in this container and just removed from this container when + * OnDone is called + * + * @tparam bireactor_class + */ +template +std::set>> + bireactor::_instances; + +template +std::mutex bireactor::_instances_m; + +template +bireactor::bireactor( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::string_view& class_name, + const std::string& peer) + : _write_pending(false), + _alive(true), + _class_name(class_name), + _peer(peer), + _io_context(io_context), + _logger(logger) { + SPDLOG_LOGGER_DEBUG(_logger, "create {} this={:p} peer:{}", _class_name, + static_cast(this), _peer); +} + +template +bireactor::~bireactor() { + SPDLOG_LOGGER_DEBUG(_logger, "delete {} this={:p} peer:{}", _class_name, + static_cast(this), _peer); +} + +template +void bireactor::register_stream( + const std::shared_ptr& strm) { + std::lock_guard l(_instances_m); + _instances.insert(strm); +} + +template +void bireactor::start_read() { + std::lock_guard l(_protect); + if (!_alive) { + return; + } + std::shared_ptr to_read; + if (_read_current) { + return; + } + to_read = _read_current = std::make_shared(); + bireactor_class::StartRead(to_read.get()); +} + +template +void bireactor::OnReadDone(bool ok) { + if (ok) { + std::shared_ptr read; + { + std::lock_guard l(_protect); + SPDLOG_LOGGER_TRACE(_logger, "{:p} {} peer {} receive: {}", + static_cast(this), _class_name, _peer, + _read_current->ShortDebugString()); + read = _read_current; + _read_current.reset(); + } + start_read(); + if (read->has_config()) { + on_incomming_request(read); + } + } else { + SPDLOG_LOGGER_ERROR(_logger, "{:p} {} peer:{} fail read from stream", + static_cast(this), _class_name, _peer); + on_error(); + shutdown(); + } +} + +template +void bireactor::write( + const std::shared_ptr& request) { + { + std::lock_guard l(_protect); + if (!_alive) { + return; + } + _write_queue.push_back(request); + } + start_write(); +} + +template +void bireactor::start_write() { + std::shared_ptr to_send; + { + std::lock_guard l(_protect); + if (!_alive || _write_pending || _write_queue.empty()) { + return; + } + to_send = _write_queue.front(); + _write_pending = true; + } + bireactor_class::StartWrite(to_send.get()); +} + +template +void bireactor::OnWriteDone(bool ok) { + if (ok) { + { + std::lock_guard l(_protect); + _write_pending = false; + SPDLOG_LOGGER_TRACE(_logger, "{:p} {} {} sent", + static_cast(this), _class_name, + (*_write_queue.begin())->ShortDebugString()); + _write_queue.pop_front(); + } + start_write(); + } else { + SPDLOG_LOGGER_ERROR(_logger, "{:p} {} peer {} fail write to stream", + static_cast(this), _class_name, _peer); + on_error(); + shutdown(); + } +} + +template +void bireactor::OnDone() { + /**grpc has a bug, sometimes if we delete this class in this handler as it is + * described in examples, it also deletes used channel and does a pthread_join + * of the current thread witch go to a EDEADLOCK error and call grpc::Crash. + * So we uses asio thread to do the job + */ + _io_context->post([me = std::enable_shared_from_this< + bireactor>::shared_from_this(), + &peer = _peer, logger = _logger]() { + std::lock_guard l(_instances_m); + SPDLOG_LOGGER_DEBUG(logger, "{:p} server::OnDone() to {}", + static_cast(me.get()), peer); + _instances.erase(std::static_pointer_cast>(me)); + }); +} + +template +void bireactor::OnDone(const ::grpc::Status& status) { + /**grpc has a bug, sometimes if we delete this class in this handler as it is + * described in examples, it also deletes used channel and does a + * pthread_join of the current thread witch go to a EDEADLOCK error and call + * grpc::Crash. So we uses asio thread to do the job + */ + _io_context->post([me = std::enable_shared_from_this< + bireactor>::shared_from_this(), + status, &peer = _peer, logger = _logger]() { + std::lock_guard l(_instances_m); + if (status.ok()) { + SPDLOG_LOGGER_DEBUG(logger, "{:p} peer: {} client::OnDone({}) {}", + static_cast(me.get()), peer, + status.error_message(), status.error_details()); + } else { + SPDLOG_LOGGER_ERROR(logger, "{:p} peer:{} client::OnDone({}) {}", + static_cast(me.get()), peer, + status.error_message(), status.error_details()); + } + _instances.erase(std::static_pointer_cast>(me)); + }); +} + +template +void bireactor::shutdown() { + SPDLOG_LOGGER_DEBUG(_logger, "{:p} {}::shutdown", static_cast(this), + _class_name); +} + +namespace com::centreon::agent { + +template class bireactor< + ::grpc::ClientBidiReactor>; + +template class bireactor< + ::grpc::ServerBidiReactor>; + +} // namespace com::centreon::agent \ No newline at end of file diff --git a/agent/src/check.cc b/agent/src/check.cc index 562fd0329b2..27c29701f16 100644 --- a/agent/src/check.cc +++ b/agent/src/check.cc @@ -109,7 +109,8 @@ void check::_timeout_timer_handler(const boost::system::error_code& err, return; } if (start_check_index == _running_check_index) { - SPDLOG_LOGGER_ERROR(_logger, "check timeout for service {}", _service); + SPDLOG_LOGGER_ERROR(_logger, "check timeout for service {} cmd: {}", + _service, _command_name); on_completion(start_check_index, 3 /*unknown*/, std::list(), {"Timeout at execution of " + _command_line}); @@ -132,7 +133,8 @@ 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 {}", _service); + SPDLOG_LOGGER_TRACE(_logger, "end check for service {} cmd: {}", _service, + _command_name); _time_out_timer.cancel(); _running_check = false; ++_running_check_index; diff --git a/agent/src/check_exec.cc b/agent/src/check_exec.cc index d38d0deeac9..b26c07ab36b 100644 --- a/agent/src/check_exec.cc +++ b/agent/src/check_exec.cc @@ -44,7 +44,7 @@ void detail::process::start(unsigned running_index) { _stdout_eof = false; _running_index = running_index; _stdout.clear(); - common::process::start_process(); + common::process::start_process(false); } /** @@ -57,7 +57,7 @@ void detail::process::on_stdout_read(const boost::system::error_code& err, size_t nb_read) { if (!err && nb_read > 0) { _stdout.append(_stdout_read_buffer, nb_read); - } else if (err == asio::error::eof) { + } else if (err) { _stdout_eof = true; _on_completion(); } @@ -174,6 +174,7 @@ void check_exec::_init() { } catch (const std::exception& e) { SPDLOG_LOGGER_ERROR(_logger, "fail to create process of cmd_line '{}' : {}", get_command_line(), e.what()); + throw; } } @@ -231,8 +232,11 @@ void check_exec::_timeout_timer_handler(const boost::system::error_code& err, return; } if (start_check_index == _get_running_check_index()) { - check::_timeout_timer_handler(err, start_check_index); _process->kill(); + check::_timeout_timer_handler(err, start_check_index); + } else { + SPDLOG_LOGGER_ERROR(_logger, "start_check_index={}, running_index={}", + start_check_index, _get_running_check_index()); } } @@ -243,6 +247,8 @@ void check_exec::_timeout_timer_handler(const boost::system::error_code& err, */ void check_exec::on_completion(unsigned running_index) { if (running_index != _get_running_check_index()) { + SPDLOG_LOGGER_ERROR(_logger, "running_index={}, running_index={}", + running_index, _get_running_check_index()); return; } diff --git a/agent/src/main.cc b/agent/src/main.cc index 562a1f05e46..e613d749d2f 100644 --- a/agent/src/main.cc +++ b/agent/src/main.cc @@ -21,6 +21,8 @@ #include #include "config.hh" +#include "streaming_client.hh" +#include "streaming_server.hh" using namespace com::centreon::agent; @@ -28,6 +30,9 @@ std::shared_ptr g_io_context = std::make_shared(); std::shared_ptr g_logger; +static std::shared_ptr _streaming_client; + +static std::shared_ptr _streaming_server; static asio::signal_set _signals(*g_io_context, SIGTERM, SIGUSR1, SIGUSR2); @@ -36,9 +41,16 @@ static void signal_handler(const boost::system::error_code& error, if (!error) { switch (signal_number) { case SIGTERM: - SPDLOG_LOGGER_INFO(g_logger, "SIGTERM received"); - g_io_context->stop(); - break; + case SIGINT: + SPDLOG_LOGGER_INFO(g_logger, "SIGTERM or SIGINT received"); + if (_streaming_client) { + _streaming_client->shutdown(); + } + if (_streaming_server) { + _streaming_server->shutdown(); + } + g_io_context->post([]() { g_io_context->stop(); }); + return; case SIGUSR2: SPDLOG_LOGGER_INFO(g_logger, "SIGUSR2 received"); if (g_logger->level()) { @@ -151,6 +163,7 @@ int main(int argc, char* argv[]) { try { // ignored but mandatory because of forks _signals.add(SIGPIPE); + _signals.add(SIGINT); _signals.async_wait(signal_handler); @@ -166,6 +179,14 @@ int main(int argc, char* argv[]) { return -1; } + if (conf->use_reverse_connection()) { + _streaming_server = streaming_server::load(g_io_context, g_logger, + grpc_conf, conf->get_host()); + } else { + _streaming_client = streaming_client::load(g_io_context, g_logger, + grpc_conf, conf->get_host()); + } + try { g_io_context->run(); } catch (const std::exception& e) { diff --git a/agent/src/scheduler.cc b/agent/src/scheduler.cc index 207ef35721e..a08749884c2 100644 --- a/agent/src/scheduler.cc +++ b/agent/src/scheduler.cc @@ -1,23 +1,23 @@ /** * Copyright 2024 Centreon * - * This file is part of Centreon Agent. + * 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 * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. + * 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. * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * . + * For more information : contact@centreon.com */ #include "scheduler.hh" +#include "com/centreon/common/utf8.hh" using namespace com::centreon::agent; @@ -175,16 +175,23 @@ void scheduler::update(const engine_to_agent_request_ptr& conf) { "check expected to start at {} for service {}", next, serv.service_description()); } - _check_queue.emplace(_check_builder( - _io_context, _logger, next, serv.service_description(), - serv.command_name(), serv.command_line(), conf, - [me = shared_from_this()]( - const std::shared_ptr& check, unsigned status, - const std::list& perfdata, - const std::list& outputs) { - me->_check_handler(check, status, perfdata, outputs); - })); - next += check_interval; + try { + auto check_to_schedule = _check_builder( + _io_context, _logger, next, serv.service_description(), + serv.command_name(), serv.command_line(), conf, + [me = shared_from_this()]( + const std::shared_ptr& check, unsigned status, + const std::list& perfdata, + const std::list& outputs) { + me->_check_handler(check, status, perfdata, outputs); + }); + _check_queue.emplace(check_to_schedule); + next += check_interval; + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR(_logger, + "service: {} command:{} won't be scheduled", + serv.service_description(), serv.command_name()); + } } } @@ -318,9 +325,9 @@ void scheduler::_store_result_in_metrics_and_exemplars( if (!outputs.empty()) { const std::string& first_line = *outputs.begin(); size_t pipe_pos = first_line.find('|'); - state_metrics->set_description(pipe_pos != std::string::npos - ? first_line.substr(0, pipe_pos) - : first_line); + state_metrics->set_description(common::check_string_utf8( + pipe_pos != std::string::npos ? first_line.substr(0, pipe_pos) + : first_line)); } auto data_point = state_metrics->mutable_gauge()->add_data_points(); data_point->set_time_unix_nano(now); @@ -402,7 +409,7 @@ void scheduler::_add_metric_to_scope( auto metric = _get_metric(scope_metric, perf.name()); metric->set_unit(perf.unit()); auto data_point = metric->mutable_gauge()->add_data_points(); - data_point->set_as_int(perf.value()); + data_point->set_as_double(perf.value()); data_point->set_time_unix_nano(now); switch (perf.value_type()) { case com::centreon::common::perfdata::counter: { diff --git a/agent/src/streaming_client.cc b/agent/src/streaming_client.cc new file mode 100644 index 00000000000..5fa122c83cd --- /dev/null +++ b/agent/src/streaming_client.cc @@ -0,0 +1,230 @@ +/** + * 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 "streaming_client.hh" +#include "check_exec.hh" +#include "com/centreon/clib/version.hh" +#include "com/centreon/common/defer.hh" + +using namespace com::centreon::agent; + +/** + * @brief Construct a new client reactor::client reactor object + * + * @param io_context + * @param parent we will keep a weak_ptr on streaming_client object + */ +client_reactor::client_reactor( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + + const std::shared_ptr& parent, + const std::string& peer) + : bireactor<::grpc::ClientBidiReactor>( + io_context, + logger, + "client", + peer), + _parent(parent) {} + +/** + * @brief pass request to streaming_client parent + * + * @param request + */ +void client_reactor::on_incomming_request( + const std::shared_ptr& request) { + std::shared_ptr parent = _parent.lock(); + if (!parent) { + shutdown(); + } else { + parent->on_incomming_request(shared_from_this(), request); + } +} + +/** + * @brief called whe OnReadDone or OnWriteDone ok parameter is false + * + */ +void client_reactor::on_error() { + std::shared_ptr parent = _parent.lock(); + if (parent) { + parent->on_error(shared_from_this()); + } +} + +/** + * @brief shutdown connection to engine if not yet done + * + */ +void client_reactor::shutdown() { + std::lock_guard l(_protect); + if (_alive) { + _alive = false; + bireactor<::grpc::ClientBidiReactor>::shutdown(); + RemoveHold(); + _context.TryCancel(); + } +} + +/** + * @brief Construct a new streaming client::streaming client object + * not use it, use load instead + * + * @param io_context + * @param conf + * @param supervised_hosts + */ +streaming_client::streaming_client( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host) + : com::centreon::common::grpc::grpc_client_base(conf, logger), + _io_context(io_context), + _logger(logger), + _supervised_host(supervised_host) { + _stub = std::move(AgentService::NewStub(_channel)); +} + +/** + * @brief to call after construction + * + */ +void streaming_client::_start() { + std::weak_ptr weak_this = shared_from_this(); + + _sched = scheduler::load( + _io_context, _logger, _supervised_host, scheduler::default_config(), + [sender = std::move(weak_this)]( + const std::shared_ptr& request) { + auto parent = sender.lock(); + if (parent) { + parent->_send(request); + } + }, + check_exec::load); + _create_reactor(); +} + +/** + * @brief create reactor on current grpc channel + * and send agent infos (hostname, supervised hosts, collect version) + * + */ +void streaming_client::_create_reactor() { + std::lock_guard l(_protect); + if (_reactor) { + _reactor->shutdown(); + } + _reactor = std::make_shared( + _io_context, _logger, shared_from_this(), get_conf()->get_hostport()); + client_reactor::register_stream(_reactor); + _stub->async()->Export(&_reactor->get_context(), _reactor.get()); + _reactor->start_read(); + _reactor->AddHold(); + _reactor->StartCall(); + + // identifies to engine + std::shared_ptr who_i_am = + std::make_shared(); + auto infos = who_i_am->mutable_init(); + + infos->mutable_centreon_version()->set_major( + com::centreon::clib::version::major); + infos->mutable_centreon_version()->set_minor( + com::centreon::clib::version::minor); + infos->mutable_centreon_version()->set_patch( + com::centreon::clib::version::patch); + + infos->set_host(_supervised_host); + + _reactor->write(who_i_am); +} + +/** + * @brief construct a new streaming_client + * + * @param io_context + * @param conf + * @param supervised_hosts list of host to supervise (match to engine config) + * @return std::shared_ptr + */ +std::shared_ptr streaming_client::load( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host) { + std::shared_ptr ret = std::make_shared( + io_context, logger, conf, supervised_host); + ret->_start(); + return ret; +} + +/** + * @brief send a request to engine + * + * @param request + */ +void streaming_client::_send(const std::shared_ptr& request) { + std::lock_guard l(_protect); + if (_reactor) + _reactor->write(request); +} + +/** + * @brief + * + * @param caller + * @param request + */ +void streaming_client::on_incomming_request( + const std::shared_ptr& caller, + const std::shared_ptr& request) { + // incoming request is used in main thread + _io_context->post([request, sched = _sched]() { sched->update(request); }); +} + +/** + * @brief called by _reactor when something was wrong + * Then we wait 10s to reconnect to engine + * + * @param caller + */ +void streaming_client::on_error(const std::shared_ptr& caller) { + std::lock_guard l(_protect); + if (caller == _reactor) { + _reactor.reset(); + common::defer(_io_context, std::chrono::seconds(10), + [me = shared_from_this()] { me->_create_reactor(); }); + } +} + +/** + * @brief stop and shutdown scheduler and connection + * After, this object is dead and must be deleted + * + */ +void streaming_client::shutdown() { + std::lock_guard l(_protect); + _sched->stop(); + if (_reactor) { + _reactor->shutdown(); + } +} diff --git a/agent/src/streaming_server.cc b/agent/src/streaming_server.cc new file mode 100644 index 00000000000..cfc23fabb11 --- /dev/null +++ b/agent/src/streaming_server.cc @@ -0,0 +1,237 @@ +/** + * 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 "streaming_server.hh" +#include "check_exec.hh" +#include "com/centreon/clib/version.hh" +#include "scheduler.hh" + +using namespace com::centreon::agent; + +namespace com::centreon::agent { + +class server_reactor + : public bireactor< + ::grpc::ServerBidiReactor> { + std::shared_ptr _sched; + std::string _supervised_host; + + void _start(); + + public: + server_reactor(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::string& supervised_hosts, + const std::string& peer); + + static std::shared_ptr load( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::string& supervised_hosts, + const std::string& peer); + + std::shared_ptr shared_from_this() { + return std::static_pointer_cast( + bireactor<::grpc::ServerBidiReactor>:: + shared_from_this()); + } + + void on_incomming_request( + const std::shared_ptr& request) override; + + void on_error() override; + + void shutdown() override; +}; + +server_reactor::server_reactor( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::string& supervised_host, + const std::string& peer) + : bireactor<::grpc::ServerBidiReactor>( + io_context, + logger, + "server", + peer), + _supervised_host(supervised_host) {} + +void server_reactor::_start() { + std::weak_ptr weak_this(shared_from_this()); + + _sched = scheduler::load( + _io_context, _logger, _supervised_host, scheduler::default_config(), + [sender = std::move(weak_this)]( + const std::shared_ptr& request) { + auto parent = sender.lock(); + if (parent) { + parent->write(request); + } + }, + check_exec::load); + + // identifies to engine + std::shared_ptr who_i_am = + std::make_shared(); + auto infos = who_i_am->mutable_init(); + + infos->mutable_centreon_version()->set_major( + com::centreon::clib::version::major); + infos->mutable_centreon_version()->set_minor( + com::centreon::clib::version::minor); + infos->mutable_centreon_version()->set_patch( + com::centreon::clib::version::patch); + infos->set_host(_supervised_host); + + write(who_i_am); +} + +std::shared_ptr server_reactor::load( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::string& supervised_host, + const std::string& peer) { + std::shared_ptr ret = std::make_shared( + io_context, logger, supervised_host, peer); + ret->_start(); + return ret; +} + +void server_reactor::on_incomming_request( + const std::shared_ptr& request) { + _io_context->post([sched = _sched, request]() { sched->update(request); }); +} + +void server_reactor::on_error() { + shutdown(); +} + +void server_reactor::shutdown() { + std::lock_guard l(_protect); + if (_alive) { + _alive = false; + _sched->stop(); + bireactor<::grpc::ServerBidiReactor>::shutdown(); + Finish(::grpc::Status::CANCELLED); + } +} + +} // namespace com::centreon::agent + +/** + * @brief Construct a new streaming server::streaming server object + * Not use it, use load instead + * @param io_context + * @param conf + * @param supervised_hosts list of supervised hosts that will be sent to engine + * in order to have checks configuration + */ +streaming_server::streaming_server( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host) + : com::centreon::common::grpc::grpc_server_base(conf, logger), + _io_context(io_context), + _logger(logger), + _supervised_host(supervised_host) { + SPDLOG_LOGGER_INFO(_logger, "create grpc server listening on {}", + conf->get_hostport()); +} + +streaming_server::~streaming_server() { + SPDLOG_LOGGER_INFO(_logger, "delete grpc server listening on {}", + get_conf()->get_hostport()); +} + +/** + * @brief register service and start grpc server + * + */ +void streaming_server::_start() { + ::grpc::Service::MarkMethodCallback( + 0, new ::grpc::internal::CallbackBidiHandler< + ::com::centreon::agent::MessageToAgent, + ::com::centreon::agent::MessageFromAgent>( + [me = shared_from_this()](::grpc::CallbackServerContext* context) { + return me->Import(context); + })); + + _init([this](::grpc::ServerBuilder& builder) { + builder.RegisterService(this); + }); +} + +/** + * @brief construct and start a new streaming_server + * + * @param io_context + * @param conf + * @param supervised_hosts list of supervised hosts that will be sent to engine + * in order to have checks configuration + * @return std::shared_ptr + */ +std::shared_ptr streaming_server::load( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const std::shared_ptr& conf, + const std::string& supervised_host) { + std::shared_ptr ret = std::make_shared( + io_context, logger, conf, supervised_host); + ret->_start(); + return ret; +} + +/** + * @brief shutdown server and incoming connection + * + */ +void streaming_server::shutdown() { + SPDLOG_LOGGER_INFO(_logger, "shutdown grpc server listening on {}", + get_conf()->get_hostport()); + { + std::lock_guard l(_protect); + if (_incoming) { + _incoming->shutdown(); + _incoming.reset(); + } + } + common::grpc::grpc_server_base::shutdown(std::chrono::seconds(10)); +} + +/** + * @brief callback called on incoming connection + * + * @param context + * @return ::grpc::ServerBidiReactor* = + * _incoming + */ +::grpc::ServerBidiReactor* +streaming_server::Import(::grpc::CallbackServerContext* context) { + SPDLOG_LOGGER_INFO(_logger, "incoming connection from {}", context->peer()); + std::lock_guard l(_protect); + if (_incoming) { + _incoming->shutdown(); + } + _incoming = server_reactor::load(_io_context, _logger, _supervised_host, + context->peer()); + server_reactor::register_stream(_incoming); + _incoming->start_read(); + return _incoming.get(); +} diff --git a/agent/test/check_exec_test.cc b/agent/test/check_exec_test.cc index 34c050f48e0..c0a6ef1278c 100644 --- a/agent/test/check_exec_test.cc +++ b/agent/test/check_exec_test.cc @@ -32,6 +32,7 @@ TEST(check_exec_test, echo) { command_line = "/bin/echo hello toto"; int status; std::list outputs; + std::mutex mut; std::condition_variable cond; std::shared_ptr check = check_exec::load( g_io_context, spdlog::default_logger(), time_point(), serv, cmd_name, @@ -40,13 +41,15 @@ TEST(check_exec_test, echo) { int statuss, const std::list& perfdata, const std::list& output) { - status = statuss; - outputs = output; + { + std::lock_guard l(mut); + status = statuss; + outputs = output; + } cond.notify_one(); }); check->start_check(std::chrono::seconds(1)); - std::mutex mut; std::unique_lock l(mut); cond.wait(l); ASSERT_EQ(status, 0); @@ -98,7 +101,8 @@ TEST(check_exec_test, bad_command) { status = statuss; outputs = output; } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + SPDLOG_INFO("end of {}", command_line); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); cond.notify_one(); }); check->start_check(std::chrono::seconds(1)); diff --git a/agent/test/scheduler_test.cc b/agent/test/scheduler_test.cc index ccd9f47a7fc..a7ac335382e 100644 --- a/agent/test/scheduler_test.cc +++ b/agent/test/scheduler_test.cc @@ -59,6 +59,7 @@ class tempo_check : public check { void start_check(const duration& timeout) override { { std::lock_guard l(check_starts_m); + SPDLOG_INFO("start tempo check"); check_starts.emplace_back(this, std::chrono::system_clock::now()); } check::start_check(timeout); @@ -148,6 +149,19 @@ TEST_F(scheduler_test, no_config) { ASSERT_FALSE(weak_shed.lock()); } +static bool tempo_check_assert_pred(const time_point& after, + const time_point& before) { + if ((after - before) <= std::chrono::milliseconds(40)) { + SPDLOG_ERROR("after={}, before={}", after, before); + return false; + } + if ((after - before) >= std::chrono::milliseconds(60)) { + SPDLOG_ERROR("after={}, before={}", after, before); + return false; + } + return true; +} + TEST_F(scheduler_test, correct_schedule) { std::shared_ptr sched = scheduler::load( g_io_context, spdlog::default_logger(), "my_host", @@ -185,10 +199,8 @@ TEST_F(scheduler_test, correct_schedule) { first = false; } else { ASSERT_NE(previous.first, check_time.first); - ASSERT_GT((check_time.second - previous.second), - expected_interval - std::chrono::milliseconds(1)); - ASSERT_LT((check_time.second - previous.second), - expected_interval + std::chrono::milliseconds(1)); + ASSERT_PRED2(tempo_check_assert_pred, check_time.second, + previous.second); } previous = check_time; } @@ -206,10 +218,8 @@ TEST_F(scheduler_test, correct_schedule) { first = false; } else { ASSERT_NE(previous.first, check_time.first); - ASSERT_TRUE((check_time.second - previous.second) > - expected_interval - std::chrono::milliseconds(1)); - ASSERT_TRUE((check_time.second - previous.second) < - expected_interval + std::chrono::milliseconds(1)); + ASSERT_PRED2(tempo_check_assert_pred, check_time.second, + previous.second); } previous = check_time; } @@ -306,7 +316,7 @@ TEST_F(scheduler_test, correct_output_examplar) { ASSERT_TRUE(exported_request); - SPDLOG_INFO("export:{}", exported_request->otel_request().DebugString()); + SPDLOG_INFO("export:{}", exported_request->otel_request().ShortDebugString()); ASSERT_EQ(exported_request->otel_request().resource_metrics_size(), 2); const ::opentelemetry::proto::metrics::v1::ResourceMetrics& res = @@ -447,4 +457,4 @@ TEST_F(scheduler_test, max_concurent) { ASSERT_EQ(concurent_check::checked.size(), 200); sched->stop(); -} \ No newline at end of file +} diff --git a/broker/bam/src/reporting_stream.cc b/broker/bam/src/reporting_stream.cc index 6fd5a8e8903..e159484af9e 100644 --- a/broker/bam/src/reporting_stream.cc +++ b/broker/bam/src/reporting_stream.cc @@ -34,9 +34,9 @@ #include "com/centreon/broker/bam/ba.hh" #include "com/centreon/broker/exceptions/shutdown.hh" #include "com/centreon/broker/io/events.hh" -#include "com/centreon/broker/misc/string.hh" #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/broker/time/timezone_manager.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" #include "common/log_v2/log_v2.hh" @@ -543,25 +543,25 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_i32(0, dk.kpi_id); binder.set_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name))); binder.set_value_as_i32(2, dk.ba_id); binder.set_value_as_str( 3, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name))); binder.set_value_as_i32(4, dk.host_id); binder.set_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( dk.host_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name))); binder.set_value_as_i32(6, dk.service_id); binder.set_value_as_str( 7, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description))); @@ -570,14 +570,14 @@ struct bulk_dimension_kpi_binder { else binder.set_null_i32(8); binder.set_value_as_str( - 9, misc::string::truncate( + 9, com::centreon::common::truncate_utf8( dk.kpi_ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name))); binder.set_value_as_i32(10, dk.meta_service_id); binder.set_value_as_str( 11, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name))); @@ -586,7 +586,7 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_f32(14, dk.impact_unknown); binder.set_value_as_i32(15, dk.boolean_id); binder.set_value_as_str( - 16, misc::string::truncate( + 16, com::centreon::common::truncate_utf8( dk.boolean_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -612,25 +612,25 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_i32(0, dk.kpi_id()); binder.set_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name))); binder.set_value_as_i32(2, dk.ba_id()); binder.set_value_as_str( - 3, misc::string::truncate( + 3, com::centreon::common::truncate_utf8( dk.ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name))); binder.set_value_as_i32(4, dk.host_id()); binder.set_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( dk.host_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name))); binder.set_value_as_i32(6, dk.service_id()); binder.set_value_as_str( 7, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description))); @@ -639,14 +639,14 @@ struct bulk_dimension_kpi_binder { else binder.set_null_i32(8); binder.set_value_as_str( - 9, misc::string::truncate( + 9, com::centreon::common::truncate_utf8( dk.kpi_ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name))); binder.set_value_as_i32(10, dk.meta_service_id()); binder.set_value_as_str( 11, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name))); @@ -655,7 +655,7 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_f32(14, dk.impact_unknown()); binder.set_value_as_i32(15, dk.boolean_id()); binder.set_value_as_str( - 16, misc::string::truncate( + 16, com::centreon::common::truncate_utf8( dk.boolean_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -691,36 +691,36 @@ struct dimension_kpi_binder { return fmt::format( "({},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},{},{},{},'{}')", dk.kpi_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name)), dk.ba_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name)), dk.host_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.host_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name)), dk.service_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description)), sz_kpi_ba_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.kpi_ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name)), dk.meta_service_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name)), dk.impact_warning, dk.impact_critical, dk.impact_unknown, dk.boolean_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.boolean_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -747,37 +747,37 @@ struct dimension_kpi_binder { return fmt::format( "({},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},{},{},{},'{}')", dk.kpi_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name)), dk.ba_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name)), dk.host_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.host_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name)), dk.service_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description)), sz_kpi_ba_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.kpi_ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name)), dk.meta_service_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name)), dk.impact_warning(), dk.impact_critical(), dk.impact_unknown(), dk.boolean_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.boolean_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -1455,11 +1455,11 @@ void reporting_stream::_process_dimension_ba( dba.ba_id, dba.ba_description); _dimension_ba_insert.bind_value_as_i32(0, dba.ba_id); _dimension_ba_insert.bind_value_as_str( - 1, misc::string::truncate( + 1, com::centreon::common::truncate_utf8( dba.ba_name, get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_name))); _dimension_ba_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dba.ba_description, get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_description))); @@ -1485,11 +1485,11 @@ void reporting_stream::_process_pb_dimension_ba( _dimension_ba_insert.bind_value_as_i32(0, dba.ba_id()); _dimension_ba_insert.bind_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( dba.ba_name(), get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_name))); _dimension_ba_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dba.ba_description(), get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_description))); @@ -1514,11 +1514,11 @@ void reporting_stream::_process_dimension_bv( _dimension_bv_insert.bind_value_as_i32(0, dbv.bv_id); _dimension_bv_insert.bind_value_as_str( - 1, misc::string::truncate( + 1, com::centreon::common::truncate_utf8( dbv.bv_name, get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_name))); _dimension_bv_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dbv.bv_description, get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_description))); @@ -1541,11 +1541,11 @@ void reporting_stream::_process_pb_dimension_bv( _dimension_bv_insert.bind_value_as_i32(0, dbv.bv_id()); _dimension_bv_insert.bind_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( dbv.bv_name(), get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_name))); _dimension_bv_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dbv.bv_description(), get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_description))); @@ -1896,42 +1896,42 @@ void reporting_stream::_process_pb_dimension_timeperiod( tp.id(), tp.name()); _dimension_timeperiod_insert.bind_value_as_i32(0, tp.id()); _dimension_timeperiod_insert.bind_value_as_str( - 1, misc::string::truncate( + 1, com::centreon::common::truncate_utf8( tp.name(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_name))); _dimension_timeperiod_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( tp.sunday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_sunday))); _dimension_timeperiod_insert.bind_value_as_str( - 3, misc::string::truncate( + 3, com::centreon::common::truncate_utf8( tp.monday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_monday))); _dimension_timeperiod_insert.bind_value_as_str( - 4, misc::string::truncate( + 4, com::centreon::common::truncate_utf8( tp.tuesday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_tuesday))); _dimension_timeperiod_insert.bind_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( tp.wednesday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_wednesday))); _dimension_timeperiod_insert.bind_value_as_str( - 6, misc::string::truncate( + 6, com::centreon::common::truncate_utf8( tp.thursday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_thursday))); _dimension_timeperiod_insert.bind_value_as_str( - 7, misc::string::truncate( + 7, com::centreon::common::truncate_utf8( tp.friday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_friday))); _dimension_timeperiod_insert.bind_value_as_str( - 8, misc::string::truncate( + 8, com::centreon::common::truncate_utf8( tp.saturday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_saturday))); @@ -1958,41 +1958,41 @@ void reporting_stream::_process_dimension_timeperiod( _dimension_timeperiod_insert.bind_value_as_i32(0, tp.id); _dimension_timeperiod_insert.bind_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( tp.name, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_name))); _dimension_timeperiod_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( tp.sunday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_sunday))); _dimension_timeperiod_insert.bind_value_as_str( - 3, misc::string::truncate( + 3, com::centreon::common::truncate_utf8( tp.monday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_monday))); _dimension_timeperiod_insert.bind_value_as_str( - 4, misc::string::truncate( + 4, com::centreon::common::truncate_utf8( tp.tuesday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_tuesday))); _dimension_timeperiod_insert.bind_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( tp.wednesday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_wednesday))); _dimension_timeperiod_insert.bind_value_as_str( - 6, misc::string::truncate( + 6, com::centreon::common::truncate_utf8( tp.thursday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_thursday))); _dimension_timeperiod_insert.bind_value_as_str( - 7, misc::string::truncate( + 7, com::centreon::common::truncate_utf8( tp.friday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_friday))); _dimension_timeperiod_insert.bind_value_as_str( - 8, misc::string::truncate( + 8, com::centreon::common::truncate_utf8( tp.saturday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_saturday))); diff --git a/broker/core/inc/com/centreon/broker/misc/string.hh b/broker/core/inc/com/centreon/broker/misc/string.hh index 2ee2db16d8e..03c234bdcaf 100644 --- a/broker/core/inc/com/centreon/broker/misc/string.hh +++ b/broker/core/inc/com/centreon/broker/misc/string.hh @@ -24,7 +24,8 @@ #include namespace com::centreon::broker::misc::string { -inline std::string& replace(std::string& str, std::string const& old_str, +inline std::string& replace(std::string& str, + std::string const& old_str, std::string const& new_str) { std::size_t pos(str.find(old_str, 0)); while (pos != std::string::npos) { @@ -37,28 +38,7 @@ inline std::string& replace(std::string& str, std::string const& old_str, std::string& trim(std::string& str) throw(); std::string base64_encode(std::string const& str); bool is_number(const std::string& s); -std::string check_string_utf8(const std::string& str) noexcept; -/** - * @brief This function works almost like the resize method but takes care - * of the UTF-8 encoding and avoids to cut a string in the middle of a - * character. This function assumes the string to be UTF-8 encoded. - * - * @param str A string to truncate. - * @param s The desired size, maybe the resulting string will contain less - * characters. - * - * @return a reference to the string str. - */ -template -fmt::string_view truncate(const T& str, size_t s) { - if (s >= str.size()) return fmt::string_view(str); - if (s > 0) - while ((str[s] & 0xc0) == 0x80) s--; - return fmt::string_view(str.data(), s); -} - -size_t adjust_size_utf8(const std::string& str, size_t s); std::string escape(const std::string& str, size_t s); std::string debug_buf(const char* data, int32_t size, int max_len = 10); diff --git a/broker/core/inc/com/centreon/broker/processing/feeder.hh b/broker/core/inc/com/centreon/broker/processing/feeder.hh index 71e6636b11c..4ccfcd90ea7 100644 --- a/broker/core/inc/com/centreon/broker/processing/feeder.hh +++ b/broker/core/inc/com/centreon/broker/processing/feeder.hh @@ -39,6 +39,7 @@ namespace processing { * Take events from a source and send them to a destination. */ class feeder : public stat_visitable, + public multiplexing::muxer::data_handler, public std::enable_shared_from_this { enum class state : unsigned { running, finished }; // Condition variable used when waiting for the thread to finish @@ -63,6 +64,8 @@ class feeder : public stat_visitable, const multiplexing::muxer_filter& read_filters, const multiplexing::muxer_filter& write_filters); + void init(); + const std::string& _get_read_filters() const override; const std::string& _get_write_filters() const override; void _forward_statistic(nlohmann::json& tree) override; @@ -74,9 +77,6 @@ class feeder : public stat_visitable, void _start_read_from_stream_timer(); void _read_from_stream_timer_handler(const boost::system::error_code& err); - unsigned _write_to_client( - const std::vector>& events); - void _stop_no_lock(); void _ack_events_on_muxer(uint32_t count) noexcept; @@ -98,6 +98,9 @@ class feeder : public stat_visitable, bool is_finished() const noexcept; bool wait_for_all_events_written(unsigned ms_timeout); + + uint32_t on_events( + const std::vector>& events) override; }; } // namespace processing diff --git a/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh b/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh index bc7b6d959a2..82887bce2af 100644 --- a/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh +++ b/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh @@ -51,6 +51,14 @@ namespace com::centreon::broker::multiplexing { * @see engine */ class muxer : public io::stream, public std::enable_shared_from_this { + public: + class data_handler { + public: + virtual ~data_handler() = default; + virtual uint32_t on_events( + const std::vector>& events) = 0; + }; + private: static uint32_t _event_queue_max_size; @@ -63,7 +71,7 @@ class muxer : public io::stream, public std::enable_shared_from_this { std::string _write_filters_str; const bool _persistent; - std::function>&)> _data_handler; + std::shared_ptr _data_handler; std::atomic_bool _reader_running = false; /** Events are stacked into _events or into _file. Because several threads @@ -139,9 +147,8 @@ class muxer : public io::stream, public std::enable_shared_from_this { void set_write_filter(const muxer_filter& w_filter); void clear_read_handler(); void unsubscribe(); - void set_action_on_new_data( - std::function>)>&& - data_handler) ABSL_LOCKS_EXCLUDED(_events_m); + void set_action_on_new_data(const std::shared_ptr& handler) + ABSL_LOCKS_EXCLUDED(_events_m); void clear_action_on_new_data() ABSL_LOCKS_EXCLUDED(_events_m); }; diff --git a/broker/core/multiplexing/src/muxer.cc b/broker/core/multiplexing/src/muxer.cc index c81a955206e..2c3250ea32a 100644 --- a/broker/core/multiplexing/src/muxer.cc +++ b/broker/core/multiplexing/src/muxer.cc @@ -311,28 +311,34 @@ uint32_t muxer::event_queue_max_size() noexcept { * execute the data handler. */ void muxer::_execute_reader_if_needed() { - _logger->debug("muxer '{}' execute reader if needed data_handler: {}", _name, - static_cast(_data_handler)); - if (_data_handler) { - bool expected = false; - if (_reader_running.compare_exchange_strong(expected, true)) { - com::centreon::common::pool::io_context_ptr()->post( - [me = shared_from_this()] { + SPDLOG_LOGGER_DEBUG( + _logger, "muxer '{}' execute reader if needed data_handler", _name); + bool expected = false; + if (_reader_running.compare_exchange_strong(expected, true)) { + com::centreon::common::pool::io_context_ptr()->post( + [me = shared_from_this(), this] { + std::shared_ptr to_call; + { + absl::MutexLock lck(&_events_m); + to_call = _data_handler; + } + if (to_call) { std::vector> to_fill; - to_fill.reserve(me->_events_size); - bool still_events_to_read = me->read(to_fill, me->_events_size); - uint32_t written = me->_data_handler(to_fill); + to_fill.reserve(_events_size); + bool still_events_to_read = read(to_fill, _events_size); + uint32_t written = to_call->on_events(to_fill); if (written > 0) - me->ack_events(written); + ack_events(written); if (written != to_fill.size()) { - me->_logger->error( + SPDLOG_LOGGER_ERROR( + _logger, "Unable to handle all the incoming events in muxer '{}'", - me->_name); - me->clear_action_on_new_data(); + _name); + clear_action_on_new_data(); } - me->_reader_running.store(false); - }); - } + _reader_running.store(false); + } + }); } } @@ -784,13 +790,12 @@ void muxer::unsubscribe() { } void muxer::set_action_on_new_data( - std::function>)>&& - data_handler) { + const std::shared_ptr& handler) { absl::MutexLock lck(&_events_m); - _data_handler = data_handler; + _data_handler = handler; } void muxer::clear_action_on_new_data() { absl::MutexLock lck(&_events_m); - _data_handler = nullptr; + _data_handler.reset(); } diff --git a/broker/core/sql/src/mysql_stmt.cc b/broker/core/sql/src/mysql_stmt.cc index c3222c0510f..728e8d4473d 100644 --- a/broker/core/sql/src/mysql_stmt.cc +++ b/broker/core/sql/src/mysql_stmt.cc @@ -24,7 +24,7 @@ #include "com/centreon/broker/io/events.hh" #include "com/centreon/broker/io/protobuf.hh" #include "com/centreon/broker/mapping/entry.hh" -#include "com/centreon/broker/misc/string.hh" +#include "com/centreon/common/utf8.hh" using namespace com::centreon::exceptions; using namespace com::centreon::broker; @@ -166,7 +166,7 @@ void mysql_stmt::operator<<(io::data const& d) { "column '{}' should admit a longer string, it is cut to {} " "characters to be stored anyway.", current_entry->get_name_v2(), max_len); - max_len = misc::string::adjust_size_utf8(v, max_len); + max_len = common::adjust_size_utf8(v, max_len); sv = fmt::string_view(v.data(), max_len); } else sv = fmt::string_view(v); @@ -283,7 +283,7 @@ void mysql_stmt::operator<<(io::data const& d) { "column '{}' should admit a longer string, it is cut to {} " "characters to be stored anyway.", field, max_len); - max_len = misc::string::adjust_size_utf8(v, max_len); + max_len = common::adjust_size_utf8(v, max_len); sv = fmt::string_view(v.data(), max_len); } else sv = fmt::string_view(v); diff --git a/broker/core/src/misc/string.cc b/broker/core/src/misc/string.cc index 263f6cbe9fd..4bb8fa5f4d6 100644 --- a/broker/core/src/misc/string.cc +++ b/broker/core/src/misc/string.cc @@ -17,6 +17,7 @@ */ #include "com/centreon/broker/misc/string.hh" +#include "com/centreon/common/utf8.hh" #include @@ -74,259 +75,6 @@ bool string::is_number(const std::string& s) { }) == s.end(); } -/** - * @brief Checks if the string given as parameter is a real UTF-8 string. - * If it is not, it tries to convert it to UTF-8. Encodings correctly changed - * are ISO-8859-15 and CP-1252. - * - * @param str The string to check - * - * @return The string itself or a new string converted to UTF-8. The output - * string should always be an UTF-8 string. - */ -std::string string::check_string_utf8(std::string const& str) noexcept { - std::string::const_iterator it; - for (it = str.begin(); it != str.end();) { - uint32_t val = (*it & 0xff); - if ((val & 0x80) == 0) { - ++it; - continue; - } - val = (val << 8) | (*(it + 1) & 0xff); - if ((val & 0xe0c0) == 0xc080) { - val &= 0x1e00; - if (val == 0) - break; - it += 2; - continue; - } - - val = (val << 8) | (*(it + 2) & 0xff); - if ((val & 0xf0c0c0) == 0xe08080) { - val &= 0xf2000; - if (val == 0 || val == 0xd2000) - break; - it += 3; - continue; - } - - val = (val << 8) | (*(it + 3) & 0xff); - if ((val & 0xf8c0c0c0) == 0xF0808080) { - val &= 0x7300000; - if (val == 0 || val > 0x4000000) - break; - it += 4; - continue; - } - break; - } - - if (it == str.end()) - return str; - - /* Not an UTF-8 string */ - bool is_cp1252 = true, is_iso8859 = true; - auto itt = it; - - auto iso8859_to_utf8 = [&str, &it]() -> std::string { - /* Strings are both cp1252 and iso8859-15 */ - std::string out; - std::size_t d = it - str.begin(); - out.reserve(d + 2 * (str.size() - d)); - out = str.substr(0, d); - while (it != str.end()) { - uint8_t c = static_cast(*it); - if (c < 128) - out.push_back(c); - else if (c <= 160) - out.push_back('_'); - else { - switch (c) { - case 0xa4: - out.append("€"); - break; - case 0xa6: - out.append("Š"); - break; - case 0xa8: - out.append("š"); - break; - case 0xb4: - out.append("Ž"); - break; - case 0xb8: - out.append("ž"); - break; - case 0xbc: - out.append("Œ"); - break; - case 0xbd: - out.append("œ"); - break; - case 0xbe: - out.append("Ÿ"); - break; - default: - out.push_back(0xc0 | c >> 6); - out.push_back((c & 0x3f) | 0x80); - break; - } - } - ++it; - } - return out; - }; - do { - uint8_t c = *itt; - /* not ISO-8859-15 */ - if (c > 126 && c < 160) - is_iso8859 = false; - /* not cp1252 */ - if (c & 128) - if (c == 129 || c == 141 || c == 143 || c == 144 || c == 155) - is_cp1252 = false; - if (!is_cp1252) - return iso8859_to_utf8(); - else if (!is_iso8859) { - std::string out; - std::size_t d = it - str.begin(); - out.reserve(d + 3 * (str.size() - d)); - out = str.substr(0, d); - while (it != str.end()) { - c = *it; - if (c < 128) - out.push_back(c); - else { - switch (c) { - case 128: - out.append("€"); - break; - case 129: - case 141: - case 143: - case 144: - case 157: - out.append("_"); - break; - case 130: - out.append("‚"); - break; - case 131: - out.append("ƒ"); - break; - case 132: - out.append("„"); - break; - case 133: - out.append("…"); - break; - case 134: - out.append("†"); - break; - case 135: - out.append("‡"); - break; - case 136: - out.append("ˆ"); - break; - case 137: - out.append("‰"); - break; - case 138: - out.append("Š"); - break; - case 139: - out.append("‹"); - break; - case 140: - out.append("Œ"); - break; - case 142: - out.append("Ž"); - break; - case 145: - out.append("‘"); - break; - case 146: - out.append("’"); - break; - case 147: - out.append("“"); - break; - case 148: - out.append("”"); - break; - case 149: - out.append("•"); - break; - case 150: - out.append("–"); - break; - case 151: - out.append("—"); - break; - case 152: - out.append("˜"); - break; - case 153: - out.append("™"); - break; - case 154: - out.append("š"); - break; - case 155: - out.append("›"); - break; - case 156: - out.append("œ"); - break; - case 158: - out.append("ž"); - break; - case 159: - out.append("Ÿ"); - break; - default: - out.push_back(0xc0 | c >> 6); - out.push_back((c & 0x3f) | 0x80); - break; - } - } - ++it; - } - return out; - } - ++itt; - } while (itt != str.end()); - assert(is_cp1252 == is_iso8859); - return iso8859_to_utf8(); -} - -/** - * @brief This function adjusts the given integer s so that the str string may - * be cut at this length and still be a UTF-8 string (we don't want to cut it - * in a middle of a character). - * - * This function assumes the string to be UTF-8 encoded. - * - * @param str A string to truncate. - * @param s The desired size, maybe the resulting string will contain less - * characters. - * - * @return The newly computed size. - */ -size_t string::adjust_size_utf8(const std::string& str, size_t s) { - if (s >= str.size()) - return str.size(); - if (s == 0) - return s; - else { - while ((str[s] & 0xc0) == 0x80) - s--; - return s; - } -} - /** * @brief Escape the given string so that it can be directly inserted into the * database. Essntially, characters \ and ' are prefixed with \. The function @@ -340,7 +88,7 @@ size_t string::adjust_size_utf8(const std::string& str, size_t s) { std::string string::escape(const std::string& str, size_t s) { size_t found = str.find_first_of("'\\"); if (found == std::string::npos) - return str.substr(0, adjust_size_utf8(str, s)); + return str.substr(0, common::adjust_size_utf8(str, s)); else { std::string ret; /* ret is reserved with the worst size */ @@ -362,7 +110,7 @@ std::string string::escape(const std::string& str, size_t s) { ret += str[ffound]; found = ffound; } while (found < s); - ret.resize(adjust_size_utf8(ret, s)); + ret.resize(common::adjust_size_utf8(ret, s)); if (ret.size() > 1) { auto it = --ret.end(); size_t nb{0}; diff --git a/broker/core/src/processing/feeder.cc b/broker/core/src/processing/feeder.cc index a433cfcb232..a032eebcf40 100644 --- a/broker/core/src/processing/feeder.cc +++ b/broker/core/src/processing/feeder.cc @@ -56,12 +56,21 @@ std::shared_ptr feeder::create( std::shared_ptr ret( new feeder(name, parent, client, read_filters, write_filters)); - ret->_start_stat_timer(); - - ret->_start_read_from_stream_timer(); + ret->init(); return ret; } +/** + * @brief to call after object construction + * + */ +void feeder::init() { + _start_stat_timer(); + _muxer->set_action_on_new_data(shared_from_this()); + + _start_read_from_stream_timer(); +} + /** * Constructor. * @@ -91,10 +100,6 @@ feeder::feeder(const std::string& name, if (!_client) throw msg_fmt("could not process '{}' with no client stream", _name); - _muxer->set_action_on_new_data( - [this](const std::vector>& events) -> uint32_t { - return _write_to_client(events); - }); set_last_connection_attempt(timestamp::now()); set_last_connection_success(timestamp::now()); set_state("connected"); @@ -146,11 +151,10 @@ void feeder::_forward_statistic(nlohmann::json& tree) { /** * @brief write event to client stream - * _protect must be locked * @param event * @return number of events written */ -unsigned feeder::_write_to_client( +uint32_t feeder::on_events( const std::vector>& events) { unsigned written = 0; try { @@ -242,11 +246,6 @@ void feeder::_stop_no_lock() { _name); _muxer->remove_queue_files(); SPDLOG_LOGGER_INFO(_logger, "feeder: {} terminated", _name); - - /* The muxer is in a shared_ptr. When the feeder is destroyed, we must be - * sure the muxer won't write data anymore otherwise we will have a segfault. - */ - _muxer->clear_action_on_new_data(); } /** diff --git a/broker/core/test/misc/string.cc b/broker/core/test/misc/string.cc index 947157ba219..cf18b6edf3f 100644 --- a/broker/core/test/misc/string.cc +++ b/broker/core/test/misc/string.cc @@ -23,6 +23,7 @@ #include #include "com/centreon/broker/misc/misc.hh" +#include "com/centreon/common/utf8.hh" using namespace com::centreon::broker::misc; @@ -56,201 +57,6 @@ TEST(StringBase64, Encode) { ASSERT_EQ(string::base64_encode("ABC"), "QUJD"); } -/* - * Given a string encoded in ISO-8859-15 and CP-1252 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, simple) { - std::string txt("L'acc\350s \340 l'h\364tel est encombr\351"); - ASSERT_EQ(string::check_string_utf8(txt), "L'accès à l'hôtel est encombré"); -} - -/* - * Given a string encoded in UTF-8 - * Then the check_string_utf8 function returns itself. - */ -TEST(string_check_utf8, utf8) { - std::string txt("L'accès à l'hôtel est encombré"); - ASSERT_EQ(string::check_string_utf8(txt), "L'accès à l'hôtel est encombré"); -} - -/* - * Given a string encoded in CP-1252 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, cp1252) { - std::string txt("Le ticket co\xfbte 12\x80\n"); - ASSERT_EQ(string::check_string_utf8(txt), "Le ticket coûte 12€\n"); -} - -/* - * Given a string encoded in ISO-8859-15 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, iso8859) { - std::string txt("Le ticket co\xfbte 12\xa4\n"); - ASSERT_EQ(string::check_string_utf8(txt), "Le ticket coûte 12€\n"); -} - -/* - * Given a string encoded in ISO-8859-15 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, iso8859_cpx) { - std::string txt("\xa4\xa6\xa8\xb4\xb8\xbc\xbd\xbe"); - ASSERT_EQ(string::check_string_utf8(txt), "€ŠšŽžŒœŸ"); -} - -/* - * Given a string encoded in CP-1252 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, cp1252_cpx) { - std::string txt("\x80\x95\x82\x89\x8a"); - ASSERT_EQ(string::check_string_utf8(txt), "€•‚‰Š"); -} - -/* - * Given a string badly encoded in CP-1252 - * Then the check_string_utf8 function converts it to UTF-8 and replaces bad - * characters into '_'. - */ -TEST(string_check_utf8, whatever_as_cp1252) { - std::string txt; - for (uint8_t c = 32; c < 255; c++) - if (c != 127) - txt.push_back(c); - std::string result( - " !\"#$%&'()*+,-./" - "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" - "abcdefghijklmnopqrstuvwxyz{|}~€_‚ƒ„…†‡ˆ‰Š‹Œ_Ž__‘’“”•–—˜™š›œ_" - "žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäå" - "æçèéêëìíîïðñòóôõö÷øùúûüýþ"); - ASSERT_EQ(string::check_string_utf8(txt), result); -} - -/* - * Given a string badly encoded in ISO-8859-15 - * Then the check_string_utf8 function converts it to UTF-8 and replaces bad - * characters into '_'. - */ -TEST(string_check_utf8, whatever_as_iso8859) { - /* Construction of a string that is not cp1252 so it should be considered as - * iso8859-15 */ - std::string txt; - for (uint8_t c = 32; c < 255; c++) { - if (c == 32) - txt.push_back(0x81); - if (c != 127) - txt.push_back(c); - } - std::string result( - "_ " - "!\"#$%&'()*+,-./" - "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" - "abcdefghijklmnopqrstuvwxyz{|}~_________________________________" - "¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçè" - "éêëìíîïðñòóôõö÷øùúûüýþ"); - ASSERT_EQ(string::check_string_utf8(txt), result); -} - -/* - * In case of a string containing multiple encoding, the resulting string should - * be an UTF-8 string. Here we have a string beginning with UTF-8 and finishing - * with cp1252. The resulting string is good and is UTF-8 only encoded. - */ -TEST(string_check_utf8, utf8_and_cp1252) { - std::string txt( - "\xc3\xa9\xc3\xa7\xc3\xa8\xc3\xa0\xc3\xb9\xc3\xaf\xc3\xab\x7e\x23\x0a\xe9" - "\xe7\xe8\xe0\xf9\xef\xeb\x7e\x23\x0a"); - std::string result("éçèàùïë~#\néçèàùïë~#\n"); - ASSERT_EQ(string::check_string_utf8(txt), result); -} - -/* A check coming from windows with characters from the cmd console */ -TEST(string_check_utf8, strange_string) { - std::string txt( - "WARNING - [Triggered by _ItemCount>0] - 1 event(s) of Severity Level: " - "\"Error\", were recorded in the last 24 hours from the Application " - "Event Log. (List is on next line. Fields shown are - " - "Logfile:TimeGenerated:EventId:EventCode:SeverityLevel:Type:SourceName:" - "Message)|'Event " - "Count'=1;0;50;\nApplication:20200806000001.000000-000:3221243278:17806:" - "Erreur:MSSQLSERVER:╔chec de la nÚgociation SSPI avec le code " - "d'erreurá0x8009030c lors de l'Útablissement d'une connexion avec une " - "sÚcuritÚ intÚgrÚeá; la connexion a ÚtÚ fermÚe. [CLIENTá: X.X.X.X]"); - ASSERT_EQ(string::check_string_utf8(txt), txt); -} - -/* A check coming from windows with characters from the cmd console */ -TEST(string_check_utf8, chinese) { - std::string txt("超级杀手死亡检查"); - ASSERT_EQ(string::check_string_utf8(txt), txt); -} - -/* A check coming from windows with characters from the cmd console */ -TEST(string_check_utf8, vietnam) { - std::string txt( - "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" - "ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong " - "chinese 告警数量 output puté! | '告警数量'=42\navé dé long ouput oçi " - "还有中国人! Hái yǒu zhòng guó rén!"); - ASSERT_EQ(string::check_string_utf8(txt), txt); -} - -TEST(truncate, nominal1) { - std::string str("foobar"); - ASSERT_EQ(string::truncate(str, 3), "foo"); -} - -TEST(truncate, nominal2) { - std::string str("foobar"); - ASSERT_EQ(string::truncate(str, 0), ""); -} - -TEST(truncate, nominal3) { - std::string str("foobar 超级杀手死亡检查"); - ASSERT_EQ(string::truncate(str, 1000), "foobar 超级杀手死亡检查"); -} - -TEST(truncate, utf8_1) { - std::string str("告警数量"); - for (size_t i = 0; i <= str.size(); i++) { - fmt::string_view tmp(str); - fmt::string_view res(string::truncate(tmp, i)); - std::string tmp1( - string::check_string_utf8(std::string(res.data(), res.size()))); - ASSERT_EQ(res, tmp1); - } -} - -TEST(adjust_size_utf8, nominal1) { - std::string str("foobar"); - ASSERT_EQ(fmt::string_view(str.data(), string::adjust_size_utf8(str, 3)), - fmt::string_view("foo")); -} - -TEST(adjust_size_utf8, nominal2) { - std::string str("foobar"); - ASSERT_EQ(fmt::string_view(str.data(), string::adjust_size_utf8(str, 0)), ""); -} - -TEST(adjust_size_utf8, nominal3) { - std::string str("foobar 超级杀手死亡检查"); - ASSERT_EQ(fmt::string_view(str.data(), string::adjust_size_utf8(str, 1000)), - str); -} - -TEST(adjust_size_utf8, utf8_1) { - std::string str("告警数量"); - for (size_t i = 0; i <= str.size(); i++) { - fmt::string_view sv(str.data(), string::adjust_size_utf8(str, i)); - std::string tmp(string::check_string_utf8( - std::string(sv.data(), sv.data() + sv.size()))); - ASSERT_EQ(sv.size(), tmp.size()); - } -} - TEST(escape, simple) { ASSERT_EQ("Hello", string::escape("Hello", 10)); ASSERT_EQ("Hello", string::escape("Hello", 5)); @@ -261,7 +67,7 @@ TEST(escape, utf8) { std::string str("告'警'数\\量"); std::string res("告\\'警\\'数\\\\量"); std::string res1(res); - res1.resize(string::adjust_size_utf8(res, 10)); + res1.resize(com::centreon::common::adjust_size_utf8(res, 10)); ASSERT_EQ(res, string::escape(str, 20)); ASSERT_EQ(res1, string::escape(str, 10)); } diff --git a/broker/lua/src/broker_utils.cc b/broker/lua/src/broker_utils.cc index e5a7f9792af..01e8a30fec1 100644 --- a/broker/lua/src/broker_utils.cc +++ b/broker/lua/src/broker_utils.cc @@ -40,6 +40,7 @@ #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/common/hex_dump.hh" #include "com/centreon/common/perfdata.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" #include "common/log_v2/log_v2.hh" @@ -654,10 +655,10 @@ static int l_broker_parse_perfdata(lua_State* L) { com::centreon::common::perfdata::parse_perfdata(0, 0, perf_data, logger)}; lua_createtable(L, 0, pds.size()); for (auto& pd : pds) { - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(com::centreon::common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(com::centreon::common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); diff --git a/broker/neb/src/callbacks.cc b/broker/neb/src/callbacks.cc index a586a6b6d49..783db2a2609 100644 --- a/broker/neb/src/callbacks.cc +++ b/broker/neb/src/callbacks.cc @@ -28,13 +28,13 @@ #include "com/centreon/broker/config/applier/state.hh" #include "com/centreon/broker/config/parser.hh" #include "com/centreon/broker/config/state.hh" -#include "com/centreon/broker/misc/string.hh" #include "com/centreon/broker/neb/callback.hh" #include "com/centreon/broker/neb/events.hh" #include "com/centreon/broker/neb/initial.hh" #include "com/centreon/broker/neb/internal.hh" #include "com/centreon/broker/neb/set_log_data.hh" #include "com/centreon/common/time.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/engine/anomalydetection.hh" #include "com/centreon/engine/broker.hh" #include "com/centreon/engine/comment.hh" @@ -178,9 +178,9 @@ int neb::callback_acknowledgement(int callback_type, void* data) { ack_data = static_cast(data); ack->acknowledgement_type = short(ack_data->acknowledgement_type); if (ack_data->author_name) - ack->author = misc::string::check_string_utf8(ack_data->author_name); + ack->author = common::check_string_utf8(ack_data->author_name); if (ack_data->comment_data) - ack->comment = misc::string::check_string_utf8(ack_data->comment_data); + ack->comment = common::check_string_utf8(ack_data->comment_data); ack->entry_time = time(nullptr); if (!ack_data->host_id) throw msg_fmt("unnamed host"); @@ -246,10 +246,9 @@ int neb::callback_pb_acknowledgement(int callback_type [[maybe_unused]], ack_obj.set_type(static_cast( ack_data->acknowledgement_type)); if (ack_data->author_name) - ack_obj.set_author(misc::string::check_string_utf8(ack_data->author_name)); + ack_obj.set_author(common::check_string_utf8(ack_data->author_name)); if (ack_data->comment_data) - ack_obj.set_comment_data( - misc::string::check_string_utf8(ack_data->comment_data)); + ack_obj.set_comment_data(common::check_string_utf8(ack_data->comment_data)); ack_obj.set_entry_time(time(nullptr)); if (!ack_data->host_id) { SPDLOG_LOGGER_ERROR(neb_logger, @@ -301,11 +300,9 @@ int neb::callback_comment(int callback_type, void* data) { // Fill output var. comment_data = static_cast(data); if (comment_data->author_name) - comment->author = - misc::string::check_string_utf8(comment_data->author_name); + comment->author = common::check_string_utf8(comment_data->author_name); if (comment_data->comment_data) - comment->data = - misc::string::check_string_utf8(comment_data->comment_data); + comment->data = common::check_string_utf8(comment_data->comment_data); comment->comment_type = comment_data->comment_type; if (NEBTYPE_COMMENT_DELETE == comment_data->type) comment->deletion_time = time(nullptr); @@ -373,11 +370,9 @@ int neb::callback_pb_comment(int, void* data) { // Fill output var. if (comment_data->author_name) - comment.set_author( - misc::string::check_string_utf8(comment_data->author_name)); + comment.set_author(common::check_string_utf8(comment_data->author_name)); if (comment_data->comment_data) - comment.set_data( - misc::string::check_string_utf8(comment_data->comment_data)); + comment.set_data(common::check_string_utf8(comment_data->comment_data)); comment.set_type( (comment_data->comment_type == com::centreon::engine::comment::type::host) ? com::centreon::broker::Comment_Type_HOST @@ -475,7 +470,7 @@ int neb::callback_pb_custom_variable(int, void* data) { if (hst && !hst->name().empty()) { uint64_t host_id = engine::get_host_id(hst->name()); if (host_id != 0) { - std::string name(misc::string::check_string_utf8(cvar->var_name)); + std::string name(common::check_string_utf8(cvar->var_name)); bool add = NEBTYPE_HOSTCUSTOMVARIABLE_ADD == cvar->type; obj.set_enabled(add); obj.set_host_id(host_id); @@ -484,7 +479,7 @@ int neb::callback_pb_custom_variable(int, void* data) { obj.set_type(com::centreon::broker::CustomVariable_VarType_HOST); obj.set_update_time(cvar->timestamp.tv_sec); if (add) { - std::string value(misc::string::check_string_utf8(cvar->var_value)); + std::string value(common::check_string_utf8(cvar->var_value)); obj.set_value(value); obj.set_default_value(value); SPDLOG_LOGGER_DEBUG( @@ -510,7 +505,7 @@ int neb::callback_pb_custom_variable(int, void* data) { p = engine::get_host_and_service_id(svc->get_hostname(), svc->description()); if (p.first && p.second) { - std::string name(misc::string::check_string_utf8(cvar->var_name)); + std::string name(common::check_string_utf8(cvar->var_name)); bool add = NEBTYPE_SERVICECUSTOMVARIABLE_ADD == cvar->type; obj.set_enabled(add); obj.set_host_id(p.first); @@ -520,7 +515,7 @@ int neb::callback_pb_custom_variable(int, void* data) { obj.set_type(com::centreon::broker::CustomVariable_VarType_SERVICE); obj.set_update_time(cvar->timestamp.tv_sec); if (add) { - std::string value(misc::string::check_string_utf8(cvar->var_value)); + std::string value(common::check_string_utf8(cvar->var_value)); obj.set_value(value); obj.set_default_value(value); SPDLOG_LOGGER_DEBUG( @@ -583,12 +578,12 @@ int neb::callback_custom_variable(int callback_type, void* data) { new_cvar->enabled = true; new_cvar->host_id = host_id; new_cvar->modified = false; - new_cvar->name = misc::string::check_string_utf8(cvar->var_name); + new_cvar->name = common::check_string_utf8(cvar->var_name); new_cvar->var_type = 0; new_cvar->update_time = cvar->timestamp.tv_sec; - new_cvar->value = misc::string::check_string_utf8(cvar->var_value); + new_cvar->value = common::check_string_utf8(cvar->var_value); new_cvar->default_value = - misc::string::check_string_utf8(cvar->var_value); + common::check_string_utf8(cvar->var_value); // Send custom variable event. SPDLOG_LOGGER_DEBUG( @@ -605,7 +600,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { auto old_cvar{std::make_shared()}; old_cvar->enabled = false; old_cvar->host_id = host_id; - old_cvar->name = misc::string::check_string_utf8(cvar->var_name); + old_cvar->name = common::check_string_utf8(cvar->var_name); old_cvar->var_type = 0; old_cvar->update_time = cvar->timestamp.tv_sec; @@ -632,13 +627,13 @@ int neb::callback_custom_variable(int callback_type, void* data) { new_cvar->enabled = true; new_cvar->host_id = p.first; new_cvar->modified = false; - new_cvar->name = misc::string::check_string_utf8(cvar->var_name); + new_cvar->name = common::check_string_utf8(cvar->var_name); new_cvar->service_id = p.second; new_cvar->var_type = 1; new_cvar->update_time = cvar->timestamp.tv_sec; - new_cvar->value = misc::string::check_string_utf8(cvar->var_value); + new_cvar->value = common::check_string_utf8(cvar->var_value); new_cvar->default_value = - misc::string::check_string_utf8(cvar->var_value); + common::check_string_utf8(cvar->var_value); // Send custom variable event. SPDLOG_LOGGER_DEBUG( @@ -659,7 +654,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { old_cvar->enabled = false; old_cvar->host_id = p.first; old_cvar->modified = true; - old_cvar->name = misc::string::check_string_utf8(cvar->var_name); + old_cvar->name = common::check_string_utf8(cvar->var_name); old_cvar->service_id = p.second; old_cvar->var_type = 1; old_cvar->update_time = cvar->timestamp.tv_sec; @@ -1016,11 +1011,10 @@ int neb::callback_downtime(int callback_type, void* data) { // Fill output var. if (downtime_data->author_name) - downtime->author = - misc::string::check_string_utf8(downtime_data->author_name); + downtime->author = common::check_string_utf8(downtime_data->author_name); if (downtime_data->comment_data) downtime->comment = - misc::string::check_string_utf8(downtime_data->comment_data); + common::check_string_utf8(downtime_data->comment_data); downtime->downtime_type = downtime_data->downtime_type; downtime->duration = downtime_data->duration; downtime->end_time = downtime_data->end_time; @@ -1108,11 +1102,10 @@ int neb::callback_pb_downtime(int callback_type, void* data) { // Fill output var. if (downtime_data->author_name) - downtime.set_author( - misc::string::check_string_utf8(downtime_data->author_name)); + downtime.set_author(common::check_string_utf8(downtime_data->author_name)); if (downtime_data->comment_data) downtime.set_comment_data( - misc::string::check_string_utf8(downtime_data->comment_data)); + common::check_string_utf8(downtime_data->comment_data)); downtime.set_id(downtime_data->downtime_id); downtime.set_type( static_cast(downtime_data->downtime_type)); @@ -1198,7 +1191,7 @@ int neb::callback_external_command(int callback_type, void* data) { // Split argument string. if (necd->command_args) { std::list l{absl::StrSplit( - misc::string::check_string_utf8(necd->command_args), ';')}; + common::check_string_utf8(necd->command_args), ';')}; if (l.size() != 3) SPDLOG_LOGGER_ERROR( neb_logger, "callbacks: invalid host custom variable command"); @@ -1235,7 +1228,7 @@ int neb::callback_external_command(int callback_type, void* data) { // Split argument string. if (necd->command_args) { std::list l{absl::StrSplit( - misc::string::check_string_utf8(necd->command_args), ';')}; + common::check_string_utf8(necd->command_args), ';')}; if (l.size() != 4) SPDLOG_LOGGER_ERROR( neb_logger, @@ -1297,8 +1290,8 @@ int neb::callback_pb_external_command(int, void* data) { nebstruct_external_command_data* necd( static_cast(data)); if (necd && (necd->type == NEBTYPE_EXTERNALCOMMAND_START)) { - auto args = absl::StrSplit( - misc::string::check_string_utf8(necd->command_args), ';'); + auto args = + absl::StrSplit(common::check_string_utf8(necd->command_args), ';'); size_t args_size = std::distance(args.begin(), args.end()); auto split_iter = args.begin(); if (necd->command_type == CMD_CHANGE_CUSTOM_HOST_VAR) { @@ -1415,8 +1408,7 @@ int neb::callback_group(int callback_type, void* data) { new_hg->id = host_group->get_id(); new_hg->enabled = (group_data->type != NEBTYPE_HOSTGROUP_DELETE && !host_group->members.empty()); - new_hg->name = - misc::string::check_string_utf8(host_group->get_group_name()); + new_hg->name = common::check_string_utf8(host_group->get_group_name()); // Send host group event. if (new_hg->id) { @@ -1447,7 +1439,7 @@ int neb::callback_group(int callback_type, void* data) { new_sg->enabled = (group_data->type != NEBTYPE_SERVICEGROUP_DELETE && !service_group->members.empty()); new_sg->name = - misc::string::check_string_utf8(service_group->get_group_name()); + common::check_string_utf8(service_group->get_group_name()); // Send service group event. if (new_sg->id) { @@ -1511,7 +1503,7 @@ int neb::callback_pb_group(int callback_type, void* data) { NEBTYPE_HOSTGROUP_DELETE && !host_group->members.empty()); new_hg->mut_obj().set_name( - misc::string::check_string_utf8(host_group->get_group_name())); + common::check_string_utf8(host_group->get_group_name())); // Send host group event. if (host_group->get_id()) { @@ -1549,7 +1541,7 @@ int neb::callback_pb_group(int callback_type, void* data) { NEBTYPE_SERVICEGROUP_DELETE && !service_group->members.empty()); new_sg->mut_obj().set_name( - misc::string::check_string_utf8(service_group->get_group_name())); + common::check_string_utf8(service_group->get_group_name())); // Send service group event. if (service_group->get_id()) { @@ -1607,7 +1599,7 @@ int neb::callback_group_member(int callback_type, void* data) { // Output variable. auto hgm{std::make_shared()}; hgm->group_id = hg->get_id(); - hgm->group_name = misc::string::check_string_utf8(hg->get_group_name()); + hgm->group_name = common::check_string_utf8(hg->get_group_name()); hgm->poller_id = config::applier::state::instance().poller_id(); uint32_t host_id = engine::get_host_id(hst->name()); if (host_id != 0 && hgm->group_id != 0) { @@ -1645,7 +1637,7 @@ int neb::callback_group_member(int callback_type, void* data) { // Output variable. auto sgm{std::make_shared()}; sgm->group_id = sg->get_id(); - sgm->group_name = misc::string::check_string_utf8(sg->get_group_name()); + sgm->group_name = common::check_string_utf8(sg->get_group_name()); sgm->poller_id = config::applier::state::instance().poller_id(); std::pair p; p = engine::get_host_and_service_id(svc->get_hostname(), @@ -1717,7 +1709,7 @@ int neb::callback_pb_group_member(int callback_type, void* data) { auto hgmp{std::make_shared()}; HostGroupMember& hgm = hgmp->mut_obj(); hgm.set_hostgroup_id(hg->get_id()); - hgm.set_name(misc::string::check_string_utf8(hg->get_group_name())); + hgm.set_name(common::check_string_utf8(hg->get_group_name())); hgm.set_poller_id(config::applier::state::instance().poller_id()); uint32_t host_id = engine::get_host_id(hst->name()); if (host_id != 0 && hgm.hostgroup_id() != 0) { @@ -1757,7 +1749,7 @@ int neb::callback_pb_group_member(int callback_type, void* data) { auto sgmp{std::make_shared()}; ServiceGroupMember& sgm = sgmp->mut_obj(); sgm.set_servicegroup_id(sg->get_id()); - sgm.set_name(misc::string::check_string_utf8(sg->get_group_name())); + sgm.set_name(common::check_string_utf8(sg->get_group_name())); sgm.set_poller_id(config::applier::state::instance().poller_id()); std::pair p; p = engine::get_host_and_service_id(svc->get_hostname(), @@ -1822,17 +1814,15 @@ int neb::callback_host(int callback_type, void* data) { my_host->acknowledged = h->problem_has_been_acknowledged(); my_host->acknowledgement_type = h->get_acknowledgement(); if (!h->get_action_url().empty()) - my_host->action_url = - misc::string::check_string_utf8(h->get_action_url()); + my_host->action_url = common::check_string_utf8(h->get_action_url()); my_host->active_checks_enabled = h->active_checks_enabled(); if (!h->get_address().empty()) - my_host->address = misc::string::check_string_utf8(h->get_address()); + my_host->address = common::check_string_utf8(h->get_address()); if (!h->get_alias().empty()) - my_host->alias = misc::string::check_string_utf8(h->get_alias()); + my_host->alias = common::check_string_utf8(h->get_alias()); my_host->check_freshness = h->check_freshness_enabled(); if (!h->check_command().empty()) - my_host->check_command = - misc::string::check_string_utf8(h->check_command()); + my_host->check_command = common::check_string_utf8(h->check_command()); my_host->check_interval = h->check_interval(); if (!h->check_period().empty()) my_host->check_period = h->check_period(); @@ -1847,12 +1837,10 @@ int neb::callback_host(int callback_type, void* data) { my_host->default_passive_checks_enabled = h->passive_checks_enabled(); my_host->downtime_depth = h->get_scheduled_downtime_depth(); if (!h->get_display_name().empty()) - my_host->display_name = - misc::string::check_string_utf8(h->get_display_name()); + my_host->display_name = common::check_string_utf8(h->get_display_name()); my_host->enabled = (host_data->type != NEBTYPE_HOST_DELETE); if (!h->event_handler().empty()) - my_host->event_handler = - misc::string::check_string_utf8(h->event_handler()); + my_host->event_handler = common::check_string_utf8(h->event_handler()); my_host->event_handler_enabled = h->event_handler_enabled(); my_host->execution_time = h->get_execution_time(); my_host->first_notification_delay = h->get_first_notification_delay(); @@ -1868,13 +1856,12 @@ int neb::callback_host(int callback_type, void* data) { my_host->has_been_checked = h->has_been_checked(); my_host->high_flap_threshold = h->get_high_flap_threshold(); if (!h->name().empty()) - my_host->host_name = misc::string::check_string_utf8(h->name()); + my_host->host_name = common::check_string_utf8(h->name()); if (!h->get_icon_image().empty()) - my_host->icon_image = - misc::string::check_string_utf8(h->get_icon_image()); + my_host->icon_image = common::check_string_utf8(h->get_icon_image()); if (!h->get_icon_image_alt().empty()) my_host->icon_image_alt = - misc::string::check_string_utf8(h->get_icon_image_alt()); + common::check_string_utf8(h->get_icon_image_alt()); my_host->is_flapping = h->get_is_flapping(); my_host->last_check = h->get_last_check(); my_host->last_hard_state = h->get_last_hard_state(); @@ -1892,9 +1879,9 @@ int neb::callback_host(int callback_type, void* data) { my_host->next_notification = h->get_next_notification(); my_host->no_more_notifications = h->get_no_more_notifications(); if (!h->get_notes().empty()) - my_host->notes = misc::string::check_string_utf8(h->get_notes()); + my_host->notes = common::check_string_utf8(h->get_notes()); if (!h->get_notes_url().empty()) - my_host->notes_url = misc::string::check_string_utf8(h->get_notes_url()); + my_host->notes_url = common::check_string_utf8(h->get_notes_url()); my_host->notifications_enabled = h->get_notifications_enabled(); my_host->notification_interval = h->get_notification_interval(); if (!h->notification_period().empty()) @@ -1908,16 +1895,16 @@ int neb::callback_host(int callback_type, void* data) { h->get_notify_on(engine::notifier::unreachable); my_host->obsess_over = h->obsess_over(); if (!h->get_plugin_output().empty()) { - my_host->output = misc::string::check_string_utf8(h->get_plugin_output()); + my_host->output = common::check_string_utf8(h->get_plugin_output()); my_host->output.append("\n"); } if (!h->get_long_plugin_output().empty()) my_host->output.append( - misc::string::check_string_utf8(h->get_long_plugin_output())); + common::check_string_utf8(h->get_long_plugin_output())); my_host->passive_checks_enabled = h->passive_checks_enabled(); my_host->percent_state_change = h->get_percent_state_change(); if (!h->get_perf_data().empty()) - my_host->perf_data = misc::string::check_string_utf8(h->get_perf_data()); + my_host->perf_data = common::check_string_utf8(h->get_perf_data()); my_host->poller_id = config::applier::state::instance().poller_id(); my_host->retain_nonstatus_information = h->get_retain_nonstatus_information(); @@ -1932,7 +1919,7 @@ int neb::callback_host(int callback_type, void* data) { (h->has_been_checked() ? h->get_state_type() : engine::notifier::hard); if (!h->get_statusmap_image().empty()) my_host->statusmap_image = - misc::string::check_string_utf8(h->get_statusmap_image()); + common::check_string_utf8(h->get_statusmap_image()); my_host->timezone = h->get_timezone(); // Find host ID. @@ -1999,11 +1986,9 @@ int neb::callback_pb_host(int callback_type, void* data) { else if (dh->modified_attribute & MODATTR_OBSESSIVE_HANDLER_ENABLED) hst.set_obsess_over_host(eh->obsess_over()); else if (dh->modified_attribute & MODATTR_EVENT_HANDLER_COMMAND) - hst.set_event_handler( - misc::string::check_string_utf8(eh->event_handler())); + hst.set_event_handler(common::check_string_utf8(eh->event_handler())); else if (dh->modified_attribute & MODATTR_CHECK_COMMAND) - hst.set_check_command( - misc::string::check_string_utf8(eh->check_command())); + hst.set_check_command(common::check_string_utf8(eh->check_command())); else if (dh->modified_attribute & MODATTR_NORMAL_CHECK_INTERVAL) hst.set_check_interval(eh->check_interval()); else if (dh->modified_attribute & MODATTR_RETRY_CHECK_INTERVAL) @@ -2044,17 +2029,15 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_acknowledged(eh->problem_has_been_acknowledged()); host.set_acknowledgement_type(eh->get_acknowledgement()); if (!eh->get_action_url().empty()) - host.set_action_url( - misc::string::check_string_utf8(eh->get_action_url())); + host.set_action_url(common::check_string_utf8(eh->get_action_url())); host.set_active_checks(eh->active_checks_enabled()); if (!eh->get_address().empty()) - host.set_address(misc::string::check_string_utf8(eh->get_address())); + host.set_address(common::check_string_utf8(eh->get_address())); if (!eh->get_alias().empty()) - host.set_alias(misc::string::check_string_utf8(eh->get_alias())); + host.set_alias(common::check_string_utf8(eh->get_alias())); host.set_check_freshness(eh->check_freshness_enabled()); if (!eh->check_command().empty()) - host.set_check_command( - misc::string::check_string_utf8(eh->check_command())); + host.set_check_command(common::check_string_utf8(eh->check_command())); host.set_check_interval(eh->check_interval()); if (!eh->check_period().empty()) host.set_check_period(eh->check_period()); @@ -2070,13 +2053,11 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_default_passive_checks(eh->passive_checks_enabled()); host.set_scheduled_downtime_depth(eh->get_scheduled_downtime_depth()); if (!eh->get_display_name().empty()) - host.set_display_name( - misc::string::check_string_utf8(eh->get_display_name())); + host.set_display_name(common::check_string_utf8(eh->get_display_name())); host.set_enabled(static_cast(data)->type != NEBTYPE_HOST_DELETE); if (!eh->event_handler().empty()) - host.set_event_handler( - misc::string::check_string_utf8(eh->event_handler())); + host.set_event_handler(common::check_string_utf8(eh->event_handler())); host.set_event_handler_enabled(eh->event_handler_enabled()); host.set_execution_time(eh->get_execution_time()); host.set_first_notification_delay(eh->get_first_notification_delay()); @@ -2092,13 +2073,12 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_checked(eh->has_been_checked()); host.set_high_flap_threshold(eh->get_high_flap_threshold()); if (!eh->name().empty()) - host.set_name(misc::string::check_string_utf8(eh->name())); + host.set_name(common::check_string_utf8(eh->name())); if (!eh->get_icon_image().empty()) - host.set_icon_image( - misc::string::check_string_utf8(eh->get_icon_image())); + host.set_icon_image(common::check_string_utf8(eh->get_icon_image())); if (!eh->get_icon_image_alt().empty()) host.set_icon_image_alt( - misc::string::check_string_utf8(eh->get_icon_image_alt())); + common::check_string_utf8(eh->get_icon_image_alt())); host.set_flapping(eh->get_is_flapping()); host.set_last_check(eh->get_last_check()); host.set_last_hard_state( @@ -2117,9 +2097,9 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_next_host_notification(eh->get_next_notification()); host.set_no_more_notifications(eh->get_no_more_notifications()); if (!eh->get_notes().empty()) - host.set_notes(misc::string::check_string_utf8(eh->get_notes())); + host.set_notes(common::check_string_utf8(eh->get_notes())); if (!eh->get_notes_url().empty()) - host.set_notes_url(misc::string::check_string_utf8(eh->get_notes_url())); + host.set_notes_url(common::check_string_utf8(eh->get_notes_url())); host.set_notify(eh->get_notifications_enabled()); host.set_notification_interval(eh->get_notification_interval()); if (!eh->notification_period().empty()) @@ -2133,15 +2113,14 @@ int neb::callback_pb_host(int callback_type, void* data) { eh->get_notify_on(engine::notifier::unreachable)); host.set_obsess_over_host(eh->obsess_over()); if (!eh->get_plugin_output().empty()) { - host.set_output(misc::string::check_string_utf8(eh->get_plugin_output())); + host.set_output(common::check_string_utf8(eh->get_plugin_output())); } if (!eh->get_long_plugin_output().empty()) - host.set_output( - misc::string::check_string_utf8(eh->get_long_plugin_output())); + host.set_output(common::check_string_utf8(eh->get_long_plugin_output())); host.set_passive_checks(eh->passive_checks_enabled()); host.set_percent_state_change(eh->get_percent_state_change()); if (!eh->get_perf_data().empty()) - host.set_perfdata(misc::string::check_string_utf8(eh->get_perf_data())); + host.set_perfdata(common::check_string_utf8(eh->get_perf_data())); host.set_instance_id(config::applier::state::instance().poller_id()); host.set_retain_nonstatus_information( eh->get_retain_nonstatus_information()); @@ -2157,7 +2136,7 @@ int neb::callback_pb_host(int callback_type, void* data) { : engine::notifier::hard)); if (!eh->get_statusmap_image().empty()) host.set_statusmap_image( - misc::string::check_string_utf8(eh->get_statusmap_image())); + common::check_string_utf8(eh->get_statusmap_image())); host.set_timezone(eh->get_timezone()); host.set_severity_id(eh->get_severity() ? eh->get_severity()->id() : 0); host.set_icon_id(eh->get_icon_id()); @@ -2225,7 +2204,7 @@ int neb::callback_host_check(int callback_type, void* data) { host_check->active_checks_enabled = h->active_checks_enabled(); host_check->check_type = hcdata->check_type; host_check->command_line = - misc::string::check_string_utf8(hcdata->command_line); + common::check_string_utf8(hcdata->command_line); if (!hcdata->host_name) throw msg_fmt("unnamed host"); host_check->host_id = engine::get_host_id(hcdata->host_name); @@ -2296,7 +2275,7 @@ int neb::callback_pb_host_check(int callback_type, void* data) { ? com::centreon::broker::CheckActive : com::centreon::broker::CheckPassive); host_check->mut_obj().set_command_line( - misc::string::check_string_utf8(hcdata->command_line)); + common::check_string_utf8(hcdata->command_line)); host_check->mut_obj().set_host_id(h->host_id()); host_check->mut_obj().set_next_check(h->get_next_check()); @@ -2336,7 +2315,7 @@ int neb::callback_host_status(int callback_type, void* data) { host_status->active_checks_enabled = h->active_checks_enabled(); if (!h->check_command().empty()) host_status->check_command = - misc::string::check_string_utf8(h->check_command()); + common::check_string_utf8(h->check_command()); host_status->check_interval = h->check_interval(); if (!h->check_period().empty()) host_status->check_period = h->check_period(); @@ -2347,7 +2326,7 @@ int neb::callback_host_status(int callback_type, void* data) { host_status->downtime_depth = h->get_scheduled_downtime_depth(); if (!h->event_handler().empty()) host_status->event_handler = - misc::string::check_string_utf8(h->event_handler()); + common::check_string_utf8(h->event_handler()); host_status->event_handler_enabled = h->event_handler_enabled(); host_status->execution_time = h->get_execution_time(); host_status->flap_detection_enabled = h->flap_detection_enabled(); @@ -2378,18 +2357,16 @@ int neb::callback_host_status(int callback_type, void* data) { host_status->notifications_enabled = h->get_notifications_enabled(); host_status->obsess_over = h->obsess_over(); if (!h->get_plugin_output().empty()) { - host_status->output = - misc::string::check_string_utf8(h->get_plugin_output()); + host_status->output = common::check_string_utf8(h->get_plugin_output()); host_status->output.append("\n"); } if (!h->get_long_plugin_output().empty()) host_status->output.append( - misc::string::check_string_utf8(h->get_long_plugin_output())); + common::check_string_utf8(h->get_long_plugin_output())); host_status->passive_checks_enabled = h->passive_checks_enabled(); host_status->percent_state_change = h->get_percent_state_change(); if (!h->get_perf_data().empty()) - host_status->perf_data = - misc::string::check_string_utf8(h->get_perf_data()); + host_status->perf_data = common::check_string_utf8(h->get_perf_data()); host_status->retry_interval = h->retry_interval(); host_status->should_be_scheduled = h->get_should_be_scheduled(); host_status->state_type = @@ -2495,14 +2472,13 @@ int neb::callback_pb_host_status(int callback_type, void* data) noexcept { hscr.set_next_host_notification(eh->get_next_notification()); hscr.set_no_more_notifications(eh->get_no_more_notifications()); if (!eh->get_plugin_output().empty()) - hscr.set_output(misc::string::check_string_utf8(eh->get_plugin_output())); + hscr.set_output(common::check_string_utf8(eh->get_plugin_output())); if (!eh->get_long_plugin_output().empty()) - hscr.set_output( - misc::string::check_string_utf8(eh->get_long_plugin_output())); + hscr.set_output(common::check_string_utf8(eh->get_long_plugin_output())); hscr.set_percent_state_change(eh->get_percent_state_change()); if (!eh->get_perf_data().empty()) - hscr.set_perfdata(misc::string::check_string_utf8(eh->get_perf_data())); + hscr.set_perfdata(common::check_string_utf8(eh->get_perf_data())); hscr.set_should_be_scheduled(eh->get_should_be_scheduled()); hscr.set_state_type(static_cast( eh->has_been_checked() ? eh->get_state_type() : engine::notifier::hard)); @@ -2567,7 +2543,7 @@ int neb::callback_log(int callback_type, void* data) { le->c_time = log_data->entry_time; le->poller_name = config::applier::state::instance().poller_name(); if (log_data->data) { - le->output = misc::string::check_string_utf8(log_data->data); + le->output = common::check_string_utf8(log_data->data); set_log_data(*le, le->output.c_str()); } @@ -2606,7 +2582,7 @@ int neb::callback_pb_log(int callback_type [[maybe_unused]], void* data) { le_obj.set_ctime(log_data->entry_time); le_obj.set_instance_name(config::applier::state::instance().poller_name()); if (log_data->data) { - std::string output = misc::string::check_string_utf8(log_data->data); + std::string output = common::check_string_utf8(log_data->data); le_obj.set_output(output); set_pb_log_data(*le, output); } @@ -2819,10 +2795,10 @@ int neb::callback_program_status(int callback_type, void* data) { is->event_handler_enabled = program_status_data->event_handlers_enabled; is->flap_detection_enabled = program_status_data->flap_detection_enabled; if (!program_status_data->global_host_event_handler.empty()) - is->global_host_event_handler = misc::string::check_string_utf8( + is->global_host_event_handler = common::check_string_utf8( program_status_data->global_host_event_handler); if (!program_status_data->global_service_event_handler.empty()) - is->global_service_event_handler = misc::string::check_string_utf8( + is->global_service_event_handler = common::check_string_utf8( program_status_data->global_service_event_handler); is->last_alive = time(nullptr); is->last_command_check = program_status_data->last_command_check; @@ -2884,10 +2860,10 @@ int neb::callback_pb_program_status(int, void* data) { is.set_event_handlers(program_status_data.event_handlers_enabled); is.set_flap_detection(program_status_data.flap_detection_enabled); if (!program_status_data.global_host_event_handler.empty()) - is.set_global_host_event_handler(misc::string::check_string_utf8( + is.set_global_host_event_handler(common::check_string_utf8( program_status_data.global_host_event_handler)); if (!program_status_data.global_service_event_handler.empty()) - is.set_global_service_event_handler(misc::string::check_string_utf8( + is.set_global_service_event_handler(common::check_string_utf8( program_status_data.global_service_event_handler)); is.set_last_alive(time(nullptr)); is.set_last_command_check(program_status_data.last_command_check); @@ -3048,12 +3024,10 @@ int neb::callback_service(int callback_type, void* data) { my_service->acknowledged = s->problem_has_been_acknowledged(); my_service->acknowledgement_type = s->get_acknowledgement(); if (!s->get_action_url().empty()) - my_service->action_url = - misc::string::check_string_utf8(s->get_action_url()); + my_service->action_url = common::check_string_utf8(s->get_action_url()); my_service->active_checks_enabled = s->active_checks_enabled(); if (!s->check_command().empty()) - my_service->check_command = - misc::string::check_string_utf8(s->check_command()); + my_service->check_command = common::check_string_utf8(s->check_command()); my_service->check_freshness = s->check_freshness_enabled(); my_service->check_interval = s->check_interval(); if (!s->check_period().empty()) @@ -3070,11 +3044,10 @@ int neb::callback_service(int callback_type, void* data) { my_service->downtime_depth = s->get_scheduled_downtime_depth(); if (!s->get_display_name().empty()) my_service->display_name = - misc::string::check_string_utf8(s->get_display_name()); + common::check_string_utf8(s->get_display_name()); my_service->enabled = (service_data->type != NEBTYPE_SERVICE_DELETE); if (!s->event_handler().empty()) - my_service->event_handler = - misc::string::check_string_utf8(s->event_handler()); + my_service->event_handler = common::check_string_utf8(s->event_handler()); my_service->event_handler_enabled = s->event_handler_enabled(); my_service->execution_time = s->get_execution_time(); my_service->first_notification_delay = s->get_first_notification_delay(); @@ -3092,14 +3065,12 @@ int neb::callback_service(int callback_type, void* data) { my_service->has_been_checked = s->has_been_checked(); my_service->high_flap_threshold = s->get_high_flap_threshold(); if (!s->get_hostname().empty()) - my_service->host_name = - misc::string::check_string_utf8(s->get_hostname()); + my_service->host_name = common::check_string_utf8(s->get_hostname()); if (!s->get_icon_image().empty()) - my_service->icon_image = - misc::string::check_string_utf8(s->get_icon_image()); + my_service->icon_image = common::check_string_utf8(s->get_icon_image()); if (!s->get_icon_image_alt().empty()) my_service->icon_image_alt = - misc::string::check_string_utf8(s->get_icon_image_alt()); + common::check_string_utf8(s->get_icon_image_alt()); my_service->is_flapping = s->get_is_flapping(); my_service->is_volatile = s->get_is_volatile(); my_service->last_check = s->get_last_check(); @@ -3119,10 +3090,9 @@ int neb::callback_service(int callback_type, void* data) { my_service->next_notification = s->get_next_notification(); my_service->no_more_notifications = s->get_no_more_notifications(); if (!s->get_notes().empty()) - my_service->notes = misc::string::check_string_utf8(s->get_notes()); + my_service->notes = common::check_string_utf8(s->get_notes()); if (!s->get_notes_url().empty()) - my_service->notes_url = - misc::string::check_string_utf8(s->get_notes_url()); + my_service->notes_url = common::check_string_utf8(s->get_notes_url()); my_service->notifications_enabled = s->get_notifications_enabled(); my_service->notification_interval = s->get_notification_interval(); if (!s->notification_period().empty()) @@ -3138,25 +3108,23 @@ int neb::callback_service(int callback_type, void* data) { my_service->notify_on_warning = s->get_notify_on(engine::notifier::warning); my_service->obsess_over = s->obsess_over(); if (!s->get_plugin_output().empty()) { - my_service->output = - misc::string::check_string_utf8(s->get_plugin_output()); + my_service->output = common::check_string_utf8(s->get_plugin_output()); my_service->output.append("\n"); } if (!s->get_long_plugin_output().empty()) my_service->output.append( - misc::string::check_string_utf8(s->get_long_plugin_output())); + common::check_string_utf8(s->get_long_plugin_output())); my_service->passive_checks_enabled = s->passive_checks_enabled(); my_service->percent_state_change = s->get_percent_state_change(); if (!s->get_perf_data().empty()) - my_service->perf_data = - misc::string::check_string_utf8(s->get_perf_data()); + my_service->perf_data = common::check_string_utf8(s->get_perf_data()); my_service->retain_nonstatus_information = s->get_retain_nonstatus_information(); my_service->retain_status_information = s->get_retain_status_information(); my_service->retry_interval = s->retry_interval(); if (!s->description().empty()) my_service->service_description = - misc::string::check_string_utf8(s->description()); + common::check_string_utf8(s->description()); my_service->should_be_scheduled = s->get_should_be_scheduled(); my_service->stalk_on_critical = s->get_stalk_on(engine::notifier::critical); my_service->stalk_on_ok = s->get_stalk_on(engine::notifier::ok); @@ -3237,11 +3205,9 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { else if (ds->modified_attribute & MODATTR_OBSESSIVE_HANDLER_ENABLED) srv.set_obsess_over_service(es->obsess_over()); else if (ds->modified_attribute & MODATTR_EVENT_HANDLER_COMMAND) - srv.set_event_handler( - misc::string::check_string_utf8(es->event_handler())); + srv.set_event_handler(common::check_string_utf8(es->event_handler())); else if (ds->modified_attribute & MODATTR_CHECK_COMMAND) - srv.set_check_command( - misc::string::check_string_utf8(es->check_command())); + srv.set_check_command(common::check_string_utf8(es->check_command())); else if (ds->modified_attribute & MODATTR_NORMAL_CHECK_INTERVAL) srv.set_check_interval(es->check_interval()); else if (ds->modified_attribute & MODATTR_RETRY_CHECK_INTERVAL) @@ -3287,11 +3253,10 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_acknowledged(es->problem_has_been_acknowledged()); srv.set_acknowledgement_type(es->get_acknowledgement()); if (!es->get_action_url().empty()) - srv.set_action_url(misc::string::check_string_utf8(es->get_action_url())); + srv.set_action_url(common::check_string_utf8(es->get_action_url())); srv.set_active_checks(es->active_checks_enabled()); if (!es->check_command().empty()) - srv.set_check_command( - misc::string::check_string_utf8(es->check_command())); + srv.set_check_command(common::check_string_utf8(es->check_command())); srv.set_check_freshness(es->check_freshness_enabled()); srv.set_check_interval(es->check_interval()); if (!es->check_period().empty()) @@ -3308,13 +3273,11 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_default_passive_checks(es->passive_checks_enabled()); srv.set_scheduled_downtime_depth(es->get_scheduled_downtime_depth()); if (!es->get_display_name().empty()) - srv.set_display_name( - misc::string::check_string_utf8(es->get_display_name())); + srv.set_display_name(common::check_string_utf8(es->get_display_name())); srv.set_enabled(static_cast(data)->type != NEBTYPE_SERVICE_DELETE); if (!es->event_handler().empty()) - srv.set_event_handler( - misc::string::check_string_utf8(es->event_handler())); + srv.set_event_handler(common::check_string_utf8(es->event_handler())); srv.set_event_handler_enabled(es->event_handler_enabled()); srv.set_execution_time(es->get_execution_time()); srv.set_first_notification_delay(es->get_first_notification_delay()); @@ -3332,10 +3295,10 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_checked(es->has_been_checked()); srv.set_high_flap_threshold(es->get_high_flap_threshold()); if (!es->description().empty()) - srv.set_description(misc::string::check_string_utf8(es->description())); + srv.set_description(common::check_string_utf8(es->description())); if (!es->get_hostname().empty()) { - std::string name{misc::string::check_string_utf8(es->get_hostname())}; + std::string name{common::check_string_utf8(es->get_hostname())}; switch (es->get_service_type()) { case com::centreon::engine::service_type::METASERVICE: { srv.set_type(METASERVICE); @@ -3387,10 +3350,10 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { } if (!es->get_icon_image().empty()) *srv.mutable_icon_image() = - misc::string::check_string_utf8(es->get_icon_image()); + common::check_string_utf8(es->get_icon_image()); if (!es->get_icon_image_alt().empty()) *srv.mutable_icon_image_alt() = - misc::string::check_string_utf8(es->get_icon_image_alt()); + common::check_string_utf8(es->get_icon_image_alt()); srv.set_flapping(es->get_is_flapping()); srv.set_is_volatile(es->get_is_volatile()); srv.set_last_check(es->get_last_check()); @@ -3411,10 +3374,9 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_next_notification(es->get_next_notification()); srv.set_no_more_notifications(es->get_no_more_notifications()); if (!es->get_notes().empty()) - srv.set_notes(misc::string::check_string_utf8(es->get_notes())); + srv.set_notes(common::check_string_utf8(es->get_notes())); if (!es->get_notes_url().empty()) - *srv.mutable_notes_url() = - misc::string::check_string_utf8(es->get_notes_url()); + *srv.mutable_notes_url() = common::check_string_utf8(es->get_notes_url()); srv.set_notify(es->get_notifications_enabled()); srv.set_notification_interval(es->get_notification_interval()); if (!es->notification_period().empty()) @@ -3429,15 +3391,14 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_obsess_over_service(es->obsess_over()); if (!es->get_plugin_output().empty()) *srv.mutable_output() = - misc::string::check_string_utf8(es->get_plugin_output()); + common::check_string_utf8(es->get_plugin_output()); if (!es->get_long_plugin_output().empty()) *srv.mutable_long_output() = - misc::string::check_string_utf8(es->get_long_plugin_output()); + common::check_string_utf8(es->get_long_plugin_output()); srv.set_passive_checks(es->passive_checks_enabled()); srv.set_percent_state_change(es->get_percent_state_change()); if (!es->get_perf_data().empty()) - *srv.mutable_perfdata() = - misc::string::check_string_utf8(es->get_perf_data()); + *srv.mutable_perfdata() = common::check_string_utf8(es->get_perf_data()); srv.set_retain_nonstatus_information( es->get_retain_nonstatus_information()); srv.set_retain_status_information(es->get_retain_status_information()); @@ -3524,7 +3485,7 @@ int neb::callback_service_check(int callback_type, void* data) { service_check->active_checks_enabled = s->active_checks_enabled(); service_check->check_type = scdata->check_type; service_check->command_line = - misc::string::check_string_utf8(scdata->command_line); + common::check_string_utf8(scdata->command_line); if (!scdata->host_id) throw msg_fmt("host without id"); if (!scdata->service_id) @@ -3597,7 +3558,7 @@ int neb::callback_pb_service_check(int, void* data) { ? com::centreon::broker::CheckActive : com::centreon::broker::CheckPassive); service_check->mut_obj().set_command_line( - misc::string::check_string_utf8(scdata->command_line)); + common::check_string_utf8(scdata->command_line)); service_check->mut_obj().set_host_id(scdata->host_id); service_check->mut_obj().set_service_id(scdata->service_id); service_check->mut_obj().set_next_check(s->get_next_check()); @@ -3776,13 +3737,13 @@ int32_t neb::callback_pb_service_status(int callback_type [[maybe_unused]], sscr.set_next_notification(es->get_next_notification()); sscr.set_no_more_notifications(es->get_no_more_notifications()); if (!es->get_plugin_output().empty()) - sscr.set_output(misc::string::check_string_utf8(es->get_plugin_output())); + sscr.set_output(common::check_string_utf8(es->get_plugin_output())); if (!es->get_long_plugin_output().empty()) sscr.set_long_output( - misc::string::check_string_utf8(es->get_long_plugin_output())); + common::check_string_utf8(es->get_long_plugin_output())); sscr.set_percent_state_change(es->get_percent_state_change()); if (!es->get_perf_data().empty()) { - sscr.set_perfdata(misc::string::check_string_utf8(es->get_perf_data())); + sscr.set_perfdata(common::check_string_utf8(es->get_perf_data())); SPDLOG_LOGGER_TRACE(neb_logger, "callbacks: service ({}, {}) has perfdata <<{}>>", es->host_id(), es->service_id(), es->get_perf_data()); @@ -3901,7 +3862,7 @@ int neb::callback_service_status(int callback_type, void* data) { service_status->active_checks_enabled = s->active_checks_enabled(); if (!s->check_command().empty()) service_status->check_command = - misc::string::check_string_utf8(s->check_command()); + common::check_string_utf8(s->check_command()); service_status->check_interval = s->check_interval(); if (!s->check_period().empty()) service_status->check_period = s->check_period(); @@ -3912,7 +3873,7 @@ int neb::callback_service_status(int callback_type, void* data) { service_status->downtime_depth = s->get_scheduled_downtime_depth(); if (!s->event_handler().empty()) service_status->event_handler = - misc::string::check_string_utf8(s->event_handler()); + common::check_string_utf8(s->event_handler()); service_status->event_handler_enabled = s->event_handler_enabled(); service_status->execution_time = s->get_execution_time(); service_status->flap_detection_enabled = s->flap_detection_enabled(); @@ -3938,27 +3899,25 @@ int neb::callback_service_status(int callback_type, void* data) { service_status->obsess_over = s->obsess_over(); if (!s->get_plugin_output().empty()) { service_status->output = - misc::string::check_string_utf8(s->get_plugin_output()); + common::check_string_utf8(s->get_plugin_output()); service_status->output.append("\n"); } if (!s->get_long_plugin_output().empty()) service_status->output.append( - misc::string::check_string_utf8(s->get_long_plugin_output())); + common::check_string_utf8(s->get_long_plugin_output())); service_status->passive_checks_enabled = s->passive_checks_enabled(); service_status->percent_state_change = s->get_percent_state_change(); if (!s->get_perf_data().empty()) - service_status->perf_data = - misc::string::check_string_utf8(s->get_perf_data()); + service_status->perf_data = common::check_string_utf8(s->get_perf_data()); service_status->retry_interval = s->retry_interval(); if (s->get_hostname().empty()) throw msg_fmt("unnamed host"); if (s->description().empty()) throw msg_fmt("unnamed service"); - service_status->host_name = - misc::string::check_string_utf8(s->get_hostname()); + service_status->host_name = common::check_string_utf8(s->get_hostname()); service_status->service_description = - misc::string::check_string_utf8(s->description()); + common::check_string_utf8(s->description()); { std::pair p{ engine::get_host_and_service_id(s->get_hostname(), s->description())}; diff --git a/broker/storage/src/conflict_manager_storage.cc b/broker/storage/src/conflict_manager_storage.cc index d2fca02796e..e3677b463c3 100644 --- a/broker/storage/src/conflict_manager_storage.cc +++ b/broker/storage/src/conflict_manager_storage.cc @@ -31,6 +31,7 @@ #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/broker/storage/conflict_manager.hh" #include "com/centreon/common/perfdata.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" using namespace com::centreon::exceptions; @@ -135,10 +136,10 @@ void conflict_manager::_storage_process_service_status( "(host_id,host_name,service_id,service_description,must_be_rebuild," "special) VALUES (?,?,?,?,?,?)"); - fmt::string_view hv(misc::string::truncate( + fmt::string_view hv(common::truncate_utf8( ss.host_name, get_centreon_storage_index_data_col_size( centreon_storage_index_data_host_name))); - fmt::string_view sv(misc::string::truncate( + fmt::string_view sv(common::truncate_utf8( ss.service_description, get_centreon_storage_index_data_col_size( centreon_storage_index_data_service_description))); @@ -264,10 +265,10 @@ void conflict_manager::_storage_process_service_status( std::deque> to_publish; for (auto& pd : pds) { - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); auto it_index_cache = _metric_cache.find({index_id, pd.name()}); diff --git a/broker/unified_sql/src/stream_sql.cc b/broker/unified_sql/src/stream_sql.cc index af662996366..edd91e546ed 100644 --- a/broker/unified_sql/src/stream_sql.cc +++ b/broker/unified_sql/src/stream_sql.cc @@ -29,6 +29,7 @@ #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/broker/unified_sql/internal.hh" #include "com/centreon/broker/unified_sql/stream.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/engine/host.hh" #include "com/centreon/engine/service.hh" @@ -2154,25 +2155,25 @@ uint64_t stream::_process_pb_host_in_resources(const Host& h, int32_t conn) { uint64_t res_id = 0; if (h.enabled()) { uint64_t sid = 0; - fmt::string_view name{misc::string::truncate( - h.name(), get_centreon_storage_resources_col_size( - centreon_storage_resources_name))}; - fmt::string_view address{misc::string::truncate( + fmt::string_view name{ + common::truncate_utf8(h.name(), get_centreon_storage_resources_col_size( + centreon_storage_resources_name))}; + fmt::string_view address{common::truncate_utf8( h.address(), get_centreon_storage_resources_col_size( centreon_storage_resources_address))}; - fmt::string_view alias{misc::string::truncate( + fmt::string_view alias{common::truncate_utf8( h.alias(), get_centreon_storage_resources_col_size( centreon_storage_resources_alias))}; - fmt::string_view parent_name{misc::string::truncate( + fmt::string_view parent_name{common::truncate_utf8( h.name(), get_centreon_storage_resources_col_size( centreon_storage_resources_parent_name))}; - fmt::string_view notes_url{misc::string::truncate( + fmt::string_view notes_url{common::truncate_utf8( h.notes_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes_url))}; - fmt::string_view notes{misc::string::truncate( + fmt::string_view notes{common::truncate_utf8( h.notes(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes))}; - fmt::string_view action_url{misc::string::truncate( + fmt::string_view action_url{common::truncate_utf8( h.action_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_action_url))}; @@ -2581,13 +2582,13 @@ void stream::_process_pb_host_status(const std::shared_ptr& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", hscr.output(), hscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_hosts_col_size(centreon_storage_hosts_output)); b->set_value_as_str(10, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( - hscr.perfdata(), get_centreon_storage_hosts_col_size( - centreon_storage_hosts_perfdata)); + size = common::adjust_size_utf8(hscr.perfdata(), + get_centreon_storage_hosts_col_size( + centreon_storage_hosts_perfdata)); b->set_value_as_str(11, fmt::string_view(hscr.perfdata().data(), size)); b->set_value_as_bool(12, hscr.flapping()); b->set_value_as_f64(13, hscr.percent_state_change()); @@ -2632,14 +2633,14 @@ void stream::_process_pb_host_status(const std::shared_ptr& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", hscr.output(), hscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_hosts_col_size(centreon_storage_hosts_output)); _hscr_update->bind_value_as_str( 10, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( - hscr.perfdata(), get_centreon_storage_hosts_col_size( - centreon_storage_hosts_perfdata)); + size = common::adjust_size_utf8(hscr.perfdata(), + get_centreon_storage_hosts_col_size( + centreon_storage_hosts_perfdata)); _hscr_update->bind_value_as_str( 11, fmt::string_view(hscr.perfdata().data(), size)); _hscr_update->bind_value_as_bool(12, hscr.flapping()); @@ -4031,19 +4032,19 @@ uint64_t stream::_process_pb_service_in_resources(const Service& s, if (s.enabled()) { uint64_t sid = 0; - fmt::string_view name{misc::string::truncate( + fmt::string_view name{common::truncate_utf8( s.display_name(), get_centreon_storage_resources_col_size( centreon_storage_resources_name))}; - fmt::string_view parent_name{misc::string::truncate( + fmt::string_view parent_name{common::truncate_utf8( s.host_name(), get_centreon_storage_resources_col_size( centreon_storage_resources_parent_name))}; - fmt::string_view notes_url{misc::string::truncate( + fmt::string_view notes_url{common::truncate_utf8( s.notes_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes_url))}; - fmt::string_view notes{misc::string::truncate( + fmt::string_view notes{common::truncate_utf8( s.notes(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes))}; - fmt::string_view action_url{misc::string::truncate( + fmt::string_view action_url{common::truncate_utf8( s.action_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_action_url))}; @@ -4401,10 +4402,10 @@ void stream::_check_and_update_index_cache(const Service& ss) { auto it_index_cache = _index_cache.find({ss.host_id(), ss.service_id()}); - fmt::string_view hv(misc::string::truncate( + fmt::string_view hv(common::truncate_utf8( ss.host_name(), get_centreon_storage_index_data_col_size( centreon_storage_index_data_host_name))); - fmt::string_view sv(misc::string::truncate( + fmt::string_view sv(common::truncate_utf8( ss.description(), get_centreon_storage_index_data_col_size( centreon_storage_index_data_service_description))); bool special = ss.type() == BA; @@ -4649,11 +4650,11 @@ void stream::_process_pb_service_status(const std::shared_ptr& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", sscr.output(), sscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_services_col_size( centreon_storage_services_output)); b->set_value_as_str(11, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( + size = common::adjust_size_utf8( sscr.perfdata(), get_centreon_storage_services_col_size( centreon_storage_services_perfdata)); b->set_value_as_str(12, fmt::string_view(sscr.perfdata().data(), size)); @@ -4703,12 +4704,12 @@ void stream::_process_pb_service_status(const std::shared_ptr& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", sscr.output(), sscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_services_col_size( centreon_storage_services_output)); _sscr_update->bind_value_as_str( 11, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( + size = common::adjust_size_utf8( sscr.perfdata(), get_centreon_storage_services_col_size( centreon_storage_services_perfdata)); _sscr_update->bind_value_as_str( @@ -4745,7 +4746,7 @@ void stream::_process_pb_service_status(const std::shared_ptr& d) { if (_store_in_resources) { int32_t conn = _mysql.choose_connection_by_instance( _cache_host_instance[static_cast(sscr.host_id())]); - size_t output_size = misc::string::adjust_size_utf8( + size_t output_size = common::adjust_size_utf8( sscr.output(), get_centreon_storage_resources_col_size( centreon_storage_resources_output)); _logger_sql->debug( diff --git a/broker/unified_sql/src/stream_storage.cc b/broker/unified_sql/src/stream_storage.cc index 01e6f92fd80..5c7968c93f6 100644 --- a/broker/unified_sql/src/stream_storage.cc +++ b/broker/unified_sql/src/stream_storage.cc @@ -38,6 +38,7 @@ #include "com/centreon/broker/unified_sql/internal.hh" #include "com/centreon/broker/unified_sql/stream.hh" #include "com/centreon/common/perfdata.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" using namespace com::centreon::exceptions; @@ -156,10 +157,10 @@ void stream::_unified_sql_process_pb_service_status( std::deque> to_publish; for (auto& pd : pds) { misc::read_lock rlck(_metric_cache_m); - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); @@ -460,10 +461,10 @@ void stream::_unified_sql_process_service_status( if (!_index_data_insert.prepared()) _index_data_insert = _mysql.prepare_query(_index_data_insert_request); - fmt::string_view hv(misc::string::truncate( + fmt::string_view hv(common::truncate_utf8( ss.host_name, get_centreon_storage_index_data_col_size( centreon_storage_index_data_host_name))); - fmt::string_view sv(misc::string::truncate( + fmt::string_view sv(common::truncate_utf8( ss.service_description, get_centreon_storage_index_data_col_size( centreon_storage_index_data_service_description))); @@ -533,10 +534,10 @@ void stream::_unified_sql_process_service_status( std::deque> to_publish; for (auto& pd : pds) { misc::read_lock rlck(_metric_cache_m); - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 0f67c0116e9..6142a2fa804 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -23,7 +23,7 @@ add_subdirectory(log_v2) # Set directories. set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/inc/com/centreon/common") -set(PROCESS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/process") +set(PROCESS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/process/inc") set(HTTP_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/http/inc/com/centreon/common/http") set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") set(TEST_DIR "${PROJECT_SOURCE_DIR}/tests") @@ -52,6 +52,7 @@ set(SOURCES ${SRC_DIR}/process_stat.pb.cc ${SRC_DIR}/process_stat.grpc.pb.cc ${SRC_DIR}/rapidjson_helper.cc + ${SRC_DIR}/utf8.cc ) # Include directories. @@ -63,7 +64,6 @@ include_directories("${INCLUDE_DIR}" add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) add_library(centreon_common STATIC ${SOURCES}) -target_include_directories(centreon_common PRIVATE ${INCLUDE_DIR}) set_property(TARGET centreon_common PROPERTY POSITION_INDEPENDENT_CODE ON) target_precompile_headers(centreon_common PRIVATE precomp_inc/precomp.hh) diff --git a/common/doc/common-doc.md b/common/doc/common-doc.md index 38959b0b41f..237453331f0 100644 --- a/common/doc/common-doc.md +++ b/common/doc/common-doc.md @@ -116,3 +116,7 @@ class process_wait : public process { ``` +### Asio bug work around +There is an issue in io_context::notify_fork. Internally, ctx.notify_fork calls epoll_reactor::notify_fork which locks registered_descriptors_mutex_. An issue occurs when registered_descriptors_mutex_ is locked by another thread at fork timepoint. +In such a case, child process starts with registered_descriptors_mutex_ already locked and both child and parent process will hang. + diff --git a/common/grpc/inc/com/centreon/common/grpc/grpc_config.hh b/common/grpc/inc/com/centreon/common/grpc/grpc_config.hh index 2d8b5978be9..4d151fa0baa 100644 --- a/common/grpc/inc/com/centreon/common/grpc/grpc_config.hh +++ b/common/grpc/inc/com/centreon/common/grpc/grpc_config.hh @@ -101,6 +101,37 @@ class grpc_config { _compress == right._compress && _second_keepalive_interval == right._second_keepalive_interval; } + + /** + * @brief identical to std:string::compare + * + * @param right + * @return int -1, 0 if equal or 1 + */ + int compare(const grpc_config& right) const { + int ret = _hostport.compare(right._hostport); + if (ret) + return ret; + ret = _crypted - right._crypted; + if (ret) + return ret; + ret = _certificate.compare(right._certificate); + if (ret) + return ret; + ret = _cert_key.compare(right._cert_key); + if (ret) + return ret; + ret = _ca_cert.compare(right._ca_cert); + if (ret) + return ret; + ret = _ca_name.compare(right._ca_name); + if (ret) + return ret; + ret = _compress - right._compress; + if (ret) + return ret; + return _second_keepalive_interval - right._second_keepalive_interval; + } }; } // namespace com::centreon::common::grpc diff --git a/common/http/src/http_connection.cc b/common/http/src/http_connection.cc index f17ea0b5904..a964a78c730 100644 --- a/common/http/src/http_connection.cc +++ b/common/http/src/http_connection.cc @@ -84,7 +84,7 @@ void connection_base::gest_keepalive(const response_ptr& resp) { if (std::regex_search(keep_alive_info->value().begin(), keep_alive_info->value().end(), res, keep_alive_time_out_r)) { - uint second_duration; + unsigned int second_duration; if (absl::SimpleAtoi(res[1].str(), &second_duration)) { _keep_alive_end = system_clock::now() + std::chrono::seconds(second_duration); diff --git a/common/inc/com/centreon/common/utf8.hh b/common/inc/com/centreon/common/utf8.hh new file mode 100644 index 00000000000..e9a671f6202 --- /dev/null +++ b/common/inc/com/centreon/common/utf8.hh @@ -0,0 +1,49 @@ +/** + * Copyright 2023 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 CCCM_UTF8_HH +#define CCCM_UTF8_HH + +namespace com::centreon::common { + +/** + * @brief This function works almost like the resize method but takes care + * of the UTF-8 encoding and avoids to cut a string in the middle of a + * character. This function assumes the string to be UTF-8 encoded. + * + * @param str A string to truncate. + * @param s The desired size, maybe the resulting string will contain less + * characters. + * + * @return a reference to the string str. + */ +template +fmt::string_view truncate_utf8(const T& str, size_t s) { + if (s >= str.size()) + return fmt::string_view(str); + if (s > 0) + while ((str[s] & 0xc0) == 0x80) + s--; + return fmt::string_view(str.data(), s); +} + +std::string check_string_utf8(std::string const& str) noexcept; +size_t adjust_size_utf8(const std::string& str, size_t s); +} // namespace com::centreon::common + +#endif \ No newline at end of file diff --git a/common/process/CMakeLists.txt b/common/process/CMakeLists.txt index cf33af66177..f79bbaaa657 100644 --- a/common/process/CMakeLists.txt +++ b/common/process/CMakeLists.txt @@ -16,14 +16,14 @@ # For more information : contact@centreon.com # -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/process/inc) add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) add_definitions(${spdlog_DEFINITIONS}) add_library( centreon_process STATIC # Sources. - process.cc) + src/process.cc) target_precompile_headers(centreon_process REUSE_FROM centreon_common) diff --git a/common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh b/common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh new file mode 100644 index 00000000000..79dc1eac355 --- /dev/null +++ b/common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh @@ -0,0 +1,275 @@ +#ifndef CENTREON_POSIX_PROCESS_LAUNCHER_HH +#define CENTREON_POSIX_PROCESS_LAUNCHER_HH + +#include +#include + +namespace boost::process::v2::posix { + +struct centreon_posix_default_launcher; + +struct centreon_process_stdio { + boost::process::v2::detail::process_input_binding in; + boost::process::v2::detail::process_output_binding out; + boost::process::v2::detail::process_error_binding err; + + error_code on_exec_setup(centreon_posix_default_launcher& launcher, + const filesystem::path&, + const char* const*) { + if (::dup2(in.fd, in.target) == -1) + return error_code(errno, system_category()); + + if (::dup2(out.fd, out.target) == -1) + return error_code(errno, system_category()); + + if (::dup2(err.fd, err.target) == -1) + return error_code(errno, system_category()); + + return error_code{}; + }; +}; + +/** + * This class is a copy of posix::default_launcher + * as io_context::notify_fork can hang on child process and as we don't care + * about child process in asio as we will do an exec, it's removed + */ +struct centreon_posix_default_launcher { + /// The pointer to the environment forwarded to the subprocess. + const char* const* env = ::environ; + /// The pid of the subprocess - will be assigned after fork. + int pid = -1; + + /// The whitelist for file descriptors. + std::vector fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; + + centreon_posix_default_launcher() = default; + + template + auto operator()( + ExecutionContext& context, + const typename std::enable_if< + std::is_convertible< + ExecutionContext&, + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) + -> basic_process { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), + std::forward(inits)...); + + if (ec) + v2::detail::throw_error(ec, "centreon_posix_default_launcher"); + + return proc; + } + + template + auto operator()( + ExecutionContext& context, + error_code& ec, + const typename std::enable_if< + std::is_convertible< + ExecutionContext&, + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) + -> basic_process { + return (*this)(context.get_executor(), executable, std::forward(args), + std::forward(inits)...); + } + + template + auto operator()( + Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor< + Executor>::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) -> basic_process { + error_code ec; + auto proc = + (*this)(std::move(exec), ec, executable, std::forward(args), + std::forward(inits)...); + + if (ec) + v2::detail::throw_error(ec, "centreon_posix_default_launcher"); + + return proc; + } + + template + auto operator()( + Executor exec, + error_code& ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor< + Executor>::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) -> basic_process { + auto argv = this->build_argv_(executable, std::forward(args)); + { + pipe_guard pg; + if (::pipe(pg.p)) { + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + return basic_process{exec}; + } + if (::fcntl(pg.p[1], F_SETFD, FD_CLOEXEC)) { + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + return basic_process{exec}; + } + ec = detail::on_setup(*this, executable, argv, inits...); + if (ec) { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process(exec); + } + fd_whitelist.push_back(pg.p[1]); + + auto& ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query( + exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context); + ctx.notify_fork( + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_prepare); + pid = ::fork(); + if (pid == -1) { + ctx.notify_fork( + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + detail::on_fork_error(*this, executable, argv, ec, inits...); + detail::on_error(*this, executable, argv, ec, inits...); + + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + return basic_process{exec}; + } else if (pid == 0) { + ::close(pg.p[0]); + /** + * ctx.notify_fork calls epoll_reactor::notify_fork which locks + * registered_descriptors_mutex_ An issue occurs when + * registered_descriptors_mutex_ is locked by another thread at fork + * timepoint. In such a case, child process starts with + * registered_descriptors_mutex_ already locked and both child and + * parent process will hang. + */ + // ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child); + ec = detail::on_exec_setup(*this, executable, argv, inits...); + if (!ec) { + close_all_fds(ec); + } + if (!ec) + ::execve(executable.c_str(), const_cast(argv), + const_cast(env)); + + ignore_unused(::write(pg.p[1], &errno, sizeof(int))); + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + detail::on_exec_error(*this, executable, argv, ec, inits...); + ::exit(EXIT_FAILURE); + return basic_process{exec}; + } + + ctx.notify_fork( + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + ::close(pg.p[1]); + pg.p[1] = -1; + int child_error{0}; + int count = -1; + while ((count = ::read(pg.p[0], &child_error, sizeof(child_error))) == + -1) { + int err = errno; + if ((err != EAGAIN) && (err != EINTR)) { + BOOST_PROCESS_V2_ASSIGN_EC(ec, err, system_category()) + break; + } + } + if (count != 0) + BOOST_PROCESS_V2_ASSIGN_EC(ec, child_error, system_category()) + + if (ec) { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process{exec}; + } + } + basic_process proc(exec, pid); + detail::on_success(*this, executable, argv, ec, inits...); + return proc; + } + + protected: + void ignore_unused(std::size_t) {} + void close_all_fds(error_code& ec) { + std::sort(fd_whitelist.begin(), fd_whitelist.end()); + detail::close_all(fd_whitelist, ec); + fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; + } + + struct pipe_guard { + int p[2]; + pipe_guard() : p{-1, -1} {} + + ~pipe_guard() { + if (p[0] != -1) + ::close(p[0]); + if (p[1] != -1) + ::close(p[1]); + } + }; + + // if we need to allocate something + std::vector argv_buffer_; + std::vector argv_; + + template + const char* const* build_argv_( + const filesystem::path& pt, + const Args& args, + typename std::enable_if< + std::is_convertible())), + cstring_ref>::value>::type* = nullptr) { + const auto arg_cnt = std::distance(std::begin(args), std::end(args)); + argv_.reserve(arg_cnt + 2); + argv_.push_back(pt.native().data()); + for (auto&& arg : args) + argv_.push_back(arg.c_str()); + + argv_.push_back(nullptr); + return argv_.data(); + } + + const char* const* build_argv_(const filesystem::path&, const char** argv) { + return argv; + } + + template + const char* const* build_argv_( + const filesystem::path& pt, + const Args& args, + typename std::enable_if< + !std::is_convertible())), + cstring_ref>::value>::type* = nullptr) { + const auto arg_cnt = std::distance(std::begin(args), std::end(args)); + argv_.reserve(arg_cnt + 2); + argv_buffer_.reserve(arg_cnt); + argv_.push_back(pt.native().data()); + + using char_type = + typename decay()))[0])>::type; + + for (basic_string_view arg : args) + argv_buffer_.push_back( + v2::detail::conv_string(arg.data(), arg.size())); + + for (auto&& arg : argv_buffer_) + argv_.push_back(arg.c_str()); + + argv_.push_back(nullptr); + return argv_.data(); + } +}; + +} // namespace boost::process::v2::posix + +#endif diff --git a/common/inc/com/centreon/common/process.hh b/common/process/inc/com/centreon/common/process/process.hh similarity index 99% rename from common/inc/com/centreon/common/process.hh rename to common/process/inc/com/centreon/common/process/process.hh index 888dbf92c1a..0d5686772fe 100644 --- a/common/inc/com/centreon/common/process.hh +++ b/common/process/inc/com/centreon/common/process/process.hh @@ -108,7 +108,7 @@ class process : public std::enable_shared_from_this { template void write_to_stdin(const string_class& content); - void start_process(); + void start_process(bool enable_stdin); void kill(); diff --git a/common/process/process.cc b/common/process/src/process.cc similarity index 65% rename from common/process/process.cc rename to common/process/src/process.cc index 599d4b787b1..615d16da564 100644 --- a/common/process/process.cc +++ b/common/process/src/process.cc @@ -1,27 +1,29 @@ -/* +/** * Copyright 2024 Centreon * - * This file is part of Centreon Engine. + * 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 * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. + * 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. * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * . + * For more information : contact@centreon.com */ -#include #include #include -#include "process.hh" +#include "com/centreon/common/process/process.hh" + +#include "com/centreon/common/process/detail/centreon_posix_process_launcher.hh" + +#include namespace proc = boost::process::v2; @@ -32,20 +34,100 @@ namespace com::centreon::common::detail { * */ struct boost_process { +#if defined(BOOST_PROCESS_V2_WINDOWS) + /** + * @brief Construct a new boost process object + * stdin of the child process is managed + * + * @param io_context + * @param exe_path absolute or relative exe path + * @param args arguments of the command + */ boost_process(asio::io_context& io_context, const std::string& exe_path, const std::vector& args) - : stdout(io_context), - stderr(io_context), - stdin(io_context), + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), + proc(io_context, + exe_path, + args, + proc::process_stdio{stdin_pipe, stdout_pipe, stderr_pipe}) {} + + /** + * @brief Construct a new boost process object + * stdin of the child process is not managed + * + * @param io_context + * @param logger + * @param cmd_line cmd line split (the first element is the path of the + * executable) + * @param no_stdin (not used) + */ + boost_process(asio::io_context& io_context, + const std::string& exe_path, + const std::vector& args, + bool no_stdin) + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), proc(io_context, exe_path, args, - proc::process_stdio{stdin, stdout, stderr}) {} + proc::process_stdio{{}, stdout_pipe, stderr_pipe}) {} + +#else + /** + * @brief Construct a new boost process object + * stdin of the child process is managed + * + * @param io_context + * @param exe_path absolute or relative exe path + * @param args arguments of the command + */ + boost_process(asio::io_context& io_context, + const std::string& exe_path, + const std::vector& args) + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), + proc(proc::posix::centreon_posix_default_launcher()( + io_context.get_executor(), + exe_path, + args, + proc::posix::centreon_process_stdio{stdin_pipe, stdout_pipe, + stderr_pipe})) {} + + /** + * @brief Construct a new boost process object + * stdin of the child process is not managed + * + * @param io_context + * @param logger + * @param cmd_line cmd line split (the first element is the path of the + * executable) + * @param no_stdin (not used) + */ + boost_process(asio::io_context& io_context, + const std::string& exe_path, + const std::vector& args, + bool no_stdin) + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), + proc(proc::posix::centreon_posix_default_launcher()( + io_context, + exe_path, + args, + proc::posix::centreon_process_stdio{{}, + stdout_pipe, + stderr_pipe})) {} - asio::readable_pipe stdout; - asio::readable_pipe stderr; - asio::writable_pipe stdin; +#endif + + asio::readable_pipe stdout_pipe; + asio::readable_pipe stderr_pipe; + asio::writable_pipe stdin_pipe; proc::process proc; }; } // namespace com::centreon::common::detail @@ -82,16 +164,23 @@ process::process(const std::shared_ptr& io_context, * In this function, we start child process and stdout, stderr asynchronous read * we also start an asynchronous read on process fd to be aware of child process * termination + * + * @param enable_stdin On Windows set it to false if you doesn't want to write + * on child stdin */ -void process::start_process() { +void process::start_process(bool enable_stdin) { SPDLOG_LOGGER_DEBUG(_logger, "start process: {}", _exe_path); absl::MutexLock l(&_protect); _stdin_write_queue.clear(); _write_pending = false; try { - _proc = - std::make_shared(*_io_context, _exe_path, _args); + _proc = enable_stdin ? std::make_shared( + *_io_context, _exe_path, _args) + : std::make_shared( + *_io_context, _exe_path, _args, false); + SPDLOG_LOGGER_TRACE(_logger, "process started: {} pid: {}", _exe_path, + _proc->proc.id()); _proc->proc.async_wait( [me = shared_from_this(), current = _proc]( const boost::system::error_code& err, int raw_exit_status) { @@ -173,7 +262,7 @@ void process::stdin_write_no_lock(const std::shared_ptr& data) { } else { try { _write_pending = true; - _proc->stdin.async_write_some( + _proc->stdin_pipe.async_write_some( asio::buffer(*data), [me = shared_from_this(), caller = _proc, data]( const boost::system::error_code& err, size_t nb_written) { @@ -229,7 +318,7 @@ void process::on_stdin_write(const boost::system::error_code& err) { void process::stdout_read() { if (_proc) { try { - _proc->stdout.async_read_some( + _proc->stdout_pipe.async_read_some( asio::buffer(_stdout_read_buffer), [me = shared_from_this(), caller = _proc]( const boost::system::error_code& err, size_t nb_read) { @@ -259,12 +348,12 @@ void process::stdout_read() { void process::on_stdout_read(const boost::system::error_code& err, size_t nb_read) { if (err) { - if (err == asio::error::eof) { + if (err == asio::error::eof || err == asio::error::broken_pipe) { SPDLOG_LOGGER_DEBUG(_logger, "fail read from stdout of process {}: {}", _exe_path, err.message()); } else { - SPDLOG_LOGGER_ERROR(_logger, "fail read from stdout of process {}: {}", - _exe_path, err.message()); + SPDLOG_LOGGER_ERROR(_logger, "fail read from stdout of process {}: {} {}", + _exe_path, err.value(), err.message()); } return; } @@ -280,7 +369,7 @@ void process::on_stdout_read(const boost::system::error_code& err, void process::stderr_read() { if (_proc) { try { - _proc->stderr.async_read_some( + _proc->stderr_pipe.async_read_some( asio::buffer(_stderr_read_buffer), [me = shared_from_this(), caller = _proc]( const boost::system::error_code& err, size_t nb_read) { @@ -310,12 +399,12 @@ void process::stderr_read() { void process::on_stderr_read(const boost::system::error_code& err, size_t nb_read) { if (err) { - if (err == asio::error::eof) { + if (err == asio::error::eof || err == asio::error::broken_pipe) { SPDLOG_LOGGER_DEBUG(_logger, "fail read from stderr of process {}: {}", _exe_path, err.message()); } else { - SPDLOG_LOGGER_ERROR(_logger, "fail read from stderr of process {}: {}", - _exe_path, err.message()); + SPDLOG_LOGGER_ERROR(_logger, "fail read from stderr of process {}: {} {}", + _exe_path, err.value(), err.message()); } } else { SPDLOG_LOGGER_TRACE(_logger, " process: {} read from stdout: {}", _exe_path, diff --git a/common/src/utf8.cc b/common/src/utf8.cc new file mode 100644 index 00000000000..7ff784b7167 --- /dev/null +++ b/common/src/utf8.cc @@ -0,0 +1,275 @@ +/** + * Copyright 2022-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 + */ + +/** + * @brief Checks if the string given as parameter is a real UTF-8 string. + * If it is not, it tries to convert it to UTF-8. Encodings correctly changed + * are ISO-8859-15 and CP-1252. + * + * @param str The string to check + * + * @return The string itself or a new string converted to UTF-8. The output + * string should always be an UTF-8 string. + */ + +#include "utf8.hh" + +std::string com::centreon::common::check_string_utf8( + std::string const& str) noexcept { + std::string::const_iterator it; + for (it = str.begin(); it != str.end();) { + uint32_t val = (*it & 0xff); + if ((val & 0x80) == 0) { + ++it; + continue; + } + val = (val << 8) | (*(it + 1) & 0xff); + if ((val & 0xe0c0) == 0xc080) { + val &= 0x1e00; + if (val == 0) + break; + it += 2; + continue; + } + + val = (val << 8) | (*(it + 2) & 0xff); + if ((val & 0xf0c0c0) == 0xe08080) { + val &= 0xf2000; + if (val == 0 || val == 0xd2000) + break; + it += 3; + continue; + } + + val = (val << 8) | (*(it + 3) & 0xff); + if ((val & 0xf8c0c0c0) == 0xF0808080) { + val &= 0x7300000; + if (val == 0 || val > 0x4000000) + break; + it += 4; + continue; + } + break; + } + + if (it == str.end()) + return str; + + /* Not an UTF-8 string */ + bool is_cp1252 = true, is_iso8859 = true; + auto itt = it; + + auto iso8859_to_utf8 = [&str, &it]() -> std::string { + /* Strings are both cp1252 and iso8859-15 */ + std::string out; + std::size_t d = it - str.begin(); + out.reserve(d + 2 * (str.size() - d)); + out = str.substr(0, d); + while (it != str.end()) { + uint8_t c = static_cast(*it); + if (c < 128) + out.push_back(c); + else if (c <= 160) + out.push_back('_'); + else { + switch (c) { + case 0xa4: + out.append("€"); + break; + case 0xa6: + out.append("Š"); + break; + case 0xa8: + out.append("š"); + break; + case 0xb4: + out.append("Ž"); + break; + case 0xb8: + out.append("ž"); + break; + case 0xbc: + out.append("Œ"); + break; + case 0xbd: + out.append("œ"); + break; + case 0xbe: + out.append("Ÿ"); + break; + default: + out.push_back(0xc0 | c >> 6); + out.push_back((c & 0x3f) | 0x80); + break; + } + } + ++it; + } + return out; + }; + do { + uint8_t c = *itt; + /* not ISO-8859-15 */ + if (c > 126 && c < 160) + is_iso8859 = false; + /* not cp1252 */ + if (c & 128) + if (c == 129 || c == 141 || c == 143 || c == 144 || c == 155) + is_cp1252 = false; + if (!is_cp1252) + return iso8859_to_utf8(); + else if (!is_iso8859) { + std::string out; + std::size_t d = it - str.begin(); + out.reserve(d + 3 * (str.size() - d)); + out = str.substr(0, d); + while (it != str.end()) { + c = *it; + if (c < 128) + out.push_back(c); + else { + switch (c) { + case 128: + out.append("€"); + break; + case 129: + case 141: + case 143: + case 144: + case 157: + out.append("_"); + break; + case 130: + out.append("‚"); + break; + case 131: + out.append("ƒ"); + break; + case 132: + out.append("„"); + break; + case 133: + out.append("…"); + break; + case 134: + out.append("†"); + break; + case 135: + out.append("‡"); + break; + case 136: + out.append("ˆ"); + break; + case 137: + out.append("‰"); + break; + case 138: + out.append("Š"); + break; + case 139: + out.append("‹"); + break; + case 140: + out.append("Œ"); + break; + case 142: + out.append("Ž"); + break; + case 145: + out.append("‘"); + break; + case 146: + out.append("’"); + break; + case 147: + out.append("“"); + break; + case 148: + out.append("”"); + break; + case 149: + out.append("•"); + break; + case 150: + out.append("–"); + break; + case 151: + out.append("—"); + break; + case 152: + out.append("˜"); + break; + case 153: + out.append("™"); + break; + case 154: + out.append("š"); + break; + case 155: + out.append("›"); + break; + case 156: + out.append("œ"); + break; + case 158: + out.append("ž"); + break; + case 159: + out.append("Ÿ"); + break; + default: + out.push_back(0xc0 | c >> 6); + out.push_back((c & 0x3f) | 0x80); + break; + } + } + ++it; + } + return out; + } + ++itt; + } while (itt != str.end()); + assert(is_cp1252 == is_iso8859); + return iso8859_to_utf8(); +} + +/** + * @brief This function adjusts the given integer s so that the str string may + * be cut at this length and still be a UTF-8 string (we don't want to cut it + * in a middle of a character). + * + * This function assumes the string to be UTF-8 encoded. + * + * @param str A string to truncate. + * @param s The desired size, maybe the resulting string will contain less + * characters. + * + * @return The newly computed size. + */ +size_t com::centreon::common::adjust_size_utf8(const std::string& str, + size_t s) { + if (s >= str.size()) + return str.size(); + if (s == 0) + return s; + else { + while ((str[s] & 0xc0) == 0x80) + s--; + return s; + } +} diff --git a/common/tests/CMakeLists.txt b/common/tests/CMakeLists.txt index fd673759850..afd6c972eb8 100644 --- a/common/tests/CMakeLists.txt +++ b/common/tests/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(ut_common process_test.cc rapidjson_helper_test.cc test_main.cc + utf8_test.cc ${TESTS_SOURCES}) set_target_properties( diff --git a/common/tests/process_test.cc b/common/tests/process_test.cc index 529c14e63cf..660c35e0d48 100644 --- a/common/tests/process_test.cc +++ b/common/tests/process_test.cc @@ -19,11 +19,18 @@ #include #include -#include "pool.hh" -#include "process.hh" +#include "com/centreon/common/process/process.hh" using namespace com::centreon::common; +#ifdef _WINDOWS +#define ECHO_PATH "tests\\echo.bat" +#define END_OF_LINE "\r\n" +#else +#define ECHO_PATH "/bin/echo" +#define END_OF_LINE "\n" +#endif + extern std::shared_ptr g_io_context; static std::shared_ptr _logger = @@ -111,11 +118,11 @@ class process_wait : public process { TEST_F(process_test, echo) { using namespace std::literals; std::shared_ptr to_wait( - new process_wait(g_io_context, _logger, "/bin/echo", {"hello"s})); - to_wait->start_process(); + new process_wait(g_io_context, _logger, ECHO_PATH, {"hello"s})); + to_wait->start_process(true); to_wait->wait(); ASSERT_EQ(to_wait->get_exit_status(), 0); - ASSERT_EQ(to_wait->get_stdout(), "hello\n"); + ASSERT_EQ(to_wait->get_stdout(), "hello" END_OF_LINE); ASSERT_EQ(to_wait->get_stderr(), ""); } @@ -123,14 +130,19 @@ TEST_F(process_test, throw_on_error) { using namespace std::literals; std::shared_ptr to_wait( new process_wait(g_io_context, _logger, "turlututu", {"hello"s})); - ASSERT_THROW(to_wait->start_process(), std::exception); + ASSERT_THROW(to_wait->start_process(true), std::exception); } TEST_F(process_test, script_error) { using namespace std::literals; +#ifdef _WINDOWS + std::shared_ptr to_wait( + new process_wait(g_io_context, _logger, "tests\\\\bad_script.bat")); +#else std::shared_ptr to_wait( new process_wait(g_io_context, _logger, "/bin/sh", {"taratata"s})); - to_wait->start_process(); +#endif + to_wait->start_process(true); to_wait->wait(); ASSERT_NE(to_wait->get_exit_status(), 0); ASSERT_EQ(to_wait->get_stdout(), ""); @@ -139,12 +151,12 @@ TEST_F(process_test, script_error) { TEST_F(process_test, call_start_several_time) { std::shared_ptr to_wait( - new process_wait(g_io_context, _logger, "/bin/echo", {"hello"})); + new process_wait(g_io_context, _logger, ECHO_PATH, {"hello"})); std::string expected; for (int ii = 0; ii < 10; ++ii) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); - to_wait->start_process(); - expected += "hello\n"; + to_wait->start_process(true); + expected += "hello" END_OF_LINE; } to_wait->wait(); ASSERT_EQ(to_wait->get_exit_status(), 0); @@ -152,6 +164,24 @@ TEST_F(process_test, call_start_several_time) { ASSERT_EQ(to_wait->get_stderr(), ""); } +TEST_F(process_test, call_start_several_time_no_args) { + std::shared_ptr to_wait( + new process_wait(g_io_context, _logger, ECHO_PATH " hello")); + std::string expected; + for (int ii = 0; ii < 10; ++ii) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + to_wait->start_process(true); + expected += "hello" END_OF_LINE; + } + to_wait->wait(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + ASSERT_EQ(to_wait->get_exit_status(), 0); + ASSERT_EQ(to_wait->get_stdout(), expected); + ASSERT_EQ(to_wait->get_stderr(), ""); +} + +#ifndef _WINDOWS + TEST_F(process_test, stdin_to_stdout) { ::remove("toto.sh"); std::ofstream script("toto.sh"); @@ -160,7 +190,7 @@ TEST_F(process_test, stdin_to_stdout) { std::shared_ptr loopback( new process_wait(g_io_context, _logger, "/bin/sh toto.sh")); - loopback->start_process(); + loopback->start_process(true); std::string expected; for (unsigned ii = 0; ii < 10; ++ii) { @@ -179,7 +209,7 @@ TEST_F(process_test, shell_stdin_to_stdout) { std::shared_ptr loopback( new process_wait(g_io_context, _logger, "/bin/sh")); - loopback->start_process(); + loopback->start_process(true); std::string expected; for (unsigned ii = 0; ii < 10; ++ii) { @@ -193,3 +223,5 @@ TEST_F(process_test, shell_stdin_to_stdout) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); ASSERT_EQ(expected, loopback->get_stdout()); } + +#endif diff --git a/common/tests/utf8_test.cc b/common/tests/utf8_test.cc new file mode 100644 index 00000000000..98376f390ce --- /dev/null +++ b/common/tests/utf8_test.cc @@ -0,0 +1,215 @@ +/** + * 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 "utf8.hh" + +using namespace com::centreon::common; + +/* + * Given a string encoded in ISO-8859-15 and CP-1252 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, simple) { + std::string txt("L'acc\350s \340 l'h\364tel est encombr\351"); + ASSERT_EQ(check_string_utf8(txt), "L'accès à l'hôtel est encombré"); +} + +/* + * Given a string encoded in UTF-8 + * Then the check_string_utf8 function returns itself. + */ +TEST(string_check_utf8, utf8) { + std::string txt("L'accès à l'hôtel est encombré"); + ASSERT_EQ(check_string_utf8(txt), "L'accès à l'hôtel est encombré"); +} + +/* + * Given a string encoded in CP-1252 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, cp1252) { + std::string txt("Le ticket co\xfbte 12\x80\n"); + ASSERT_EQ(check_string_utf8(txt), "Le ticket coûte 12€\n"); +} + +/* + * Given a string encoded in ISO-8859-15 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, iso8859) { + std::string txt("Le ticket co\xfbte 12\xa4\n"); + ASSERT_EQ(check_string_utf8(txt), "Le ticket coûte 12€\n"); +} + +/* + * Given a string encoded in ISO-8859-15 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, iso8859_cpx) { + std::string txt("\xa4\xa6\xa8\xb4\xb8\xbc\xbd\xbe"); + ASSERT_EQ(check_string_utf8(txt), "€ŠšŽžŒœŸ"); +} + +/* + * Given a string encoded in CP-1252 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, cp1252_cpx) { + std::string txt("\x80\x95\x82\x89\x8a"); + ASSERT_EQ(check_string_utf8(txt), "€•‚‰Š"); +} + +/* + * Given a string badly encoded in CP-1252 + * Then the check_string_utf8 function converts it to UTF-8 and replaces bad + * characters into '_'. + */ +TEST(string_check_utf8, whatever_as_cp1252) { + std::string txt; + for (uint8_t c = 32; c < 255; c++) + if (c != 127) + txt.push_back(c); + std::string result( + " !\"#$%&'()*+,-./" + "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~€_‚ƒ„…†‡ˆ‰Š‹Œ_Ž__‘’“”•–—˜™š›œ_" + "žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäå" + "æçèéêëìíîïðñòóôõö÷øùúûüýþ"); + ASSERT_EQ(check_string_utf8(txt), result); +} + +/* + * Given a string badly encoded in ISO-8859-15 + * Then the check_string_utf8 function converts it to UTF-8 and replaces bad + * characters into '_'. + */ +TEST(string_check_utf8, whatever_as_iso8859) { + /* Construction of a string that is not cp1252 so it should be considered as + * iso8859-15 */ + std::string txt; + for (uint8_t c = 32; c < 255; c++) { + if (c == 32) + txt.push_back(0x81); + if (c != 127) + txt.push_back(c); + } + std::string result( + "_ " + "!\"#$%&'()*+,-./" + "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~_________________________________" + "¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçè" + "éêëìíîïðñòóôõö÷øùúûüýþ"); + ASSERT_EQ(check_string_utf8(txt), result); +} + +/* + * In case of a string containing multiple encoding, the resulting string should + * be an UTF-8 string. Here we have a string beginning with UTF-8 and finishing + * with cp1252. The resulting string is good and is UTF-8 only encoded. + */ +TEST(string_check_utf8, utf8_and_cp1252) { + std::string txt( + "\xc3\xa9\xc3\xa7\xc3\xa8\xc3\xa0\xc3\xb9\xc3\xaf\xc3\xab\x7e\x23\x0a\xe9" + "\xe7\xe8\xe0\xf9\xef\xeb\x7e\x23\x0a"); + std::string result("éçèàùïë~#\néçèàùïë~#\n"); + ASSERT_EQ(check_string_utf8(txt), result); +} + +/* A check coming from windows with characters from the cmd console */ +TEST(string_check_utf8, strange_string) { + std::string txt( + "WARNING - [Triggered by _ItemCount>0] - 1 event(s) of Severity Level: " + "\"Error\", were recorded in the last 24 hours from the Application " + "Event Log. (List is on next line. Fields shown are - " + "Logfile:TimeGenerated:EventId:EventCode:SeverityLevel:Type:SourceName:" + "Message)|'Event " + "Count'=1;0;50;\nApplication:20200806000001.000000-000:3221243278:17806:" + "Erreur:MSSQLSERVER:╔chec de la nÚgociation SSPI avec le code " + "d'erreurá0x8009030c lors de l'Útablissement d'une connexion avec une " + "sÚcuritÚ intÚgrÚeá; la connexion a ÚtÚ fermÚe. [CLIENTá: X.X.X.X]"); + ASSERT_EQ(check_string_utf8(txt), txt); +} + +/* A check coming from windows with characters from the cmd console */ +TEST(string_check_utf8, chinese) { + std::string txt("超级杀手死亡检查"); + ASSERT_EQ(check_string_utf8(txt), txt); +} + +/* A check coming from windows with characters from the cmd console */ +TEST(string_check_utf8, vietnam) { + std::string txt( + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" + "ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong " + "chinese 告警数量 output puté! | '告警数量'=42\navé dé long ouput oçi " + "还有中国人! Hái yǒu zhòng guó rén!"); + ASSERT_EQ(check_string_utf8(txt), txt); +} + +TEST(truncate, nominal1) { + std::string str("foobar"); + ASSERT_EQ(truncate_utf8(str, 3), "foo"); +} + +TEST(truncate, nominal2) { + std::string str("foobar"); + ASSERT_EQ(truncate_utf8(str, 0), ""); +} + +TEST(truncate, nominal3) { + std::string str("foobar 超级杀手死亡检查"); + ASSERT_EQ(truncate_utf8(str, 1000), "foobar 超级杀手死亡检查"); +} + +TEST(truncate, utf8_1) { + std::string str("告警数量"); + for (size_t i = 0; i <= str.size(); i++) { + fmt::string_view tmp(str); + fmt::string_view res(truncate_utf8(tmp, i)); + std::string tmp1(check_string_utf8(std::string(res.data(), res.size()))); + ASSERT_EQ(res, tmp1); + } +} + +TEST(adjust_size_utf8, nominal1) { + std::string str("foobar"); + ASSERT_EQ(fmt::string_view(str.data(), adjust_size_utf8(str, 3)), + fmt::string_view("foo")); +} + +TEST(adjust_size_utf8, nominal2) { + std::string str("foobar"); + ASSERT_EQ(fmt::string_view(str.data(), adjust_size_utf8(str, 0)), ""); +} + +TEST(adjust_size_utf8, nominal3) { + std::string str("foobar 超级杀手死亡检查"); + ASSERT_EQ(fmt::string_view(str.data(), adjust_size_utf8(str, 1000)), str); +} + +TEST(adjust_size_utf8, utf8_1) { + std::string str("告警数量"); + for (size_t i = 0; i <= str.size(); i++) { + fmt::string_view sv(str.data(), adjust_size_utf8(str, i)); + std::string tmp( + check_string_utf8(std::string(sv.data(), sv.data() + sv.size()))); + ASSERT_EQ(sv.size(), tmp.size()); + } +} diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 6b10ba0401a..fa045d94148 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -528,11 +528,13 @@ target_link_libraries( enginerpc centreon_grpc centreon_http + centreon_common -L${Boost_LIBRARY_DIR_RELEASE} boost_url cce_core gRPC::grpc++ boost_program_options + protobuf "-Wl,--no-whole-archive" gRPC::gpr gRPC::grpc diff --git a/engine/modules/opentelemetry/CMakeLists.txt b/engine/modules/opentelemetry/CMakeLists.txt index 0146d3f81de..8607d6db378 100644 --- a/engine/modules/opentelemetry/CMakeLists.txt +++ b/engine/modules/opentelemetry/CMakeLists.txt @@ -20,7 +20,6 @@ set(MODULE_DIR "${PROJECT_SOURCE_DIR}/modules/opentelemetry") set(SRC_DIR "${MODULE_DIR}/src") - #protobuf service set(service_files opentelemetry/proto/collector/metrics/v1/metrics_service @@ -42,9 +41,35 @@ foreach(name IN LISTS service_files) endforeach() +#centagent server and client +add_custom_command( + DEPENDS ${CMAKE_SOURCE_DIR}/agent/proto/agent.proto + COMMENT "Generating interface files of the conf centreon_agent proto file (grpc)" + OUTPUT ${SRC_DIR}/centreon_agent/agent.grpc.pb.cc + COMMAND + ${Protobuf_PROTOC_EXECUTABLE} ARGS + --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} + --proto_path=${CMAKE_SOURCE_DIR}/agent/proto --proto_path=${CMAKE_SOURCE_DIR}/opentelemetry-proto + --grpc_out=${SRC_DIR}/centreon_agent ${CMAKE_SOURCE_DIR}/agent/proto/agent.proto + DEPENDS ${CMAKE_SOURCE_DIR}/agent/proto/agent.proto + COMMENT "Generating interface files of the conf centreon_agent proto file (protobuf)" + OUTPUT ${SRC_DIR}/centreon_agent/agent.pb.cc + COMMAND + ${Protobuf_PROTOC_EXECUTABLE} ARGS --cpp_out=${SRC_DIR}/centreon_agent + --proto_path=${CMAKE_SOURCE_DIR}/agent/proto --proto_path=${CMAKE_SOURCE_DIR}/opentelemetry-proto + ${CMAKE_SOURCE_DIR}/agent/proto/agent.proto + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) # mod_externalcmd target. add_library(opentelemetry SHARED +${SRC_DIR}/centreon_agent/agent.grpc.pb.cc +${SRC_DIR}/centreon_agent/agent.pb.cc +${SRC_DIR}/centreon_agent/agent_check_result_builder.cc +${SRC_DIR}/centreon_agent/agent_config.cc +${SRC_DIR}/centreon_agent/agent_impl.cc +${SRC_DIR}/centreon_agent/agent_reverse_client.cc +${SRC_DIR}/centreon_agent/agent_service.cc +${SRC_DIR}/centreon_agent/to_agent_connector.cc ${SRC_DIR}/data_point_fifo.cc ${SRC_DIR}/data_point_fifo_container.cc ${SRC_DIR}/grpc_config.cc @@ -63,7 +88,10 @@ ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.cc target_precompile_headers(opentelemetry PRIVATE precomp_inc/precomp.hh) # set(EXTERNALCMD_MODULE "${EXTERNALCMD_MODULE}" PARENT_SCOPE) -target_link_libraries(opentelemetry spdlog::spdlog) +target_link_libraries(opentelemetry + spdlog::spdlog + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options) add_dependencies(opentelemetry pb_open_telemetry_lib diff --git a/engine/modules/opentelemetry/doc/opentelemetry.md b/engine/modules/opentelemetry/doc/opentelemetry.md index 3ad030d9e8a..379643f2889 100644 --- a/engine/modules/opentelemetry/doc/opentelemetry.md +++ b/engine/modules/opentelemetry/doc/opentelemetry.md @@ -207,3 +207,187 @@ An example of configuration: } } ``` + +### centreon monitoring agent + +#### agent connects to engine +Even if all protobuf objects are opentelemetry objects, grpc communication is made in streaming mode. It is more efficient, it allows reverse connection (engine can connect to an agent running in a DMZ) and +Engine can send configuration on each config update. +You can find all grpc definitions are agent/proto/agent.proto. +Every time engine configuration is updated, we calculate configuration for each connected agent and send it on the wire if we find a difference with the old configuration. That's why each connection has a ```agent::MessageToAgent _last_config``` attribute. +So, the opentelemetry engine server supports two services, opentelemetry service and agent streaming service. +OpenTelemetry data is different from telegraf one: +* host service attributes are stored in resource_metrics.resource.attributes +* performance data (min, max, critical lt, warning gt...) is stored in exemplar, service status is stored in status metric + +Example for metric output ```OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;```: +```json +resource_metrics { + resource { + attributes { + key: "host.name" + value { + string_value: "host_1" + } + } + attributes { + key: "service.name" + value { + string_value: "" + } + } + } + scope_metrics { + metrics { + name: "status" + description: "OK - 127.0.0.1: rta 0,010ms, lost 0%" + gauge { + data_points { + time_unix_nano: 1719911975421977886 + as_int: 0 + } + } + } + metrics { + name: "rta" + unit: "ms" + gauge { + data_points { + time_unix_nano: 1719911975421977886 + exemplars { + as_double: 500 + filtered_attributes { + key: "crit_gt" + } + } + exemplars { + as_double: 0 + filtered_attributes { + key: "crit_lt" + } + } + exemplars { + as_double: 200 + filtered_attributes { + key: "warn_gt" + } + } + exemplars { + as_double: 0 + filtered_attributes { + key: "warn_lt" + } + } + exemplars { + as_double: 0 + filtered_attributes { + key: "min" + } + } + as_double: 0 + } + } + } + metrics { + name: "pl" + unit: "%" + gauge { + data_points { + time_unix_nano: 1719911975421977886 + exemplars { + as_double: 80 + filtered_attributes { + key: "crit_gt" + } + } + exemplars { + as_double: 0 + filtered_attributes { + key: "crit_lt" + } + } + exemplars { + as_double: 40 + filtered_attributes { + key: "warn_gt" + } + } + exemplars { + as_double: 0 + filtered_attributes { + key: "warn_lt" + } + } + as_double: 0 + } + } + } + metrics { + name: "rtmax" + unit: "ms" + gauge { + data_points { + time_unix_nano: 1719911975421977886 + as_double: 0 + } + } + } + metrics { + name: "rtmin" + unit: "ms" + gauge { + data_points { + time_unix_nano: 1719911975421977886 + as_double: 0 + } + } + } + } +}``` + +Parsing of this format is done by ```agent_check_result_builder``` class + +Configuration of agent is divided in two parts: +* A common part to all agents: + ```protobuf + uint32 check_interval = 2; + //limit the number of active checks in order to limit charge + uint32 max_concurrent_checks = 3; + //period of metric exports (in seconds) + uint32 export_period = 4; + //after this timeout, process is killed (in seconds) + uint32 check_timeout = 5; + ``` +* A list of services that agent has to check + +The first part is owned by agent protobuf service (agent_service.cc), the second is build by a common code shared with telegraf server (conf_helper.hh) + +So when centengine receives a HUP signal, opentelemetry::reload check configuration changes on each established connection and update also agent service conf part1 which is used to configure future incoming connections. + +#### engine connects to agent + +##### configuration +Each agent has its own grpc configuration. Each object in this array is a grpc configuration object like those we can find in Agent or server + +An example: +```json +{ + "max_length_grpc_log": 0, + "centreon_agent": { + "check_interval": 10, + "export_period": 15, + "reverse_connections": [ + { + "host": "127.0.0.1", + "port": 4317 + } + ] + } +} +``` + +#### classes +From this configuration an agent_reverse_client object maintains a list of endpoints engine has to connect to. It manages also agent list updates. +It contains a map of to_agent_connector indexed by config. +The role to_agent_connector is to maintain an alive connection to agent (agent_connection class). It owns an agent_connection class and recreates it in case of network failure. +Agent_connection holds a weak_ptr to agent_connection to warn it about connection failure. \ No newline at end of file diff --git a/engine/modules/opentelemetry/doc/otel_configuration.odg b/engine/modules/opentelemetry/doc/otel_configuration.odg new file mode 100644 index 0000000000000000000000000000000000000000..c14e698328b87709a3ab132b81e0a30a53c1bf1e GIT binary patch literal 24096 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 logger) + : otl_check_result_builder(cmd_line, + command_id, + host, + service, + timeout, + std::move(handler), + logger) {} +}; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +#endif \ No newline at end of file diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh new file mode 100644 index 00000000000..f65940cbf92 --- /dev/null +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh @@ -0,0 +1,76 @@ +/** + * 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 CCE_MOD_OTL_CENTREON_AGENT_AGENT_CONFIG_HH +#define CCE_MOD_OTL_CENTREON_AGENT_AGENT_CONFIG_HH + +#include "com/centreon/engine/modules/opentelemetry/grpc_config.hh" + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +class agent_config { + public: + using grpc_config_set = + absl::btree_set; + + using pointer = std::shared_ptr; + + private: + // all endpoints engine has to connect to + grpc_config_set _agent_grpc_reverse_conf; + // delay between 2 checks of one service, so we will do all check in that + // period (in seconds) + uint32_t _check_interval; + // limit the number of active checks in order to limit charge + uint32_t _max_concurrent_checks; + // period of metric exports (in seconds) + uint32_t _export_period; + // after this timeout, process is killed (in seconds) + uint32_t _check_timeout; + + public: + agent_config(const rapidjson::Value& json_config_v); + + // used for tests + agent_config(uint32_t check_interval, + uint32_t max_concurrent_checks, + uint32_t export_period, + uint32_t check_timeout); + + agent_config(uint32_t check_interval, + uint32_t max_concurrent_checks, + uint32_t export_period, + uint32_t check_timeout, + const std::initializer_list& endpoints); + + const grpc_config_set& get_agent_grpc_reverse_conf() const { + return _agent_grpc_reverse_conf; + } + + uint32_t get_check_interval() const { return _check_interval; } + uint32_t get_max_concurrent_checks() const { return _max_concurrent_checks; } + uint32_t get_export_period() const { return _export_period; } + uint32_t get_check_timeout() const { return _check_timeout; } + + bool operator==(const agent_config& right) const; + + bool operator!=(const agent_config& right) const { return !(*this == right); } +}; + +}; // namespace com::centreon::engine::modules::opentelemetry::centreon_agent +#endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_impl.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_impl.hh new file mode 100644 index 00000000000..41d63ac029c --- /dev/null +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_impl.hh @@ -0,0 +1,114 @@ +/** + * 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 CCE_MOD_OTL_CENTREON_AGENT_AGENT_IMPL_HH +#define CCE_MOD_OTL_CENTREON_AGENT_AGENT_IMPL_HH + +#include "centreon_agent/agent.grpc.pb.h" +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_data_point.hh" + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +/** + * @brief this class manages connection with centreon monitoring agent + * reverse connection or no + * + * @tparam bireactor_class (grpc::bireactor<,>) + */ +template +class agent_impl + : public bireactor_class, + public std::enable_shared_from_this> { + std::shared_ptr _io_context; + const std::string_view _class_name; + + agent_config::pointer _conf ABSL_GUARDED_BY(_protect); + + metric_handler _metric_handler; + + std::shared_ptr _agent_info + ABSL_GUARDED_BY(_protect); + std::shared_ptr _last_sent_config + ABSL_GUARDED_BY(_protect); + + static std::set> _instances + ABSL_GUARDED_BY(_instances_m); + static absl::Mutex _instances_m; + + bool _write_pending; + std::deque> _write_queue + ABSL_GUARDED_BY(_protect); + std::shared_ptr _read_current + ABSL_GUARDED_BY(_protect); + + void _calc_and_send_config_if_needed(); + + virtual const std::string& get_peer() const = 0; + + void _write(const std::shared_ptr& request); + + protected: + std::shared_ptr _logger; + bool _alive ABSL_GUARDED_BY(_protect); + mutable absl::Mutex _protect; + + public: + agent_impl(const std::shared_ptr& io_context, + const std::string_view class_name, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger); + + virtual ~agent_impl(); + + void calc_and_send_config_if_needed(const agent_config::pointer& new_conf); + + static void all_agent_calc_and_send_config_if_needed( + const agent_config::pointer& new_conf); + + static void update_config(); + + void on_request(const std::shared_ptr& request); + + static void register_stream(const std::shared_ptr& strm); + + void start_read(); + + void start_write(); + + // bireactor part + void OnReadDone(bool ok) override; + + virtual void on_error() = 0; + + void OnWriteDone(bool ok) override; + + // server version + void OnDone(); + // client version + void OnDone(const ::grpc::Status& /*s*/); + + virtual void shutdown(); + + static void shutdown_all(); +}; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +#endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh new file mode 100644 index 00000000000..cc02b91e8af --- /dev/null +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh @@ -0,0 +1,62 @@ +/** + * 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 CCE_MOD_OTL_CENTREON_AGENT_AGENT_REVERSE_CLIENT_HH +#define CCE_MOD_OTL_CENTREON_AGENT_AGENT_REVERSE_CLIENT_HH + +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_data_point.hh" + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +class to_agent_connector; + +class agent_reverse_client { + protected: + std::shared_ptr _io_context; + agent_config::pointer _conf; + const metric_handler _metric_handler; + std::shared_ptr _logger; + + using config_to_client = absl::btree_map, + grpc_config_compare>; + absl::Mutex _agents_m; + config_to_client _agents ABSL_GUARDED_BY(_agents_m); + + virtual config_to_client::iterator _create_new_client_connection( + const grpc_config::pointer& agent_endpoint, + const agent_config::pointer& agent_conf) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(_agents_m); + + virtual void _shutdown_connection(config_to_client::const_iterator to_delete); + + public: + agent_reverse_client( + const std::shared_ptr& io_context, + const metric_handler& handler, + const std::shared_ptr& logger); + + virtual ~agent_reverse_client(); + + void update(const agent_config::pointer& new_conf); +}; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +#endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_service.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_service.hh new file mode 100644 index 00000000000..a58f8263a50 --- /dev/null +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_service.hh @@ -0,0 +1,75 @@ +/** + * 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 CCE_MOD_OTL_CENTREON_AGENT_AGENT_SERVICE_HH +#define CCE_MOD_OTL_CENTREON_AGENT_AGENT_SERVICE_HH + +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh" +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_impl.hh" + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +/** + * @brief this class is a grpc service provided by otel_server for incoming + * centreon monitoring agent connection + * + */ +class agent_service : public agent::AgentService::Service, + public std::enable_shared_from_this { + std::shared_ptr _io_context; + agent_config::pointer _conf; + absl::Mutex _conf_m; + + metric_handler _metric_handler; + std::shared_ptr _logger; + + public: + agent_service(const std::shared_ptr& io_context, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger); + + void init(); + + static std::shared_ptr load( + const std::shared_ptr& io_context, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger); + + // disable synchronous version of this method + ::grpc::Status Export( + ::grpc::ServerContext* /*context*/, + ::grpc::ServerReaderWriter* /*stream*/) + override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + + ::grpc::ServerBidiReactor* + Export(::grpc::CallbackServerContext* context); + + void update(const agent_config::pointer& conf); + + static void shutdown_all_accepted(); +}; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +#endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh new file mode 100644 index 00000000000..3fc016aebb9 --- /dev/null +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh @@ -0,0 +1,78 @@ +/** + * 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 CCE_MOD_OTL_CENTREON_AGENT_AGENT_CLIENT_HH +#define CCE_MOD_OTL_CENTREON_AGENT_AGENT_CLIENT_HH + +#include "centreon_agent/agent.grpc.pb.h" +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh" + +#include "com/centreon/common/grpc/grpc_client.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_data_point.hh" + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +class agent_connection; + +/** + * @brief this class is used in case of reverse connection + * it maintains one connection to agent server and reconnect in case of failure + * + */ +class to_agent_connector + : public common::grpc::grpc_client_base, + public std::enable_shared_from_this { + std::shared_ptr _io_context; + metric_handler _metric_handler; + agent_config::pointer _conf; + + bool _alive; + std::unique_ptr _stub; + + absl::Mutex _connection_m; + std::shared_ptr _connection ABSL_GUARDED_BY(_connection_m); + + public: + to_agent_connector(const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr& logger); + + virtual ~to_agent_connector(); + + virtual void start(); + + static std::shared_ptr load( + const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr& logger); + + void refresh_agent_configuration_if_needed( + const agent_config::pointer& new_conf); + + virtual void shutdown(); + + void on_error(); +}; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +#endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh index c3a0456eeae..a2bbb242c08 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh @@ -41,15 +41,17 @@ bool get_otel_commands(const std::string& host_name, command_handler&& handler, const std::shared_ptr& logger) { auto use_otl_command = [](const checkable& to_test) -> bool { - if (to_test.get_check_command_ptr()->get_type() == - commands::command::e_type::otel) - return true; - if (to_test.get_check_command_ptr()->get_type() == - commands::command::e_type::forward) { - return std::static_pointer_cast( - to_test.get_check_command_ptr()) - ->get_sub_command() - ->get_type() == commands::command::e_type::otel; + if (to_test.get_check_command_ptr()) { + if (to_test.get_check_command_ptr()->get_type() == + commands::command::e_type::otel) + return true; + if (to_test.get_check_command_ptr()->get_type() == + commands::command::e_type::forward) { + return std::static_pointer_cast( + to_test.get_check_command_ptr()) + ->get_sub_command() + ->get_type() == commands::command::e_type::otel; + } } return false; }; diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/grpc_config.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/grpc_config.hh index a31149670f7..8775f42c420 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/grpc_config.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/grpc_config.hh @@ -43,6 +43,14 @@ class grpc_config : public common::grpc::grpc_config { return !(*this == right); } }; + +struct grpc_config_compare { + bool operator()(const grpc_config::pointer& left, + const grpc_config::pointer& right) const { + return left->compare(*right) < 0; + } +}; + } // namespace com::centreon::engine::modules::opentelemetry #endif // !CCE_MOD_OTL_SERVER_GRPC_CONFIG_HH diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh index b558b07c4e4..aa601e0c951 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh @@ -22,6 +22,7 @@ #include "com/centreon/engine/commands/otel_interface.hh" +#include "centreon_agent/agent_reverse_client.hh" #include "data_point_fifo_container.hh" #include "host_serv_extractor.hh" #include "otl_check_result_builder.hh" @@ -48,6 +49,7 @@ class open_telemetry : public commands::otel::open_telemetry_base { asio::system_timer _second_timer; std::shared_ptr _otl_server; std::shared_ptr _telegraf_conf_server; + std::unique_ptr _agent_reverse_client; using cmd_line_to_extractor_map = absl::btree_map>; @@ -98,7 +100,9 @@ class open_telemetry : public commands::otel::open_telemetry_base { const telegraf::conf_server_config::pointer& conf); protected: - virtual void _create_otl_server(const grpc_config::pointer& server_conf); + virtual void _create_otl_server( + const grpc_config::pointer& server_conf, + const centreon_agent::agent_config::pointer& agent_conf); void _on_metric(const metric_request_ptr& metric); void _reload(); void _start_second_timer(); diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_check_result_builder.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_check_result_builder.hh index 2c1d3526819..71b44670c3a 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_check_result_builder.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_check_result_builder.hh @@ -35,7 +35,10 @@ class data_point_fifo_container; class check_result_builder_config : public commands::otel::check_result_builder_config { public: - enum class converter_type { nagios_check_result_builder }; + enum class converter_type { + nagios_check_result_builder, + centreon_agent_check_result_builder + }; private: const converter_type _type; diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_config.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_config.hh index 16276151653..5b87b0db2fb 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_config.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_config.hh @@ -19,6 +19,7 @@ #ifndef CCE_MOD_OTL_SERVER_OTLCONFIG_HH #define CCE_MOD_OTL_SERVER_OTLCONFIG_HH +#include "centreon_agent/agent_config.hh" #include "grpc_config.hh" #include "telegraf/conf_server.hh" @@ -27,6 +28,8 @@ class otl_config { grpc_config::pointer _grpc_conf; telegraf::conf_server_config::pointer _telegraf_conf_server_config; + centreon_agent::agent_config::pointer _centreon_agent_config; + int _max_length_grpc_log = -1; // all otel are logged if negative bool _json_grpc_log = false; // if true, otel object are logged in json // format instead of protobuf debug format @@ -46,6 +49,10 @@ class otl_config { return _telegraf_conf_server_config; } + centreon_agent::agent_config::pointer get_centreon_agent_config() const { + return _centreon_agent_config; + } + int get_max_length_grpc_log() const { return _max_length_grpc_log; } bool get_json_grpc_log() const { return _json_grpc_log; } diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_data_point.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_data_point.hh index 1e0ca128278..bad1bc2236e 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_data_point.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_data_point.hh @@ -88,6 +88,13 @@ using metric_request_ptr = std::shared_ptr<::opentelemetry::proto::collector::metrics::v1:: ExportMetricsServiceRequest>; +/** + * @brief the server grpc model used is the callback model + * So you need to give to the server this handler to handle incoming requests + * + */ +using metric_handler = std::function; + /** * @brief some metrics will be computed and other not * This bean represents a DataPoint, it embeds all ExportMetricsServiceRequest @@ -113,6 +120,8 @@ class otl_data_point { const google::protobuf::Message& _data_point; const ::google::protobuf::RepeatedPtrField< ::opentelemetry::proto::common::v1::KeyValue>& _data_point_attributes; + const ::google::protobuf::RepeatedPtrField< + ::opentelemetry::proto::metrics::v1::Exemplar>& _exemplars; uint64_t _nano_timestamp; data_point_type _type; double _value; @@ -176,6 +185,12 @@ class otl_data_point { double get_value() const { return _value; } + const ::google::protobuf::RepeatedPtrField< + ::opentelemetry::proto::metrics::v1::Exemplar>& + get_exemplars() const { + return _exemplars; + } + template static void extract_data_points(const metric_request_ptr& metrics, data_point_handler&& handler); diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_fmt.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_fmt.hh index c50048a6d0b..40c2facfd18 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_fmt.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_fmt.hh @@ -63,6 +63,72 @@ struct formatter< } }; +template <> +struct formatter + : formatter { + /** + * @brief if this static parameter is < 0, we dump all request, otherwise, we + * limit dump length to this value + * + */ + template + auto format(const com::centreon::agent::MessageFromAgent& p, + FormatContext& ctx) const -> decltype(ctx.out()) { + using otl_formatter = + formatter< ::opentelemetry::proto::collector::metrics::v1:: + ExportMetricsServiceRequest>; + + if (otl_formatter::json_grpc_format) { + std::string output; + google::protobuf::util::MessageToJsonString(p, &output); + return formatter::format( + otl_formatter::max_length_log > 0 + ? output.substr(0, otl_formatter::max_length_log) + : output, + ctx); + } else { + return formatter::format( + otl_formatter::max_length_log > 0 + ? p.ShortDebugString().substr(0, otl_formatter::max_length_log) + : p.ShortDebugString(), + ctx); + } + } +}; + +template <> +struct formatter + : formatter { + /** + * @brief if this static parameter is < 0, we dump all request, otherwise, we + * limit dump length to this value + * + */ + template + auto format(const com::centreon::agent::MessageToAgent& p, + FormatContext& ctx) const -> decltype(ctx.out()) { + using otl_formatter = + formatter< ::opentelemetry::proto::collector::metrics::v1:: + ExportMetricsServiceRequest>; + + if (otl_formatter::json_grpc_format) { + std::string output; + google::protobuf::util::MessageToJsonString(p, &output); + return formatter::format( + otl_formatter::max_length_log > 0 + ? output.substr(0, otl_formatter::max_length_log) + : output, + ctx); + } else { + return formatter::format( + otl_formatter::max_length_log > 0 + ? p.ShortDebugString().substr(0, otl_formatter::max_length_log) + : p.ShortDebugString(), + ctx); + } + } +}; + }; // namespace fmt #endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_server.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_server.hh index 0dd766bb982..935aac30d9c 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_server.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/otl_server.hh @@ -24,6 +24,7 @@ #include "otl_data_point.hh" #include "com/centreon/common/grpc/grpc_server.hh" +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_service.hh" namespace com::centreon::engine::modules::opentelemetry { @@ -31,13 +32,6 @@ namespace detail { class metric_service; }; -/** - * @brief the server grpc model used is the callback model - * So you need to give to the server this handler to handle incoming requests - * - */ -using metric_handler = std::function; - /** * @brief grpc metric receiver server * must be constructed with load method @@ -45,8 +39,12 @@ using metric_handler = std::function; */ class otl_server : public common::grpc::grpc_server_base { std::shared_ptr _service; + std::shared_ptr _agent_service; + absl::Mutex _protect; - otl_server(const grpc_config::pointer& conf, + otl_server(const std::shared_ptr& io_context, + const grpc_config::pointer& conf, + const centreon_agent::agent_config::pointer& agent_config, const metric_handler& handler, const std::shared_ptr& logger); void start(); @@ -56,9 +54,15 @@ class otl_server : public common::grpc::grpc_server_base { ~otl_server(); - static pointer load(const grpc_config::pointer& conf, - const metric_handler& handler, - const std::shared_ptr& logger); + static pointer load( + const std::shared_ptr& io_context, + const grpc_config::pointer& conf, + const centreon_agent::agent_config::pointer& agent_config, + const metric_handler& handler, + const std::shared_ptr& logger); + + void update_agent_config( + const centreon_agent::agent_config::pointer& agent_config); }; } // namespace com::centreon::engine::modules::opentelemetry diff --git a/engine/modules/opentelemetry/precomp_inc/precomp.hh b/engine/modules/opentelemetry/precomp_inc/precomp.hh index 67a56f7e324..de025ed071d 100644 --- a/engine/modules/opentelemetry/precomp_inc/precomp.hh +++ b/engine/modules/opentelemetry/precomp_inc/precomp.hh @@ -25,6 +25,7 @@ #include #include +#include #include #include #include diff --git a/engine/modules/opentelemetry/src/centreon_agent/agent_check_result_builder.cc b/engine/modules/opentelemetry/src/centreon_agent/agent_check_result_builder.cc new file mode 100644 index 00000000000..769869ea12e --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/agent_check_result_builder.cc @@ -0,0 +1,185 @@ +/** + * 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 "data_point_fifo_container.hh" + +#include "otl_check_result_builder.hh" + +#include "centreon_agent/agent_check_result_builder.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent:: + detail { + +/** + * @brief used to create centreon perfdata from agent metric data + * + */ +struct perf_data { + std::optional warning_le, warning_lt, warning_ge, warning_gt; + std::optional critical_le, critical_lt, critical_ge, critical_gt; + std::optional min, max; + + void apply_exemplar( + const ::opentelemetry::proto::metrics::v1::Exemplar& exemplar); + + void append_to_string(std::string* to_append); + + static const absl::flat_hash_map perf_data::*> + _suffix_to_value; +}; + +const absl::flat_hash_map perf_data::*> + perf_data::_suffix_to_value = {{"warn_le", &perf_data::warning_le}, + {"warn_lt", &perf_data::warning_lt}, + {"warn_ge", &perf_data::warning_ge}, + {"warn_gt", &perf_data::warning_gt}, + {"crit_le", &perf_data::critical_le}, + {"crit_lt", &perf_data::critical_lt}, + {"crit_ge", &perf_data::critical_ge}, + {"crit_gt", &perf_data::critical_gt}, + {"min", &perf_data::min}, + {"max", &perf_data::max}}; + +/** + * @brief all metrics sub values are stored in exemplars, so we apply above + * table to perfdata + * + * @param exemplar + */ +void perf_data::apply_exemplar( + const ::opentelemetry::proto::metrics::v1::Exemplar& exemplar) { + if (!exemplar.filtered_attributes().empty()) { + auto search = + _suffix_to_value.find(exemplar.filtered_attributes().begin()->key()); + if (search != _suffix_to_value.end()) { + this->*search->second = exemplar.as_double(); + } + } +} + +/** + * @brief create a nagios style perfdata string from protobuf received data + * + * @param to_append + */ +void perf_data::append_to_string(std::string* to_append) { + if (warning_le) { + absl::StrAppend(to_append, "@", *warning_le, ":"); + if (warning_ge) + absl::StrAppend(to_append, *warning_ge); + } else if (warning_ge) { + absl::StrAppend(to_append, "@~:", *warning_ge); + } else if (warning_lt) { + absl::StrAppend(to_append, *warning_lt, ":"); + if (warning_gt) + absl::StrAppend(to_append, *warning_gt); + } else if (warning_gt) { + absl::StrAppend(to_append, "~:", *warning_gt); + } + to_append->push_back(';'); + if (critical_le) { + absl::StrAppend(to_append, "@", *critical_le, ":"); + if (critical_ge) + absl::StrAppend(to_append, *critical_ge); + } else if (critical_ge) { + absl::StrAppend(to_append, "@~:", *critical_ge); + } else if (critical_lt) { + absl::StrAppend(to_append, *critical_lt, ":"); + if (critical_gt) + absl::StrAppend(to_append, *critical_gt); + } else if (critical_gt) { + absl::StrAppend(to_append, "~:", *critical_gt); + } + to_append->push_back(';'); + if (min) + absl::StrAppend(to_append, *min); + to_append->push_back(';'); + if (max) + absl::StrAppend(to_append, *max); +} + +} // namespace + // com::centreon::engine::modules::opentelemetry::centreon_agent::detail + +/** + * @brief + * + * @param fifos all metrics for a given service + * @param res + * @return true + * @return false + */ +bool agent_check_result_builder::_build_result_from_metrics( + metric_name_to_fifo& fifos, + commands::result& res) { + // first we search last state timestamp from status + uint64_t last_time = 0; + + for (auto& metric_to_fifo : fifos) { + if (metric_to_fifo.first == "status") { + auto& fifo = metric_to_fifo.second.get_fifo(); + if (!fifo.empty()) { + const auto& last_sample = *fifo.rbegin(); + last_time = last_sample.get_nano_timestamp(); + res.exit_code = last_sample.get_value(); + // output of plugins is stored in description metric field + res.output = last_sample.get_metric().description(); + metric_to_fifo.second.clean_oldest(last_time); + } + break; + } + } + if (!last_time) { + return false; + } + res.command_id = get_command_id(); + res.exit_status = process::normal; + res.end_time = res.start_time = + timestamp(last_time / 1000000000, (last_time / 1000) % 1000000); + + res.output.push_back('|'); + + for (auto& metric_to_fifo : fifos) { + if (metric_to_fifo.first == "status") + continue; + auto& fifo = metric_to_fifo.second.get_fifo(); + auto data_pt_search = fifo.find(last_time); + if (data_pt_search != fifo.end()) { + res.output.push_back(' '); + const otl_data_point& data_pt = *data_pt_search; + absl::StrAppend(&res.output, metric_to_fifo.first, "=", + data_pt.get_value(), data_pt.get_metric().unit(), ";"); + + // all other metric value (warning_lt, critical_gt, min... are stored in + // exemplars) + detail::perf_data to_append; + for (const auto& exemplar : data_pt.get_exemplars()) { + to_append.apply_exemplar(exemplar); + } + to_append.append_to_string(&res.output); + } + metric_to_fifo.second.clean_oldest(last_time); + } + + data_point_fifo_container::clean_empty_fifos(fifos); + + return true; +} diff --git a/engine/modules/opentelemetry/src/centreon_agent/agent_config.cc b/engine/modules/opentelemetry/src/centreon_agent/agent_config.cc new file mode 100644 index 00000000000..0d49927f5c7 --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/agent_config.cc @@ -0,0 +1,154 @@ +/** + * 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 "com/centreon/common/rapidjson_helper.hh" + +#include "centreon_agent/agent_config.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; +using namespace com::centreon::common; + +static constexpr std::string_view _config_schema(R"( +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "centreon agent config", + "properties": { + "check_interval": { + "description": "interval in seconds between two checks", + "type": "integer", + "minimum": 10 + }, + "max_concurrent_checks": { + "description": "maximum of running checks at the same time", + "type": "integer", + "minimum": 1 + }, + "export_period": { + "description": "period in second of agent metric export", + "type": "integer", + "minimum": 10 + }, + "check_timeout": { + "description": "check running timeout", + "type": "integer", + "minimum": 1 + }, + "reverse_connections": { + "description": "array of agent endpoints (reverse mode, engine connects to centreon-agent) ", + "type": "array", + "items": { + "type" : "object" + } + } + }, + "type": "object" +} +)"); + +/** + * @brief Construct a new agent config::agent from json data + * + * @param json_config_v + */ +agent_config::agent_config(const rapidjson::Value& json_config_v) { + static json_validator validator(_config_schema); + + rapidjson_helper file_content(json_config_v); + + file_content.validate(validator); + + _check_interval = file_content.get_unsigned("check_interval", 60); + _max_concurrent_checks = + file_content.get_unsigned("max_concurrent_checks", 100); + _export_period = file_content.get_unsigned("export_period", 60); + _check_timeout = file_content.get_unsigned("check_timeout", 30); + + if (file_content.has_member("reverse_connections")) { + const auto& reverse_array = file_content.get_member("reverse_connections"); + for (auto conf_iter = reverse_array.Begin(); + conf_iter != reverse_array.End(); ++conf_iter) { + _agent_grpc_reverse_conf.insert( + std::make_shared(*conf_iter)); + } + } +} + +/** + * @brief Constructor used by tests + * + * @param check_interval + * @param max_concurrent_checks + * @param export_period + * @param check_timeout + */ +agent_config::agent_config(uint32_t check_interval, + uint32_t max_concurrent_checks, + uint32_t export_period, + uint32_t check_timeout) + : _check_interval(check_interval), + _max_concurrent_checks(max_concurrent_checks), + _export_period(export_period), + _check_timeout(check_timeout) {} + +/** + * @brief Constructor used by tests + * + * @param check_interval + * @param max_concurrent_checks + * @param export_period + * @param check_timeout + * @param endpoints + */ +agent_config::agent_config( + uint32_t check_interval, + uint32_t max_concurrent_checks, + uint32_t export_period, + uint32_t check_timeout, + const std::initializer_list& endpoints) + : _agent_grpc_reverse_conf(endpoints), + _check_interval(check_interval), + _max_concurrent_checks(max_concurrent_checks), + _export_period(export_period), + _check_timeout(check_timeout) {} + +/** + * @brief equality operator + * + * @param right + * @return true + * @return false + */ +bool agent_config::operator==(const agent_config& right) const { + if (_check_interval != right._check_interval || + _max_concurrent_checks != right._max_concurrent_checks || + _export_period != right._export_period || + _check_timeout != right._check_timeout || + _agent_grpc_reverse_conf.size() != right._agent_grpc_reverse_conf.size()) + return false; + + for (auto rev_conf_left = _agent_grpc_reverse_conf.begin(), + rev_conf_right = right._agent_grpc_reverse_conf.begin(); + rev_conf_left != _agent_grpc_reverse_conf.end(); + ++rev_conf_left, ++rev_conf_right) { + if (**rev_conf_left != **rev_conf_right) + return false; + } + return true; +} diff --git a/engine/modules/opentelemetry/src/centreon_agent/agent_impl.cc b/engine/modules/opentelemetry/src/centreon_agent/agent_impl.cc new file mode 100644 index 00000000000..5db31e4c877 --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/agent_impl.cc @@ -0,0 +1,446 @@ +/** + * 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 "centreon_agent/agent_impl.hh" + +#include "conf_helper.hh" +#include "otl_fmt.hh" + +#include "com/centreon/engine/command_manager.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +/** + * @brief when BiReactor::OnDone is called by grpc layers, we should delete + * this. But this object is even used by others. + * So it's stored in this container and just removed from this container when + * OnDone is called + * This container is also used to push configuration changes to agent + * + * @tparam bireactor_class + */ +template +std::set>> + agent_impl::_instances; + +template +absl::Mutex agent_impl::_instances_m; + +/** + * @brief Construct a new agent impl::agent impl object + * + * @tparam bireactor_class + * @param io_context + * @param class_name + * @param handler handler that will process received metrics + * @param logger + */ +template +agent_impl::agent_impl( + const std::shared_ptr& io_context, + const std::string_view class_name, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger) + : _io_context(io_context), + _class_name(class_name), + _conf(conf), + _metric_handler(handler), + _logger(logger), + _write_pending(false), + _alive(true) { + SPDLOG_LOGGER_DEBUG(logger, "create {} this={:p}", _class_name, + static_cast(this)); +} + +/** + * @brief Destroy the agent impl::agent impl object + * + * @tparam bireactor_class + */ +template +agent_impl::~agent_impl() { + SPDLOG_LOGGER_DEBUG(_logger, "delete {} this={:p}", _class_name, + static_cast(this)); +} + +/** + * @brief just call _calc_and_send_config_if_needed in main engine thread + * + * @tparam bireactor_class + */ +template +void agent_impl::calc_and_send_config_if_needed( + const agent_config::pointer& new_conf) { + { + absl::MutexLock l(&_protect); + _conf = new_conf; + } + auto to_call = std::packaged_task( + [me = std::enable_shared_from_this>:: + shared_from_this()]() mutable -> int32_t { + // then we are in the main thread + // services, hosts and commands are stable + me->_calc_and_send_config_if_needed(); + return 0; + }); + command_manager::instance().enqueue(std::move(to_call)); +} + +/** + * @brief static method used to push new configuration to all agents + * + * @tparam bireactor_class + */ +template +void agent_impl::all_agent_calc_and_send_config_if_needed( + const agent_config::pointer& new_conf) { + absl::MutexLock l(&_instances_m); + for (auto& instance : _instances) { + instance->calc_and_send_config_if_needed(new_conf); + } +} + +static bool add_command_to_agent_conf( + const std::string& cmd_name, + const std::string& cmd_line, + const std::string& service, + com::centreon::agent::AgentConfiguration* cnf, + const std::shared_ptr& logger, + const std::string& peer) { + std::string plugins_cmdline = boost::trim_copy(cmd_line); + + if (plugins_cmdline.empty()) { + SPDLOG_LOGGER_ERROR( + logger, + "no add command: agent: {} serv: {}, no plugins cmd_line found in {}", + peer, service, cmd_line); + return false; + } + + SPDLOG_LOGGER_TRACE( + logger, "add command to agent: {}, serv: {}, cmd {} plugins cmd_line {}", + peer, service, cmd_name, cmd_line); + + com::centreon::agent::Service* serv = cnf->add_services(); + serv->set_service_description(service); + serv->set_command_name(cmd_name); + serv->set_command_line(plugins_cmdline); + + return true; +} + +/** + * @brief this function must be called in the engine main thread + * It calculates agent configuration, if different to the older, it sends it to + * agent + * + * @tparam bireactor_class + */ +template +void agent_impl::_calc_and_send_config_if_needed() { + std::shared_ptr new_conf = + std::make_shared(); + { + agent::AgentConfiguration* cnf = new_conf->mutable_config(); + cnf->set_check_interval(_conf->get_check_interval()); + cnf->set_check_timeout(_conf->get_check_timeout()); + cnf->set_export_period(_conf->get_export_period()); + cnf->set_max_concurrent_checks(_conf->get_max_concurrent_checks()); + cnf->set_use_exemplar(true); + absl::MutexLock l(&_protect); + if (!_alive) { + return; + } + if (_agent_info) { + const std::string& peer = get_peer(); + bool at_least_one_command_found = get_otel_commands( + _agent_info->init().host(), + [cnf, &peer](const std::string& cmd_name, const std::string& cmd_line, + const std::string& service, + const std::shared_ptr& logger) { + return add_command_to_agent_conf(cmd_name, cmd_line, service, cnf, + logger, peer); + }, + _logger); + if (!at_least_one_command_found) { + SPDLOG_LOGGER_ERROR(_logger, "no command found for agent {}", + get_peer()); + } + } + if (!_last_sent_config || + !::google::protobuf::util::MessageDifferencer::Equals( + *cnf, _last_sent_config->config())) { + _last_sent_config = new_conf; + } else { + new_conf.reset(); + SPDLOG_LOGGER_DEBUG(_logger, "no need to update conf to {}", get_peer()); + } + } + if (new_conf) { + SPDLOG_LOGGER_DEBUG(_logger, "send conf to {}", get_peer()); + _write(new_conf); + } +} + +/** + * @brief manages incoming request (init or otel data) + * + * @tparam bireactor_class + * @param request + */ +template +void agent_impl::on_request( + const std::shared_ptr& request) { + agent_config::pointer agent_conf; + if (request->has_init()) { + { + absl::MutexLock l(&_protect); + _agent_info = request; + agent_conf = _conf; + _last_sent_config.reset(); + } + SPDLOG_LOGGER_DEBUG(_logger, "init from {}", get_peer()); + calc_and_send_config_if_needed(agent_conf); + } + if (request->has_otel_request()) { + metric_request_ptr received(request->unsafe_arena_release_otel_request()); + _metric_handler(received); + } +} + +/** + * @brief send request to agent + * + * @tparam bireactor_class + * @param request + */ +template +void agent_impl::_write( + const std::shared_ptr& request) { + { + absl::MutexLock l(&_protect); + if (!_alive) { + return; + } + _write_queue.push_back(request); + } + start_write(); +} + +/** + * @brief all grpc streams are stored in an static container + * + * @tparam bireactor_class + * @param strm + */ +template +void agent_impl::register_stream( + const std::shared_ptr& strm) { + absl::MutexLock l(&_instances_m); + _instances.insert(strm); +} + +/** + * @brief start an asynchronous read + * + * @tparam bireactor_class + */ +template +void agent_impl::start_read() { + absl::MutexLock l(&_protect); + if (!_alive) { + return; + } + std::shared_ptr to_read; + if (_read_current) { + return; + } + to_read = _read_current = std::make_shared(); + bireactor_class::StartRead(to_read.get()); +} + +/** + * @brief we have receive a request or an eof + * + * @tparam bireactor_class + * @param ok + */ +template +void agent_impl::OnReadDone(bool ok) { + if (ok) { + std::shared_ptr readden; + { + absl::MutexLock l(&_protect); + SPDLOG_LOGGER_TRACE(_logger, "{:p} {} receive from {}: {}", + static_cast(this), _class_name, + get_peer(), *_read_current); + readden = _read_current; + _read_current.reset(); + } + start_read(); + on_request(readden); + } else { + SPDLOG_LOGGER_ERROR(_logger, "{:p} {} fail read from {}", + static_cast(this), _class_name, get_peer()); + on_error(); + this->shutdown(); + } +} + +/** + * @brief starts an asynchronous write + * + * @tparam bireactor_class + */ +template +void agent_impl::start_write() { + std::shared_ptr to_send; + { + absl::MutexLock l(&_protect); + if (!_alive || _write_pending || _write_queue.empty()) { + return; + } + to_send = _write_queue.front(); + _write_pending = true; + } + SPDLOG_LOGGER_TRACE(_logger, "{:p} {} send to {}: {}", + static_cast(this), _class_name, get_peer(), + *to_send); + bireactor_class::StartWrite(to_send.get()); +} + +/** + * @brief write handler + * + * @tparam bireactor_class + * @param ok + */ +template +void agent_impl::OnWriteDone(bool ok) { + if (ok) { + { + absl::MutexLock l(&_protect); + _write_pending = false; + SPDLOG_LOGGER_TRACE(_logger, "{:p} {} {} sent", + static_cast(this), _class_name, + **_write_queue.begin()); + _write_queue.pop_front(); + } + start_write(); + } else { + SPDLOG_LOGGER_ERROR(_logger, "{:p} {} fail write to stream", + static_cast(this), _class_name); + on_error(); + this->shutdown(); + } +} + +/** + * @brief called when server agent connection is closed + * When grpc layers call this handler, oject must be deleted + * + * @tparam bireactor_class + */ +template +void agent_impl::OnDone() { + /**grpc has a bug, sometimes if we delete this class in this handler as it is + * described in examples, it also deletes used channel and does a pthread_join + * of the current thread witch go to a EDEADLOCK error and call grpc::Crash. + * So we uses asio thread to do the job + */ + _io_context->post([me = std::enable_shared_from_this< + agent_impl>::shared_from_this(), + logger = _logger]() { + absl::MutexLock l(&_instances_m); + SPDLOG_LOGGER_DEBUG(logger, "{:p} server::OnDone()", + static_cast(me.get())); + _instances.erase(std::static_pointer_cast>(me)); + }); +} + +/** + * @brief called when client agent connection is closed + * When grpc layers call this handler, oject must be deleted + * + * @tparam bireactor_class + * @param status status passed to Finish agent side method + */ +template +void agent_impl::OnDone(const ::grpc::Status& status) { + /**grpc has a bug, sometimes if we delete this class in this handler as it is + * described in examples, it also deletes used channel and does a + * pthread_join of the current thread witch go to a EDEADLOCK error and call + * grpc::Crash. So we uses asio thread to do the job + */ + _io_context->post([me = std::enable_shared_from_this< + agent_impl>::shared_from_this(), + status, logger = _logger]() { + absl::MutexLock l(&_instances_m); + if (status.ok()) { + SPDLOG_LOGGER_DEBUG(logger, "{:p} client::OnDone({}) {}", + static_cast(me.get()), status.error_message(), + status.error_details()); + } else { + SPDLOG_LOGGER_ERROR(logger, "{:p} client::OnDone({}) {}", + static_cast(me.get()), status.error_message(), + status.error_details()); + } + _instances.erase(std::static_pointer_cast>(me)); + }); +} + +/** + * @brief just log, must be inherited + * + * @tparam bireactor_class + */ +template +void agent_impl::shutdown() { + SPDLOG_LOGGER_DEBUG(_logger, "{:p} {}::shutdown", static_cast(this), + _class_name); +} + +/** + * @brief static method used to shutdown all connections + * + * @tparam bireactor_class + */ +template +void agent_impl::shutdown_all() { + std::set> to_shutdown; + { + absl::MutexLock l(&_instances_m); + to_shutdown = std::move(_instances); + } + for (std::shared_ptr conn : to_shutdown) { + conn->shutdown(); + } +} + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +template class agent_impl< + ::grpc::ClientBidiReactor>; + +template class agent_impl< + ::grpc::ServerBidiReactor>; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent \ No newline at end of file diff --git a/engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc b/engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc new file mode 100644 index 00000000000..7c38cee5ad4 --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc @@ -0,0 +1,127 @@ +/** + * 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 "centreon_agent/agent_reverse_client.hh" +#include "centreon_agent/to_agent_connector.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +/** + * @brief Construct a new agent reverse client::agent reverse client object + * + * @param io_context + * @param handler handler that will process received metrics + * @param logger + */ +agent_reverse_client::agent_reverse_client( + const std::shared_ptr& io_context, + const metric_handler& handler, + const std::shared_ptr& logger) + : _io_context(io_context), _metric_handler(handler), _logger(logger) {} + +/** + * @brief Destroy the agent reverse client::agent reverse client object + * it also shutdown all connectors + * + */ +agent_reverse_client::~agent_reverse_client() { + absl::MutexLock l(&_agents_m); + for (auto& conn : _agents) { + conn.second->shutdown(); + } + _agents.clear(); +} + +/** + * @brief update agent list by doing a symmetric difference + * + * @param new_conf + */ +void agent_reverse_client::update(const agent_config::pointer& new_conf) { + absl::MutexLock l(&_agents_m); + + auto connection_iterator = _agents.begin(); + + if (!new_conf) { + while (connection_iterator != _agents.end()) { + _shutdown_connection(connection_iterator); + connection_iterator = _agents.erase(connection_iterator); + } + return; + } + + auto conf_iterator = new_conf->get_agent_grpc_reverse_conf().begin(); + + while (connection_iterator != _agents.end() && + conf_iterator != new_conf->get_agent_grpc_reverse_conf().end()) { + int compare_res = connection_iterator->first->compare(**conf_iterator); + if (compare_res > 0) { + connection_iterator = + _create_new_client_connection(*conf_iterator, new_conf); + ++connection_iterator; + ++conf_iterator; + } else if (compare_res < 0) { + _shutdown_connection(connection_iterator); + connection_iterator = _agents.erase(connection_iterator); + } else { + connection_iterator->second->refresh_agent_configuration_if_needed( + new_conf); + ++connection_iterator; + ++conf_iterator; + } + } + + while (connection_iterator != _agents.end()) { + _shutdown_connection(connection_iterator); + connection_iterator = _agents.erase(connection_iterator); + } + + for (; conf_iterator != new_conf->get_agent_grpc_reverse_conf().end(); + ++conf_iterator) { + _create_new_client_connection(*conf_iterator, new_conf); + } +} + +/** + * @brief create and start a new agent reversed connection + * + * @param agent_endpoint endpoint to connect + * @param new_conf global agent configuration + * @return agent_reverse_client::config_to_client::iterator iterator to the new + * element inserted + */ +agent_reverse_client::config_to_client::iterator +agent_reverse_client::_create_new_client_connection( + const grpc_config::pointer& agent_endpoint, + const agent_config::pointer& agent_conf) { + auto insert_res = _agents.try_emplace( + agent_endpoint, + to_agent_connector::load(agent_endpoint, _io_context, agent_conf, + _metric_handler, _logger)); + return insert_res.first; +} + +/** + * @brief only shutdown client connection, no container erase + * + * @param to_delete + */ +void agent_reverse_client::_shutdown_connection( + config_to_client::const_iterator to_delete) { + to_delete->second->shutdown(); +} diff --git a/engine/modules/opentelemetry/src/centreon_agent/agent_service.cc b/engine/modules/opentelemetry/src/centreon_agent/agent_service.cc new file mode 100644 index 00000000000..8fea6fcb1bc --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/agent_service.cc @@ -0,0 +1,162 @@ +/** + * 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 "centreon_agent/agent_service.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +/** + * @brief managed incoming centreon monitoring agent connection + * + */ +class server_bireactor + : public agent_impl<::grpc::ServerBidiReactor> { + const std::string _peer; + + public: + template + server_bireactor(const std::shared_ptr& io_context, + const agent_config::pointer& conf, + const otel_request_handler& handler, + const std::shared_ptr& logger, + const std::string& peer) + : agent_impl<::grpc::ServerBidiReactor>( + io_context, + "agent_server", + conf, + handler, + logger), + _peer(peer) { + SPDLOG_LOGGER_DEBUG(_logger, "connected with agent {}", _peer); + } + + const std::string& get_peer() const override { return _peer; } + + void on_error() override; + void shutdown() override; +}; + +void server_bireactor::on_error() { + shutdown(); +} + +void server_bireactor::shutdown() { + absl::MutexLock l(&_protect); + if (_alive) { + _alive = false; + agent_impl<::grpc::ServerBidiReactor>::shutdown(); + Finish(::grpc::Status::CANCELLED); + SPDLOG_LOGGER_DEBUG(_logger, "end of agent connection with {}", _peer); + } +} + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +/** + * @brief Construct a new agent service::agent service object + * don't use it, use agent_service::load instead + * + * @param io_context + * @param handler + * @param logger + */ +agent_service::agent_service( + const std::shared_ptr& io_context, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger) + : _io_context(io_context), + _conf(conf), + _metric_handler(handler), + _logger(logger) { + if (!_conf) { + _conf = std::make_shared(60, 100, 10, 30); + SPDLOG_LOGGER_INFO(logger, + "no centreon_agent configuration given => we use a " + "default configuration "); + } +} + +/** + * @brief prefered way to construct an agent_service + * + * @param io_context + * @param handler + * @param logger + * @return std::shared_ptr + */ +std::shared_ptr agent_service::load( + const std::shared_ptr& io_context, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger) { + std::shared_ptr ret = std::make_shared( + io_context, conf, std::move(handler), logger); + ret->init(); + return ret; +} + +/** + * @brief to call after construction + * + */ +void agent_service::init() { + ::grpc::Service::MarkMethodCallback( + 0, new ::grpc::internal::CallbackBidiHandler< + com::centreon::agent::MessageFromAgent, + com::centreon::agent::MessageToAgent>( + [me = shared_from_this()](::grpc::CallbackServerContext* context) { + return me->Export(context); + })); +} + +/** + * @brief called by grpc layer on each incoming connection + * + * @param context + * @return ::grpc::ServerBidiReactor* + */ +::grpc::ServerBidiReactor* +agent_service::Export(::grpc::CallbackServerContext* context) { + std::shared_ptr new_reactor; + { + absl::MutexLock l(&_conf_m); + new_reactor = std::make_shared( + _io_context, _conf, _metric_handler, _logger, context->peer()); + } + server_bireactor::register_stream(new_reactor); + new_reactor->start_read(); + + return new_reactor.get(); +} + +void agent_service::shutdown_all_accepted() { + server_bireactor::shutdown_all(); +} + +void agent_service::update(const agent_config::pointer& conf) { + absl::MutexLock l(&_conf_m); + _conf = conf; +} diff --git a/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc b/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc new file mode 100644 index 00000000000..f8cce8607a9 --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc @@ -0,0 +1,223 @@ +/** + * 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 "com/centreon/common/defer.hh" + +#include "centreon_agent/to_agent_connector.hh" + +#include "centreon_agent/agent_impl.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +/** + * @brief reverse connection to an agent + * + */ +class agent_connection + : public agent_impl<::grpc::ClientBidiReactor> { + std::weak_ptr _parent; + + std::string _peer; + ::grpc::ClientContext _context; + + public: + agent_connection(const std::shared_ptr& io_context, + const std::shared_ptr& parent, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger); + + ::grpc::ClientContext& get_context() { return _context; } + + void on_error() override; + + void shutdown() override; + + const std::string& get_peer() const override { return _peer; } +}; + +/** + * @brief Construct a new agent connection::agent connection object + * + * @param io_context + * @param parent to_agent_connector that had created this object + * @param handler handler called on every metric received + * @param logger + */ +agent_connection::agent_connection( + const std::shared_ptr& io_context, + const std::shared_ptr& parent, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr& logger) + : agent_impl<::grpc::ClientBidiReactor>( + io_context, + "reverse_client", + conf, + handler, + logger), + _parent(parent) { + _peer = parent->get_conf()->get_hostport(); +} + +/** + * @brief called by OnReadDone or OnWriteDone when ok = false + * + */ +void agent_connection::on_error() { + std::shared_ptr parent = _parent.lock(); + if (parent) { + parent->on_error(); + } +} + +/** + * @brief shutdown connection before delete + * + */ +void agent_connection::shutdown() { + absl::MutexLock l(&_protect); + if (_alive) { + _alive = false; + agent_impl<::grpc::ClientBidiReactor>::shutdown(); + RemoveHold(); + _context.TryCancel(); + } +} + +}; // namespace com::centreon::engine::modules::opentelemetry::centreon_agent +/** + * @brief Construct a new agent client::agent client object + * use to_agent_connector instead + * @param conf + * @param io_context + * @param handler handler that will process received metrics + * @param logger + */ +to_agent_connector::to_agent_connector( + const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr& logger) + : common::grpc::grpc_client_base(agent_endpoint_conf, logger), + _io_context(io_context), + _conf(agent_conf), + _metric_handler(handler), + _alive(true) { + _stub = std::move(agent::ReversedAgentService::NewStub(_channel)); +} + +/** + * @brief Destroy the to agent connector::to agent connector object + * shutdown connection + */ +to_agent_connector::~to_agent_connector() { + shutdown(); +} + +/** + * @brief construct an start a new client + * + * @param conf conf of the agent endpoint + * @param io_context + * @param handler handler that will process received metrics + * @param logger + * @return std::shared_ptr client created and started + */ +std::shared_ptr to_agent_connector::load( + const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr& logger) { + std::shared_ptr ret = + std::make_shared(agent_endpoint_conf, io_context, + agent_conf, handler, logger); + ret->start(); + return ret; +} + +/** + * @brief connect to agent and initialize exchange + * + */ +void to_agent_connector::start() { + absl::MutexLock l(&_connection_m); + if (!_alive) { + return; + } + SPDLOG_LOGGER_INFO(get_logger(), "connect to {}", get_conf()->get_hostport()); + if (_connection) { + _connection->shutdown(); + _connection.reset(); + } + _connection = std::make_shared( + _io_context, shared_from_this(), _conf, _metric_handler, get_logger()); + agent_connection::register_stream(_connection); + _stub->async()->Import(&_connection->get_context(), _connection.get()); + _connection->start_read(); + _connection->AddHold(); + _connection->StartCall(); +} + +/** + * @brief send conf to agent if something has changed (list of services, + * commands...) + * + */ +void to_agent_connector::refresh_agent_configuration_if_needed( + const agent_config::pointer& new_conf) { + absl::MutexLock l(&_connection_m); + if (_connection) { + _connection->calc_and_send_config_if_needed(new_conf); + } +} + +/** + * @brief shutdown configuration, once this method has been called, this object + * is dead and must be deleted + * + */ +void to_agent_connector::shutdown() { + absl::MutexLock l(&_connection_m); + if (_alive) { + SPDLOG_LOGGER_INFO(get_logger(), "shutdown client of {}", + get_conf()->get_hostport()); + if (_connection) { + _connection->shutdown(); + _connection.reset(); + } + _alive = false; + } +} + +/** + * @brief called by connection + * reconnection is delayed of 10 second + * + */ +void to_agent_connector::on_error() { + common::defer(_io_context, std::chrono::seconds(10), + [me = shared_from_this()] { me->start(); }); +} diff --git a/engine/modules/opentelemetry/src/host_serv_extractor.cc b/engine/modules/opentelemetry/src/host_serv_extractor.cc index bbb26cdb215..6a9ed8506ad 100644 --- a/engine/modules/opentelemetry/src/host_serv_extractor.cc +++ b/engine/modules/opentelemetry/src/host_serv_extractor.cc @@ -87,17 +87,26 @@ host_serv_attributes_extractor::host_serv_attributes_extractor( [](po::options_description& desc) { desc.add_options()( "host_path", po::value(), - "where to find host name. Example: " - "resourceMetrics.scopeMetrics.metrics.dataPoints.attributes.host"); - desc.add_options()("service_path", po::value(), - "where to find service description. Example: " - "resourceMetrics.scopeMetrics.metrics.dataPoints." - "attributes.service"); + "where to find host name. Example:\n" + "resource_metrics.scopeMetrics.metrics.dataPoints.attributes.host\n" + "or\n" + "resource_metrics.resource.attributes.host\n" + "or\n" + "resource_metrics.scope_metrics.scope.attributes.host"); + desc.add_options()( + "service_path", po::value(), + "where to find service description. Example:\n" + "resource_metrics.scope_metrics.data.data_points.attributes." + "service\n" + "or\n" + "resource_metrics.resource.attributes.service\n" + "or\n" + "resource_metrics.scope_metrics.scope.attributes.service"); }); static auto parse_path = [](const std::string& path, attribute_owner& attr, std::string& key) { - static re2::RE2 path_extractor("\\.(\\w+)\\.attributes\\.(\\w+)"); + static re2::RE2 path_extractor("(?i)\\.(\\w+)\\.attributes\\.([\\.\\w]+)"); std::string sz_attr; if (!RE2::PartialMatch(path, path_extractor, &sz_attr, &key)) { throw exceptions::msg_fmt( diff --git a/engine/modules/opentelemetry/src/open_telemetry.cc b/engine/modules/opentelemetry/src/open_telemetry.cc index 34da5d8fbf8..98707492915 100644 --- a/engine/modules/opentelemetry/src/open_telemetry.cc +++ b/engine/modules/opentelemetry/src/open_telemetry.cc @@ -18,6 +18,7 @@ #include "com/centreon/exceptions/msg_fmt.hh" +#include "centreon_agent/agent_impl.hh" #include "com/centreon/common/http/https_connection.hh" #include "com/centreon/engine/modules/opentelemetry/open_telemetry.hh" @@ -50,8 +51,23 @@ open_telemetry::open_telemetry( void open_telemetry::_reload() { std::unique_ptr new_conf = std::make_unique(_config_file_path, *_io_context); - if (!_conf || *new_conf->get_grpc_config() != *_conf->get_grpc_config()) { - this->_create_otl_server(new_conf->get_grpc_config()); + + if (new_conf->get_grpc_config()) { + if (!_conf || !_conf->get_grpc_config() || + *new_conf->get_grpc_config() != *_conf->get_grpc_config()) { + this->_create_otl_server(new_conf->get_grpc_config(), + new_conf->get_centreon_agent_config()); + } + if (_conf && _conf->get_centreon_agent_config() && + *_conf->get_centreon_agent_config() != + *new_conf->get_centreon_agent_config()) { + _otl_server->update_agent_config(new_conf->get_centreon_agent_config()); + } + } else { // only reverse connection + std::shared_ptr to_shutdown = std::move(_otl_server); + if (to_shutdown) { + to_shutdown->shutdown(std::chrono::seconds(10)); + } } if (!new_conf->get_telegraf_conf_server_config()) { @@ -76,7 +92,28 @@ void open_telemetry::_reload() { new_conf->get_max_fifo_size()); _conf = std::move(new_conf); + + if (!_agent_reverse_client) { + _agent_reverse_client = + std::make_unique( + _io_context, + [me = shared_from_this()](const metric_request_ptr& request) { + me->_on_metric(request); + }, + _logger); + } + _agent_reverse_client->update(_conf->get_centreon_agent_config()); } + // push new configuration to connected agents + centreon_agent::agent_impl<::grpc::ServerBidiReactor>:: + all_agent_calc_and_send_config_if_needed( + _conf->get_centreon_agent_config()); + + centreon_agent::agent_impl<::grpc::ClientBidiReactor< + agent::MessageToAgent, agent::MessageFromAgent>>:: + all_agent_calc_and_send_config_if_needed( + _conf->get_centreon_agent_config()); } /** @@ -105,14 +142,15 @@ std::shared_ptr open_telemetry::load( * @param server_conf json server config */ void open_telemetry::_create_otl_server( - const grpc_config::pointer& server_conf) { + const grpc_config::pointer& server_conf, + const centreon_agent::agent_config::pointer& agent_conf) { try { std::shared_ptr to_shutdown = std::move(_otl_server); if (to_shutdown) { to_shutdown->shutdown(std::chrono::seconds(10)); } _otl_server = otl_server::load( - server_conf, + _io_context, server_conf, agent_conf, [me = shared_from_this()](const metric_request_ptr& request) { me->_on_metric(request); }, diff --git a/engine/modules/opentelemetry/src/otl_check_result_builder.cc b/engine/modules/opentelemetry/src/otl_check_result_builder.cc index e1f75423fee..517374773a5 100644 --- a/engine/modules/opentelemetry/src/otl_check_result_builder.cc +++ b/engine/modules/opentelemetry/src/otl_check_result_builder.cc @@ -21,6 +21,8 @@ #include "data_point_fifo_container.hh" #include "otl_check_result_builder.hh" + +#include "centreon_agent/agent_check_result_builder.hh" #include "telegraf/nagios_check_result_builder.hh" #include "absl/flags/commandlineflag.h" @@ -147,6 +149,11 @@ std::shared_ptr otl_check_result_builder::create( return std::make_shared( cmd_line, command_id, host, service, timeout, std::move(handler), logger); + case check_result_builder_config::converter_type:: + centreon_agent_check_result_builder: + return std::make_shared( + cmd_line, command_id, host, service, timeout, std::move(handler), + logger); default: SPDLOG_LOGGER_ERROR(logger, "unknown converter type:{}", cmd_line); throw exceptions::msg_fmt("unknown converter type:{}", cmd_line); @@ -200,6 +207,10 @@ otl_check_result_builder::create_check_result_builder_config( return std::make_shared( check_result_builder_config::converter_type:: nagios_check_result_builder); + } else if (extractor_type == "centreon_agent") { + return std::make_shared( + check_result_builder_config::converter_type:: + centreon_agent_check_result_builder); } else { throw exceptions::msg_fmt("unknown processor in {}", cmd_line); } diff --git a/engine/modules/opentelemetry/src/otl_config.cc b/engine/modules/opentelemetry/src/otl_config.cc index c36fd359f24..f0c62dda374 100644 --- a/engine/modules/opentelemetry/src/otl_config.cc +++ b/engine/modules/opentelemetry/src/otl_config.cc @@ -19,6 +19,8 @@ #include "com/centreon/common/rapidjson_helper.hh" #include "com/centreon/engine/globals.hh" +#include "centreon_agent/agent.grpc.pb.h" + #include "otl_config.hh" #include "otl_fmt.hh" @@ -62,12 +64,13 @@ static constexpr std::string_view _grpc_config_schema(R"( "telegraf_conf_server": { "description": "http(s) telegraf config server", "type": "object" + }, + "centreon_agent": { + "description": "config of centreon_agent", + "type": "object" } - }, - "required": [ - "otel_server" - ], - "type": "object" + }, + "type" : "object" } )"); @@ -97,12 +100,48 @@ otl_config::otl_config(const std::string_view& file_path, _json_grpc_log = file_content.get_bool("grpc_json_log", false); _second_fifo_expiry = file_content.get_unsigned("second_fifo_expiry", 600); _max_fifo_size = file_content.get_unsigned("max_fifo_size", 5); - _grpc_conf = - std::make_shared(file_content.get_member("otel_server")); + if (file_content.has_member("otel_server")) { + try { + _grpc_conf = + std::make_shared(file_content.get_member("otel_server")); + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR(config_logger, + "fail to parse otl_server object: ", e.what()); + throw; + } + } + + if (file_content.has_member("centreon_agent")) { + try { + _centreon_agent_config = std::make_shared( + file_content.get_member("centreon_agent")); + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR( + config_logger, + "fail to parse centreon agent conf server object: ", e.what()); + throw; + } + } + + // nor server nor reverse client? + if (!_grpc_conf && + !(_centreon_agent_config && + !_centreon_agent_config->get_agent_grpc_reverse_conf().empty())) { + throw exceptions::msg_fmt( + "nor an grpc server, nor a reverse client configured"); + } + if (file_content.has_member("telegraf_conf_server")) { - _telegraf_conf_server_config = - std::make_shared( - file_content.get_member("telegraf_conf_server"), io_context); + try { + _telegraf_conf_server_config = + std::make_shared( + file_content.get_member("telegraf_conf_server"), io_context); + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR( + config_logger, + "fail to parse telegraf conf server object: ", e.what()); + throw; + } } } diff --git a/engine/modules/opentelemetry/src/otl_data_point.cc b/engine/modules/opentelemetry/src/otl_data_point.cc index 515244c92a9..7e5273725f1 100644 --- a/engine/modules/opentelemetry/src/otl_data_point.cc +++ b/engine/modules/opentelemetry/src/otl_data_point.cc @@ -21,6 +21,15 @@ using namespace com::centreon::engine::modules::opentelemetry; using namespace ::opentelemetry::proto::metrics::v1; +/** + * @brief SummaryDataPoint doesn't have Exemplars so we use it to return an + * array of exemplars in any case + * + */ +static const ::google::protobuf::RepeatedPtrField< + ::opentelemetry::proto::metrics::v1::Exemplar> + _empty_exemplars; + otl_data_point::otl_data_point( const metric_request_ptr& parent, const ::opentelemetry::proto::resource::v1::Resource& resource, @@ -33,6 +42,7 @@ otl_data_point::otl_data_point( _metric(metric), _data_point(data_pt), _data_point_attributes(data_pt.attributes()), + _exemplars(data_pt.exemplars()), _nano_timestamp(data_pt.time_unix_nano()), _type(data_point_type::number) { _value = data_pt.as_double() ? data_pt.as_double() : data_pt.as_int(); @@ -50,6 +60,7 @@ otl_data_point::otl_data_point( _metric(metric), _data_point(data_pt), _data_point_attributes(data_pt.attributes()), + _exemplars(data_pt.exemplars()), _nano_timestamp(data_pt.time_unix_nano()), _type(data_point_type::histogram) { _value = data_pt.count(); @@ -68,6 +79,7 @@ otl_data_point::otl_data_point( _metric(metric), _data_point(data_pt), _data_point_attributes(data_pt.attributes()), + _exemplars(data_pt.exemplars()), _nano_timestamp(data_pt.time_unix_nano()), _type(data_point_type::exponential_histogram) { _value = data_pt.count(); @@ -85,6 +97,7 @@ otl_data_point::otl_data_point( _metric(metric), _data_point(data_pt), _data_point_attributes(data_pt.attributes()), + _exemplars(_empty_exemplars), _nano_timestamp(data_pt.time_unix_nano()), _type(data_point_type::summary) { _value = data_pt.count(); diff --git a/engine/modules/opentelemetry/src/otl_server.cc b/engine/modules/opentelemetry/src/otl_server.cc index b6b9097df78..b502953ddb3 100644 --- a/engine/modules/opentelemetry/src/otl_server.cc +++ b/engine/modules/opentelemetry/src/otl_server.cc @@ -19,6 +19,7 @@ #include #include +#include "centreon_agent/agent.grpc.pb.h" #include "opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.h" #include "otl_fmt.hh" @@ -282,12 +283,19 @@ ::grpc::ServerUnaryReactor* metric_service::Export( * @param conf grpc configuration * @param handler handler that will be called on every request */ -otl_server::otl_server(const grpc_config::pointer& conf, - const metric_handler& handler, - const std::shared_ptr& logger) +otl_server::otl_server( + const std::shared_ptr& io_context, + const grpc_config::pointer& conf, + const centreon_agent::agent_config::pointer& agent_config, + const metric_handler& handler, + const std::shared_ptr& logger) : common::grpc::grpc_server_base(conf, logger), - _service(detail::metric_service::load(handler, logger)) {} + _service(detail::metric_service::load(handler, logger)), + _agent_service(centreon_agent::agent_service::load(io_context, + agent_config, + handler, + logger)) {} /** * @brief Destroy the otl server::otl server object @@ -305,10 +313,13 @@ otl_server::~otl_server() { * @return otl_server::pointer otl_server started */ otl_server::pointer otl_server::load( + const std::shared_ptr& io_context, const grpc_config::pointer& conf, + const centreon_agent::agent_config::pointer& agent_config, const metric_handler& handler, const std::shared_ptr& logger) { - otl_server::pointer ret(new otl_server(conf, handler, logger)); + otl_server::pointer ret( + new otl_server(io_context, conf, agent_config, handler, logger)); ret->start(); return ret; } @@ -320,5 +331,16 @@ otl_server::pointer otl_server::load( void otl_server::start() { _init([this](::grpc::ServerBuilder& builder) { builder.RegisterService(_service.get()); + builder.RegisterService(_agent_service.get()); }); } + +/** + * @brief update conf used by service to create + * + * @param agent_config + */ +void otl_server::update_agent_config( + const centreon_agent::agent_config::pointer& agent_config) { + _agent_service->update(agent_config); +} diff --git a/engine/precomp_inc/precomp.hh b/engine/precomp_inc/precomp.hh index 852545a1567..0d306a733b3 100644 --- a/engine/precomp_inc/precomp.hh +++ b/engine/precomp_inc/precomp.hh @@ -62,6 +62,7 @@ #include #include +#include #include #include #include diff --git a/engine/src/configuration/applier/state.cc b/engine/src/configuration/applier/state.cc index 356f85aa9fb..a758a310b21 100644 --- a/engine/src/configuration/applier/state.cc +++ b/engine/src/configuration/applier/state.cc @@ -1168,10 +1168,10 @@ void applier::state::apply_log_config(configuration::state& new_cfg) { broker_sink->set_level(spdlog::level::info); log_cfg.add_custom_sink(broker_sink); - log_cfg.apply_custom_sinks({"functions", "config", "events", "checks", - "notifications", "eventbroker", - "external_command", "commands", "downtimes", - "comments", "macros", "process", "runtime"}); + log_cfg.apply_custom_sinks( + {"functions", "config", "events", "checks", "notifications", + "eventbroker", "external_command", "commands", "downtimes", "comments", + "macros", "process", "runtime", "otl"}); log_cfg.set_level("functions", new_cfg.log_level_functions()); log_cfg.set_level("config", new_cfg.log_level_config()); log_cfg.set_level("events", new_cfg.log_level_events()); diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 651c5ae6ef0..89aaffc7b6a 100755 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -111,6 +111,9 @@ if(WITH_TESTING) "${TESTS_DIR}/notifications/service_timeperiod_notification.cc" "${TESTS_DIR}/notifications/service_flapping_notification.cc" "${TESTS_DIR}/notifications/service_downtime_notification_test.cc" + "${TESTS_DIR}/opentelemetry/agent_check_result_builder_test.cc" + "${TESTS_DIR}/opentelemetry/agent_reverse_client_test.cc" + "${TESTS_DIR}/opentelemetry/agent_to_engine_test.cc" "${TESTS_DIR}/opentelemetry/grpc_config_test.cc" "${TESTS_DIR}/opentelemetry/host_serv_extractor_test.cc" "${TESTS_DIR}/opentelemetry/otl_server_test.cc" @@ -157,7 +160,9 @@ if(WITH_TESTING) add_executable(ut_engine ${ut_sources}) target_include_directories(ut_engine PRIVATE ${MODULE_DIR_OTL}/src - ${CMAKE_SOURCE_DIR}/common/grpc/inc) + ${CMAKE_SOURCE_DIR}/common/grpc/inc + ${CMAKE_SOURCE_DIR}/agent/inc + ${CMAKE_SOURCE_DIR}/agent/src) target_precompile_headers(ut_engine REUSE_FROM cce_core) @@ -193,12 +198,14 @@ if(WITH_TESTING) cce_core log_v2 opentelemetry + centagent_lib "-Wl,-no-whole-archive" pb_open_telemetry_lib centreon_grpc centreon_http - -L${Boost_LIBRARY_DIR_RELEASE} - boost_url + centreon_process + -L${Boost_LIBRARY_DIR_RELEASE} + boost_url boost_program_options pthread ${GCOV} diff --git a/engine/tests/opentelemetry/agent_check_result_builder_test.cc b/engine/tests/opentelemetry/agent_check_result_builder_test.cc new file mode 100644 index 00000000000..30976c03948 --- /dev/null +++ b/engine/tests/opentelemetry/agent_check_result_builder_test.cc @@ -0,0 +1,483 @@ +/** + * Copyright 2024 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Centreon Engine. If not, see + * . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/host.hh" +#include "com/centreon/engine/configuration/service.hh" + +#include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" +#include "opentelemetry/proto/common/v1/common.pb.h" +#include "opentelemetry/proto/metrics/v1/metrics.pb.h" + +#include "com/centreon/engine/modules/opentelemetry/data_point_fifo_container.hh" + +#include "com/centreon/engine/modules/opentelemetry/otl_check_result_builder.hh" + +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_check_result_builder.hh" + +#include "helper.hh" +#include "test_engine.hh" + +using namespace com::centreon::engine::modules::opentelemetry; +using namespace com::centreon::engine; + +static const char* agent_exemple = R"( +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "host.name", + "value": { + "stringValue": "test_host" + } + }, + { + "key": "service.name", + "value": { + "stringValue": "" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "status", + "description": "0", + "gauge": { + "dataPoints": [ + { + "timeUnixNano": "1718345061146529731", + "asInt": "0" + } + ] + } + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "host.name", + "value": { + "stringValue": "test_host" + } + }, + { + "key": "service.name", + "value": { + "stringValue": "test_svc_builder" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "status", + "description": "output of plugin", + "gauge": { + "dataPoints": [ + { + "timeUnixNano": "1718345061381922153", + "asInt": "0" + } + ] + } + }, + { + "name": "metric", + "gauge": { + "dataPoints": [ + { + "timeUnixNano": "1718345061381922153", + "exemplars": [ + { + "asDouble": 75, + "filteredAttributes": [ + { + "key": "crit_gt" + } + ] + }, + { + "asDouble": 0, + "filteredAttributes": [ + { + "key": "crit_lt" + } + ] + }, + { + "asDouble": 50, + "filteredAttributes": [ + { + "key": "warn_gt" + } + ] + }, + { + "asDouble": 0, + "filteredAttributes": [ + { + "key": "warn_lt" + } + ] + } + ], + "asInt": "12" + } + ] + } + }, + { + "name": "metric2", + "unit": "ms", + "gauge": { + "dataPoints": [ + { + "timeUnixNano": "1718345061381922153", + "exemplars": [ + { + "asDouble": 80, + "filteredAttributes": [ + { + "key": "crit_gt" + } + ] + }, + { + "asDouble": 75, + "filteredAttributes": [ + { + "key": "crit_lt" + } + ] + }, + { + "asDouble": 75, + "filteredAttributes": [ + { + "key": "warn_gt" + } + ] + }, + { + "asDouble": 50, + "filteredAttributes": [ + { + "key": "warn_lt" + } + ] + }, + { + "asDouble": 0, + "filteredAttributes": [ + { + "key": "min" + } + ] + }, + { + "asDouble": 100, + "filteredAttributes": [ + { + "key": "max" + } + ] + } + ], + "asInt": "30" + } + ] + } + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "host.name", + "value": { + "stringValue": "test_host" + } + }, + { + "key": "service.name", + "value": { + "stringValue": "test_svc_builder_2" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "status", + "description": "output taratata", + "gauge": { + "dataPoints": [ + { + "timeUnixNano": "1718345061713456225", + "asInt": "0" + } + ] + } + }, + { + "name": "metric", + "gauge": { + "dataPoints": [ + { + "timeUnixNano": "1718345061713456225", + "exemplars": [ + { + "asDouble": 75, + "filteredAttributes": [ + { + "key": "crit_ge" + } + ] + }, + { + "asDouble": 50, + "filteredAttributes": [ + { + "key": "warn_ge" + } + ] + }, + { + "asDouble": 0, + "filteredAttributes": [ + { + "key": "warn_le" + } + ] + } + ], + "asInt": "12" + } + ] + } + }, + { + "name": "metric2", + "unit": "ms", + "gauge": { + "dataPoints": [ + { + "timeUnixNano": "1718345061713456225", + "exemplars": [ + { + "asDouble": 80, + "filteredAttributes": [ + { + "key": "crit_gt" + } + ] + }, + { + "asDouble": 75, + "filteredAttributes": [ + { + "key": "crit_lt" + } + ] + }, + { + "asDouble": 75, + "filteredAttributes": [ + { + "key": "warn_gt" + } + ] + }, + { + "asDouble": 0, + "filteredAttributes": [ + { + "key": "min" + } + ] + }, + { + "asDouble": 100, + "filteredAttributes": [ + { + "key": "max" + } + ] + } + ], + "asInt": "30" + } + ] + } + } + ] + } + ] + } + ] +} +)"; + +class otl_agent_check_result_builder_test : public TestEngine { + protected: + std::shared_ptr _builder_config; + data_point_fifo_container _fifos; + + public: + otl_agent_check_result_builder_test() { + if (service::services.find({"test_host", "test_svc_builder_2"}) == + service::services.end()) { + init_config_state(); + config->contacts().clear(); + + configuration::applier::contact ct_aply; + configuration::contact ctct{new_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(*config); + ct_aply.resolve_object(ctct); + + configuration::host hst{ + new_configuration_host("test_host", "admin", 457)}; + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::service svc{new_configuration_service( + "test_host", "test_svc_builder", "admin", 458)}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + configuration::service svc2{new_configuration_service( + "test_host", "test_svc_builder_2", "admin", 459)}; + svc_aply.add_object(svc2); + + hst_aply.resolve_object(hst); + svc_aply.resolve_object(svc); + svc_aply.resolve_object(svc2); + } + + _builder_config = + otl_check_result_builder::create_check_result_builder_config( + "--processor=centreon_agent"); + + metric_request_ptr request = + std::make_shared< ::opentelemetry::proto::collector::metrics::v1:: + ExportMetricsServiceRequest>(); + + ::google::protobuf::util::JsonStringToMessage(agent_exemple, request.get()); + + otl_data_point::extract_data_points( + request, [&](const otl_data_point& data_pt) { + std::string service_name; + for (const auto attrib : data_pt.get_resource().attributes()) { + if (attrib.key() == "service.name") { + service_name = attrib.value().string_value(); + break; + } + } + _fifos.add_data_point("test_host", service_name, + data_pt.get_metric().name(), data_pt); + }); + } +}; + +TEST_F(otl_agent_check_result_builder_test, test_svc_builder) { + auto check_result_builder = otl_check_result_builder::create( + "", _builder_config, 1789, *host::hosts.find("test_host")->second, + service::services.find({"test_host", "test_svc_builder"})->second.get(), + std::chrono::system_clock::time_point(), [&](const commands::result&) {}, + spdlog::default_logger()); + + commands::result res; + bool success = + check_result_builder->sync_build_result_from_metrics(_fifos, res); + + ASSERT_TRUE(success); + ASSERT_EQ(res.exit_code, 0); + ASSERT_EQ(res.exit_status, com::centreon::process::normal); + ASSERT_EQ(res.command_id, 1789); + ASSERT_EQ(res.start_time.to_useconds(), 1718345061381922153 / 1000); + ASSERT_EQ(res.end_time.to_useconds(), 1718345061381922153 / 1000); + + auto compare_to_excepted = [](const std::string& to_cmp) -> bool { + return to_cmp == + "output of plugin| metric=12;0:50;0:75;; " + "metric2=30ms;50:75;75:80;0;100" || + to_cmp == + "output of plugin| metric2=30ms;50:75;75:80;0;100 " + "metric=12;0:50;0:75;;"; + }; + + ASSERT_PRED1(compare_to_excepted, res.output); +} + +TEST_F(otl_agent_check_result_builder_test, test_svc_builder_2) { + auto check_result_builder = otl_check_result_builder::create( + "", _builder_config, 1789, *host::hosts.find("test_host")->second, + service::services.find({"test_host", "test_svc_builder_2"})->second.get(), + std::chrono::system_clock::time_point(), [&](const commands::result&) {}, + spdlog::default_logger()); + + commands::result res; + bool success = + check_result_builder->sync_build_result_from_metrics(_fifos, res); + + ASSERT_TRUE(success); + ASSERT_EQ(res.exit_code, 0); + ASSERT_EQ(res.exit_status, com::centreon::process::normal); + ASSERT_EQ(res.command_id, 1789); + ASSERT_EQ(res.start_time.to_useconds(), 1718345061713456225 / 1000); + ASSERT_EQ(res.end_time.to_useconds(), 1718345061713456225 / 1000); + + auto compare_to_excepted = [](const std::string& to_cmp) -> bool { + return to_cmp == + "output taratata| metric=12;@0:50;@~:75;; " + "metric2=30ms;~:75;75:80;0;100" || + to_cmp == + "output taratata| metric2=30ms;~:75;75:80;0;100 " + "metric=12;@0:50;@~:75;;"; + }; + + ASSERT_PRED1(compare_to_excepted, res.output); +} \ No newline at end of file diff --git a/engine/tests/opentelemetry/agent_reverse_client_test.cc b/engine/tests/opentelemetry/agent_reverse_client_test.cc new file mode 100644 index 00000000000..79c1e166682 --- /dev/null +++ b/engine/tests/opentelemetry/agent_reverse_client_test.cc @@ -0,0 +1,153 @@ +/** + * 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 "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" +#include "opentelemetry/proto/common/v1/common.pb.h" +#include "opentelemetry/proto/metrics/v1/metrics.pb.h" + +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh" +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh" + +using namespace com::centreon::engine::modules::opentelemetry; +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +extern std::shared_ptr g_io_context; + +struct fake_connector : public to_agent_connector { + using config_to_fake = absl::btree_map, + grpc_config_compare>; + + fake_connector(const grpc_config::pointer& conf, + const std::shared_ptr& io_context, + const centreon_agent::agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr& logger) + : to_agent_connector(conf, io_context, agent_conf, handler, logger) {} + + void start() override { + all_fake.emplace(std::static_pointer_cast(get_conf()), + shared_from_this()); + } + + static std::shared_ptr load( + const grpc_config::pointer& conf, + const std::shared_ptr& io_context, + const centreon_agent::agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr& logger) { + std::shared_ptr ret = std::make_shared( + conf, io_context, agent_conf, handler, logger); + ret->start(); + return ret; + } + + static config_to_fake all_fake; + + void shutdown() override { + all_fake.erase(std::static_pointer_cast(get_conf())); + } +}; + +fake_connector::config_to_fake fake_connector::all_fake; + +class my_agent_reverse_client : public agent_reverse_client { + public: + my_agent_reverse_client( + const std::shared_ptr& io_context, + const metric_handler& handler, + const std::shared_ptr& logger) + : agent_reverse_client(io_context, handler, logger) {} + + agent_reverse_client::config_to_client::iterator + _create_new_client_connection( + const grpc_config::pointer& agent_endpoint, + const agent_config::pointer& agent_conf) override { + return _agents + .try_emplace(agent_endpoint, + fake_connector::load(agent_endpoint, _io_context, + agent_conf, _metric_handler, _logger)) + .first; + } + + void _shutdown_connection(config_to_client::const_iterator to_delete) { + to_delete->second->shutdown(); + } +}; + +TEST(agent_reverse_client, update_config) { + my_agent_reverse_client to_test( + g_io_context, [](const metric_request_ptr&) {}, spdlog::default_logger()); + + ASSERT_TRUE(fake_connector::all_fake.empty()); + + auto agent_conf = std::shared_ptr( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared("host1:port1", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 1); + ASSERT_EQ(fake_connector::all_fake.begin()->first, + *agent_conf->get_agent_grpc_reverse_conf().begin()); + agent_conf = std::make_shared(1, 100, 1, 10); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 0); + + agent_conf = std::shared_ptr( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared("host1:port1", false), + std::make_shared("host1:port3", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 2); + auto first_conn = fake_connector::all_fake.begin()->second; + auto second_conn = (++fake_connector::all_fake.begin())->second; + agent_conf = std::shared_ptr( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared("host1:port1", false), + std::make_shared("host1:port2", false), + std::make_shared("host1:port3", false)})); + + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 3); + ASSERT_EQ(fake_connector::all_fake.begin()->second, first_conn); + ASSERT_EQ((++(++fake_connector::all_fake.begin()))->second, second_conn); + second_conn = (++fake_connector::all_fake.begin())->second; + auto third_conn = (++(++fake_connector::all_fake.begin()))->second; + + agent_conf = std::shared_ptr( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared("host1:port1", false), + std::make_shared("host1:port3", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 2); + ASSERT_EQ(fake_connector::all_fake.begin()->second, first_conn); + ASSERT_EQ((++fake_connector::all_fake.begin())->second, third_conn); + + agent_conf = std::shared_ptr( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared("host1:port3", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 1); + ASSERT_EQ(fake_connector::all_fake.begin()->second, third_conn); +} \ No newline at end of file diff --git a/engine/tests/opentelemetry/agent_to_engine_test.cc b/engine/tests/opentelemetry/agent_to_engine_test.cc new file mode 100644 index 00000000000..245f94bf288 --- /dev/null +++ b/engine/tests/opentelemetry/agent_to_engine_test.cc @@ -0,0 +1,326 @@ +/** + * Copyright 2024 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Centreon Engine. If not, see + * . + */ + +#include +#include + +#include +#include + +#include + +#include "opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.h" +#include "opentelemetry/proto/metrics/v1/metrics.pb.h" + +#include "com/centreon/engine/contact.hh" +#include "com/centreon/engine/host.hh" +#include "com/centreon/engine/service.hh" + +#include "com/centreon/engine/command_manager.hh" +#include "com/centreon/engine/configuration/applier/connector.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" + +#include "com/centreon/agent/streaming_client.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_fmt.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_server.hh" + +#include "../test_engine.hh" +#include "helper.hh" + +using namespace com::centreon::engine; +using namespace com::centreon::agent; +// using namespace com::centreon::engine::configuration; +// using namespace com::centreon::engine::configuration::applier; +using namespace com::centreon::engine::modules::opentelemetry; +using namespace ::opentelemetry::proto::collector::metrics::v1; + +class agent_to_engine_test : public TestEngine { + protected: + std::shared_ptr _server; + + // agent code is mono-thread so it runs on his own io_context run by only one + // thread + std::shared_ptr _agent_io_context; + + asio::executor_work_guard _worker; + std::thread _agent_io_ctx_thread; + + public: + agent_to_engine_test() + : _agent_io_context(std::make_shared()), + _worker{asio::make_work_guard(*_agent_io_context)}, + _agent_io_ctx_thread([this] { _agent_io_context->run(); }) {} + + ~agent_to_engine_test() { + _agent_io_context->stop(); + _agent_io_ctx_thread.join(); + } + + void SetUp() override { + spdlog::default_logger()->set_level(spdlog::level::trace); + ::fmt::formatter< ::opentelemetry::proto::collector::metrics::v1:: + ExportMetricsServiceRequest>::json_grpc_format = true; + timeperiod::timeperiods.clear(); + contact::contacts.clear(); + host::hosts.clear(); + host::hosts_by_id.clear(); + service::services.clear(); + service::services_by_id.clear(); + + init_config_state(); + + configuration::applier::connector conn_aply; + configuration::connector cnn("agent"); + cnn.parse("connector_line", + "opentelemetry " + "--processor=nagios_telegraf --extractor=attributes " + "--host_path=resource_metrics.scope_metrics.data.data_points." + "attributes.host " + "--service_path=resource_metrics.scope_metrics.data.data_points." + "attributes.service"); + conn_aply.add_object(cnn); + + configuration::applier::contact ct_aply; + configuration::contact ctct{new_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(*config); + ct_aply.resolve_object(ctct); + + configuration::host hst = + new_configuration_host("test_host", "admin", 1, "agent"); + + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::service svc{new_configuration_service( + "test_host", "test_svc", "admin", 1, "agent")}; + configuration::service svc2{new_configuration_service( + "test_host", "test_svc_2", "admin", 2, "agent")}; + configuration::service svc_no_otel{ + new_configuration_service("test_host", "test_svc_2", "admin", 3)}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + svc_aply.add_object(svc2); + svc_aply.add_object(svc_no_otel); + + hst_aply.resolve_object(hst); + svc_aply.resolve_object(svc); + svc_aply.resolve_object(svc2); + svc_aply.resolve_object(svc_no_otel); + } + + void TearDown() override { + if (_server) { + _server->shutdown(std::chrono::seconds(15)); + _server.reset(); + } + deinit_config_state(); + } + + template + void start_server(const grpc_config::pointer& listen_endpoint, + const centreon_agent::agent_config::pointer& agent_conf, + const metric_handler_type& handler) { + _server = otl_server::load(_agent_io_context, listen_endpoint, agent_conf, + handler, spdlog::default_logger()); + } +}; + +bool compare_to_expected_host_metric( + const opentelemetry::proto::metrics::v1::ResourceMetrics& metric) { + bool host_found = false, serv_found = false; + for (const auto& attrib : metric.resource().attributes()) { + if (attrib.key() == "host.name") { + if (attrib.value().string_value() != "test_host") { + return false; + } + host_found = true; + } + if (attrib.key() == "service.name") { + if (!attrib.value().string_value().empty()) { + return false; + } + serv_found = true; + } + } + if (!host_found || !serv_found) { + return false; + } + const auto& scope_metric = metric.scope_metrics(); + if (scope_metric.size() != 1) + return false; + const auto& metrics = scope_metric.begin()->metrics(); + if (metrics.empty()) + return false; + const auto& status_metric = *metrics.begin(); + if (status_metric.name() != "status") + return false; + if (!status_metric.has_gauge()) + return false; + if (status_metric.gauge().data_points().empty()) + return false; + return status_metric.gauge().data_points().begin()->as_int() == 0; +} + +bool test_exemplars( + const google::protobuf::RepeatedPtrField< + ::opentelemetry::proto::metrics::v1::Exemplar>& examplars, + const std::map& expected) { + std::set matches; + + for (const auto& ex : examplars) { + if (ex.filtered_attributes().empty()) + continue; + auto search = expected.find(ex.filtered_attributes().begin()->key()); + if (search == expected.end()) + return false; + + if (search->second != ex.as_double()) + return false; + matches.insert(search->first); + } + return matches.size() == expected.size(); +} + +bool compare_to_expected_serv_metric( + const opentelemetry::proto::metrics::v1::ResourceMetrics& metric, + const std::string_view& serv_name) { + bool host_found = false, serv_found = false; + for (const auto& attrib : metric.resource().attributes()) { + if (attrib.key() == "host.name") { + if (attrib.value().string_value() != "test_host") { + return false; + } + host_found = true; + } + if (attrib.key() == "service.name") { + if (attrib.value().string_value() != serv_name) { + return false; + } + serv_found = true; + } + } + if (!host_found || !serv_found) { + return false; + } + const auto& scope_metric = metric.scope_metrics(); + if (scope_metric.size() != 1) + return false; + const auto& metrics = scope_metric.begin()->metrics(); + if (metrics.empty()) + return false; + + for (const auto& met : metrics) { + if (!met.has_gauge()) + return false; + if (met.name() == "metric") { + if (met.gauge().data_points().empty()) + return false; + if (met.gauge().data_points().begin()->as_double() != 12) + return false; + if (!test_exemplars(met.gauge().data_points().begin()->exemplars(), + {{"crit_gt", 75.0}, + {"crit_lt", 0.0}, + {"warn_gt", 50.0}, + {"warn_lt", 0.0}})) + return false; + } else if (met.name() == "metric2") { + if (met.gauge().data_points().empty()) + return false; + if (met.gauge().data_points().begin()->as_double() != 30) + return false; + if (!test_exemplars(met.gauge().data_points().begin()->exemplars(), + {{"crit_gt", 80.0}, + {"crit_lt", 75.0}, + {"warn_gt", 75.0}, + {"warn_lt", 50.0}, + {"min", 0.0}, + {"max", 100.0}})) + return false; + + } else if (met.name() == "status") { + if (met.gauge().data_points().begin()->as_int() != 0) + return false; + } else + return false; + } + + return true; +} + +TEST_F(agent_to_engine_test, server_send_conf_to_agent_and_receive_metrics) { + grpc_config::pointer listen_endpoint = + std::make_shared("127.0.0.1:4623", false); + + absl::Mutex mut; + std::vector received; + std::vector + resource_metrics; + + auto agent_conf = std::make_shared(1, 10, 1, 5); + + start_server(listen_endpoint, agent_conf, + [&](const metric_request_ptr& metric) { + absl::MutexLock l(&mut); + received.push_back(metric); + for (const opentelemetry::proto::metrics::v1::ResourceMetrics& + res_metric : metric->resource_metrics()) { + resource_metrics.push_back(&res_metric); + } + }); + + auto agent_client = + streaming_client::load(_agent_io_context, spdlog::default_logger(), + listen_endpoint, "test_host"); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + command_manager::instance().execute(); + + auto metric_received = [&]() { return resource_metrics.size() >= 3; }; + + mut.LockWhen(absl::Condition(&metric_received)); + mut.Unlock(); + + agent_client->shutdown(); + + _server->shutdown(std::chrono::seconds(15)); + + bool host_metric_found = true; + bool serv_1_found = false; + bool serv_2_found = false; + + for (const opentelemetry::proto::metrics::v1::ResourceMetrics* to_compare : + resource_metrics) { + if (compare_to_expected_serv_metric(*to_compare, "test_svc")) { + serv_1_found = true; + } else if (compare_to_expected_serv_metric(*to_compare, "test_svc_2")) { + serv_2_found = true; + } else if (compare_to_expected_host_metric(*to_compare)) { + host_metric_found = true; + } else { + SPDLOG_ERROR("bad resource metric: {}", to_compare->DebugString()); + ASSERT_TRUE(false); + } + } + ASSERT_TRUE(host_metric_found); + ASSERT_TRUE(serv_1_found); + ASSERT_TRUE(serv_2_found); +} \ No newline at end of file diff --git a/engine/tests/opentelemetry/open_telemetry_test.cc b/engine/tests/opentelemetry/open_telemetry_test.cc index 58603487909..7e14bc917b7 100644 --- a/engine/tests/opentelemetry/open_telemetry_test.cc +++ b/engine/tests/opentelemetry/open_telemetry_test.cc @@ -60,7 +60,9 @@ extern std::shared_ptr g_io_context; class open_telemetry : public com::centreon::engine::modules::opentelemetry::open_telemetry { protected: - void _create_otl_server(const grpc_config::pointer& server_conf) override {} + void _create_otl_server( + const grpc_config::pointer& server_conf, + const centreon_agent::agent_config::pointer&) override {} public: open_telemetry(const std::string_view config_file_path, diff --git a/engine/tests/opentelemetry/otl_converter_test.cc b/engine/tests/opentelemetry/otl_converter_test.cc index 8ebc07f4282..b579e40a7ef 100644 --- a/engine/tests/opentelemetry/otl_converter_test.cc +++ b/engine/tests/opentelemetry/otl_converter_test.cc @@ -54,6 +54,12 @@ class otl_converter_test : public TestEngine { void otl_converter_test::SetUp() { init_config_state(); + timeperiod::timeperiods.clear(); + contact::contacts.clear(); + host::hosts.clear(); + host::hosts_by_id.clear(); + service::services.clear(); + service::services_by_id.clear(); config->contacts().clear(); configuration::applier::contact ct_aply; configuration::contact ctct{new_configuration_contact("admin", true)}; diff --git a/engine/tests/opentelemetry/otl_server_test.cc b/engine/tests/opentelemetry/otl_server_test.cc index 8c99d849c64..5d6291a6cc3 100644 --- a/engine/tests/opentelemetry/otl_server_test.cc +++ b/engine/tests/opentelemetry/otl_server_test.cc @@ -32,6 +32,8 @@ using namespace com::centreon::engine::modules::opentelemetry; using namespace ::opentelemetry::proto::collector::metrics::v1; +extern std::shared_ptr g_io_context; + class otl_client { std::shared_ptr<::grpc::Channel> _channel; std::unique_ptr _stub; @@ -81,18 +83,18 @@ class otl_server_test : public ::testing::Test { template void start_server(const grpc_config::pointer& conf, const metric_handler_type& handler) { - _server = otl_server::load(conf, handler, spdlog::default_logger()); + std::shared_ptr agent_conf = + std::make_shared(60, 100, 60, 10); + _server = otl_server::load(g_io_context, conf, agent_conf, handler, + spdlog::default_logger()); } }; TEST_F(otl_server_test, unsecure_client_server) { grpc_config::pointer serv_conf = std::make_shared("127.0.0.1:6789", false); - std::shared_ptr received; - auto handler = - [&](const std::shared_ptr& request) { - received = request; - }; + metric_request_ptr received; + auto handler = [&](const metric_request_ptr& request) { received = request; }; start_server(serv_conf, handler); otl_client client("127.0.0.1:6789"); diff --git a/engine/tests/test_engine.cc b/engine/tests/test_engine.cc index 30daa0c6516..c2183609ca1 100644 --- a/engine/tests/test_engine.cc +++ b/engine/tests/test_engine.cc @@ -132,7 +132,8 @@ TestEngine::new_configuration_servicedependency( configuration::host TestEngine::new_configuration_host( const std::string& hostname, const std::string& contacts, - uint64_t hst_id) { + uint64_t hst_id, + const std::string_view& connector) { configuration::host hst; hst.parse("host_name", hostname.c_str()); hst.parse("address", "127.0.0.1"); @@ -140,7 +141,10 @@ configuration::host TestEngine::new_configuration_host( hst.parse("contacts", contacts.c_str()); configuration::command cmd("hcmd"); - cmd.parse("command_line", "echo 0"); + cmd.parse("command_line", "/bin/echo 0"); + if (!connector.empty()) { + cmd.parse("connector", connector.data()); + } hst.parse("check_command", "hcmd"); configuration::applier::command cmd_aply; cmd_aply.add_object(cmd); @@ -169,7 +173,8 @@ configuration::service TestEngine::new_configuration_service( const std::string& hostname, const std::string& description, const std::string& contacts, - uint64_t svc_id) { + uint64_t svc_id, + const std::string_view& connector) { configuration::service svc; svc.parse("host_name", hostname.c_str()); svc.parse("description", description.c_str()); @@ -187,9 +192,14 @@ configuration::service TestEngine::new_configuration_service( else svc.set_host_id(12); - configuration::command cmd("cmd"); - cmd.parse("command_line", "echo 'output| metric=$ARG1$;50;75'"); - svc.parse("check_command", "cmd!12"); + configuration::command cmd(fmt::format("cmd_serv_{}", svc_id)); + cmd.parse("command_line", + "/bin/echo -n 'output| metric=$ARG1$;50;75 " + "metric2=30ms;50:75;75:80;0;100'"); + if (!connector.empty()) { + cmd.parse("connector", connector.data()); + } + svc.parse("check_command", (cmd.command_name() + "!12").c_str()); configuration::applier::command cmd_aply; cmd_aply.add_object(cmd); diff --git a/engine/tests/test_engine.hh b/engine/tests/test_engine.hh index f20334c7577..1c335d7e775 100644 --- a/engine/tests/test_engine.hh +++ b/engine/tests/test_engine.hh @@ -41,14 +41,17 @@ class TestEngine : public ::testing::Test { std::string const& name, bool full, const std::string& notif = "a") const; - configuration::host new_configuration_host(std::string const& hostname, - std::string const& contacts, - uint64_t hst_id = 12); + configuration::host new_configuration_host( + std::string const& hostname, + std::string const& contacts, + uint64_t hst_id = 12, + const std::string_view& connector = ""); configuration::service new_configuration_service( std::string const& hostname, std::string const& description, std::string const& contacts, - uint64_t svc_id = 13); + uint64_t svc_id = 13, + const std::string_view& connector = ""); configuration::anomalydetection new_configuration_anomalydetection( std::string const& hostname, std::string const& description, diff --git a/packaging/centreon-monitoring-agent.yaml b/packaging/centreon-monitoring-agent.yaml index 83bba81f424..c452432cf47 100644 --- a/packaging/centreon-monitoring-agent.yaml +++ b/packaging/centreon-monitoring-agent.yaml @@ -51,6 +51,16 @@ contents: owner: centreon-monitoring-agent group: centreon-monitoring-agent +overrides: + rpm: + depends: + - openssl-libs >= 3 + - zlib + deb: + depends: + - libssl1.1 | libssl3 + - zlib1g + scripts: preinstall: ./scripts/centreon-monitoring-agent-preinstall.sh postinstall: ./scripts/centreon-monitoring-agent-postinstall.sh diff --git a/tests/broker-engine/opentelemetry.robot b/tests/broker-engine/opentelemetry.robot index 728e624a924..2397f9dbdca 100644 --- a/tests/broker-engine/opentelemetry.robot +++ b/tests/broker-engine/opentelemetry.robot @@ -2,6 +2,7 @@ Documentation Engine/Broker tests on opentelemetry engine server Resource ../resources/import.resource +Library ../resources/Agent.py Suite Setup Ctn Clean Before Suite Suite Teardown Ctn Clean After Suite @@ -142,6 +143,20 @@ BEOTEL_TELEGRAF_CHECK_HOST ${result} Ctn Check Host Output Resource Status With Timeout host_1 30 ${start} 0 HARD OK Should Be True ${result} hosts table not updated + + Log To Console export metrics + Ctn Send Otl To Engine 4317 ${resources_list} + + Sleep 5 + + + # feed and check + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK + Should Be True ${result} hosts table not updated + # check then feed, three times to modify hard state ${start} Ctn Get Round Current Date Ctn Schedule Forced Host Check host_1 @@ -224,6 +239,18 @@ BEOTEL_TELEGRAF_CHECK_SERVICE ${result} Ctn Check Service Output Resource Status With Timeout host_1 service_1 30 ${start} 0 HARD OK Should Be True ${result} services table not updated + Log To Console export metrics + Ctn Send Otl To Engine 4317 ${resources_list} + + Sleep 5 + + # feed and check + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 30 ${start} 0 OK + Should Be True ${result} services table not updated + # check then feed, three times to modify hard state ${start} Ctn Get Round Current Date ${resources_list} Ctn Create Otl Request ${2} host_1 service_1 @@ -392,6 +419,381 @@ BEOTEL_SERVE_TELEGRAF_CONFIGURATION_NO_CRYPTED ... unexpected telegraf server response: ${telegraf_conf_response.text} +BEOTEL_CENTREON_AGENT_CHECK_HOST + [Documentation] agent check host and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + 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":10}} + 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 Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get 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. + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp_2 + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp_2 + ... /bin/echo "OK check2 - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + #update conf engine, it must be taken into account by agent + Log To Console modify engine conf and reload engine + Ctn Reload Engine + + #wait for new data from agent + ${start} Ctn Get Round Current Date + ${content} Create List description: \"OK check2 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 22 + Should Be True ${result} "description: "OK check2" should be available. + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK check2 - 127.0.0.1: rta 0,010ms, lost 0% + Should Be True ${result} hosts table not updated + + +BEOTEL_CENTREON_AGENT_CHECK_SERVICE + [Documentation] agent check service and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + 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 Engine Config Add Command + ... ${0} + ... otel_check + ... /tmp/var/lib/centreon-engine/check.pl --id 456 + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + #service_1 check fail CRITICAL + Ctn Set Command Status 456 ${2} + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 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. + + ${content} Create List fifos:{"host_1,service_1" + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} fifos not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 2 Test check 456 + Should Be True ${result} services table not updated + + ${start} Ctn Get Round Current Date + #service_1 check ok + Ctn Set Command Status 456 ${0} + + ${content} Create List as_int: 0 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} status 0 not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 0 Test check 456 + Should Be True ${result} services table not updated + + +BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST + [Documentation] agent check host with reversed connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15, "reverse_connections":[{"host": "127.0.0.1","port": 4317}]}} + 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 Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Reverse Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for engine to connect to agent + ${content} Create List init from [.\\s]*127.0.0.1:4317 + ${result} Ctn Find Regex In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "init from localhost:4317" not found in log + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp_2 + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp_2 + ... /bin/echo "OK check2 - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + #update conf engine, it must be taken into account by agent + Log To Console modify engine conf and reload engine + Ctn Reload Engine + + #wait for new data from agent + ${start} Ctn Get Round Current Date + ${content} Create List description: \"OK check2 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} "description: "OK check2" should be available. + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK check2 - 127.0.0.1: rta 0,010ms, lost 0% + Should Be True ${result} hosts table not updated + + +BEOTEL_REVERSE_CENTREON_AGENT_CHECK_SERVICE + [Documentation] agent check service with reversed connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15, "reverse_connections":[{"host": "127.0.0.1","port": 4317}]}} + 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 Engine Config Add Command + ... ${0} + ... otel_check + ... /tmp/var/lib/centreon-engine/check.pl --id 456 + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + #service_1 check fail CRITICAL + Ctn Set Command Status 456 ${2} + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Reverse Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Ctn Get Round Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for engine to connect to agent + ${content} Create List init from [.\\s]*127.0.0.1:4317 + ${result} Ctn Find Regex In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "init from 127.0.0.1:4317" not found in log + + + ${content} Create List fifos:{"host_1,service_1" + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} fifos not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 2 Test check 456 + Should Be True ${result} services table not updated + + ${start} Ctn Get Round Current Date + #service_1 check ok + Ctn Set Command Status 456 ${0} + + ${content} Create List as_int: 0 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} status 0 not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 0 Test check 456 + Should Be True ${result} services table not updated + +BEOTEL_CENTREON_AGENT_CHECK_HOST_CRYPTED + [Documentation] agent check host with encrypted connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Copy File ../broker/grpc/test/grpc_test_keys/ca_1234.crt /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.key /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.crt /tmp/ + Ctn Add Otl ServerModule + ... 0 + ... {"otel_server":{"host": "0.0.0.0","port": 4317, "encryption": true, "public_cert": "/tmp/server_1234.crt", "private_key": "/tmp/server_1234.key", "ca_certificate": "/tmp/ca_1234.crt"},"max_length_grpc_log":0} + 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 Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent ${None} ${None} /tmp/ca_1234.crt + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for the otel server start + ${content} Create List encrypted server listening on 0.0.0.0:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "encrypted server listening on 0.0.0.0:4317" should be available. + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + + +BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST_CRYPTED + [Documentation] agent check host with encrypted reversed connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Copy File ../broker/grpc/test/grpc_test_keys/ca_1234.crt /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.key /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.crt /tmp/ + + Ctn Add Otl ServerModule + ... 0 + ... {"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15, "reverse_connections":[{"host": "localhost","port": 4317, "encryption": true, "ca_certificate": "/tmp/ca_1234.crt"}]}} + + 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 Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Reverse Centreon Agent /tmp/server_1234.key /tmp/server_1234.crt /tmp/ca_1234.crt + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for engine to connect to agent + ${content} Create List init from localhost:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "init from localhost:4317" not found in log + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + + + *** Keywords *** Ctn Create Otl Request [Documentation] create an otl request with nagios telegraf style diff --git a/tests/init-sql-docker.sh b/tests/init-sql-docker.sh index acbc4965601..70efc5b97ee 100755 --- a/tests/init-sql-docker.sh +++ b/tests/init-sql-docker.sh @@ -16,8 +16,8 @@ apt update && apt install -y mysql-client #create users if [ $database_type == 'mysql' ]; then echo "create users mysql" - mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'centreon'@'%' IDENTIFIED WITH mysql_native_password BY 'centreon'" - mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'root_centreon'@'%' IDENTIFIED WITH mysql_native_password BY 'centreon'" + mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'centreon'@'%' IDENTIFIED BY 'centreon'" + mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'root_centreon'@'%' IDENTIFIED BY 'centreon'" else #mariadb case ss -plant | grep -w 3306 diff --git a/tests/resources/Agent.py b/tests/resources/Agent.py new file mode 100644 index 00000000000..4497a4453f3 --- /dev/null +++ b/tests/resources/Agent.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +# +# Copyright 2023-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 +# + +from os import makedirs +from robot.libraries.BuiltIn import BuiltIn + +ETC_ROOT = BuiltIn().get_variable_value("${EtcRoot}") +CONF_DIR = ETC_ROOT + "/centreon-engine" + + +agent_config=""" +{ + "log_level":"trace", + "endpoint":"localhost:4317", + "host":"host_1", + "log_type":"file", + "log_file":"/tmp/var/log/centreon-engine/centreon-agent.log" """ + + +def ctn_config_centreon_agent(key_path:str = None, cert_path:str = None, ca_path:str = None): + """ctn_config_centreon_agent + Creates a default centreon agent config without encryption nor reverse connection + """ + makedirs(CONF_DIR, mode=0o777, exist_ok=True) + with open(f"{CONF_DIR}/centagent.json", "w") as ff: + ff.write(agent_config) + if key_path is not None or cert_path is not None or ca_path is not None: + ff.write(",\n \"encryption\":true") + if key_path is not None: + ff.write(f",\n \"private_key\":\"{key_path}\"") + if cert_path is not None: + ff.write(f",\n \"public_cert\":\"{cert_path}\"") + if ca_path is not None: + ff.write(f",\n \"ca_certificate\":\"{ca_path}\"") + ff.write("\n}\n") + + + +def ctn_config_reverse_centreon_agent(key_path:str = None, cert_path:str = None, ca_path:str = None): + """ctn_config_centreon_agent + Creates a default reversed centreon agent config without encryption listening on 0.0.0.0:4317 + """ + makedirs(CONF_DIR, mode=0o777, exist_ok=True) + with open(f"{CONF_DIR}/centagent.json", "w") as ff: + ff.write(agent_config) + ff.write(",\n \"reverse_connection\":true") + if key_path is not None or cert_path is not None or ca_path is not None: + ff.write(",\n \"encryption\":true") + if key_path is not None: + ff.write(f",\n \"private_key\":\"{key_path}\"") + if cert_path is not None: + ff.write(f",\n \"public_cert\":\"{cert_path}\"") + if ca_path is not None: + ff.write(f",\n \"ca_certificate\":\"{ca_path}\"") + ff.write("\n}\n") diff --git a/tests/resources/resources.resource b/tests/resources/resources.resource index f53d7f6ff08..474c70b1d4f 100644 --- a/tests/resources/resources.resource +++ b/tests/resources/resources.resource @@ -233,6 +233,11 @@ Ctn Stop Engine Broker And Save Logs EXCEPT Log Can't kindly stop Broker END + TRY + Ctn Kindly Stop Agent + EXCEPT + Log Can't kindly stop Agent + END Ctn Save Logs If Failed Ctn Get Engine Pid @@ -283,7 +288,9 @@ Ctn Save Logs Copy Files ${rrdLog} ${failDir} Copy Files ${moduleLog0} ${failDir} Copy Files ${engineLog0} ${failDir} + Copy Files ${ENGINE_LOG}/*.log ${failDir} Copy Files ${EtcRoot}/centreon-engine/config0/*.cfg ${failDir}/etc/centreon-engine/config0 + Copy Files ${EtcRoot}/centreon-engine/*.json ${failDir}/etc/centreon-engine Copy Files ${EtcRoot}/centreon-broker/*.json ${failDir}/etc/centreon-broker Move Files /tmp/lua*.log ${failDir} @@ -384,3 +391,30 @@ Ctn Wait For Engine To Be Ready ... ${result} ... A message telling check_for_external_commands() should be available in config${i}/centengine.log. END + + +Ctn Start Agent + Start Process /usr/bin/centagent ${EtcRoot}/centreon-engine/centagent.json alias=centreon_agent + +Ctn Kindly Stop Agent + #in most case centreon_agent is not started + ${centreon_agent_process} Get Process Object centreon_agent + + IF ${{$centreon_agent_process is None}} RETURN + + Send Signal To Process SIGTERM centreon_agent + ${result} Wait For Process centreon_agent timeout=60s + # In case of process not stopping + IF "${result}" == "${None}" + Log To Console "fail to stop centreon_agent" + Ctn Save Logs + Ctn Dump Process centreon_agent /usr/bin/centagent centreon_agent + Send Signal To Process SIGKILL centreon_agent + Fail centreon_agent not correctly stopped (coredump generated) + ELSE + IF ${result.rc} != 0 + Ctn Save Logs + Ctn Coredump Info centreon_agent /usr/bin/centagent centreon_agent + Fail centreon_agent not correctly stopped, result status: ${result.rc} + END + END