diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces')
20 files changed, 4671 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Add-ConnectedVia-property-to-virtual-media-item-temp.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Add-ConnectedVia-property-to-virtual-media-item-temp.patch new file mode 100644 index 000000000..ec6d70df1 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Add-ConnectedVia-property-to-virtual-media-item-temp.patch @@ -0,0 +1,25 @@ +From 4af788655c5b5a5fae4d85b365a70dc619810fe0 Mon Sep 17 00:00:00 2001 +From: Karol Wachowski <karol.wachowski@intel.com> +Date: Thu, 11 Feb 2021 08:35:41 +0000 +Subject: [PATCH] Add ConnectedVia property to virtual media item template + +Tested: Verified that ConnectedVia property is returned and set to + "NotConnected" for disconnected media. + +Signed-off-by: Karol Wachowski <karol.wachowski@intel.com> +--- + redfish-core/lib/virtual_media.hpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 188248a..80e7315 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -192,6 +192,7 @@ static nlohmann::json vmItemTemplate(const std::string& name, + item["@odata.id"] = + "/redfish/v1/Managers/" + name + "/VirtualMedia/" + resName; + item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia"; ++ item["ConnectedVia"] = "NotConnected"; + item["Name"] = "Virtual Removable Media"; + item["Id"] = resName; + item["WriteProtected"] = true; diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Firmware-update-configuration-changes.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Firmware-update-configuration-changes.patch new file mode 100755 index 000000000..193461baf --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Firmware-update-configuration-changes.patch @@ -0,0 +1,696 @@ +From 10cb7cb14974725a29b3ead4c543ca5e58234c07 Mon Sep 17 00:00:00 2001 +From: Vikram Bodireddy <vikram.bodireddy@intel.com> +Date: Wed, 18 Nov 2020 17:14:41 +0530 +Subject: [PATCH] Firmware update configuration changes + +This commit will provide user to PATCH the below firmware update +attributes before uploding the firmware image. + +1. This will have PATCH support for 'HttpPushUriTargets' and +'HttpPushUriTargetsBusy' attributes. These attributes enables +'HttpPushUri' to distinguish between the firmware update targets. + +2. ApplyOptions are used to specify firmware update specific options +such as ClearConfig which is used while activating the updated +firmware. This setting is maintained in a local static variable +when set using PATCH method. Its used in activate image as input +parameter. This attribute is added as Oem as the default +UpdateService interface doesn't specify any relevant or appropriate +attribute for this. + +Tested: + - GET on "/redfish/v1/UpdateService", got below response +......... + "HttpPushUriTargets": [], + "HttpPushUriTargetsBusy": false +........ + + - PATCH on "/redfish/v1/UpdateService" and works fine. +{ + "HttpPushUriTargets": ["bmc_recovery"], + "HttpPushUriTargetsBusy": true +} + + - Did Firmware update and verified end to end functionality + for both bmc active and backup images. + + - Tested setting ClearConfig to true or false using PATCH + method. + + - Successfully ran redfish validater with no new errors. + +Signed-off-by: Vikram Bodireddy <vikram.bodireddy@intel.com> + +%% original patch: 0001-Firmware-update-configuration-changes.patch + +Change-Id: I44e1743fd76aa37c7b8affa49a3e05f808187037 +Signed-off-by: Helen Huang <he.huang@intel.com> +--- + redfish-core/lib/update_service.hpp | 339 ++++++++++++++++-- + static/redfish/v1/$metadata/index.xml | 3 + + .../JsonSchemas/OemUpdateService/index.json | 69 ++++ + .../redfish/v1/schema/OemUpdateService_v1.xml | 40 +++ + 4 files changed, 421 insertions(+), 30 deletions(-) + create mode 100644 static/redfish/v1/JsonSchemas/OemUpdateService/index.json + create mode 100644 static/redfish/v1/schema/OemUpdateService_v1.xml + +diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp +index 6d44171..8eda265 100644 +--- a/redfish-core/lib/update_service.hpp ++++ b/redfish-core/lib/update_service.hpp +@@ -32,6 +32,17 @@ static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateErrorMatcher; + static bool fwUpdateInProgress = false; + // Timer for software available + static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer; ++static constexpr const char* versionIntf = ++ "xyz.openbmc_project.Software.Version"; ++static constexpr const char* activationIntf = ++ "xyz.openbmc_project.Software.Activation"; ++static constexpr const char* reqActivationPropName = "RequestedActivation"; ++static constexpr const char* reqActivationsActive = ++ "xyz.openbmc_project.Software.Activation.RequestedActivations.Active"; ++static constexpr const char* reqActivationsStandBySpare = ++ "xyz.openbmc_project.Software.Activation.RequestedActivations.StandbySpare"; ++static constexpr const char* activationsStandBySpare = ++ "xyz.openbmc_project.Software.Activation.Activations.StandbySpare"; + + static void cleanUp() + { +@@ -40,27 +51,119 @@ static void cleanUp() + fwUpdateErrorMatcher = nullptr; + } + static void activateImage(const std::string& objPath, +- const std::string& service) ++ const std::string& service, ++ const std::vector<std::string>& imgUriTargets) + { + BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service; ++ // If targets is empty, it will apply to the active. ++ if (imgUriTargets.size() == 0) ++ { ++ crow::connections::systemBus->async_method_call( ++ [](const boost::system::error_code error_code) { ++ if (error_code) ++ { ++ BMCWEB_LOG_DEBUG ++ << "RequestedActivation failed: error_code = " ++ << error_code; ++ BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); ++ } ++ }, ++ service, objPath, "org.freedesktop.DBus.Properties", "Set", ++ activationIntf, reqActivationPropName, ++ std::variant<std::string>(reqActivationsActive)); ++ return; ++ } ++ ++ // TODO: Now we support only one target becuase software-manager ++ // code support one activation per object. It will be enhanced ++ // to multiple targets for single image in future. For now, ++ // consider first target alone. + crow::connections::systemBus->async_method_call( +- [](const boost::system::error_code errorCode) { +- if (errorCode) ++ [objPath, service, imgTarget{imgUriTargets[0]}]( ++ const boost::system::error_code ec, ++ const crow::openbmc_mapper::GetSubTreeType& subtree) { ++ if (ec || !subtree.size()) + { +- BMCWEB_LOG_DEBUG << "error_code = " << errorCode; +- BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message(); ++ return; ++ } ++ ++ for (const auto& [invObjPath, invDict] : subtree) ++ { ++ std::size_t idPos = invObjPath.rfind("/"); ++ if ((idPos == std::string::npos) || ++ ((idPos + 1) >= invObjPath.size())) ++ { ++ BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; ++ return; ++ } ++ std::string swId = invObjPath.substr(idPos + 1); ++ ++ if (swId != imgTarget) ++ { ++ continue; ++ } ++ ++ if (invDict.size() < 1) ++ { ++ continue; ++ } ++ BMCWEB_LOG_DEBUG << "Image target matched with object " ++ << invObjPath; ++ crow::connections::systemBus->async_method_call( ++ [objPath, ++ service](const boost::system::error_code error_code, ++ const std::variant<std::string> value) { ++ if (error_code) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Error in querying activation value"; ++ // not all fwtypes are updateable, ++ // this is ok ++ return; ++ } ++ std::string activationValue = ++ std::get<std::string>(value); ++ BMCWEB_LOG_DEBUG << "Activation Value: " ++ << activationValue; ++ std::string reqActivation = reqActivationsActive; ++ if (activationValue == activationsStandBySpare) ++ { ++ reqActivation = reqActivationsStandBySpare; ++ } ++ BMCWEB_LOG_DEBUG ++ << "Setting RequestedActivation value as " ++ << reqActivation << " for " << service << " " ++ << objPath; ++ crow::connections::systemBus->async_method_call( ++ [](const boost::system::error_code error_code) { ++ if (error_code) ++ { ++ BMCWEB_LOG_DEBUG ++ << "RequestedActivation failed: ec = " ++ << error_code; ++ } ++ return; ++ }, ++ service, objPath, "org.freedesktop.DBus.Properties", ++ "Set", activationIntf, reqActivationPropName, ++ std::variant<std::string>(reqActivation)); ++ }, ++ invDict[0].first, ++ "/xyz/openbmc_project/software/" + imgTarget, ++ "org.freedesktop.DBus.Properties", "Get", activationIntf, ++ "Activation"); + } + }, +- service, objPath, "org.freedesktop.DBus.Properties", "Set", +- "xyz.openbmc_project.Software.Activation", "RequestedActivation", +- std::variant<std::string>( +- "xyz.openbmc_project.Software.Activation.RequestedActivations." +- "Active")); ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", ++ static_cast<int32_t>(0), std::array<const char*, 1>{versionIntf}); + } + + // Note that asyncResp can be either a valid pointer or nullptr. If nullptr + // then no asyncResp updates will occur + static void softwareInterfaceAdded(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::vector<std::string> imgUriTargets, + sdbusplus::message::message& m, + const crow::Request& req) + { +@@ -73,22 +176,24 @@ static void softwareInterfaceAdded(const std::shared_ptr<AsyncResp>& asyncResp, + + m.read(objPath, interfacesProperties); + +- BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; ++ BMCWEB_LOG_DEBUG << "Software Interface Added. obj path = " << objPath.str; + for (auto& interface : interfacesProperties) + { + BMCWEB_LOG_DEBUG << "interface = " << interface.first; + +- if (interface.first == "xyz.openbmc_project.Software.Activation") ++ if (interface.first == activationIntf) + { + // Retrieve service and activate + crow::connections::systemBus->async_method_call( +- [objPath, asyncResp, ++ [objPath, asyncResp, imgTargets{imgUriTargets}, + req](const boost::system::error_code errorCode, + const std::vector<std::pair< + std::string, std::vector<std::string>>>& objInfo) { + if (errorCode) + { +- BMCWEB_LOG_DEBUG << "error_code = " << errorCode; ++ BMCWEB_LOG_DEBUG ++ << "GetSoftwareObject path failed: error_code = " ++ << errorCode; + BMCWEB_LOG_DEBUG << "error msg = " + << errorCode.message(); + if (asyncResp) +@@ -115,7 +220,7 @@ static void softwareInterfaceAdded(const std::shared_ptr<AsyncResp>& asyncResp, + // is added + fwAvailableTimer = nullptr; + +- activateImage(objPath.str, objInfo[0].first); ++ activateImage(objPath.str, objInfo[0].first, imgTargets); + if (asyncResp) + { + std::shared_ptr<task::TaskData> task = +@@ -247,8 +352,7 @@ static void softwareInterfaceAdded(const std::shared_ptr<AsyncResp>& asyncResp, + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, +- std::array<const char*, 1>{ +- "xyz.openbmc_project.Software.Activation"}); ++ std::array<const char*, 1>{activationIntf}); + } + } + } +@@ -257,7 +361,8 @@ static void softwareInterfaceAdded(const std::shared_ptr<AsyncResp>& asyncResp, + // then no asyncResp updates will occur + static void monitorForSoftwareAvailable( + const std::shared_ptr<AsyncResp>& asyncResp, const crow::Request& req, +- const std::string& url, int timeoutTimeSeconds = 10) ++ const std::string& url, const std::vector<std::string>& imgUriTargets, ++ int timeoutTimeSeconds = 10) + { + // Only allow one FW update at a time + if (fwUpdateInProgress != false) +@@ -297,9 +402,10 @@ static void monitorForSoftwareAvailable( + } + }); + +- auto callback = [asyncResp, req](sdbusplus::message::message& m) { ++ auto callback = [asyncResp, imgTargets{imgUriTargets}, ++ req](sdbusplus::message::message& m) { + BMCWEB_LOG_DEBUG << "Match fired"; +- softwareInterfaceAdded(asyncResp, m, req); ++ softwareInterfaceAdded(asyncResp, imgTargets, m, req); + }; + + fwUpdateInProgress = true; +@@ -475,12 +581,15 @@ class UpdateServiceActionsSimpleUpdate : public Node + std::string fwFile = imageURI.substr(separator + 1); + BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile; + ++ // We will pass empty targets and its handled in activation. ++ std::vector<std::string> httpUriTargets; ++ + // Setup callback for when new software detected + // Give TFTP 10 minutes to complete + monitorForSoftwareAvailable( + asyncResp, req, + "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", +- 600); ++ httpUriTargets, 600); + + // TFTP can take up to 10 minutes depending on image size and + // connection speed. Return to caller as soon as the TFTP operation +@@ -514,7 +623,8 @@ class UpdateServiceActionsSimpleUpdate : public Node + class UpdateService : public Node + { + public: +- UpdateService(App& app) : Node(app, "/redfish/v1/UpdateService/") ++ UpdateService(App& app) : ++ Node(app, "/redfish/v1/UpdateService/"), httpPushUriTargetBusy(false) + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, +@@ -526,6 +636,8 @@ class UpdateService : public Node + } + + private: ++ std::vector<std::string> httpPushUriTargets; ++ bool httpPushUriTargetBusy; + void doGet(crow::Response& res, const crow::Request&, + const std::vector<std::string>&) override + { +@@ -536,6 +648,8 @@ class UpdateService : public Node + res.jsonValue["Description"] = "Service for Software Update"; + res.jsonValue["Name"] = "Update Service"; + res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService"; ++ res.jsonValue["HttpPushUriTargets"] = httpPushUriTargets; ++ res.jsonValue["HttpPushUriTargetsBusy"] = httpPushUriTargetBusy; + // UpdateService cannot be disabled + res.jsonValue["ServiceEnabled"] = true; + res.jsonValue["FirmwareInventory"] = { +@@ -585,6 +699,31 @@ class UpdateService : public Node + "/xyz/openbmc_project/software/apply_time", + "org.freedesktop.DBus.Properties", "Get", + "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime"); ++ ++ // Get the ApplyOptions value ++ crow::connections::systemBus->async_method_call( ++ [aResp](const boost::system::error_code ec, ++ const std::variant<bool> applyOption) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG << "DBUS response error " << ec; ++ messages::internalError(aResp->res); ++ return; ++ } ++ ++ const bool* b = std::get_if<bool>(&applyOption); ++ ++ if (b) ++ { ++ aResp->res.jsonValue["Oem"]["ApplyOptions"]["@odata.type"] = ++ "#OemUpdateService.ApplyOptions"; ++ aResp->res.jsonValue["Oem"]["ApplyOptions"]["ClearConfig"] = ++ *b; ++ } ++ }, ++ "xyz.openbmc_project.Software.BMC.Updater", ++ "/xyz/openbmc_project/software", "org.freedesktop.DBus.Properties", ++ "Get", "xyz.openbmc_project.Software.ApplyOptions", "ClearConfig"); + } + + void doPatch(crow::Response& res, const crow::Request& req, +@@ -595,12 +734,61 @@ class UpdateService : public Node + std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); + + std::optional<nlohmann::json> pushUriOptions; +- if (!json_util::readJson(req, res, "HttpPushUriOptions", +- pushUriOptions)) ++ std::optional<std::vector<std::string>> imgTargets; ++ std::optional<bool> imgTargetBusy; ++ std::optional<nlohmann::json> oemProps; ++ ++ if (!json_util::readJson(req, res, "HttpPushUriOptions", pushUriOptions, ++ "HttpPushUriTargets", imgTargets, ++ "HttpPushUriTargetsBusy", imgTargetBusy, "Oem", ++ oemProps)) + { ++ BMCWEB_LOG_DEBUG << "UpdateService doPatch: Invalid request body"; + return; + } + ++ if (oemProps) ++ { ++ std::optional<nlohmann::json> applyOptions; ++ ++ if (!json_util::readJson(*oemProps, res, "ApplyOptions", ++ applyOptions)) ++ { ++ return; ++ } ++ ++ if (applyOptions) ++ { ++ std::optional<bool> clearConfig; ++ if (!json_util::readJson(*applyOptions, res, "ClearConfig", ++ clearConfig)) ++ { ++ return; ++ } ++ ++ if (clearConfig) ++ { ++ // Set the requested image apply time value ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "D-Bus responses error: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ messages::success(asyncResp->res); ++ }, ++ "xyz.openbmc_project.Software.BMC.Updater", ++ "/xyz/openbmc_project/software", ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Software.ApplyOptions", ++ "ClearConfig", std::variant<bool>{*clearConfig}); ++ } ++ } ++ } ++ + if (pushUriOptions) + { + std::optional<nlohmann::json> pushUriApplyTime; +@@ -665,6 +853,98 @@ class UpdateService : public Node + } + } + } ++ ++ if (imgTargetBusy) ++ { ++ if ((httpPushUriTargetBusy) && (*imgTargetBusy)) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Other client has reserved the HttpPushUriTargets " ++ "property for firmware updates."; ++ messages::resourceInUse(asyncResp->res); ++ return; ++ } ++ ++ if (imgTargets) ++ { ++ if (!(*imgTargetBusy)) ++ { ++ BMCWEB_LOG_DEBUG ++ << "UpdateService doPatch: httpPushUriTargetBusy " ++ "should be " ++ "true before setting httpPushUriTargets"; ++ messages::invalidObject(asyncResp->res, ++ "HttpPushUriTargetsBusy"); ++ return; ++ } ++ if ((*imgTargets).size() != 0) ++ { ++ // TODO: Now we support max one target becuase ++ // software-manager code support one activation per object. ++ // It will be enhanced to multiple targets for single image ++ // in future. For now, consider first target alone. ++ if ((*imgTargets).size() != 1) ++ { ++ messages::invalidObject(asyncResp->res, ++ "HttpPushUriTargets"); ++ return; ++ } ++ crow::connections::systemBus->async_method_call( ++ [this, asyncResp, uriTargets{*imgTargets}, ++ targetBusy{*imgTargetBusy}]( ++ const boost::system::error_code ec, ++ const std::vector<std::string> swInvPaths) { ++ if (ec) ++ { ++ return; ++ } ++ ++ bool swInvObjFound = false; ++ for (const std::string& path : swInvPaths) ++ { ++ std::size_t idPos = path.rfind("/"); ++ if ((idPos == std::string::npos) || ++ ((idPos + 1) >= path.size())) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_DEBUG ++ << "Can't parse firmware ID!!"; ++ return; ++ } ++ std::string swId = path.substr(idPos + 1); ++ ++ if (swId == uriTargets[0]) ++ { ++ swInvObjFound = true; ++ break; ++ } ++ } ++ if (!swInvObjFound) ++ { ++ messages::invalidObject(asyncResp->res, ++ "HttpPushUriTargets"); ++ return; ++ } ++ this->httpPushUriTargetBusy = targetBusy; ++ this->httpPushUriTargets = uriTargets; ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", ++ "/", static_cast<int32_t>(0), ++ std::array<const char*, 1>{versionIntf}); ++ } ++ else ++ { ++ httpPushUriTargetBusy = *imgTargetBusy; ++ httpPushUriTargets = *imgTargets; ++ } ++ } ++ else ++ { ++ httpPushUriTargetBusy = *imgTargetBusy; ++ } ++ } + } + + void doPost(crow::Response& res, const crow::Request& req, +@@ -675,8 +955,8 @@ class UpdateService : public Node + std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); + + // Setup callback for when new software detected +- monitorForSoftwareAvailable(asyncResp, req, +- "/redfish/v1/UpdateService"); ++ monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService", ++ httpPushUriTargets); + + std::string filepath( + "/tmp/images/" + +@@ -761,7 +1041,7 @@ class SoftwareInventoryCollection : public Node + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTree", + "/xyz/openbmc_project/software", static_cast<int32_t>(0), +- std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"}); ++ std::array<const char*, 1>{versionIntf}); + } + }; + +@@ -943,7 +1223,7 @@ class SoftwareInventory : public Node + }, + obj.second[0].first, obj.first, + "org.freedesktop.DBus.Properties", "GetAll", +- "xyz.openbmc_project.Software.Version"); ++ versionIntf); + } + if (!found) + { +@@ -964,8 +1244,7 @@ class SoftwareInventory : public Node + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", +- static_cast<int32_t>(0), +- std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"}); ++ static_cast<int32_t>(0), std::array<const char*, 1>{versionIntf}); + } + }; + +diff --git a/static/redfish/v1/$metadata/index.xml b/static/redfish/v1/$metadata/index.xml +index 514f3dd..c068d4f 100644 +--- a/static/redfish/v1/$metadata/index.xml ++++ b/static/redfish/v1/$metadata/index.xml +@@ -2142,6 +2142,9 @@ + <edmx:Reference Uri="/redfish/v1/schema/OemManager_v1.xml"> + <edmx:Include Namespace="OemManager"/> + </edmx:Reference> ++ <edmx:Reference Uri="/redfish/v1/schema/OemUpdateService_v1.xml"> ++ <edmx:Include Namespace="OemUpdateService"/> ++ </edmx:Reference> + <edmx:Reference Uri="/redfish/v1/schema/OemCrashdump_v1.xml"> + <edmx:Include Namespace="OemCrashdump.v1_0_0"/> + </edmx:Reference> +diff --git a/static/redfish/v1/JsonSchemas/OemUpdateService/index.json b/static/redfish/v1/JsonSchemas/OemUpdateService/index.json +new file mode 100644 +index 0000000..74e39cd +--- /dev/null ++++ b/static/redfish/v1/JsonSchemas/OemUpdateService/index.json +@@ -0,0 +1,69 @@ ++{ ++ "$id": "http://redfish.dmtf.org/schemas/v1/OemUpdateService.json", ++ "$schema": "http://redfish.dmtf.org/schemas/v1/redfish-schema-v1.json", ++ "copyright": "Copyright 2014-2019 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright", ++ "definitions": { ++ "ApplyOptions": { ++ "additionalProperties": false, ++ "description": "An indication by boolean value whether to update firmware configuration along with firmware image update.", ++ "patternProperties": { ++ "^([a-zA-Z_][a-zA-Z0-9_]*)?@(odata|Redfish|Message)\\.[a-zA-Z_][a-zA-Z0-9_]*$": { ++ "description": "This property shall specify a valid odata or Redfish property.", ++ "type": [ ++ "array", ++ "boolean", ++ "integer", ++ "number", ++ "null", ++ "object", ++ "string" ++ ] ++ } ++ }, ++ "properties": { ++ "ClearConfig": { ++ "description": "This indicates whether to update firmware configuration or not.", ++ "longDescription": "The value of this property is used to indicate the firmware configuration update.", ++ "readonly": false, ++ "type": [ ++ "boolean", ++ "null" ++ ] ++ } ++ }, ++ "type": "object" ++ }, ++ "Oem": { ++ "additionalProperties": true, ++ "description": "OemUpdateService Oem properties.", ++ "patternProperties": { ++ "^([a-zA-Z_][a-zA-Z0-9_]*)?@(odata|Redfish|Message)\\.[a-zA-Z_][a-zA-Z0-9_]*$": { ++ "description": "This property shall specify a valid odata or Redfish property.", ++ "type": [ ++ "array", ++ "boolean", ++ "integer", ++ "number", ++ "null", ++ "object", ++ "string" ++ ] ++ } ++ }, ++ "properties": { ++ "ApplyOptions": { ++ "anyOf": [ ++ { ++ "$ref": "#/definitions/ApplyOptions" ++ }, ++ { ++ "type": "null" ++ } ++ ] ++ } ++ }, ++ "type": "object" ++ } ++ }, ++ "title": "#OemUpdateService" ++} +diff --git a/static/redfish/v1/schema/OemUpdateService_v1.xml b/static/redfish/v1/schema/OemUpdateService_v1.xml +new file mode 100644 +index 0000000..cbb7aa4 +--- /dev/null ++++ b/static/redfish/v1/schema/OemUpdateService_v1.xml +@@ -0,0 +1,40 @@ ++<?xml version="1.0" encoding="UTF-8"?> ++<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0"> ++ <edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/errata03/csd01/complete/vocabularies/Org.OData.Core.V1.xml"> ++ <edmx:Include Namespace="Org.OData.Core.V1" Alias="OData" /> ++ </edmx:Reference> ++ <edmx:Reference Uri="http://redfish.dmtf.org/schemas/v1/RedfishExtensions_v1.xml"> ++ <edmx:Include Namespace="Validation.v1_0_0" Alias="Validation"/> ++ <edmx:Include Namespace="RedfishExtensions.v1_0_0" Alias="Redfish"/> ++ </edmx:Reference> ++ <edmx:Reference Uri="http://redfish.dmtf.org/schemas/v1/UpdateService_v1.xml"> ++ <edmx:Include Namespace="UpdateService"/> ++ <edmx:Include Namespace="UpdateService.v1_4_0"/> ++ </edmx:Reference> ++ <edmx:Reference Uri="http://redfish.dmtf.org/schemas/v1/Resource_v1.xml"> ++ <edmx:Include Namespace="Resource"/> ++ <edmx:Include Namespace="Resource.v1_0_0"/> ++ </edmx:Reference> ++ ++ <edmx:DataServices> ++ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="OemUpdateService"> ++ <ComplexType Name="Oem" BaseType="Resource.OemObject"> ++ <Annotation Term="OData.AdditionalProperties" Bool="true" /> ++ <Annotation Term="OData.Description" String="OemUpdateService Oem properties." /> ++ <Annotation Term="OData.AutoExpand"/> ++ <Property Name="ApplyOptions" Type="OemUpdateService.ApplyOptions"/> ++ </ComplexType> ++ ++ <ComplexType Name="ApplyOptions" BaseType="Resource.OemObject"> ++ <Annotation Term="OData.AdditionalProperties" Bool="false" /> ++ <Annotation Term="OData.Description" String="An indication by boolean value whether to update firmware configuration along with firmware image update." /> ++ <Property Name="ClearConfig" Type="Edm.Boolean"> ++ <Annotation Term="OData.Permissions" EnumMember="OData.Permission/ReadWrite"/> ++ <Annotation Term="OData.Description" String="This indicates whether to update firmware configuration or not."/> ++ <Annotation Term="OData.LongDescription" String="The value of this property is used to indicate the firmware configuration update."/> ++ </Property> ++ </ComplexType> ++ ++ </Schema> ++ </edmx:DataServices> ++</edmx:Edmx> +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0002-Change-InsertMedia-action-response-for-POST-in-proxy.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0002-Change-InsertMedia-action-response-for-POST-in-proxy.patch new file mode 100644 index 000000000..d04220b89 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0002-Change-InsertMedia-action-response-for-POST-in-proxy.patch @@ -0,0 +1,30 @@ +From dcc94627aac5b8e4ad181c8548391c53d27b8896 Mon Sep 17 00:00:00 2001 +From: Karol Wachowski <karol.wachowski@intel.com> +Date: Tue, 16 Feb 2021 06:47:11 +0000 +Subject: [PATCH] Change InsertMedia action response for POST in proxy mode + +Set boost::beast::http::status::method_not_allowed as a response +for POST request to Virtual Media Insert Media action to keep +consistency with other non existing requests. +--- + redfish-core/lib/virtual_media.hpp | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 80e7315..76e8c4a 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -611,10 +611,9 @@ class VirtualMediaActionInsertMedia : public Node + // Not possible in proxy mode + BMCWEB_LOG_DEBUG << "InsertMedia not " + "allowed in proxy mode"; +- messages::resourceNotFound( +- aResp->res, "VirtualMedia.InsertMedia", +- resName); +- ++ aResp->res.result( ++ boost::beast::http::status:: ++ method_not_allowed); + return; + } + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0002-Use-chip-id-based-UUID-for-Service-Root.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0002-Use-chip-id-based-UUID-for-Service-Root.patch new file mode 100644 index 000000000..02f843bb8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0002-Use-chip-id-based-UUID-for-Service-Root.patch @@ -0,0 +1,74 @@ +From 034920eca21bc25899565484928ee72025e21ff8 Mon Sep 17 00:00:00 2001 +From: Wiktor Golgowski <wiktor.golgowski@linux.intel.com> +Date: Thu, 30 Apr 2020 11:09:35 +0200 +Subject: [PATCH] Use chip id-based UUID for Service Root. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If the sysfs-provided chip id is available, it will be used as +payload to generate Service Root UUID from hardcoded namespace. + +Tested: +Generated UUID is consistent between BMC image reflashes. +If the sysfs node is not available, code falls back to randomly +generated UUID. + +Signed-off-by: Wiktor Gołgowski <wiktor.golgowski@linux.intel.com> +--- + include/persistent_data.hpp | 32 +++++++++++++++++++++++++++++--- + 1 file changed, 29 insertions(+), 3 deletions(-) + +diff --git a/include/persistent_data.hpp b/include/persistent_data.hpp +index 24f7afd..8826b06 100644 +--- a/include/persistent_data.hpp ++++ b/include/persistent_data.hpp +@@ -25,6 +25,10 @@ class ConfigFile + public: + // todo(ed) should read this from a fixed location somewhere, not CWD + static constexpr const char* filename = "bmcweb_persistent_data.json"; ++ static constexpr const char* chipIdSysfsNode = "/sys/devices/platform" ++ "/ahb/ahb:apb/1e6e2000.syscon/1e6e2000.syscon:misc_control/chip_id"; ++ static constexpr const char* UuidNs = "{b7b0553a-54cc-4162-982d-" ++ "944847ed76f5}"; + + ConfigFile() + { +@@ -144,9 +148,31 @@ class ConfigFile + + if (systemUuid.empty()) + { +- systemUuid = +- boost::uuids::to_string(boost::uuids::random_generator()()); +- needWrite = true; ++ // Try to retrieve chip id-based uuid. ++ std::ifstream chipIdFile(chipIdSysfsNode); ++ if (chipIdFile.is_open()) ++ { ++ std::string chipId; ++ std::getline(chipIdFile, chipId); ++ if (!chipId.empty()) ++ { ++ boost::uuids::name_generator_sha1 gen( ++ boost::uuids::string_generator()(UuidNs)); ++ systemUuid = boost::uuids::to_string(gen(chipId.c_str())); ++ needWrite = true; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "Cannot get chip id-based System UUID."; ++ } ++ } ++ // If the above fails, generate random uuid. ++ if (systemUuid.empty()) ++ { ++ systemUuid = ++ boost::uuids::to_string(boost::uuids::random_generator()()); ++ needWrite = true; ++ } + } + if (fileRevision < jsonRevision) + { +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0003-Set-Inserted-redfish-property-for-not-inserted-resou.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0003-Set-Inserted-redfish-property-for-not-inserted-resou.patch new file mode 100644 index 000000000..eaba041d5 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0003-Set-Inserted-redfish-property-for-not-inserted-resou.patch @@ -0,0 +1,40 @@ +From dab4adbf211b6867f86fcf6080b34a0e41f6f4a1 Mon Sep 17 00:00:00 2001 +From: Karol Wachowski <karol.wachowski@intel.com> +Date: Tue, 23 Feb 2021 15:53:16 +0000 +Subject: [PATCH] Set Inserted redfish property for not inserted resources + +Tested: Verified that Inserted property is returned and set to + "false" for not inserted media. +Signed-off-by: Karol Wachowski <karol.wachowski@intel.com> +--- + redfish-core/lib/virtual_media.hpp | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 188248a..f477f63 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -95,6 +95,7 @@ static void vmParseInterfaceObject(const DbusInterfaceType& interface, + BMCWEB_LOG_DEBUG << "Value Active not found"; + return; + } ++ aResp->res.jsonValue["Inserted"] = *activeValue; + + const std::string* endpointIdValue = + std::get_if<std::string>(&endpointIdProperty->second); +@@ -106,7 +107,6 @@ static void vmParseInterfaceObject(const DbusInterfaceType& interface, + aResp->res.jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] = + *endpointIdValue; + aResp->res.jsonValue["TransferProtocolType"] = "OEM"; +- aResp->res.jsonValue["Inserted"] = *activeValue; + if (*activeValue == true) + { + aResp->res.jsonValue["ConnectedVia"] = "Applet"; +@@ -137,7 +137,6 @@ static void vmParseInterfaceObject(const DbusInterfaceType& interface, + } + + aResp->res.jsonValue["Image"] = *imageUrlValue; +- aResp->res.jsonValue["Inserted"] = *activeValue; + aResp->res.jsonValue["TransferProtocolType"] = + getTransferProtocolTypeFromUri(*imageUrlValue); + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-bmcweb-handle-device-or-resource-busy-exception.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-bmcweb-handle-device-or-resource-busy-exception.patch new file mode 100644 index 000000000..01c1c858c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-bmcweb-handle-device-or-resource-busy-exception.patch @@ -0,0 +1,219 @@ +From 76480c6a5b1708113f28aecab32a85984371243c Mon Sep 17 00:00:00 2001 +From: Karol Wachowski <karol.wachowski@intel.com> +Date: Fri, 10 Jul 2020 09:54:06 +0000 +Subject: [PATCH] bmcweb handle device or resource busy exception + +Use async_method_call_timed() for mount/unmount dbus oprations. +Long mount/unmount times are supported by VirtualMedia service, +this works because of settable timeout property, available for each block +device. +Default dbus calls will timeout when mount/unmount timeout is long enough. + +Get mount/unmount timeout property and use it for mount/unmount calls. +Add handling of device or resource busy exception (EBUSY) that +can be thrown by VirtualMedia service during Mount/Unmount dbus operations. + +Tested: Verified that after mounting non-existing HTTPS resource + in proxy mode, VirtualMedia recovers restoring ready state + and returns EBUSY during that transition. + Verfied that resources can be mounted/unmounted in both legacy + and proxy mode. +Signed-off-by: Karol Wachowski <karol.wachowski@intel.com> +Change-Id: Ica62c34db0cce24c4c6169fc661edfde49e948d0 +--- + redfish-core/lib/virtual_media.hpp | 144 ++++++++++++++++++++++------- + 1 file changed, 110 insertions(+), 34 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 95a8881..188248a 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -24,6 +24,8 @@ + #include <account_service.hpp> + #include <boost/url/url_view.hpp> + ++#include <chrono> ++ + namespace redfish + + { +@@ -160,6 +162,26 @@ static void vmParseInterfaceObject(const DbusInterfaceType& interface, + } + } + ++/** ++ * @brief parses Timeout property and converts to microseconds ++ */ ++static std::optional<uint64_t> ++ vmParseTimeoutProperty(const std::variant<int>& timeoutProperty) ++{ ++ const int* timeoutValue = std::get_if<int>(&timeoutProperty); ++ if (timeoutValue) ++ { ++ constexpr int timeoutMarginSeconds = 10; ++ return std::chrono::duration_cast<std::chrono::microseconds>( ++ std::chrono::seconds(*timeoutValue + timeoutMarginSeconds)) ++ .count(); ++ } ++ else ++ { ++ return std::nullopt; ++ } ++} ++ + /** + * @brief Fill template for Virtual Media Item. + */ +@@ -856,22 +878,54 @@ class VirtualMediaActionInsertMedia : public Node + } + + crow::connections::systemBus->async_method_call( +- [asyncResp, secretPipe](const boost::system::error_code ec, +- bool success) { ++ [asyncResp, service, name, imageUrl, rw, unixFd, ++ secretPipe](const boost::system::error_code ec, ++ const std::variant<int> timeoutProperty) { + if (ec) + { + BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; + messages::internalError(asyncResp->res); ++ return; + } +- else if (!success) ++ ++ auto timeout = vmParseTimeoutProperty(timeoutProperty); ++ if (timeout == std::nullopt) + { +- BMCWEB_LOG_ERROR << "Service responded with error"; +- messages::generalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "Timeout property is empty."; ++ messages::internalError(asyncResp->res); ++ return; + } ++ ++ crow::connections::systemBus->async_method_call_timed( ++ [asyncResp, secretPipe](const boost::system::error_code ec, ++ bool success) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Bad D-Bus request error: " ++ << ec; ++ if (ec == ++ boost::system::errc::device_or_resource_busy) ++ { ++ messages::resourceInUse(asyncResp->res); ++ } ++ else ++ { ++ messages::internalError(asyncResp->res); ++ } ++ } ++ else if (!success) ++ { ++ BMCWEB_LOG_ERROR << "Service responded with error"; ++ messages::generalError(asyncResp->res); ++ } ++ }, ++ service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, ++ "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", ++ *timeout, imageUrl, rw, unixFd); + }, + service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, +- "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw, +- unixFd); ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.VirtualMedia.MountPoint", "Timeout"); + } + }; + +@@ -1003,38 +1057,60 @@ class VirtualMediaActionEjectMedia : public Node + const std::string& service, const std::string& name, + bool legacy) + { +- +- // Legacy mount requires parameter with image ++ std::string objectPath = "/xyz/openbmc_project/VirtualMedia/"; ++ std::string ifaceName = "xyz.openbmc_project.VirtualMedia"; + if (legacy) + { +- crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; +- +- messages::internalError(asyncResp->res); +- return; +- } +- }, +- service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, +- "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); ++ objectPath += "Legacy/"; ++ ifaceName += ".Legacy"; + } +- else // proxy ++ else + { +- crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; +- +- messages::internalError(asyncResp->res); +- return; +- } +- }, +- service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, +- "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); ++ objectPath += "Proxy/"; ++ ifaceName += ".Proxy"; + } ++ objectPath += name; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, service, name, objectPath, ++ ifaceName](const boost::system::error_code ec, ++ const std::variant<int> timeoutProperty) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ auto timeout = vmParseTimeoutProperty(timeoutProperty); ++ if (timeout == std::nullopt) ++ { ++ BMCWEB_LOG_ERROR << "Timeout property is empty."; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ crow::connections::systemBus->async_method_call_timed( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Bad D-Bus request error: " ++ << ec; ++ if (ec == ++ boost::system::errc::device_or_resource_busy) ++ { ++ messages::resourceInUse(asyncResp->res); ++ } ++ else ++ { ++ messages::internalError(asyncResp->res); ++ } ++ return; ++ } ++ }, ++ service, objectPath, ifaceName, "Unmount", *timeout); ++ }, ++ service, objectPath, "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.VirtualMedia.MountPoint", "Timeout"); + } + }; + +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch new file mode 100644 index 000000000..54f00aa39 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch @@ -0,0 +1,547 @@ +From d340953bc925ff8535c5a8fac54db24b243ba8ad Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Mon, 19 Oct 2020 13:21:42 +0530 +Subject: [PATCH] EventService: https client support + +Add https client support for push style +eventing. Using this BMC can push the event +logs/telemetry data to event listener over +secure http channel. + +Tested: + - Created subscription with https destination + url. Using SubmitTestEvent action set the + event and can see event on event listener. + - Validator passed. + +Change-Id: I44c3918b39baa2eb5fddda9d635f99aa280a422a +Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com> +Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com> +--- + http/http_client.hpp | 370 +++++++++++++----- + .../include/event_service_manager.hpp | 2 +- + 2 files changed, 267 insertions(+), 105 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 5c7b13f..d782dee 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -31,12 +31,17 @@ namespace crow + { + + static constexpr uint8_t maxRequestQueueSize = 50; ++static constexpr unsigned int httpReadBodyLimit = 1024; + + enum class ConnState + { + initialized, ++ resolveInProgress, ++ resolveFailed, ++ resolved, + connectInProgress, + connectFailed, ++ sslHandshakeInProgress, + connected, + sendInProgress, + sendFailed, +@@ -50,53 +55,124 @@ enum class ConnState + class HttpClient : public std::enable_shared_from_this<HttpClient> + { + private: ++ boost::asio::ip::tcp::resolver resolver; ++ boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client}; + boost::beast::tcp_stream conn; ++ std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream&>> sslConn; + boost::asio::steady_timer timer; +- boost::beast::flat_buffer buffer; ++ boost::beast::flat_static_buffer<httpReadBodyLimit> buffer; ++ std::optional< ++ boost::beast::http::response_parser<boost::beast::http::string_body>> ++ parser; + boost::beast::http::request<boost::beast::http::string_body> req; +- boost::beast::http::response<boost::beast::http::string_body> res; + boost::asio::ip::tcp::resolver::results_type endpoint; +- std::vector<std::pair<std::string, std::string>> headers; ++ boost::beast::http::fields fields; + std::queue<std::string> requestDataQueue; +- ConnState state; + std::string subId; + std::string host; + std::string port; + std::string uri; ++ bool useSsl; + uint32_t retryCount; + uint32_t maxRetryAttempts; + uint32_t retryIntervalSecs; + std::string retryPolicyAction; + bool runningTimer; ++ ConnState state; ++ ++ void doResolve() ++ { ++ BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port; ++ if (state == ConnState::resolveInProgress) ++ { ++ return; ++ } ++ state = ConnState::resolveInProgress; ++ // TODO: Use async_resolver. boost asio example ++ // code as is crashing with async_resolve(). ++ try ++ { ++ endpoint = resolver.resolve(host, port); ++ } ++ catch (const std::exception& e) ++ { ++ BMCWEB_LOG_ERROR << "Failed to resolve hostname: " << host << " - " ++ << e.what(); ++ state = ConnState::resolveFailed; ++ checkQueue(); ++ return; ++ } ++ state = ConnState::resolved; ++ checkQueue(); ++ } + + void doConnect() + { +- if (state == ConnState::connectInProgress) ++ if (useSsl) ++ { ++ sslConn.emplace(conn, ctx); ++ } ++ ++ if ((state == ConnState::connectInProgress) || ++ (state == ConnState::sslHandshakeInProgress)) + { + return; + } + state = ConnState::connectInProgress; + + BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; +- // Set a timeout on the operation ++ ++ auto respHandler = ++ [self(shared_from_this())](const boost::beast::error_code ec, ++ const boost::asio::ip::tcp::resolver:: ++ results_type::endpoint_type& ep) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Connect " << ep ++ << " failed: " << ec.message(); ++ self->state = ConnState::connectFailed; ++ self->checkQueue(); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "Connected to: " << ep; ++ if (self->sslConn) ++ { ++ self->performHandshake(); ++ } ++ else ++ { ++ self->state = ConnState::connected; ++ self->checkQueue(); ++ } ++ }; ++ + conn.expires_after(std::chrono::seconds(30)); +- conn.async_connect(endpoint, [self(shared_from_this())]( +- const boost::beast::error_code& ec, +- const boost::asio::ip::tcp::resolver:: +- results_type::endpoint_type& ep) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Connect " << ep +- << " failed: " << ec.message(); +- self->state = ConnState::connectFailed; +- self->checkQueue(); +- return; +- } +- self->state = ConnState::connected; +- BMCWEB_LOG_DEBUG << "Connected to: " << ep; ++ conn.async_connect(endpoint, std::move(respHandler)); ++ } ++ ++ void performHandshake() ++ { ++ if (state == ConnState::sslHandshakeInProgress) ++ { ++ return; ++ } ++ state = ConnState::sslHandshakeInProgress; ++ ++ sslConn->async_handshake( ++ boost::asio::ssl::stream_base::client, ++ [self(shared_from_this())](const boost::beast::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "SSL handshake failed: " ++ << ec.message(); ++ self->doCloseAndCheckQueue(ConnState::connectFailed); ++ return; ++ } ++ self->state = ConnState::connected; ++ BMCWEB_LOG_DEBUG << "SSL Handshake successfull"; + +- self->checkQueue(); +- }); ++ self->checkQueue(); ++ }); + } + + void sendMessage(const std::string& data) +@@ -107,100 +183,170 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + state = ConnState::sendInProgress; + +- BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; ++ BMCWEB_LOG_DEBUG << host << ":" << port; + +- req.version(static_cast<int>(11)); // HTTP 1.1 +- req.target(uri); +- req.method(boost::beast::http::verb::post); +- +- // Set headers +- for (const auto& [key, value] : headers) ++ req = {}; ++ for (const auto& field : fields) + { +- req.set(key, value); ++ req.set(field.name_string(), field.value()); + } + req.set(boost::beast::http::field::host, host); ++ req.set(boost::beast::http::field::content_type, "text/plain"); ++ ++ req.version(static_cast<int>(11)); // HTTP 1.1 ++ req.target(uri); ++ req.method(boost::beast::http::verb::post); + req.keep_alive(true); + + req.body() = data; + req.prepare_payload(); + +- // Set a timeout on the operation +- conn.expires_after(std::chrono::seconds(30)); ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code ec, ++ const std::size_t& bytesTransferred) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message(); ++ self->doCloseAndCheckQueue(ConnState::sendFailed); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); + +- // Send the HTTP request to the remote host +- boost::beast::http::async_write( +- conn, req, +- [self(shared_from_this())](const boost::beast::error_code& ec, +- const std::size_t& bytesTransferred) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "sendMessage() failed: " +- << ec.message(); +- self->state = ConnState::sendFailed; +- self->checkQueue(); +- return; +- } +- BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " +- << bytesTransferred; +- boost::ignore_unused(bytesTransferred); ++ self->recvMessage(); ++ }; + +- self->recvMessage(); +- }); ++ conn.expires_after(std::chrono::seconds(30)); ++ if (sslConn) ++ { ++ boost::beast::http::async_write(*sslConn, req, ++ std::move(respHandler)); ++ } ++ else ++ { ++ boost::beast::http::async_write(conn, req, std::move(respHandler)); ++ } + } + + void recvMessage() + { +- // Receive the HTTP response +- boost::beast::http::async_read( +- conn, buffer, res, +- [self(shared_from_this())](const boost::beast::error_code& ec, +- const std::size_t& bytesTransferred) { ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code ec, ++ const std::size_t& bytesTransferred) { ++ if (ec && ec != boost::beast::http::error::partial_message) ++ { ++ BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); ++ self->doCloseAndCheckQueue(ConnState::recvFailed); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); ++ ++ // TODO: check for return status code and perform ++ // retry if fails(Ex: 40x). Take action depending on ++ // retry policy. ++ BMCWEB_LOG_DEBUG << "recvMessage() data: " ++ << self->parser->get().body(); ++ ++ // Send is successful, Lets remove data from queue ++ // check for next request data in queue. ++ self->requestDataQueue.pop(); ++ ++ // Transfer ownership of the response ++ self->parser->release(); ++ ++ // TODO: Implement the keep-alive connections. ++ // Most of the web servers close connection abruptly ++ // and might be reason due to which its observed that ++ // stream_truncated(Next read) or partial_message ++ // errors. So for now, closing connection and re-open ++ // for all cases. ++ self->doCloseAndCheckQueue(ConnState::closed); ++ }; ++ ++ parser.emplace(std::piecewise_construct, std::make_tuple()); ++ parser->body_limit(httpReadBodyLimit); ++ // Since these are all push style eventing, we are not ++ // bothered about response parsing. ++ parser->skip(true); ++ buffer.consume(buffer.size()); ++ ++ conn.expires_after(std::chrono::seconds(30)); ++ if (sslConn) ++ { ++ boost::beast::http::async_read(*sslConn, buffer, *parser, ++ std::move(respHandler)); ++ } ++ else ++ { ++ boost::beast::http::async_read(conn, buffer, *parser, ++ std::move(respHandler)); ++ } ++ } ++ ++ void doCloseAndCheckQueue(const ConnState setState = ConnState::closed) ++ { ++ if (sslConn) ++ { ++ conn.expires_after(std::chrono::seconds(30)); ++ sslConn->async_shutdown([self = shared_from_this(), ++ setState{std::move(setState)}]( ++ const boost::system::error_code ec) { + if (ec) + { +- BMCWEB_LOG_ERROR << "recvMessage() failed: " +- << ec.message(); +- self->state = ConnState::recvFailed; +- self->checkQueue(); +- return; ++ // Many https server closes connection abruptly ++ // i.e witnout close_notify. More details are at ++ // https://github.com/boostorg/beast/issues/824 ++ if (ec == boost::asio::ssl::error::stream_truncated) ++ { ++ BMCWEB_LOG_ERROR ++ << "doCloseAndCheckQueue(): Connection " ++ "closed by server. "; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "doCloseAndCheckQueue() failed: " ++ << ec.message(); ++ } + } +- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " +- << bytesTransferred; +- boost::ignore_unused(bytesTransferred); +- +- // Discard received data. We are not interested. +- BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res; +- +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- self->requestDataQueue.pop(); +- self->state = ConnState::idle; ++ else ++ { ++ BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; ++ } ++ self->conn.cancel(); ++ self->state = setState; + self->checkQueue(); + }); +- } +- +- void doClose() +- { +- boost::beast::error_code ec; +- conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); +- +- state = ConnState::closed; +- // not_connected happens sometimes so don't bother reporting it. +- if (ec && ec != boost::beast::errc::not_connected) ++ } ++ else + { +- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); +- return; ++ boost::beast::error_code ec; ++ conn.expires_after(std::chrono::seconds(30)); ++ conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ++ ec); ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "doCloseAndCheckQueue() failed: " ++ << ec.message(); ++ } ++ else ++ { ++ BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; ++ } ++ ++ conn.close(); ++ state = setState; ++ checkQueue(); + } +- BMCWEB_LOG_DEBUG << "Connection closed gracefully"; ++ return; + } + + void checkQueue(const bool newRecord = false) + { + if (requestDataQueue.empty()) + { +- // TODO: Having issue in keeping connection alive. So lets close if +- // nothing to be transferred. +- doClose(); +- + BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n"; + return; + } +@@ -232,6 +378,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + } + + if ((state == ConnState::connectFailed) || ++ (state == ConnState::resolveFailed) || + (state == ConnState::sendFailed) || + (state == ConnState::recvFailed)) + { +@@ -256,14 +403,18 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + << " seconds. RetryCount = " << retryCount; + timer.expires_after(std::chrono::seconds(retryIntervalSecs)); + timer.async_wait( +- [self = shared_from_this()](const boost::system::error_code&) { ++ [self = shared_from_this()](boost::system::error_code) { + self->runningTimer = false; + self->connStateCheck(); + }); + return; + } +- // reset retry count. +- retryCount = 0; ++ ++ if (state == ConnState::idle) ++ { ++ // State idle means, previous attempt is successful. ++ retryCount = 0; ++ } + connStateCheck(); + + return; +@@ -273,15 +424,21 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + { + switch (state) + { ++ case ConnState::initialized: ++ case ConnState::resolveFailed: ++ case ConnState::connectFailed: ++ doResolve(); ++ break; + case ConnState::connectInProgress: ++ case ConnState::resolveInProgress: ++ case ConnState::sslHandshakeInProgress: + case ConnState::sendInProgress: + case ConnState::suspended: + case ConnState::terminated: + // do nothing + break; +- case ConnState::initialized: + case ConnState::closed: +- case ConnState::connectFailed: ++ case ConnState::resolved: + case ConnState::sendFailed: + case ConnState::recvFailed: + { +@@ -297,22 +454,22 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + sendMessage(data); + break; + } ++ default: ++ break; + } + } + + public: + explicit HttpClient(boost::asio::io_context& ioc, const std::string& id, + const std::string& destIP, const std::string& destPort, +- const std::string& destUri) : +- conn(ioc), +- timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri), +- retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0), +- retryPolicyAction("TerminateAfterRetries"), runningTimer(false) +- { +- boost::asio::ip::tcp::resolver resolver(ioc); +- endpoint = resolver.resolve(host, port); +- state = ConnState::initialized; +- } ++ const std::string& destUri, ++ const bool inUseSsl = true) : ++ resolver(ioc), ++ conn(ioc), timer(ioc), subId(id), host(destIP), port(destPort), ++ uri(destUri), useSsl(inUseSsl), retryCount(0), maxRetryAttempts(5), ++ retryPolicyAction("TerminateAfterRetries"), runningTimer(false), ++ state(ConnState::initialized) ++ {} + + void sendData(const std::string& data) + { +@@ -337,7 +494,12 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + void setHeaders( + const std::vector<std::pair<std::string, std::string>>& httpHeaders) + { +- headers = httpHeaders; ++ // Set headers ++ for (const auto& [key, value] : httpHeaders) ++ { ++ // TODO: Validate the header fileds before assign. ++ fields.set(key, value); ++ } + } + + void setRetryConfig(const uint32_t retryAttempts, +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 54dafb4..f68ae1d 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -387,7 +387,7 @@ class Subscription + { + conn = std::make_shared<crow::HttpClient>( + crow::connections::systemBus->get_io_context(), id, host, port, +- path); ++ path, (uriProto == "https" ? true : false)); + } + + Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) : +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Define-Redfish-interface-Registries-Bios.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Define-Redfish-interface-Registries-Bios.patch new file mode 100644 index 000000000..b2627644b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Define-Redfish-interface-Registries-Bios.patch @@ -0,0 +1,850 @@ +From c645c011bb3ea2a2aaf52560cb9fcc461d048cae Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Fri, 4 Sep 2020 19:24:25 +0800 +Subject: [PATCH] Define Redfish interface "/Registries/Bios" and enable + Attributes property + +1. Define Redfish interface "/Registries/Bios" for BIOS Attribute Registry + RBC Daemon provide method to get BIOS attribute registry. +2. Eanble Attributes property for BIOS resource +3. Define Redfish interface "/Systems/system/Bios/Settings" for BIOS +settings +4. RBC daemon is at +https://gerrit.openbmc-project.xyz/#/c/openbmc/bios-settings-mgr/+/35563/ +5. IPMI command implementation is at +https://gerrit.openbmc-project.xyz/#/c/openbmc/intel-ipmi-oem/+/30827/ +6. Property design is at +https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/xyz/openbmc_project/BIOSConfig +7. Design doc is at +https://github.com/openbmc/docs/blob/master/designs/remote-bios-configuration.md +8. There will be 95 test cases for this feature in the validation team. + +Tested: + +1. Use postman (Redfish tool) could get all the attributes in bios +resouce, get bios settings, get bios attribute +registry. +https://IP_ADDR/redfish/v1/Systems/system/Bios +{ + "@Redfish.Settings": { + "@odata.type": "#Settings.v1_3_0.Settings", + "SettingsObject": { + "@odata.id": "/redfish/v1/Systems/system/Bios/Settings" + } + }, + "@odata.id": "/redfish/v1/Systems/system/Bios", + "@odata.type": "#Bios.v1_1_0.Bios", + "Actions": { + "#Bios.ChangePassword": { + "target": "/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword" + }, + "#Bios.ResetBios": { + "target": "/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios" + } + }, + "AttributeRegistry": "BiosAttributeRegistry", + "Attributes": { + "attr0": "current value" + }, + "Description": "BIOS Configuration Service", + "Id": "BIOS", + "Links": { + "ActiveSoftwareImage": { + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/bios_active" + }, + "SoftwareImages": [ + { + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/bios_active" + } + ], + "SoftwareImages@odata.count": 1 + }, + "Name": "BIOS Configuration" +} + +Redfish interface: https://BMCIP/redfish/v1/Registries/BiosAttributeRegistry +{ + "@odata.id": "/redfish/v1/Registries/BiosAttributeRegistry", + "@odata.type": "#MessageRegistryFile.v1_1_0.MessageRegistryFile", + "Description": "BiosAttributeRegistry Message Registry File Location", + "Id": "BiosAttributeRegistry", + "Languages": [ + "en" + ], + "Languages@odata.count": 1, + "Location": [ + { + "Language": "en", + "Uri": "/redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry" + } + ], + "Location@odata.count": 1, + "Name": "BiosAttributeRegistry Message Registry File", + "Registry": "BiosAttributeRegistry.1.0.0" +} + +Redfish interface: https://BMCIP/redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry +{ + "@odata.id": "/redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry", + "@odata.type": "#AttributeRegistry.v1_3_2.AttributeRegistry", + "Id": "BiosAttributeRegistry", + "Language": "en", + "Name": "Bios Attribute Registry", + "OwningEntity": "OpenBMC", + "RegistryEntries": { + "Attributes": [ + { + "AttributeName": "attr0", + "CurrentValue": "current value", + "DefaultValue": "default value", + "DisplayName": "display name for attr0", + "HelpText": "description for attr0", + "MenuPath": "./menu/path/for/attr0", + "ReadOnly": false, + "Type": "String", + "Value": [] + } + ] + }, + "RegistryVersion": "1.0.0" +} + +https://BMC_IPADDR/redfish/v1/Systems/system/Bios/Settings +{ + "@odata.id": "/redfish/v1/Systems/system/Bios/Settings", + "@odata.type": "#Bios.v1_1_0.Bios", + "AttributeRegistry": "BiosAttributeRegistry", + "Attributes": { + "QuietBoot": "0x0" + }, + "Id": "BiosSettingsV1", + "Name": "Bios Settings Version 1" +} + +2. Passed Validator check for bios resource and bios attribute registry +*** /redfish/v1/Systems/system/Bios +INFO - Type (#Bios.v1_1_0.Bios), GET SUCCESS (time: 1.57377) +INFO - PASS +*** /redfish/v1/Registries/BiosAttributeRegistry +INFO - Type (#MessageRegistryFile.v1_1_0.MessageRegistryFile), GET SUCCESS (time: 0.075438) +INFO - PASS +INFO - +*** /redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry +INFO - Type (#AttributeRegistry.v1_3_2.AttributeRegistry), GET SUCCESS (time: 0.075751) +INFO - PASS + +@odata.id /redfish/v1/Systems/system/Bios odata Exists PASS +@odata.type #Settings.v1_3_0.Settings odata Exists PASS +Links [JSON Object] Bios.v1_1_0.Links Yes complex +Links.ActiveSoftwareImage Link: /redfish/v1/UpdateService/FirmwareInventory/bios_active link to: SoftwareInventory Yes PASS +Links.SoftwareImages Array (size: 1) array of: SoftwareInventory Yes ... +Links.SoftwareImages[0] Link: /redfish/v1/UpdateService/FirmwareInventory/bios_active SoftwareInventory Yes PASS +Links.Oem - Resource.Oem No Optional +SoftwareImages@odata.count 1 odata Exists PASS +AttributeRegistry BiosAttributeRegistry string Yes PASS +Actions [JSON Object] Bios.v1_0_0.Actions Yes complex +Actions.#Bios.ResetBios Action - Yes PASS +Actions.#Bios.ChangePassword Action - Yes PASS +Attributes [JSON Object] Bios.v1_0_0.Attributes Yes complex +Attributes.attr0 current value primitive Yes PASS +Id BIOS string Yes PASS +Description BIOS Configuration Service string Yes PASS +Name BIOS Configuration string Yes PASS +Oem - Resource.Oem No Optional +@Redfish.Settings [JSON Object] Settings.Settings Yes complex +@Redfish.Settings.MaintenanceWindowResource - link to: ItemOrCollection No Optional +@Redfish.Settings.SupportedApplyTimes - string (enum) No Optional +@Redfish.Settings.Time - date No Optional +@Redfish.Settings.ETag - string No Optional +@Redfish.Settings.SettingsObject Link: /redfish/v1/Systems/system/Bios/Settings link to: Item Yes PASS +@Redfish.Settings.Messages - Message No Optional + +@odata.id /redfish/v1/Registries/BiosAttributeRegistry odata Exists PASS +@odata.type #MessageRegistryFile.v1_1_0.MessageRegistryFile odata Exists PASS +Languages@odata.count 1 odata Exists PASS +Location@odata.count 1 odata Exists PASS +Actions - MessageRegistryFile.v1_1_0.Actions No Optional +Languages Array (size: 1) string Yes ... +Languages[0] en string Yes PASS +Registry BiosAttributeRegistry.1.0.0 string Yes PASS +Location Array (size: 1) array of: Location Yes ... +Location[0] [JSON Object] Location Yes complex +Location[0].Language en string Yes PASS +Location[0].Uri /redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry string Yes PASS +Location[0].ArchiveUri - string No Optional +Location[0].PublicationUri - string No Optional +Location[0].ArchiveFile - string No Optional +Id BiosAttributeRegistry string Yes PASS +Description BiosAttributeRegistry Message Registry File Location string Yes PASS +Name BiosAttributeRegistry Message Registry File string Yes PASS +Oem - Resource.Oem No Optional + +@odata.id /redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry odata Exists PASS +@odata.type #AttributeRegistry.v1_3_2.AttributeRegistry odata Exists PASS +Actions - AttributeRegistry.v1_1_0.Actions No Optional +Language en string Yes PASS +RegistryVersion 1.0.0 string Yes PASS +OwningEntity OpenBMC string Yes PASS +SupportedSystems - SupportedSystems No Optional +RegistryEntries [JSON Object] AttributeRegistry.v1_0_0.RegistryEntries Yes complex +RegistryEntries.Attributes Array (size: 1) array of: Attributes Yes ... +RegistryEntries.Attributes[0] [JSON Object] Attributes Yes complex +RegistryEntries.Attributes[0].Oem - Resource.Oem No Optional +RegistryEntries.Attributes[0].ResetRequired - boolean No Optional +RegistryEntries.Attributes[0].UefiDevicePath - string No Optional +RegistryEntries.Attributes[0].UefiKeywordName - string No Optional +RegistryEntries.Attributes[0].UefiNamespaceId - string No Optional +RegistryEntries.Attributes[0].AttributeName attr0 string Yes PASS +RegistryEntries.Attributes[0].Type String string (enum) Yes PASS +RegistryEntries.Attributes[0].Value Array (size: 0) array of: AttributeValue Yes ... +RegistryEntries.Attributes[0].DisplayName display name for attr0 string Yes PASS +RegistryEntries.Attributes[0].HelpText description for attr0 string Yes PASS +RegistryEntries.Attributes[0].WarningText - string No Optional +RegistryEntries.Attributes[0].CurrentValue current value primitive Yes PASS +RegistryEntries.Attributes[0].DefaultValue default value primitive Yes PASS +RegistryEntries.Attributes[0].DisplayOrder - number No Optional +RegistryEntries.Attributes[0].MenuPath ./menu/path/for/attr0 string Yes PASS +RegistryEntries.Attributes[0].ReadOnly False boolean Yes PASS +RegistryEntries.Attributes[0].WriteOnly - boolean No Optional +RegistryEntries.Attributes[0].GrayOut - boolean No Optional +RegistryEntries.Attributes[0].Hidden - boolean No Optional +RegistryEntries.Attributes[0].Immutable - boolean No Optional +RegistryEntries.Attributes[0].IsSystemUniqueProperty - boolean No Optional +RegistryEntries.Attributes[0].MaxLength - number No Optional +RegistryEntries.Attributes[0].MinLength - number No Optional +RegistryEntries.Attributes[0].ScalarIncrement - number No Optional +RegistryEntries.Attributes[0].UpperBound - number No Optional +RegistryEntries.Attributes[0].LowerBound - number No Optional +RegistryEntries.Attributes[0].ValueExpression - string No Optional +RegistryEntries.Menus - Menus No Optional +RegistryEntries.Dependencies - Dependencies No Optional +Id BiosAttributeRegistry string Yes PASS +Description - string No Optional +Name Bios Attribute Registry string Yes PASS +Oem - Resource.Oem No Optional + +Change-Id: Iecc61018c350f0b8c89df59b2864b941508b1916 +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> +--- + redfish-core/include/redfish.hpp | 2 + + .../include/registries/bios_registry.hpp | 31 ++ + redfish-core/lib/bios.hpp | 503 ++++++++++++++++++ + redfish-core/lib/message_registries.hpp | 9 +- + 4 files changed, 544 insertions(+), 1 deletion(-) + create mode 100644 redfish-core/include/registries/bios_registry.hpp + +diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp +index 5d5eb7b..a8e5cf2 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -157,6 +157,8 @@ class RedfishService + nodes.emplace_back(std::make_unique<SystemActionsReset>(app)); + nodes.emplace_back(std::make_unique<SystemResetActionInfo>(app)); + nodes.emplace_back(std::make_unique<BiosService>(app)); ++ nodes.emplace_back(std::make_unique<BiosSettings>(app)); ++ nodes.emplace_back(std::make_unique<BiosAttributeRegistry>(app)); + nodes.emplace_back(std::make_unique<BiosReset>(app)); + #ifdef BMCWEB_ENABLE_VM_NBDPROXY + nodes.emplace_back(std::make_unique<VirtualMedia>(app)); +diff --git a/redfish-core/include/registries/bios_registry.hpp b/redfish-core/include/registries/bios_registry.hpp +new file mode 100644 +index 0000000..88ef782 +--- /dev/null ++++ b/redfish-core/include/registries/bios_registry.hpp +@@ -0,0 +1,31 @@ ++/* ++// Copyright (c) 2020 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 ++ ++namespace redfish::message_registries::bios ++{ ++const Header header = { ++ "Copyright 2020 OpenBMC. All rights reserved.", ++ "#MessageRegistry.v1_4_0.MessageRegistry", ++ "BiosAttributeRegistry.1.0.0", ++ "Bios Attribute Registry", ++ "en", ++ "This registry defines the messages for bios attribute registry.", ++ "BiosAttributeRegistry", ++ "1.0.0", ++ "OpenBMC", ++}; ++} // namespace redfish::message_registries::bios +\ No newline at end of file +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 2c31077..5f8c91b 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -3,8 +3,140 @@ + #include "node.hpp" + + #include <utils/fw_utils.hpp> ++ + namespace redfish + { ++ ++/*baseBIOSTable ++map{attributeName,struct{attributeType,readonlyStatus,displayname, ++ description,menuPath,current,default, ++ array{struct{optionstring,optionvalue}}}} ++*/ ++using BiosBaseTableType = std::vector<std::pair< ++ std::string, ++ std::tuple< ++ std::string, bool, std::string, std::string, std::string, ++ std::variant<int64_t, std::string>, std::variant<int64_t, std::string>, ++ std::vector< ++ std::tuple<std::string, std::variant<int64_t, std::string>>>>>>; ++using BiosBaseTableItemType = std::pair< ++ std::string, ++ std::tuple< ++ std::string, bool, std::string, std::string, std::string, ++ std::variant<int64_t, std::string>, std::variant<int64_t, std::string>, ++ std::vector< ++ std::tuple<std::string, std::variant<int64_t, std::string>>>>>; ++using OptionsItemType = ++ std::tuple<std::string, std::variant<int64_t, std::string>>; ++ ++enum BiosBaseTableIndex ++{ ++ biosBaseAttrType = 0, ++ biosBaseReadonlyStatus, ++ biosBaseDisplayName, ++ biosBaseDescription, ++ biosBaseMenuPath, ++ biosBaseCurrValue, ++ biosBaseDefaultValue, ++ biosBaseOptions ++}; ++enum OptionsItemIndex ++{ ++ optItemType = 0, ++ optItemValue ++}; ++/* ++ The Pending attribute name and new value. ++ ex- { {"QuietBoot",Type.Integer, 0x1}, ++ { "DdrFreqLimit",Type.String,"2933"} ++ } ++*/ ++using PendingAttributesType = std::vector<std::pair< ++ std::string, std::tuple<std::string, std::variant<int64_t, std::string>>>>; ++using PendingAttributesItemType = ++ std::pair<std::string, ++ std::tuple<std::string, std::variant<int64_t, std::string>>>; ++enum PendingAttributesIndex ++{ ++ pendingAttrType = 0, ++ pendingAttrValue ++}; ++static std::string mapAttrTypeToRedfish(const std::string_view typeDbus) ++{ ++ std::string ret; ++ if (typeDbus == "xyz.openbmc_project.BIOSConfig.Manager." ++ "AttributeType.Enumeration") ++ { ++ ret = "Enumeration"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.String") ++ { ++ ret = "String"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.Password") ++ { ++ ret = "Password"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.Integer") ++ { ++ ret = "Integer"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.Boolean") ++ { ++ ret = "Boolean"; ++ } ++ else ++ { ++ ret = "UNKNOWN"; ++ } ++ ++ return ret; ++} ++static std::string mapBoundTypeToRedfish(const std::string_view typeDbus) ++{ ++ std::string ret; ++ if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.ScalarIncrement") ++ { ++ ret = "ScalarIncrement"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.LowerBound") ++ { ++ ret = "LowerBound"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.UpperBound") ++ { ++ ret = "UpperBound"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.MinStringLength") ++ { ++ ret = "MinStringLength"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.MaxStringLength") ++ { ++ ret = "MaxStringLength"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.OneOf") ++ { ++ ret = "OneOf"; ++ } ++ else ++ { ++ ret = "UNKNOWN"; ++ } ++ ++ return ret; ++} ++ + /** + * BiosService class supports handle get method for bios. + */ +@@ -35,6 +167,377 @@ class BiosService : public Node + // Get the ActiveSoftwareImage and SoftwareImages + fw_util::populateFirmwareInformation(asyncResp, fw_util::biosPurpose, + "", true); ++ asyncResp->res.jsonValue["@Redfish.Settings"] = { ++ {"@odata.type", "#Settings.v1_3_0.Settings"}, ++ {"SettingsObject", ++ {{"@odata.id", "/redfish/v1/Systems/system/Bios/Settings"}}}}; ++ asyncResp->res.jsonValue["AttributeRegistry"] = "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["Attributes"] = {}; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ const std::string& service = getObjectType.begin()->first; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const std::variant<BiosBaseTableType>& retBiosTable) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "getBiosAttributes DBUS error: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ const BiosBaseTableType* baseBiosTable = ++ std::get_if<BiosBaseTableType>(&retBiosTable); ++ nlohmann::json& attributesJson = ++ asyncResp->res.jsonValue["Attributes"]; ++ if (baseBiosTable == nullptr) ++ { ++ BMCWEB_LOG_ERROR << "baseBiosTable == nullptr "; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ for (const BiosBaseTableItemType& item : *baseBiosTable) ++ { ++ const std::string& key = item.first; ++ const std::string& itemType = ++ std::get<biosBaseAttrType>(item.second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<biosBaseCurrValue>( ++ item.second)); ++ attributesJson.emplace(key, currValue != nullptr ++ ? *currValue ++ : ""); ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = std::get_if<int64_t>( ++ &std::get<biosBaseCurrValue>(item.second)); ++ attributesJson.emplace( ++ key, currValue != nullptr ? *currValue : 0); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "Unsupported attribute type."; ++ messages::internalError(asyncResp->res); ++ } ++ } ++ }, ++ service, "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.BIOSConfig.Manager", "BaseBIOSTable"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/bios_config/manager", ++ std::array<const char*, 0>()); ++ } ++}; ++ ++/** ++ * BiosSettings class supports handle GET/PATCH method for ++ * BIOS configuration pending settings. ++ */ ++class BiosSettings : public Node ++{ ++ public: ++ BiosSettings(App& app) : ++ Node(app, "/redfish/v1/Systems/system/Bios/Settings") ++ { ++ entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ asyncResp->res.jsonValue["@odata.id"] = ++ "/redfish/v1/Systems/system/Bios/Settings"; ++ asyncResp->res.jsonValue["@odata.type"] = "#Bios.v1_1_0.Bios"; ++ asyncResp->res.jsonValue["Name"] = "Bios Settings Version 1"; ++ asyncResp->res.jsonValue["Id"] = "BiosSettingsV1"; ++ asyncResp->res.jsonValue["AttributeRegistry"] = "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["Attributes"] = {}; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ std::string service = getObjectType.begin()->first; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const std::variant<PendingAttributesType>& ++ retPendingAttributes) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "getBiosSettings DBUS error: " ++ << ec; ++ messages::resourceNotFound(asyncResp->res, ++ "Systems/system/Bios", ++ "Settings"); ++ return; ++ } ++ const PendingAttributesType* pendingAttributes = ++ std::get_if<PendingAttributesType>( ++ &retPendingAttributes); ++ nlohmann::json& attributesJson = ++ asyncResp->res.jsonValue["Attributes"]; ++ if (pendingAttributes == nullptr) ++ { ++ BMCWEB_LOG_ERROR << "pendingAttributes == nullptr "; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ for (const PendingAttributesItemType& item : ++ *pendingAttributes) ++ { ++ const std::string& key = item.first; ++ const std::string& itemType = ++ std::get<pendingAttrType>(item.second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<pendingAttrValue>( ++ item.second)); ++ attributesJson.emplace(key, currValue != nullptr ++ ? *currValue ++ : ""); ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = std::get_if<int64_t>( ++ &std::get<pendingAttrValue>(item.second)); ++ attributesJson.emplace( ++ key, currValue != nullptr ? *currValue : 0); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "Unsupported attribute type."; ++ messages::internalError(asyncResp->res); ++ } ++ } ++ }, ++ service, "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.BIOSConfig.Manager", ++ "PendingAttributes"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/bios_config/manager", ++ std::array<const char*, 0>()); ++ } ++}; ++/** ++ * BiosAttributeRegistry class supports handle get method for BIOS attribute ++ * registry. ++ */ ++class BiosAttributeRegistry : public Node ++{ ++ public: ++ BiosAttributeRegistry(App& app) : ++ Node(app, "/redfish/v1/Registries/BiosAttributeRegistry/" ++ "BiosAttributeRegistry") ++ { ++ entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ asyncResp->res.jsonValue["@odata.id"] = ++ "/redfish/v1/Registries/BiosAttributeRegistry/" ++ "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#AttributeRegistry.v1_3_2.AttributeRegistry"; ++ asyncResp->res.jsonValue["Name"] = "Bios Attribute Registry"; ++ asyncResp->res.jsonValue["Id"] = "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["RegistryVersion"] = "1.0.0"; ++ asyncResp->res.jsonValue["Language"] = "en"; ++ asyncResp->res.jsonValue["OwningEntity"] = "OpenBMC"; ++ asyncResp->res.jsonValue["RegistryEntries"]["Attributes"] = ++ nlohmann::json::array(); ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ std::string service = getObjectType.begin()->first; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const std::variant<BiosBaseTableType>& retBiosTable) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR ++ << "getBiosAttributeRegistry DBUS error: " ++ << ec; ++ messages::resourceNotFound( ++ asyncResp->res, "Registries/Bios", "Bios"); ++ return; ++ } ++ const BiosBaseTableType* baseBiosTable = ++ std::get_if<BiosBaseTableType>(&retBiosTable); ++ nlohmann::json& attributeArray = ++ asyncResp->res ++ .jsonValue["RegistryEntries"]["Attributes"]; ++ nlohmann::json optionsArray = nlohmann::json::array(); ++ if (baseBiosTable == nullptr) ++ { ++ BMCWEB_LOG_ERROR << "baseBiosTable == nullptr "; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ for (const BiosBaseTableItemType& item : *baseBiosTable) ++ { ++ const std::string& itemType = ++ std::get<biosBaseAttrType>(item.second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ if (attrType == "UNKNOWN") ++ { ++ BMCWEB_LOG_ERROR << "attrType == UNKNOWN"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ nlohmann::json attributeItem; ++ attributeItem["AttributeName"] = item.first; ++ attributeItem["Type"] = attrType; ++ attributeItem["ReadOnly"] = ++ std::get<biosBaseReadonlyStatus>(item.second); ++ attributeItem["DisplayName"] = ++ std::get<biosBaseDisplayName>(item.second); ++ attributeItem["HelpText"] = ++ std::get<biosBaseDescription>(item.second); ++ attributeItem["MenuPath"] = ++ std::get<biosBaseMenuPath>(item.second); ++ ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<biosBaseCurrValue>( ++ item.second)); ++ const std::string* defValue = ++ std::get_if<std::string>( ++ &std::get<biosBaseDefaultValue>( ++ item.second)); ++ attributeItem["CurrentValue"] = ++ currValue != nullptr ? *currValue : ""; ++ attributeItem["DefaultValue"] = ++ defValue != nullptr ? *defValue : ""; ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = std::get_if<int64_t>( ++ &std::get<biosBaseCurrValue>(item.second)); ++ const int64_t* defValue = std::get_if<int64_t>( ++ &std::get<biosBaseDefaultValue>( ++ item.second)); ++ attributeItem["CurrentValue"] = ++ currValue != nullptr ? *currValue : 0; ++ attributeItem["DefaultValue"] = ++ defValue != nullptr ? *defValue : 0; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "Unsupported attribute type."; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::vector<OptionsItemType>& optionsVector = ++ std::get<biosBaseOptions>(item.second); ++ for (const OptionsItemType& optItem : optionsVector) ++ { ++ nlohmann::json optItemJson; ++ const std::string& strOptItemType = ++ std::get<optItemType>(optItem); ++ std::string optItemTypeRedfish = ++ mapBoundTypeToRedfish(strOptItemType); ++ if (optItemTypeRedfish == "UNKNOWN") ++ { ++ BMCWEB_LOG_ERROR ++ << "optItemTypeRedfish == UNKNOWN"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ if (optItemTypeRedfish == "OneOf") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<optItemValue>(optItem)); ++ optItemJson[optItemTypeRedfish] = ++ currValue != nullptr ? *currValue : ""; ++ } ++ else ++ { ++ const int64_t* currValue = ++ std::get_if<int64_t>( ++ &std::get<optItemValue>(optItem)); ++ optItemJson[optItemTypeRedfish] = ++ currValue != nullptr ? *currValue : 0; ++ } ++ ++ optionsArray.push_back(optItemJson); ++ } ++ ++ attributeItem["Value"] = optionsArray; ++ attributeArray.push_back(attributeItem); ++ } ++ }, ++ service, "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.BIOSConfig.Manager", "BaseBIOSTable"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/bios_config/manager", ++ std::array<const char*, 0>()); + } + }; + /** +diff --git a/redfish-core/lib/message_registries.hpp b/redfish-core/lib/message_registries.hpp +index 77fc10e..0caf01c 100644 +--- a/redfish-core/lib/message_registries.hpp ++++ b/redfish-core/lib/message_registries.hpp +@@ -18,6 +18,7 @@ + #include "node.hpp" + #include "registries.hpp" + #include "registries/base_message_registry.hpp" ++#include "registries/bios_registry.hpp" + #include "registries/openbmc_message_registry.hpp" + #include "registries/resource_event_message_registry.hpp" + #include "registries/task_event_message_registry.hpp" +@@ -56,11 +57,12 @@ class MessageRegistryFileCollection : public Node + {"@odata.id", "/redfish/v1/Registries"}, + {"Name", "MessageRegistryFile Collection"}, + {"Description", "Collection of MessageRegistryFiles"}, +- {"Members@odata.count", 4}, ++ {"Members@odata.count", 5}, + {"Members", + {{{"@odata.id", "/redfish/v1/Registries/Base"}}, + {{"@odata.id", "/redfish/v1/Registries/TaskEvent"}}, + {{"@odata.id", "/redfish/v1/Registries/ResourceEvent"}}, ++ {{"@odata.id", "/redfish/v1/Registries/BiosAttributeRegistry"}}, + {{"@odata.id", "/redfish/v1/Registries/OpenBMC"}}}}}; + + res.end(); +@@ -118,6 +120,11 @@ class MessageRegistryFile : public Node + header = &message_registries::resource_event::header; + url = message_registries::resource_event::url; + } ++ else if (registry == "BiosAttributeRegistry") ++ { ++ header = &message_registries::bios::header; ++ dmtf.clear(); ++ } + else + { + messages::resourceNotFound( +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0007-BIOS-config-Add-support-for-PATCH-operation.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0007-BIOS-config-Add-support-for-PATCH-operation.patch new file mode 100644 index 000000000..6f3794478 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0007-BIOS-config-Add-support-for-PATCH-operation.patch @@ -0,0 +1,153 @@ +From ad2b1c83bd9cb1bb6eb86bebd1867b0172e5a7a8 Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Wed, 23 Dec 2020 16:50:45 +0800 +Subject: [PATCH] BaseBiosTable: Add support for PATCH operation + +This commit brings in support for PATCH operation of the +bios variables that updates the BaseBiosTable. + +Tested-By: +* Passed Redfish validator + +* Single Attribute: +PATCH https://${bmc}/redfish/v1/Systems/system/Bios/Settings -d +'{"data":[{"AttributeName": <attribute name>, "AttributeType": +<attribute type>, "AttributeValue": <attribute value>}]}' + +* Multiple Attributes: +PATCH https://${bmc}/redfish/v1/Systems/system/Bios/Settings -d +'{"data":[{"AttributeName": <attribute name>, "AttributeType": +<attribute type>, "AttributeValue": <attribute value>}, +{"AttributeName": <attribute name>, "AttributeType": +<attribute type>, "AttributeValue": <attribute value>}]}' + +This makes use of the "Set" of "PendingAttributes" in the +backend and that updates the BaseBiosTable. + +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> +--- + redfish-core/lib/bios.hpp | 94 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 93 insertions(+), 1 deletion(-) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 5f8c91b..cf76fe0 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -96,6 +96,29 @@ static std::string mapAttrTypeToRedfish(const std::string_view typeDbus) + + return ret; + } ++static std::string mapRedfishToAttrType(const std::string_view type) ++{ ++ std::string ret; ++ if (type == "string") ++ { ++ ret = "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String"; ++ } ++ else if (type == "int") ++ { ++ ret = "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.Integer"; ++ } ++ else if (type == "enum") ++ { ++ ret = "xyz.openbmc_project.BIOSConfig.Manager.AttributeType." ++ "Enumeration"; ++ } ++ else ++ { ++ ret = "UNKNOWN"; ++ } ++ ++ return ret; ++} + static std::string mapBoundTypeToRedfish(const std::string_view typeDbus) + { + std::string ret; +@@ -262,7 +285,9 @@ class BiosSettings : public Node + BiosSettings(App& app) : + Node(app, "/redfish/v1/Systems/system/Bios/Settings") + { +- entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}}}; ++ entityPrivileges = { ++ {boost::beast::http::verb::get, {{"Login"}}}, ++ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}}; + } + + private: +@@ -359,6 +384,73 @@ class BiosSettings : public Node + "/xyz/openbmc_project/bios_config/manager", + std::array<const char*, 0>()); + } ++ ++ void doPatch(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ ++ nlohmann::json inpJson; ++ ++ if (!redfish::json_util::readJson(req, asyncResp->res, "data", inpJson)) ++ { ++ return; ++ } ++ ++ for (auto& attrInfo : inpJson) ++ { ++ std::optional<std::string> attrName; ++ std::optional<std::string> attrType; ++ std::optional<std::string> attrValue; ++ if (!json_util::getValueFromJsonObject(attrInfo, "AttributeName", ++ attrName)) ++ { ++ messages::propertyMissing(asyncResp->res, "AttributeName"); ++ return; ++ } ++ if (!json_util::getValueFromJsonObject(attrInfo, "AttributeType", ++ attrType)) ++ { ++ messages::propertyMissing(asyncResp->res, "AttributeType"); ++ return; ++ } ++ if (!json_util::getValueFromJsonObject(attrInfo, "AttributeValue", ++ attrValue)) ++ { ++ messages::propertyMissing(asyncResp->res, "AttributeValue"); ++ return; ++ } ++ std::string biosAttrType = mapRedfishToAttrType(*attrType); ++ ++ if (biosAttrType == "UNKNOWN") ++ { ++ BMCWEB_LOG_ERROR << "Invalid attribute type"; ++ messages::propertyValueNotInList(asyncResp->res, ++ "AttributeType", *attrType); ++ return; ++ } ++ ++ PendingAttributesType pendingAttributes; ++ pendingAttributes.emplace_back(std::make_pair( ++ *attrName, std::make_tuple(biosAttrType, *attrValue))); ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "doPatch resp_handler got error " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ }, ++ "xyz.openbmc_project.BIOSConfigManager", ++ "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.BIOSConfig.Manager", "PendingAttributes", ++ std::variant<PendingAttributesType>(pendingAttributes)); ++ } ++ } + }; + /** + * BiosAttributeRegistry class supports handle get method for BIOS attribute +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0008-Add-support-to-ResetBios-action.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0008-Add-support-to-ResetBios-action.patch new file mode 100644 index 000000000..7e4e2e8d8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0008-Add-support-to-ResetBios-action.patch @@ -0,0 +1,62 @@ +From a78eecb032eefeb84da3ec042700a40f55ae8f10 Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Wed, 23 Dec 2020 22:47:56 +0800 +Subject: [PATCH] Add support to ResetBios action + +Tested: + +Bios reset flag can be modified throw redfish +POST https://IP_ADDR/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios + +Change-Id: I5e5fbdd70d4a3ce3b976cc2eb0a7d9a2a3adb124 +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> + +--- + redfish-core/lib/bios.hpp | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index cf76fe0..7b6fc3d 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -643,7 +643,7 @@ class BiosReset : public Node + Node(app, "/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios/") + { + entityPrivileges = { +- {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; ++ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; + } + + private: +@@ -655,19 +655,23 @@ class BiosReset : public Node + const std::vector<std::string>&) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); +- ++ std::string resetFlag = ++ "xyz.openbmc_project.BIOSConfig.Manager.ResetFlag.FactoryDefaults"; + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec) { + if (ec) + { +- BMCWEB_LOG_ERROR << "Failed to reset bios: " << ec; ++ BMCWEB_LOG_ERROR << "doPost bios reset got error " << ec; + messages::internalError(asyncResp->res); + return; + } ++ BMCWEB_LOG_DEBUG << "bios reset action is done"; + }, +- "org.open_power.Software.Host.Updater", +- "/xyz/openbmc_project/software", +- "xyz.openbmc_project.Common.FactoryReset", "Reset"); ++ "xyz.openbmc_project.BIOSConfigManager", ++ "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.BIOSConfig.Manager", "ResetBIOSSettings", ++ std::variant<std::string>(resetFlag)); + } + }; + } // namespace redfish +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0009-Add-support-to-ChangePassword-action.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0009-Add-support-to-ChangePassword-action.patch new file mode 100644 index 000000000..976292197 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0009-Add-support-to-ChangePassword-action.patch @@ -0,0 +1,139 @@ +From ede8454491b554c2494a61f42993fa2e39b4d865 Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Wed, 23 Dec 2020 14:41:23 +0800 +Subject: [PATCH] Add support to ChangePassword action + +Tested: + +Passed Redfish validator. +Bios change password: +root@intel-obmc:~# cat /var/lib/bios-settings-manager/seedData +{ +"UserPwdHash": "08D91157785366CDC3AA64D87E5E3C621EDAB13E26B6E484397EBA5E459E54C567BF5B1FFB36A43B6142B18F8D642E9D", +"AdminPwdHash": "08D91157785366CDC3AA64D87E5E3C621EDAB13E26B6E484397EBA5E459E54C567BF5B1FFB36A43B6142B18F8D642E9D", +"Seed": "123456", +"HashAlgo": "SHA384" +} +POST https://IP_ADDR/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword +{ + "NewPassword": "12345678", + "OldPassword": "1234567890", + "PasswordName": "Administrator" +} +root@intel-obmc:~# cat /var/lib/bios-settings-manager/passwordData +{ + "CurrentPassword": "1234567890", + "IsAdminPwdChanged": 1, + "IsUserPwdChanged": 0, + "NewPassword": "2DD65D57EB60B1D92C5F3D2DC84724FCEE7BC02E57AA75E834712266ED94CAC704047B2FF7CEC1C36BED280B36BB5AC6", + "UserName": "Administrator" +} + +Change-Id: I90319a68da0b0a7f9c5cd65a8cb8cf52269a5f52 +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> +--- + redfish-core/include/redfish.hpp | 1 + + redfish-core/lib/bios.hpp | 70 ++++++++++++++++++++++++++++++++ + 2 files changed, 71 insertions(+) + +diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp +index a8e5cf2..dabf78e 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -160,6 +160,7 @@ class RedfishService + nodes.emplace_back(std::make_unique<BiosSettings>(app)); + nodes.emplace_back(std::make_unique<BiosAttributeRegistry>(app)); + nodes.emplace_back(std::make_unique<BiosReset>(app)); ++ nodes.emplace_back(std::make_unique<BiosChangePassword>(app)); + #ifdef BMCWEB_ENABLE_VM_NBDPROXY + nodes.emplace_back(std::make_unique<VirtualMedia>(app)); + nodes.emplace_back(std::make_unique<VirtualMediaCollection>(app)); +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 7b6fc3d..61b396b 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -186,6 +186,9 @@ class BiosService : public Node + asyncResp->res.jsonValue["Actions"]["#Bios.ResetBios"] = { + {"target", + "/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios"}}; ++ asyncResp->res.jsonValue["Actions"]["#Bios.ChangePassword"] = { ++ {"target", ++ "/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword"}}; + + // Get the ActiveSoftwareImage and SoftwareImages + fw_util::populateFirmwareInformation(asyncResp, fw_util::biosPurpose, +@@ -674,4 +677,71 @@ class BiosReset : public Node + std::variant<std::string>(resetFlag)); + } + }; ++ ++/** ++ * BiosChangePassword class supports handle POST method for change bios ++ * password. The class retrieves and sends data directly to D-Bus. ++ */ ++class BiosChangePassword : public Node ++{ ++ public: ++ BiosChangePassword(App& app) : ++ Node(app, ++ "/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword/") ++ { ++ entityPrivileges = { ++ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; ++ } ++ ++ private: ++ /** ++ * Function handles POST method request. ++ * Analyzes POST body message before sends Reset request data to D-Bus. ++ */ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ std::string currentPassword, newPassword, userName; ++ if (!json_util::readJson(req, res, "NewPassword", newPassword, ++ "OldPassword", currentPassword, "PasswordName", ++ userName)) ++ { ++ return; ++ } ++ if (currentPassword.empty()) ++ { ++ messages::actionParameterUnknown(asyncResp->res, "ChangePassword", ++ "OldPassword"); ++ return; ++ } ++ if (newPassword.empty()) ++ { ++ messages::actionParameterUnknown(asyncResp->res, "ChangePassword", ++ "NewPassword"); ++ return; ++ } ++ if (userName.empty()) ++ { ++ messages::actionParameterUnknown(asyncResp->res, "ChangePassword", ++ "PasswordName"); ++ return; ++ } ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_CRITICAL << "Failed in doPost(BiosChangePassword) " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ }, ++ "xyz.openbmc_project.BIOSConfigPassword", ++ "/xyz/openbmc_project/bios_config/password", ++ "xyz.openbmc_project.BIOSConfig.Password", "ChangePassword", ++ userName, currentPassword, newPassword); ++ } ++}; ++ + } // namespace redfish +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch new file mode 100644 index 000000000..a9c46f487 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch @@ -0,0 +1,57 @@ +From a76314cd29f5cbcf19142b7120c5bf83358910fd Mon Sep 17 00:00:00 2001 +From: Jayaprakash Mutyala <mutyalax.jayaprakash@intel.com> +Date: Mon, 28 Dec 2020 18:55:57 +0000 +Subject: [PATCH] managers: add attributes for Manager.CommandShell + +Issue: ConnectTypesSupported, ServiceEnabled and + MaxConcurrentSessions Attributes are missing for + Manager.CommandShell, though Requirement mandates it. + +Fix: Added missing attributes to Manager.CommandShell + +Tested: +1. Verified redfish validator passed +2. Get bmc details from Redfish +Redfish URI: https://<BMC IP>/redfish/v1/Managers/bmc +Response: +{ + "@odata.id": "/redfish/v1/Managers/bmc", + "@odata.type": "#Manager.v1_9_0.Manager", +.... +.... + "CommandShell": { + "ConnectTypesSupported": [ + "SSH", + "IPMI" + ], + "MaxConcurrentSessions": 4, + "ServiceEnabled": true + }, +.... +.... + +Signed-off-by: Jayaprakash Mutyala <mutyalax.jayaprakash@intel.com> +--- + redfish-core/lib/managers.hpp | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp +index 6347caf..c401ca9 100644 +--- a/redfish-core/lib/managers.hpp ++++ b/redfish-core/lib/managers.hpp +@@ -1767,6 +1767,12 @@ class Manager : public Node + res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15; + res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI", + "SSH"}; ++ // Fill in CommandShell info ++ res.jsonValue["CommandShell"]["ServiceEnabled"] = true; ++ res.jsonValue["CommandShell"]["MaxConcurrentSessions"] = 4; ++ res.jsonValue["CommandShell"]["ConnectTypesSupported"] = {"SSH", ++ "IPMI"}; ++ + #ifdef BMCWEB_ENABLE_KVM + // Fill in GraphicalConsole info + res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true; +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0034-recommended-fixes-by-crypto-review-team.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0034-recommended-fixes-by-crypto-review-team.patch new file mode 100644 index 000000000..5ffc259c0 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0034-recommended-fixes-by-crypto-review-team.patch @@ -0,0 +1,75 @@ +From aaaa117817687a05284f8bfff07e2404e0d616b7 Mon Sep 17 00:00:00 2001 +From: Radivoje Jovanovic <radivoje.jovanovic@intel.com> +Date: Thu, 10 Dec 2020 13:42:20 -0800 +Subject: [PATCH] recommended fixes by crypto review team + +some curves/cyphers are forbiden to be used by +Intel crypto team. +Only enable approved ones. +the patch was created by aleksandr.v.tereschenko@intel.com + +Signed-off-by: Radivoje Jovanovic <radivoje.jovanovic@intel.com> +--- + include/ssl_key_handler.hpp | 39 ++++++++++++++++++++----------------- + 1 file changed, 21 insertions(+), 18 deletions(-) + +diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp +index 39e83d7..8de7349 100644 +--- a/include/ssl_key_handler.hpp ++++ b/include/ssl_key_handler.hpp +@@ -381,31 +381,34 @@ inline std::shared_ptr<boost::asio::ssl::context> + mSslContext->use_private_key_file(sslPemFile, + boost::asio::ssl::context::pem); + +- // Set up EC curves to auto (boost asio doesn't have a method for this) +- // There is a pull request to add this. Once this is included in an asio +- // drop, use the right way +- // http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue +- if (SSL_CTX_set_ecdh_auto(mSslContext->native_handle(), 1) != 1) ++ std::string handshakeCurves = "P-384:P-521:X448"; ++ if (SSL_CTX_set1_groups_list(mSslContext->native_handle(), handshakeCurves.c_str()) != 1) + { +- BMCWEB_LOG_ERROR << "Error setting tmp ecdh list\n"; ++ BMCWEB_LOG_ERROR << "Error setting ECDHE group list\n"; + } + +- std::string mozillaModern = "ECDHE-ECDSA-AES256-GCM-SHA384:" +- "ECDHE-RSA-AES256-GCM-SHA384:" +- "ECDHE-ECDSA-CHACHA20-POLY1305:" +- "ECDHE-RSA-CHACHA20-POLY1305:" +- "ECDHE-ECDSA-AES128-GCM-SHA256:" +- "ECDHE-RSA-AES128-GCM-SHA256:" +- "ECDHE-ECDSA-AES256-SHA384:" +- "ECDHE-RSA-AES256-SHA384:" +- "ECDHE-ECDSA-AES128-SHA256:" +- "ECDHE-RSA-AES128-SHA256"; ++ std::string tls12Ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384:" ++ "ECDHE-RSA-AES256-GCM-SHA384"; ++ std::string tls13Ciphers = "TLS_AES_256_GCM_SHA384"; + + if (SSL_CTX_set_cipher_list(mSslContext->native_handle(), +- mozillaModern.c_str()) != 1) ++ tls12Ciphers.c_str()) != 1) + { +- BMCWEB_LOG_ERROR << "Error setting cipher list\n"; ++ BMCWEB_LOG_ERROR << "Error setting TLS 1.2 cipher list\n"; + } ++ ++ if (SSL_CTX_set_ciphersuites(mSslContext->native_handle(), ++ tls13Ciphers.c_str()) != 1) ++ { ++ BMCWEB_LOG_ERROR << "Error setting TLS 1.3 cipher list\n"; ++ } ++ ++ if ((SSL_CTX_set_options(mSslContext->native_handle(), ++ SSL_OP_CIPHER_SERVER_PREFERENCE) & SSL_OP_CIPHER_SERVER_PREFERENCE) == 0) ++ { ++ BMCWEB_LOG_ERROR << "Error setting TLS server preference option\n"; ++ } ++ + return mSslContext; + } + } // namespace ensuressl +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0037-Add-state-sensor-messages-to-the-registry.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0037-Add-state-sensor-messages-to-the-registry.patch new file mode 100644 index 000000000..b171a8b2c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0037-Add-state-sensor-messages-to-the-registry.patch @@ -0,0 +1,98 @@ +From df571ddf0596f73c0318da3a90b9813e6df19dd9 Mon Sep 17 00:00:00 2001 +From: "Arun P. Mohanan" <arun.p.m@linux.intel.com> +Date: Wed, 27 Jan 2021 18:22:58 +0530 +Subject: [PATCH] Add state sensor messages to the registry + +Add messages to registry to indicate state sensor state change. + +Tested: +Build and redfish validator passes. +Logged these events and confirmed that they appear as expected on +Redfish. +GET: https://<BMC IP>/redfish/v1/Systems/system/LogServices/EventLog/Entries/1612528180 +{ + "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/1612528180", + "@odata.type": "#LogEntry.v1_4_0.LogEntry", + "Created": "2021-02-05T12:29:40+00:00", + "EntryType": "Event", + "Id": "1612528180", + "Message": "Operational Fault Status of Card_health_1 state sensor changed from Error to Normal.", + "MessageArgs": [ + "Operational Fault Status", + "Card_health_1", + "Error", + "Normal" + ], + "MessageId": "OpenBMC.0.1.StateSensorNormal", + "Name": "System Event Log Entry", + "Severity": "OK" +} + +Signed-off-by: Arun P. Mohanan <arun.p.m@linux.intel.com> +--- + .../registries/openbmc_message_registry.hpp | 36 +++++++++++++++++-- + 1 file changed, 34 insertions(+), 2 deletions(-) + +diff --git a/redfish-core/include/registries/openbmc_message_registry.hpp b/redfish-core/include/registries/openbmc_message_registry.hpp +index 5eb9380..dbea97c 100644 +--- a/redfish-core/include/registries/openbmc_message_registry.hpp ++++ b/redfish-core/include/registries/openbmc_message_registry.hpp +@@ -29,7 +29,7 @@ const Header header = { + "0.1.0", + "OpenBMC", + }; +-constexpr std::array<MessageEntry, 187> registry = { ++constexpr std::array<MessageEntry, 190> registry = { + MessageEntry{ + "ADDDCCorrectable", + { +@@ -2318,6 +2318,39 @@ constexpr std::array<MessageEntry, 187> registry = { + {}, + "None.", + }}, ++ MessageEntry{ ++ "StateSensorNormal", ++ { ++ "Indicates that a state sensor has changed state to normal.", ++ "%1 of %2 state sensor changed from %3 to %4.", ++ "OK", ++ "OK", ++ 4, ++ {"string", "string", "string", "string"}, ++ "None.", ++ }}, ++ MessageEntry{ ++ "StateSensorWarning", ++ { ++ "Indicates that a state sensor has changed state to warning.", ++ "%1 of %2 state sensor changed from %3 to %4.", ++ "Warning", ++ "Warning", ++ 4, ++ {"string", "string", "string", "string"}, ++ "Check sensor subsystem for errors.", ++ }}, ++ MessageEntry{ ++ "StateSensorCritical", ++ { ++ "Indicates that a state sensor has changed state to critical.", ++ "%1 of %2 state sensor changed from %3 to %4.", ++ "Critical", ++ "Critical", ++ 4, ++ {"string", "string", "string", "string"}, ++ "Check sensor subsystem for errors.", ++ }}, + MessageEntry{"SystemInterfaceDisabledProvisioned", + { + "Indicates that the system interface is in the disabled " +@@ -2410,6 +2443,5 @@ constexpr std::array<MessageEntry, 187> registry = { + {"string"}, + "None.", + }}, +- + }; + } // namespace redfish::message_registries::openbmc +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/bmcweb.socket b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/bmcweb.socket new file mode 100644 index 000000000..8782e4dd3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/bmcweb.socket @@ -0,0 +1,9 @@ +[Unit] +Description=BMC Webserver socket + +[Socket] +ListenStream=443 +ReusePort=true + +[Install] +WantedBy=sockets.target diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch new file mode 100644 index 000000000..fd7e8a445 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch @@ -0,0 +1,658 @@ +From bc1635622e122f307fb3b8eb9bbd66ea576a8f0c Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" <jozef.wludzik@intel.com> +Date: Mon, 18 May 2020 11:56:57 +0200 +Subject: [PATCH 2/4] Add POST and DELETE in MetricReportDefinitions + +Added POST action in MetricReportDefinitions node to allow user +to add new MetricReportDefinition. Using minimal set of +MetricReportDefinition parameters from user bmcweb converts it to +DBus call "AddReport" to Telemetry that serves as a backend +for Redfish TelemetryService. +Added DELETE request in MetricReportDefinitions node to allow user +to remove report from Telemetry. +Added conversion from string that represents duration format into +its numeric equivalent. +Added unit tests for conversion from and to Duration format. + +Tested: + - Tested using witherspoon image on QEMU + - Verified POST action in different cases: + - all parameters are provided, new report is added to collection + - some parameters are missing or invalid, user gets response with + description of the issue + - Verified that reports are removed on DELETE request + - Verified that on invalid DELETE request user receives response + with error + - Verified time_utils::fromDurationString() + - Succesfully passed RedfishServiceValidator.py + +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a +--- + meson.build | 1 + + redfish-core/include/utils/time_utils.hpp | 138 ++++++++++- + redfish-core/lib/metric_report_definition.hpp | 328 ++++++++++++++++++++++++++ + redfish-core/ut/time_utils_test.cpp | 63 +++++ + 4 files changed, 528 insertions(+), 2 deletions(-) + create mode 100644 redfish-core/ut/time_utils_test.cpp + +diff --git a/meson.build b/meson.build +index 7a16e91..3d65b01 100644 +--- a/meson.build ++++ b/meson.build +@@ -330,6 +330,7 @@ srcfiles_bmcweb = ['src/webserver_main.cpp','redfish-core/src/error_messages.cpp + srcfiles_unittest = ['include/ut/dbus_utility_test.cpp', + 'redfish-core/ut/privileges_test.cpp', + 'redfish-core/ut/lock_test.cpp', ++ 'redfish-core/ut/time_utils_test.cpp', + 'http/ut/utility_test.cpp'] + + # Gather the Configuration data +diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp +index dd4ea75..e94801b 100644 +--- a/redfish-core/include/utils/time_utils.hpp ++++ b/redfish-core/include/utils/time_utils.hpp +@@ -1,7 +1,13 @@ + #pragma once + ++#include "logging.hpp" ++ ++#include <charconv> + #include <chrono> ++#include <cmath> ++#include <optional> + #include <string> ++#include <system_error> + + namespace redfish + { +@@ -12,6 +18,8 @@ namespace time_utils + namespace details + { + ++using Days = std::chrono::duration<long long, std::ratio<24 * 60 * 60>>; ++ + inline void leftZeroPadding(std::string& str, const std::size_t padding) + { + if (str.size() < padding) +@@ -19,8 +27,135 @@ inline void leftZeroPadding(std::string& str, const std::size_t padding) + str.insert(0, padding - str.size(), '0'); + } + } ++ ++template <typename FromTime> ++bool fromDurationItem(std::string_view& fmt, const char postfix, ++ std::chrono::milliseconds& out) ++{ ++ const size_t pos = fmt.find(postfix); ++ if (pos == std::string::npos) ++ { ++ return true; ++ } ++ if ((pos + 1U) > fmt.size()) ++ { ++ return false; ++ } ++ ++ const char* end; ++ std::chrono::milliseconds::rep ticks = 0; ++ if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) ++ { ++ end = fmt.data() + std::min<size_t>(pos, 3U); ++ } ++ else ++ { ++ end = fmt.data() + pos; ++ } ++ ++ auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks); ++ if (ptr != end || ec != std::errc()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " ++ << static_cast<int>(ec) << "(" ++ << std::make_error_code(ec).message() << "), ptr{" ++ << static_cast<const void*>(ptr) << "} != end{" ++ << static_cast<const void*>(end) << "})"; ++ return false; ++ } ++ ++ if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) ++ { ++ ticks *= static_cast<std::chrono::milliseconds::rep>( ++ std::pow(10, 3 - std::min<size_t>(pos, 3U))); ++ } ++ if (ticks < 0) ++ { ++ return false; ++ } ++ ++ out += FromTime(ticks); ++ const auto maxConversionRange = ++ std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max()) ++ .count(); ++ if (out < FromTime(ticks) || maxConversionRange < ticks) ++ { ++ return false; ++ } ++ ++ fmt.remove_prefix(pos + 1U); ++ return true; ++} + } // namespace details + ++/** ++ * @brief Convert string that represents value in Duration Format to its numeric ++ * equivalent. ++ */ ++std::optional<std::chrono::milliseconds> ++ fromDurationString(const std::string& str) ++{ ++ std::chrono::milliseconds out = std::chrono::milliseconds::zero(); ++ std::string_view v = str; ++ ++ if (v.empty()) ++ { ++ return out; ++ } ++ if (v.front() != 'P') ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ v.remove_prefix(1); ++ if (!details::fromDurationItem<details::Days>(v, 'D', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ if (v.empty()) ++ { ++ return out; ++ } ++ if (v.front() != 'T') ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ v.remove_prefix(1); ++ if (!details::fromDurationItem<std::chrono::hours>(v, 'H', out) || ++ !details::fromDurationItem<std::chrono::minutes>(v, 'M', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ if (v.find('.') != std::string::npos && v.find('S') != std::string::npos) ++ { ++ if (!details::fromDurationItem<std::chrono::seconds>(v, '.', out) || ++ !details::fromDurationItem<std::chrono::milliseconds>(v, 'S', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ } ++ else if (!details::fromDurationItem<std::chrono::seconds>(v, 'S', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ if (!v.empty()) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ return out; ++} ++ + /** + * @brief Convert time value into duration format that is based on ISO 8601. + * Example output: "P12DT1M5.5S" +@@ -36,8 +171,7 @@ std::string toDurationString(std::chrono::milliseconds ms) + std::string fmt; + fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); + +- using Days = std::chrono::duration<long, std::ratio<24 * 60 * 60>>; +- Days days = std::chrono::floor<Days>(ms); ++ details::Days days = std::chrono::floor<details::Days>(ms); + ms -= days; + + std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index 59025d9..fcbc99c 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -1,9 +1,12 @@ + #pragma once + + #include "node.hpp" ++#include "sensors.hpp" + #include "utils/telemetry_utils.hpp" + #include "utils/time_utils.hpp" + ++#include <boost/container/flat_map.hpp> ++ + #include <tuple> + #include <variant> + +@@ -95,6 +98,252 @@ inline void fillReportDefinition( + asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = + time_utils::toDurationString(std::chrono::milliseconds(*interval)); + } ++ ++struct AddReportArgs ++{ ++ std::string name; ++ std::string reportingType; ++ bool emitsReadingsUpdate = false; ++ bool logToMetricReportsCollection = false; ++ uint64_t interval = 0; ++ std::vector<std::pair<std::string, std::vector<std::string>>> metrics; ++}; ++ ++inline bool toDbusReportActions(crow::Response& res, ++ std::vector<std::string>& actions, ++ AddReportArgs& args) ++{ ++ size_t index = 0; ++ for (auto& action : actions) ++ { ++ if (action == "RedfishEvent") ++ { ++ args.emitsReadingsUpdate = true; ++ } ++ else if (action == "LogToMetricReportsCollection") ++ { ++ args.logToMetricReportsCollection = true; ++ } ++ else ++ { ++ messages::propertyValueNotInList( ++ res, action, "ReportActions/" + std::to_string(index)); ++ return false; ++ } ++ index++; ++ } ++ return true; ++} ++ ++inline bool getUserParameters(crow::Response& res, const crow::Request& req, ++ AddReportArgs& args) ++{ ++ std::vector<nlohmann::json> metrics; ++ std::vector<std::string> reportActions; ++ std::optional<nlohmann::json> schedule; ++ if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, ++ "MetricReportDefinitionType", args.reportingType, ++ "ReportActions", reportActions, "Schedule", ++ schedule)) ++ { ++ return false; ++ } ++ ++ constexpr const char* allowedCharactersInName = ++ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; ++ if (args.name.empty() || args.name.find_first_not_of( ++ allowedCharactersInName) != std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << args.name ++ << " with allowed character " ++ << allowedCharactersInName; ++ messages::propertyValueIncorrect(res, "Id", args.name); ++ return false; ++ } ++ ++ if (args.reportingType != "Periodic" && args.reportingType != "OnRequest") ++ { ++ messages::propertyValueNotInList(res, args.reportingType, ++ "MetricReportDefinitionType"); ++ return false; ++ } ++ ++ if (!toDbusReportActions(res, reportActions, args)) ++ { ++ return false; ++ } ++ ++ if (args.reportingType == "Periodic") ++ { ++ if (!schedule) ++ { ++ messages::createFailedMissingReqProperties(res, "Schedule"); ++ return false; ++ } ++ ++ std::string durationStr; ++ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", ++ durationStr)) ++ { ++ return false; ++ } ++ ++ std::optional<std::chrono::milliseconds> durationNum = ++ time_utils::fromDurationString(durationStr); ++ if (!durationNum) ++ { ++ messages::propertyValueIncorrect(res, "RecurrenceInterval", ++ durationStr); ++ return false; ++ } ++ args.interval = static_cast<uint64_t>(durationNum->count()); ++ } ++ ++ args.metrics.reserve(metrics.size()); ++ for (auto& m : metrics) ++ { ++ std::string id; ++ std::vector<std::string> uris; ++ if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", ++ uris)) ++ { ++ return false; ++ } ++ ++ args.metrics.emplace_back(std::move(id), std::move(uris)); ++ } ++ ++ return true; ++} ++ ++inline bool getChassisSensorNode( ++ const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::vector<std::pair<std::string, std::vector<std::string>>>& ++ metrics, ++ boost::container::flat_set<std::pair<std::string, std::string>>& matched) ++{ ++ for (const auto& [id, uris] : metrics) ++ { ++ for (size_t i = 0; i < uris.size(); i++) ++ { ++ const std::string& uri = uris[i]; ++ std::string chassis; ++ std::string node; ++ ++ if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || ++ !dbus::utility::getNthStringFromPath(uri, 3, chassis) || ++ !dbus::utility::getNthStringFromPath(uri, 4, node)) ++ { ++ BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node " ++ "from " ++ << uri; ++ messages::propertyValueIncorrect(asyncResp->res, uri, ++ "MetricProperties/" + ++ std::to_string(i)); ++ return false; ++ } ++ ++ if (boost::ends_with(node, "#")) ++ { ++ node.pop_back(); ++ } ++ ++ matched.emplace(std::move(chassis), std::move(node)); ++ } ++ } ++ return true; ++} ++ ++class AddReport ++{ ++ public: ++ AddReport(AddReportArgs argsIn, std::shared_ptr<AsyncResp> asyncResp) : ++ asyncResp{std::move(asyncResp)}, args{std::move(argsIn)} ++ {} ++ ~AddReport() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ ++ telemetry::ReadingParameters readingParams; ++ readingParams.reserve(args.metrics.size()); ++ ++ for (const auto& [id, uris] : args.metrics) ++ { ++ for (size_t i = 0; i < uris.size(); i++) ++ { ++ const std::string& uri = uris[i]; ++ auto el = uriToDbus.find(uri); ++ if (el == uriToDbus.end()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corresponding to URI " ++ << uri; ++ messages::propertyValueNotInList(asyncResp->res, uri, ++ "MetricProperties/" + ++ std::to_string(i)); ++ return; ++ } ++ ++ const std::string& dbusPath = el->second; ++ readingParams.emplace_back(dbusPath, "SINGLE", id, uri); ++ } ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp = std::move(asyncResp), name = args.name, ++ uriToDbus = std::move(uriToDbus)]( ++ const boost::system::error_code ec, const std::string&) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists( ++ asyncResp->res, "MetricReportDefinition", "Id", name); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(asyncResp->res); ++ return; ++ } ++ if (ec == boost::system::errc::argument_list_too_long) ++ { ++ nlohmann::json metricProperties = nlohmann::json::array(); ++ for (const auto& [uri, _] : uriToDbus) ++ { ++ metricProperties.emplace_back(uri); ++ } ++ messages::propertyValueIncorrect( ++ asyncResp->res, metricProperties, "MetricProperties"); ++ return; ++ } ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ messages::created(asyncResp->res); ++ }, ++ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", ++ "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", ++ "TelemetryService/" + args.name, args.reportingType, ++ args.emitsReadingsUpdate, args.logToMetricReportsCollection, ++ args.interval, readingParams); ++ } ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ uriToDbus.insert(el.begin(), el.end()); ++ } ++ ++ private: ++ std::shared_ptr<AsyncResp> asyncResp; ++ AddReportArgs args; ++ boost::container::flat_map<std::string, std::string> uriToDbus{}; ++}; + } // namespace telemetry + + class MetricReportDefinitionCollection : public Node +@@ -126,6 +375,46 @@ class MetricReportDefinitionCollection : public Node + telemetry::getReportCollection(asyncResp, + telemetry::metricReportDefinitionUri); + } ++ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ telemetry::AddReportArgs args; ++ if (!telemetry::getUserParameters(res, req, args)) ++ { ++ return; ++ } ++ ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!telemetry::getChassisSensorNode(asyncResp, args.metrics, ++ chassisSensors)) ++ { ++ return; ++ } ++ ++ auto addReportReq = ++ std::make_shared<telemetry::AddReport>(std::move(args), asyncResp); ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, addReportReq]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map<std::string, std::string>& ++ uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ return; ++ } ++ addReportReq->insert(uriToDbus); ++ }); ++ } ++ } + }; + + class MetricReportDefinition : public Node +@@ -184,5 +473,44 @@ class MetricReportDefinition : public Node + "org.freedesktop.DBus.Properties", "GetAll", + telemetry::reportInterface); + } ++ ++ void doDelete(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>& params) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ const std::string reportPath = telemetry::getDbusReportPath(id); ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, id](const boost::system::error_code ec) { ++ /* ++ * boost::system::errc and std::errc are missing value for ++ * EBADR error that is defined in Linux. ++ */ ++ if (ec.value() == EBADR) ++ { ++ messages::resourceNotFound(asyncResp->res, ++ "MetricReportDefinition", id); ++ return; ++ } ++ ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ asyncResp->res.result(boost::beast::http::status::no_content); ++ }, ++ telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", ++ "Delete"); ++ } + }; + } // namespace redfish +diff --git a/redfish-core/ut/time_utils_test.cpp b/redfish-core/ut/time_utils_test.cpp +new file mode 100644 +index 0000000..70999ce +--- /dev/null ++++ b/redfish-core/ut/time_utils_test.cpp +@@ -0,0 +1,63 @@ ++#include "utils/time_utils.hpp" ++ ++#include <gmock/gmock.h> ++ ++using namespace testing; ++ ++class FromDurationTest : ++ public Test, ++ public WithParamInterface< ++ std::pair<std::string, std::optional<std::chrono::milliseconds>>> ++{}; ++ ++INSTANTIATE_TEST_SUITE_P( ++ _, FromDurationTest, ++ Values(std::make_pair("PT12S", std::chrono::milliseconds(12000)), ++ std::make_pair("PT0.204S", std::chrono::milliseconds(204)), ++ std::make_pair("PT0.2S", std::chrono::milliseconds(200)), ++ std::make_pair("PT50M", std::chrono::milliseconds(3000000)), ++ std::make_pair("PT23H", std::chrono::milliseconds(82800000)), ++ std::make_pair("P51D", std::chrono::milliseconds(4406400000)), ++ std::make_pair("PT2H40M10.1S", std::chrono::milliseconds(9610100)), ++ std::make_pair("P20DT2H40M10.1S", ++ std::chrono::milliseconds(1737610100)), ++ std::make_pair("", std::chrono::milliseconds(0)), ++ std::make_pair("PTS", std::nullopt), ++ std::make_pair("P1T", std::nullopt), ++ std::make_pair("PT100M1000S100", std::nullopt), ++ std::make_pair("PDTHMS", std::nullopt), ++ std::make_pair("P99999999999999999DT", std::nullopt), ++ std::make_pair("PD222T222H222M222.222S", std::nullopt), ++ std::make_pair("PT99999H9999999999999999999999M99999999999S", ++ std::nullopt), ++ std::make_pair("PT-9H", std::nullopt))); ++ ++TEST_P(FromDurationTest, convertToMilliseconds) ++{ ++ const auto& [str, expected] = GetParam(); ++ EXPECT_THAT(redfish::time_utils::fromDurationString(str), Eq(expected)); ++} ++ ++class ToDurationTest : ++ public Test, ++ public WithParamInterface<std::pair<std::chrono::milliseconds, std::string>> ++{}; ++ ++INSTANTIATE_TEST_SUITE_P( ++ _, ToDurationTest, ++ Values(std::make_pair(std::chrono::milliseconds(12000), "PT12.000S"), ++ std::make_pair(std::chrono::milliseconds(204), "PT0.204S"), ++ std::make_pair(std::chrono::milliseconds(200), "PT0.200S"), ++ std::make_pair(std::chrono::milliseconds(3000000), "PT50M"), ++ std::make_pair(std::chrono::milliseconds(82800000), "PT23H"), ++ std::make_pair(std::chrono::milliseconds(4406400000), "P51DT"), ++ std::make_pair(std::chrono::milliseconds(9610100), "PT2H40M10.100S"), ++ std::make_pair(std::chrono::milliseconds(1737610100), ++ "P20DT2H40M10.100S"), ++ std::make_pair(std::chrono::milliseconds(-250), ""))); ++ ++TEST_P(ToDurationTest, convertToDuration) ++{ ++ const auto& [ms, expected] = GetParam(); ++ EXPECT_THAT(redfish::time_utils::toDurationString(ms), Eq(expected)); ++} +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch new file mode 100644 index 000000000..99af0ab86 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch @@ -0,0 +1,555 @@ +From 462b2e814698e12a18b4956eb3c6421c34a3a4ba Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" <jozef.wludzik@intel.com> +Date: Tue, 15 Dec 2020 12:28:17 +0100 +Subject: [PATCH 3/4] Add support for MetricDefinition scheme + +Added MetricDefinition node to Redfish code. Now user is able +to list all available metrics in OpenBMC that are supported +by Telemetry service. Metrics are grouped by following +categories: temperature, power, voltage, current, fan_tach, +fan_pwm, utilization. + +Added generic function to fill ReadingUnits and ReadingType +in Sensor node. + +Tested: + - MetricDefinitions response is filled with existing sensors, + it works with and without Telemetry service + - Validated a presence of MetricDefinition members and it + attributes + - Succesfully passed RedfishServiceValidator.py using + witherspoon image on QEMU + +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Change-Id: I3086e1302e1ba2e5442d1367939fd5507a0cbc00 +--- + redfish-core/include/redfish.hpp | 3 + + redfish-core/include/utils/telemetry_utils.hpp | 2 + + redfish-core/lib/metric_definition.hpp | 283 +++++++++++++++++++++++++ + redfish-core/lib/power.hpp | 4 +- + redfish-core/lib/sensors.hpp | 85 ++++++-- + redfish-core/lib/telemetry_service.hpp | 2 + + redfish-core/lib/thermal.hpp | 4 +- + 7 files changed, 361 insertions(+), 22 deletions(-) + create mode 100644 redfish-core/lib/metric_definition.hpp + +diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp +index e94c0f3..83f2300 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -25,6 +25,7 @@ + #include "../lib/managers.hpp" + #include "../lib/memory.hpp" + #include "../lib/message_registries.hpp" ++#include "../lib/metric_definition.hpp" + #include "../lib/metric_report.hpp" + #include "../lib/metric_report_definition.hpp" + #include "../lib/network_protocol.hpp" +@@ -213,6 +214,8 @@ class RedfishService + nodes.emplace_back(std::make_unique<HypervisorSystem>(app)); + + nodes.emplace_back(std::make_unique<TelemetryService>(app)); ++ nodes.emplace_back(std::make_unique<MetricDefinitionCollection>(app)); ++ nodes.emplace_back(std::make_unique<MetricDefinition>(app)); + nodes.emplace_back( + std::make_unique<MetricReportDefinitionCollection>(app)); + nodes.emplace_back(std::make_unique<MetricReportDefinition>(app)); +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index a3a8156..c1b7639 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -8,6 +8,8 @@ namespace telemetry + + constexpr const char* service = "xyz.openbmc_project.Telemetry"; + constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; ++constexpr const char* metricDefinitionUri = ++ "/redfish/v1/TelemetryService/MetricDefinitions/"; + constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions/"; + constexpr const char* metricReportUri = +diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp +new file mode 100644 +index 0000000..4a40af5 +--- /dev/null ++++ b/redfish-core/lib/metric_definition.hpp +@@ -0,0 +1,283 @@ ++#pragma once ++ ++#include "node.hpp" ++#include "sensors.hpp" ++#include "utils/telemetry_utils.hpp" ++ ++namespace redfish ++{ ++ ++namespace utils ++{ ++ ++template <typename F> ++inline void getChassisNames(F&& cb, const std::shared_ptr<AsyncResp>& asyncResp) ++{ ++ const std::array<const char*, 2> interfaces = { ++ "xyz.openbmc_project.Inventory.Item.Board", ++ "xyz.openbmc_project.Inventory.Item.Chassis"}; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, ++ callback = std::move(cb)](const boost::system::error_code ec, ++ std::vector<std::string>& chassises) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_DEBUG << "DBus call error: " << ec.value(); ++ return; ++ } ++ ++ std::vector<std::string> chassisNames; ++ chassisNames.reserve(chassises.size()); ++ for (const std::string& chassis : chassises) ++ { ++ sdbusplus::message::object_path path(chassis); ++ std::string name = path.filename(); ++ if (name.empty()) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "Invalid chassis: " << chassis; ++ return; ++ } ++ chassisNames.push_back(name); ++ } ++ ++ callback(chassisNames); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", ++ "/xyz/openbmc_project/inventory", 0, interfaces); ++} ++} // namespace utils ++ ++namespace telemetry ++{ ++ ++class DefinitionCollectionReduce ++{ ++ public: ++ DefinitionCollectionReduce(const std::shared_ptr<AsyncResp>& asyncResp) : ++ asyncResp{asyncResp} ++ {} ++ ++ ~DefinitionCollectionReduce() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ ++ nlohmann::json& members = asyncResp->res.jsonValue["Members"]; ++ members = nlohmann::json::array(); ++ ++ for (const std::string& type : dbusTypes) ++ { ++ members.push_back( ++ {{"@odata.id", telemetry::metricDefinitionUri + type}}); ++ } ++ asyncResp->res.jsonValue["Members@odata.count"] = members.size(); ++ } ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ for (const auto& [_, dbusSensor] : el) ++ { ++ sdbusplus::message::object_path path(dbusSensor); ++ sdbusplus::message::object_path parentPath = path.parent_path(); ++ std::string type = parentPath.filename(); ++ if (type.empty()) ++ { ++ BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " ++ << dbusSensor; ++ continue; ++ } ++ ++ dbusTypes.insert(std::move(type)); ++ } ++ } ++ ++ private: ++ const std::shared_ptr<AsyncResp> asyncResp; ++ boost::container::flat_set<std::string> dbusTypes; ++}; ++ ++class DefinitionReduce ++{ ++ public: ++ DefinitionReduce(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::string& id) : ++ id(id), ++ pattern{'/' + id + '/'}, asyncResp{asyncResp} ++ {} ++ ~DefinitionReduce() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ if (redfishSensors.empty()) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricDefinition", id); ++ return; ++ } ++ ++ asyncResp->res.jsonValue["MetricProperties"] = redfishSensors; ++ asyncResp->res.jsonValue["Id"] = id; ++ asyncResp->res.jsonValue["Name"] = id; ++ asyncResp->res.jsonValue["@odata.id"] = ++ telemetry::metricDefinitionUri + id; ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#MetricDefinition.v1_0_3.MetricDefinition"; ++ asyncResp->res.jsonValue["MetricDataType"] = "Decimal"; ++ asyncResp->res.jsonValue["MetricType"] = "Numeric"; ++ asyncResp->res.jsonValue["IsLinear"] = true; ++ asyncResp->res.jsonValue["Units"] = sensors::toReadingUnits(id); ++ } ++ ++ void insert(const boost::container::flat_map<std::string, std::string>& el) ++ { ++ for (const auto& [redfishSensor, dbusSensor] : el) ++ { ++ if (dbusSensor.find(pattern) != std::string::npos) ++ { ++ redfishSensors.push_back(redfishSensor); ++ } ++ } ++ } ++ ++ private: ++ const std::string id; ++ const std::string pattern; ++ const std::shared_ptr<AsyncResp> asyncResp; ++ std::vector<std::string> redfishSensors; ++}; ++} // namespace telemetry ++ ++class MetricDefinitionCollection : public Node ++{ ++ public: ++ MetricDefinitionCollection(App& app) : ++ Node(app, "/redfish/v1/TelemetryService/MetricDefinitions/") ++ { ++ entityPrivileges = { ++ {boost::beast::http::verb::get, {{"Login"}}}, ++ {boost::beast::http::verb::head, {{"Login"}}}, ++ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>&) override ++ { ++ res.jsonValue["@odata.type"] = "#MetricDefinitionCollection." ++ "MetricDefinitionCollection"; ++ res.jsonValue["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricDefinitions"; ++ res.jsonValue["Name"] = "Metric Definition Collection"; ++ res.jsonValue["Members"] = nlohmann::json::array(); ++ res.jsonValue["Members@odata.count"] = 0; ++ ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ auto collectionReduce = ++ std::make_shared<telemetry::DefinitionCollectionReduce>(asyncResp); ++ utils::getChassisNames( ++ [asyncResp, ++ collectionReduce](const std::vector<std::string>& chassisNames) { ++ for (const std::string& chassisName : chassisNames) ++ { ++ for (const auto& [sensorNode, _] : sensors::dbus::paths) ++ { ++ BMCWEB_LOG_INFO << "Chassis: " << chassisName ++ << " sensor: " << sensorNode; ++ retrieveUriToDbusMap( ++ chassisName, sensorNode.data(), ++ [asyncResp, collectionReduce]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR ++ << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ collectionReduce->insert(uriToDbus); ++ }); ++ } ++ } ++ }, ++ asyncResp); ++ } ++}; ++ ++class MetricDefinition : public Node ++{ ++ public: ++ MetricDefinition(App& app) : ++ Node(app, "/redfish/v1/TelemetryService/MetricDefinitions/<str>/", ++ std::string()) ++ { ++ entityPrivileges = { ++ {boost::beast::http::verb::get, {{"Login"}}}, ++ {boost::beast::http::verb::head, {{"Login"}}}, ++ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>& params) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ auto definitionGather = ++ std::make_shared<telemetry::DefinitionReduce>(asyncResp, id); ++ utils::getChassisNames( ++ [asyncResp, ++ definitionGather](const std::vector<std::string>& chassisNames) { ++ for (const std::string& chassisName : chassisNames) ++ { ++ for (const auto& [sensorNode, dbusPaths] : ++ sensors::dbus::paths) ++ { ++ retrieveUriToDbusMap( ++ chassisName, sensorNode.data(), ++ [asyncResp, definitionGather]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR ++ << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ definitionGather->insert(uriToDbus); ++ }); ++ } ++ } ++ }, ++ asyncResp); ++ } ++}; ++ ++} // namespace redfish +diff --git a/redfish-core/lib/power.hpp b/redfish-core/lib/power.hpp +index 1c7a009..99c45ef 100644 +--- a/redfish-core/lib/power.hpp ++++ b/redfish-core/lib/power.hpp +@@ -153,7 +153,7 @@ class Power : public Node + res.jsonValue["PowerControl"] = nlohmann::json::array(); + + auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::power), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::power), + sensors::node::power); + + getChassisData(sensorAsyncResp); +@@ -336,7 +336,7 @@ class Power : public Node + + const std::string& chassisName = params[0]; + auto asyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::power), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::power), + sensors::node::power); + + std::optional<std::vector<nlohmann::json>> voltageCollections; +diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp +index 14c9593..5080f77 100644 +--- a/redfish-core/lib/sensors.hpp ++++ b/redfish-core/lib/sensors.hpp +@@ -54,9 +54,10 @@ static constexpr std::string_view thermal = "Thermal"; + + namespace dbus + { ++ + static const boost::container::flat_map<std::string_view, + std::vector<const char*>> +- types = {{node::power, ++ paths = {{node::power, + {"/xyz/openbmc_project/sensors/voltage", + "/xyz/openbmc_project/sensors/power"}}, + {node::sensors, +@@ -67,6 +68,64 @@ static const boost::container::flat_map<std::string_view, + {"/xyz/openbmc_project/sensors/fan_tach", + "/xyz/openbmc_project/sensors/temperature", + "/xyz/openbmc_project/sensors/fan_pwm"}}}; ++} // namespace dbus ++ ++inline const char* toReadingType(const std::string& sensorType) ++{ ++ if (sensorType == "voltage") ++ { ++ return "Voltage"; ++ } ++ if (sensorType == "power") ++ { ++ return "Power"; ++ } ++ if (sensorType == "current") ++ { ++ return "Current"; ++ } ++ if (sensorType == "fan_tach") ++ { ++ return "Rotational"; ++ } ++ if (sensorType == "temperature") ++ { ++ return "Temperature"; ++ } ++ if (sensorType == "fan_pwm" || sensorType == "utilization") ++ { ++ return "Percent"; ++ } ++ return ""; ++} ++ ++inline const char* toReadingUnits(const std::string& sensorType) ++{ ++ if (sensorType == "voltage") ++ { ++ return "V"; ++ } ++ if (sensorType == "power") ++ { ++ return "W"; ++ } ++ if (sensorType == "current") ++ { ++ return "A"; ++ } ++ if (sensorType == "fan_tach") ++ { ++ return "RPM"; ++ } ++ if (sensorType == "temperature") ++ { ++ return "Cel"; ++ } ++ if (sensorType == "fan_pwm" || sensorType == "utilization") ++ { ++ return "%"; ++ } ++ return ""; + } + } // namespace sensors + +@@ -345,11 +404,11 @@ inline void reduceSensorList( + return; + } + +- for (const char* type : sensorsAsyncResp->types) ++ for (const char* path : sensorsAsyncResp->types) + { + for (const std::string& sensor : *allSensors) + { +- if (boost::starts_with(sensor, type)) ++ if (boost::starts_with(sensor, path)) + { + activeSensors->emplace(sensor); + } +@@ -853,18 +912,8 @@ inline void objectInterfacesToJson( + if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) + { + sensorJson["@odata.type"] = "#Sensor.v1_0_0.Sensor"; +- if (sensorType == "power") +- { +- sensorJson["ReadingUnits"] = "Watts"; +- } +- else if (sensorType == "current") +- { +- sensorJson["ReadingUnits"] = "Amperes"; +- } +- else if (sensorType == "utilization") +- { +- sensorJson["ReadingUnits"] = "Percent"; +- } ++ sensorJson["ReadingType"] = sensors::toReadingType(sensorType); ++ sensorJson["ReadingUnits"] = sensors::toReadingUnits(sensorType); + } + else if (sensorType == "temperature") + { +@@ -2976,8 +3025,8 @@ inline void retrieveUriToDbusMap(const std::string& chassis, + const std::string& node, + SensorsAsyncResp::DataCompleteCb&& mapComplete) + { +- auto typesIt = sensors::dbus::types.find(node); +- if (typesIt == sensors::dbus::types.end()) ++ auto typesIt = sensors::dbus::paths.find(node); ++ if (typesIt == sensors::dbus::paths.end()) + { + BMCWEB_LOG_ERROR << "Wrong node provided : " << node; + mapComplete(boost::beast::http::status::bad_request, {}); +@@ -3027,7 +3076,7 @@ class SensorCollection : public Node + const std::string& chassisId = params[0]; + std::shared_ptr<SensorsAsyncResp> asyncResp = + std::make_shared<SensorsAsyncResp>( +- res, chassisId, sensors::dbus::types.at(sensors::node::sensors), ++ res, chassisId, sensors::dbus::paths.at(sensors::node::sensors), + sensors::node::sensors); + + auto getChassisCb = +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index 61ca891..a8c8b03 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -32,6 +32,8 @@ class TelemetryService : public Node + res.jsonValue["Id"] = "TelemetryService"; + res.jsonValue["Name"] = "Telemetry Service"; + ++ res.jsonValue["MetricDefinitions"]["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricDefinitions"; + res.jsonValue["MetricReportDefinitions"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricReportDefinitions"; + res.jsonValue["MetricReports"]["@odata.id"] = +diff --git a/redfish-core/lib/thermal.hpp b/redfish-core/lib/thermal.hpp +index 8e01bee..00acdf9 100644 +--- a/redfish-core/lib/thermal.hpp ++++ b/redfish-core/lib/thermal.hpp +@@ -48,7 +48,7 @@ class Thermal : public Node + } + const std::string& chassisName = params[0]; + auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::thermal), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::thermal), + sensors::node::thermal); + + // TODO Need to get Chassis Redundancy information. +@@ -71,7 +71,7 @@ class Thermal : public Node + allCollections; + + auto asyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::thermal), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::thermal), + sensors::node::thermal); + + if (!json_util::readJson(req, asyncResp->res, "Temperatures", +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch new file mode 100644 index 000000000..3df9fe5ae --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch @@ -0,0 +1,311 @@ +From 14a429586fd8ccb82c72611fe3310860db2e643b Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" <jozef.wludzik@intel.com> +Date: Tue, 15 Dec 2020 12:30:31 +0100 +Subject: [PATCH 4/4] Sync Telmetry service with EventService + +Synced the latest changes in Telemetry service with Event Service +code. Now assembling MetricReport is covered in single place in +code. Updated method of fetching Readings from Telemetry by +Event Service. Using ReportUpdate signal is no longer +supported. Now Event Service monitors for PropertiesChanged signal +from /xyz/openbmc_project/Telemetry/Reports path. + +Tested: + - Verified that EventListener received MetricReport response from + Event Service in insecure http push style eventing mode + +Change-Id: I2fc1841a6c9259a8bff30b34bddc0d4aabd41912 +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +--- + redfish-core/include/event_service_manager.hpp | 157 +++++++++---------------- + redfish-core/lib/metric_report.hpp | 28 +++-- + 2 files changed, 71 insertions(+), 114 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 3db9f0c..5c5a6c1 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -14,6 +14,7 @@ + // limitations under the License. + */ + #pragma once ++#include "metric_report.hpp" + #include "node.hpp" + #include "registries.hpp" + #include "registries/base_message_registry.hpp" +@@ -510,48 +511,29 @@ class Subscription + } + #endif + +- void filterAndSendReports(const std::string& id2, +- const std::string& readingsTs, +- const ReadingsObjType& readings) ++ void filterAndSendReports( ++ const std::string& id, ++ const std::variant<telemetry::TimestampReadings>& var) + { +- std::string metricReportDef = +- "/redfish/v1/TelemetryService/MetricReportDefinitions/" + id2; ++ std::string mrdUri = telemetry::metricReportDefinitionUri + id; + + // Empty list means no filter. Send everything. + if (metricReportDefinitions.size()) + { + if (std::find(metricReportDefinitions.begin(), + metricReportDefinitions.end(), +- metricReportDef) == metricReportDefinitions.end()) ++ mrdUri) == metricReportDefinitions.end()) + { + return; + } + } + +- nlohmann::json metricValuesArray = nlohmann::json::array(); +- for (const auto& it : readings) ++ nlohmann::json json; ++ if (!telemetry::fillReport(json, id, var)) + { +- metricValuesArray.push_back({}); +- nlohmann::json& entry = metricValuesArray.back(); +- +- auto& [id, property, value, timestamp] = it; +- +- entry = {{"MetricId", id}, +- {"MetricProperty", property}, +- {"MetricValue", std::to_string(value)}, +- {"Timestamp", crow::utility::getDateTime(timestamp)}}; ++ return; + } +- +- nlohmann::json msg = { +- {"@odata.id", "/redfish/v1/TelemetryService/MetricReports/" + id}, +- {"@odata.type", "#MetricReport.v1_3_0.MetricReport"}, +- {"Id", id2}, +- {"Name", id2}, +- {"Timestamp", readingsTs}, +- {"MetricReportDefinition", {{"@odata.id", metricReportDef}}}, +- {"MetricValues", metricValuesArray}}; +- +- this->sendEvent(msg.dump()); ++ this->sendEvent(json.dump()); + } + + void updateRetryConfig(const uint32_t retryAttempts, +@@ -1342,56 +1324,72 @@ class EventServiceManager + } + + #endif +- +- void getMetricReading(const std::string& service, +- const std::string& objPath, const std::string& intf) ++ void unregisterMetricReportSignal() + { +- std::size_t found = objPath.find_last_of('/'); +- if (found == std::string::npos) ++ if (matchTelemetryMonitor) + { +- BMCWEB_LOG_DEBUG << "Invalid objPath received"; +- return; ++ BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; ++ matchTelemetryMonitor.reset(); ++ matchTelemetryMonitor = nullptr; + } ++ } + +- std::string idStr = objPath.substr(found + 1); +- if (idStr.empty()) ++ void registerMetricReportSignal() ++ { ++ if (!serviceEnabled || matchTelemetryMonitor) + { +- BMCWEB_LOG_DEBUG << "Invalid ID in objPath"; ++ BMCWEB_LOG_DEBUG << "Not registering metric report signal."; + return; + } + +- crow::connections::systemBus->async_method_call( +- [idStr{std::move(idStr)}]( +- const boost::system::error_code ec, +- boost::container::flat_map< +- std::string, std::variant<int32_t, ReadingsObjType>>& +- resp) { +- if (ec) ++ BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; ++ std::string matchStr = "type='signal',member='PropertiesChanged'," ++ "interface='org.freedesktop.DBus.Properties'," ++ "path_namespace=/xyz/openbmc_project/Telemetry/" ++ "Reports/TelemetryService," ++ "arg0=xyz.openbmc_project.Telemetry.Report"; ++ ++ matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( ++ *crow::connections::systemBus, matchStr, ++ [this](sdbusplus::message::message& msg) { ++ if (msg.is_method_error()) + { +- BMCWEB_LOG_DEBUG +- << "D-Bus call failed to GetAll metric readings."; ++ BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; + return; + } + +- const int32_t* timestampPtr = +- std::get_if<int32_t>(&resp["Timestamp"]); +- if (!timestampPtr) ++ std::string intf; ++ std::vector<std::pair< ++ std::string, std::variant<telemetry::TimestampReadings>>> ++ props; ++ std::vector<std::string> invalidProp; ++ ++ msg.read(intf, props, invalidProp); ++ if (intf != "xyz.openbmc_project.Telemetry.Report") + { +- BMCWEB_LOG_DEBUG << "Failed to Get timestamp."; + return; + } + +- ReadingsObjType* readingsPtr = +- std::get_if<ReadingsObjType>(&resp["Readings"]); +- if (!readingsPtr) ++ const std::variant<telemetry::TimestampReadings>* varPtr = ++ nullptr; ++ for (const auto& [key, var] : props) ++ { ++ if (key == "Readings") ++ { ++ varPtr = &var; ++ break; ++ } ++ } ++ if (!varPtr) + { +- BMCWEB_LOG_DEBUG << "Failed to Get Readings property."; + return; + } + +- if (!readingsPtr->size()) ++ sdbusplus::message::object_path path(msg.get_path()); ++ std::string id = path.filename(); ++ if (id.empty()) + { +- BMCWEB_LOG_DEBUG << "No metrics report to be transferred"; ++ BMCWEB_LOG_ERROR << "Failed to get Id from path"; + return; + } + +@@ -1401,52 +1399,9 @@ class EventServiceManager + std::shared_ptr<Subscription> entry = it.second; + if (entry->eventFormatType == metricReportFormatType) + { +- entry->filterAndSendReports( +- idStr, crow::utility::getDateTime(*timestampPtr), +- *readingsPtr); ++ entry->filterAndSendReports(id, *varPtr); + } + } +- }, +- service, objPath, "org.freedesktop.DBus.Properties", "GetAll", +- intf); +- } +- +- void unregisterMetricReportSignal() +- { +- if (matchTelemetryMonitor) +- { +- BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; +- matchTelemetryMonitor.reset(); +- matchTelemetryMonitor = nullptr; +- } +- } +- +- void registerMetricReportSignal() +- { +- if (!serviceEnabled || matchTelemetryMonitor) +- { +- BMCWEB_LOG_DEBUG << "Not registering metric report signal."; +- return; +- } +- +- BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; +- std::string matchStr( +- "type='signal',member='ReportUpdate', " +- "interface='xyz.openbmc_project.MonitoringService.Report'"); +- +- matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( +- *crow::connections::systemBus, matchStr, +- [this](sdbusplus::message::message& msg) { +- if (msg.is_method_error()) +- { +- BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; +- return; +- } +- +- std::string service = msg.get_sender(); +- std::string objPath = msg.get_path(); +- std::string intf = msg.get_interface(); +- getMetricReading(service, objPath, intf); + }); + } + +diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp +index 9caf4a3..e79a41c 100644 +--- a/redfish-core/lib/metric_report.hpp ++++ b/redfish-core/lib/metric_report.hpp +@@ -31,16 +31,14 @@ inline nlohmann::json toMetricValues(const Readings& readings) + return metricValues; + } + +-inline void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, +- const std::string& id, ++inline bool fillReport(nlohmann::json& json, const std::string& id, + const std::variant<TimestampReadings>& var) + { +- asyncResp->res.jsonValue["@odata.type"] = +- "#MetricReport.v1_3_0.MetricReport"; +- asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + id; +- asyncResp->res.jsonValue["Id"] = id; +- asyncResp->res.jsonValue["Name"] = id; +- asyncResp->res.jsonValue["MetricReportDefinition"]["@odata.id"] = ++ json["@odata.type"] = "#MetricReport.v1_3_0.MetricReport"; ++ json["@odata.id"] = telemetry::metricReportUri + id; ++ json["Id"] = id; ++ json["Name"] = id; ++ json["MetricReportDefinition"]["@odata.id"] = + telemetry::metricReportDefinitionUri + id; + + const TimestampReadings* timestampReadings = +@@ -48,14 +46,14 @@ inline void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, + if (!timestampReadings) + { + BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; +- messages::internalError(asyncResp->res); +- return; ++ return false; + } + + const auto& [timestamp, readings] = *timestampReadings; +- asyncResp->res.jsonValue["Timestamp"] = ++ json["Timestamp"] = + crow::utility::getDateTime(static_cast<time_t>(timestamp)); +- asyncResp->res.jsonValue["MetricValues"] = toMetricValues(readings); ++ json["MetricValues"] = toMetricValues(readings); ++ return true; + } + } // namespace telemetry + +@@ -146,7 +144,11 @@ class MetricReport : public Node + return; + } + +- telemetry::fillReport(asyncResp, id, ret); ++ if (!telemetry::fillReport(asyncResp->res.jsonValue, id, ++ ret)) ++ { ++ messages::internalError(asyncResp->res); ++ } + }, + telemetry::service, reportPath, + "org.freedesktop.DBus.Properties", "Get", +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README new file mode 100644 index 000000000..35c6e90bc --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README @@ -0,0 +1,13 @@ +These patches are mirror of upstream TelemetryService implementation. +Until change is integrated they will be manually merged here to enable feature in Intel builds. + +Current revisions: +- Add POST and DELETE in MetricReportDefinitions + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32536/58 + +- Add support for MetricDefinition scheme + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/54 + +- Sync Telmetry service with EventService + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/38798/21 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend new file mode 100644 index 000000000..db8a7a90f --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend @@ -0,0 +1,60 @@ +SRC_URI = "git://github.com/openbmc/bmcweb.git" +SRCREV = "2b3da45876aac57a36d3093379a992d699e7e396" + +DEPENDS += "boost-url" +RDEPENDS_${PN} += "phosphor-nslcd-authority-cert-config" + +FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" + +# add a user called bmcweb for the server to assume +# bmcweb is part of group shadow for non-root pam authentication +USERADD_PARAM_${PN} = "-r -s /usr/sbin/nologin -d /home/bmcweb -m -G shadow bmcweb" + +GROUPADD_PARAM_${PN} = "web; redfish " + +SRC_URI += "file://0001-Firmware-update-configuration-changes.patch \ + file://0002-Use-chip-id-based-UUID-for-Service-Root.patch \ + file://0004-bmcweb-handle-device-or-resource-busy-exception.patch \ + file://0005-EventService-https-client-support.patch \ + file://0006-Define-Redfish-interface-Registries-Bios.patch \ + file://0007-BIOS-config-Add-support-for-PATCH-operation.patch \ + file://0008-Add-support-to-ResetBios-action.patch \ + file://0009-Add-support-to-ChangePassword-action.patch \ + file://0010-managers-add-attributes-for-Manager.CommandShell.patch \ + file://0034-recommended-fixes-by-crypto-review-team.patch \ +" + +# Temporary downstream mirror of upstream patch to enable feature in Intel builds. +SRC_URI += "file://0037-Add-state-sensor-messages-to-the-registry.patch \ +" + +# Temporary downstream mirror of upstream patches, see telemetry\README for details +SRC_URI += "file://telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch \ + file://telemetry/0003-Add-support-for-MetricDefinition-scheme.patch \ + file://telemetry/0004-Sync-Telmetry-service-with-EventService.patch \ +" + +SRC_URI += "file://0001-Add-ConnectedVia-property-to-virtual-media-item-temp.patch \ + file://0002-Change-InsertMedia-action-response-for-POST-in-proxy.patch \ + file://0003-Set-Inserted-redfish-property-for-not-inserted-resou.patch \ +" + +# Temporary fix: Move it to service file +do_install_append() { + install -d ${D}/var/lib/bmcweb + install -d ${D}/etc/ssl/certs/authority +} + +# Enable PFR support +EXTRA_OEMESON += "${@bb.utils.contains('IMAGE_FSTYPES', 'intel-pfr', '-Dredfish-provisioning-feature=enabled', '', d)}" + +# Enable NBD proxy embedded in bmcweb +EXTRA_OEMESON += " -Dvm-nbdproxy=enabled" + +# Disable dependency on external nbd-proxy application +EXTRA_OEMESON += " -Dvm-websocket=disabled" +RDEPENDS_${PN}_remove += "jsnbd" + +# Enable Validation unsecure based on IMAGE_FEATURES +EXTRA_OEMESON += "${@bb.utils.contains('EXTRA_IMAGE_FEATURES', 'validation-unsecure', '-Dvalidate-unsecure-feature=enabled', '', d)}" + |