blob: 04281bf90384de41e61539ee7f8005354a21abb3 [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 "async_resp.hpp"
#include <app.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <dbus_singleton.hpp>
#include <dbus_utility.hpp>
#include <nlohmann/json.hpp>
#include <variant>
namespace redfish
{
struct HealthPopulate : std::enable_shared_from_this<HealthPopulate>
{
// By default populate status to "/Status" of |asyncResp->res.jsonValue|.
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 isChild = false;
bool isSelf = false;
if (selfPath)
{
if (boost::equals(path.str, *selfPath) ||
boost::starts_with(path.str, *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
for (const std::string& child : inventory)
{
if (boost::starts_with(path.str, 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::find(inventory.begin(),
inventory.end(),
endpoint) != inventory.end())
{
containsChild = true;
break;
}
}
if (!containsChild)
{
continue;
}
}
}
}
}
if (boost::starts_with(path.str, globalInventoryPath) &&
boost::ends_with(path.str, "critical"))
{
health = "Critical";
rollup = "Critical";
return;
}
if (boost::starts_with(path.str, globalInventoryPath) &&
boost::ends_with(path.str, "warning"))
{
health = "Warning";
if (rollup != "Critical")
{
rollup = "Warning";
}
}
else if (boost::ends_with(path.str, "critical"))
{
rollup = "Critical";
if (isSelf)
{
health = "Critical";
return;
}
}
else if (boost::ends_with(path.str, "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()
{
std::shared_ptr<HealthPopulate> self = shared_from_this();
crow::connections::systemBus->async_method_call(
[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];
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 0,
std::array<const char*, 1>{
"xyz.openbmc_project.Inventory.Item.Global"});
}
void getAllStatusAssociations()
{
std::shared_ptr<HealthPopulate> self = shared_from_this();
crow::connections::systemBus->async_method_call(
[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 (boost::ends_with(it->first.str, "critical") ||
boost::ends_with(it->first.str, "warning"))
{
it++;
continue;
}
it = self->statuses.erase(it);
}
},
"xyz.openbmc_project.ObjectMapper", "/",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
}
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