From 1debbb461f347323520b9681231bb8b016edcb0f Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Thu, 16 Dec 2021 09:55:44 +0100 Subject: [PATCH 01/16] Refacto streamconnector BSM v2 --- .../bsm/bsm_connector-apiv2.lua | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 centreon-certified/bsm/bsm_connector-apiv2.lua diff --git a/centreon-certified/bsm/bsm_connector-apiv2.lua b/centreon-certified/bsm/bsm_connector-apiv2.lua new file mode 100644 index 00000000..358d6621 --- /dev/null +++ b/centreon-certified/bsm/bsm_connector-apiv2.lua @@ -0,0 +1,300 @@ +-- +-- Copyright © 2021 Centreon +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the Licensself.sc_event.event. +-- You may obtain a copy of the License at +-- +-- http://www.apachself.sc_event.event.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 Licensself.sc_event.event. +-- +-- For more information : contact@centreon.com +-- +-- To work you need to provide to this script a Broker stream connector output configuration +-- with the following informations: +-- +-- source_ci (string): Name of the transmiter, usually Centreon server name +-- http_server_url (string): the full HTTP URL. Default: https://my.bsm.server:30005/bsmc/rest/events/ws-centreon/. +-- http_proxy_string (string): the full proxy URL if needed to reach the BSM server. Default: empty. +-- log_path (string): the log file to use +-- log_level (number): the log level (0, 1, 2, 3) where 3 is the maximum level. 0 logs almost nothing. 1 logs only the beginning of the script and errors. 2 logs a reasonable amount of verbosself.sc_event.event. 3 logs almost everything possible, to be used only for debug. Recommended value in production: 1. +-- max_buffer_size (number): how many events to store before sending them to the server. +-- max_buffer_age (number): flush the events when the specified time (in second) is reached (even if max_size is not reached). + +-- Libraries +local curl = require "cURL" +require("LuaXML") + +-- Centreon lua core libraries +local sc_common = require("centreon-stream-connectors-lib.sc_common") +local sc_logger = require("centreon-stream-connectors-lib.sc_logger") +local sc_broker = require("centreon-stream-connectors-lib.sc_broker") +local sc_event = require("centreon-stream-connectors-lib.sc_event") +local sc_params = require("centreon-stream-connectors-lib.sc_params") +local sc_macros = require("centreon-stream-connectors-lib.sc_macros") +local sc_flush = require("centreon-stream-connectors-lib.sc_flush") + +-- workaround https://github.com/centreon/centreon-broker/issues/201 +local previous_event = "" + +-------------------------------------------------------------------------------- +-- EventQueue class +-------------------------------------------------------------------------------- + +local EventQueue = {} +EventQueue.__index = EventQueue + +-------------------------------------------------------------------------------- +-- Constructor +-- @param conf The table given by the init() function and returned from the GUI +-- @return the new EventQueue +-------------------------------------------------------------------------------- + +function EventQueue.new(params) + local self = {} + self.fail = false + + local mandatory_parameters = { + "http_server_url" + } + + -- set up log configuration + local logfile = params.logfile or "/var/log/centreon-broker/bsm_connector-apiv2.log" + local log_level = params.log_level or 1 + + -- initiate mandatory objects + self.sc_logger = sc_logger.new(logfile, log_level) + self.sc_common = sc_common.new(self.sc_logger) + self.sc_broker = sc_broker.new(self.sc_logger) + self.sc_params = sc_params.new(self.sc_common, self.sc_logger) + + -- checking mandatory parameters and setting a fail flag + if not self.sc_params:is_mandatory_config_set(mandatory_parameters, params) then + self.fail = true + end + + -- overriding default parameters for this stream connector if the default values doesn't suit the basic needs + self.sc_params.params.accepted_categories = params.accepted_categories or "neb" + self.sc_params.params.accepted_elements = params.accepted_elements or "host_status,service_status" + self.sc_params.params.source_ci = params.source_ci or "Centreon" + self.sc_params.params.max_output_length = params.max_output_length or 1024 + + -- apply users params and check syntax of standard ones + self.sc_params:param_override(params) + self.sc_params:check_params() + + self.sc_macros = sc_macros.new(self.sc_params.params, self.sc_logger) + self.format_template = self.sc_params:load_event_format_file(true) + self.sc_params:build_accepted_elements_info() + self.sc_flush = sc_flush.new(self.sc_params.params, self.sc_logger) + + local categories = self.sc_params.params.bbdo.categories + local elements = self.sc_params.params.bbdo.elements + + self.format_event = { + [categories.neb.id] = { + [elements.host_status.id] = function () return self:format_event_host() end, + [elements.service_status.id] = function () return self:format_event_service() end + }, + [categories.bam.id] = {} + } + self.send_data_method = { + [1] = function (data, element) return self:send_data(data, element) end + } + + -- return EventQueue object + setmetatable(self, { __index = EventQueue }) + return self +end + +-- Format XML file with host infoamtion +function EventQueue:format_event_host() + local xml_host_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id) , '0') .. "" + local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.service.action_url, 'no action url for this host') + local xml_notes = "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.cache.service.notes, 'OS not set') .. "" + + self.sc_event.event.formated_event = { + "" + .. "" .. hostname .. "" + .. xml_host_severity .. xml_notes + .. "" .. xml_url .. "" + .. "" .. ifnil_or_empty(self.source_ci, 'Centreon') .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.host_id, '0') .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, '0') .. "" + .. "" + } +end + +-- Format XML file with service infoamtion +function EventQueue:format_event_service() + local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.service.notes_url, 'no notes url for this service') + local xml_service_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id, self.sc_event.event.service_id) , '0') .. "" + + self.sc_event.event.formated_event = { + "" + .. "" .. hostname .. "" + .. "" .. service_description .. "" + .. "" ..self.sc_event.event.state .. "" + .. "" ..self.sc_event.event.last_update .. "" + .. "" .. string.match(e.output, "^(.*)\n") .. "" + .. xml_service_severity + .. "" .. xml_url .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.host_id, '0') .. "" + .."" .. ifnil_or_empty(self.sc_event.event.service_id, '0') .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, '0') .. "" + .."" + } +end + +-------------------------------------------------------------------------------- +-- EventQueue:add method +-- @param e An event +-------------------------------------------------------------------------------- + +function EventQueue:add() + -- store event in self.events lists + local category = self.sc_event.event.category + local element = self.sc_event.event.element + + self.sc_logger:debug("[EventQueue:add]: add event in queue category: " .. tostring(self.sc_params.params.reverse_category_mapping[category]) + .. " element: " .. tostring(self.sc_params.params.reverse_element_mapping[category][element])) + + self.sc_logger:debug("[EventQueue:add]: queue size before adding event: " .. tostring(#self.sc_flush.queues[category][element].events)) + self.sc_flush.queues[category][element].events[#self.sc_flush.queues[category][element].events + 1] = self.sc_event.event.formated_event + + self.sc_logger:info("[EventQueue:add]: queue size is now: " .. tostring(#self.sc_flush.queues[category][element].events) + .. "max is: " .. tostring(self.sc_params.params.max_buffer_size)) +end + +function EventQueue:send_data(data, element) + self.sc_logger:debug("[EventQueue:send_data]: Starting to send data") + + -- write payload in the logfile for test purpose + if self.sc_params.params.send_data_test == 1 then + for _, xml_str in ipairs(data) do + http_post_data = http_post_data .. tostring(xml.eval(xml_str)) + end + + self.sc_logger:info(http_post_data) + return true + end + + local http_post_data = "" + + for _, xml_str in ipairs(data) do + http_post_data = http_post_data .. tostring(xml.eval(xml_str)) + end + + self.sc_logger:info("[EventQueue:send_data]: Going to send the following json " .. tostring(http_post_data)) + self.sc_logger:info("[EventQueue:send_data]: BSM Http Server URL is: \"" .. tostring(self.sc_params.params.http_server_url .. "\"")) + + local http_response_body = "" + local http_request = curl.easy() + :setopt_url(self.sc_params.params.http_server_url) + :setopt_writefunction( + function (response) + http_response_body = http_response_body .. tostring(response) + end + ) + :setopt(curl.OPT_TIMEOUT, self.sc_params.params.connection_timeout) + :setopt(curl.OPT_SSL_VERIFYPEER, self.sc_params.params.allow_insecure_connection) + :setopt( + curl.OPT_HTTPHEADER, + { + "Content-Type: text/xml", + "content-length: " .. string.len(http_post_data) + } + ) + + -- set proxy address configuration + if (self.sc_params.params.proxy_address ~= '') then + if (self.sc_params.params.proxy_port ~= '') then + http_request:setopt(curl.OPT_PROXY, self.sc_params.params.proxy_address .. ':' .. self.sc_params.params.proxy_port) + else + self.sc_logger:error("[EventQueue:send_data]: proxy_port parameter is not set but proxy_address is used") + end + end + + -- set proxy user configuration + if (self.sc_params.params.proxy_username ~= '') then + if (self.sc_params.params.proxy_password ~= '') then + http_request:setopt(curl.OPT_PROXYUSERPWD, self.sc_params.params.proxy_username .. ':' .. self.sc_params.params.proxy_password) + else + self.sc_logger:error("[EventQueue:send_data]: proxy_password parameter is not set but proxy_username is used") + end + end + + -- adding the HTTP POST data + http_request:setopt_postfields(http_post_data) + -- performing the HTTP request + http_request:perform() + -- collecting results + http_response_code = http_request:getinfo(curl.INFO_RESPONSE_CODE) + http_request:close() + + -- Handling the return code + local retval = false + if http_response_code == 202 or http_response_code == 200 then + self.sc_logger:info("[EventQueue:send_data]: HTTP POST request successful: return code is " .. tostring(http_response_code)) + retval = true + else + self.sc_logger:error("[EventQueue:send_data]: HTTP POST request FAILED, return code is " .. tostring(http_response_code) .. ". Message is: " .. tostring(http_response_body)) + end + return retval +end + +-------------------------------------------------------------------------------- +-- Required functions for Broker StreamConnector +-------------------------------------------------------------------------------- + +local queue + +-- Fonction init() +function init(conf) + queue = EventQueue.sc_event.event.new(conf) +end + +-- Fonction write() +function write(event) + -- First, flush all queues if needed (too old or size too big) + Queue.sc_event.event.sc_flush:flush_all_queues(Queue.sc_event.event.send_data_method[1]) + + -- skip event if a mandatory parameter is missing + if Queue.sc_event.event.fail then + Queue.sc_event.event.sc_logger:error("Skipping event because a mandatory parameter is not set") + return true + end + + -- initiate event object + Queue.sc_event.event.sc_event = sc_event.new(event, Queue.sc_event.event.sc_params.params, Queue.sc_event.event.sc_common, Queue.sc_event.event.sc_logger, Queue.sc_event.event.sc_broker) + + -- drop event if wrong category + if not Queue.sc_event.event.sc_event:is_valid_category() then + Queue.sc_event.event.sc_logger:debug("dropping event because category is not valid. Event category is: " + .. tostring(Queue.sc_event.event.sc_params.params.reverse_category_mapping[Queue.sc_event.event.sc_event.event.category])) + return true + end + + -- drop event if wrong element + if not Queue.sc_event.event.sc_event:is_valid_element() then + Queue.sc_event.event.sc_logger:debug("dropping event because element is not valid. Event element is: " + .. tostring(Queue.sc_event.event.sc_params.params.reverse_element_mapping[Queue.sc_event.event.sc_event.event.category][Queue.sc_event.event.sc_event.event.element])) + return true + end + + -- drop event if it is not validated + if Queue.sc_event.event.sc_event:is_valid_event() then + queue:format_accepted_event() + else + return true + end + + -- Since we've added an event to a specific queue, flush it if queue is full + Queue.sc_event.event.sc_flush:flush_queue(Queue.sc_event.event.send_data_method[1], Queue.sc_event.event.sc_event.event.category, Queue.sc_event.event.sc_event.event.element) + return true +end From 30cb05a936390d35449b8eb85c4657d27d378f8b Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Mon, 20 Dec 2021 10:39:04 +0100 Subject: [PATCH 02/16] Review code after firts recommandations --- .../bsm/bsm_connector-apiv2.lua | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/centreon-certified/bsm/bsm_connector-apiv2.lua b/centreon-certified/bsm/bsm_connector-apiv2.lua index 358d6621..192f4a17 100644 --- a/centreon-certified/bsm/bsm_connector-apiv2.lua +++ b/centreon-certified/bsm/bsm_connector-apiv2.lua @@ -114,26 +114,27 @@ end -- Format XML file with host infoamtion function EventQueue:format_event_host() - local xml_host_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id) , '0') .. "" - local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.service.action_url, 'no action url for this host') - local xml_notes = "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.cache.service.notes, 'OS not set') .. "" + local xml_host_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id) , 0) .. "" + local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.action_url, 'no action url for this host') + local xml_notes = "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.notes, 'no notes found on host') .. "" self.sc_event.event.formated_event = { "" .. "" .. hostname .. "" - .. xml_host_severity .. xml_notes + .. "" .. xml_host_severity .. "" + .. "" .. xml_notes .. "" .. "" .. xml_url .. "" .. "" .. ifnil_or_empty(self.source_ci, 'Centreon') .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.host_id, '0') .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, '0') .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" .. "" } end -- Format XML file with service infoamtion function EventQueue:format_event_service() - local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.service.notes_url, 'no notes url for this service') - local xml_service_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id, self.sc_event.event.service_id) , '0') .. "" + local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.notes_url, 'no url for this service') + local xml_service_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id, self.sc_event.event.service_id) , 0) .. "" self.sc_event.event.formated_event = { "" @@ -141,13 +142,13 @@ function EventQueue:format_event_service() .. "" .. service_description .. "" .. "" ..self.sc_event.event.state .. "" .. "" ..self.sc_event.event.last_update .. "" - .. "" .. string.match(e.output, "^(.*)\n") .. "" - .. xml_service_severity + .. "" .. string.match(self.sc_event.event.output, "^(.*)\n") .. "" + .. "" .. xml_service_severity .. "" .. "" .. xml_url .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.host_id, '0') .. "" - .."" .. ifnil_or_empty(self.sc_event.event.service_id, '0') .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, '0') .. "" - .."" + .. "" .. ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.service_id, 0) .. "" + .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" + .. "" } end @@ -256,45 +257,45 @@ local queue -- Fonction init() function init(conf) - queue = EventQueue.sc_event.event.new(conf) + queue = EventQueue.new(conf) end -- Fonction write() function write(event) -- First, flush all queues if needed (too old or size too big) - Queue.sc_event.event.sc_flush:flush_all_queues(Queue.sc_event.event.send_data_method[1]) + queue.sc_flush:flush_all_queues(queue.send_data_method[1]) -- skip event if a mandatory parameter is missing - if Queue.sc_event.event.fail then - Queue.sc_event.event.sc_logger:error("Skipping event because a mandatory parameter is not set") + if queue.event.fail then + queue.event.sc_logger:error("Skipping event because a mandatory parameter is not set") return true end -- initiate event object - Queue.sc_event.event.sc_event = sc_event.new(event, Queue.sc_event.event.sc_params.params, Queue.sc_event.event.sc_common, Queue.sc_event.event.sc_logger, Queue.sc_event.event.sc_broker) + queue.event.sc_event = sc_event.new(event, queue.event.sc_params.params, queue.event.sc_common, queue.event.sc_logger, queue.event.sc_broker) -- drop event if wrong category - if not Queue.sc_event.event.sc_event:is_valid_category() then - Queue.sc_event.event.sc_logger:debug("dropping event because category is not valid. Event category is: " - .. tostring(Queue.sc_event.event.sc_params.params.reverse_category_mapping[Queue.sc_event.event.sc_event.event.category])) + if not queue.event.sc_event:is_valid_category() then + queue.event.sc_logger:debug("dropping event because category is not valid. Event category is: " + .. tostring(queue.event.sc_params.params.reverse_category_mapping[queue.event.sc_event.event.category])) return true end -- drop event if wrong element - if not Queue.sc_event.event.sc_event:is_valid_element() then - Queue.sc_event.event.sc_logger:debug("dropping event because element is not valid. Event element is: " - .. tostring(Queue.sc_event.event.sc_params.params.reverse_element_mapping[Queue.sc_event.event.sc_event.event.category][Queue.sc_event.event.sc_event.event.element])) + if not queue.event.sc_event:is_valid_element() then + queue.event.sc_logger:debug("dropping event because element is not valid. Event element is: " + .. tostring(queue.event.sc_params.params.reverse_element_mapping[queue.event.sc_event.event.category][queue.event.sc_event.event.element])) return true end -- drop event if it is not validated - if Queue.sc_event.event.sc_event:is_valid_event() then + if queue.event.sc_event:is_valid_event() then queue:format_accepted_event() else return true end -- Since we've added an event to a specific queue, flush it if queue is full - Queue.sc_event.event.sc_flush:flush_queue(Queue.sc_event.event.send_data_method[1], Queue.sc_event.event.sc_event.event.category, Queue.sc_event.event.sc_event.event.element) + queue.event.sc_flush:flush_queue(queue.event.send_data_method[1], queue.event.sc_event.event.category, queue.event.sc_event.event.element) return true end From 69cb151f2a0ff591851fc7d955dfb44e9d2b2a8b Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Mon, 20 Dec 2021 10:40:56 +0100 Subject: [PATCH 03/16] Change name file --- .../bsm/{bsm_connector-apiv2.lua => bsm-events-apiv2.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename centreon-certified/bsm/{bsm_connector-apiv2.lua => bsm-events-apiv2.lua} (100%) diff --git a/centreon-certified/bsm/bsm_connector-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua similarity index 100% rename from centreon-certified/bsm/bsm_connector-apiv2.lua rename to centreon-certified/bsm/bsm-events-apiv2.lua From 17414d567c9caa10cfb0e94e51405e173b9917a7 Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:20:00 +0100 Subject: [PATCH 04/16] Review function write --- centreon-certified/bsm/bsm-events-apiv2.lua | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index 192f4a17..9bbbb9e0 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -266,36 +266,36 @@ function write(event) queue.sc_flush:flush_all_queues(queue.send_data_method[1]) -- skip event if a mandatory parameter is missing - if queue.event.fail then - queue.event.sc_logger:error("Skipping event because a mandatory parameter is not set") + if queue.fail then + queue.sc_logger:error("Skipping event because a mandatory parameter is not set") return true end -- initiate event object - queue.event.sc_event = sc_event.new(event, queue.event.sc_params.params, queue.event.sc_common, queue.event.sc_logger, queue.event.sc_broker) + queue.sc_event = sc_event.new(event, queue.sc_params.params, queue.sc_common, queue.sc_logger, queue.sc_broker) -- drop event if wrong category - if not queue.event.sc_event:is_valid_category() then - queue.event.sc_logger:debug("dropping event because category is not valid. Event category is: " - .. tostring(queue.event.sc_params.params.reverse_category_mapping[queue.event.sc_event.event.category])) + if not queue.sc_event:is_valid_category() then + queue.sc_logger:debug("dropping event because category is not valid. Event category is: " + .. tostring(queue.sc_params.params.reverse_category_mapping[queue.sc_event.event.category])) return true end -- drop event if wrong element - if not queue.event.sc_event:is_valid_element() then - queue.event.sc_logger:debug("dropping event because element is not valid. Event element is: " - .. tostring(queue.event.sc_params.params.reverse_element_mapping[queue.event.sc_event.event.category][queue.event.sc_event.event.element])) + if not queue.sc_event:is_valid_element() then + queue.sc_logger:debug("dropping event because element is not valid. Event element is: " + .. tostring(queue.sc_params.params.reverse_element_mapping[queue.sc_event.event.category][queue.sc_event.event.element])) return true end -- drop event if it is not validated - if queue.event.sc_event:is_valid_event() then + if queue.sc_event:is_valid_event() then queue:format_accepted_event() else return true end -- Since we've added an event to a specific queue, flush it if queue is full - queue.event.sc_flush:flush_queue(queue.event.send_data_method[1], queue.event.sc_event.event.category, queue.event.sc_event.event.element) + queue.sc_flush:flush_queue(queue.send_data_method[1], queue.sc_event.event.category, queue.sc_event.event.element) return true end From 5cef5f5862d4c918e8b48f777a64ccf874902de3 Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Tue, 28 Dec 2021 13:47:35 +0100 Subject: [PATCH 05/16] Refacto event treatment for when send_data_test=1 --- centreon-certified/bsm/bsm-events-apiv2.lua | 82 +++++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index 9bbbb9e0..85732fea 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -112,21 +112,54 @@ function EventQueue.new(params) return self end +-------------------------------------------------------------------------------- +---- EventQueue:format_event method +--------------------------------------------------------------------------------- +function EventQueue:format_accepted_event() + local category = self.sc_event.event.category + local element = self.sc_event.event.element + local template = self.sc_params.params.format_template[category][element] + self.sc_logger:debug("[EventQueue:format_event]: starting format event") + self.sc_event.event.formated_event = {} + + if self.format_template and template ~= nil and template ~= "" then + for index, value in pairs(template) do + self.sc_event.event.formated_event[index] = self.sc_macros:replace_sc_macro(value, self.sc_event.event) + end + else + -- can't format event if stream connector is not handling this kind of event and that it is not handled with a template file + if not self.format_event[category][element] then + self.sc_logger:error("[format_event]: You are trying to format an event with category: " + .. tostring(self.sc_params.params.reverse_category_mapping[category]) .. " and element: " + .. tostring(self.sc_params.params.reverse_element_mapping[category][element]) + .. ". If it is a not a misconfiguration, you should create a format file to handle this kind of element") + else + self.format_event[category][element]() + end + end + + self:add() + self.sc_logger:debug("[EventQueue:format_event]: event formatting is finished") +end + -- Format XML file with host infoamtion function EventQueue:format_event_host() - local xml_host_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id) , 0) .. "" + local xml_host_severity = self.sc_broker:get_severity(self.sc_event.event.host_id) local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.action_url, 'no action url for this host') - local xml_notes = "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.notes, 'no notes found on host') .. "" + local xml_notes = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.notes, 'no notes found on host') + if xml_host_severity == false then + xml_host_severity = 0 + end self.sc_event.event.formated_event = { "" - .. "" .. hostname .. "" + .. "" .. self.sc_event.event.cache.host.name .. "" .. "" .. xml_host_severity .. "" - .. "" .. xml_notes .. "" + .. "" .. xml_notes .. "" .. "" .. xml_url .. "" - .. "" .. ifnil_or_empty(self.source_ci, 'Centreon') .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" + .. "" .. self.sc_common:ifnil_or_empty(self.source_ci, 'Centreon') .. "" + .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" + .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" .. "" } end @@ -134,20 +167,24 @@ end -- Format XML file with service infoamtion function EventQueue:format_event_service() local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.notes_url, 'no url for this service') - local xml_service_severity = "" .. self.sc_common:ifnil_or_empty(self.sc_broker:get_severity(self.sc_event.event.host_id, self.sc_event.event.service_id) , 0) .. "" - + local xml_service_severity = self.sc_broker:get_severity(self.sc_event.event.host_id, self.sc_event.event.service_id) + + if xml_service_severity == false then + xml_service_severity = 0 + end + self.sc_event.event.formated_event = { "" - .. "" .. hostname .. "" - .. "" .. service_description .. "" + .. "" .. self.sc_event.event.cache.host.name .. "" + .. "" .. self.sc_event.event.cache.service.description .. "" .. "" ..self.sc_event.event.state .. "" .. "" ..self.sc_event.event.last_update .. "" .. "" .. string.match(self.sc_event.event.output, "^(.*)\n") .. "" - .. "" .. xml_service_severity .. "" + .. "" .. xml_service_severity .. "" .. "" .. xml_url .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.service_id, 0) .. "" - .. "" .. ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" + .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" + .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.service_id, 0) .. "" + .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" .. "" } end @@ -177,21 +214,18 @@ function EventQueue:send_data(data, element) -- write payload in the logfile for test purpose if self.sc_params.params.send_data_test == 1 then - for _, xml_str in ipairs(data) do - http_post_data = http_post_data .. tostring(xml.eval(xml_str)) + for _, xml_str in pairs(data) do + self.sc_logger:info( " value is: " .. xml.str(xml.new(xml_str,0,'<event_data>'))) end - - self.sc_logger:info(http_post_data) - return true + return true end - local http_post_data = "" - for _, xml_str in ipairs(data) do - http_post_data = http_post_data .. tostring(xml.eval(xml_str)) + for _, xml_str in pairs(data) do + http_post_data = http_post_data .. xml.str(xml.new(xml_str,0,'<event_data>')) end - self.sc_logger:info("[EventQueue:send_data]: Going to send the following json " .. tostring(http_post_data)) + self.sc_logger:info("[EventQueue:send_data]: Going to send the following xml " .. xml.str(http_post_data)) self.sc_logger:info("[EventQueue:send_data]: BSM Http Server URL is: \"" .. tostring(self.sc_params.params.http_server_url .. "\"")) local http_response_body = "" From ecb51472ca7bae748792f0012cabd19a50a55271 Mon Sep 17 00:00:00 2001 From: psamecentreon Date: Fri, 14 Jan 2022 19:33:05 +0000 Subject: [PATCH 06/16] Review code with of formated data --- centreon-certified/bsm/bsm-events-apiv2.lua | 55 ++++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index 85732fea..b2424c95 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -65,8 +65,8 @@ function EventQueue.new(params) -- set up log configuration local logfile = params.logfile or "/var/log/centreon-broker/bsm_connector-apiv2.log" - local log_level = params.log_level or 1 - + local log_level = params.log_level or 1 + -- initiate mandatory objects self.sc_logger = sc_logger.new(logfile, log_level) self.sc_common = sc_common.new(self.sc_logger) @@ -106,7 +106,7 @@ function EventQueue.new(params) self.send_data_method = { [1] = function (data, element) return self:send_data(data, element) end } - + -- return EventQueue object setmetatable(self, { __index = EventQueue }) return self @@ -125,7 +125,7 @@ function EventQueue:format_accepted_event() if self.format_template and template ~= nil and template ~= "" then for index, value in pairs(template) do self.sc_event.event.formated_event[index] = self.sc_macros:replace_sc_macro(value, self.sc_event.event) - end + end else -- can't format event if stream connector is not handling this kind of event and that it is not handled with a template file if not self.format_event[category][element] then @@ -147,11 +147,11 @@ function EventQueue:format_event_host() local xml_host_severity = self.sc_broker:get_severity(self.sc_event.event.host_id) local xml_url = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.action_url, 'no action url for this host') local xml_notes = self.sc_common:ifnil_or_empty(self.sc_event.event.cache.host.notes, 'no notes found on host') - + if xml_host_severity == false then xml_host_severity = 0 end - self.sc_event.event.formated_event = { + --[[.sc_event.event.formated_event = { "" .. "" .. self.sc_event.event.cache.host.name .. "" .. "" .. xml_host_severity .. "" @@ -161,6 +161,15 @@ function EventQueue:format_event_host() .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" .. "" + }--]] + self.sc_event.event.formated_event = { + hostname = self.sc_event.event.cache.host.name, + host_severity = xml_host_severity, + host_notes = xml_notes, + url = xml_url, + source_ci = self.sc_common:ifnil_or_empty(self.source_ci, 'Centreon'), + source_host_id = self.sc_common:ifnil_or_empty(self.sc_event.event.host_id, 0), + scheduled_downtime_depth = self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) } end @@ -173,7 +182,7 @@ function EventQueue:format_event_service() xml_service_severity = 0 end - self.sc_event.event.formated_event = { + --[[self.sc_event.event.formated_event = { "" .. "" .. self.sc_event.event.cache.host.name .. "" .. "" .. self.sc_event.event.cache.service.description .. "" @@ -186,7 +195,20 @@ function EventQueue:format_event_service() .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.service_id, 0) .. "" .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" .. "" - } + }--]] + + self.sc_event.event.formated_event = { + hostname = self.sc_event.event.cache.host.name, + svc_desc = self.sc_event.event.cache.service.description, + state = self.sc_event.event.state, + last_update = self.sc_event.event.last_update, + output = string.match(self.sc_event.event.output, "^(.*)\n"), + service_severity = xml_service_severity, + url = xml_url, + source_host_id = self.sc_common:ifnil_or_empty(self.sc_event.event.host_id, 0), + source_svc_id = self.sc_common:ifnil_or_empty(self.sc_event.event.service_id, 0), + scheduled_downtime_depth = self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) + } end -------------------------------------------------------------------------------- @@ -211,19 +233,26 @@ end function EventQueue:send_data(data, element) self.sc_logger:debug("[EventQueue:send_data]: Starting to send data") - + -- write payload in the logfile for test purpose if self.sc_params.params.send_data_test == 1 then + local data_formated = "" for _, xml_str in pairs(data) do - self.sc_logger:info( " value is: " .. xml.str(xml.new(xml_str,0,'<event_data>'))) + for index, http_post_data in pairs(xml_str) do + data_formated = data_formated .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(http_post_data)) .. "" + end end + self.sc_logger:notice(tostring(data_formated) .. "") return true end - local http_post_data = "" + local http_post_data = "" for _, xml_str in pairs(data) do - http_post_data = http_post_data .. xml.str(xml.new(xml_str,0,'<event_data>')) + for index, data_formated in pairs(xml_str) do + http_post_data = http_post_data .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(data_formated)) .. "" + end end + http_post_data = http_post_data .. "" self.sc_logger:info("[EventQueue:send_data]: Going to send the following xml " .. xml.str(http_post_data)) self.sc_logger:info("[EventQueue:send_data]: BSM Http Server URL is: \"" .. tostring(self.sc_params.params.http_server_url .. "\"")) @@ -332,4 +361,4 @@ function write(event) -- Since we've added an event to a specific queue, flush it if queue is full queue.sc_flush:flush_queue(queue.send_data_method[1], queue.sc_event.event.category, queue.sc_event.event.element) return true -end +end \ No newline at end of file From 84fb5bfd235f2f8de97a21edc30d79a02272000b Mon Sep 17 00:00:00 2001 From: psamecentreon Date: Fri, 14 Jan 2022 19:55:01 +0000 Subject: [PATCH 07/16] delete comment block --- centreon-certified/bsm/bsm-events-apiv2.lua | 29 ++------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index b2424c95..542dc927 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -151,17 +151,7 @@ function EventQueue:format_event_host() if xml_host_severity == false then xml_host_severity = 0 end - --[[.sc_event.event.formated_event = { - "" - .. "" .. self.sc_event.event.cache.host.name .. "" - .. "" .. xml_host_severity .. "" - .. "" .. xml_notes .. "" - .. "" .. xml_url .. "" - .. "" .. self.sc_common:ifnil_or_empty(self.source_ci, 'Centreon') .. "" - .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" - .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" - .. "" - }--]] + self.sc_event.event.formated_event = { hostname = self.sc_event.event.cache.host.name, host_severity = xml_host_severity, @@ -182,21 +172,6 @@ function EventQueue:format_event_service() xml_service_severity = 0 end - --[[self.sc_event.event.formated_event = { - "" - .. "" .. self.sc_event.event.cache.host.name .. "" - .. "" .. self.sc_event.event.cache.service.description .. "" - .. "" ..self.sc_event.event.state .. "" - .. "" ..self.sc_event.event.last_update .. "" - .. "" .. string.match(self.sc_event.event.output, "^(.*)\n") .. "" - .. "" .. xml_service_severity .. "" - .. "" .. xml_url .. "" - .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.host_id, 0) .. "" - .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.service_id, 0) .. "" - .. "" .. self.sc_common:ifnil_or_empty(self.sc_event.event.scheduled_downtime_depth, 0) .. "" - .. "" - }--]] - self.sc_event.event.formated_event = { hostname = self.sc_event.event.cache.host.name, svc_desc = self.sc_event.event.cache.service.description, @@ -361,4 +336,4 @@ function write(event) -- Since we've added an event to a specific queue, flush it if queue is full queue.sc_flush:flush_queue(queue.send_data_method[1], queue.sc_event.event.category, queue.sc_event.event.element) return true -end \ No newline at end of file +end From 85b28b7200a5c9e8f67d8be1ba579dc270e12916 Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Fri, 14 Jan 2022 20:57:22 +0100 Subject: [PATCH 08/16] Create function xml_escape for bsm-event SC --- .../sc_common.lua | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/modules/centreon-stream-connectors-lib/sc_common.lua b/modules/centreon-stream-connectors-lib/sc_common.lua index 0d42b7a3..9d9ba13f 100644 --- a/modules/centreon-stream-connectors-lib/sc_common.lua +++ b/modules/centreon-stream-connectors-lib/sc_common.lua @@ -247,4 +247,31 @@ function ScCommon:json_escape(string) return string end +--- xml_escape: escape xml special characters in a string +-- @param string (string) the string that must be escaped +-- @return string (string) the string with escaped characters +function ScCommon:xml_escape(string) + local type = type(string) + + -- check that param is a valid string + if string == nil or type == "table" then + self.sc_logger:error("[sc_common:escape_string]: the input parameter is not valid, it is either nil or a table. Sent value: " .. tostring(string)) + return string + end + + -- nothing to escape in a boolean or number value + if type ~= "string" then + return string + end + + -- escape all characters + string = string.gsub(string, '<', '<') + string = string.gsub(string, '>', '>') + string = string.gsub(string, '&', '&') + string = string.gsub(string, ''', "'") + string = string.gsub(string, '"', '"') + + return string +end + return sc_common From fac6bb3828307c9f4c9dfcff8bc4cc670b0d2ace Mon Sep 17 00:00:00 2001 From: psamecentreon Date: Fri, 21 Jan 2022 16:16:26 +0000 Subject: [PATCH 09/16] refacto to v3 --- centreon-certified/bsm/bsm-events-apiv2.lua | 112 +++++++++++++------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index 542dc927..d60ad368 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -104,7 +104,11 @@ function EventQueue.new(params) [categories.bam.id] = {} } self.send_data_method = { - [1] = function (data, element) return self:send_data(data, element) end + [1] = function (payload) return self:send_data(payload) end + } + + self.build_payload_method = { + [1] = function (payload, event) return self:build_payload(payload, event) end } -- return EventQueue object @@ -151,7 +155,7 @@ function EventQueue:format_event_host() if xml_host_severity == false then xml_host_severity = 0 end - + self.sc_event.event.formated_event = { hostname = self.sc_event.event.cache.host.name, host_severity = xml_host_severity, @@ -206,30 +210,41 @@ function EventQueue:add() .. "max is: " .. tostring(self.sc_params.params.max_buffer_size)) end -function EventQueue:send_data(data, element) +-------------------------------------------------------------------------------- +-- EventQueue:build_payload, concatenate data so it is ready to be sent +-- @param payload {string} json encoded string +-- @param event {table} the event that is going to be added to the payload +-- @return payload {string} json encoded string +-------------------------------------------------------------------------------- +function EventQueue:build_payload(payload, event) + if not payload then + payload = "[send_data]: " .. "" + for index, xml_str in pairs(event) do + payload = payload .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(xml_str)) .. "" + end + payload = payload .. "" + + else + payload = payload .. "[send_data]: " .. "" + for index, xml_str in pairs(event) do + payload = payload .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(xml_str)) .. "" + end + payload = payload .. "" + end + + return payload +end + +function EventQueue:send_data(payload) self.sc_logger:debug("[EventQueue:send_data]: Starting to send data") -- write payload in the logfile for test purpose if self.sc_params.params.send_data_test == 1 then - local data_formated = "" - for _, xml_str in pairs(data) do - for index, http_post_data in pairs(xml_str) do - data_formated = data_formated .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(http_post_data)) .. "" - end - end - self.sc_logger:notice(tostring(data_formated) .. "") + self.sc_logger:notice("[send_data]: " .. tostring(payload)) return true end - local http_post_data = "" - for _, xml_str in pairs(data) do - for index, data_formated in pairs(xml_str) do - http_post_data = http_post_data .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(data_formated)) .. "" - end - end - http_post_data = http_post_data .. "" - - self.sc_logger:info("[EventQueue:send_data]: Going to send the following xml " .. xml.str(http_post_data)) + self.sc_logger:info("[EventQueue:send_data]: Going to send the following xml " .. tostring(payload)) self.sc_logger:info("[EventQueue:send_data]: BSM Http Server URL is: \"" .. tostring(self.sc_params.params.http_server_url .. "\"")) local http_response_body = "" @@ -246,7 +261,7 @@ function EventQueue:send_data(data, element) curl.OPT_HTTPHEADER, { "Content-Type: text/xml", - "content-length: " .. string.len(http_post_data) + "content-length: " .. string.len(payload) } ) @@ -269,7 +284,7 @@ function EventQueue:send_data(data, element) end -- adding the HTTP POST data - http_request:setopt_postfields(http_post_data) + http_request:setopt_postfields(payload) -- performing the HTTP request http_request:perform() -- collecting results @@ -300,40 +315,61 @@ end -- Fonction write() function write(event) - -- First, flush all queues if needed (too old or size too big) - queue.sc_flush:flush_all_queues(queue.send_data_method[1]) - -- skip event if a mandatory parameter is missing if queue.fail then queue.sc_logger:error("Skipping event because a mandatory parameter is not set") - return true + return false end -- initiate event object queue.sc_event = sc_event.new(event, queue.sc_params.params, queue.sc_common, queue.sc_logger, queue.sc_broker) - -- drop event if wrong category - if not queue.sc_event:is_valid_category() then + if queue.sc_event:is_valid_category() then + if queue.sc_event:is_valid_element() then + -- format event if it is validated + if queue.sc_event:is_valid_event() then + queue:format_accepted_event() + end + --- log why the event has been dropped + else + queue.sc_logger:debug("dropping event because element is not valid. Event element is: " + .. tostring(queue.sc_params.params.reverse_element_mapping[queue.sc_event.event.category][queue.sc_event.event.element])) + end + else queue.sc_logger:debug("dropping event because category is not valid. Event category is: " .. tostring(queue.sc_params.params.reverse_category_mapping[queue.sc_event.event.category])) + end + + return flush() +end + +-- flush method is called by broker every now and then (more often when broker has nothing else to do) +function flush() + local queues_size = queue.sc_flush:get_queues_size() + + -- nothing to flush + if queues_size == 0 then return true end - -- drop event if wrong element - if not queue.sc_event:is_valid_element() then - queue.sc_logger:debug("dropping event because element is not valid. Event element is: " - .. tostring(queue.sc_params.params.reverse_element_mapping[queue.sc_event.event.category][queue.sc_event.event.element])) + -- flush all queues because last global flush is too old + if queue.sc_flush.last_global_flush < os.time() - queue.sc_params.params.max_all_queues_age then + if not queue.sc_flush:flush_all_queues(queue.build_payload_method[1], queue.send_data_method[1]) then + return false + end + return true end - -- drop event if it is not validated - if queue.sc_event:is_valid_event() then - queue:format_accepted_event() - else + -- flush queues because too many events are stored in them + if queues_size > queue.sc_params.params.max_buffer_size then + if not queue.sc_flush:flush_all_queues(queue.build_payload_method[1], queue.send_data_method[1]) then + return false + end + return true end - -- Since we've added an event to a specific queue, flush it if queue is full - queue.sc_flush:flush_queue(queue.send_data_method[1], queue.sc_event.event.category, queue.sc_event.event.element) - return true + -- there are events in the queue but they were not ready to be send + return false end From 5fcca75f512b27a43482a17d43897e4c16e92b6a Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Fri, 21 Jan 2022 17:20:13 +0100 Subject: [PATCH 10/16] Delete file because already in others PR --- .../sc_common.lua | 277 ------------------ 1 file changed, 277 deletions(-) delete mode 100644 modules/centreon-stream-connectors-lib/sc_common.lua diff --git a/modules/centreon-stream-connectors-lib/sc_common.lua b/modules/centreon-stream-connectors-lib/sc_common.lua deleted file mode 100644 index 9d9ba13f..00000000 --- a/modules/centreon-stream-connectors-lib/sc_common.lua +++ /dev/null @@ -1,277 +0,0 @@ -#!/usr/bin/lua - ---- --- Module with common methods for Centreon Stream Connectors --- @module sc_common --- @alias sc_common - -local sc_common = {} - -local sc_logger = require("centreon-stream-connectors-lib.sc_logger") - ---- ifnil_or_empty: change a nil or empty variable for a specified value --- @param var (string|number) the variable that needs to be checked --- @param alt (string|number|table) the alternate value if "var" is nil or empty --- @return var or alt (string|number|table) the variable or the alternate value -local function ifnil_or_empty(var, alt) - if var == nil or var == "" then - return alt - else - return var - end -end - -local ScCommon = {} - -function sc_common.new(sc_logger) - local self = {} - - self.sc_logger = sc_logger - if not self.sc_logger then - self.sc_logger = sc_logger.new() - end - - setmetatable(self, { __index = ScCommon }) - - return self -end - ---- ifnil_or_empty: change a nil or empty variable for a specified value --- @param var (string|number) the variable that needs to be checked --- @param alt (string|number|table) the alternate value if "var" is nil or empty --- @return var or alt (string|number|table) the variable or the alternate value -function ScCommon:ifnil_or_empty(var, alt) - return ifnil_or_empty(var, alt) -end - ---- if_wrong_type: change a wrong type variable with a default value --- @param var (any) the variable that needs to be checked --- @param type (string) the expected type of the variable --- @param default (any) the default value for the variable if type is wrong --- @return var or default (any) the variable if type is good or the default value -function ScCommon:if_wrong_type(var, var_type, default) - if type(var) == var_type then - return var - end - - return default -end - ---- boolean_to_number: convert boolean variable to number --- @param boolean (boolean) the boolean that will be converted --- @return (number) a number according to the boolean value -function ScCommon:boolean_to_number(boolean) - return boolean and 1 or 0 -end - ---- number_to_boolean: convert a 0, 1 number to its boolean counterpart --- @param number (number) the number to convert --- @return (boolean) true if param is 1, false if param is 0 -function ScCommon:number_to_boolean(number) - if number ~= 0 and number ~= 1 then - self.sc_logger:error("[sc_common:number_to_boolean]: number is not 1 or 0. Returning nil. Parameter value is: " .. tostring(number)) - return nil - end - - if number == 1 then - return true - end - - return false -end - - ---- check_boolean_number_option_syntax: make sure the number is either 1 or 0 --- @param number (number) the boolean number that must be validated --- @param default (number) the default value that is going to be return if the default number is not validated --- @return number (number) a boolean number -function ScCommon:check_boolean_number_option_syntax(number, default) - if number ~= 1 and number ~= 0 then - number = default - end - - return number -end - ---- split: convert a string into a table --- @param text (string) the string that is going to be splitted into a table --- @param [opt] separator (string) the separator character that will be used to split the string --- @return false (boolean) if text param is empty or nil --- @return table (table) a table of strings -function ScCommon:split (text, separator) - -- return false if text is nil or empty - if text == nil or text == "" then - self.sc_logger:error("[sc_common:split]: could not split text because it is nil or empty") - return false - end - - local hash = {} - - -- set default separator - separator = ifnil_or_empty(separator, ",") - - for value in string.gmatch(text, "([^" .. separator .. "]+)") do - table.insert(hash, value) - end - - return hash -end - ---- compare_numbers: compare two numbers, if comparison is valid, then return true --- @param firstNumber {number} --- @param secondNumber {number} --- @param operator {string} the mathematical operator that is used for the comparison --- @return {boolean} -function ScCommon:compare_numbers(firstNumber, secondNumber, operator) - if operator ~= "==" and operator ~= "~=" and operator ~= "<" and operator ~= ">" and operator ~= ">=" and operator ~= "<=" then - return nil - end - - if type(firstNumber) ~= "number" or type(secondNumber) ~= "number" then - return nil - end - - if operator == "<" then - if firstNumber < secondNumber then - return true - end - elseif operator == ">" then - if firstNumber > secondNumber then - return true - end - elseif operator == ">=" then - if firstNumber >= secondNumber then - return true - end - elseif operator == "<=" then - if firstNumber <= secondNumber then - return true - end - elseif operator == "==" then - if firstNumber == secondNumber then - return true - end - elseif operator == "~=" then - if firstNumber ~= secondNumber then - return true - end - end - - return false -end - ---- generate_postfield_param_string: convert a table of parameters into an url encoded url parameters string --- @param params (table) the table of all url string parameters to convert --- @return false (boolean) if params variable is not a table --- @return param_string (string) the url encoded parameters string -function ScCommon:generate_postfield_param_string(params) - -- return false because params type is wrong - if (type(params) ~= "table") then - self.sc_logger:error("[sc_common:generate_postfield_param_string]: parameters to convert aren't in a table") - return false - end - - local param_string = "" - - -- concatenate data in params table into a string - for field, value in pairs(params) do - if param_string == "" then - param_string = field .. "=" .. broker.url_encode(value) - else - param_string = param_string .. "&" .. field .. "=" .. broker.url_encode(value) - end - end - - -- return url encoded string - return param_string -end - ---- load_json_file: load a json file --- @param json_file (string) path to the json file --- @return true|false (boolean) if json file is valid or not --- @return content (table) the parsed json -function ScCommon:load_json_file(json_file) - local file = io.open(json_file, "r") - - -- return false if we can't open the file - if not file then - self.sc_logger:error("[sc_common:load_json_file]: couldn't open file " - .. tostring(json_file) .. ". Make sure your file is there and that it is readable by centreon-broker") - return false - end - - -- get content of the file - local file_content = file:read("*a") - io.close(file) - - -- parse it - local content, error = broker.json_decode(file_content) - - -- return false if json couldn't be parsed - if error then - self.sc_logger:error("[sc_common:load_json_file]: could not parse json file " - .. tostring(json_file) .. ". Error is: " .. tostring(error)) - return false - end - - return true, content -end - ---- json_escape: escape json special characters in a string --- @param string (string) the string that must be escaped --- @return string (string) the string with escaped characters -function ScCommon:json_escape(string) - local type = type(string) - - -- check that param is a valid string - if string == nil or type == "table" then - self.sc_logger:error("[sc_common:escape_string]: the input parameter is not valid, it is either nil or a table. Sent value: " .. tostring(string)) - return string - end - - -- nothing to escape in a boolean or number value - if type ~= "string" then - return string - end - - -- escape all characters - string = string.gsub(string, '\\', '\\\\') - string = string.gsub(string, '\t', '\\t') - string = string.gsub(string, '\n', '\\n') - string = string.gsub(string, '\b', '\\b') - string = string.gsub(string, '\r', '\\r') - string = string.gsub(string, '\f', '\\f') - string = string.gsub(string, '/', '\\/') - string = string.gsub(string, '"', '\\"') - - return string -end - ---- xml_escape: escape xml special characters in a string --- @param string (string) the string that must be escaped --- @return string (string) the string with escaped characters -function ScCommon:xml_escape(string) - local type = type(string) - - -- check that param is a valid string - if string == nil or type == "table" then - self.sc_logger:error("[sc_common:escape_string]: the input parameter is not valid, it is either nil or a table. Sent value: " .. tostring(string)) - return string - end - - -- nothing to escape in a boolean or number value - if type ~= "string" then - return string - end - - -- escape all characters - string = string.gsub(string, '<', '<') - string = string.gsub(string, '>', '>') - string = string.gsub(string, '&', '&') - string = string.gsub(string, ''', "'") - string = string.gsub(string, '"', '"') - - return string -end - -return sc_common From 92641dd248a4d20e570b4791c57fae1c4318aa67 Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Tue, 25 Jan 2022 11:01:09 +0100 Subject: [PATCH 11/16] Apply suggestions from code review Co-authored-by: tcharles --- centreon-certified/bsm/bsm-events-apiv2.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index d60ad368..f61c1a65 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -218,14 +218,14 @@ end -------------------------------------------------------------------------------- function EventQueue:build_payload(payload, event) if not payload then - payload = "[send_data]: " .. "" + payload = "" for index, xml_str in pairs(event) do payload = payload .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(xml_str)) .. "" end payload = payload .. "" else - payload = payload .. "[send_data]: " .. "" + payload = payload .. "" for index, xml_str in pairs(event) do payload = payload .. "<" .. tostring(index) .. ">" .. tostring(self.sc_common:xml_escape(xml_str)) .. "" end From 92c7063160be81c8b5f58c0ff76010c8fa8aa9f2 Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Tue, 25 Jan 2022 15:15:49 +0100 Subject: [PATCH 12/16] Create sc_common.lua --- .../sc_common.lua | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 modules/centreon-stream-connectors-lib/sc_common.lua diff --git a/modules/centreon-stream-connectors-lib/sc_common.lua b/modules/centreon-stream-connectors-lib/sc_common.lua new file mode 100644 index 00000000..0d42b7a3 --- /dev/null +++ b/modules/centreon-stream-connectors-lib/sc_common.lua @@ -0,0 +1,250 @@ +#!/usr/bin/lua + +--- +-- Module with common methods for Centreon Stream Connectors +-- @module sc_common +-- @alias sc_common + +local sc_common = {} + +local sc_logger = require("centreon-stream-connectors-lib.sc_logger") + +--- ifnil_or_empty: change a nil or empty variable for a specified value +-- @param var (string|number) the variable that needs to be checked +-- @param alt (string|number|table) the alternate value if "var" is nil or empty +-- @return var or alt (string|number|table) the variable or the alternate value +local function ifnil_or_empty(var, alt) + if var == nil or var == "" then + return alt + else + return var + end +end + +local ScCommon = {} + +function sc_common.new(sc_logger) + local self = {} + + self.sc_logger = sc_logger + if not self.sc_logger then + self.sc_logger = sc_logger.new() + end + + setmetatable(self, { __index = ScCommon }) + + return self +end + +--- ifnil_or_empty: change a nil or empty variable for a specified value +-- @param var (string|number) the variable that needs to be checked +-- @param alt (string|number|table) the alternate value if "var" is nil or empty +-- @return var or alt (string|number|table) the variable or the alternate value +function ScCommon:ifnil_or_empty(var, alt) + return ifnil_or_empty(var, alt) +end + +--- if_wrong_type: change a wrong type variable with a default value +-- @param var (any) the variable that needs to be checked +-- @param type (string) the expected type of the variable +-- @param default (any) the default value for the variable if type is wrong +-- @return var or default (any) the variable if type is good or the default value +function ScCommon:if_wrong_type(var, var_type, default) + if type(var) == var_type then + return var + end + + return default +end + +--- boolean_to_number: convert boolean variable to number +-- @param boolean (boolean) the boolean that will be converted +-- @return (number) a number according to the boolean value +function ScCommon:boolean_to_number(boolean) + return boolean and 1 or 0 +end + +--- number_to_boolean: convert a 0, 1 number to its boolean counterpart +-- @param number (number) the number to convert +-- @return (boolean) true if param is 1, false if param is 0 +function ScCommon:number_to_boolean(number) + if number ~= 0 and number ~= 1 then + self.sc_logger:error("[sc_common:number_to_boolean]: number is not 1 or 0. Returning nil. Parameter value is: " .. tostring(number)) + return nil + end + + if number == 1 then + return true + end + + return false +end + + +--- check_boolean_number_option_syntax: make sure the number is either 1 or 0 +-- @param number (number) the boolean number that must be validated +-- @param default (number) the default value that is going to be return if the default number is not validated +-- @return number (number) a boolean number +function ScCommon:check_boolean_number_option_syntax(number, default) + if number ~= 1 and number ~= 0 then + number = default + end + + return number +end + +--- split: convert a string into a table +-- @param text (string) the string that is going to be splitted into a table +-- @param [opt] separator (string) the separator character that will be used to split the string +-- @return false (boolean) if text param is empty or nil +-- @return table (table) a table of strings +function ScCommon:split (text, separator) + -- return false if text is nil or empty + if text == nil or text == "" then + self.sc_logger:error("[sc_common:split]: could not split text because it is nil or empty") + return false + end + + local hash = {} + + -- set default separator + separator = ifnil_or_empty(separator, ",") + + for value in string.gmatch(text, "([^" .. separator .. "]+)") do + table.insert(hash, value) + end + + return hash +end + +--- compare_numbers: compare two numbers, if comparison is valid, then return true +-- @param firstNumber {number} +-- @param secondNumber {number} +-- @param operator {string} the mathematical operator that is used for the comparison +-- @return {boolean} +function ScCommon:compare_numbers(firstNumber, secondNumber, operator) + if operator ~= "==" and operator ~= "~=" and operator ~= "<" and operator ~= ">" and operator ~= ">=" and operator ~= "<=" then + return nil + end + + if type(firstNumber) ~= "number" or type(secondNumber) ~= "number" then + return nil + end + + if operator == "<" then + if firstNumber < secondNumber then + return true + end + elseif operator == ">" then + if firstNumber > secondNumber then + return true + end + elseif operator == ">=" then + if firstNumber >= secondNumber then + return true + end + elseif operator == "<=" then + if firstNumber <= secondNumber then + return true + end + elseif operator == "==" then + if firstNumber == secondNumber then + return true + end + elseif operator == "~=" then + if firstNumber ~= secondNumber then + return true + end + end + + return false +end + +--- generate_postfield_param_string: convert a table of parameters into an url encoded url parameters string +-- @param params (table) the table of all url string parameters to convert +-- @return false (boolean) if params variable is not a table +-- @return param_string (string) the url encoded parameters string +function ScCommon:generate_postfield_param_string(params) + -- return false because params type is wrong + if (type(params) ~= "table") then + self.sc_logger:error("[sc_common:generate_postfield_param_string]: parameters to convert aren't in a table") + return false + end + + local param_string = "" + + -- concatenate data in params table into a string + for field, value in pairs(params) do + if param_string == "" then + param_string = field .. "=" .. broker.url_encode(value) + else + param_string = param_string .. "&" .. field .. "=" .. broker.url_encode(value) + end + end + + -- return url encoded string + return param_string +end + +--- load_json_file: load a json file +-- @param json_file (string) path to the json file +-- @return true|false (boolean) if json file is valid or not +-- @return content (table) the parsed json +function ScCommon:load_json_file(json_file) + local file = io.open(json_file, "r") + + -- return false if we can't open the file + if not file then + self.sc_logger:error("[sc_common:load_json_file]: couldn't open file " + .. tostring(json_file) .. ". Make sure your file is there and that it is readable by centreon-broker") + return false + end + + -- get content of the file + local file_content = file:read("*a") + io.close(file) + + -- parse it + local content, error = broker.json_decode(file_content) + + -- return false if json couldn't be parsed + if error then + self.sc_logger:error("[sc_common:load_json_file]: could not parse json file " + .. tostring(json_file) .. ". Error is: " .. tostring(error)) + return false + end + + return true, content +end + +--- json_escape: escape json special characters in a string +-- @param string (string) the string that must be escaped +-- @return string (string) the string with escaped characters +function ScCommon:json_escape(string) + local type = type(string) + + -- check that param is a valid string + if string == nil or type == "table" then + self.sc_logger:error("[sc_common:escape_string]: the input parameter is not valid, it is either nil or a table. Sent value: " .. tostring(string)) + return string + end + + -- nothing to escape in a boolean or number value + if type ~= "string" then + return string + end + + -- escape all characters + string = string.gsub(string, '\\', '\\\\') + string = string.gsub(string, '\t', '\\t') + string = string.gsub(string, '\n', '\\n') + string = string.gsub(string, '\b', '\\b') + string = string.gsub(string, '\r', '\\r') + string = string.gsub(string, '\f', '\\f') + string = string.gsub(string, '/', '\\/') + string = string.gsub(string, '"', '\\"') + + return string +end + +return sc_common From d41d32660adc08f4e833d712e5bb177e74d779fa Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Tue, 25 Jan 2022 16:50:39 +0100 Subject: [PATCH 13/16] Last review --- centreon-certified/bsm/bsm-events-apiv2.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index f61c1a65..a9169de2 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -28,7 +28,6 @@ -- Libraries local curl = require "cURL" -require("LuaXML") -- Centreon lua core libraries local sc_common = require("centreon-stream-connectors-lib.sc_common") @@ -103,6 +102,7 @@ function EventQueue.new(params) }, [categories.bam.id] = {} } + self.send_data_method = { [1] = function (payload) return self:send_data(payload) end } @@ -330,6 +330,7 @@ function write(event) if queue.sc_event:is_valid_event() then queue:format_accepted_event() end + --- log why the event has been dropped else queue.sc_logger:debug("dropping event because element is not valid. Event element is: " From ce03a507c10ce8c976adbbd230660a73a35c7cc4 Mon Sep 17 00:00:00 2001 From: psame <44295022+psamecentreon@users.noreply.github.com> Date: Wed, 26 Jan 2022 14:45:17 +0100 Subject: [PATCH 14/16] Update bsm-events-apiv2.lua --- centreon-certified/bsm/bsm-events-apiv2.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index a9169de2..b166bb4e 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -63,7 +63,7 @@ function EventQueue.new(params) } -- set up log configuration - local logfile = params.logfile or "/var/log/centreon-broker/bsm_connector-apiv2.log" + local logfile = params.logfile or "/var/log/centreon-broker/bsm_event-apiv2.log" local log_level = params.log_level or 1 -- initiate mandatory objects From 6f23a773aada9a9bd4414016599580693092dc6b Mon Sep 17 00:00:00 2001 From: tcharles Date: Mon, 9 May 2022 16:48:31 +0200 Subject: [PATCH 15/16] Update centreon-certified/bsm/bsm-events-apiv2.lua --- centreon-certified/bsm/bsm-events-apiv2.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index b166bb4e..ca76ddf1 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -127,9 +127,7 @@ function EventQueue:format_accepted_event() self.sc_event.event.formated_event = {} if self.format_template and template ~= nil and template ~= "" then - for index, value in pairs(template) do - self.sc_event.event.formated_event[index] = self.sc_macros:replace_sc_macro(value, self.sc_event.event) - end + self.sc_event.event.formated_event = self.sc_macros:replace_sc_macro(template, self.sc_event.event, true) else -- can't format event if stream connector is not handling this kind of event and that it is not handled with a template file if not self.format_event[category][element] then From 2cd14e127387f588d4dc6df662dfebab5d75bc67 Mon Sep 17 00:00:00 2001 From: tcharles Date: Mon, 9 May 2022 16:48:52 +0200 Subject: [PATCH 16/16] Update centreon-certified/bsm/bsm-events-apiv2.lua --- centreon-certified/bsm/bsm-events-apiv2.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/centreon-certified/bsm/bsm-events-apiv2.lua b/centreon-certified/bsm/bsm-events-apiv2.lua index ca76ddf1..94f548e4 100644 --- a/centreon-certified/bsm/bsm-events-apiv2.lua +++ b/centreon-certified/bsm/bsm-events-apiv2.lua @@ -38,8 +38,6 @@ local sc_params = require("centreon-stream-connectors-lib.sc_params") local sc_macros = require("centreon-stream-connectors-lib.sc_macros") local sc_flush = require("centreon-stream-connectors-lib.sc_flush") --- workaround https://github.com/centreon/centreon-broker/issues/201 -local previous_event = "" -------------------------------------------------------------------------------- -- EventQueue class