blob: 6e0d3349bd2e09328c6e2e1da884d575b066170e [file] [log] [blame]
/*
// 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 <nlohmann/json.hpp>
#include <array>
#include <ranges>
#include <string_view>
#include <variant>
namespace redfish
{
struct HealthPopulate : std::enable_shared_from_this<HealthPopulate>
{
// By default populate status to "/Status" of |asyncResp->res.jsonValue|.
explicit HealthPopulate(
const std::shared_ptr<bmcweb::AsyncResp>& 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<bmcweb::AsyncResp>& 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<HealthPopulate>& 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<std::string>* endpoints =
std::get_if<std::vector<std::string>>(&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<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Global"};
std::shared_ptr<HealthPopulate> 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<HealthPopulate> 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<bmcweb::AsyncResp> 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<std::shared_ptr<HealthPopulate>> 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<std::string> selfPath;
std::vector<std::string> inventory;
bool isManagersHealth = false;
dbus::utility::ManagedObjectType statuses;
std::string globalInventoryPath = "-"; // default to illegal dbus path
bool populated = false;
};
} // namespace redfish