diff --git a/Makefile b/Makefile index cc303f78ea3fcc99d3bb2de32c2e47b00cb8aa83..6009ca204bbdfdf35275f72ac9356cfce7c98070 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ PYTHON_VERSION := $(shell $(PYBIN) --version 2>&1 | awk '{print $$2}' | cut -d ' PKGVER := syssentry-$(VERSION)-py$(PYTHON_VERSION) PKGVEREGG := syssentry-$(VERSION)-py$(PYTHON_VERSION).egg-info -all: lib ebpf hbm_online_repair sentry_msg_monitor +all: lib ebpf hbm_online_repair sentry_msg_monitor bmc_block_io lib:libxalarm log @@ -50,6 +50,9 @@ hbm_online_repair: sentry_msg_monitor: lib cd $(CURSRCDIR)/sentryPlugins/sentry_msg_monitor/ && make +bmc_block_io: lib + cd $(CURSRCDIR)/sentryPlugins/bmc_block_io/ && sh build.sh + install: all dirs isentry dirs: @@ -131,6 +134,11 @@ isentry: install -m 600 $(CURCONFIGDIR)/env/sentry_msg_monitor.env $(ETCDIR)/sysconfig/ install -m 600 $(CURCONFIGDIR)/tasks/sentry_msg_monitor.mod $(ETCDIR)/sysSentry/tasks/ + # bmc_block_io + install -m 550 $(CURSRCDIR)/sentryPlugins/bmc_block_io/output/bmc_block_io $(BINDIR) + install -m 600 $(CURCONFIGDIR)/plugins/bmc_block_io.ini $(ETCDIR)/sysSentry/plugins/ + install -m 600 $(CURCONFIGDIR)/tasks/bmc_block_io.mod $(ETCDIR)/sysSentry/tasks/ + # pysentry_notify install -m 550 src/libsentry/python/pySentryNotify/sentry_notify.py $(PYDIR)/xalarm @@ -161,7 +169,10 @@ hbm_clean: smm_clean: cd $(CURSRCDIR)/sentryPlugins/sentry_msg_monitor && make clean -clean: ebpf_clean hbm_clean smm_clean +bmc_clean: + cd $(CURSRCDIR)/sentryPlugins/bmc_block_io && sh build.sh clean + +clean: ebpf_clean hbm_clean smm_clean bmc_clean rm -rf $(CURLIBDIR)/build rm -rf $(CURSRCDIR)/build rm -rf $(CURSRCDIR)/libsentry/c/log/build @@ -175,6 +186,7 @@ uninstall: rm -rf $(BINDIR)/sentryCollector rm -rf $(BINDIR)/hbm_online_repair rm -rf $(BINDIR)/sentry_msg_monitor + rm -rf $(BINDIR)/bmc_block_io rm -rf $(BINDIR)/ebpf_collector rm -rf $(LIBINSTALLDIR)/libxalarm.so rm -rf $(INCLUDEDIR)/xalarm diff --git a/config/plugins/bmc_block_io.ini b/config/plugins/bmc_block_io.ini new file mode 100644 index 0000000000000000000000000000000000000000..41a39ca2c106a9bb73bfe1a4125e80ab5816971c --- /dev/null +++ b/config/plugins/bmc_block_io.ini @@ -0,0 +1,5 @@ +# log level, accepts debug, info, warning, error or critical +log_level=info + +# polling cycle, unit: seconds, range: [60, 3600] +patrol_second=60 \ No newline at end of file diff --git a/config/tasks/bmc_block_io.mod b/config/tasks/bmc_block_io.mod new file mode 100644 index 0000000000000000000000000000000000000000..9518c5fefd1d05e04730c3a22f23334355afd340 --- /dev/null +++ b/config/tasks/bmc_block_io.mod @@ -0,0 +1,7 @@ +[common] +enabled=yes +task_start=/usr/bin/bmc_block_io +task_stop=kill $pid +type=oneshot +alarm_id=1002 +alarm_clear_time=5 \ No newline at end of file diff --git a/src/sentryPlugins/bmc_block_io/CMakeLists.txt b/src/sentryPlugins/bmc_block_io/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..70ed35421e40d4bbf6e5c7c7cf2329c1a9ecba89 --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required (VERSION 3.12) +project(bmc_block_io) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/output) + +set(CMAKE_SKIP_BUILD_RPATH TRUE) +set(CMAKE_SKIP_INSTALL_RPATH TRUE) + +include_directories( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/../../libs/libxalarm +) +link_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/build/libxalarm +) + +set(SOURCE src/main.cpp + src/logger.cpp + src/common.cpp + src/cbmcblockio.cpp) + +add_executable(bmc_block_io ${SOURCE}) +target_link_libraries(bmc_block_io PRIVATE xalarm pthread json-c) diff --git a/src/sentryPlugins/bmc_block_io/build.sh b/src/sentryPlugins/bmc_block_io/build.sh new file mode 100644 index 0000000000000000000000000000000000000000..8db398c445400b26f2449d64e6f13198495ca652 --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/build.sh @@ -0,0 +1,26 @@ +#!/bin/sh +echo "----------build begin------------" +echo "---------------------------------" + +BUILD_DIR=build + +if [ "$1" = "clean" ]; then + if [ -d "$BUILD_DIR" ]; then + echo "----------clean begin------------" + cd "$BUILD_DIR" && make clean + echo "----------clean end--------------" + else + echo "Build directory does not exist. Nothing to clean." + fi + exit 0 +fi + +[ ! -d $BUILD_DIR ] && mkdir -p $BUILD_DIR +cd $BUILD_DIR + +cmake .. +make || exit "$?" + +echo "------- build end -----------" +echo "-----------------------------" +exit 0 \ No newline at end of file diff --git a/src/sentryPlugins/bmc_block_io/include/cbmcblockio.h b/src/sentryPlugins/bmc_block_io/include/cbmcblockio.h new file mode 100644 index 0000000000000000000000000000000000000000..a579c4a6a6426e053d4468912f5e38d135e6f7cb --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/include/cbmcblockio.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#ifndef _BMC_BLOCK_IO_H_ +#define _BMC_BLOCK_IO_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BMCBlockIoPlu { + +struct ResponseHeader { + uint16_t totalEvents; + uint8_t eventCount; + bool valid; +}; + +struct IPMIEvent { + uint32_t alarmTypeCode; + uint32_t timestamp; + uint8_t severity; + uint8_t subjectType; + uint8_t deviceId; + bool valid; +}; + +class CBMCBlockIo { +public: + CBMCBlockIo(); + ~CBMCBlockIo(); + void Start(); + void Stop(); + void SetPatrolInterval(int seconds); + bool IsRunning(); +private: + void SentryWorker(); + void GetBMCIp(); + void ReportAlarm(const IPMIEvent& event); + void ReportResult(int resultLevel, const std::string& msg); + int QueryEvents(); + std::string BuildIPMICommand(uint16_t startIndex); + std::vector ExecuteIPMICommand(const std::string& cmd); + ResponseHeader ParseResponseHeader(const std::vector& hexBytes); + IPMIEvent ParseSingleEvent(const std::vector& hexBytes, size_t startPos); + void ProcessEvents(const std::vector& hexBytes, uint8_t eventCount); + +private: + std::atomic m_running; + std::thread m_worker; + std::mutex m_mutex; + std::condition_variable m_cv; + std::string m_bmcIp; + std::set m_lastDeviceIds; + std::set m_currentDeviceIds; + int m_patrolSeconds; + int m_alarmId; +}; +} +#endif + diff --git a/src/sentryPlugins/bmc_block_io/include/common.h b/src/sentryPlugins/bmc_block_io/include/common.h new file mode 100644 index 0000000000000000000000000000000000000000..20fa8b1d66336c94e7b8756bbbd412b0dc28a8c5 --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/include/common.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#ifndef _BMCPLU_COMMON_H_ +#define _BMCPLU_COMMON_H_ + +#include +#include +#include +#include +#include +#include "configure.h" +#include "logger.h" + +#define BMCPLU_FAILED (-1) +#define BMCPLU_SUCCESS (0) + +struct PluConfig { + BMCBlockIoPlu::Logger::Level logLevel; + int patrolSeconds; +}; + +struct ConfigItem { + bool required; + bool found; + std::function processor; +}; + +namespace BMCBlockIoPlu { + +std::string Trim(const std::string& str); +bool IsValidNumber(const std::string& str, int& num); +int ParseConfig(const std::string& path, PluConfig& config); +std::map> parseModConfig(const std::string& path); +std::string ExtractFileName(const std::string& path); +int ExecCommand(const std::string& cmd, std::vector& result); +std::string ByteToHex(uint8_t byte); +std::vector SplitString(const std::string& str, const std::string& split); +} + +#endif diff --git a/src/sentryPlugins/bmc_block_io/include/configure.h b/src/sentryPlugins/bmc_block_io/include/configure.h new file mode 100644 index 0000000000000000000000000000000000000000..380b6b89d1f59dba44f505787d8ae6d9da949056 --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/include/configure.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#ifndef _BMCPLU_CONFIGURE_H_ +#define _BMCPLU_CONFIGURE_H_ + +#include + +namespace BMCBlockIoPlu { + +const std::string BMCPLU_CONFIG_PATH = "/etc/sysSentry/plugins/bmc_block_io.ini"; +const std::string BMCPLU_LOG_PATH = "/var/log/sysSentry/bmc_block_io.log"; +const std::string BMCPLU_MOD_CONFIG = "/etc/sysSentry/tasks/bmc_block_io.mod"; +const int BMCPLU_PATROL_MIN = 60; +const int BMCPLU_PATROL_MAX = 3600; +const int BMCPLU_PATROL_DEFAULT = 300; +const int BMCPLU_CONFIG_CHECK_CYCLE = 10; // seconds +const int BMCPLU_DEFAULT_SLEEP_CYCLE = 3; // seconds +const int BMCPLU_LOGFILE_CHECK_CYCLE = 30; // second +const int BMCPLU_DEFAULT_ALARM_ID = 1002; +} +#endif diff --git a/src/sentryPlugins/bmc_block_io/include/logger.h b/src/sentryPlugins/bmc_block_io/include/logger.h new file mode 100644 index 0000000000000000000000000000000000000000..062799393bfdbf02af539347cbe75bdcedcef34c --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/include/logger.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#ifndef __BMCPLU_LOGGER_H__ +#define __BMCPLU_LOGGER_H__ + +#include +#include +#include +#include +#include +#include + +namespace BMCBlockIoPlu { + +class Logger { +public: + enum class Level { + Debug, + Info, + Warning, + Error, + Critical + }; + static Logger& GetInstance(); + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + bool Initialize(const std::string& logPath, Level level = Level::Info); + void SetLevel(Level level); + Level GetLevel() const; + void WriteLog(Level level, const char* file, int line, const std::string& message); + std::string LevelToString(Level level) const; +private: + Logger() = default; + void OpenLogFile(); + void CheckFileState(); + void ReopenLogFile(); + std::string GetTimeStamp() const; + std::string Format(Level level, const char* file, int line, const std::string& message) const; + +private: + std::ofstream m_logFile; + std::string m_logPath; + Level m_level = Level::Info; + mutable std::mutex m_writeMutex; + std::time_t m_checkTime = 0; + ino_t m_inode = 0; + dev_t m_device = 0; + off_t m_fileSize = 0; + bool m_fileOpen = false; +}; + +class LogStream { +public: + LogStream(Logger::Level level, const char* file, int line) + : m_level(level), m_file(file), m_line(line) + {} + ~LogStream() + { + Logger::GetInstance().WriteLog(m_level, m_file, m_line, m_stream.str()); + } + template + LogStream& operator<<(const T& value) + { + m_stream << value; + return *this; + } + LogStream& operator<<(std::ostream& (*manip)(std::ostream&)) // std::endl, std::flush... + { + m_stream << manip; + return *this; + } + +private: + Logger::Level m_level; + const char* m_file; + int m_line; + std::ostringstream m_stream; +}; + +#define BMC_LOG_DEBUG BMCBlockIoPlu::LogStream(BMCBlockIoPlu::Logger::Level::Debug, __FILE__, __LINE__) +#define BMC_LOG_INFO BMCBlockIoPlu::LogStream(BMCBlockIoPlu::Logger::Level::Info, __FILE__, __LINE__) +#define BMC_LOG_WARNING BMCBlockIoPlu::LogStream(BMCBlockIoPlu::Logger::Level::Warning, __FILE__, __LINE__) +#define BMC_LOG_ERROR BMCBlockIoPlu::LogStream(BMCBlockIoPlu::Logger::Level::Error, __FILE__, __LINE__) +#define BMC_LOG_CRITICAL BMCBlockIoPlu::LogStream(BMCBlockIoPlu::Logger::Level::Critical, __FILE__, __LINE__) +} +#endif \ No newline at end of file diff --git a/src/sentryPlugins/bmc_block_io/src/cbmcblockio.cpp b/src/sentryPlugins/bmc_block_io/src/cbmcblockio.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e2867d95aa79ed73acc8d538e1638691a5988045 --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/src/cbmcblockio.cpp @@ -0,0 +1,432 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#include "cbmcblockio.h" +#include +#include +#include +#include +#include +#include +#include +extern "C" { +#include "register_xalarm.h" +} +#include "common.h" +#include "configure.h" +#include "logger.h" + +namespace BMCBlockIoPlu { + +const int RESP_HEADER_SIZE = 7; +const int EVENT_SIZE = 15; +const uint32_t ALARM_OCCUR_CODE = 0x02000039; +const uint32_t ALARM_CLEAR_CODE = 0x0200003A; +const std::string BMC_TASK_NAME = "bmc_block_io"; +const std::string GET_BMCIP_CMD = "ipmitool lan print"; +const std::string IPMI_KEY_IP_ADDR = "IP Address"; +const std::string MSG_BMCIP_EMPTY = "ipmitool get bmc ip failed."; +const std::string MSG_BMC_QUERY_FAIL = "ipmitool query failed."; +const std::string MSG_EXIT_SUCCESS = "receive exit signal, task completed."; +const std::string JSON_KEY_MSG = "msg"; +const std::string JSON_KEY_ALARM_SOURCE = "alarm_source"; +const std::string JSON_KEY_DRIVER_NAME = "driver_name"; +const std::string JSON_KEY_IO_TYPE = "io_type"; +const std::string JSON_KEY_REASON = "reason"; +const std::string JSON_KEY_BLOCK_STACK = "block_stack"; +const std::string JSON_KEY_DETAILS = "details"; +const std::string MOD_SECTION_COMMON = "common"; +const std::string MOD_COMMON_ALARM_ID = "alarm_id"; + +CBMCBlockIo::CBMCBlockIo() : + m_running(false), + m_patrolSeconds(BMCPLU_PATROL_DEFAULT) +{ + std::map> modConfig = parseModConfig(BMCPLU_MOD_CONFIG); + if (modConfig.find(MOD_SECTION_COMMON) != modConfig.end()) { + auto commonSection = modConfig[MOD_SECTION_COMMON]; + if (commonSection.find(MOD_COMMON_ALARM_ID) != commonSection.end()) { + int alarmId = 0; + if (IsValidNumber(commonSection[MOD_COMMON_ALARM_ID], alarmId) && alarmId > 0) { + m_alarmId = alarmId; + } else { + m_alarmId = BMCPLU_DEFAULT_ALARM_ID; + BMC_LOG_WARNING << "Invalid alarm_id in mod config, use default alarm id: " << BMCPLU_DEFAULT_ALARM_ID; + } + } + } +} + +CBMCBlockIo::~CBMCBlockIo() +{ +} + +void CBMCBlockIo::Start() +{ + if (m_running) { + return; + } + + GetBMCIp(); + if (m_bmcIp.empty()) { + BMC_LOG_ERROR << "BMC Ip is empty."; + ReportResult(RESULT_LEVEL_FAIL, MSG_BMCIP_EMPTY); + return; + } + m_running = true; + m_worker = std::thread(&CBMCBlockIo::SentryWorker, this); + BMC_LOG_INFO << "BMC block io Start."; +} + +void CBMCBlockIo::Stop() +{ + { + std::lock_guard lock(m_mutex); + m_running = false; + } + m_cv.notify_all(); + + if (m_worker.joinable()) { + m_worker.join(); + } + BMC_LOG_INFO <<"BMC block io Stop."; +} + +void CBMCBlockIo::SetPatrolInterval(int seconds) +{ + m_patrolSeconds = seconds; +} + +bool CBMCBlockIo::IsRunning() +{ + return m_running; +} + +void CBMCBlockIo::SentryWorker() +{ + int ret = BMCPLU_SUCCESS; + while (m_running) { + std::unique_lock lock(m_mutex); + m_cv.wait_for(lock, std::chrono::seconds(m_patrolSeconds), [this] { + return !m_running; + }); + + if (!m_running) { + break; + } + ret = QueryEvents(); + if (ret != BMCPLU_SUCCESS) { + break; + } + } + + if (ret == BMCPLU_SUCCESS) { + ReportResult(RESULT_LEVEL_PASS, MSG_EXIT_SUCCESS); + } else { + ReportResult(RESULT_LEVEL_FAIL, MSG_BMC_QUERY_FAIL); + } + m_running = false; + BMC_LOG_INFO << "BMC block io SentryWorker exit."; + return; +} + +void CBMCBlockIo::GetBMCIp() +{ + std::vector result; + if (ExecCommand(GET_BMCIP_CMD, result)) { + return; + } + for (const auto& iter: result) { + if (iter.find(IPMI_KEY_IP_ADDR) != std::string::npos) { + size_t eq_pos = iter.find(':'); + if (eq_pos != std::string::npos) { + std::string key = Trim(iter.substr(0, eq_pos)); + std::string value = Trim(iter.substr(eq_pos + 1)); + if (key == IPMI_KEY_IP_ADDR) { + m_bmcIp = value; + return; + } + } + } + } + return; +} + +/***** ipml protocol *****/ +/* +请求 字节顺序 含义 + 1-3 厂商id 默认0xDB 0x07 0x0 + 4 子命令 默认0x40 + 5 请求类型 默认0x00 + 6-7 需要查询的事件起始编号,某些情况下查询到的事件可能有多条, + 单次响应无法全部返回,因此需要修改该值分页查询 + 8 事件严重级别 位图形式,bit0-normal,bit1-minor,bit2-major,bit3-critical,慢盘事件只支持normal + 9 主体类型 硬盘类型0x02 +响应 字节顺序 含义 + 1 completion code 调用成功时该字节不会显示在终端上 + 2-4 厂商ID,对应请求中内容 + 5-6 事件总数量 + 7 本次返回中包含的事件数量 + 8 占位字节,默认0 + 9-12 告警类型码,0x0200039为告警产生,0x0200003A为告警消除 + 13-16 事件发生的linux时间戳 + 17 事件严重级别,0-normal,1-minor,2-major,3-critical + 18 主体类型,对应请求中内容 + 19 设备序号 带外编号 + 20-23 占位字节,默认0 + N+1-N+15重复上面9-23中的内容,表示下一个事件 +厂商ID固定,其他所有多字节对象均为小端序, eg: +ipmitool raw 0x30 0x94 0xDB 0x07 0x00 0x40 0x00 0x00 0x00 0x01 0x02 +db 07 00 03 00 03 00 39 00 00 02 2f ab 91 68 00 02 04 00 00 00 00 +39 00 00 02 2e ab 91 68 00 02 02 00 00 00 00 39 00 00 02 2e ab 91 +68 00 02 01 00 00 00 00 + */ +int CBMCBlockIo::QueryEvents() +{ + uint16_t currentIndex = 0; + int ret = BMCPLU_SUCCESS; + m_currentDeviceIds.clear(); + + while (true) { + std::string cmd = BuildIPMICommand(currentIndex); + std::vector hexBytes = ExecuteIPMICommand(cmd); + if (hexBytes.empty()) { + break; + } + + ResponseHeader header = ParseResponseHeader(hexBytes); + if (!header.valid) { + ret = BMCPLU_FAILED; + break; + } + + BMC_LOG_INFO << "Total events: " << header.totalEvents + << ", returned: " << static_cast(header.eventCount) + << ", current index: " << currentIndex; + if (header.eventCount == 0) { + break; + } + + size_t expectedSize = RESP_HEADER_SIZE + header.eventCount * EVENT_SIZE; + if (hexBytes.size() != expectedSize) { + BMC_LOG_ERROR << "Response size invalid. Expected: " << expectedSize + << ", Actual: " << hexBytes.size(); + ret = BMCPLU_FAILED; + break; + } + + ProcessEvents(hexBytes, header.eventCount); + currentIndex += header.eventCount; + + if (currentIndex >= header.totalEvents) { + break; + } + } + + if (ret == BMCPLU_SUCCESS) { + for (const auto& id : m_lastDeviceIds) { + if (m_currentDeviceIds.find(id) == m_currentDeviceIds.end()) { + uint32_t timeNow = static_cast(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); + IPMIEvent clearEvent = {ALARM_CLEAR_CODE, timeNow, 0, 0x02, id, true}; + ReportAlarm(clearEvent); + } + } + m_lastDeviceIds = m_currentDeviceIds; + } + return ret; +} + +std::string CBMCBlockIo::BuildIPMICommand(uint16_t startIndex) +{ + uint8_t indexHigh = static_cast((startIndex >> 8) & 0xff); + uint8_t indexLow = static_cast(startIndex & 0xff); + std::ostringstream cmdStream; + cmdStream << "ipmitool raw 0x30 0x94 0xDB 0x07 0x00 0x40 0x00" + << " " << ByteToHex(indexLow) + << " " << ByteToHex(indexHigh) + << " 0x01 0x02"; + return cmdStream.str(); +} + +std::vector CBMCBlockIo::ExecuteIPMICommand(const std::string& cmd) +{ + BMC_LOG_DEBUG << "IPMI event query command: " << cmd; + + std::vector cmdOut; + if (ExecCommand(cmd, cmdOut)) { + BMC_LOG_ERROR << "IPMI command execute failed."; + return {}; + } + + std::ostringstream responseStream; + for (size_t i = 0; i < cmdOut.size(); ++i) { + std::string line = cmdOut[i]; + BMC_LOG_DEBUG << "Execute IPMI event response: " << line; + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); + if (i > 0 && !line.empty()) { + responseStream << ' '; + } + responseStream << line; + } + return SplitString(responseStream.str(), " "); +} + +ResponseHeader CBMCBlockIo::ParseResponseHeader(const std::vector& hexBytes) +{ + ResponseHeader header = {0, 0, false}; + + if (hexBytes.size() < RESP_HEADER_SIZE) { + BMC_LOG_ERROR << "Invalid response length: " << hexBytes.size(); + return header; + } + + if (hexBytes[0] != "db" || hexBytes[1] != "07" || hexBytes[2] != "00") { + BMC_LOG_ERROR << "Unexpected manufacturer ID: " + << hexBytes[0] << " " << hexBytes[1] << " " << hexBytes[2]; + return header; + } + + char* endPtr = nullptr; + unsigned long totalLow = std::strtoul(hexBytes[3].c_str(), &endPtr, 16); + if (endPtr == hexBytes[3].c_str() || *endPtr != '\0' || totalLow > 0xff) { + BMC_LOG_ERROR << "Invalid totalLow byte: " << hexBytes[3]; + return header; + } + + unsigned long totalHigh = std::strtoul(hexBytes[4].c_str(), &endPtr, 16); + if (endPtr == hexBytes[4].c_str() || *endPtr != '\0' || totalHigh > 0xff) { + BMC_LOG_ERROR << "Invalid totalHigh byte: " << hexBytes[4]; + return header; + } + + header.totalEvents = static_cast(totalLow) | (static_cast(totalHigh) << 8); + unsigned long count = std::strtoul(hexBytes[5].c_str(), &endPtr, 16); + if (endPtr == hexBytes[5].c_str() || *endPtr != '\0' || count > 0xff) { + BMC_LOG_ERROR << "Invalid event count byte: " << hexBytes[5]; + return header; + } + + header.eventCount = static_cast(count); + header.valid = true; + return header; +} + +IPMIEvent CBMCBlockIo::ParseSingleEvent(const std::vector& hexBytes, size_t startPos) +{ + IPMIEvent event = {0, 0, 0, 0, 0, false}; + char* endPtr = nullptr; + + for (int i = 0; i < 4; ++i) { + unsigned long byte = std::strtoul(hexBytes[startPos + i].c_str(), &endPtr, 16); + if (endPtr == hexBytes[startPos + i].c_str() || *endPtr != '\0' || byte > 0xff) { + BMC_LOG_ERROR << "Invalid alarm type byte at pos " << startPos + i + << ": " << hexBytes[startPos + i]; + return event; + } + event.alarmTypeCode |= (static_cast(byte) << (i * 8)); + } + + for (int i = 0; i < 4; ++i) { + unsigned long byte = std::strtoul(hexBytes[startPos + 4 + i].c_str(), &endPtr, 16); + if (endPtr == hexBytes[startPos + 4 + i].c_str() || *endPtr != '\0' || byte > 0xff) { + BMC_LOG_ERROR << "Invalid timestamp byte at pos " << startPos + 4 + i + << ": " << hexBytes[startPos + 4 + i]; + return event; + } + event.timestamp |= (static_cast(byte) << (i * 8)); + } + + unsigned long severity = std::strtoul(hexBytes[startPos + 8].c_str(), &endPtr, 16); + if (endPtr == hexBytes[startPos + 8].c_str() || *endPtr != '\0' || severity > 0xff) { + BMC_LOG_ERROR << "Invalid severity byte: " << hexBytes[startPos + 8]; + return event; + } + event.severity = static_cast(severity); + + unsigned long subjectType = std::strtoul(hexBytes[startPos + 9].c_str(), &endPtr, 16); + if (endPtr == hexBytes[startPos + 9].c_str() || *endPtr != '\0' || subjectType > 0xff) { + BMC_LOG_ERROR << "Invalid subject type byte: " << hexBytes[startPos + 9]; + return event; + } + event.subjectType = static_cast(subjectType); + + unsigned long deviceId = std::strtoul(hexBytes[startPos + 10].c_str(), &endPtr, 16); + if (endPtr == hexBytes[startPos + 10].c_str() || *endPtr != '\0' || deviceId > 0xff) { + BMC_LOG_ERROR << "Invalid device ID byte: " << hexBytes[startPos + 10]; + return event; + } + event.deviceId = static_cast(deviceId); + + event.valid = true; + return event; +} + +void CBMCBlockIo::ProcessEvents(const std::vector& hexBytes, uint8_t eventCount) +{ + for (int i = 0; i < eventCount; ++i) { + size_t startPos = RESP_HEADER_SIZE + i * EVENT_SIZE; + + IPMIEvent event = ParseSingleEvent(hexBytes, startPos); + if (!event.valid) { + continue; + } + + ReportAlarm(event); + } + return; +} + +void CBMCBlockIo::ReportAlarm(const IPMIEvent& event) +{ + uint8_t ucAlarmLevel = MINOR_ALM; + uint8_t ucAlarmType = 0; + if (event.alarmTypeCode == ALARM_OCCUR_CODE) { + ucAlarmType = ALARM_TYPE_OCCUR; + m_currentDeviceIds.insert(event.deviceId); + } else if (event.alarmTypeCode == ALARM_CLEAR_CODE) { + ucAlarmType = ALARM_TYPE_RECOVER; + } else { + BMC_LOG_DEBUG << "Skipping unknown alarm type: 0x" + << std::hex << event.alarmTypeCode; + return; + } + + BMC_LOG_INFO << "Report alarm, type: " << static_cast(ucAlarmType); + BMC_LOG_INFO << "level: " << static_cast(ucAlarmLevel); + BMC_LOG_INFO << "deviceId: " << static_cast(event.deviceId); + BMC_LOG_INFO << "timestamp: " << event.timestamp; + json_object* jObject = json_object_new_object(); + json_object_object_add(jObject, JSON_KEY_ALARM_SOURCE.c_str(), json_object_new_string(BMC_TASK_NAME.c_str())); + json_object_object_add(jObject, JSON_KEY_DRIVER_NAME.c_str(), json_object_new_string(std::to_string(event.deviceId).c_str())); + json_object_object_add(jObject, JSON_KEY_IO_TYPE.c_str(), json_object_new_string("read,write")); + json_object_object_add(jObject, JSON_KEY_REASON.c_str(), json_object_new_string("driver slow")); + json_object_object_add(jObject, JSON_KEY_BLOCK_STACK.c_str(), json_object_new_string("rq_driver")); + json_object_object_add(jObject, JSON_KEY_DETAILS.c_str(), json_object_new_string("{}}")); + const char *jData = json_object_to_json_string(jObject); + int ret = xalarm_Report(m_alarmId, ucAlarmLevel, ucAlarmType, const_cast(jData)); + if (ret != RETURE_CODE_SUCCESS) { + BMC_LOG_ERROR << "Failed to xalarm_Report, ret: " << ret; + } + json_object_put(jObject); + return; +} + +void CBMCBlockIo::ReportResult(int resultLevel, const std::string& msg) +{ + RESULT_LEVEL level = static_cast(resultLevel); + json_object* jObject = json_object_new_object(); + json_object_object_add(jObject, JSON_KEY_MSG.c_str(), json_object_new_string(msg.c_str())); + const char *jData = json_object_to_json_string(jObject); + int ret = report_result(BMC_TASK_NAME.c_str(), level, const_cast(jData)); + if (ret != RETURE_CODE_SUCCESS) { + BMC_LOG_ERROR << "Failed to report_result, ret: " << ret; + } + json_object_put(jObject); + return; +} +}; diff --git a/src/sentryPlugins/bmc_block_io/src/common.cpp b/src/sentryPlugins/bmc_block_io/src/common.cpp new file mode 100644 index 0000000000000000000000000000000000000000..94a9b92ac087e92bb7499419e0e1e97845ed420b --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/src/common.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BMCBlockIoPlu { + +std::string Trim(const std::string& str) +{ + size_t first = str.find_first_not_of(" \t\n\r\v\f"); + if (std::string::npos == first) { + return ""; + } + size_t last = str.find_last_not_of(" \t\n\r\v\f"); + return str.substr(first, (last - first + 1)); +} + +bool IsValidNumber(const std::string& str, int& num) +{ + if (str.empty()) { + return false; + } + for (const auto& iter : str) { + if (!std::isdigit(iter)) { + return false; + } + } + std::istringstream iss(str); + if (!(iss >> num)) { + return false; + } + return true; +} + +int ParseConfig(const std::string& path, PluConfig& config) +{ + std::ifstream file(path); + if (!file.is_open()) { + BMC_LOG_ERROR << "Failed to open config file: " << path; + return BMCPLU_FAILED; + } + + std::unordered_map configMap; + configMap["log_level"] = {true, false, [&](const std::string& value) { + if (value == "debug") { + config.logLevel = Logger::Level::Debug; + } else if (value == "info") { + config.logLevel = Logger::Level::Info; + } else if (value == "warning") { + config.logLevel = Logger::Level::Warning; + } else if (value == "error") { + config.logLevel = Logger::Level::Error; + } else if (value == "critical") { + config.logLevel = Logger::Level::Critical; + } else { + BMC_LOG_ERROR << "Invalid log_level value."; + return false; + } + return true; + }}; + + configMap["patrol_second"] = {true, false, [&](const std::string& value) { + int num = 0; + if (!IsValidNumber(value, num) || !(num >= BMCPLU_PATROL_MIN && num <= BMCPLU_PATROL_MAX)) { + BMC_LOG_ERROR << "Invalid patrol_second value."; + return false; + } + config.patrolSeconds = num; + return true; + }}; + + std::string line; + while (std::getline(file, line)) { + line = Trim(line); + if (line.empty() || line[0] == '#') { + continue; + } + + size_t eqPos = line.find('='); + if (eqPos == std::string::npos || eqPos == 0) { + BMC_LOG_ERROR << "Config file format invalid."; + return BMCPLU_FAILED; + } + + std::string key = Trim(line.substr(0, eqPos)); + std::string value = Trim(line.substr(eqPos + 1)); + if (value.empty()) { + BMC_LOG_ERROR << "Config key: " << key << " cannot empty."; + return BMCPLU_FAILED; + } + + auto iter = configMap.find(key); + if (iter == configMap.end()) { + BMC_LOG_ERROR << "Config error, unknown key : " << key; + return BMCPLU_FAILED; + } + + if (!iter->second.processor(value)) { + return BMCPLU_FAILED; + } + iter->second.found = true; + } + + for (const auto& iter : configMap) { + if (iter.second.required && !iter.second.found) { + BMC_LOG_ERROR << "Config error, missing required key : " << iter.first; + return BMCPLU_FAILED; + } + } + return BMCPLU_SUCCESS; +} + +std::map> parseModConfig(const std::string& path) +{ + std::map> result; + + std::ifstream file(path); + if (!file.is_open()) { + BMC_LOG_ERROR << "Failed to open mod file: " << path; + return result; + } + + std::string line; + std::string currentSection; + while (std::getline(file, line)) { + line = Trim(line); + if (line.empty() || line[0] == '#') { + continue; + } + + // check for section + if (line[0] == '[' && line[line.length() - 1] == ']') { + currentSection = Trim(line.substr(1, line.length() - 2)); + if (!currentSection.empty()) { + result[currentSection] = std::map(); + } + continue; + } + + // check for key=value + size_t eqPos = line.find('='); + if (eqPos != std::string::npos && !currentSection.empty()) { + std::string key = Trim(line.substr(0, eqPos)); + std::string value = Trim(line.substr(eqPos + 1)); + if (!key.empty()) { + result[currentSection][key] = value; + } + } + } + + return result; +} + +std::string ExtractFileName(const std::string& path) +{ + size_t lastSlashPos = path.find_last_of('/'); + if (lastSlashPos == std::string::npos) { + return path; + } else { + return path.substr(lastSlashPos + 1); + } +} + +int ExecCommand(const std::string& cmd, std::vector& result) +{ + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) { + BMC_LOG_ERROR << "Cmd: " << cmd << ", popen failed."; + return BMCPLU_FAILED; + } + + char buffer[512]; + result.clear(); + while (fgets(buffer, sizeof(buffer), pipe)) { + result.push_back(buffer); + } + + int status = pclose(pipe); + if (status == -1) { + BMC_LOG_ERROR << "Cmd: " << cmd << ", pclose failed."; + return BMCPLU_FAILED; + } else { + int exitCode = WEXITSTATUS(status); + if (exitCode != 0) { + BMC_LOG_ERROR << "Cmd: " << cmd << ", exit failed, err code: " << exitCode; + return BMCPLU_FAILED; + } + } + return BMCPLU_SUCCESS; +} + +std::string ByteToHex(uint8_t byte) +{ + std::ostringstream oss; + const int hexLen = 2; + oss << std::hex << std::setfill('0') << std::setw(hexLen) << static_cast(byte); + return "0x" + oss.str(); +} + +std::vector SplitString(const std::string& str, const std::string& split) +{ + std::vector result; + if (split.empty()) { + result.push_back(str); + return result; + } + + size_t pos = 0; + while (true) { + size_t split_pos = str.find(split, pos); + std::string substring = str.substr(pos, split_pos - pos); + + if (!substring.empty()) { + result.push_back(substring); + } + + if (split_pos == std::string::npos) { + break; + } + pos = split_pos + split.size(); + } + return result; +} +} diff --git a/src/sentryPlugins/bmc_block_io/src/logger.cpp b/src/sentryPlugins/bmc_block_io/src/logger.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f80e46c5ac231ff3c0f53c7d8e9335886804a3f8 --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/src/logger.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#include "logger.h" +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +namespace BMCBlockIoPlu { + +Logger& Logger::GetInstance() +{ + static Logger instance; + return instance; +} + +bool Logger::Initialize(const std::string& logPath, Level level) +{ + m_logPath = logPath; + m_level = level; + OpenLogFile(); + return m_fileOpen; +} + +void Logger::SetLevel(Level level) +{ + m_level = level; +} + +Logger::Level Logger::GetLevel() const +{ + return m_level; +} + +void Logger::OpenLogFile() +{ + m_logFile.open(m_logPath, std::ios::out | std::ios::app); + if (!m_logFile.is_open()) { + std::cerr << "Failed to open log file: " << m_logPath + << ", error: " << strerror(errno) << std::endl; + m_fileOpen = false; + return; + } + + struct stat fileStat; + if (stat(m_logPath.c_str(), &fileStat) == 0) { + m_inode = fileStat.st_ino; + m_device = fileStat.st_dev; + m_fileSize = fileStat.st_size; + } + m_checkTime = std::time(nullptr); + + m_fileOpen = true; + return; +} + +void Logger::CheckFileState() +{ + std::time_t timeNow = std::time(nullptr); + if (timeNow - m_checkTime < BMCPLU_LOGFILE_CHECK_CYCLE) { + return; + } + + struct stat fileStat; + if (stat(m_logPath.c_str(), &fileStat) != 0) { + if (errno == ENOENT) { // file deleted + std::lock_guard lock(m_writeMutex); + ReopenLogFile(); + } + std::cerr << "Failed to get file state: " << m_logPath + << ", error: " << strerror(errno) << std::endl; + return; + } + + if (fileStat.st_ino != m_inode || fileStat.st_dev != m_device || fileStat.st_size < m_fileSize) { + ReopenLogFile(); + } else { + m_fileSize = fileStat.st_size; + } + + m_checkTime = timeNow; +} + +void Logger::ReopenLogFile() +{ + if (m_logFile.is_open()) { + m_logFile.close(); + } + OpenLogFile(); + return; +} + +void Logger::WriteLog(Level level, const char* file, int line, const std::string& message) +{ + if (level < GetLevel() || message.empty()) { + return; + } + + CheckFileState(); + std::lock_guard lock(m_writeMutex); + if (m_fileOpen && m_logFile.good()) { + m_logFile << Format(level, file, line, message) << std::endl; + m_logFile.flush(); + } else { + std::cerr << Format(level, file, line, message) << std::endl; + } +} + +std::string Logger::LevelToString(Level level) const +{ + switch (level) { + case Level::Debug: return std::string("DEBUG"); + case Level::Info: return std::string("INFO"); + case Level::Warning: return std::string("WARNING"); + case Level::Error: return std::string("ERROR"); + case Level::Critical: return std::string("CRITICAL"); + default: return std::string("UNKNOWN"); + } + return std::string("UNKNOWN"); +} + +std::string Logger::GetTimeStamp() const +{ + auto now = std::chrono::system_clock::now(); + auto nowTimer = std::chrono::system_clock::to_time_t(now); + auto milliseconds = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + + struct tm nowTm; + localtime_r(&nowTimer, &nowTm); + + std::ostringstream oss; + const int millisecLen = 3; + oss << std::put_time(&nowTm, "%Y-%m-%d %H:%M:%S"); + oss << '.' << std::setfill('0') << std::setw(millisecLen) << milliseconds.count(); + + return oss.str(); +} + +std::string Logger::Format(Level level, const char* file, int line, const std::string & message) const +{ + std::ostringstream oss; + oss << GetTimeStamp() << " - " << LevelToString(level) << " - [" + << ExtractFileName(file) << ":" << line << "]" << " - " << message; + return oss.str(); +} +} diff --git a/src/sentryPlugins/bmc_block_io/src/main.cpp b/src/sentryPlugins/bmc_block_io/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..136bf13658fab32529f95f87790431376eaa6402 --- /dev/null +++ b/src/sentryPlugins/bmc_block_io/src/main.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * bmc_block_io is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * Author: hewanhan@h-partners.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cbmcblockio.h" +#include "configure.h" +#include "logger.h" +#include "common.h" + +std::atomic g_exit(false); +std::mutex g_mutex; +std::condition_variable g_cv; + +void HandleSignal(int sig) +{ + if (sig == SIGTERM || sig == SIGINT) { + g_exit = true; + BMC_LOG_INFO << "Receive signal SIGTERM or SIGINT, exit."; + g_cv.notify_all(); + } + return; +} + +void SetSignalHandler() +{ + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGTERM); // block SIGTERM + sigaddset(&sa.sa_mask, SIGINT); // block SIGINT + sa.sa_handler = HandleSignal; + sa.sa_flags = SA_RESTART; + + if (sigaction(SIGTERM, &sa, nullptr) == -1) { + BMC_LOG_ERROR << "Failed to setup signal with SIGTERM:" << strerror(errno); + } + + if (sigaction(SIGINT, &sa, nullptr) == -1) { + BMC_LOG_ERROR << "Failed to setup signal with SIGINT:" << strerror(errno); + } +} + +int main(int argc, char* argv[]) +{ + if (!BMCBlockIoPlu::Logger::GetInstance().Initialize(BMCBlockIoPlu::BMCPLU_LOG_PATH)) { + std::cerr << "Failed to initialize logger." << std::endl; + } + SetSignalHandler(); + + BMCBlockIoPlu::CBMCBlockIo blockIo; + PluConfig config; + if (BMCBlockIoPlu::ParseConfig(BMCBlockIoPlu::BMCPLU_CONFIG_PATH, config)) { + BMC_LOG_ERROR << "Parse config failed, use default configuration."; + } else { + BMCBlockIoPlu::Logger::GetInstance().SetLevel(config.logLevel); + blockIo.SetPatrolInterval(config.patrolSeconds); + } + + std::thread configMonitor([&] { + time_t lastModTime = 0; + struct stat st; + if (stat(BMCBlockIoPlu::BMCPLU_CONFIG_PATH.c_str(), &st) == 0) { + lastModTime = st.st_mtime; + } + + while (!g_exit) { + { + std::unique_lock lock(g_mutex); + g_cv.wait_for(lock, std::chrono::seconds(BMCBlockIoPlu::BMCPLU_CONFIG_CHECK_CYCLE), + [] { return g_exit.load(); }); + if (g_exit) { + break; + } + } + + struct stat st_; + if (stat(BMCBlockIoPlu::BMCPLU_CONFIG_PATH.c_str(), &st_) != 0) { + continue; + } + if (st_.st_mtime != lastModTime) { + lastModTime = st_.st_mtime; + PluConfig newConfig; + if (BMCBlockIoPlu::ParseConfig(BMCBlockIoPlu::BMCPLU_CONFIG_PATH, newConfig) == BMCPLU_SUCCESS) { + if (newConfig.logLevel != config.logLevel) { + config.logLevel = newConfig.logLevel; + BMC_LOG_INFO << "Log level update to " + << BMCBlockIoPlu::Logger::GetInstance().LevelToString(config.logLevel); + BMCBlockIoPlu::Logger::GetInstance().SetLevel(config.logLevel); + } + if (newConfig.patrolSeconds != config.patrolSeconds) { + config.patrolSeconds = newConfig.patrolSeconds; + BMC_LOG_INFO << "Patrol interval update to " << config.patrolSeconds; + blockIo.SetPatrolInterval(config.patrolSeconds); + } + } + } + } + }); + blockIo.Start(); + while (!g_exit) { + { + std::unique_lock lock(g_mutex); + g_cv.wait_for(lock, std::chrono::seconds(BMCBlockIoPlu::BMCPLU_DEFAULT_SLEEP_CYCLE), + [] { return g_exit.load(); }); + } + if (!blockIo.IsRunning()) { + g_exit = true; + g_cv.notify_all(); + break; + } + } + blockIo.Stop(); + if (configMonitor.joinable()) { + configMonitor.join(); + } + return 0; +}