blob: c9ac95cac39d1d379aa3dbfb207fef6931771329 [file] [log] [blame]
/*
// Copyright (c) 2018 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 "bmcweb_config.h"
#include "app.hpp"
#include "dbus_utility.hpp"
#include "query.hpp"
#include "redfish_util.hpp"
#include "registries/privilege_registry.hpp"
#include "utils/dbus_utils.hpp"
#include "utils/json_utils.hpp"
#include "utils/sw_utils.hpp"
#include "utils/systemd_utils.hpp"
#include "utils/time_utils.hpp"
#include <boost/system/error_code.hpp>
#include <boost/url/format.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/unpack_properties.hpp>
#include <algorithm>
#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <ranges>
#include <sstream>
#include <string>
#include <string_view>
#include <variant>
namespace redfish
{
/**
* Function reboots the BMC.
*
* @param[in] asyncResp - Shared pointer for completing asynchronous calls
*/
inline void
doBMCGracefulRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
const char* processName = "xyz.openbmc_project.State.BMC";
const char* objectPath = "/xyz/openbmc_project/state/bmc0";
const char* interfaceName = "xyz.openbmc_project.State.BMC";
const std::string& propertyValue =
"xyz.openbmc_project.State.BMC.Transition.Reboot";
const char* destProperty = "RequestedBMCTransition";
// Create the D-Bus variant for D-Bus call.
sdbusplus::asio::setProperty(
*crow::connections::systemBus, processName, objectPath, interfaceName,
destProperty, propertyValue,
[asyncResp](const boost::system::error_code& ec) {
// Use "Set" method to set the property value.
if (ec)
{
BMCWEB_LOG_DEBUG("[Set] Bad D-Bus request error: {}", ec);
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
});
}
inline void
doBMCForceRestart(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
const char* processName = "xyz.openbmc_project.State.BMC";
const char* objectPath = "/xyz/openbmc_project/state/bmc0";
const char* interfaceName = "xyz.openbmc_project.State.BMC";
const std::string& propertyValue =
"xyz.openbmc_project.State.BMC.Transition.HardReboot";
const char* destProperty = "RequestedBMCTransition";
// Create the D-Bus variant for D-Bus call.
sdbusplus::asio::setProperty(
*crow::connections::systemBus, processName, objectPath, interfaceName,
destProperty, propertyValue,
[asyncResp](const boost::system::error_code& ec) {
// Use "Set" method to set the property value.
if (ec)
{
BMCWEB_LOG_DEBUG("[Set] Bad D-Bus request error: {}", ec);
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
});
}
/**
* ManagerResetAction class supports the POST method for the Reset (reboot)
* action.
*/
inline void requestRoutesManagerResetAction(App& app)
{
/**
* Function handles POST method request.
* Analyzes POST body before sending Reset (Reboot) request data to D-Bus.
* OpenBMC supports ResetType "GracefulRestart" and "ForceRestart".
*/
BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Actions/Manager.Reset/")
.privileges(redfish::privileges::postManager)
.methods(boost::beast::http::verb::post)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& managerId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
{
messages::resourceNotFound(asyncResp->res, "Manager", managerId);
return;
}
BMCWEB_LOG_DEBUG("Post Manager Reset.");
std::string resetType;
if (!json_util::readJsonAction(req, asyncResp->res, "ResetType",
resetType))
{
return;
}
if (resetType == "GracefulRestart")
{
BMCWEB_LOG_DEBUG("Proceeding with {}", resetType);
doBMCGracefulRestart(asyncResp);
return;
}
if (resetType == "ForceRestart")
{
BMCWEB_LOG_DEBUG("Proceeding with {}", resetType);
doBMCForceRestart(asyncResp);
return;
}
BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}", resetType);
messages::actionParameterNotSupported(asyncResp->res, resetType,
"ResetType");
return;
});
}
/**
* ManagerResetToDefaultsAction class supports POST method for factory reset
* action.
*/
inline void requestRoutesManagerResetToDefaultsAction(App& app)
{
/**
* Function handles ResetToDefaults POST method request.
*
* Analyzes POST body message and factory resets BMC by calling
* BMC code updater factory reset followed by a BMC reboot.
*
* BMC code updater factory reset wipes the whole BMC read-write
* filesystem which includes things like the network settings.
*
* OpenBMC only supports ResetToDefaultsType "ResetAll".
*/
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/<str>/Actions/Manager.ResetToDefaults/")
.privileges(redfish::privileges::postManager)
.methods(boost::beast::http::verb::post)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& managerId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
{
messages::resourceNotFound(asyncResp->res, "Manager", managerId);
return;
}
BMCWEB_LOG_DEBUG("Post ResetToDefaults.");
std::optional<std::string> resetType;
std::optional<std::string> resetToDefaultsType;
if (!json_util::readJsonAction(req, asyncResp->res, "ResetType",
resetType, "ResetToDefaultsType",
resetToDefaultsType))
{
BMCWEB_LOG_DEBUG("Missing property ResetType.");
messages::actionParameterMissing(asyncResp->res, "ResetToDefaults",
"ResetType");
return;
}
if (resetToDefaultsType && !resetType)
{
BMCWEB_LOG_WARNING(
"Using deprecated ResetToDefaultsType, should be ResetType."
"Support for the ResetToDefaultsType will be dropped in 2Q24");
resetType = resetToDefaultsType;
}
if (resetType != "ResetAll")
{
BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}",
*resetType);
messages::actionParameterNotSupported(asyncResp->res, *resetType,
"ResetType");
return;
}
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_DEBUG("Failed to ResetToDefaults: {}", ec);
messages::internalError(asyncResp->res);
return;
}
// Factory Reset doesn't actually happen until a reboot
// Can't erase what the BMC is running on
doBMCGracefulRestart(asyncResp);
},
"xyz.openbmc_project.Software.BMC.Updater",
"/xyz/openbmc_project/software",
"xyz.openbmc_project.Common.FactoryReset", "Reset");
});
}
/**
* ManagerResetActionInfo derived class for delivering Manager
* ResetType AllowableValues using ResetInfo schema.
*/
inline void requestRoutesManagerResetActionInfo(App& app)
{
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/ResetActionInfo/")
.privileges(redfish::privileges::getActionInfo)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& managerId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
{
messages::resourceNotFound(asyncResp->res, "Manager", managerId);
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#ActionInfo.v1_1_2.ActionInfo";
asyncResp->res.jsonValue["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}/ResetActionInfo",
BMCWEB_REDFISH_MANAGER_URI_NAME);
asyncResp->res.jsonValue["Name"] = "Reset Action Info";
asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
nlohmann::json::object_t parameter;
parameter["Name"] = "ResetType";
parameter["Required"] = true;
parameter["DataType"] = "String";
nlohmann::json::array_t allowableValues;
allowableValues.emplace_back("GracefulRestart");
allowableValues.emplace_back("ForceRestart");
parameter["AllowableValues"] = std::move(allowableValues);
nlohmann::json::array_t parameters;
parameters.emplace_back(std::move(parameter));
asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
});
}
static constexpr const char* objectManagerIface =
"org.freedesktop.DBus.ObjectManager";
static constexpr const char* pidConfigurationIface =
"xyz.openbmc_project.Configuration.Pid";
static constexpr const char* pidZoneConfigurationIface =
"xyz.openbmc_project.Configuration.Pid.Zone";
static constexpr const char* stepwiseConfigurationIface =
"xyz.openbmc_project.Configuration.Stepwise";
static constexpr const char* thermalModeIface =
"xyz.openbmc_project.Control.ThermalMode";
inline void
asyncPopulatePid(const std::string& connection, const std::string& path,
const std::string& currentProfile,
const std::vector<std::string>& supportedProfiles,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
sdbusplus::message::object_path objPath(path);
dbus::utility::getManagedObjects(
connection, objPath,
[asyncResp, currentProfile, supportedProfiles](
const boost::system::error_code& ec,
const dbus::utility::ManagedObjectType& managedObj) {
if (ec)
{
BMCWEB_LOG_ERROR("{}", ec);
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& configRoot =
asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
nlohmann::json& fans = configRoot["FanControllers"];
fans["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanControllers";
fans["@odata.id"] = boost::urls::format(
"/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanControllers",
BMCWEB_REDFISH_MANAGER_URI_NAME);
nlohmann::json& pids = configRoot["PidControllers"];
pids["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.PidControllers";
pids["@odata.id"] = boost::urls::format(
"/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/PidControllers",
BMCWEB_REDFISH_MANAGER_URI_NAME);
nlohmann::json& stepwise = configRoot["StepwiseControllers"];
stepwise["@odata.type"] =
"#OpenBMCManager.v1_0_0.Manager.StepwiseControllers";
stepwise["@odata.id"] = boost::urls::format(
"/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/StepwiseControllers",
BMCWEB_REDFISH_MANAGER_URI_NAME);
nlohmann::json& zones = configRoot["FanZones"];
zones["@odata.id"] = boost::urls::format(
"/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanZones",
BMCWEB_REDFISH_MANAGER_URI_NAME);
zones["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanZones";
configRoot["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan",
BMCWEB_REDFISH_MANAGER_URI_NAME);
configRoot["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.Fan";
configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
if (!currentProfile.empty())
{
configRoot["Profile"] = currentProfile;
}
BMCWEB_LOG_DEBUG("profile = {} !", currentProfile);
for (const auto& pathPair : managedObj)
{
for (const auto& intfPair : pathPair.second)
{
if (intfPair.first != pidConfigurationIface &&
intfPair.first != pidZoneConfigurationIface &&
intfPair.first != stepwiseConfigurationIface)
{
continue;
}
std::string name;
for (const std::pair<std::string,
dbus::utility::DbusVariantType>& propPair :
intfPair.second)
{
if (propPair.first == "Name")
{
const std::string* namePtr =
std::get_if<std::string>(&propPair.second);
if (namePtr == nullptr)
{
BMCWEB_LOG_ERROR("Pid Name Field illegal");
messages::internalError(asyncResp->res);
return;
}
name = *namePtr;
dbus::utility::escapePathForDbus(name);
}
else if (propPair.first == "Profiles")
{
const std::vector<std::string>* profiles =
std::get_if<std::vector<std::string>>(
&propPair.second);
if (profiles == nullptr)
{
BMCWEB_LOG_ERROR("Pid Profiles Field illegal");
messages::internalError(asyncResp->res);
return;
}
if (std::find(profiles->begin(), profiles->end(),
currentProfile) == profiles->end())
{
BMCWEB_LOG_INFO(
"{} not supported in current profile", name);
continue;
}
}
}
nlohmann::json* config = nullptr;
const std::string* classPtr = nullptr;
for (const std::pair<std::string,
dbus::utility::DbusVariantType>& propPair :
intfPair.second)
{
if (propPair.first == "Class")
{
classPtr = std::get_if<std::string>(&propPair.second);
}
}
boost::urls::url url(
boost::urls::format("/redfish/v1/Managers/{}",
BMCWEB_REDFISH_MANAGER_URI_NAME));
if (intfPair.first == pidZoneConfigurationIface)
{
std::string chassis;
if (!dbus::utility::getNthStringFromPath(pathPair.first.str,
5, chassis))
{
chassis = "#IllegalValue";
}
nlohmann::json& zone = zones[name];
zone["Chassis"]["@odata.id"] =
boost::urls::format("/redfish/v1/Chassis/{}", chassis);
url.set_fragment(
("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name)
.to_string());
zone["@odata.id"] = std::move(url);
zone["@odata.type"] =
"#OpenBMCManager.v1_0_0.Manager.FanZone";
config = &zone;
}
else if (intfPair.first == stepwiseConfigurationIface)
{
if (classPtr == nullptr)
{
BMCWEB_LOG_ERROR("Pid Class Field illegal");
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& controller = stepwise[name];
config = &controller;
url.set_fragment(
("/Oem/OpenBmc/Fan/StepwiseControllers"_json_pointer /
name)
.to_string());
controller["@odata.id"] = std::move(url);
controller["@odata.type"] =
"#OpenBMCManager.v1_0_0.Manager.StepwiseController";
controller["Direction"] = *classPtr;
}
// pid and fans are off the same configuration
else if (intfPair.first == pidConfigurationIface)
{
if (classPtr == nullptr)
{
BMCWEB_LOG_ERROR("Pid Class Field illegal");
messages::internalError(asyncResp->res);
return;
}
bool isFan = *classPtr == "fan";
nlohmann::json& element = isFan ? fans[name] : pids[name];
config = &element;
if (isFan)
{
url.set_fragment(
("/Oem/OpenBmc/Fan/FanControllers"_json_pointer /
name)
.to_string());
element["@odata.id"] = std::move(url);
element["@odata.type"] =
"#OpenBMCManager.v1_0_0.Manager.FanController";
}
else
{
url.set_fragment(
("/Oem/OpenBmc/Fan/PidControllers"_json_pointer /
name)
.to_string());
element["@odata.id"] = std::move(url);
element["@odata.type"] =
"#OpenBMCManager.v1_0_0.Manager.PidController";
}
}
else
{
BMCWEB_LOG_ERROR("Unexpected configuration");
messages::internalError(asyncResp->res);
return;
}
// used for making maps out of 2 vectors
const std::vector<double>* keys = nullptr;
const std::vector<double>* values = nullptr;
for (const auto& propertyPair : intfPair.second)
{
if (propertyPair.first == "Type" ||
propertyPair.first == "Class" ||
propertyPair.first == "Name")
{
continue;
}
// zones
if (intfPair.first == pidZoneConfigurationIface)
{
const double* ptr =
std::get_if<double>(&propertyPair.second);
if (ptr == nullptr)
{
BMCWEB_LOG_ERROR("Field Illegal {}",
propertyPair.first);
messages::internalError(asyncResp->res);
return;
}
(*config)[propertyPair.first] = *ptr;
}
if (intfPair.first == stepwiseConfigurationIface)
{
if (propertyPair.first == "Reading" ||
propertyPair.first == "Output")
{
const std::vector<double>* ptr =
std::get_if<std::vector<double>>(
&propertyPair.second);
if (ptr == nullptr)
{
BMCWEB_LOG_ERROR("Field Illegal {}",
propertyPair.first);
messages::internalError(asyncResp->res);
return;
}
if (propertyPair.first == "Reading")
{
keys = ptr;
}
else
{
values = ptr;
}
if (keys != nullptr && values != nullptr)
{
if (keys->size() != values->size())
{
BMCWEB_LOG_ERROR(
"Reading and Output size don't match ");
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& steps = (*config)["Steps"];
steps = nlohmann::json::array();
for (size_t ii = 0; ii < keys->size(); ii++)
{
nlohmann::json::object_t step;
step["Target"] = (*keys)[ii];
step["Output"] = (*values)[ii];
steps.emplace_back(std::move(step));
}
}
}
if (propertyPair.first == "NegativeHysteresis" ||
propertyPair.first == "PositiveHysteresis")
{
const double* ptr =
std::get_if<double>(&propertyPair.second);
if (ptr == nullptr)
{
BMCWEB_LOG_ERROR("Field Illegal {}",
propertyPair.first);
messages::internalError(asyncResp->res);
return;
}
(*config)[propertyPair.first] = *ptr;
}
}
// pid and fans are off the same configuration
if (intfPair.first == pidConfigurationIface ||
intfPair.first == stepwiseConfigurationIface)
{
if (propertyPair.first == "Zones")
{
const std::vector<std::string>* inputs =
std::get_if<std::vector<std::string>>(
&propertyPair.second);
if (inputs == nullptr)
{
BMCWEB_LOG_ERROR("Zones Pid Field Illegal");
messages::internalError(asyncResp->res);
return;
}
auto& data = (*config)[propertyPair.first];
data = nlohmann::json::array();
for (std::string itemCopy : *inputs)
{
dbus::utility::escapePathForDbus(itemCopy);
nlohmann::json::object_t input;
boost::urls::url managerUrl = boost::urls::format(
"/redfish/v1/Managers/{}#{}",
BMCWEB_REDFISH_MANAGER_URI_NAME,
("/Oem/OpenBmc/Fan/FanZones"_json_pointer /
itemCopy)
.to_string());
input["@odata.id"] = std::move(managerUrl);
data.emplace_back(std::move(input));
}
}
// todo(james): may never happen, but this
// assumes configuration data referenced in the
// PID config is provided by the same daemon, we
// could add another loop to cover all cases,
// but I'm okay kicking this can down the road a
// bit
else if (propertyPair.first == "Inputs" ||
propertyPair.first == "Outputs")
{
auto& data = (*config)[propertyPair.first];
const std::vector<std::string>* inputs =
std::get_if<std::vector<std::string>>(
&propertyPair.second);
if (inputs == nullptr)
{
BMCWEB_LOG_ERROR("Field Illegal {}",
propertyPair.first);
messages::internalError(asyncResp->res);
return;
}
data = *inputs;
}
else if (propertyPair.first == "SetPointOffset")
{
const std::string* ptr =
std::get_if<std::string>(&propertyPair.second);
if (ptr == nullptr)
{
BMCWEB_LOG_ERROR("Field Illegal {}",
propertyPair.first);
messages::internalError(asyncResp->res);
return;
}
// translate from dbus to redfish
if (*ptr == "WarningHigh")
{
(*config)["SetPointOffset"] =
"UpperThresholdNonCritical";
}
else if (*ptr == "WarningLow")
{
(*config)["SetPointOffset"] =
"LowerThresholdNonCritical";
}
else if (*ptr == "CriticalHigh")
{
(*config)["SetPointOffset"] =
"UpperThresholdCritical";
}
else if (*ptr == "CriticalLow")
{
(*config)["SetPointOffset"] =
"LowerThresholdCritical";
}
else
{
BMCWEB_LOG_ERROR("Value Illegal {}", *ptr);
messages::internalError(asyncResp->res);
return;
}
}
// doubles
else if (propertyPair.first == "FFGainCoefficient" ||
propertyPair.first == "FFOffCoefficient" ||
propertyPair.first == "ICoefficient" ||
propertyPair.first == "ILimitMax" ||
propertyPair.first == "ILimitMin" ||
propertyPair.first == "PositiveHysteresis" ||
propertyPair.first == "NegativeHysteresis" ||
propertyPair.first == "OutLimitMax" ||
propertyPair.first == "OutLimitMin" ||
propertyPair.first == "PCoefficient" ||
propertyPair.first == "SetPoint" ||
propertyPair.first == "SlewNeg" ||
propertyPair.first == "SlewPos")
{
const double* ptr =
std::get_if<double>(&propertyPair.second);
if (ptr == nullptr)
{
BMCWEB_LOG_ERROR("Field Illegal {}",
propertyPair.first);
messages::internalError(asyncResp->res);
return;
}
(*config)[propertyPair.first] = *ptr;
}
}
}
}
}
});
}
enum class CreatePIDRet
{
fail,
del,
patch
};
inline bool
getZonesFromJsonReq(const std::shared_ptr<bmcweb::AsyncResp>& response,
std::vector<nlohmann::json::object_t>& config,
std::vector<std::string>& zones)
{
if (config.empty())
{
BMCWEB_LOG_ERROR("Empty Zones");
messages::propertyValueFormatError(response->res, config, "Zones");
return false;
}
for (auto& odata : config)
{
std::string path;
if (!redfish::json_util::readJsonObject(odata, response->res,
"@odata.id", path))
{
return false;
}
std::string input;
// 8 below comes from
// /redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones/Left
// 0 1 2 3 4 5 6 7 8
if (!dbus::utility::getNthStringFromPath(path, 8, input))
{
BMCWEB_LOG_ERROR("Got invalid path {}", path);
BMCWEB_LOG_ERROR("Illegal Type Zones");
messages::propertyValueFormatError(response->res, odata, "Zones");
return false;
}
std::replace(input.begin(), input.end(), '_', ' ');
zones.emplace_back(std::move(input));
}
return true;
}
inline const dbus::utility::ManagedObjectType::value_type*
findChassis(const dbus::utility::ManagedObjectType& managedObj,
std::string_view value, std::string& chassis)
{
BMCWEB_LOG_DEBUG("Find Chassis: {}", value);
std::string escaped(value);
std::replace(escaped.begin(), escaped.end(), ' ', '_');
escaped = "/" + escaped;
auto it = std::ranges::find_if(managedObj, [&escaped](const auto& obj) {
if (obj.first.str.ends_with(escaped))
{
BMCWEB_LOG_DEBUG("Matched {}", obj.first.str);
return true;
}
return false;
});
if (it == managedObj.end())
{
return nullptr;
}
// 5 comes from <chassis-name> being the 5th element
// /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
{
return &(*it);
}
return nullptr;
}
inline CreatePIDRet createPidInterface(
const std::shared_ptr<bmcweb::AsyncResp>& response, const std::string& type,
std::string_view name, nlohmann::json& jsonValue, const std::string& path,
const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
dbus::utility::DBusPropertiesMap& output, std::string& chassis,
const std::string& profile)
{
// common deleter
if (jsonValue == nullptr)
{
std::string iface;
if (type == "PidControllers" || type == "FanControllers")
{
iface = pidConfigurationIface;
}
else if (type == "FanZones")
{
iface = pidZoneConfigurationIface;
}
else if (type == "StepwiseControllers")
{
iface = stepwiseConfigurationIface;
}
else
{
BMCWEB_LOG_ERROR("Illegal Type {}", type);
messages::propertyUnknown(response->res, type);
return CreatePIDRet::fail;
}
BMCWEB_LOG_DEBUG("del {} {}", path, iface);
// delete interface
crow::connections::systemBus->async_method_call(
[response, path](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR("Error patching {}: {}", path, ec);
messages::internalError(response->res);
return;
}
messages::success(response->res);
},
"xyz.openbmc_project.EntityManager", path, iface, "Delete");
return CreatePIDRet::del;
}
const dbus::utility::ManagedObjectType::value_type* managedItem = nullptr;
if (!createNewObject)
{
// if we aren't creating a new object, we should be able to find it on
// d-bus
managedItem = findChassis(managedObj, name, chassis);
if (managedItem == nullptr)
{
BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
messages::invalidObject(
response->res,
boost::urls::format("/redfish/v1/Chassis/{}", chassis));
return CreatePIDRet::fail;
}
}
if (!profile.empty() &&
(type == "PidControllers" || type == "FanControllers" ||
type == "StepwiseControllers"))
{
if (managedItem == nullptr)
{
output.emplace_back("Profiles", std::vector<std::string>{profile});
}
else
{
std::string interface;
if (type == "StepwiseControllers")
{
interface = stepwiseConfigurationIface;
}
else
{
interface = pidConfigurationIface;
}
bool ifaceFound = false;
for (const auto& iface : managedItem->second)
{
if (iface.first == interface)
{
ifaceFound = true;
for (const auto& prop : iface.second)
{
if (prop.first == "Profiles")
{
const std::vector<std::string>* curProfiles =
std::get_if<std::vector<std::string>>(
&(prop.second));
if (curProfiles == nullptr)
{
BMCWEB_LOG_ERROR(
"Illegal profiles in managed object");
messages::internalError(response->res);
return CreatePIDRet::fail;
}
if (std::find(curProfiles->begin(),
curProfiles->end(),
profile) == curProfiles->end())
{
std::vector<std::string> newProfiles =
*curProfiles;
newProfiles.push_back(profile);
output.emplace_back("Profiles", newProfiles);
}
}
}
}
}
if (!ifaceFound)
{
BMCWEB_LOG_ERROR("Failed to find interface in managed object");
messages::internalError(response->res);
return CreatePIDRet::fail;
}
}
}
if (type == "PidControllers" || type == "FanControllers")
{
if (createNewObject)
{
output.emplace_back("Class",
type == "PidControllers" ? "temp" : "fan");
output.emplace_back("Type", "Pid");
}
std::optional<std::vector<nlohmann::json::object_t>> zones;
std::optional<std::vector<std::string>> inputs;
std::optional<std::vector<std::string>> outputs;
std::map<std::string, std::optional<double>> doubles;
std::optional<std::string> setpointOffset;
if (!redfish::json_util::readJson(
jsonValue, response->res, "Inputs", inputs, "Outputs", outputs,
"Zones", zones, "FFGainCoefficient",
doubles["FFGainCoefficient"], "FFOffCoefficient",
doubles["FFOffCoefficient"], "ICoefficient",
doubles["ICoefficient"], "ILimitMax", doubles["ILimitMax"],
"ILimitMin", doubles["ILimitMin"], "OutLimitMax",
doubles["OutLimitMax"], "OutLimitMin", doubles["OutLimitMin"],
"PCoefficient", doubles["PCoefficient"], "SetPoint",
doubles["SetPoint"], "SetPointOffset", setpointOffset,
"SlewNeg", doubles["SlewNeg"], "SlewPos", doubles["SlewPos"],
"PositiveHysteresis", doubles["PositiveHysteresis"],
"NegativeHysteresis", doubles["NegativeHysteresis"]))
{
return CreatePIDRet::fail;
}
if (zones)
{
std::vector<std::string> zonesStr;
if (!getZonesFromJsonReq(response, *zones, zonesStr))
{
BMCWEB_LOG_ERROR("Illegal Zones");
return CreatePIDRet::fail;
}
if (chassis.empty() &&
findChassis(managedObj, zonesStr[0], chassis) == nullptr)
{
BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
messages::invalidObject(
response->res,
boost::urls::format("/redfish/v1/Chassis/{}", chassis));
return CreatePIDRet::fail;
}
output.emplace_back("Zones", std::move(zonesStr));
}
if (inputs)
{
for (std::string& value : *inputs)
{
std::replace(value.begin(), value.end(), '_', ' ');
}
output.emplace_back("Inputs", *inputs);
}
if (outputs)
{
for (std::string& value : *outputs)
{
std::replace(value.begin(), value.end(), '_', ' ');
}
output.emplace_back("Outputs", *outputs);
}
if (setpointOffset)
{
// translate between redfish and dbus names
if (*setpointOffset == "UpperThresholdNonCritical")
{
output.emplace_back("SetPointOffset", "WarningLow");
}
else if (*setpointOffset == "LowerThresholdNonCritical")
{
output.emplace_back("SetPointOffset", "WarningHigh");
}
else if (*setpointOffset == "LowerThresholdCritical")
{
output.emplace_back("SetPointOffset", "CriticalLow");
}
else if (*setpointOffset == "UpperThresholdCritical")
{
output.emplace_back("SetPointOffset", "CriticalHigh");
}
else
{
BMCWEB_LOG_ERROR("Invalid setpointoffset {}", *setpointOffset);
messages::propertyValueNotInList(response->res, name,
"SetPointOffset");
return CreatePIDRet::fail;
}
}
// doubles
for (const auto& pairs : doubles)
{
if (!pairs.second)
{
continue;
}
BMCWEB_LOG_DEBUG("{} = {}", pairs.first, *pairs.second);
output.emplace_back(pairs.first, *pairs.second);
}
}
else if (type == "FanZones")
{
output.emplace_back("Type", "Pid.Zone");
std::optional<std::string> chassisId;
std::optional<double> failSafePercent;
std::optional<double> minThermalOutput;
if (!redfish::json_util::readJson(jsonValue, response->res,
"Chassis/@odata.id", chassisId,
"FailSafePercent", failSafePercent,
"MinThermalOutput", minThermalOutput))
{
return CreatePIDRet::fail;
}
if (chassisId)
{
// /redfish/v1/chassis/chassis_name/
if (!dbus::utility::getNthStringFromPath(*chassisId, 3, chassis))
{
BMCWEB_LOG_ERROR("Got invalid path {}", *chassisId);
messages::invalidObject(
response->res,
boost::urls::format("/redfish/v1/Chassis/{}", *chassisId));
return CreatePIDRet::fail;
}
}
if (minThermalOutput)
{
output.emplace_back("MinThermalOutput", *minThermalOutput);
}
if (failSafePercent)
{
output.emplace_back("FailSafePercent", *failSafePercent);
}
}
else if (type == "StepwiseControllers")
{
output.emplace_back("Type", "Stepwise");
std::optional<std::vector<nlohmann::json::object_t>> zones;
std::optional<std::vector<nlohmann::json::object_t>> steps;
std::optional<std::vector<std::string>> inputs;
std::optional<double> positiveHysteresis;
std::optional<double> negativeHysteresis;
std::optional<std::string> direction; // upper clipping curve vs lower
if (!redfish::json_util::readJson(
jsonValue, response->res, "Zones", zones, "Steps", steps,
"Inputs", inputs, "PositiveHysteresis", positiveHysteresis,
"NegativeHysteresis", negativeHysteresis, "Direction",
direction))
{
return CreatePIDRet::fail;
}
if (zones)
{
std::vector<std::string> zonesStrs;
if (!getZonesFromJsonReq(response, *zones, zonesStrs))
{
BMCWEB_LOG_ERROR("Illegal Zones");
return CreatePIDRet::fail;
}
if (chassis.empty() &&
findChassis(managedObj, zonesStrs[0], chassis) == nullptr)
{
BMCWEB_LOG_ERROR("Failed to get chassis from config patch");
messages::invalidObject(
response->res,
boost::urls::format("/redfish/v1/Chassis/{}", chassis));
return CreatePIDRet::fail;
}
output.emplace_back("Zones", std::move(zonesStrs));
}
if (steps)
{
std::vector<double> readings;
std::vector<double> outputs;
for (auto& step : *steps)
{
double target = 0.0;
double out = 0.0;
if (!redfish::json_util::readJsonObject(
step, response->res, "Target", target, "Output", out))
{
return CreatePIDRet::fail;
}
readings.emplace_back(target);
outputs.emplace_back(out);
}
output.emplace_back("Reading", std::move(readings));
output.emplace_back("Output", std::move(outputs));
}
if (inputs)
{
for (std::string& value : *inputs)
{
std::replace(value.begin(), value.end(), '_', ' ');
}
output.emplace_back("Inputs", std::move(*inputs));
}
if (negativeHysteresis)
{
output.emplace_back("NegativeHysteresis", *negativeHysteresis);
}
if (positiveHysteresis)
{
output.emplace_back("PositiveHysteresis", *positiveHysteresis);
}
if (direction)
{
constexpr const std::array<const char*, 2> allowedDirections = {
"Ceiling", "Floor"};
if (std::ranges::find(allowedDirections, *direction) ==
allowedDirections.end())
{
messages::propertyValueTypeError(response->res, "Direction",
*direction);
return CreatePIDRet::fail;
}
output.emplace_back("Class", *direction);
}
}
else
{
BMCWEB_LOG_ERROR("Illegal Type {}", type);
messages::propertyUnknown(response->res, type);
return CreatePIDRet::fail;
}
return CreatePIDRet::patch;
}
struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
{
struct CompletionValues
{
std::vector<std::string> supportedProfiles;
std::string currentProfile;
dbus::utility::MapperGetSubTreeResponse subtree;
};
explicit GetPIDValues(
const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
asyncResp(asyncRespIn)
{}
void run()
{
std::shared_ptr<GetPIDValues> self = shared_from_this();
// get all configurations
constexpr std::array<std::string_view, 4> interfaces = {
pidConfigurationIface, pidZoneConfigurationIface,
objectManagerIface, stepwiseConfigurationIface};
dbus::utility::getSubTree(
"/", 0, interfaces,
[self](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
if (ec)
{
BMCWEB_LOG_ERROR("{}", ec);
messages::internalError(self->asyncResp->res);
return;
}
self->complete.subtree = subtreeLocal;
});
// at the same time get the selected profile
constexpr std::array<std::string_view, 1> thermalModeIfaces = {
thermalModeIface};
dbus::utility::getSubTree(
"/", 0, thermalModeIfaces,
[self](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtreeLocal) {
if (ec || subtreeLocal.empty())
{
return;
}
if (subtreeLocal[0].second.size() != 1)
{
// invalid mapper response, should never happen
BMCWEB_LOG_ERROR("GetPIDValues: Mapper Error");
messages::internalError(self->asyncResp->res);
return;
}
const std::string& path = subtreeLocal[0].first;
const std::string& owner = subtreeLocal[0].second[0].first;
sdbusplus::asio::getAllProperties(
*crow::connections::systemBus, owner, path, thermalModeIface,
[path, owner,
self](const boost::system::error_code& ec2,
const dbus::utility::DBusPropertiesMap& resp) {
if (ec2)
{
BMCWEB_LOG_ERROR(
"GetPIDValues: Can't get thermalModeIface {}", path);
messages::internalError(self->asyncResp->res);
return;
}
const std::string* current = nullptr;
const std::vector<std::string>* supported = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), resp, "Current", current,
"Supported", supported);
if (!success)
{
messages::internalError(self->asyncResp->res);
return;
}
if (current == nullptr || supported == nullptr)
{
BMCWEB_LOG_ERROR(
"GetPIDValues: thermal mode iface invalid {}", path);
messages::internalError(self->asyncResp->res);
return;
}
self->complete.currentProfile = *current;
self->complete.supportedProfiles = *supported;
});
});
}
static void
processingComplete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const CompletionValues& completion)
{
if (asyncResp->res.result() != boost::beast::http::status::ok)
{
return;
}
// create map of <connection, path to objMgr>>
boost::container::flat_map<
std::string, std::string, std::less<>,
std::vector<std::pair<std::string, std::string>>>
objectMgrPaths;
boost::container::flat_set<std::string, std::less<>,
std::vector<std::string>>
calledConnections;
for (const auto& pathGroup : completion.subtree)
{
for (const auto& connectionGroup : pathGroup.second)
{
auto findConnection =
calledConnections.find(connectionGroup.first);
if (findConnection != calledConnections.end())
{
break;
}
for (const std::string& interface : connectionGroup.second)
{
if (interface == objectManagerIface)
{
objectMgrPaths[connectionGroup.first] = pathGroup.first;
}
// this list is alphabetical, so we
// should have found the objMgr by now
if (interface == pidConfigurationIface ||
interface == pidZoneConfigurationIface ||
interface == stepwiseConfigurationIface)
{
auto findObjMgr =
objectMgrPaths.find(connectionGroup.first);
if (findObjMgr == objectMgrPaths.end())
{
BMCWEB_LOG_DEBUG("{}Has no Object Manager",
connectionGroup.first);
continue;
}
calledConnections.insert(connectionGroup.first);
asyncPopulatePid(findObjMgr->first, findObjMgr->second,
completion.currentProfile,
completion.supportedProfiles,
asyncResp);
break;
}
}
}
}
}
~GetPIDValues()
{
boost::asio::post(crow::connections::systemBus->get_io_context(),
std::bind_front(&processingComplete, asyncResp,
std::move(complete)));
}
GetPIDValues(const GetPIDValues&) = delete;
GetPIDValues(GetPIDValues&&) = delete;
GetPIDValues& operator=(const GetPIDValues&) = delete;
GetPIDValues& operator=(GetPIDValues&&) = delete;
std::shared_ptr<bmcweb::AsyncResp> asyncResp;
CompletionValues complete;
};
struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
{
SetPIDValues(
const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
std::vector<
std::pair<std::string, std::optional<nlohmann::json::object_t>>>&&
configurationsIn,
std::optional<std::string>& profileIn) :
asyncResp(asyncRespIn),
configuration(std::move(configurationsIn)),
profile(std::move(profileIn))
{}
SetPIDValues(const SetPIDValues&) = delete;
SetPIDValues(SetPIDValues&&) = delete;
SetPIDValues& operator=(const SetPIDValues&) = delete;
SetPIDValues& operator=(SetPIDValues&&) = delete;
void run()
{
if (asyncResp->res.result() != boost::beast::http::status::ok)
{
return;
}
std::shared_ptr<SetPIDValues> self = shared_from_this();
// todo(james): might make sense to do a mapper call here if this
// interface gets more traction
sdbusplus::message::object_path objPath(
"/xyz/openbmc_project/inventory");
dbus::utility::getManagedObjects(
"xyz.openbmc_project.EntityManager", objPath,
[self](const boost::system::error_code& ec,
const dbus::utility::ManagedObjectType& mObj) {
if (ec)
{
BMCWEB_LOG_ERROR("Error communicating to Entity Manager");
messages::internalError(self->asyncResp->res);
return;
}
const std::array<const char*, 3> configurations = {
pidConfigurationIface, pidZoneConfigurationIface,
stepwiseConfigurationIface};
for (const auto& [path, object] : mObj)
{
for (const auto& [interface, _] : object)
{
if (std::ranges::find(configurations, interface) !=
configurations.end())
{
self->objectCount++;
break;
}
}
}
self->managedObj = mObj;
});
// at the same time get the profile information
constexpr std::array<std::string_view, 1> thermalModeIfaces = {
thermalModeIface};
dbus::utility::getSubTree(
"/", 0, thermalModeIfaces,
[self](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec || subtree.empty())
{
return;
}
if (subtree[0].second.empty())
{
// invalid mapper response, should never happen
BMCWEB_LOG_ERROR("SetPIDValues: Mapper Error");
messages::internalError(self->asyncResp->res);
return;
}
const std::string& path = subtree[0].first;
const std::string& owner = subtree[0].second[0].first;
sdbusplus::asio::getAllProperties(
*crow::connections::systemBus, owner, path, thermalModeIface,
[self, path, owner](const boost::system::error_code& ec2,
const dbus::utility::DBusPropertiesMap& r) {
if (ec2)
{
BMCWEB_LOG_ERROR(
"SetPIDValues: Can't get thermalModeIface {}", path);
messages::internalError(self->asyncResp->res);
return;
}
const std::string* current = nullptr;
const std::vector<std::string>* supported = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), r, "Current", current,
"Supported", supported);
if (!success)
{
messages::internalError(self->asyncResp->res);
return;
}
if (current == nullptr || supported == nullptr)
{
BMCWEB_LOG_ERROR(
"SetPIDValues: thermal mode iface invalid {}", path);
messages::internalError(self->asyncResp->res);
return;
}
self->currentProfile = *current;
self->supportedProfiles = *supported;
self->profileConnection = owner;
self->profilePath = path;
});
});
}
void pidSetDone()
{
if (asyncResp->res.result() != boost::beast::http::status::ok)
{
return;
}
std::shared_ptr<bmcweb::AsyncResp> response = asyncResp;
if (profile)
{
if (std::ranges::find(supportedProfiles, *profile) ==
supportedProfiles.end())
{
messages::actionParameterUnknown(response->res, "Profile",
*profile);
return;
}
currentProfile = *profile;
sdbusplus::asio::setProperty(
*crow::connections::systemBus, profileConnection, profilePath,
thermalModeIface, "Current", *profile,
[response](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR("Error patching profile{}", ec);
messages::internalError(response->res);
}
});
}
for (auto& containerPair : configuration)
{
auto& container = containerPair.second;
if (!container)
{
continue;
}
const std::string& type = containerPair.first;
for (auto& [name, value] : *container)
{
std::string dbusObjName = name;
std::replace(dbusObjName.begin(), dbusObjName.end(), ' ', '_');
BMCWEB_LOG_DEBUG("looking for {}", name);
auto pathItr = std::ranges::find_if(
managedObj, [&dbusObjName](const auto& obj) {
return obj.first.filename() == dbusObjName;
});
dbus::utility::DBusPropertiesMap output;
output.reserve(16); // The pid interface length
// determines if we're patching entity-manager or
// creating a new object
bool createNewObject = (pathItr == managedObj.end());
BMCWEB_LOG_DEBUG("Found = {}", !createNewObject);
std::string iface;
if (!createNewObject)
{
bool findInterface = false;
for (const auto& interface : pathItr->second)
{
if (interface.first == pidConfigurationIface)
{
if (type == "PidControllers" ||
type == "FanControllers")
{
iface = pidConfigurationIface;
findInterface = true;
break;
}
}
else if (interface.first == pidZoneConfigurationIface)
{
if (type == "FanZones")
{
iface = pidZoneConfigurationIface;
findInterface = true;
break;
}
}
else if (interface.first == stepwiseConfigurationIface)
{
if (type == "StepwiseControllers")
{
iface = stepwiseConfigurationIface;
findInterface = true;
break;
}
}
}
// create new object if interface not found
if (!findInterface)
{
createNewObject = true;
}
}
if (createNewObject && value == nullptr)
{
// can't delete a non-existent object
messages::propertyValueNotInList(response->res, value,
name);
continue;
}
std::string path;
if (pathItr != managedObj.end())
{
path = pathItr->first.str;
}
BMCWEB_LOG_DEBUG("Create new = {}", createNewObject);
// arbitrary limit to avoid attacks
constexpr const size_t controllerLimit = 500;
if (createNewObject && objectCount >= controllerLimit)
{
messages::resourceExhaustion(response->res, type);
continue;
}
std::string escaped = name;
std::replace(escaped.begin(), escaped.end(), '_', ' ');
output.emplace_back("Name", escaped);
std::string chassis;
CreatePIDRet ret = createPidInterface(
response, type, name, value, path, managedObj,
createNewObject, output, chassis, currentProfile);
if (ret == CreatePIDRet::fail)
{
return;
}
if (ret == CreatePIDRet::del)
{
continue;
}
if (!createNewObject)
{
for (const auto& property : output)
{
crow::connections::systemBus->async_method_call(
[response,
propertyName{std::string(property.first)}](
const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR("Error patching {}: {}",
propertyName, ec);
messages::internalError(response->res);
return;
}
messages::success(response->res);
},
"xyz.openbmc_project.EntityManager", path,
"org.freedesktop.DBus.Properties", "Set", iface,
property.first, property.second);
}
}
else
{
if (chassis.empty())
{
BMCWEB_LOG_ERROR("Failed to get chassis from config");
messages::internalError(response->res);
return;
}
bool foundChassis = false;
for (const auto& obj : managedObj)
{
if (obj.first.filename() == chassis)
{
chassis = obj.first.str;
foundChassis = true;
break;
}
}
if (!foundChassis)
{
BMCWEB_LOG_ERROR("Failed to find chassis on dbus");
messages::resourceMissingAtURI(
response->res,
boost::urls::format("/redfish/v1/Chassis/{}",
chassis));
return;
}
crow::connections::systemBus->async_method_call(
[response](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR("Error Adding Pid Object {}", ec);
messages::internalError(response->res);
return;
}
messages::success(response->res);
},
"xyz.openbmc_project.EntityManager", chassis,
"xyz.openbmc_project.AddObject", "AddObject", output);
}
}
}
}
~SetPIDValues()
{
try
{
pidSetDone();
}
catch (...)
{
BMCWEB_LOG_CRITICAL("pidSetDone threw exception");
}
}
std::shared_ptr<bmcweb::AsyncResp> asyncResp;
std::vector<std::pair<std::string, std::optional<nlohmann::json::object_t>>>
configuration;
std::optional<std::string> profile;
dbus::utility::ManagedObjectType managedObj;
std::vector<std::string> supportedProfiles;
std::string currentProfile;
std::string profileConnection;
std::string profilePath;
size_t objectCount = 0;
};
/**
* @brief Retrieves BMC manager location data over DBus
*
* @param[in] asyncResp Shared pointer for completing asynchronous calls
* @param[in] connectionName - service name
* @param[in] path - object path
* @return none
*/
inline void getLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connectionName,
const std::string& path)
{
BMCWEB_LOG_DEBUG("Get BMC manager Location data.");
sdbusplus::asio::getProperty<std::string>(
*crow::connections::systemBus, connectionName, path,
"xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
[asyncResp](const boost::system::error_code& ec,
const std::string& property) {
if (ec)
{
BMCWEB_LOG_DEBUG("DBUS response error for "
"Location");
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
property;
});
}
// avoid name collision systems.hpp
inline void
managerGetLastResetTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
BMCWEB_LOG_DEBUG("Getting Manager Last Reset Time");
sdbusplus::asio::getProperty<uint64_t>(
*crow::connections::systemBus, "xyz.openbmc_project.State.BMC",
"/xyz/openbmc_project/state/bmc0", "xyz.openbmc_project.State.BMC",
"LastRebootTime",
[asyncResp](const boost::system::error_code& ec,
const uint64_t lastResetTime) {
if (ec)
{
BMCWEB_LOG_DEBUG("D-BUS response error {}", ec);
return;
}
// LastRebootTime is epoch time, in milliseconds
// https://github.com/openbmc/phosphor-dbus-interfaces/blob/7f9a128eb9296e926422ddc312c148b625890bb6/xyz/openbmc_project/State/BMC.interface.yaml#L19
uint64_t lastResetTimeStamp = lastResetTime / 1000;
// Convert to ISO 8601 standard
asyncResp->res.jsonValue["LastResetTime"] =
redfish::time_utils::getDateTimeUint(lastResetTimeStamp);
});
}
/**
* @brief Set the running firmware image
*
* @param[i,o] asyncResp - Async response object
* @param[i] runningFirmwareTarget - Image to make the running image
*
* @return void
*/
inline void
setActiveFirmwareImage(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& runningFirmwareTarget)
{
// Get the Id from /redfish/v1/UpdateService/FirmwareInventory/<Id>
std::string::size_type idPos = runningFirmwareTarget.rfind('/');
if (idPos == std::string::npos)
{
messages::propertyValueNotInList(asyncResp->res, runningFirmwareTarget,
"@odata.id");
BMCWEB_LOG_DEBUG("Can't parse firmware ID!");
return;
}
idPos++;
if (idPos >= runningFirmwareTarget.size())
{
messages::propertyValueNotInList(asyncResp->res, runningFirmwareTarget,
"@odata.id");
BMCWEB_LOG_DEBUG("Invalid firmware ID.");
return;
}
std::string firmwareId = runningFirmwareTarget.substr(idPos);
// Make sure the image is valid before setting priority
sdbusplus::message::object_path objPath("/xyz/openbmc_project/software");
dbus::utility::getManagedObjects(
"xyz.openbmc_project.Software.BMC.Updater", objPath,
[asyncResp, firmwareId, runningFirmwareTarget](
const boost::system::error_code& ec,
const dbus::utility::ManagedObjectType& subtree) {
if (ec)
{
BMCWEB_LOG_DEBUG("D-Bus response error getting objects.");
messages::internalError(asyncResp->res);
return;
}
if (subtree.empty())
{
BMCWEB_LOG_DEBUG("Can't find image!");
messages::internalError(asyncResp->res);
return;
}
bool foundImage = false;
for (const auto& object : subtree)
{
const std::string& path =
static_cast<const std::string&>(object.first);
std::size_t idPos2 = path.rfind('/');
if (idPos2 == std::string::npos)
{
continue;
}
idPos2++;
if (idPos2 >= path.size())
{
continue;
}
if (path.substr(idPos2) == firmwareId)
{
foundImage = true;
break;
}
}
if (!foundImage)
{
messages::propertyValueNotInList(
asyncResp->res, runningFirmwareTarget, "@odata.id");
BMCWEB_LOG_DEBUG("Invalid firmware ID.");
return;
}
BMCWEB_LOG_DEBUG("Setting firmware version {} to priority 0.",
firmwareId);
// Only support Immediate
// An addition could be a Redfish Setting like
// ActiveSoftwareImageApplyTime and support OnReset
sdbusplus::asio::setProperty(
*crow::connections::systemBus,
"xyz.openbmc_project.Software.BMC.Updater",
"/xyz/openbmc_project/software/" + firmwareId,
"xyz.openbmc_project.Software.RedundancyPriority", "Priority",
static_cast<uint8_t>(0),
[asyncResp](const boost::system::error_code& ec2) {
if (ec2)
{
BMCWEB_LOG_DEBUG("D-Bus response error setting.");
messages::internalError(asyncResp->res);
return;
}
doBMCGracefulRestart(asyncResp);
});
});
}
inline void
afterSetDateTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const boost::system::error_code& ec,
const sdbusplus::message_t& msg)
{
if (ec)
{
BMCWEB_LOG_DEBUG("Failed to set elapsed time. DBUS response error {}",
ec);
const sd_bus_error* dbusError = msg.get_error();
if (dbusError != nullptr)
{
std::string_view errorName(dbusError->name);
if (errorName ==
"org.freedesktop.timedate1.AutomaticTimeSyncEnabled")
{
BMCWEB_LOG_DEBUG("Setting conflict");
messages::propertyValueConflict(
asyncResp->res, "DateTime",
"Managers/NetworkProtocol/NTPProcotolEnabled");
return;
}
}
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.result(boost::beast::http::status::no_content);
}
inline void setDateTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& datetime)
{
BMCWEB_LOG_DEBUG("Set date time: {}", datetime);
std::optional<redfish::time_utils::usSinceEpoch> us =
redfish::time_utils::dateStringToEpoch(datetime);
if (!us)
{
messages::propertyValueFormatError(asyncResp->res, datetime,
"DateTime");
return;
}
// Set the absolute datetime
bool relative = false;
bool interactive = false;
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code& ec,
const sdbusplus::message_t& msg) {
afterSetDateTime(asyncResp, ec, msg);
},
"org.freedesktop.timedate1", "/org/freedesktop/timedate1",
"org.freedesktop.timedate1", "SetTime", us->count(), relative,
interactive);
}
inline void
checkForQuiesced(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
sdbusplus::asio::getProperty<std::string>(
*crow::connections::systemBus, "org.freedesktop.systemd1",
"/org/freedesktop/systemd1/unit/obmc-bmc-service-quiesce@0.target",
"org.freedesktop.systemd1.Unit", "ActiveState",
[asyncResp](const boost::system::error_code& ec,
const std::string& val) {
if (!ec)
{
if (val == "active")
{
asyncResp->res.jsonValue["Status"]["Health"] = "Critical";
asyncResp->res.jsonValue["Status"]["State"] = "Quiesced";
return;
}
}
asyncResp->res.jsonValue["Status"]["Health"] = "OK";
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
});
}
inline void requestRoutesManager(App& app)
{
std::string uuid = persistent_data::getConfig().systemUuid;
BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/")
.privileges(redfish::privileges::getManager)
.methods(boost::beast::http::verb::get)(
[&app, uuid](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& managerId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
{
messages::resourceNotFound(asyncResp->res, "Manager", managerId);
return;
}
asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
"/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
asyncResp->res.jsonValue["@odata.type"] = "#Manager.v1_14_0.Manager";
asyncResp->res.jsonValue["Id"] = BMCWEB_REDFISH_MANAGER_URI_NAME;
asyncResp->res.jsonValue["Name"] = "OpenBmc Manager";
asyncResp->res.jsonValue["Description"] =
"Baseboard Management Controller";
asyncResp->res.jsonValue["PowerState"] = "On";
asyncResp->res.jsonValue["ManagerType"] = "BMC";
asyncResp->res.jsonValue["UUID"] = systemd_utils::getUuid();
asyncResp->res.jsonValue["ServiceEntryPointUUID"] = uuid;
asyncResp->res.jsonValue["Model"] = "OpenBmc"; // TODO(ed), get model
asyncResp->res.jsonValue["LogServices"]["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}/LogServices",
BMCWEB_REDFISH_MANAGER_URI_NAME);
asyncResp->res.jsonValue["NetworkProtocol"]["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}/NetworkProtocol",
BMCWEB_REDFISH_MANAGER_URI_NAME);
asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces",
BMCWEB_REDFISH_MANAGER_URI_NAME);
if constexpr (BMCWEB_VM_NBDPROXY)
{
asyncResp->res.jsonValue["VirtualMedia"]["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}/VirtualMedia",
BMCWEB_REDFISH_MANAGER_URI_NAME);
}
// default oem data
nlohmann::json& oem = asyncResp->res.jsonValue["Oem"];
nlohmann::json& oemOpenbmc = oem["OpenBmc"];
oem["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}#/Oem",
BMCWEB_REDFISH_MANAGER_URI_NAME);
oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
oemOpenbmc["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
BMCWEB_REDFISH_MANAGER_URI_NAME);
nlohmann::json::object_t certificates;
certificates["@odata.id"] = boost::urls::format(
"/redfish/v1/Managers/{}/Truststore/Certificates",
BMCWEB_REDFISH_MANAGER_URI_NAME);
oemOpenbmc["Certificates"] = std::move(certificates);
// Manager.Reset (an action) can be many values, OpenBMC only
// supports BMC reboot.
nlohmann::json& managerReset =
asyncResp->res.jsonValue["Actions"]["#Manager.Reset"];
managerReset["target"] =
boost::urls::format("/redfish/v1/Managers/{}/Actions/Manager.Reset",
BMCWEB_REDFISH_MANAGER_URI_NAME);
managerReset["@Redfish.ActionInfo"] =
boost::urls::format("/redfish/v1/Managers/{}/ResetActionInfo",
BMCWEB_REDFISH_MANAGER_URI_NAME);
// ResetToDefaults (Factory Reset) has values like
// PreserveNetworkAndUsers and PreserveNetwork that aren't supported
// on OpenBMC
nlohmann::json& resetToDefaults =
asyncResp->res.jsonValue["Actions"]["#Manager.ResetToDefaults"];
resetToDefaults["target"] = boost::urls::format(
"/redfish/v1/Managers/{}/Actions/Manager.ResetToDefaults",
BMCWEB_REDFISH_MANAGER_URI_NAME);
resetToDefaults["ResetType@Redfish.AllowableValues"] =
nlohmann::json::array_t({"ResetAll"});
std::pair<std::string, std::string> redfishDateTimeOffset =
redfish::time_utils::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
// TODO (Gunnar): Remove these one day since moved to ComputerSystem
// Still used by OCP profiles
// https://github.com/opencomputeproject/OCP-Profiles/issues/23
// Fill in SerialConsole info
asyncResp->res.jsonValue["SerialConsole"]["ServiceEnabled"] = true;
asyncResp->res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15;
asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] =
nlohmann::json::array_t({"IPMI", "SSH"});
if constexpr (BMCWEB_KVM)
{
// Fill in GraphicalConsole info
asyncResp->res.jsonValue["GraphicalConsole"]["ServiceEnabled"] =
true;
asyncResp->res
.jsonValue["GraphicalConsole"]["MaxConcurrentSessions"] = 4;
asyncResp->res
.jsonValue["GraphicalConsole"]["ConnectTypesSupported"] =
nlohmann::json::array_t({"KVMIP"});
}
if constexpr (!BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
{
asyncResp->res.jsonValue["Links"]["ManagerForServers@odata.count"] =
1;
nlohmann::json::array_t managerForServers;
nlohmann::json::object_t manager;
manager["@odata.id"] = std::format("/redfish/v1/Systems/{}",
BMCWEB_REDFISH_SYSTEM_URI_NAME);
managerForServers.emplace_back(std::move(manager));
asyncResp->res.jsonValue["Links"]["ManagerForServers"] =
std::move(managerForServers);
}
sw_util::populateSoftwareInformation(asyncResp, sw_util::bmcPurpose,
"FirmwareVersion", true);
managerGetLastResetTime(asyncResp);
// ManagerDiagnosticData is added for all BMCs.
nlohmann::json& managerDiagnosticData =
asyncResp->res.jsonValue["ManagerDiagnosticData"];
managerDiagnosticData["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}/ManagerDiagnosticData",
BMCWEB_REDFISH_MANAGER_URI_NAME);
if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
{
auto pids = std::make_shared<GetPIDValues>(asyncResp);
pids->run();
}
getMainChassisId(asyncResp,
[](const std::string& chassisId,
const std::shared_ptr<bmcweb::AsyncResp>& aRsp) {
aRsp->res.jsonValue["Links"]["ManagerForChassis@odata.count"] = 1;
nlohmann::json::array_t managerForChassis;
nlohmann::json::object_t managerObj;
boost::urls::url chassiUrl =
boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
managerObj["@odata.id"] = chassiUrl;
managerForChassis.emplace_back(std::move(managerObj));
aRsp->res.jsonValue["Links"]["ManagerForChassis"] =
std::move(managerForChassis);
aRsp->res.jsonValue["Links"]["ManagerInChassis"]["@odata.id"] =
chassiUrl;
});
sdbusplus::asio::getProperty<double>(
*crow::connections::systemBus, "org.freedesktop.systemd1",
"/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager",
"Progress",
[asyncResp](const boost::system::error_code& ec, double val) {
if (ec)
{
BMCWEB_LOG_ERROR("Error while getting progress");
messages::internalError(asyncResp->res);
return;
}
if (val < 1.0)
{
asyncResp->res.jsonValue["Status"]["Health"] = "OK";
asyncResp->res.jsonValue["Status"]["State"] = "Starting";
return;
}
checkForQuiesced(asyncResp);
});
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Inventory.Item.Bmc"};
dbus::utility::getSubTree(
"/xyz/openbmc_project/inventory", 0, interfaces,
[asyncResp](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
BMCWEB_LOG_DEBUG("D-Bus response error on GetSubTree {}", ec);
return;
}
if (subtree.empty())
{
BMCWEB_LOG_DEBUG("Can't find bmc D-Bus object!");
return;
}
// Assume only 1 bmc D-Bus object
// Throw an error if there is more than 1
if (subtree.size() > 1)
{
BMCWEB_LOG_DEBUG("Found more than 1 bmc D-Bus object!");
messages::internalError(asyncResp->res);
return;
}
if (subtree[0].first.empty() || subtree[0].second.size() != 1)
{
BMCWEB_LOG_DEBUG("Error getting bmc D-Bus object!");
messages::internalError(asyncResp->res);
return;
}
const std::string& path = subtree[0].first;
const std::string& connectionName = subtree[0].second[0].first;
for (const auto& interfaceName : subtree[0].second[0].second)
{
if (interfaceName ==
"xyz.openbmc_project.Inventory.Decorator.Asset")
{
sdbusplus::asio::getAllProperties(
*crow::connections::systemBus, connectionName, path,
"xyz.openbmc_project.Inventory.Decorator.Asset",
[asyncResp](const boost::system::error_code& ec2,
const dbus::utility::DBusPropertiesMap&
propertiesList) {
if (ec2)
{
BMCWEB_LOG_DEBUG("Can't get bmc asset!");
return;
}
const std::string* partNumber = nullptr;
const std::string* serialNumber = nullptr;
const std::string* manufacturer = nullptr;
const std::string* model = nullptr;
const std::string* sparePartNumber = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList,
"PartNumber", partNumber, "SerialNumber",
serialNumber, "Manufacturer", manufacturer, "Model",
model, "SparePartNumber", sparePartNumber);
if (!success)
{
messages::internalError(asyncResp->res);
return;
}
if (partNumber != nullptr)
{
asyncResp->res.jsonValue["PartNumber"] =
*partNumber;
}
if (serialNumber != nullptr)
{
asyncResp->res.jsonValue["SerialNumber"] =
*serialNumber;
}
if (manufacturer != nullptr)
{
asyncResp->res.jsonValue["Manufacturer"] =
*manufacturer;
}
if (model != nullptr)
{
asyncResp->res.jsonValue["Model"] = *model;
}
if (sparePartNumber != nullptr)
{
asyncResp->res.jsonValue["SparePartNumber"] =
*sparePartNumber;
}
});
}
else if (interfaceName ==
"xyz.openbmc_project.Inventory.Decorator.LocationCode")
{
getLocation(asyncResp, connectionName, path);
}
}
});
});
BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/")
.privileges(redfish::privileges::patchManager)
.methods(boost::beast::http::verb::patch)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& managerId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
{
messages::resourceNotFound(asyncResp->res, "Manager", managerId);
return;
}
std::optional<std::string> activeSoftwareImageOdataId;
std::optional<std::string> datetime;
std::optional<nlohmann::json::object_t> pidControllers;
std::optional<nlohmann::json::object_t> fanControllers;
std::optional<nlohmann::json::object_t> fanZones;
std::optional<nlohmann::json::object_t> stepwiseControllers;
std::optional<std::string> profile;
// clang-format off
if (!json_util::readJsonPatch(req, asyncResp->res,
"DateTime", datetime,
"Links/ActiveSoftwareImage/@odata.id", activeSoftwareImageOdataId,
"Oem/OpenBmc/Fan/FanControllers", fanControllers,
"Oem/OpenBmc/Fan/FanZones", fanZones,
"Oem/OpenBmc/Fan/PidControllers", pidControllers,
"Oem/OpenBmc/Fan/Profile", profile,
"Oem/OpenBmc/Fan/StepwiseControllers", stepwiseControllers
))
{
return;
}
// clang-format on
if (pidControllers || fanControllers || fanZones ||
stepwiseControllers || profile)
{
if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
{
std::vector<std::pair<std::string,
std::optional<nlohmann::json::object_t>>>
configuration;
if (pidControllers)
{
configuration.emplace_back("PidControllers",
std::move(pidControllers));
}
if (fanControllers)
{
configuration.emplace_back("FanControllers",
std::move(fanControllers));
}
if (fanZones)
{
configuration.emplace_back("FanZones", std::move(fanZones));
}
if (stepwiseControllers)
{
configuration.emplace_back("StepwiseControllers",
std::move(stepwiseControllers));
}
auto pid = std::make_shared<SetPIDValues>(
asyncResp, std::move(configuration), profile);
pid->run();
}
else
{
messages::propertyUnknown(asyncResp->res, "Oem");
return;
}
}
if (activeSoftwareImageOdataId)
{
setActiveFirmwareImage(asyncResp, *activeSoftwareImageOdataId);
}
if (datetime)
{
setDateTime(asyncResp, *datetime);
}
});
}
inline void requestRoutesManagerCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/")
.privileges(redfish::privileges::getManagerCollection)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
asyncResp->res.jsonValue["@odata.type"] =
"#ManagerCollection.ManagerCollection";
asyncResp->res.jsonValue["Name"] = "Manager Collection";
asyncResp->res.jsonValue["Members@odata.count"] = 1;
nlohmann::json::array_t members;
nlohmann::json& bmc = members.emplace_back();
bmc["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}",
BMCWEB_REDFISH_MANAGER_URI_NAME);
asyncResp->res.jsonValue["Members"] = std::move(members);
});
}
} // namespace redfish