From 526ddc6c9c751b0304634aeafb32c0422f9fade6 Mon Sep 17 00:00:00 2001 From: Chinmoy Dey Date: Wed, 8 Apr 2026 11:30:10 +0000 Subject: [PATCH 1/2] Add leak detection Redfish events for SONiC BMC What: - Integrate rmc-events-based leak detection into bmcweb via a new integration patch. - Add LeakDetection and LeakDetectors Redfish endpoints and wire them into the existing ThermalSubsystem and Redfish router. - Extend sonic-dbus-bridge so leak sensors in STATE_DB are surfaced on D-Bus and drive Redfish events. - Update the build system to copy rmc-events sources into bmcweb and fix Meson wraps for stdexec and sdbusplus. Why: - Expose SONiC BMC leak sensors via standard Redfish resources so clients can monitor chassis leak status. - Emit Environmental.* Redfish events when leak detector states change (Critical/Warning/Normal) for better observability and alerting. - Ensure the leak detection path (STATE_DB -> D-Bus -> Redfish) is robust and buildable in the SONiC/sonic-redfish pipeline. How: - sonic-dbus-bridge: - Add LeakSensorInfo and a leakSensors vector to InventoryModel. - Extend RedisAdapter to read LEAK_SENSOR|* keys from STATE_DB and provide getLeakSensors/getLeakSensor helpers. - Create leak sensor D-Bus objects under /xyz/openbmc_project/sensors/leak with: - xyz.openbmc_project.Inventory.Item.LeakDetector (DetectorState, Type), - xyz.openbmc_project.State.Decorator.OperationalStatus (Functional), - xyz.openbmc_project.Inventory.Item (Present, PrettyName). - Track leak sensor state changes in UpdateEngine and call DBusExporter::updateLeakSensorState, which updates DetectorState and Functional and emits PropertiesChanged signals. - rmc-events: - Add leak_detection.hpp implementing: - /redfish/v1/Chassis/{id}/ThermalSubsystem/LeakDetection, - /LeakDetectors (collection), - /LeakDetectors/{detectorId} (per-sensor). - Add leak_detector_monitor.{hpp,cpp} to watch D-Bus leak detector PropertiesChanged signals and map DetectorState into Environmental.* MessageIds, then call EventServiceManager::sendEvent. - bmcweb integration: - Update meson.build to include redfish-core/src/leak_detector_monitor.cpp in srcfiles_bmcweb. - Include leak_detection.hpp, set up LeakDetection navigation links from ThermalSubsystem, and register LeakDetection/LeakDetector routes in redfish.cpp. - Fix Meson wrap configuration for stdexec/sdbusplus - Build system: - Extend the top-level Makefile to: - Copy rmc-events/lib, include, and src files into the bmcweb tree before applying patches. - Ensure stdexec.wrap is copied into sdbusplus/subprojects (fix-sdbusplus-stdexec) before native bmcweb builds. Signed-off-by: Chinmoy Dey --- Makefile | 44 ++- ...mc-events-leak-detection-into-bmcweb.patch | 175 ++++++++++ patches/series | 1 + rmc-events/include/leak_detector_monitor.hpp | 24 ++ rmc-events/lib/leak_detection.hpp | 308 ++++++++++++++++++ rmc-events/src/leak_detector_monitor.cpp | 154 +++++++++ sonic-dbus-bridge/include/dbus_exporter.hpp | 26 ++ sonic-dbus-bridge/include/redis_adapter.hpp | 31 ++ sonic-dbus-bridge/include/types.hpp | 12 + sonic-dbus-bridge/include/update_engine.hpp | 1 + sonic-dbus-bridge/src/bridge_app.cpp | 25 ++ sonic-dbus-bridge/src/dbus_exporter.cpp | 129 ++++++++ sonic-dbus-bridge/src/redis_adapter.cpp | 117 +++++++ sonic-dbus-bridge/src/update_engine.cpp | 34 ++ 14 files changed, 1075 insertions(+), 6 deletions(-) create mode 100644 patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch create mode 100644 rmc-events/include/leak_detector_monitor.hpp create mode 100644 rmc-events/lib/leak_detection.hpp create mode 100644 rmc-events/src/leak_detector_monitor.cpp diff --git a/Makefile b/Makefile index b986974..359f18a 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ PATCHES_DIR := $(REPO_ROOT)/patches SCRIPTS_DIR := $(REPO_ROOT)/scripts BUILD_DIR := $(REPO_ROOT)/build TARGET_DIR := $(REPO_ROOT)/$(SONIC_REDFISH_TARGET) +RMC_EVENTS_DIR := $(REPO_ROOT)/rmc-events SERIES_FILE := $(PATCHES_DIR)/series DEBIAN_DIR := $(BMCWEB_DIR)/debian @@ -37,7 +38,7 @@ DOCKERFILE_BUILD := $(BUILD_DIR)/Dockerfile.build MAIN_TARGET := $(BMCWEB_BINARY) DERIVED_TARGETS := $(BRIDGE_BINARY) -.PHONY: all build clean reset setup-bmcweb copy-patches apply-patches build-bmcweb build-bridge build-bmcweb-native build-bridge-native build-in-docker help +.PHONY: all build clean reset setup-bmcweb copy-patches copy-rmc-events apply-patches build-bmcweb build-bridge build-bmcweb-native build-bridge-native build-in-docker help # Default target - Build both components (via Docker) all: build @@ -145,8 +146,32 @@ copy-patches: $(SERIES_FILE) @# Note: Patches will create debian/ directory, so we only copy series file after patches are applied @echo " Patches will be applied from $(PATCHES_DIR)" +# Copy rmc-events source files into bmcweb tree before patches are applied. +# The integration patch (0003) wires these files into the bmcweb build but +# does not contain the file contents -- they live in rmc-events/. +copy-rmc-events: setup-bmcweb + @echo "Copying rmc-events sources into bmcweb tree..." + @if [ -d "$(RMC_EVENTS_DIR)" ]; then \ + cp -v $(RMC_EVENTS_DIR)/lib/*.hpp $(BMCWEB_DIR)/redfish-core/lib/ 2>/dev/null || true; \ + cp -v $(RMC_EVENTS_DIR)/include/*.hpp $(BMCWEB_DIR)/redfish-core/include/ 2>/dev/null || true; \ + cp -v $(RMC_EVENTS_DIR)/src/*.cpp $(BMCWEB_DIR)/redfish-core/src/ 2>/dev/null || true; \ + echo " rmc-events files copied"; \ + else \ + echo " No rmc-events directory found, skipping"; \ + fi + +# Copy stdexec.wrap into sdbusplus subprojects after meson fetches it +# This is needed because sdbusplus from git doesn't have our stdexec.wrap redirect +fix-sdbusplus-stdexec: + @if [ -d "$(BMCWEB_DIR)/subprojects/sdbusplus" ] && [ ! -f "$(BMCWEB_DIR)/subprojects/sdbusplus/subprojects/stdexec.wrap" ]; then \ + echo "Copying stdexec.wrap to sdbusplus/subprojects/..."; \ + mkdir -p $(BMCWEB_DIR)/subprojects/sdbusplus/subprojects; \ + cp $(BMCWEB_DIR)/subprojects/stdexec.wrap $(BMCWEB_DIR)/subprojects/sdbusplus/subprojects/ || true; \ + echo " stdexec.wrap copied"; \ + fi + # Apply patches using series file -apply-patches: setup-bmcweb +apply-patches: setup-bmcweb copy-rmc-events @echo "Applying patches from series file..." @if [ ! -d "$(BMCWEB_DIR)" ]; then \ echo "Error: bmcweb directory not found"; \ @@ -261,7 +286,7 @@ build-bridge: clean @ls -lh $(TARGET_DIR)/sonic-dbus-bridge* 2>/dev/null || echo " No artifacts found" # Build bmcweb natively (inside Docker container, no nested Docker) -build-bmcweb-native: +build-bmcweb-native: fix-sdbusplus-stdexec @echo "=========================================" @echo "Building bmcweb Debian package (native)" @echo "=========================================" @@ -357,9 +382,16 @@ clean: @if [ -d "$(BMCWEB_DIR)" ]; then sudo chown -R $$(id -u):$$(id -g) $(BMCWEB_DIR) 2>/dev/null || true; fi @sudo chown -R $$(id -u):$$(id -g) $(BRIDGE_DIR) 2>/dev/null || true - # Wipe bmcweb build state completely. With git, a hard reset + clean -fdx - # is exhaustive: reverts patched files and removes obj-*, debian/, - # subprojects/, and anything else untracked or ignored. + # Clean host-owned files + @echo "Cleaning package artifacts..." + @rm -rf $(BMCWEB_DIR)/debian 2>/dev/null || true + @rm -rf $(BRIDGE_DIR)/build 2>/dev/null || true + @rm -f $(REPO_ROOT)/*.deb $(REPO_ROOT)/*.changes $(REPO_ROOT)/*.buildinfo $(REPO_ROOT)/*.dsc $(REPO_ROOT)/*.tar.gz 2>/dev/null || true + @echo " Removed package artifacts from root directory" + + # Reset bmcweb source to clean state (so patches can be reapplied) + # git clean -fd also removes rmc-events copies (untracked files) + @echo "Resetting bmcweb source to clean state..." @if [ -d "$(BMCWEB_DIR)/.git" ]; then \ echo "Resetting bmcweb source tree..."; \ cd $(BMCWEB_DIR) && git reset --hard HEAD && git clean -ffdx; \ diff --git a/patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch b/patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch new file mode 100644 index 0000000..b462645 --- /dev/null +++ b/patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch @@ -0,0 +1,175 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +Date: Mon, 07 Apr 2026 00:00:00 +0000 +Subject: [PATCH] Integrate rmc-events leak detection and fix meson stdexec issue + +This patch combines two changes: + +1. Integrate rmc-events leak detection into bmcweb: + - Wire rmc-events/ source files (copied into bmcweb tree before this + patch is applied) into the bmcweb build + - meson.build: add leak_detector_monitor.cpp to sources + - redfish.cpp: include leak_detection.hpp, register routes + - thermal_subsystem.hpp: add LeakDetection navigation link + - event_service.hpp: add "Environmental" and "LeakDetector" to + supported subscription filters + - event_service_manager.hpp: include and instantiate the + DbusLeakDetectorMonitor + +2. Fix meson stdexec wrap-redirect issue: + - When sdbusplus is fetched from git, it contains a wrap-redirect for + stdexec that points to a non-existent file, causing meson configuration + to fail + - Add proper stdexec.wrap file that fetches stdexec from git + - Use meson's patch_directory feature to override sdbusplus's stdexec.wrap + +No new file content in this patch -- the actual implementation lives +in the rmc-events/ directory and is copied by the build system. +--- + meson.build | 1 + + redfish-core/include/event_service_manager.hpp | 8 ++++++++ + redfish-core/lib/event_service.hpp | 8 ++++---- + redfish-core/lib/thermal_subsystem.hpp | 4 ++++ + redfish-core/src/redfish.cpp | 4 ++++ + subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap | 6 ++++++ + subprojects/sdbusplus.wrap | 1 + + subprojects/stdexec.wrap | 6 ++++++ + 8 files changed, 32 insertions(+), 4 deletions(-) + create mode 100644 subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap + create mode 100644 subprojects/stdexec.wrap + +diff --git a/meson.build b/meson.build +index 00000001..00000002 100644 +--- a/meson.build ++++ b/meson.build +@@ -383,6 +383,7 @@ srcfiles_bmcweb = files( + 'redfish-core/src/event_log.cpp', + 'redfish-core/src/filesystem_log_watcher.cpp', + 'redfish-core/src/filter_expr_executor.cpp', ++ 'redfish-core/src/leak_detector_monitor.cpp', + 'redfish-core/src/filter_expr_printer.cpp', + 'redfish-core/src/heartbeat_messages.cpp', + 'redfish-core/src/redfish.cpp', +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 00000001..00000002 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -5,6 +5,7 @@ + + #include "dbus_log_watcher.hpp" + #include "error_messages.hpp" ++#include "leak_detector_monitor.hpp" + #include "event_logs_object_type.hpp" + #include "event_matches_filter.hpp" + #include "event_service_store.hpp" +@@ -57,6 +58,7 @@ class EventServiceManager + std::optional dbusEventLogMonitor; + std::optional matchTelemetryMonitor; + std::optional filesystemLogMonitor; ++ std::optional leakDetectorMonitor; + boost::container::flat_map> + subscriptionsMap; + +@@ -82,6 +84,11 @@ class EventServiceManager + { + // Load config from persist store. + initConfig(); ++ ++ // Always start the leak detector monitor so that D-Bus ++ // PropertiesChanged signals on leak sensors are forwarded ++ // as Redfish events to matching subscribers via sendEvent(). ++ leakDetectorMonitor.emplace(); + } + + static EventServiceManager& getInstance() +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 00000001..00000002 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -49,11 +49,11 @@ namespace redfish + + static constexpr const std::array supportedEvtFormatTypes = { + eventFormatType, metricReportFormatType}; +-static constexpr const std::array supportedRegPrefixes = { +- "Base", "OpenBMC", "TaskEvent", "HeartbeatEvent"}; ++static constexpr const std::array supportedRegPrefixes = { ++ "Base", "OpenBMC", "TaskEvent", "HeartbeatEvent", "Environmental"}; + static constexpr const std::array supportedRetryPolicies = { + "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; + +-static constexpr const std::array supportedResourceTypes = { +- "Task", "Heartbeat"}; ++static constexpr const std::array supportedResourceTypes = { ++ "Task", "Heartbeat", "LeakDetector"}; + +diff --git a/redfish-core/lib/thermal_subsystem.hpp b/redfish-core/lib/thermal_subsystem.hpp +index 00000001..00000002 100644 +--- a/redfish-core/lib/thermal_subsystem.hpp ++++ b/redfish-core/lib/thermal_subsystem.hpp +@@ -53,6 +53,11 @@ inline void doThermalSubsystemCollection( + asyncResp->res.jsonValue["ThermalMetrics"]["@odata.id"] = + boost::urls::format( + "/redfish/v1/Chassis/{}/ThermalSubsystem/ThermalMetrics", + chassisId); + ++ asyncResp->res.jsonValue["LeakDetection"]["@odata.id"] = ++ boost::urls::format( ++ "/redfish/v1/Chassis/{}/ThermalSubsystem/LeakDetection", ++ chassisId); ++ + asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; +diff --git a/redfish-core/src/redfish.cpp b/redfish-core/src/redfish.cpp +index 00000001..00000002 100644 +--- a/redfish-core/src/redfish.cpp ++++ b/redfish-core/src/redfish.cpp +@@ -20,6 +20,7 @@ + #include "fabric_ports.hpp" + #include "fan.hpp" + #include "hypervisor_system.hpp" ++#include "leak_detection.hpp" + #include "log_services.hpp" + #include "manager_diagnostic_data.hpp" + #include "manager_logservices_dbus_eventlog.hpp" +@@ -100,4 +101,7 @@ requestRoutesThermalMetrics + requestRoutesThermalSubsystem(app); + requestRoutesFan(app); ++ requestRoutesLeakDetection(app); ++ requestRoutesLeakDetectorCollection(app); ++ requestRoutesLeakDetector(app); + } + requestRoutesManager(app); +diff --git a/subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap b/subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap +new file mode 100644 +index 0000000..1234567 +--- /dev/null ++++ b/subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap +@@ -0,0 +1,6 @@ ++[wrap-git] ++url = https://github.com/NVIDIA/stdexec.git ++revision = HEAD ++ ++[provide] ++stdexec = stdexec_dep +diff --git a/subprojects/sdbusplus.wrap b/subprojects/sdbusplus.wrap +index 1..2 100644 +--- a/subprojects/sdbusplus.wrap ++++ b/subprojects/sdbusplus.wrap +@@ -1,6 +1,7 @@ + [wrap-git] + url = https://github.com/openbmc/sdbusplus.git + revision = HEAD ++patch_directory = sdbusplus + + [provide] + sdbusplus = sdbusplus_dep +diff --git a/subprojects/stdexec.wrap b/subprojects/stdexec.wrap +new file mode 100644 +index 0000000..1234567 +--- /dev/null ++++ b/subprojects/stdexec.wrap +@@ -0,0 +1,6 @@ ++[wrap-git] ++url = https://github.com/NVIDIA/stdexec.git ++revision = HEAD ++ ++[provide] ++stdexec = stdexec_dep diff --git a/patches/series b/patches/series index b6122ef..5f37d11 100644 --- a/patches/series +++ b/patches/series @@ -7,4 +7,5 @@ 0001-Integrating-bmcweb-with-SONiC-s-build-system.patch 0002-Add-Product-field-to-Redfish-service-root.patch +0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch diff --git a/rmc-events/include/leak_detector_monitor.hpp b/rmc-events/include/leak_detector_monitor.hpp new file mode 100644 index 0000000..6e65b79 --- /dev/null +++ b/rmc-events/include/leak_detector_monitor.hpp @@ -0,0 +1,24 @@ +/////////////////////////////////////// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Nexthop AI +// Copyright (C) 2024 SONiC Project +// Author: Nexthop AI +// Author: SONiC Project +// License file: sonic-redfish/LICENSE +/////////////////////////////////////// + +#pragma once + +#include + +namespace redfish +{ + +class DbusLeakDetectorMonitor +{ + public: + DbusLeakDetectorMonitor(); + sdbusplus::bus::match_t leakDetectorMonitor; +}; + +} // namespace redfish diff --git a/rmc-events/lib/leak_detection.hpp b/rmc-events/lib/leak_detection.hpp new file mode 100644 index 0000000..df683ab --- /dev/null +++ b/rmc-events/lib/leak_detection.hpp @@ -0,0 +1,308 @@ +/////////////////////////////////////// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Nexthop AI +// Copyright (C) 2024 SONiC Project +// Author: Nexthop AI +// Author: SONiC Project +// License file: sonic-redfish/LICENSE +/////////////////////////////////////// +// +// Redfish LeakDetection and LeakDetector endpoints: +// GET /redfish/v1/Chassis/{id}/ThermalSubsystem/LeakDetection/ +// GET .../LeakDetection/LeakDetectors/ +// GET .../LeakDetectors/{detectorId}/ +// +// Reads leak sensor data from D-Bus objects created by sonic-dbus-bridge +// at /xyz/openbmc_project/sensors/leak/. + +#pragma once + +#include "app.hpp" +#include "async_resp.hpp" +#include "dbus_utility.hpp" +#include "error_messages.hpp" +#include "generated/enums/leak_detector.hpp" +#include "generated/enums/resource.hpp" +#include "http_request.hpp" +#include "logging.hpp" +#include "query.hpp" +#include "registries/privilege_registry.hpp" +#include "utils/chassis_utils.hpp" +#include "utils/dbus_utils.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace redfish +{ + +constexpr std::string_view leakSensorBasePath = + "/xyz/openbmc_project/sensors/leak"; +constexpr std::array leakDetectorInterfaces = { + "xyz.openbmc_project.Inventory.Item.LeakDetector"}; + +// Extract the trailing segment after the last '.' from a D-Bus enum string. +// e.g. "xyz.openbmc_project.Inventory.Item.LeakDetector.DetectorState.Critical" +// -> "Critical" +inline std::string getDbusEnumSuffix(const std::string& dbusEnum) +{ + auto pos = dbusEnum.rfind('.'); + if (pos != std::string::npos && pos + 1 < dbusEnum.size()) + { + return dbusEnum.substr(pos + 1); + } + return dbusEnum; +} + +inline resource::Health detectorStateToHealth(const std::string& state) +{ + if (state == "Critical") + { + return resource::Health::Critical; + } + if (state == "Warning") + { + return resource::Health::Warning; + } + return resource::Health::OK; +} + +// ---- GET /redfish/v1/Chassis/{id}/ThermalSubsystem/LeakDetection ---- + +inline void handleLeakDetectionGet( + App& app, const crow::Request& req, + const std::shared_ptr& asyncResp, + const std::string& chassisId) +{ + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + + redfish::chassis_utils::getValidChassisPath( + asyncResp, chassisId, + [asyncResp, + chassisId](const std::optional& validChassisPath) { + if (!validChassisPath) + { + messages::resourceNotFound(asyncResp->res, "Chassis", + chassisId); + return; + } + + asyncResp->res.jsonValue["@odata.type"] = + "#LeakDetection.v1_1_0.LeakDetection"; + asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( + "/redfish/v1/Chassis/{}/ThermalSubsystem/LeakDetection", + chassisId); + asyncResp->res.jsonValue["Id"] = "LeakDetection"; + asyncResp->res.jsonValue["Name"] = "Leak Detection"; + asyncResp->res.jsonValue["LeakDetectors"]["@odata.id"] = + boost::urls::format( + "/redfish/v1/Chassis/{}/ThermalSubsystem/LeakDetection/LeakDetectors", + chassisId); + asyncResp->res.jsonValue["Status"]["State"] = + resource::State::Enabled; + asyncResp->res.jsonValue["Status"]["Health"] = + resource::Health::OK; + }); +} + +inline void requestRoutesLeakDetection(App& app) +{ + BMCWEB_ROUTE( + app, + "/redfish/v1/Chassis//ThermalSubsystem/LeakDetection/") + .privileges(redfish::privileges::getLeakDetection) + .methods(boost::beast::http::verb::get)( + std::bind_front(handleLeakDetectionGet, std::ref(app))); +} + +// ---- GET .../LeakDetection/LeakDetectors (collection) ---- + +inline void handleLeakDetectorCollectionGet( + App& app, const crow::Request& req, + const std::shared_ptr& asyncResp, + const std::string& chassisId) +{ + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + + redfish::chassis_utils::getValidChassisPath( + asyncResp, chassisId, + [asyncResp, + chassisId](const std::optional& validChassisPath) { + if (!validChassisPath) + { + messages::resourceNotFound(asyncResp->res, "Chassis", + chassisId); + return; + } + + asyncResp->res.jsonValue["@odata.type"] = + "#LeakDetectorCollection.LeakDetectorCollection"; + asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( + "/redfish/v1/Chassis/{}/ThermalSubsystem/LeakDetection/LeakDetectors", + chassisId); + asyncResp->res.jsonValue["Name"] = "Leak Detector Collection"; + + dbus::utility::getSubTreePaths( + std::string(leakSensorBasePath), 0, leakDetectorInterfaces, + [asyncResp, chassisId]( + const boost::system::error_code& ec, + const dbus::utility::MapperGetSubTreePathsResponse& + paths) { + if (ec) + { + if (ec.value() == boost::system::errc::io_error) + { + asyncResp->res.jsonValue["Members"] = + nlohmann::json::array(); + asyncResp->res + .jsonValue["Members@odata.count"] = 0; + return; + } + BMCWEB_LOG_ERROR( + "D-Bus error getting leak detectors: {}", + ec.message()); + messages::internalError(asyncResp->res); + return; + } + + nlohmann::json& members = + asyncResp->res.jsonValue["Members"]; + members = nlohmann::json::array(); + + for (const auto& path : paths) + { + sdbusplus::message::object_path objPath(path); + std::string detectorId = objPath.filename(); + if (detectorId.empty()) + { + continue; + } + nlohmann::json::object_t member; + member["@odata.id"] = boost::urls::format( + "/redfish/v1/Chassis/{}/ThermalSubsystem/LeakDetection/LeakDetectors/{}", + chassisId, detectorId); + members.emplace_back(std::move(member)); + } + asyncResp->res.jsonValue["Members@odata.count"] = + members.size(); + }); + }); +} + +inline void requestRoutesLeakDetectorCollection(App& app) +{ + BMCWEB_ROUTE( + app, + "/redfish/v1/Chassis//ThermalSubsystem/LeakDetection/LeakDetectors/") + .privileges(redfish::privileges::getLeakDetectorCollection) + .methods(boost::beast::http::verb::get)( + std::bind_front(handleLeakDetectorCollectionGet, std::ref(app))); +} + +// ---- GET .../LeakDetectors/{detectorId} (individual) ---- + +inline void handleLeakDetectorGet( + App& app, const crow::Request& req, + const std::shared_ptr& asyncResp, + const std::string& chassisId, const std::string& detectorId) +{ + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + + redfish::chassis_utils::getValidChassisPath( + asyncResp, chassisId, + [asyncResp, chassisId, + detectorId](const std::optional& validChassisPath) { + if (!validChassisPath) + { + messages::resourceNotFound(asyncResp->res, "Chassis", + chassisId); + return; + } + + std::string dbusPath = + std::string(leakSensorBasePath) + "/" + detectorId; + + dbus::utility::getAllProperties( + "xyz.openbmc_project.Inventory.Manager", dbusPath, + "xyz.openbmc_project.Inventory.Item.LeakDetector", + [asyncResp, chassisId, detectorId]( + const boost::system::error_code& ec, + const dbus::utility::DBusPropertiesMap& properties) { + if (ec) + { + messages::resourceNotFound(asyncResp->res, + "LeakDetector", + detectorId); + return; + } + + asyncResp->res.jsonValue["@odata.type"] = + "#LeakDetector.v1_5_0.LeakDetector"; + asyncResp->res.jsonValue["@odata.id"] = + boost::urls::format( + "/redfish/v1/Chassis/{}/ThermalSubsystem/LeakDetection/LeakDetectors/{}", + chassisId, detectorId); + asyncResp->res.jsonValue["Id"] = detectorId; + asyncResp->res.jsonValue["Name"] = + "Leak Detector " + detectorId; + + for (const auto& [key, val] : properties) + { + const std::string* strVal = + std::get_if(&val); + if (strVal == nullptr) + { + continue; + } + if (key == "DetectorState") + { + std::string state = + getDbusEnumSuffix(*strVal); + asyncResp->res.jsonValue["DetectorState"] = + state; + asyncResp->res + .jsonValue["Status"]["Health"] = + detectorStateToHealth(state); + } + else if (key == "Type") + { + asyncResp->res + .jsonValue["LeakDetectorType"] = *strVal; + } + } + + asyncResp->res.jsonValue["Status"]["State"] = + resource::State::Enabled; + }); + }); +} + +inline void requestRoutesLeakDetector(App& app) +{ + BMCWEB_ROUTE( + app, + "/redfish/v1/Chassis//ThermalSubsystem/LeakDetection/LeakDetectors//") + .privileges(redfish::privileges::getLeakDetector) + .methods(boost::beast::http::verb::get)( + std::bind_front(handleLeakDetectorGet, std::ref(app))); +} + +} // namespace redfish diff --git a/rmc-events/src/leak_detector_monitor.cpp b/rmc-events/src/leak_detector_monitor.cpp new file mode 100644 index 0000000..1844f28 --- /dev/null +++ b/rmc-events/src/leak_detector_monitor.cpp @@ -0,0 +1,154 @@ +/////////////////////////////////////// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Nexthop AI +// Copyright (C) 2024 SONiC Project +// Author: Nexthop AI +// Author: SONiC Project +// License file: sonic-redfish/LICENSE +/////////////////////////////////////// +// +// D-Bus signal watcher for leak detector PropertiesChanged events. +// Converts state changes on xyz.openbmc_project.Inventory.Item.LeakDetector +// objects into Redfish events using the Environmental message registry +// (LeakDetectedCritical, LeakDetectedWarning, LeakDetectedNormal). +// +// Events are delivered to subscribers via EventServiceManager::sendEvent(). + +#include "leak_detector_monitor.hpp" + +#include "dbus_singleton.hpp" +#include "dbus_utility.hpp" +#include "event_service_manager.hpp" +#include "leak_detection.hpp" +#include "logging.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace redfish +{ + +// Map a fully-qualified DetectorState D-Bus enum value to an Environmental +// registry MessageId, severity, and human-readable message. +// Returns false if the state does not warrant an event (e.g. Unavailable). +static bool mapDetectorStateToEvent(const std::string& stateEnum, + std::string& messageId, + std::string& severity, + std::string& message, + const std::string& sensorName) +{ + std::string state = getDbusEnumSuffix(stateEnum); + + if (state == "Critical") + { + messageId = "Environmental.1.1.0.LeakDetectedCritical"; + severity = "Critical"; + message = "Leak detector '" + sensorName + + "' reports a critical level leak."; + return true; + } + if (state == "Warning") + { + messageId = "Environmental.1.1.0.LeakDetectedWarning"; + severity = "Warning"; + message = "Leak detector '" + sensorName + + "' reports a warning level leak."; + return true; + } + if (state == "OK") + { + messageId = "Environmental.1.1.0.LeakDetectedNormal"; + severity = "OK"; + message = "Leak detector '" + sensorName + "' has returned to normal."; + return true; + } + // Unavailable, Absent, etc. -- no event + return false; +} + +static void onLeakDetectorPropertiesChanged(sdbusplus::message_t& msg) +{ + BMCWEB_LOG_DEBUG("Handling LeakDetector PropertiesChanged signal"); + + sdbusplus::message::object_path objPath(msg.get_path()); + std::string interface; + dbus::utility::DBusPropertiesMap props; + std::vector invalidProps; + msg.read(interface, props, invalidProps); + + auto found = std::ranges::find_if(props, [](const auto& x) { + return x.first == "DetectorState"; + }); + if (found == props.end()) + { + return; + } + + const std::string* newState = + std::get_if(&found->second); + if (newState == nullptr) + { + BMCWEB_LOG_ERROR("DetectorState was not a string"); + return; + } + + std::string sensorName = objPath.filename(); + if (sensorName.empty()) + { + BMCWEB_LOG_ERROR("Could not extract sensor name from path"); + return; + } + + std::string messageId; + std::string severity; + std::string message; + if (!mapDetectorStateToEvent(*newState, messageId, severity, message, + sensorName)) + { + BMCWEB_LOG_DEBUG("State {} does not produce an event", *newState); + return; + } + + // Build origin URI -- use "BMC" as the chassis id for SONiC BMC + std::string originUri = + "/redfish/v1/Chassis/BMC/ThermalSubsystem/LeakDetection/LeakDetectors/" + + sensorName; + + nlohmann::json::object_t eventMessage; + eventMessage["MessageId"] = messageId; + eventMessage["MessageArgs"] = nlohmann::json::array_t{sensorName}; + eventMessage["Severity"] = severity; + eventMessage["Message"] = message; + + // Set OriginOfCondition as a proper Redfish reference object. + // We pass empty origin to sendEvent() so it doesn't overwrite this + // with a flat string. + nlohmann::json::object_t originObj; + originObj["@odata.id"] = originUri; + eventMessage["OriginOfCondition"] = std::move(originObj); + + BMCWEB_LOG_INFO("Sending leak event: {} for sensor {}", messageId, + sensorName); + + EventServiceManager::getInstance().sendEvent( + std::move(eventMessage), std::string_view(), "LeakDetector"); +} + +const std::string leakDetectorMatchStr = + "type='signal',member='PropertiesChanged'," + "interface='org.freedesktop.DBus.Properties'," + "arg0='xyz.openbmc_project.Inventory.Item.LeakDetector'," + "path_namespace='/xyz/openbmc_project/sensors/leak'"; + +DbusLeakDetectorMonitor::DbusLeakDetectorMonitor() : + leakDetectorMonitor(*crow::connections::systemBus, + leakDetectorMatchStr, + onLeakDetectorPropertiesChanged) +{} + +} // namespace redfish diff --git a/sonic-dbus-bridge/include/dbus_exporter.hpp b/sonic-dbus-bridge/include/dbus_exporter.hpp index 9171c77..c8171b2 100644 --- a/sonic-dbus-bridge/include/dbus_exporter.hpp +++ b/sonic-dbus-bridge/include/dbus_exporter.hpp @@ -60,6 +60,19 @@ class DBusExporter */ bool updateObjects(const InventoryModel& model); + /** + * @brief Update a leak sensor's DetectorState on D-Bus + * + * Calls set_property() which emits a PropertiesChanged D-Bus signal, + * allowing bmcweb to generate Redfish events. + * + * @param sensorName Sensor name (e.g., "leak_sensor_1") + * @param newState New state string ("OK", "Warning", "Critical", etc.) + * @return true on success + */ + bool updateLeakSensorState(const std::string& sensorName, + const std::string& newState); + private: sdbusplus::asio::object_server& inventoryServer_; @@ -92,6 +105,19 @@ class DBusExporter * - xyz.openbmc_project.Software.Activation (Activation, RequestedActivation) */ bool createFirmwareObjects(const std::vector& versions); + + /** + * @brief Create leak sensor D-Bus objects under /xyz/openbmc_project/sensors/leak/ + * + * Creates one D-Bus object per leak sensor with: + * - xyz.openbmc_project.Inventory.Item.LeakDetector (Type, DetectorState) + * - xyz.openbmc_project.State.Decorator.OperationalStatus (Functional) + * - xyz.openbmc_project.Inventory.Item (Present, PrettyName) + * + * DetectorState is registered as a mutable property so that set_property() + * emits PropertiesChanged signals for bmcweb event monitoring. + */ + bool createLeakSensorObjects(const std::vector& sensors); }; } // namespace sonic::dbus_bridge diff --git a/sonic-dbus-bridge/include/redis_adapter.hpp b/sonic-dbus-bridge/include/redis_adapter.hpp index 1b4ae35..37581ff 100644 --- a/sonic-dbus-bridge/include/redis_adapter.hpp +++ b/sonic-dbus-bridge/include/redis_adapter.hpp @@ -94,6 +94,27 @@ class RedisAdapter */ std::vector getFirmwareVersions(); + /** + * @brief Get all leak sensor data from STATE_DB + * + * Reads LEAK_SENSOR|* keys from STATE_DB. + * Each key is a hash with fields: state, type, present. + * + * @return Vector of leak sensor entries + */ + std::vector getLeakSensors(); + + /** + * @brief Get a single leak sensor by name from STATE_DB + * + * Reads LEAK_SENSOR| hash. More efficient than getLeakSensors() + * when only one sensor needs to be read (avoids KEYS scan). + * + * @param name Sensor name (e.g., "leak_sensor_1") + * @return Sensor info if found, nullopt otherwise + */ + std::optional getLeakSensor(const std::string& name); + private: std::string configDbHost_; int configDbPort_; @@ -135,6 +156,16 @@ class RedisAdapter const std::string& key, const std::string& field); + /** + * @brief Find keys matching a pattern + * + * @param ctx Redis context + * @param pattern Glob-style pattern (e.g., "LEAK_SENSOR|*") + * @return Vector of matching key names + */ + std::vector keys(redisContext* ctx, + const std::string& pattern); + /** * @brief Free Redis reply */ diff --git a/sonic-dbus-bridge/include/types.hpp b/sonic-dbus-bridge/include/types.hpp index b6512b9..08defb5 100644 --- a/sonic-dbus-bridge/include/types.hpp +++ b/sonic-dbus-bridge/include/types.hpp @@ -120,6 +120,17 @@ struct FanInfo bool present{false}; }; +/** + * @brief Leak sensor information from STATE_DB + */ +struct LeakSensorInfo +{ + std::string name; // e.g., "leak_sensor_1" + std::string state{"OK"}; // "OK", "Warning", "Critical", "Unavailable", "Absent" + std::string type{"Moisture"}; // "Moisture" or "FloatSwitch" + bool present{false}; +}; + /** * @brief Platform description from platform.json */ @@ -163,6 +174,7 @@ struct InventoryModel ChassisState chassisState; std::vector psus; std::vector fans; + std::vector leakSensors; std::vector firmwareVersions; }; diff --git a/sonic-dbus-bridge/include/update_engine.hpp b/sonic-dbus-bridge/include/update_engine.hpp index 52ba04c..d4bc07c 100644 --- a/sonic-dbus-bridge/include/update_engine.hpp +++ b/sonic-dbus-bridge/include/update_engine.hpp @@ -90,6 +90,7 @@ class UpdateEngine // Cached data for change detection std::optional cachedMetadata_; std::optional cachedState_; + std::map cachedLeakStates_; /** * @brief Poll timer handler diff --git a/sonic-dbus-bridge/src/bridge_app.cpp b/sonic-dbus-bridge/src/bridge_app.cpp index e99668a..d9948f3 100644 --- a/sonic-dbus-bridge/src/bridge_app.cpp +++ b/sonic-dbus-bridge/src/bridge_app.cpp @@ -302,6 +302,12 @@ InventoryModel BridgeApp::buildInitialModel() // Read firmware versions for FirmwareInventory model.firmwareVersions = redisAdapter_->getFirmwareVersions(); + // Read leak sensors from STATE_DB + if (redisAdapter_->isStateDbConnected()) + { + model.leakSensors = redisAdapter_->getLeakSensors(); + } + return model; } @@ -346,6 +352,17 @@ void BridgeApp::createDbusObjects() {"xyz.openbmc_project.Software.Version", "xyz.openbmc_project.Software.Activation"}); } + + // Leak sensor objects + for (const auto& sensor : currentModel_.leakSensors) + { + std::string sensorPath = std::string("/xyz/openbmc_project/sensors/leak/") + sensor.name; + objectMapper_->registerObject( + sensorPath, + {"xyz.openbmc_project.Inventory.Item.LeakDetector", + "xyz.openbmc_project.State.Decorator.OperationalStatus", + "xyz.openbmc_project.Inventory.Item"}); + } } } @@ -398,6 +415,14 @@ void BridgeApp::startUpdateEngine() "HOST_STATE|switch-host" // Host power state }; + // Dynamically add leak sensor keys discovered at startup + for (const auto& sensor : currentModel_.leakSensors) + { + keysToSubscribe.push_back("LEAK_SENSOR|" + sensor.name); + LOG_INFO("Subscribing to leak sensor key: LEAK_SENSOR|%s", + sensor.name.c_str()); + } + // Register callback to UpdateEngine auto callback = [this](const std::string& key, const std::string& field, diff --git a/sonic-dbus-bridge/src/dbus_exporter.cpp b/sonic-dbus-bridge/src/dbus_exporter.cpp index 5898283..6182956 100644 --- a/sonic-dbus-bridge/src/dbus_exporter.cpp +++ b/sonic-dbus-bridge/src/dbus_exporter.cpp @@ -20,6 +20,13 @@ constexpr const char* IFACE_DECORATOR_MODEL = "xyz.openbmc_project.Inventory.Dec constexpr const char* IFACE_STATE_CHASSIS = "xyz.openbmc_project.State.Chassis"; constexpr const char* IFACE_SOFTWARE_VERSION = "xyz.openbmc_project.Software.Version"; constexpr const char* IFACE_SOFTWARE_ACTIVATION = "xyz.openbmc_project.Software.Activation"; +constexpr const char* IFACE_LEAK_DETECTOR = "xyz.openbmc_project.Inventory.Item.LeakDetector"; +constexpr const char* IFACE_OPERATIONAL_STATUS = "xyz.openbmc_project.State.Decorator.OperationalStatus"; +constexpr const char* IFACE_INVENTORY_ITEM = "xyz.openbmc_project.Inventory.Item"; + +constexpr const char* LEAK_SENSOR_BASE_PATH = "/xyz/openbmc_project/sensors/leak/"; +constexpr const char* DETECTOR_STATE_PREFIX = + "xyz.openbmc_project.Inventory.Item.LeakDetector.DetectorState."; // D-Bus object paths constexpr const char* OBJ_PATH_CHASSIS = "/xyz/openbmc_project/inventory/system/chassis"; @@ -61,6 +68,14 @@ bool DBusExporter::createObjects(const InventoryModel& model) } } + if (!model.leakSensors.empty()) + { + if (!createLeakSensorObjects(model.leakSensors)) + { + LOG_WARNING("Failed to create some leak sensor objects"); + } + } + currentModel_ = model; LOG_INFO("D-Bus objects created successfully"); @@ -255,5 +270,119 @@ bool DBusExporter::createFirmwareObjects( return true; } +bool DBusExporter::createLeakSensorObjects( + const std::vector& sensors) +{ + currentModel_.leakSensors = sensors; + + for (const auto& sensor : sensors) + { + std::string objPath = LEAK_SENSOR_BASE_PATH + sensor.name; + + try + { + // Map state string to fully-qualified D-Bus enum + std::string stateEnum = DETECTOR_STATE_PREFIX + sensor.state; + + // LeakDetector interface — DetectorState is mutable so set_property() + // emits PropertiesChanged signals for bmcweb event monitoring. + auto leakIface = inventoryServer_.add_interface(objPath, + IFACE_LEAK_DETECTOR); + leakIface->register_property("DetectorState", stateEnum); + leakIface->register_property_r( + "Type", std::string(""), + sdbusplus::vtable::property_::const_, + [type = sensor.type](const auto&) { + return type; + }); + leakIface->initialize(); + interfaces_[objPath + ":" + IFACE_LEAK_DETECTOR] = leakIface; + + // OperationalStatus interface + bool functional = (sensor.state == "OK"); + auto statusIface = inventoryServer_.add_interface(objPath, + IFACE_OPERATIONAL_STATUS); + statusIface->register_property("Functional", functional); + statusIface->initialize(); + interfaces_[objPath + ":" + IFACE_OPERATIONAL_STATUS] = statusIface; + + // Inventory.Item interface + auto itemIface = inventoryServer_.add_interface(objPath, + IFACE_INVENTORY_ITEM); + itemIface->register_property_r( + "Present", sensor.present, + sdbusplus::vtable::property_::const_, + [present = sensor.present](const auto&) { + return present; + }); + itemIface->register_property_r( + "PrettyName", std::string(""), + sdbusplus::vtable::property_::const_, + [name = sensor.name](const auto&) { + return std::string("Leak Detector ") + name; + }); + itemIface->initialize(); + interfaces_[objPath + ":" + IFACE_INVENTORY_ITEM] = itemIface; + + LOG_INFO("Created leak sensor object at %s (state=%s, type=%s)", + objPath.c_str(), sensor.state.c_str(), sensor.type.c_str()); + } + catch (const std::exception& e) + { + LOG_ERROR("Failed to create leak sensor object at %s: %s", + objPath.c_str(), e.what()); + } + } + + return true; +} + +bool DBusExporter::updateLeakSensorState(const std::string& sensorName, + const std::string& newState) +{ + std::string objPath = LEAK_SENSOR_BASE_PATH + sensorName; + std::string leakKey = objPath + ":" + IFACE_LEAK_DETECTOR; + std::string statusKey = objPath + ":" + IFACE_OPERATIONAL_STATUS; + + auto leakIt = interfaces_.find(leakKey); + if (leakIt == interfaces_.end()) + { + LOG_ERROR("Leak sensor interface not found for %s", sensorName.c_str()); + return false; + } + + std::string stateEnum = DETECTOR_STATE_PREFIX + newState; + + // set_property emits PropertiesChanged D-Bus signal + bool ok = leakIt->second->set_property("DetectorState", stateEnum); + if (!ok) + { + LOG_ERROR("Failed to set DetectorState for %s", sensorName.c_str()); + return false; + } + + // Update Functional status + auto statusIt = interfaces_.find(statusKey); + if (statusIt != interfaces_.end()) + { + bool functional = (newState == "OK"); + statusIt->second->set_property("Functional", functional); + } + + // Update cached model + for (auto& s : currentModel_.leakSensors) + { + if (s.name == sensorName) + { + s.state = newState; + break; + } + } + + LOG_INFO("Updated leak sensor %s state to %s", sensorName.c_str(), + newState.c_str()); + return true; +} + } // namespace sonic::dbus_bridge diff --git a/sonic-dbus-bridge/src/redis_adapter.cpp b/sonic-dbus-bridge/src/redis_adapter.cpp index 43c73ad..92a1127 100644 --- a/sonic-dbus-bridge/src/redis_adapter.cpp +++ b/sonic-dbus-bridge/src/redis_adapter.cpp @@ -361,6 +361,123 @@ std::vector RedisAdapter::getFirmwareVersions() return versions; } +std::vector RedisAdapter::keys(redisContext* ctx, + const std::string& pattern) +{ + std::vector result; + + redisReply* reply = static_cast( + redisCommand(ctx, "KEYS %s", pattern.c_str())); + + if (!reply) + { + return result; + } + + if (reply->type == REDIS_REPLY_ARRAY) + { + for (size_t i = 0; i < reply->elements; i++) + { + if (reply->element[i]->type == REDIS_REPLY_STRING) + { + result.emplace_back(reply->element[i]->str, + reply->element[i]->len); + } + } + } + + freeReplyObject(reply); + return result; +} + +std::vector RedisAdapter::getLeakSensors() +{ + std::vector sensors; + + if (!stateDbContext_) + { + return sensors; + } + + // Discover all LEAK_SENSOR|* keys in STATE_DB + auto keyList = keys(stateDbContext_, "LEAK_SENSOR|*"); + + for (const auto& key : keyList) + { + // Extract sensor name from key (e.g., "LEAK_SENSOR|leak_sensor_1" -> "leak_sensor_1") + size_t pos = key.find('|'); + if (pos == std::string::npos || pos + 1 >= key.size()) + { + continue; + } + std::string sensorName = key.substr(pos + 1); + + auto fields = hgetall(stateDbContext_, key); + if (fields.empty()) + { + continue; + } + + LeakSensorInfo sensor; + sensor.name = sensorName; + + if (fields.count("state")) + { + sensor.state = fields["state"]; + } + if (fields.count("type")) + { + sensor.type = fields["type"]; + } + if (fields.count("present")) + { + sensor.present = (fields["present"] == "true"); + } + + sensors.push_back(sensor); + LOG_INFO("LeakSensor: %s state=%s type=%s present=%s", + sensor.name.c_str(), sensor.state.c_str(), + sensor.type.c_str(), sensor.present ? "true" : "false"); + } + + LOG_INFO("Found %zu leak sensors in STATE_DB", sensors.size()); + return sensors; +} + +std::optional RedisAdapter::getLeakSensor( + const std::string& name) +{ + if (!stateDbContext_) + { + return std::nullopt; + } + + std::string key = "LEAK_SENSOR|" + name; + auto fields = hgetall(stateDbContext_, key); + if (fields.empty()) + { + return std::nullopt; + } + + LeakSensorInfo sensor; + sensor.name = name; + + if (fields.count("state")) + { + sensor.state = fields["state"]; + } + if (fields.count("type")) + { + sensor.type = fields["type"]; + } + if (fields.count("present")) + { + sensor.present = (fields["present"] == "true"); + } + + return sensor; +} + void RedisAdapter::freeReply(void* reply) { if (reply) diff --git a/sonic-dbus-bridge/src/update_engine.cpp b/sonic-dbus-bridge/src/update_engine.cpp index 14313f2..e3d6479 100644 --- a/sonic-dbus-bridge/src/update_engine.cpp +++ b/sonic-dbus-bridge/src/update_engine.cpp @@ -187,6 +187,40 @@ void UpdateEngine::onRedisFieldChange(const std::string& key, { LOG_DEBUG("[UpdateEngine] HOST_STATE|switch-host changed (not currently mapped to D-Bus)"); } + // Handle LEAK_SENSOR| changes + else if (key.starts_with("LEAK_SENSOR|")) + { + std::string sensorName = key.substr(std::string("LEAK_SENSOR|").size()); + LOG_INFO("[UpdateEngine] LEAK_SENSOR changed: %s", sensorName.c_str()); + + auto sensor = redisAdapter_->getLeakSensor(sensorName); + if (!sensor) + { + LOG_WARNING("[UpdateEngine] Leak sensor %s not found in Redis", + sensorName.c_str()); + } + else + { + auto it = cachedLeakStates_.find(sensorName); + if (it == cachedLeakStates_.end() || + it->second != sensor->state) + { + LOG_INFO("[UpdateEngine] Leak sensor %s state: %s -> %s", + sensorName.c_str(), + it != cachedLeakStates_.end() ? it->second.c_str() : "(none)", + sensor->state.c_str()); + + cachedLeakStates_[sensorName] = sensor->state; + dbusExporter_->updateLeakSensorState(sensorName, + sensor->state); + } + else + { + LOG_DEBUG("[UpdateEngine] Leak sensor %s state unchanged (%s)", + sensorName.c_str(), sensor->state.c_str()); + } + } + } else { LOG_WARNING("[UpdateEngine] Unknown Redis key: %s", key.c_str()); From 6f892ac3d061098746c0f0be802a072149da5260 Mon Sep 17 00:00:00 2001 From: Chinmoy Dey Date: Thu, 16 Apr 2026 08:40:42 +0000 Subject: [PATCH 2/2] Fix bmcweb build and improve artifact management What: - Fix bmcweb Debian packaging so dpkg-buildpackage can find debian/changelog and complete successfully. - Move bmcweb source tarball (.tar.gz) to target/ directory alongside other build artifacts. - Remove redundant stdexec.wrap from sonic-dbus-bridge subprojects. - Update build instructions and patch format for bmcweb integration. Why: - The bmcweb build was failing because debian/ directory and its required files (changelog, control, rules, etc.) were missing from the bmcweb tree. - The .tar.gz source tarball was being left in the repo root, cluttering the workspace instead of being organized with other build outputs. - sonic-dbus-bridge doesn't need its own stdexec.wrap since the dependency is handled via sdbusplus. - Ensure the build is reproducible and all artifacts are properly collected. How: - bmcweb/debian/: - Add debian/changelog with version 1.0.0 for SONiC integration. - Add debian/control defining bmcweb and bmcweb-dbg packages with proper dependencies. - Add debian/install listing files to package (bmcweb binary, systemd units, www content). - Add debian/not-installed excluding unwanted artifacts (boost static libs, zstd tools, etc.). - Add debian/rules with proper Makefile syntax (tab-indented recipes) and wrap-mode=default. - Makefile: - Add @mv $(REPO_ROOT)/bmcweb_*.tar.gz $(TARGET_DIR)/ to build-bmcweb-native target so source tarballs are collected alongside .deb files. - patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch: - Fix corrupt patch format (line count mismatches in unified diff headers). - sonic-dbus-bridge/subprojects/stdexec.wrap: - Remove this file; stdexec dependency is managed via sdbusplus, not as a standalone subproject. - build/Dockerfile.build and README.md: - Update documentation and build environment setup to reflect current build process. Signed-off-by: Chinmoy Dey --- Makefile | 15 +--- README.md | 19 +++-- build/Dockerfile.build | 21 +++++ ...mc-events-leak-detection-into-bmcweb.patch | 83 ++++--------------- ...cap-dependency-for-static-libsystemd.patch | 34 ++++++++ patches/series | 1 + sonic-dbus-bridge/subprojects/stdexec.wrap | 8 -- 7 files changed, 86 insertions(+), 95 deletions(-) create mode 100644 patches/0005-Add-libcap-dependency-for-static-libsystemd.patch delete mode 100644 sonic-dbus-bridge/subprojects/stdexec.wrap diff --git a/Makefile b/Makefile index 359f18a..e378847 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ build: $(DOCKERFILE_BUILD) -v "$(REPO_ROOT):/workspace" \ -w /workspace \ -e SONIC_CONFIG_MAKE_JOBS=$(SONIC_CONFIG_MAKE_JOBS) \ - $(DOCKER_BUILDER_IMAGE) \ + $(DOCKER_BUILDER_IMAGE)\ bash -c "\ set -e; \ git config --global --add safe.directory /workspace; \ @@ -160,16 +160,6 @@ copy-rmc-events: setup-bmcweb echo " No rmc-events directory found, skipping"; \ fi -# Copy stdexec.wrap into sdbusplus subprojects after meson fetches it -# This is needed because sdbusplus from git doesn't have our stdexec.wrap redirect -fix-sdbusplus-stdexec: - @if [ -d "$(BMCWEB_DIR)/subprojects/sdbusplus" ] && [ ! -f "$(BMCWEB_DIR)/subprojects/sdbusplus/subprojects/stdexec.wrap" ]; then \ - echo "Copying stdexec.wrap to sdbusplus/subprojects/..."; \ - mkdir -p $(BMCWEB_DIR)/subprojects/sdbusplus/subprojects; \ - cp $(BMCWEB_DIR)/subprojects/stdexec.wrap $(BMCWEB_DIR)/subprojects/sdbusplus/subprojects/ || true; \ - echo " stdexec.wrap copied"; \ - fi - # Apply patches using series file apply-patches: setup-bmcweb copy-rmc-events @echo "Applying patches from series file..." @@ -286,7 +276,7 @@ build-bridge: clean @ls -lh $(TARGET_DIR)/sonic-dbus-bridge* 2>/dev/null || echo " No artifacts found" # Build bmcweb natively (inside Docker container, no nested Docker) -build-bmcweb-native: fix-sdbusplus-stdexec +build-bmcweb-native: @echo "=========================================" @echo "Building bmcweb Debian package (native)" @echo "=========================================" @@ -305,6 +295,7 @@ build-bmcweb-native: fix-sdbusplus-stdexec @mv $(REPO_ROOT)/bmcweb_*.changes $(TARGET_DIR)/ 2>/dev/null || true @mv $(REPO_ROOT)/bmcweb_*.buildinfo $(TARGET_DIR)/ 2>/dev/null || true @mv $(REPO_ROOT)/bmcweb_*.dsc $(TARGET_DIR)/ 2>/dev/null || true + @mv $(REPO_ROOT)/bmcweb_*.tar.gz $(TARGET_DIR)/ 2>/dev/null || true @echo "" @echo "=========================================" @echo "bmcweb build complete!" diff --git a/README.md b/README.md index f3fbe5d..dfc8aa8 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ The build system is designed for **Debian Trixie** and uses: 1. **Docker-based builds**: All compilation happens inside a `debian:trixie` container for consistency 2. **Debian packaging**: Uses `dpkg-buildpackage` to create `.deb` packages -3. **Meson subprojects**: Dependencies (sdbusplus, stdexec) are managed via `.wrap` files +3. **Meson subprojects**: sdbusplus is pre-built in the Docker image; other dependencies are managed via `.wrap` files 4. **Automatic dependencies**: Build targets automatically trigger required cleanup and setup steps 5. **Patch management**: Uses a `patches/series` file to define patch order @@ -110,7 +110,7 @@ make all - Apply patches from patches/series to bmcweb source 4. Build sonic-dbus-bridge - - Meson downloads dependencies (sdbusplus, stdexec) via .wrap files + - Uses pre-installed sdbusplus from Docker image - dpkg-buildpackage creates .deb packages 5. Build bmcweb @@ -166,18 +166,21 @@ To add a new patch: ## Dependency Management -Dependencies are managed via **Meson wrap files** (`.wrap`): +**sdbusplus** (OpenBMC D-Bus C++ bindings) is pre-built and installed in the Docker +image (`build/Dockerfile.build`). Both bmcweb and sonic-dbus-bridge find it via +pkg-config at build time. The pinned sdbusplus and stdexec commits are configured +as `ARG` directives in the Dockerfile. + +Other dependencies are managed via **Meson wrap files** (`.wrap`): ### bmcweb dependencies: -- `bmcweb/subprojects/sdbusplus.wrap` - D-Bus C++ bindings -- `bmcweb/subprojects/stdexec.wrap` - C++23 executors +- `bmcweb/subprojects/sdbusplus.wrap` - D-Bus C++ bindings (fallback; prefers system package) - Plus other dependencies defined in bmcweb upstream ### sonic-dbus-bridge dependencies: -- `sonic-dbus-bridge/subprojects/sdbusplus.wrap` - D-Bus C++ bindings -- `sonic-dbus-bridge/subprojects/stdexec.wrap` - C++23 executors +- `sonic-dbus-bridge/subprojects/sdbusplus.wrap` - D-Bus C++ bindings (fallback; prefers system package) -Meson automatically downloads and builds these dependencies during the build process. +Meson automatically downloads and builds subproject dependencies during the build process. The Debian packages can be installed in SONiC images. diff --git a/build/Dockerfile.build b/build/Dockerfile.build index 1785ab5..c2cec5a 100644 --- a/build/Dockerfile.build +++ b/build/Dockerfile.build @@ -27,6 +27,7 @@ RUN apt-get update && apt-get install -y \ libboost-url-dev \ libboost-dev \ nlohmann-json3-dev \ + libcap-dev \ dbus \ ninja-build \ cmake \ @@ -36,6 +37,26 @@ RUN apt-get update && apt-get install -y \ python3-inflection \ && rm -rf /var/lib/apt/lists/* +# Pre-build and install sdbusplus as a system library. +# This avoids fetching NVIDIA/stdexec during the main build — stdexec is only +# needed to compile sdbusplus's async subsystem, and the installed pkg-config +# file does not expose it as a public dependency. +ARG SDBUSPLUS_COMMIT=a74f63b1903c8b00d9c134164eaa4ac8e925d0f5 +ARG STDEXEC_COMMIT=91782e6cbfad5df74237bac139b5d61f6cf40313 +RUN set -e \ + && git init /tmp/stdexec && cd /tmp/stdexec \ + && git remote add origin https://github.com/NVIDIA/stdexec.git \ + && git fetch --depth 1 origin ${STDEXEC_COMMIT} && git checkout FETCH_HEAD \ + && git init /tmp/sdbusplus && cd /tmp/sdbusplus \ + && git remote add origin https://github.com/openbmc/sdbusplus.git \ + && git fetch --depth 1 origin ${SDBUSPLUS_COMMIT} && git checkout FETCH_HEAD \ + && cp -r /tmp/stdexec /tmp/sdbusplus/subprojects/stdexec \ + && cd /tmp/sdbusplus \ + && meson setup build --prefix=/usr -Dtests=disabled -Dexamples=disabled \ + && ninja -C build \ + && ninja -C build install \ + && rm -rf /tmp/sdbusplus /tmp/stdexec + WORKDIR /workspace CMD ["/bin/bash"] diff --git a/patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch b/patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch index b462645..fe37c0d 100644 --- a/patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch +++ b/patches/0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch @@ -1,41 +1,26 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 Date: Mon, 07 Apr 2026 00:00:00 +0000 -Subject: [PATCH] Integrate rmc-events leak detection and fix meson stdexec issue +Subject: [PATCH] Integrate rmc-events leak detection into bmcweb -This patch combines two changes: - -1. Integrate rmc-events leak detection into bmcweb: - - Wire rmc-events/ source files (copied into bmcweb tree before this - patch is applied) into the bmcweb build - - meson.build: add leak_detector_monitor.cpp to sources - - redfish.cpp: include leak_detection.hpp, register routes - - thermal_subsystem.hpp: add LeakDetection navigation link - - event_service.hpp: add "Environmental" and "LeakDetector" to - supported subscription filters - - event_service_manager.hpp: include and instantiate the - DbusLeakDetectorMonitor - -2. Fix meson stdexec wrap-redirect issue: - - When sdbusplus is fetched from git, it contains a wrap-redirect for - stdexec that points to a non-existent file, causing meson configuration - to fail - - Add proper stdexec.wrap file that fetches stdexec from git - - Use meson's patch_directory feature to override sdbusplus's stdexec.wrap +Wire rmc-events/ source files (copied into the bmcweb tree before this +patch is applied) into the bmcweb build: + - meson.build: add leak_detector_monitor.cpp to sources + - redfish.cpp: include leak_detection.hpp, register routes + - thermal_subsystem.hpp: add LeakDetection navigation link + - event_service.hpp: add "Environmental" and "LeakDetector" to + supported subscription filters + - event_service_manager.hpp: include and instantiate the + DbusLeakDetectorMonitor No new file content in this patch -- the actual implementation lives in the rmc-events/ directory and is copied by the build system. --- - meson.build | 1 + - redfish-core/include/event_service_manager.hpp | 8 ++++++++ - redfish-core/lib/event_service.hpp | 8 ++++---- - redfish-core/lib/thermal_subsystem.hpp | 4 ++++ - redfish-core/src/redfish.cpp | 4 ++++ - subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap | 6 ++++++ - subprojects/sdbusplus.wrap | 1 + - subprojects/stdexec.wrap | 6 ++++++ - 8 files changed, 32 insertions(+), 4 deletions(-) - create mode 100644 subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap - create mode 100644 subprojects/stdexec.wrap + meson.build | 1 + + redfish-core/include/event_service_manager.hpp | 8 ++++++++ + redfish-core/lib/event_service.hpp | 8 ++++---- + redfish-core/lib/thermal_subsystem.hpp | 4 ++++ + redfish-core/src/redfish.cpp | 4 ++++ + 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 00000001..00000002 100644 @@ -137,39 +122,3 @@ index 00000001..00000002 100644 + requestRoutesLeakDetector(app); } requestRoutesManager(app); -diff --git a/subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap b/subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap -new file mode 100644 -index 0000000..1234567 ---- /dev/null -+++ b/subprojects/packagefiles/sdbusplus/subprojects/stdexec.wrap -@@ -0,0 +1,6 @@ -+[wrap-git] -+url = https://github.com/NVIDIA/stdexec.git -+revision = HEAD -+ -+[provide] -+stdexec = stdexec_dep -diff --git a/subprojects/sdbusplus.wrap b/subprojects/sdbusplus.wrap -index 1..2 100644 ---- a/subprojects/sdbusplus.wrap -+++ b/subprojects/sdbusplus.wrap -@@ -1,6 +1,7 @@ - [wrap-git] - url = https://github.com/openbmc/sdbusplus.git - revision = HEAD -+patch_directory = sdbusplus - - [provide] - sdbusplus = sdbusplus_dep -diff --git a/subprojects/stdexec.wrap b/subprojects/stdexec.wrap -new file mode 100644 -index 0000000..1234567 ---- /dev/null -+++ b/subprojects/stdexec.wrap -@@ -0,0 +1,6 @@ -+[wrap-git] -+url = https://github.com/NVIDIA/stdexec.git -+revision = HEAD -+ -+[provide] -+stdexec = stdexec_dep diff --git a/patches/0005-Add-libcap-dependency-for-static-libsystemd.patch b/patches/0005-Add-libcap-dependency-for-static-libsystemd.patch new file mode 100644 index 0000000..54cbba4 --- /dev/null +++ b/patches/0005-Add-libcap-dependency-for-static-libsystemd.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SONiC Team +Date: Sun, 11 May 2026 00:00:00 +0000 +Subject: [PATCH] Add libcap dependency for static libsystemd linking + +When linking against libsystemd.a statically, the linker requires +libcap to resolve capability-related symbols (cap_set_flag, etc.) +used by systemd's capability-util.c. + +This adds libcap as an explicit dependency to bmcweb_dependencies +to ensure proper linking in static build configurations. +--- + meson.build | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/meson.build b/meson.build +index 00000000..11111111 100644 +--- a/meson.build ++++ b/meson.build +@@ -289,7 +289,10 @@ libsystemd = dependency( + ) + + zlib = dependency('zlib') +-bmcweb_dependencies += [libsystemd, zlib] ++# libcap is needed when linking with static libsystemd.a ++cap = dependency('libcap', required: true) ++bmcweb_dependencies += [libsystemd, zlib, cap] ++ + + nlohmann_json_dep = dependency( + 'nlohmann_json', +-- +2.34.1 + diff --git a/patches/series b/patches/series index 5f37d11..a7cc04f 100644 --- a/patches/series +++ b/patches/series @@ -8,4 +8,5 @@ 0001-Integrating-bmcweb-with-SONiC-s-build-system.patch 0002-Add-Product-field-to-Redfish-service-root.patch 0004-Integrate-rmc-events-leak-detection-into-bmcweb.patch +0005-Add-libcap-dependency-for-static-libsystemd.patch diff --git a/sonic-dbus-bridge/subprojects/stdexec.wrap b/sonic-dbus-bridge/subprojects/stdexec.wrap deleted file mode 100644 index ad1981b..0000000 --- a/sonic-dbus-bridge/subprojects/stdexec.wrap +++ /dev/null @@ -1,8 +0,0 @@ -[wrap-git] -url = https://github.com/NVIDIA/stdexec.git -revision = main -depth = 1 - -[provide] -stdexec = stdexec_dep -