/* // Copyright (c) 2019 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #pragma once #include "app.hpp" #include "async_resp.hpp" #include "dbus_singleton.hpp" #include "dbus_utility.hpp" #include #include #include #include #include namespace redfish { struct HealthPopulate : std::enable_shared_from_this { // By default populate status to "/Status" of |asyncResp->res.jsonValue|. explicit HealthPopulate( const std::shared_ptr& asyncRespIn) : asyncResp(asyncRespIn), statusPtr("/Status") {} // Takes a JSON pointer rather than a reference. This is pretty useful when // the address of the status JSON might change, for example, elements in an // array. HealthPopulate(const std::shared_ptr& asyncRespIn, const nlohmann::json::json_pointer& ptr) : asyncResp(asyncRespIn), statusPtr(ptr) {} HealthPopulate(const HealthPopulate&) = delete; HealthPopulate(HealthPopulate&&) = delete; HealthPopulate& operator=(const HealthPopulate&) = delete; HealthPopulate& operator=(const HealthPopulate&&) = delete; ~HealthPopulate() { nlohmann::json& jsonStatus = asyncResp->res.jsonValue[statusPtr]; nlohmann::json& health = jsonStatus["Health"]; nlohmann::json& rollup = jsonStatus["HealthRollup"]; health = "OK"; rollup = "OK"; for (const std::shared_ptr& healthChild : children) { healthChild->globalInventoryPath = globalInventoryPath; healthChild->statuses = statuses; } for (const auto& [path, interfaces] : statuses) { bool isSelf = false; if (selfPath) { if (boost::equals(path.str, *selfPath) || path.str.starts_with(*selfPath + "/")) { isSelf = true; } } // managers inventory is all the inventory, don't skip any if (!isManagersHealth && !isSelf) { // We only want to look at this association if either the path // of this association is an inventory item, or one of the // endpoints in this association is a child bool isChild = false; for (const std::string& child : inventory) { if (path.str.starts_with(child)) { isChild = true; break; } } if (!isChild) { for (const auto& [interface, association] : interfaces) { if (interface != "xyz.openbmc_project.Association") { continue; } for (const auto& [name, value] : association) { if (name != "endpoints") { continue; } const std::vector* endpoints = std::get_if>(&value); if (endpoints == nullptr) { BMCWEB_LOG_ERROR("Illegal association at {}", path.str); continue; } bool containsChild = false; for (const std::string& endpoint : *endpoints) { if (std::ranges::find(inventory, endpoint) != inventory.end()) { containsChild = true; break; } } if (!containsChild) { continue; } } } } } if (path.str.starts_with(globalInventoryPath) && path.str.ends_with("critical")) { health = "Critical"; rollup = "Critical"; return; } if (path.str.starts_with(globalInventoryPath) && path.str.ends_with("warning")) { health = "Warning"; if (rollup != "Critical") { rollup = "Warning"; } } else if (path.str.ends_with("critical")) { rollup = "Critical"; if (isSelf) { health = "Critical"; return; } } else if (path.str.ends_with("warning")) { if (rollup != "Critical") { rollup = "Warning"; } if (isSelf) { health = "Warning"; } } } } // this should only be called once per url, others should get updated by // being added as children to the 'main' health object for the page void populate() { if (populated) { return; } populated = true; getAllStatusAssociations(); getGlobalPath(); } void getGlobalPath() { constexpr std::array interfaces = { "xyz.openbmc_project.Inventory.Item.Global"}; std::shared_ptr self = shared_from_this(); dbus::utility::getSubTreePaths( "/", 0, interfaces, [self](const boost::system::error_code& ec, const dbus::utility::MapperGetSubTreePathsResponse& resp) { if (ec || resp.size() != 1) { // no global item, or too many return; } self->globalInventoryPath = resp[0]; }); } void getAllStatusAssociations() { std::shared_ptr self = shared_from_this(); sdbusplus::message::object_path path("/"); dbus::utility::getManagedObjects( "xyz.openbmc_project.ObjectMapper", path, [self](const boost::system::error_code& ec, const dbus::utility::ManagedObjectType& resp) { if (ec) { return; } self->statuses = resp; for (auto it = self->statuses.begin(); it != self->statuses.end();) { if (it->first.str.ends_with("critical") || it->first.str.ends_with("warning")) { it++; continue; } it = self->statuses.erase(it); } }); } std::shared_ptr asyncResp; // Will populate the health status into |asyncResp_json[statusPtr]| nlohmann::json::json_pointer statusPtr; // we store pointers to other HealthPopulate items so we can update their // members and reduce dbus calls. As we hold a shared_ptr to them, they get // destroyed last, and they need not call populate() std::vector> children; // self is used if health is for an individual items status, as this is the // 'lowest most' item, the rollup will equal the health std::optional selfPath; std::vector inventory; bool isManagersHealth = false; dbus::utility::ManagedObjectType statuses; std::string globalInventoryPath = "-"; // default to illegal dbus path bool populated = false; }; } // namespace redfish