From 45f1c88d8936cb0f8bb3ff0740ae00f4e13c681e Mon Sep 17 00:00:00 2001 From: "Jason M. Bills" Date: Thu, 24 Mar 2022 09:15:20 -0700 Subject: bmcweb patches on current bump to f7725d7 Signed-off-by: Jason M. Bills --- ...001-Firmware-update-configuration-changes.patch | 691 +++++++++++++++ ...2-Use-chip-id-based-UUID-for-Service-Root.patch | 74 ++ ...s-add-attributes-for-Manager.CommandShell.patch | 60 ++ ...-Add-PhysicalContext-to-Thermal-resources.patch | 158 ++++ ...g-RedFish-event-for-Invalid-login-attempt.patch | 67 ++ ...uting-logic-into-host-console-connection-.patch | 59 ++ ...4-recommended-fixes-by-crypto-review-team.patch | 75 ++ ...Add-state-sensor-messages-to-the-registry.patch | 95 ++ ...b-crashes-if-socket-directory-not-present.patch | 44 + ...registry-for-subscription-related-actions.patch | 78 ++ ...b-Add-BMC-Time-update-log-to-the-registry.patch | 74 ++ ...-Add-generic-message-PropertySizeExceeded.patch | 120 +++ ...fish-Deny-set-AccountLockDuration-to-zero.patch | 85 ++ ...0023-Add-get-IPMI-session-id-s-to-Redfish.patch | 390 +++++++++ .../bmcweb/0024-Add-count-sensor-type.patch | 29 + .../0025-Add-Model-to-ProcessorSummary.patch | 108 +++ ...elete-the-copy-constructor-on-the-Request.patch | 33 + ...028-Add-Redfish-schema-files-for-OEM-PECI.patch | 216 +++++ ...-Define-Redfish-interface-Registries-Bios.patch | 874 +++++++++++++++++++ ...BiosTable-Add-support-for-PATCH-operation.patch | 148 ++++ .../0003-Add-support-to-ResetBios-action.patch | 53 ++ ...0004-Add-support-to-ChangePassword-action.patch | 117 +++ ...e-bios-user-pwd-change-option-via-Redfish.patch | 46 + ...fix-for-broken-feature-Pending-Attributes.patch | 963 +++++++++++++++++++++ ...osAttributeRegistry-node-under-Registries.patch | 76 ++ .../interfaces/bmcweb/bmcweb.socket | 9 + ...d-unmerged-changes-for-http-retry-support.patch | 163 ++++ .../0002-EventService-https-client-support.patch | 448 ++++++++++ .../0004-Add-Server-Sent-Events-support.patch | 471 ++++++++++ ...tyle-subscription-support-to-eventservice.patch | 677 +++++++++++++++ .../0006-Add-EventService-SSE-filter-support.patch | 326 +++++++ ...rvice-Log-events-for-subscription-actions.patch | 133 +++ ...ks-on-Event-Subscription-input-parameters.patch | 85 ++ ...010-Remove-Terminated-Event-Subscriptions.patch | 267 ++++++ ...h-while-deleting-terminated-subscriptions.patch | 141 +++ ...ort-for-deleting-terminated-subscriptions.patch | 66 ++ ...rvice-fix-added-Context-field-to-response.patch | 33 + .../interfaces/bmcweb/eventservice/README | 37 + ...dd-asyncResp-support-during-handleUpgrade.patch | 194 +++++ .../0002-Move-privileges-to-separate-entity.patch | 109 +++ ...port-for-privilege-check-in-handleUpgrade.patch | 220 +++++ .../0004-Add-Privileges-to-Websockets.patch | 140 +++ .../0005-Add-Privileges-to-SseSockets.patch | 63 ++ ...t-Remove-LogService-from-TelemetryService.patch | 26 + .../0002-ref-use-url_view-for-telemetry-uris.patch | 193 +++++ .../0003-Fix-Trigger-GET-functionality.patch | 127 +++ ...dd-support-for-POST-on-TriggersCollection.patch | 848 ++++++++++++++++++ ...d-bmcweb-to-use-new-telemetry-service-API.patch | 559 ++++++++++++ ...-PUT-and-PATCH-for-MetricReportDefinition.patch | 948 ++++++++++++++++++++ ...-Links-Triggers-to-MetricReportDefinition.patch | 107 +++ .../interfaces/bmcweb/telemetry/README | 24 + ...1-Revert-Disable-nbd-proxy-from-the-build.patch | 61 ++ ...-handle-device-or-resource-busy-exception.patch | 215 +++++ ...edVia-property-to-virtual-media-item-temp.patch | 28 + ...status-code-from-InsertMedia-REST-methods.patch | 175 ++++ ...Bmcweb-handle-permission-denied-exception.patch | 38 + .../0007-Fix-unmounting-image-in-proxy-mode.patch | 35 + .../bmcweb/vm/0008-Apply-async-dbus-API.patch | 369 ++++++++ ...-Return-404-for-POST-on-Proxy-InsertMedia.patch | 380 ++++++++ .../recipes-phosphor/interfaces/bmcweb_%.bbappend | 96 ++ 60 files changed, 12544 insertions(+) create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Firmware-update-configuration-changes.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0002-Use-chip-id-based-UUID-for-Service-Root.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0011-bmcweb-Add-PhysicalContext-to-Thermal-resources.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0012-Log-RedFish-event-for-Invalid-login-attempt.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0013-Add-UART-routing-logic-into-host-console-connection-.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0014-recommended-fixes-by-crypto-review-team.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0015-Add-state-sensor-messages-to-the-registry.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0016-Fix-bmcweb-crashes-if-socket-directory-not-present.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0017-Add-msg-registry-for-subscription-related-actions.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0018-bmcweb-Add-BMC-Time-update-log-to-the-registry.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0019-Add-generic-message-PropertySizeExceeded.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0020-Redfish-Deny-set-AccountLockDuration-to-zero.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0023-Add-get-IPMI-session-id-s-to-Redfish.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0024-Add-count-sensor-type.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0025-Add-Model-to-ProcessorSummary.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0026-Revert-Delete-the-copy-constructor-on-the-Request.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0028-Add-Redfish-schema-files-for-OEM-PECI.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0001-Define-Redfish-interface-Registries-Bios.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0002-BaseBiosTable-Add-support-for-PATCH-operation.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0003-Add-support-to-ResetBios-action.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0004-Add-support-to-ChangePassword-action.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0005-Fix-remove-bios-user-pwd-change-option-via-Redfish.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0006-Add-fix-for-broken-feature-Pending-Attributes.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0007-Add-BiosAttributeRegistry-node-under-Registries.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/bmcweb.socket create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0013-event-service-fix-added-Context-field-to-response.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0001-Add-asyncResp-support-during-handleUpgrade.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0002-Move-privileges-to-separate-entity.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0003-Add-Support-for-privilege-check-in-handleUpgrade.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0004-Add-Privileges-to-Websockets.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0005-Add-Privileges-to-SseSockets.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0001-Revert-Disable-nbd-proxy-from-the-build.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0002-bmcweb-handle-device-or-resource-busy-exception.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0003-Add-ConnectedVia-property-to-virtual-media-item-temp.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0004-Invalid-status-code-from-InsertMedia-REST-methods.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0006-Bmcweb-handle-permission-denied-exception.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0007-Fix-unmounting-image-in-proxy-mode.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Apply-async-dbus-API.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Return-404-for-POST-on-Proxy-InsertMedia.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend 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 100644 index 000000000..168c1b21c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0001-Firmware-update-configuration-changes.patch @@ -0,0 +1,691 @@ +From 4baa1fbf5d2deedfc4144e3fd36c8547c3902c98 Mon Sep 17 00:00:00 2001 +From: Vikram Bodireddy +Date: Fri, 3 Dec 2021 05:25:02 +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 +--- + redfish-core/lib/update_service.hpp | 343 ++++++++++++++++-- + static/redfish/v1/$metadata/index.xml | 3 + + .../JsonSchemas/OemUpdateService/index.json | 69 ++++ + .../redfish/v1/schema/OemUpdateService_v1.xml | 40 ++ + 4 files changed, 426 insertions(+), 29 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 6c61e40..3161427 100644 +--- a/redfish-core/lib/update_service.hpp ++++ b/redfish-core/lib/update_service.hpp +@@ -26,7 +26,9 @@ + + namespace redfish + { +- ++// params for multiple firmware targets ++std::vector httpPushUriTargets; ++bool httpPushUriTargetBusy = false; + // Match signals added on software path + static std::unique_ptr fwUpdateMatcher; + static std::unique_ptr fwUpdateErrorMatcher; +@@ -34,6 +36,17 @@ static std::unique_ptr fwUpdateErrorMatcher; + static bool fwUpdateInProgress = false; + // Timer for software available + static std::unique_ptr 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"; + + inline static void cleanUp() + { +@@ -42,27 +55,120 @@ inline static void cleanUp() + fwUpdateErrorMatcher = nullptr; + } + inline static void activateImage(const std::string& objPath, +- const std::string& service) ++ const std::string& service, ++ const std::vector& 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 errorCode) { ++ if (errorCode) ++ { ++ BMCWEB_LOG_DEBUG ++ << "RequestedActivation failed: error_code = " ++ << errorCode; ++ BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message(); ++ } ++ }, ++ service, objPath, "org.freedesktop.DBus.Properties", "Set", ++ activationIntf, reqActivationPropName, ++ std::variant(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 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(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(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", +- dbus::utility::DbusVariantType( +- "xyz.openbmc_project.Software.Activation.RequestedActivations.Active")); ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", ++ static_cast(0), std::array{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, ++ const std::vector imgUriTargets, + sdbusplus::message::message& m, + task::Payload&& payload) + { +@@ -75,23 +181,26 @@ static void + + 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, payload(std::move(payload))]( ++ [objPath, asyncResp, imgTargets{imgUriTargets}, ++ payload(std::move(payload))]( + const boost::system::error_code errorCode, + const std::vector< + std::pair>>& + objInfo) mutable { + 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) +@@ -118,7 +227,7 @@ static void + // is added + fwAvailableTimer = nullptr; + +- activateImage(objPath.str, objInfo[0].first); ++ activateImage(objPath.str, objInfo[0].first, imgTargets); + if (asyncResp) + { + std::shared_ptr task = +@@ -249,8 +358,7 @@ static void + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, +- std::array{ +- "xyz.openbmc_project.Software.Activation"}); ++ std::array{activationIntf}); + } + } + } +@@ -260,7 +368,7 @@ static void + static void monitorForSoftwareAvailable( + const std::shared_ptr& asyncResp, + const crow::Request& req, const std::string& url, +- int timeoutTimeSeconds = 10) ++ const std::vector& imgUriTargets, int timeoutTimeSeconds = 10) + { + // Only allow one FW update at a time + if (fwUpdateInProgress) +@@ -300,10 +408,10 @@ static void monitorForSoftwareAvailable( + } + }); + task::Payload payload(req); +- auto callback = [asyncResp, ++ auto callback = [asyncResp, imgTargets{imgUriTargets}, + payload](sdbusplus::message::message& m) mutable { + BMCWEB_LOG_DEBUG << "Match fired"; +- softwareInterfaceAdded(asyncResp, m, std::move(payload)); ++ softwareInterfaceAdded(asyncResp, imgTargets, m, std::move(payload)); + }; + + fwUpdateInProgress = true; +@@ -485,12 +593,15 @@ inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app) + 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 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 +@@ -539,6 +650,9 @@ inline void requestRoutesUpdateService(App& app) + asyncResp->res.jsonValue["Name"] = "Update Service"; + asyncResp->res.jsonValue["HttpPushUri"] = + "/redfish/v1/UpdateService"; ++ asyncResp->res.jsonValue["HttpPushUriTargets"] = httpPushUriTargets; ++ asyncResp->res.jsonValue["HttpPushUriTargetsBusy"] = ++ httpPushUriTargetBusy; + // UpdateService cannot be disabled + asyncResp->res.jsonValue["ServiceEnabled"] = true; + asyncResp->res.jsonValue["FirmwareInventory"] = { +@@ -590,6 +704,34 @@ inline void requestRoutesUpdateService(App& app) + "OnReset"; + } + }); ++ ++ // Get the ApplyOptions value ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const std::variant applyOption) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG << "DBUS response error " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const bool* b = std::get_if(&applyOption); ++ ++ if (b) ++ { ++ asyncResp->res ++ .jsonValue["Oem"]["ApplyOptions"]["@odata.type"] = ++ "#OemUpdateService.ApplyOptions"; ++ asyncResp->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"); + }); + BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") + .privileges(redfish::privileges::patchUpdateService) +@@ -600,12 +742,60 @@ inline void requestRoutesUpdateService(App& app) + BMCWEB_LOG_DEBUG << "doPatch..."; + + std::optional pushUriOptions; +- if (!json_util::readJsonPatch(req, asyncResp->res, +- "HttpPushUriOptions", pushUriOptions)) ++ std::optional> imgTargets; ++ std::optional imgTargetBusy; ++ std::optional oemProps; ++ if (!json_util::readJsonPatch( ++ req, asyncResp->res, "HttpPushUriOptions", pushUriOptions, ++ "HttpPushUriTargets", imgTargets, "HttpPushUriTargetsBusy", ++ imgTargetBusy, "Oem", oemProps)) + { ++ BMCWEB_LOG_DEBUG ++ << "UpdateService doPatch: Invalid request body"; + return; + } + ++ if (oemProps) ++ { ++ std::optional applyOptions; ++ ++ if (!json_util::readJson(*oemProps, asyncResp->res, ++ "ApplyOptions", applyOptions)) ++ { ++ return; ++ } ++ ++ if (applyOptions) ++ { ++ std::optional clearConfig; ++ if (!json_util::readJson(*applyOptions, asyncResp->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{*clearConfig}); ++ } ++ } ++ } + if (pushUriOptions) + { + std::optional pushUriApplyTime; +@@ -668,6 +858,102 @@ inline void requestRoutesUpdateService(App& app) + } + } + } ++ 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, ++ crow::utility::urlFromPieces( ++ "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, crow::utility::urlFromPieces( ++ "HttpPushUriTargets")); ++ return; ++ } ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, uriTargets{*imgTargets}, ++ targetBusy{*imgTargetBusy}]( ++ const boost::system::error_code ec, ++ const std::vector 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, ++ crow::utility::urlFromPieces( ++ "HttpPushUriTargets")); ++ return; ++ } ++ httpPushUriTargetBusy = targetBusy; ++ httpPushUriTargets = uriTargets; ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", ++ "GetSubTreePaths", "/", static_cast(0), ++ std::array{versionIntf}); ++ } ++ else ++ { ++ httpPushUriTargetBusy = *imgTargetBusy; ++ httpPushUriTargets = *imgTargets; ++ } ++ } ++ else ++ { ++ httpPushUriTargetBusy = *imgTargetBusy; ++ } ++ } + }); + BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") + .privileges(redfish::privileges::postUpdateService) +@@ -678,7 +964,8 @@ inline void requestRoutesUpdateService(App& app) + + // Setup callback for when new software detected + monitorForSoftwareAvailable(asyncResp, req, +- "/redfish/v1/UpdateService"); ++ "/redfish/v1/UpdateService", ++ httpPushUriTargets); + + std::string filepath("/tmp/images/" + + boost::uuids::to_string( +@@ -754,8 +1041,7 @@ inline void requestRoutesSoftwareInventoryCollection(App& app) + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTree", + "/xyz/openbmc_project/software", static_cast(0), +- std::array{ +- "xyz.openbmc_project.Software.Version"}); ++ std::array{versionIntf}); + }); + } + /* Fill related item links (i.e. bmc, bios) in for inventory */ +@@ -923,7 +1209,7 @@ inline void requestRoutesSoftwareInventory(App& app) + }, + obj.second[0].first, obj.first, + "org.freedesktop.DBus.Properties", "GetAll", +- "xyz.openbmc_project.Software.Version"); ++ versionIntf); + } + if (!found) + { +@@ -948,8 +1234,7 @@ inline void requestRoutesSoftwareInventory(App& app) + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", + static_cast(0), +- std::array{ +- "xyz.openbmc_project.Software.Version"}); ++ std::array{versionIntf}); + }); + } + +diff --git a/static/redfish/v1/$metadata/index.xml b/static/redfish/v1/$metadata/index.xml +index 49835a7..1a6767b 100644 +--- a/static/redfish/v1/$metadata/index.xml ++++ b/static/redfish/v1/$metadata/index.xml +@@ -2575,6 +2575,9 @@ + + + ++ ++ ++ + + + +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +-- +2.25.1 + 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 +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 +--- + 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/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..e54e495bb --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch @@ -0,0 +1,60 @@ +From 971aa5058ac4bb626eeadf8b00738737748ed549 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Tue, 29 Jun 2021 15:25:38 +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:///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 +Change-Id: I2a56db912fc81064098f7aa9f4d110ac3baf361d +--- + redfish-core/lib/managers.hpp | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp +index b286f19..186003b 100644 +--- a/redfish-core/lib/managers.hpp ++++ b/redfish-core/lib/managers.hpp +@@ -1998,6 +1998,14 @@ inline void requestRoutesManager(App& app) + 15; + asyncResp->res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = + {"IPMI", "SSH"}; ++ ++ // Fill in CommandShell info ++ asyncResp->res.jsonValue["CommandShell"]["ServiceEnabled"] = true; ++ asyncResp->res.jsonValue["CommandShell"]["MaxConcurrentSessions"] = ++ 4; ++ asyncResp->res.jsonValue["CommandShell"]["ConnectTypesSupported"] = ++ {"SSH", "IPMI"}; ++ + #ifdef BMCWEB_ENABLE_KVM + // Fill in GraphicalConsole info + asyncResp->res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0011-bmcweb-Add-PhysicalContext-to-Thermal-resources.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0011-bmcweb-Add-PhysicalContext-to-Thermal-resources.patch new file mode 100644 index 000000000..f41e6f994 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0011-bmcweb-Add-PhysicalContext-to-Thermal-resources.patch @@ -0,0 +1,158 @@ +From b9747ecfce682f15dce0bb6e41e0c894f29419f3 Mon Sep 17 00:00:00 2001 +From: Snehalatha Venkatesh +Date: Thu, 8 Apr 2021 14:42:07 +0000 +Subject: [PATCH] bmcweb: Add PhysicalContext to Thermal resources + +Adding PhysicalContext to make redfish data compliance with OCP +Server Mgmt Interface v0.2.1.pdf and specific to Thermal resources. +https://github.com/opencomputeproject/OCP-Profiles/blob/master/ +OCPServerHardwareManagement.v0_2_4.json + +Tested: +1. Redfish validator - passed for this new change +2. GET - https:///redfish/v1/Chassis//Thermal +Response: +{ + "@odata.id": "/redfish/v1/Chassis//Thermal#/Temperatures/0", + "@odata.type": "#Thermal.v1_3_0.Temperature", + "LowerThresholdCritical": 0.0, + "LowerThresholdNonCritical": 5.0, + "MaxReadingRangeTemp": 127.0, + "MemberId": "BMC_Temp", + "MinReadingRangeTemp": -128.0, + "Name": "BMC Temp", + "PhysicalContext": "SystemBoard", + "ReadingCelsius": 25.75, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 115.0, + "UpperThresholdNonCritical": 110.0 +}, +{ + "@odata.id": "/redfish/v1/Chassis//Thermal#/Temperatures/1", + "@odata.type": "#Thermal.v1_3_0.Temperature", + "LowerThresholdCritical": 0.0, + "LowerThresholdNonCritical": 5.0, + "MaxReadingRangeTemp": 255.0, + "MemberId": "CPU1_P12V_PVCCIN_VR_Temp", + "MinReadingRangeTemp": 0.0, + "Name": "CPU1 P12V PVCCIN VR Temp", + "PhysicalContext": "CPU", + "ReadingCelsius": 41.0, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 115.0, + "UpperThresholdNonCritical": 110.0 +}, +{ + "@odata.id": "/redfish/v1/Chassis//Thermal#/Temperatures/28", + "@odata.type": "#Thermal.v1_3_0.Temperature", + "LowerThresholdCritical": 0.0, + "LowerThresholdNonCritical": 5.0, + "MaxReadingRangeTemp": 127.0, + "MemberId": "Inlet_BRD_Temp", + "MinReadingRangeTemp": -128.0, + "Name": "Inlet BRD Temp", + "PhysicalContext": "Intake", + "ReadingCelsius": 23.187, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 115.0, + "UpperThresholdNonCritical": 110.0 +}, +{ + @odata.id": "/redfish/v1/Chassis/F2U8X25_HSBP_2/Thermal#/Temperatures/0", + @odata.type": "#Thermal.v1_3_0.Temperature", + LowerThresholdCritical": 7.0, + LowerThresholdNonCritical": 12.0, + MaxReadingRangeTemp": 127.0, + MemberId": "HSBP2_Temp", + MinReadingRangeTemp": -128.0, + Name": "HSBP2 Temp", + PhysicalContext": "Backplane", + ReadingCelsius": 21.437, + Status": { + "Health": "OK", + "State": "Enabled" + }, + UpperThresholdCritical": 57.0, + UpperThresholdNonCritical": 52.0 +} +3. GET - https:///redfish/v1/Chassis//Power +Response: +{ + "@odata.id": "/redfish/v1/Chassis//Power#/Voltages/3", + "@odata.type": "#Power.v1_0_0.Voltage", + "LowerThresholdCritical": 1.648, + "LowerThresholdNonCritical": 1.699, + "MaxReadingRange": 2.3984009912875566, + "MemberId": "P1V8_PCH", + "MinReadingRange": 0.0, + "Name": "P1V8 PCH", + "ReadingVolts": 1.8055, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.961, + "UpperThresholdNonCritical": 1.904 +} +4. GET - https:///redfish/v1/Chassis//Sensors/PSU1_Input_Current +Response: +{ + "@odata.id": "/redfish/v1/Chassis//Sensors/PSU1_Input_Current", + "@odata.type": "#Sensor.v1_0_0.Sensor", + "Id": "PSU1_Input_Current", + "Name": "PSU1 Input Current", + "Reading": 0.947, + "ReadingRangeMax": 12.0, + "ReadingRangeMin": 0.0, + "ReadingType": "Current", + "ReadingUnits": "A", + "Status": { + "Health": "OK", + "State": "Enabled" + } +} +Signed-off-by: Snehalatha Venkatesh +Signed-off-by: sunitakx +--- + redfish-core/lib/sensors.hpp | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp +index 5d27577..d51d09f 100644 +--- a/redfish-core/lib/sensors.hpp ++++ b/redfish-core/lib/sensors.hpp +@@ -973,6 +973,22 @@ inline void objectInterfacesToJson( + { + unit = "/ReadingCelsius"_json_pointer; + sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; ++ if (sensorName.find("CPU") != std::string::npos) ++ { ++ sensorJson["PhysicalContext"] = "CPU"; ++ } ++ else if (sensorName.find("Inlet") != std::string::npos) ++ { ++ sensorJson["PhysicalContext"] = "Intake"; ++ } ++ else if (sensorName.find("HSBP") != std::string::npos) ++ { ++ sensorJson["PhysicalContext"] = "Backplane"; ++ } ++ else ++ { ++ sensorJson["PhysicalContext"] = "SystemBoard"; ++ } + // TODO(ed) Documentation says that path should be type fan_tach, + // implementation seems to implement fan + } +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0012-Log-RedFish-event-for-Invalid-login-attempt.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0012-Log-RedFish-event-for-Invalid-login-attempt.patch new file mode 100644 index 000000000..3ef4ee2de --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0012-Log-RedFish-event-for-Invalid-login-attempt.patch @@ -0,0 +1,67 @@ +From 1f572a1991fc8d9b08689aa6e3470080467977a7 Mon Sep 17 00:00:00 2001 +From: Jayaprakash Mutyala +Date: Thu, 15 Apr 2021 10:59:42 +0000 +Subject: [PATCH] Log RedFish event for Invalid login attempt + +This commit adds support for logging RedFish event log while user tries +to attempt login with invalid credentials. +When user trying to login with invalid credentials on HTTPS interface +like WebUI and RedFish, event should be logged in RedFish event log. +This event log is useful for further analysis to debug the root-cause +for failure. + +Tested: +1. Verified RedFish validator passed +2. Login with wrong credentials on HTTPS interface. +3. Verified for RedFish/WebUI events. RedFish event logged successfully. +GET: https://BMC-IP/redfish/v1/Systems/system/LogServices/ + EventLog/Entries +Response: +"Members": [ +{ + "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/ + Entries/1618466128", + "@odata.type": "#LogEntry.v1_4_0.LogEntry", + "Created": "2021-04-15T05:55:28+00:00", + "EntryType": "Event", + "Id": "1618466128", + "Message": "Invalid username or password attempted on HTTPS.", + "MessageArgs": [ + "HTTPS" + ], + "MessageId": "OpenBMC.0.1.InvalidLoginAttempted", + "Name": "System Event Log Entry", + "Severity": "Warning" +} + +Signed-off-by: Jayaprakash Mutyala +--- + include/pam_authenticate.hpp | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp +index 12f19c0..01bf301 100644 +--- a/include/pam_authenticate.hpp ++++ b/include/pam_authenticate.hpp +@@ -1,6 +1,7 @@ + #pragma once + + #include ++#include + + #include + +@@ -75,6 +76,10 @@ inline int pamAuthenticateUser(const std::string_view username, + PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); + if (retval != PAM_SUCCESS) + { ++ sd_journal_send("MESSAGE= %s", "Invalid login attempted on HTTPS", ++ "PRIORITY=%i", LOG_WARNING, "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.InvalidLoginAttempted", ++ "REDFISH_MESSAGE_ARGS=%s", "HTTPS", NULL); + pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval + return retval; + } +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0013-Add-UART-routing-logic-into-host-console-connection-.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0013-Add-UART-routing-logic-into-host-console-connection-.patch new file mode 100644 index 000000000..41acb6057 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0013-Add-UART-routing-logic-into-host-console-connection-.patch @@ -0,0 +1,59 @@ +From 6c10adb53d3247f65e5d9399290e6b8e7962cdef Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Wed, 28 Apr 2021 17:19:50 -0700 +Subject: [PATCH] Add UART routing logic into host console connection flow + +Switching UART routing when starting obmc-service introduces garbled +character printing out on physical host serial output and it's +inevitable so this commit moves the routing logic into host console +connection flow in bmcweb to avoid the issue until SOL is actually +activated. + +Tested: The garbled character printing out was not observed during +BMC booting. SOL worked well. + +Signed-off-by: Jae Hyun Yoo +--- + include/obmc_console.hpp | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/include/obmc_console.hpp b/include/obmc_console.hpp +index cdb19901e82d..9c4ae8821074 100644 +--- a/include/obmc_console.hpp ++++ b/include/obmc_console.hpp +@@ -22,6 +22,9 @@ static boost::container::flat_set sessions; + + static bool doingWrite = false; + ++constexpr char const* uartMuxCtrlPath = "/sys/bus/platform/drivers/aspeed-uart-routing/1e789098.uart-routing/hicra"; ++constexpr char const* uartMuxCtrlVal = "0x03450003"; ++ + inline void doWrite() + { + if (doingWrite) +@@ -110,6 +113,22 @@ inline void connectHandler(const boost::system::error_code& ec) + return; + } + ++ FILE* file = fopen(uartMuxCtrlPath, "w"); ++ if (file != nullptr) ++ { ++ int rc = fputs(uartMuxCtrlVal, file); ++ fclose(file); ++ if (rc < 0) ++ { ++ BMCWEB_LOG_ERROR << "Couldn't change UART routing: " << rc; ++ for (crow::websocket::Connection* session : sessions) ++ { ++ session->close("Error in connecting to host port"); ++ } ++ return; ++ } ++ } ++ + doWrite(); + doRead(); + } +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0014-recommended-fixes-by-crypto-review-team.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0014-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/0014-recommended-fixes-by-crypto-review-team.patch @@ -0,0 +1,75 @@ +From aaaa117817687a05284f8bfff07e2404e0d616b7 Mon Sep 17 00:00:00 2001 +From: Radivoje Jovanovic +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 +--- + 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 + 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/0015-Add-state-sensor-messages-to-the-registry.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0015-Add-state-sensor-messages-to-the-registry.patch new file mode 100644 index 000000000..2b8a6f294 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0015-Add-state-sensor-messages-to-the-registry.patch @@ -0,0 +1,95 @@ +From b76ee9de6bc994e4ea1d65b797785a7b9e2f994c Mon Sep 17 00:00:00 2001 +From: "Arun P. Mohanan" +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:///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 +--- + .../registries/openbmc_message_registry.hpp | 33 +++++++++++++++++-- + 1 file changed, 31 insertions(+), 2 deletions(-) + +diff --git a/redfish-core/include/registries/openbmc_message_registry.hpp b/redfish-core/include/registries/openbmc_message_registry.hpp +index 9a8c0f6..8f66d7d 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.3.1", + "OpenBMC", + }; +-constexpr std::array registry = { ++constexpr std::array registry = { + MessageEntry{ + "ADDDCCorrectable", + { +@@ -2155,6 +2155,36 @@ constexpr std::array 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", ++ 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", ++ 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", ++ 4, ++ {"string", "string", "string", "string"}, ++ "Check sensor subsystem for errors.", ++ }}, + MessageEntry{"SystemInterfaceDisabledProvisioned", + { + "Indicates that the system interface is in the disabled " +@@ -2239,6 +2269,5 @@ constexpr std::array registry = { + {"string"}, + "None.", + }}, +- + }; + } // namespace redfish::registries::openbmc +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0016-Fix-bmcweb-crashes-if-socket-directory-not-present.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0016-Fix-bmcweb-crashes-if-socket-directory-not-present.patch new file mode 100644 index 000000000..c34ff741f --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0016-Fix-bmcweb-crashes-if-socket-directory-not-present.patch @@ -0,0 +1,44 @@ +From ae7b8d17579a0d5aae28e568d179bda779137fed Mon Sep 17 00:00:00 2001 +From: Nidhin MS +Date: Wed, 14 Apr 2021 11:28:44 +0530 +Subject: [PATCH] Fix: bmcweb crashes if socket directory not present + +When trying to mount virtual media image bmcweb tries to create unix +socket and if the parent directory does not exist +stream_protocol::acceptor throws error and bmcweb crashes. Fix the same + +Tested: +Removed directory and mounted the vm image. bmcweb crash was not +observed + +Change-Id: I3aea1d8e197c06238f425a97435c01d3c80552a9 +Signed-off-by: Nidhin MS +--- + include/nbd_proxy.hpp | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/include/nbd_proxy.hpp b/include/nbd_proxy.hpp +index afb6493..b451f3a 100644 +--- a/include/nbd_proxy.hpp ++++ b/include/nbd_proxy.hpp +@@ -385,6 +385,17 @@ inline void requestRoutes(App& app) + // If the socket file exists (i.e. after bmcweb crash), + // we cannot reuse it. + std::remove((*socketValue).c_str()); ++ std::filesystem::path socketPath(*socketValue); ++ std::error_code fsErr; ++ if (!std::filesystem::exists(socketPath.parent_path(), ++ fsErr)) ++ { ++ BMCWEB_LOG_ERROR ++ << "VirtualMedia socket directory not present. " ++ << socketPath.parent_path(); ++ conn.close("Unable to create unix socket"); ++ return; ++ } + + sessions[&conn] = std::make_shared( + conn, *socketValue, *endpointValue, +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0017-Add-msg-registry-for-subscription-related-actions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0017-Add-msg-registry-for-subscription-related-actions.patch new file mode 100644 index 000000000..54c892ffd --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0017-Add-msg-registry-for-subscription-related-actions.patch @@ -0,0 +1,78 @@ +From a7769b0d85bb6c88f5eeaf70d56939d173a1cd80 Mon Sep 17 00:00:00 2001 +From: Ayushi Smriti +Date: Mon, 10 May 2021 12:32:30 +0530 +Subject: [PATCH] Add msg registry for subscription related actions + +For subscription event message log purpose, added message registry +entry for event service subscription related actions- add, update +and delete. + +Tested: + - Message registry entry appears in the log for the corresponding + subscription action. + +Signed-off-by: AppaRao Puli +Signed-off-by: Ayushi Smriti +--- + .../registries/openbmc_message_registry.hpp | 38 ++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/redfish-core/include/registries/openbmc_message_registry.hpp b/redfish-core/include/registries/openbmc_message_registry.hpp +index 8f66d7d..bedaf6a 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.3.1", + "OpenBMC", + }; +-constexpr std::array registry = { ++constexpr std::array registry = { + MessageEntry{ + "ADDDCCorrectable", + { +@@ -392,6 +392,42 @@ constexpr std::array registry = { + {}, + "None.", + }}, ++ MessageEntry{ ++ "EventSubscriptionAdded", ++ { ++ "Indicates that an Event subscription with specific id was added.", ++ "Event subscription with id %1 was added.", ++ "OK", ++ 1, ++ { ++ "string", ++ }, ++ "None.", ++ }}, ++ MessageEntry{ ++ "EventSubscriptionRemoved", ++ { ++ "Indicates that an Event subscription with specific id was removed.", ++ "Event subscription with id %1 was removed.", ++ "OK", ++ 1, ++ { ++ "string", ++ }, ++ "None.", ++ }}, ++ MessageEntry{ ++ "EventSubscriptionUpdated", ++ { ++ "Indicates that an Event subscription with specific id was updated.", ++ "Event subscription with id %1 was updated.", ++ "OK", ++ 1, ++ { ++ "string", ++ }, ++ "None.", ++ }}, + MessageEntry{"FanInserted", + { + "Indicates that a system fan has been inserted.", +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0018-bmcweb-Add-BMC-Time-update-log-to-the-registry.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0018-bmcweb-Add-BMC-Time-update-log-to-the-registry.patch new file mode 100644 index 000000000..ab4f65b8b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0018-bmcweb-Add-BMC-Time-update-log-to-the-registry.patch @@ -0,0 +1,74 @@ +From d36121d09db4e044b0d7599eae84ad7dc3240fca Mon Sep 17 00:00:00 2001 +From: mansijos +Date: Wed, 26 May 2021 17:40:04 +0530 +Subject: [PATCH] Add BMC Time update log to the registry + +Add message in registry to log an event that indicates BMC time +is set via NTP, Host or Manually. +During early stage of system boot if any critical events occur, +they are getting logged with 1970 timestamp till the time BMC +time update happens. This is expected behavior, but to call it out +explicitly it is good to log when BMC time is updated. + +Tested: +Built and validator passes. +Confirmed that the event is getting logged correctly in Redfish. + +Signed-off-by: mansijos +--- + .../registries/openbmc_message_registry.hpp | 32 ++++++++++++++++++- + 1 file changed, 31 insertions(+), 1 deletion(-) + +diff --git a/redfish-core/include/registries/openbmc_message_registry.hpp b/redfish-core/include/registries/openbmc_message_registry.hpp +index bedaf6a..4837d2b 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.3.1", + "OpenBMC", + }; +-constexpr std::array registry = { ++constexpr std::array registry = { + MessageEntry{ + "ADDDCCorrectable", + { +@@ -263,6 +263,36 @@ constexpr std::array registry = { + {}, + "None.", + }}, ++ MessageEntry{ ++ "BMCTimeUpdatedViaHost", ++ { ++ "Indicates that BMC time has been set via Host.", ++ "BMC time has been set via Host. Date Time is set to %1 from %2.", ++ "OK", ++ 2, ++ {"string", "string"}, ++ "None.", ++ }}, ++ MessageEntry{ ++ "BMCTimeUpdatedManually", ++ { ++ "Indicates that BMC time has been set Manually.", ++ "BMC time has been set Manually. Date Time is set to %1 from %2.", ++ "OK", ++ 2, ++ {"string", "string"}, ++ "None.", ++ }}, ++ MessageEntry{ ++ "BMCTimeUpdatedViaNTP", ++ { ++ "Indicates that BMC time has been set via NTP.", ++ "BMC time has been set via NTP. Date Time is set to %1 from %2.", ++ "OK", ++ 2, ++ {"string", "string"}, ++ "None.", ++ }}, + MessageEntry{"ChassisIntrusionDetected", + { + "Indicates that a physical security event " +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0019-Add-generic-message-PropertySizeExceeded.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0019-Add-generic-message-PropertySizeExceeded.patch new file mode 100644 index 000000000..c2be99ce0 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0019-Add-generic-message-PropertySizeExceeded.patch @@ -0,0 +1,120 @@ +From 32db2ac5cdd9f32a8bbd4c6e045dad9747634d5b Mon Sep 17 00:00:00 2001 +From: AppaRao Puli +Date: Wed, 6 Oct 2021 21:51:16 +0000 +Subject: [PATCH] Add generic message - PropertySizeExceeded + +Adding a generic error message "PropertySizeExceeded" +to address properties which exceed there defined size limit. + +Tested: +No functional change. Build passed. +Verified by explicitly sending this message as a response. + +Change-Id: I0e9f85f82a69c598e169fc8e9a68c3f66c0084d8 +Signed-off-by: Nitin Wankhade +Signed-off-by: AppaRao Puli + +%% original patch: 0019-Add-generic-message-PropertySizeExceeded.patch +--- + redfish-core/include/error_messages.hpp | 12 +++++++++++ + .../registries/base_message_registry.hpp | 16 +++++++++++++-- + redfish-core/src/error_messages.cpp | 20 +++++++++++++++++++ + 3 files changed, 46 insertions(+), 2 deletions(-) + +diff --git a/redfish-core/include/error_messages.hpp b/redfish-core/include/error_messages.hpp +index 6993a9d..2375bc3 100644 +--- a/redfish-core/include/error_messages.hpp ++++ b/redfish-core/include/error_messages.hpp +@@ -242,6 +242,18 @@ nlohmann::json propertyValueNotInList(std::string_view arg1, + void propertyValueNotInList(crow::Response& res, std::string_view arg1, + std::string_view arg2); + ++/** ++ * @brief Formats PropertySizeExceeded message into JSON ++ * Message body: "The property is too long. The value exceeds its size ++ * limit." ++ * ++ * @param[in] arg1 Parameter of message that will replace %1 in its body. ++ * ++ * @returns Message PropertySizeExceeded formatted to JSON */ ++nlohmann::json propertySizeExceeded(std::string_view arg1); ++ ++void propertySizeExceeded(crow::Response& res, std::string_view arg1); ++ + /** + * @brief Formats ResourceAtUriInUnknownFormat message into JSON + * Message body: "The resource at is in a format not recognized by the +diff --git a/redfish-core/include/registries/base_message_registry.hpp b/redfish-core/include/registries/base_message_registry.hpp +index 48875ec..279c0a0 100644 +--- a/redfish-core/include/registries/base_message_registry.hpp ++++ b/redfish-core/include/registries/base_message_registry.hpp +@@ -29,7 +29,7 @@ const Header header = { + constexpr const char* url = + "https://redfish.dmtf.org/registries/Base.1.11.0.json"; + +-constexpr std::array registry = ++constexpr std::array registry = + { + MessageEntry{ + "AccessDenied", +@@ -1108,7 +1108,18 @@ constexpr std::array registry = + {}, + "Correct the request body and resubmit the request if it failed.", + }}, +- ++ MessageEntry{ ++ "PropertySizeExceeded", ++ { ++ "Indicates that a given property exceeds the size limit imposed.", ++ "The property %1 is too long. The value exceeds its size limit.", ++ "Warning", ++ 1, ++ { ++ "string", ++ }, ++ "Correct the value for the property in the request body and resubmit the request if the operation failed.", ++ }} + }; + + enum class Index +@@ -1206,5 +1217,6 @@ enum class Index + success = 90, + undeterminedFault = 91, + unrecognizedRequestBody = 92, ++ propertySizeExceeded = 93 + }; + } // namespace redfish::registries::base +diff --git a/redfish-core/src/error_messages.cpp b/redfish-core/src/error_messages.cpp +index 7733db3..ed3256f 100644 +--- a/redfish-core/src/error_messages.cpp ++++ b/redfish-core/src/error_messages.cpp +@@ -448,6 +448,26 @@ void propertyValueFormatError(crow::Response& res, std::string_view arg1, + addMessageToJson(res.jsonValue, propertyValueFormatError(arg1, arg2), arg2); + } + ++/** ++ * @internal ++ * @brief Formats PropertySizeExceeded message into JSON for the specified ++ * property ++ * ++ * See header file for more information ++ * @endinternal ++ */ ++nlohmann::json propertySizeExceeded(std::string_view arg1) ++{ ++ return getLog(redfish::registries::base::Index::propertySizeExceeded, ++ std::to_array({arg1})); ++} ++ ++void propertySizeExceeded(crow::Response& res, std::string_view arg1) ++{ ++ res.result(boost::beast::http::status::bad_request); ++ addMessageToJson(res.jsonValue, propertySizeExceeded(arg1), arg1); ++} ++ + /** + * @internal + * @brief Formats PropertyValueNotInList message into JSON for the specified +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0020-Redfish-Deny-set-AccountLockDuration-to-zero.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0020-Redfish-Deny-set-AccountLockDuration-to-zero.patch new file mode 100644 index 000000000..cc9da3b8b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0020-Redfish-Deny-set-AccountLockDuration-to-zero.patch @@ -0,0 +1,85 @@ +From f75efac9eebea8bf8f548d10a8cbafa28f556a8f Mon Sep 17 00:00:00 2001 +From: Meera-Katta +Date: Wed, 7 Jul 2021 13:19:09 +0000 +Subject: [PATCH] Redfish: Deny set AccountLockDuration to zero +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Issue: Redfish schema says, no lockout shall occur in case of Account +LockoutDuration value is zero. But Linux PAM module documentation says, if +account lockout value is zero, account will be locked out indefinitely +after the number of failed login attempts. As per the current +implementation user can write any value into the PAM module. If user tried +to set unlock timeout value to zero, account will be locked out +indefinitely until administrator explicitly reenables it. + +Workaround: Denying user to set AccountLockDuration to zero from Redfish. +Setting ‘AccountLockDuration’ to 0 will be permitted only after +‘AccountLockoutCounterResetEnabled’ support is added. +Otherwise,account will be locked permanently after the AccountLockoutDuration +if ‘AccountLockDuration’ is set to zero, while +AccountLockoutThreshold is non zero. If someone wants no account lockout +irrespective of number of failed login attempts, it can be still achieved by +setting ‘AccountLockoutThreshold’ to zero +(instead of trying to set ‘AccountLockDuration’ to zero.) + +Tested: +1) Redfish Service Validator passed for this change. +2) Verified from Redfish +PATCH : https:///redfish/v1/AccountService +Body: +{"AccountLockoutDuration":0} + +Response: +{ + "AccountLockoutDuration@Message.ExtendedInfo": [ + { + "@odata.type": "#Message.v1_1_1.Message", + "Message": "The value unlockTimeout for the property + AccountLockoutDuration is not in the list of acceptable values.", + "MessageArgs": [ + "unlockTimeout", + "AccountLockoutDuration" + ], + "MessageId": "Base.1.8.1.PropertyValueNotInList", + "MessageSeverity": "Warning", + "Resolution": "Choose a value from the enumeration list that the + implementation can support and resubmit the request if the + operation failed." + } + ] +} + +Signed-off-by: Meera-Katta +--- + redfish-core/lib/account_service.hpp | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp +index e6fe205..42085fa 100644 +--- a/redfish-core/lib/account_service.hpp ++++ b/redfish-core/lib/account_service.hpp +@@ -1448,6 +1448,19 @@ inline void requestAccountServiceRoutes(App& app) + + if (unlockTimeout) + { ++ // Account will be locked permanently after the N number ++ // of failed login attempts if we set unlockTimeout value ++ // to be 0. ++ if (unlockTimeout.value() == 0) ++ { ++ BMCWEB_LOG_INFO ++ << "Unlock timeout value must be greater" ++ "than zero"; ++ messages::propertyValueNotInList(asyncResp->res, ++ "unlockTimeout", ++ "AccountLockoutDuration"); ++ return; ++ } + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec) { + if (ec) +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0023-Add-get-IPMI-session-id-s-to-Redfish.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0023-Add-get-IPMI-session-id-s-to-Redfish.patch new file mode 100644 index 000000000..b3feee39a --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0023-Add-get-IPMI-session-id-s-to-Redfish.patch @@ -0,0 +1,390 @@ +From 5c79e34be9357c2a2cd9bac61cd0162dbd342a2d Mon Sep 17 00:00:00 2001 +From: Jayaprakash Mutyala +Date: Fri, 30 Jul 2021 17:33:16 +0000 +Subject: [PATCH] Add/get IPMI session id's to Redfish + +As per existing implementation, Redfish supports to get only sessions +created on Redfish & EWS. But as per Redfish schema Redfish should +include to get IPMI sessions as well. +So add support to display IPMI session Id's as well on Redfish. +This commit will not impact any functionality/behavior of existing code. +Below functionalities implemented in this commit. +1. Get IPMI session collection +2. Get individual IPMI session information +3. Delete IPMI sessions - Respond with not supported as we can't delete + IPMI sessions from Redfish interface + +Tested: +1. Verified redfish validator passed with active IPMI session. +2. Get session details from Redfish +GET: https:///redfish/v1/SessionService/Sessions +Response: +{ + "@odata.id": "/redfish/v1/SessionService/Sessions/", + "@odata.type": "#SessionCollection.SessionCollection", + "Description": "Session Collection", + "Members": [ + { + "@odata.id": "/redfish/v1/SessionService/Sessions/TlFPbR9ZIn" + }, + { + "@odata.id": "/redfish/v1/SessionService/Sessions/184U3014ub" + }, + { + "@odata.id": "/redfish/v1/SessionService/Sessions/cV0xi5QoPy" + }, + { + "@odata.id": "/redfish/v1/SessionService/Sessions/8f6234d7_81" + } + ], + "Members@odata.count": 4, + "Name": "Session Collection" +} + +3. Get session details from RedFish +Case 1: RedFish session +GET: https:///redfish/v1/SessionService/Sessions/TlFPbR9ZIn +Response: +{ + "@odata.id": "/redfish/v1/SessionService/Sessions/TlFPbR9ZIn", + "@odata.type": "#Session.v1_3_0.Session", + "ClientOriginIPAddress": "::ffff:10.213.91.40", + "Description": "Manager User Session", + "Id": "TlFPbR9ZIn", + "Name": "User Session", + "UserName": "root" +} +Case 2: IPMI session +Verified and displayed IPMI session details on RedFish. +GET: https:///redfish/v1/SessionService/Sessions/8f6234d7_81 +Response: +{ + "@odata.id": "/redfish/v1/SessionService/Sessions/8f6234d7_81", + "@odata.type": "#Session.v1_3_0.Session", + "ClientOriginIPAddress": "xx.xx.xx.xx", + "Description": "Manager User Session", + "Id": "8f6234d7_81", + "Name": "User Session", + "UserName": "root" +} +4. Delete IPMI session: +Verified IPMI session is not allowed to delete from Redfish +DELETE: https:///redfish/v1/SessionService/Sessions/8f6234d7_81 +Response: +{ + "error": { + "@Message.ExtendedInfo": [ + { + "@odata.type": "#Message.v1_1_1.Message", + "Message": "The action deleting IPMI session from + Redfish is not supported by the resource.", + "MessageArgs": [ + "deleting IPMI session from Redfish" + ], + "MessageId": "Base.1.8.1.ActionNotSupported", + "MessageSeverity": "Critical", + "Resolution": "The action supplied cannot be resubmitted + to the implementation. Perhaps the action was invalid, + the wrong resource was the target or the implementation + documentation may be of assistance." + } + ], + "code": "Base.1.8.1.ActionNotSupported", + "message": "The action deleting IPMI session from Redfish is not + supported by the resource." + } +} +5. Delete RedFish session +Result: successfully deleted valid RedFish session. + +Signed-off-by: Jayaprakash Mutyala +--- + redfish-core/lib/redfish_sessions.hpp | 244 +++++++++++++++++++++++--- + 1 file changed, 222 insertions(+), 22 deletions(-) + +diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp +index 929e0c8..3c7a968 100644 +--- a/redfish-core/lib/redfish_sessions.hpp ++++ b/redfish-core/lib/redfish_sessions.hpp +@@ -56,14 +56,127 @@ inline void requestRoutesSession(App& app) + auto session = persistent_data::SessionStore::getInstance() + .getSessionByUid(sessionId); + +- if (session == nullptr) ++ if (session) + { +- messages::resourceNotFound(asyncResp->res, "Session", +- sessionId); ++ fillSessionObject(asyncResp->res, *session); + return; + } + +- fillSessionObject(asyncResp->res, *session); ++ std::array interfaces = { ++ "xyz.openbmc_project.Ipmi.SessionInfo"}; ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, sessionId](const boost::system::error_code ec, ++ const GetSubTreeType& subtree) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Error in querying GetSubTree with " ++ "Object Mapper. " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ if (subtree.size() == 0) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Can't find Session Info Attributes!"; ++ messages::resourceNotFound(asyncResp->res, ++ "Session", sessionId); ++ return; ++ } ++ bool ipmiSessionFound = false; ++ std::string ipmiSessionService; ++ std::string ipmiSessionInfPath; ++ for (const auto& [ipmiSessionPath, object] : subtree) ++ { ++ if (ipmiSessionPath.empty() || object.empty()) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Session Info Attributes mapper error!"; ++ continue; ++ } ++ if (!boost::ends_with(ipmiSessionPath, sessionId)) ++ { ++ continue; ++ } ++ ipmiSessionFound = true; ++ ipmiSessionService = object[0].first; ++ ipmiSessionInfPath = ipmiSessionPath; ++ break; ++ } ++ if (!ipmiSessionFound) ++ { ++ messages::resourceNotFound(asyncResp->res, ++ "Session", sessionId); ++ return; ++ } ++ if (ipmiSessionService.empty()) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Session Info Attributes mapper " ++ "error!"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, sessionId]( ++ const boost::system::error_code ec, ++ const std::vector>>& properties) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Error in querying Session " ++ "Info State property " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ std::string userName = ""; ++ uint32_t remoteIpAddr; ++ try ++ { ++ sdbusplus::unpackProperties( ++ properties, "Username", userName, ++ "RemoteIPAddr", remoteIpAddr); ++ asyncResp->res.jsonValue["Id"] = sessionId; ++ asyncResp->res.jsonValue["UserName"] = ++ userName; ++ asyncResp->res.jsonValue["@odata.id"] = ++ "/redfish/v1/SessionService/" ++ "Sessions/" + ++ sessionId; ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#Session.v1_3_0.Session"; ++ asyncResp->res.jsonValue["Name"] = ++ "User Session"; ++ asyncResp->res.jsonValue["Description"] = ++ "Manager User Session"; ++ struct in_addr ipAddr; ++ ipAddr.s_addr = remoteIpAddr; ++ asyncResp->res ++ .jsonValue["ClientOriginIPAddress"] = ++ inet_ntoa(ipAddr); ++ } ++ catch (const sdbusplus::exception:: ++ UnpackPropertyError& error) ++ { ++ BMCWEB_LOG_ERROR << error.what(); ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ return; ++ }, ++ ipmiSessionService, ipmiSessionInfPath, ++ "org.freedesktop.DBus.Properties", "GetAll", ++ "xyz.openbmc_project.Ipmi.SessionInfo"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, ++ interfaces); + }); + + BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions//") +@@ -75,34 +188,79 @@ inline void requestRoutesSession(App& app) + auto session = persistent_data::SessionStore::getInstance() + .getSessionByUid(sessionId); + +- if (session == nullptr) +- { +- messages::resourceNotFound(asyncResp->res, "Session", +- sessionId); +- return; +- } +- + // Perform a proper ConfigureSelf authority check. If a + // session is being used to DELETE some other user's session, + // then the ConfigureSelf privilege does not apply. In that + // case, perform the authority check again without the user's + // ConfigureSelf privilege. +- if (session->username != req.session->username) ++ if (session) + { +- Privileges effectiveUserPrivileges = +- redfish::getUserPrivileges(req.userRole); +- +- if (!effectiveUserPrivileges.isSupersetOf( +- {"ConfigureUsers"})) ++ if (session->username != req.session->username) + { +- messages::insufficientPrivilege(asyncResp->res); +- return; ++ Privileges effectiveUserPrivileges = ++ redfish::getUserPrivileges(req.userRole); ++ ++ if (!effectiveUserPrivileges.isSupersetOf( ++ {"ConfigureUsers"})) ++ { ++ messages::insufficientPrivilege(asyncResp->res); ++ return; ++ } + } ++ persistent_data::SessionStore::getInstance().removeSession( ++ session); ++ messages::success(asyncResp->res); ++ return; + } + +- persistent_data::SessionStore::getInstance().removeSession( +- session); +- messages::success(asyncResp->res); ++ std::array interfaces = { ++ "xyz.openbmc_project.Ipmi.SessionInfo"}; ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, ++ sessionId](const boost::system::error_code ec, ++ const std::vector& ifaceList) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Error in querying GetSubTreePaths " ++ "with Object Mapper. " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ if (ifaceList.size() == 0) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Can't find Session Info Attributes!"; ++ return; ++ } ++ bool ipmiSessionFound = false; ++ for (const std::string& ipmiSessionPath : ifaceList) ++ { ++ if (!boost::ends_with(ipmiSessionPath, sessionId)) ++ { ++ continue; ++ } ++ ipmiSessionFound = true; ++ break; ++ } ++ if (ipmiSessionFound) ++ { ++ BMCWEB_LOG_DEBUG << "Deleting IPMI session from " ++ "Redfish is not allowed."; ++ messages::actionNotSupported( ++ asyncResp->res, ++ "deleting IPMI session from Redfish"); ++ return; ++ } ++ messages::resourceNotFound(asyncResp->res, "Session", ++ sessionId); ++ return; ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", ++ 0, interfaces); + }); + + BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/") +@@ -131,6 +289,48 @@ inline void requestRoutesSession(App& app) + "/redfish/v1/SessionService/Sessions/"; + asyncResp->res.jsonValue["Name"] = "Session Collection"; + asyncResp->res.jsonValue["Description"] = "Session Collection"; ++ ++ std::array interfaces = { ++ "xyz.openbmc_project.Ipmi.SessionInfo"}; ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const std::vector& ifaceList) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Error in querying GetSubTreePaths " ++ "with Object Mapper. " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ if (ifaceList.size() == 0) ++ { ++ BMCWEB_LOG_DEBUG ++ << "Can't find Session Info Attributes!"; ++ return; ++ } ++ for (const std::string& ipmiSessionPath : ifaceList) ++ { ++ std::filesystem::path filePath(ipmiSessionPath); ++ std::string ipmiSessionID = ++ filePath.has_filename() ? filePath.filename() ++ : ""; ++ if (!ipmiSessionID.empty() && ipmiSessionID != "0") ++ { ++ asyncResp->res.jsonValue["Members"].push_back( ++ {{"@odata.id", ++ "/redfish/v1/SessionService/Sessions/" + ++ ipmiSessionID}}); ++ } ++ } ++ asyncResp->res.jsonValue["Members@odata.count"] = ++ asyncResp->res.jsonValue["Members"].size(); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", ++ 0, interfaces); + }); + + BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/") +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0024-Add-count-sensor-type.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0024-Add-count-sensor-type.patch new file mode 100644 index 000000000..22ae05fa3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0024-Add-count-sensor-type.patch @@ -0,0 +1,29 @@ +From 94a0ae774933b7801d0c8d843b3ac3a39a5e5646 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Adrian=20Ambro=C5=BCewicz?= +Date: Fri, 30 Jul 2021 15:25:29 +0200 +Subject: [PATCH] Add 'count' sensor type + +PMT exposes data mainly in raw counter formats. This change makes +bmcweb aware of new sensor type. + +Testing: +- values of type 'count' from PMT exposed successfully on Redfish +--- + redfish-core/lib/sensors.hpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp +index 45a1eb6..7405e5a 100644 +--- a/redfish-core/lib/sensors.hpp ++++ b/redfish-core/lib/sensors.hpp +@@ -63,6 +63,7 @@ static const boost::container::flat_map +Date: Fri, 3 Sep 2021 02:33:43 -0500 +Subject: [PATCH] Add Model to ProcessorSummary + +In Redfish ComputerSystem schema, the ProcessorSummary parameter +lists summary information of the Processors on the system. This commit +adds the 'Model' property to ProcessorSummary. + +If the CPU Models are different, then the 'Model' field takes the first +entry in alphabetical order. + +Testing: +1. Redfish Validator Testing successfully passed. +2. Curl testing: + +curl -k -H "X-Auth-Token: $tok" https://$bmc/redfish/v1/Systems/system + +... + "ProcessorSummary": { + "CoreCount": 24, + "Count": 2, + "Model": "test_name", + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Disabled" + } + }, +... + +Change-Id: I39cbf6ed35c35ce3a3551c9689237d5023775326 +Signed-off-by: Ali Ahmed +Signed-off-by: AppaRao Puli +--- + redfish-core/lib/systems.hpp | 47 +++++++++++++++++++++++++++++++----- + 1 file changed, 41 insertions(+), 6 deletions(-) + +diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp +index 62bd11b0..1b343693 100644 +--- a/redfish-core/lib/systems.hpp ++++ b/redfish-core/lib/systems.hpp +@@ -165,21 +165,56 @@ inline void getProcessorProperties( + + for (const auto& property : properties) + { ++ if (property.first == "Family") ++ { ++ // Get the CPU Model ++ const std::string* modelStr = ++ std::get_if(&property.second); ++ if (!modelStr) ++ { ++ BMCWEB_LOG_DEBUG << "Failed to get CPU Family"; ++ // Skip it and continue with other properties ++ continue; ++ } ++ if ((*modelStr).size() < 1) ++ { ++ BMCWEB_LOG_DEBUG << "Empty CPU Family info, skipping..."; ++ continue; ++ } ++ nlohmann::json& prevModel = ++ aResp->res.jsonValue["ProcessorSummary"]["Model"]; ++ std::string* prevModelPtr = prevModel.get_ptr(); + +- // TODO: Get Model ++ // If CPU Models are different, use the first entry in ++ // alphabetical order + +- // Get CoreCount +- if (property.first == "CoreCount") ++ // If Model has never been set ++ // before, set it to *modelStr ++ if (prevModelPtr == nullptr) ++ { ++ prevModel = *modelStr; ++ } ++ // If Model has been set before, only change if new Model is ++ // higher in alphabetical order ++ else ++ { ++ if (*modelStr < *prevModelPtr) ++ { ++ prevModel = *modelStr; ++ } ++ } ++ } ++ else if (property.first == "CoreCount") + { +- + // Get CPU CoreCount and add it to the total + const uint16_t* coreCountVal = + std::get_if(&property.second); + + if (coreCountVal == nullptr) + { +- messages::internalError(aResp->res); +- return; ++ BMCWEB_LOG_DEBUG << "Failed to get CPU Core count"; ++ // Skip it and continue with other properties ++ continue; + } + + nlohmann::json& coreCount = +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0026-Revert-Delete-the-copy-constructor-on-the-Request.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0026-Revert-Delete-the-copy-constructor-on-the-Request.patch new file mode 100644 index 000000000..4e525c9a3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0026-Revert-Delete-the-copy-constructor-on-the-Request.patch @@ -0,0 +1,33 @@ +From 1503fcc9e5fb8d85cfceb03b38a52a519fb63210 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Tue, 4 Jan 2022 06:49:58 +0530 +Subject: [PATCH] Revert "Delete the copy constructor on the Request object" + +This commit is reverted to resolve build issues due arising due +to removal of the copy constructor. + +This reverts commit 597d2b142362bafa90f24fc8c30750afab91f78f. +--- + http/http_request.hpp | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/http/http_request.hpp b/http/http_request.hpp +index be1c2a2..be84f18 100644 +--- a/http/http_request.hpp ++++ b/http/http_request.hpp +@@ -45,12 +45,6 @@ struct Request + } + } + +- Request(const Request&) = delete; +- Request(const Request&&) = delete; +- Request& operator=(const Request&) = delete; +- Request& operator=(const Request&&) = delete; +- ~Request() = default; +- + boost::beast::http::verb method() const + { + return req.method(); +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0028-Add-Redfish-schema-files-for-OEM-PECI.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0028-Add-Redfish-schema-files-for-OEM-PECI.patch new file mode 100644 index 000000000..80b5c9678 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0028-Add-Redfish-schema-files-for-OEM-PECI.patch @@ -0,0 +1,216 @@ +From cf40628193cfbb77580b60d1c7da0c34c0bb3d8f Mon Sep 17 00:00:00 2001 +From: yes +Date: Mon, 7 Feb 2022 22:56:42 +0530 +Subject: [PATCH] Add Redfish schema files for OEM PECI + +OpenBMC supports 'SendRawPeci' command in Redfish. +This change is to add Json and Xml OEM schema files for the same +in standard Redfish schema file format. + +Tested: +-Redfish validator passed successfully. +-Able to read the schema file from bmc ip. OemCrashdump namespace + gets created. + +Signed-off-by: smriti.ayushi@intel.com +--- + scripts/update_schemas.py | 5 ++ + static/redfish/v1/$metadata/index.xml | 3 + + .../OemCrashdump/OemCrashdump.json | 86 +++++++++++++++++++ + .../v1/JsonSchemas/OemCrashdump/index.json | 20 +++++ + static/redfish/v1/schema/OemCrashdump_v1.xml | 33 +++++++ + 5 files changed, 147 insertions(+) + create mode 100644 static/redfish/v1/JsonSchemas/OemCrashdump/OemCrashdump.json + create mode 100644 static/redfish/v1/JsonSchemas/OemCrashdump/index.json + create mode 100644 static/redfish/v1/schema/OemCrashdump_v1.xml + +diff --git a/scripts/update_schemas.py b/scripts/update_schemas.py +index eb6318f..aa92744 100755 +--- a/scripts/update_schemas.py ++++ b/scripts/update_schemas.py +@@ -226,6 +226,11 @@ with open(metadata_index_path, 'w') as metadata_index: + metadata_index.write(" \n") + metadata_index.write(" \n") + ++ metadata_index.write( ++ " \n") ++ metadata_index.write(" \n") ++ metadata_index.write(" \n") ++ + metadata_index.write( + " \n") +diff --git a/static/redfish/v1/$metadata/index.xml b/static/redfish/v1/$metadata/index.xml +index c925581..b0fae5c 100644 +--- a/static/redfish/v1/$metadata/index.xml ++++ b/static/redfish/v1/$metadata/index.xml +@@ -2502,6 +2502,9 @@ + + + ++ ++ ++ + + + +diff --git a/static/redfish/v1/JsonSchemas/OemCrashdump/OemCrashdump.json b/static/redfish/v1/JsonSchemas/OemCrashdump/OemCrashdump.json +new file mode 100644 +index 0000000..7446e93 +--- /dev/null ++++ b/static/redfish/v1/JsonSchemas/OemCrashdump/OemCrashdump.json +@@ -0,0 +1,86 @@ ++{ ++ "$id": "http://redfish.dmtf.org/schemas/v1/OemCrashdump.v1_0_0.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": { ++ "Actions": { ++ "additionalProperties": false, ++ "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": { ++ "#Crashdump.SendRawPeci": { ++ "$ref": "#/definitions/SendRawPeci" ++ } ++ }, ++ "type": "object" ++ }, ++ "SendRawPeci": { ++ "additionalProperties": false, ++ "description": "This action is used to send a raw PECI command to the CPU.", ++ "longDescription": "This action is used to send a raw PECI command to the CPU.", ++ "parameters": { ++ "PECICommands": { ++ "description": "This parameter contains the raw PECI command data to be sent to the CPU.", ++ "items": { ++ "type": "integer" ++ }, ++ "longDescription": "Single array contains arrays of bytes each represents a single full raw PECI command.", ++ "requiredParameter": true, ++ "type": "array" ++ }, ++ "PECIDevice": { ++ "description": "PECI device.", ++ "longDescription": "This represents the PECI device to use for the transaction.", ++ "type": "string" ++ }, ++ "RetryTimingUs": { ++ "description": "This parameter can be used to modify the retry timing in the BMC PECI driver.", ++ "items": { ++ "type": "integer" ++ }, ++ "longDescription": "An array of three-time values in microseconds as: [retryIntervalMin,retryIntervalMax,retryTimeout].", ++ "type": "array" ++ } ++ }, ++ "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": { ++ "target": { ++ "description": "Link to invoke action", ++ "format": "uri-reference", ++ "type": "string" ++ }, ++ "title": { ++ "description": "Friendly action name", ++ "type": "string" ++ } ++ }, ++ "type": "object" ++ } ++ }, ++ "title": "#OemCrashdump" ++} +diff --git a/static/redfish/v1/JsonSchemas/OemCrashdump/index.json b/static/redfish/v1/JsonSchemas/OemCrashdump/index.json +new file mode 100644 +index 0000000..7a19e8e +--- /dev/null ++++ b/static/redfish/v1/JsonSchemas/OemCrashdump/index.json +@@ -0,0 +1,20 @@ ++{ ++ "@odata.context": "/redfish/v1/$metadata#JsonSchemaFile.JsonSchemaFile", ++ "@odata.id": "/redfish/v1/JsonSchemas/OemCrashdump", ++ "@odata.type": "#JsonSchemaFile.v1_0_2.JsonSchemaFile", ++ "Name": "Oem Crashdump Schema File", ++ "Schema": "#OemCrashdump.OemCrashdump", ++ "Description": "Oem Crashdump Schema File Location", ++ "Id": "OemCrashdump", ++ "Languages": [ ++ "en" ++ ], ++ "Languages@odata.count": 1, ++ "Location": [ ++ { ++ "Language": "en", ++ "Uri": "/redfish/v1/JsonSchemas/OemCrashdump/OemCrashdump.json" ++ } ++ ], ++ "Location@odata.count": 1 ++} +diff --git a/static/redfish/v1/schema/OemCrashdump_v1.xml b/static/redfish/v1/schema/OemCrashdump_v1.xml +new file mode 100644 +index 0000000..3570408 +--- /dev/null ++++ b/static/redfish/v1/schema/OemCrashdump_v1.xml +@@ -0,0 +1,33 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0001-Define-Redfish-interface-Registries-Bios.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0001-Define-Redfish-interface-Registries-Bios.patch new file mode 100644 index 000000000..409a2af3f --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0001-Define-Redfish-interface-Registries-Bios.patch @@ -0,0 +1,874 @@ +From 8b21bce7fa0974c454e4bed539be0475738ee21e Mon Sep 17 00:00:00 2001 +From: Kuiying Wang +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 +Signed-off-by: Snehalatha Venkatesh +Signed-off-by: Smriti Ayushi +--- + redfish-core/include/redfish.hpp | 3 + + .../include/registries/bios_registry.hpp | 40 ++ + redfish-core/lib/bios.hpp | 511 ++++++++++++++++++ + redfish-core/lib/message_registries.hpp | 10 +- + 4 files changed, 563 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 a9704d6..e4881ff 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -152,7 +152,10 @@ class RedfishService + requestRoutesSystemActionsReset(app); + requestRoutesSystemResetActionInfo(app); + requestRoutesBiosService(app); ++ requestRoutesBiosSettings(app); ++ requestRoutesBiosAttributeRegistry(app); + requestRoutesBiosReset(app); ++ requestRoutesBiosChangePassword(app); + + #ifdef BMCWEB_ENABLE_VM_NBDPROXY + requestNBDVirtualMediaRoutes(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..bcf6b73 +--- /dev/null ++++ b/redfish-core/include/registries/bios_registry.hpp +@@ -0,0 +1,40 @@ ++/* ++// 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 ++#include ++ ++namespace redfish::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", ++}; ++// BiosAttributeRegistry registry is not defined in DMTF, We should use ++// OEM defined registries for this purpose. ++// Below link is wrong - We need to define OEM registries and use ++// appropriate data here. ++constexpr const char* url = ++ "https://redfish.dmtf.org/registries/BiosAttributeRegistry.1.0.0.json"; ++ ++constexpr std::array registry = {}; ++} // namespace redfish::registries::bios +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index d8475ae..e92e842 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -3,8 +3,140 @@ + #include + #include + #include ++ + namespace redfish + { ++ ++/*baseBIOSTable ++map{attributeName,struct{attributeType,readonlyStatus,displayname, ++ description,menuPath,current,default, ++ array{struct{optionstring,optionvalue}}}} ++*/ ++using BiosBaseTableType = std::vector, std::variant, ++ std::vector< ++ std::tuple>>>>>; ++using BiosBaseTableItemType = std::pair< ++ std::string, ++ std::tuple< ++ std::string, bool, std::string, std::string, std::string, ++ std::variant, std::variant, ++ std::vector< ++ std::tuple>>>>; ++using OptionsItemType = ++ std::tuple>; ++ ++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>>>; ++using PendingAttributesItemType = ++ std::pair>>; ++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. + */ +@@ -23,6 +155,85 @@ inline void + // 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"] = nlohmann::json::object(); ++ ++ 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& retBiosTable) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "getBiosAttributes DBUS error: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ const BiosBaseTableType* baseBiosTable = ++ std::get_if(&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(item.second); ++ std::string attrType = mapAttrTypeToRedfish(itemType); ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if( ++ &std::get(item.second)); ++ attributesJson.emplace( ++ key, currValue != nullptr ? *currValue : ""); ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = std::get_if( ++ &std::get(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()); + } + inline void requestRoutesBiosService(App& app) + { +@@ -31,6 +242,306 @@ inline void requestRoutesBiosService(App& app) + .methods(boost::beast::http::verb::get)(handleBiosServiceGet); + } + ++/** ++ * BiosSettings class supports handle GET/PATCH method for ++ * BIOS configuration pending settings. ++ */ ++inline void requestRoutesBiosSettings(App& app) ++{ ++ BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Bios/Settings") ++ .privileges(redfish::privileges::getBios) ++ .methods(boost::beast::http::verb::get)( ++ [](const crow::Request&, ++ const std::shared_ptr& asyncResp) { ++ asyncResp->res.jsonValue["@odata.id"] = ++ 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& ++ 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( ++ &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(item.second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if( ++ &std::get( ++ item.second)); ++ attributesJson.emplace( ++ key, currValue != nullptr ++ ? *currValue ++ : ""); ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = ++ std::get_if( ++ &std::get( ++ 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()); ++ }); ++} ++/** ++ * BiosAttributeRegistry class supports handle get method for BIOS attribute ++ * registry. ++ */ ++inline void requestRoutesBiosAttributeRegistry(App& app) ++{ ++ BMCWEB_ROUTE( ++ app, ++ "/redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry/") ++ .privileges(redfish::privileges::getBios) ++ .methods( ++ boost::beast::http::verb:: ++ get)([](const crow::Request&, ++ const std::shared_ptr& asyncResp) { ++ 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& ++ retBiosTable) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR ++ << "getBiosAttributeRegistry DBUS error: " ++ << ec; ++ messages::resourceNotFound( ++ asyncResp->res, "Registries/Bios", "Bios"); ++ return; ++ } ++ const BiosBaseTableType* baseBiosTable = ++ std::get_if(&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(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( ++ item.second); ++ attributeItem["DisplayName"] = ++ std::get(item.second); ++ attributeItem["HelpText"] = ++ std::get(item.second); ++ attributeItem["MenuPath"] = ++ std::get(item.second); ++ ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if( ++ &std::get( ++ item.second)); ++ const std::string* defValue = ++ std::get_if( ++ &std::get( ++ item.second)); ++ attributeItem["CurrentValue"] = ++ currValue != nullptr ? *currValue : ""; ++ attributeItem["DefaultValue"] = ++ defValue != nullptr ? *defValue : ""; ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = ++ std::get_if( ++ &std::get( ++ item.second)); ++ const int64_t* defValue = ++ std::get_if( ++ &std::get( ++ 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& ++ optionsVector = ++ std::get(item.second); ++ for (const OptionsItemType& optItem : ++ optionsVector) ++ { ++ nlohmann::json optItemJson; ++ const std::string& strOptItemType = ++ std::get(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::get( ++ optItem)); ++ optItemJson[optItemTypeRedfish] = ++ currValue != nullptr ? *currValue ++ : ""; ++ } ++ else ++ { ++ const int64_t* currValue = ++ std::get_if( ++ &std::get( ++ 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()); ++ }); ++} ++ + /** + * BiosReset class supports handle POST method for Reset bios. + * The class retrieves and sends data directly to D-Bus. +diff --git a/redfish-core/lib/message_registries.hpp b/redfish-core/lib/message_registries.hpp +index c030e5a..d76f70b 100644 +--- a/redfish-core/lib/message_registries.hpp ++++ b/redfish-core/lib/message_registries.hpp +@@ -17,6 +17,7 @@ + + #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" +@@ -128,7 +129,6 @@ inline void handleMessageRegistryGet( + const crow::Request& /*req*/, + const std::shared_ptr& asyncResp, + const std::string& registry, const std::string& registryMatch) +- + { + const registries::Header* header = nullptr; + std::vector registryEntries; +@@ -167,6 +167,14 @@ inline void handleMessageRegistryGet( + registryEntries.emplace_back(&entry); + } + } ++ else if (registry == "BiosAttributeRegistry") ++ { ++ header = ®istries::bios::header; ++ for (const registries::MessageEntry& entry : registries::bios::registry) ++ { ++ registryEntries.emplace_back(&entry); ++ } ++ } + else + { + messages::resourceNotFound( +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0002-BaseBiosTable-Add-support-for-PATCH-operation.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0002-BaseBiosTable-Add-support-for-PATCH-operation.patch new file mode 100644 index 000000000..a52a1614f --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0002-BaseBiosTable-Add-support-for-PATCH-operation.patch @@ -0,0 +1,148 @@ +From ffa924ef204930a5bb442bf654eac02543acfb8f Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Wed, 30 Jun 2021 15:18:46 +0000 +Subject: [PATCH 2/5] 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": , "AttributeType": +, "AttributeValue": }]}' + +* Multiple Attributes: +PATCH https://${bmc}/redfish/v1/Systems/system/Bios/Settings -d +'{"data":[{"AttributeName": , "AttributeType": +, "AttributeValue": }, +{"AttributeName": , "AttributeType": +, "AttributeValue": }]}' + +This makes use of the "Set" of "PendingAttributes" in the +backend and that updates the BaseBiosTable. + +Signed-off-by: Kuiying Wang +Change-Id: I12e78e5ac623c264c7a3e1dd5198aca67172736d +--- + redfish-core/lib/bios.hpp | 95 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 95 insertions(+) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index c1a5c56..14d2171 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; +@@ -370,6 +393,78 @@ inline void requestRoutesBiosSettings(App& app) + "/xyz/openbmc_project/bios_config/manager", + std::array()); + }); ++ ++ BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Bios/Settings") ++ .privileges({{"ConfigureComponents"}}) ++ .methods(boost::beast::http::verb::patch)( ++ [](const crow::Request& req, ++ const std::shared_ptr& asyncResp) { ++ nlohmann::json inpJson; ++ ++ if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "data", ++ inpJson)) ++ { ++ return; ++ } ++ ++ for (auto& attrInfo : inpJson) ++ { ++ std::optional attrName; ++ std::optional attrType; ++ std::optional 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(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/biosconfig/0003-Add-support-to-ResetBios-action.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0003-Add-support-to-ResetBios-action.patch new file mode 100644 index 000000000..5ed92cc3e --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0003-Add-support-to-ResetBios-action.patch @@ -0,0 +1,53 @@ +From b7adca60dd69ac9566dc8f417065e244198fc711 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli +Date: Wed, 6 Oct 2021 22:27:20 +0000 +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: Ic719c55705e5f634539b3dd858b60922e505a8d0 +Signed-off-by: Kuiying Wang +Signed-off-by: AppaRao Puli + +Signed-off-by: AppaRao Puli +--- + redfish-core/lib/bios.hpp | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index f5aa7b7..f613613 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -648,17 +648,23 @@ inline void + handleBiosResetPost(const crow::Request&, + const std::shared_ptr& asyncResp) + { ++ 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; + } + }, +- "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(resetFlag)); + } + + inline void requestRoutesBiosReset(App& app) +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0004-Add-support-to-ChangePassword-action.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0004-Add-support-to-ChangePassword-action.patch new file mode 100644 index 000000000..de705b60b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0004-Add-support-to-ChangePassword-action.patch @@ -0,0 +1,117 @@ +From 22956921a228f6f1cbbbd3045a3cc3969732dca3 Mon Sep 17 00:00:00 2001 +From: Arun Lal K M +Date: Fri, 8 Oct 2021 20:56:00 +0000 +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" +} + +Signed-off-by: Arun Lal K M +Signed-off-by: Kuiying Wang +--- + redfish-core/lib/bios.hpp | 59 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 59 insertions(+) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index f613613..b06a904 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -175,6 +175,10 @@ inline void + 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, "", + true); +@@ -265,6 +269,61 @@ inline void requestRoutesBiosService(App& app) + .methods(boost::beast::http::verb::get)(handleBiosServiceGet); + } + ++/** ++ * BiosChangePassword class supports handle POST method for change bios ++ * password. The class retrieves and sends data directly to D-Bus. ++ */ ++inline void requestRoutesBiosChangePassword(App& app) ++{ ++ BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Bios/") ++ .privileges({{"ConfigureComponents"}}) ++ .methods(boost::beast::http::verb::post)( ++ [](const crow::Request& req, ++ const std::shared_ptr& asyncResp) { ++ std::string currentPassword, newPassword, userName; ++ if (!json_util::readJsonPatch(req, asyncResp->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); ++ }); ++} ++ + /** + * BiosSettings class supports handle GET/PATCH method for + * BIOS configuration pending settings. +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0005-Fix-remove-bios-user-pwd-change-option-via-Redfish.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0005-Fix-remove-bios-user-pwd-change-option-via-Redfish.patch new file mode 100644 index 000000000..26393bfee --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0005-Fix-remove-bios-user-pwd-change-option-via-Redfish.patch @@ -0,0 +1,46 @@ +From edc6925e8c0d9f60da1f70c524261efaf05b2710 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Wed, 30 Jun 2021 15:42:06 +0000 +Subject: [PATCH 5/5] Fix:remove bios user pwd change option via Redfish + +BMC should not provide user bios setup password change option via +Redfish as per bios security requirements. Only Admin BIOS setup +password is supported. + +Added check for the password name action parameter and +do not allow if it has User Password value from redfish side. + +Tested: sent POST query in redfish on URI: +https:///redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword +error occurs for UserPassword parameter and allows for AdminPassword. + +Signed-off-by: Ayushi Smriti +Change-Id: I169cc6a4f786625d9e8b8dfe56816d52b1740f4c +--- + redfish-core/lib/bios.hpp | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 0250c59..360a749 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -323,6 +323,16 @@ inline void requestRoutesBiosChangePassword(App& app) + asyncResp->res, "ChangePassword", "PasswordName"); + return; + } ++ ++ // In Intel BIOS, we are not supporting user password in BIOS ++ // setup ++ if (userName == "UserPassword") ++ { ++ messages::actionParameterUnknown( ++ asyncResp->res, "ChangePassword", "PasswordName"); ++ return; ++ } ++ + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec) { + if (ec) +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0006-Add-fix-for-broken-feature-Pending-Attributes.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0006-Add-fix-for-broken-feature-Pending-Attributes.patch new file mode 100644 index 000000000..05257bbf7 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0006-Add-fix-for-broken-feature-Pending-Attributes.patch @@ -0,0 +1,963 @@ +From bedec07809e2d2ecfed9db0d9509d2380ea094b7 Mon Sep 17 00:00:00 2001 +From: Arun Lal K M +Date: Wed, 19 Jan 2022 14:44:07 +0000 +Subject: [PATCH] Add fix for broken feature 'Pending Attributes'. + +Fix is added for the following: +1) GET to 'redfish/v1/Systems/system/Bios'. +2) PATCH to 'redfish/v1/Systems/system/Bios/Settings'. +3) GET to 'redfish/v1/Systems/system/Bios/Settings'. +4) Fix for incremental duplicate values in BiosAttributeRegistry. +5) POST to '/redfish/v1/Systems/system/Bios/Actions + /Bios.ChangePassword/'. +6) Add support for Enumeration. +7) Support DMTF standard of displaying "Value" in BIOS attribute registries. + +Tested: +By giving PATCH to 'redfish/v1/Systems/system/Bios/Settings' +PATCH command raw data: +{ + "data":{ + "AmpPrefetchEnable": "0x1", + "Ce2LmLoggingEn": "0x1", + "DfxEadrDebugLogs": "0x2", + "PsfUrEnable": "0x1", + "ATS": "0x0" + } +} + +Response: +{ + "@Message.ExtendedInfo": [ + { + "@odata.type": "#Message.v1_1_1.Message", + "Message": "Successfully Completed Request", + "MessageArgs": [], + "MessageId": "Base.1.8.1.Success", + "MessageSeverity": "OK", + "Resolution": "None" + } + ] +} + +By giving GET to 'redfish/v1/Systems/system/Bios' +Response: +{ + "@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": { + "AEPErrorInjEn": "0x00", + "ARIEnable": "0x01", + "ARIForward": "0x00", + ... + ... + ... + "txEqCalibration": "0x01", + "volMemMode": "0x00", + "wrVrefCenter": "0x01" + }, + "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" +} + +By giving GET to 'redfish/v1/Systems/system/Bios/Settings' +Response: +{ + "@odata.id": "/redfish/v1/Systems/system/Bios/Settings", + "@odata.type": "#Bios.v1_1_0.Bios", + "AttributeRegistry": "BiosAttributeRegistry", + "Attributes": { + "ATS": "0x0", + "AmpPrefetchEnable": "0x1", + "Ce2LmLoggingEn": "0x1", + "DfxEadrDebugLogs": "0x2", + "PsfUrEnable": "0x1" + }, + "Id": "BiosSettingsV1", + "Name": "Bios Settings Version 1" +} + +By giving POST to '/redfish/v1/Systems/system/Bios/Actions +/Bios.ChangePassword/' +Response: Success + +By running Redfish-Service-Validator +Result: +Elapsed time: 0:09:36 +invalidPropertyValue: 108 +metadataNamespaces: 2185 +missingNamespaces: 1 +optionalAction: 9 +pass: 13772 +passAction: 22 +passGet: 541 +reflink: 1 +repeat: 47 +serviceNamespaces: 75 +skipOptional: 9276 +unverifiedComplexAdditional: 1 +warnDeprecated: 230 +warningPresent: 54 +Validation has succeeded. + +Signed-off-by: Arun Lal K M +Signed-off-by: Snehalatha Venkatesh +--- + redfish-core/lib/bios.hpp | 605 +++++++++++++++++++++++++------------- + 1 file changed, 394 insertions(+), 211 deletions(-) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index bbf9460..c3192dd 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -12,13 +12,15 @@ map{attributeName,struct{attributeType,readonlyStatus,displayname, + description,menuPath,current,default, + array{struct{optionstring,optionvalue}}}} + */ +-using BiosBaseTableType = std::vector, std::variant, + std::vector< +- std::tuple>>>>>; ++ std::tuple>>>>; ++ + using BiosBaseTableItemType = std::pair< + std::string, + std::tuple< +@@ -29,6 +31,13 @@ using BiosBaseTableItemType = std::pair< + using OptionsItemType = + std::tuple>; + ++using PendingAttributesType = boost::container::flat_map< ++ std::string, std::tuple>>; ++ ++using PendingAttributesItemType = ++ std::pair>>; ++ + enum BiosBaseTableIndex + { + biosBaseAttrType = 0, +@@ -45,17 +54,7 @@ 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>>>; +-using PendingAttributesItemType = +- std::pair>>; ++ + enum PendingAttributesIndex + { + pendingAttrType = 0, +@@ -64,30 +63,20 @@ enum PendingAttributesIndex + 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") ++ 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") ++ "Manager.AttributeType.Enumeration") + { +- ret = "Boolean"; ++ ret = "Enumeration"; + } + else + { +@@ -96,29 +85,7 @@ 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; +@@ -201,6 +168,15 @@ inline void + + return; + } ++ ++ if (getObjectType.empty()) ++ { ++ BMCWEB_LOG_ERROR << "getObjectType is empty."; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ + const std::string& service = getObjectType.begin()->first; + + crow::connections::systemBus->async_method_call( +@@ -220,7 +196,7 @@ inline void + asyncResp->res.jsonValue["Attributes"]; + if (baseBiosTable == nullptr) + { +- BMCWEB_LOG_ERROR << "baseBiosTable == nullptr "; ++ BMCWEB_LOG_ERROR << "baseBiosTable is empty"; + messages::internalError(asyncResp->res); + return; + } +@@ -230,7 +206,7 @@ inline void + const std::string& itemType = + std::get(item.second); + std::string attrType = mapAttrTypeToRedfish(itemType); +- if (attrType == "String") ++ if (attrType == "String" || attrType == "Enumeration") + { + const std::string* currValue = + std::get_if( +@@ -248,7 +224,6 @@ inline void + else + { + BMCWEB_LOG_ERROR << "Unsupported attribute type."; +- messages::internalError(asyncResp->res); + } + } + }, +@@ -275,8 +250,9 @@ inline void requestRoutesBiosService(App& app) + */ + inline void requestRoutesBiosChangePassword(App& app) + { +- BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Bios/") +- .privileges({{"ConfigureComponents"}}) ++ BMCWEB_ROUTE(app, ++ "/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword/") ++ .privileges(redfish::privileges::postBios) + .methods(boost::beast::http::verb::post)( + [](const crow::Request& req, + const std::shared_ptr& asyncResp) { +@@ -342,180 +318,303 @@ inline void requestRoutesBiosSettings(App& app) + { + BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Bios/Settings") + .privileges(redfish::privileges::getBios) +- .methods(boost::beast::http::verb::get)( +- [](const crow::Request&, +- const std::shared_ptr& asyncResp) { +- asyncResp->res.jsonValue["@odata.id"] = +- 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"] = {}; ++ .methods( ++ boost::beast::http::verb:: ++ get)([](const crow::Request&, ++ const std::shared_ptr& asyncResp) { ++ 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); ++ 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& +- 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( +- &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) ++ return; ++ } ++ ++ if (getObjectType.empty()) ++ { ++ BMCWEB_LOG_ERROR << "getObjectType is empty."; ++ 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& ++ 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( ++ &retPendingAttributes); ++ nlohmann::json& attributesJson = ++ asyncResp->res.jsonValue["Attributes"]; ++ if (pendingAttributes == nullptr) ++ { ++ BMCWEB_LOG_ERROR ++ << "pendingAttributes is empty"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ for (const PendingAttributesItemType& ++ pendingAttributesItem : *pendingAttributes) ++ { ++ const std::string& biosAttrType = ++ std::get( ++ pendingAttributesItem.second); ++ ++ std::string itemType = ++ mapAttrTypeToRedfish(biosAttrType); ++ ++ if (itemType == "String" || ++ itemType == "Enumeration") + { +- const std::string& key = item.first; +- const std::string& itemType = +- std::get(item.second); +- std::string attrType = +- mapAttrTypeToRedfish(itemType); +- if (attrType == "String") +- { +- const std::string* currValue = +- std::get_if( +- &std::get( +- item.second)); +- attributesJson.emplace( +- key, currValue != nullptr +- ? *currValue +- : ""); +- } +- else if (attrType == "Integer") ++ const std::string* currValue = ++ std::get_if( ++ &std::get( ++ pendingAttributesItem.second)); ++ ++ if (!currValue) + { +- const int64_t* currValue = +- std::get_if( +- &std::get( +- item.second)); +- attributesJson.emplace( +- key, currValue != nullptr +- ? *currValue +- : 0); ++ BMCWEB_LOG_ERROR ++ << "No string data in pending " ++ "attributes item data"; ++ messages::internalError(asyncResp->res); ++ return; + } +- else ++ ++ attributesJson.emplace( ++ pendingAttributesItem.first, ++ *currValue); ++ } ++ else if (itemType == "Integer") ++ { ++ const int64_t* currValue = ++ std::get_if( ++ &std::get( ++ pendingAttributesItem.second)); ++ ++ if (!currValue) + { + BMCWEB_LOG_ERROR +- << "Unsupported attribute type."; ++ << "No int64_t data in pending " ++ "attributes item data"; + messages::internalError(asyncResp->res); ++ return; + } ++ ++ attributesJson.emplace( ++ pendingAttributesItem.first, ++ *currValue); + } +- }, +- 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()); +- }); ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "Unsupported attribute type."; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ } ++ }, ++ 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()); ++ }); + + BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Bios/Settings") +- .privileges({{"ConfigureComponents"}}) +- .methods(boost::beast::http::verb::patch)( +- [](const crow::Request& req, +- const std::shared_ptr& asyncResp) { +- nlohmann::json inpJson; ++ .privileges(redfish::privileges::patchBios) ++ .methods( ++ boost::beast::http::verb:: ++ patch)([](const crow::Request& req, ++ const std::shared_ptr& asyncResp) { ++ nlohmann::json inpJson; + +- if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "data", +- inpJson)) +- { +- return; +- } ++ if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "data", ++ inpJson)) ++ { ++ BMCWEB_LOG_ERROR << "No 'data' in req!"; ++ return; ++ } + +- for (auto& attrInfo : inpJson) +- { +- std::optional attrName; +- std::optional attrType; +- std::optional 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)) ++ if (inpJson.empty()) ++ { ++ messages::invalidObject(asyncResp->res, ++ crow::utility::urlFromPieces("data")); ++ BMCWEB_LOG_ERROR << "No input in req!"; ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, inpJson](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) + { +- messages::propertyMissing(asyncResp->res, +- "AttributeValue"); ++ BMCWEB_LOG_ERROR ++ << "ObjectMapper::GetObject call failed: " << ec; ++ messages::internalError(asyncResp->res); ++ + return; + } +- std::string biosAttrType = mapRedfishToAttrType(*attrType); + +- if (biosAttrType == "UNKNOWN") ++ if (getObjectType.empty()) + { +- BMCWEB_LOG_ERROR << "Invalid attribute type"; +- messages::propertyValueNotInList( +- asyncResp->res, "AttributeType", *attrType); ++ BMCWEB_LOG_ERROR << "getObjectType is empty."; ++ messages::internalError(asyncResp->res); ++ + return; + } + +- PendingAttributesType pendingAttributes; +- pendingAttributes.emplace_back(std::make_pair( +- *attrName, std::make_tuple(biosAttrType, *attrValue))); ++ std::string service = getObjectType.begin()->first; + + crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec) { ++ [asyncResp, ++ inpJson](const boost::system::error_code ec, ++ const std::variant& ++ retBiosTable) { + if (ec) + { + BMCWEB_LOG_ERROR +- << "doPatch resp_handler got error " << ec; ++ << "getBiosAttributes DBUS error: " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const BiosBaseTableType* baseBiosTable = ++ std::get_if(&retBiosTable); ++ ++ if (baseBiosTable == nullptr) ++ { ++ BMCWEB_LOG_ERROR << "baseBiosTable is empty."; + messages::internalError(asyncResp->res); + return; + } ++ ++ PendingAttributesType pendingAttributes{}; ++ ++ for (nlohmann::detail::iteration_proxy_value< ++ nlohmann::detail::iter_impl< ++ const nlohmann::basic_json<>>>& ++ attributes : inpJson.items()) ++ { ++ BiosBaseTableType::const_iterator knobIter = ++ baseBiosTable->find(attributes.key()); ++ if (knobIter == baseBiosTable->end()) ++ { ++ BMCWEB_LOG_ERROR << "Cannot find " ++ << attributes.key() ++ << " in baseBiosTable"; ++ messages::propertyValueNotInList( ++ asyncResp->res, attributes.key(), ++ "data"); ++ return; ++ } ++ ++ const std::string& itemType = ++ std::get( ++ knobIter->second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ ++ if (attrType == "String" || ++ attrType == "Enumeration") ++ { ++ std::string val = attributes.value(); ++ ++ pendingAttributes.emplace( ++ attributes.key(), ++ std::make_tuple(itemType, val)); ++ } ++ else if (attrType == "Integer") ++ { ++ pendingAttributes.emplace( ++ attributes.key(), ++ std::make_tuple( ++ itemType, static_cast( ++ attributes.value()))); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "UNKNOWN attrType == " ++ << itemType; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ } ++ ++ if (pendingAttributes.empty()) ++ { ++ BMCWEB_LOG_ERROR ++ << "pendingAttributes is empty."; ++ messages::invalidObject( ++ asyncResp->res, ++ crow::utility::urlFromPieces("data")); ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp]( ++ const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR ++ << "doPatch resp_handler got error " ++ << ec << "\n"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ messages::success(asyncResp->res); ++ }, ++ "xyz.openbmc_project.BIOSConfigManager", ++ "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.BIOSConfig.Manager", ++ "PendingAttributes", ++ std::variant( ++ pendingAttributes)); + }, +- "xyz.openbmc_project.BIOSConfigManager", +- "/xyz/openbmc_project/bios_config/manager", +- "org.freedesktop.DBus.Properties", "Set", ++ service, "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Get", + "xyz.openbmc_project.BIOSConfig.Manager", +- "PendingAttributes", +- std::variant(pendingAttributes)); +- } +- }); ++ "BaseBIOSTable"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/bios_config/manager", ++ std::array()); ++ }); + } + /** + * BiosAttributeRegistry class supports handle get method for BIOS attribute +@@ -555,6 +654,15 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + + return; + } ++ ++ if (getObjectType.empty()) ++ { ++ BMCWEB_LOG_ERROR << "getObjectType is empty."; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ + std::string service = getObjectType.begin()->first; + + crow::connections::systemBus->async_method_call( +@@ -575,8 +683,6 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + nlohmann::json& attributeArray = + asyncResp->res + .jsonValue["RegistryEntries"]["Attributes"]; +- nlohmann::json optionsArray = +- nlohmann::json::array(); + if (baseBiosTable == nullptr) + { + BMCWEB_LOG_ERROR << "baseBiosTable == nullptr "; +@@ -592,10 +698,11 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + mapAttrTypeToRedfish(itemType); + if (attrType == "UNKNOWN") + { +- BMCWEB_LOG_ERROR << "attrType == UNKNOWN"; +- messages::internalError(asyncResp->res); +- return; ++ BMCWEB_LOG_ERROR << "UNKNOWN attrType == " ++ << itemType; ++ continue; + } ++ + nlohmann::json attributeItem; + attributeItem["AttributeName"] = item.first; + attributeItem["Type"] = attrType; +@@ -609,16 +716,37 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + attributeItem["MenuPath"] = + std::get(item.second); + +- if (attrType == "String") ++ if (attrType == "String" || ++ attrType == "Enumeration") + { + const std::string* currValue = + std::get_if( + &std::get( + item.second)); ++ ++ if (!currValue) ++ { ++ BMCWEB_LOG_ERROR ++ << "Unable to get currValue, no " ++ "std::string data in BIOS " ++ "attributes item data"; ++ continue; ++ } ++ + const std::string* defValue = + std::get_if( + &std::get( + item.second)); ++ ++ if (!defValue) ++ { ++ BMCWEB_LOG_ERROR ++ << "Unable to get defValue, no " ++ "std::string data in BIOS " ++ "attributes item data"; ++ continue; ++ } ++ + attributeItem["CurrentValue"] = + currValue != nullptr ? *currValue : ""; + attributeItem["DefaultValue"] = +@@ -630,10 +758,30 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + std::get_if( + &std::get( + item.second)); ++ ++ if (!currValue) ++ { ++ BMCWEB_LOG_ERROR ++ << "Unable to get currValue, no " ++ "int64_t data in BIOS " ++ "attributes item data"; ++ continue; ++ } ++ + const int64_t* defValue = + std::get_if( + &std::get( + item.second)); ++ ++ if (!defValue) ++ { ++ BMCWEB_LOG_ERROR ++ << "Unable to get defValue, no " ++ "int64_t data in BIOS " ++ "attributes item data"; ++ continue; ++ } ++ + attributeItem["CurrentValue"] = + currValue != nullptr ? *currValue : 0; + attributeItem["DefaultValue"] = +@@ -641,12 +789,13 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + } + else + { +- BMCWEB_LOG_ERROR +- << "Unsupported attribute type."; +- messages::internalError(asyncResp->res); +- return; ++ BMCWEB_LOG_ERROR << "UNKNOWN attrType == " ++ << itemType; ++ continue; + } + ++ nlohmann::json optionsArray = ++ nlohmann::json::array(); + const std::vector& + optionsVector = + std::get(item.second); +@@ -661,9 +810,9 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + if (optItemTypeRedfish == "UNKNOWN") + { + BMCWEB_LOG_ERROR +- << "optItemTypeRedfish == UNKNOWN"; +- messages::internalError(asyncResp->res); +- return; ++ << "UNKNOWN optItemTypeRedfish == " ++ << strOptItemType; ++ continue; + } + if (optItemTypeRedfish == "OneOf") + { +@@ -671,7 +820,21 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + std::get_if( + &std::get( + optItem)); +- optItemJson[optItemTypeRedfish] = ++ ++ if (!currValue) ++ { ++ BMCWEB_LOG_ERROR ++ << "Unable to get currValue, " ++ "no " ++ "std::string data in option " ++ "item value"; ++ continue; ++ } ++ ++ optItemJson["ValueDisplayName"] = ++ currValue != nullptr ? *currValue ++ : ""; ++ optItemJson["ValueName"] = + currValue != nullptr ? *currValue + : ""; + } +@@ -681,7 +844,21 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + std::get_if( + &std::get( + optItem)); +- optItemJson[optItemTypeRedfish] = ++ ++ if (!currValue) ++ { ++ BMCWEB_LOG_ERROR ++ << "Unable to get currValue, " ++ "no " ++ "int64_t data in option " ++ "item value"; ++ continue; ++ } ++ ++ optItemJson["ValueDisplayName"] = ++ currValue != nullptr ? *currValue ++ : 0; ++ optItemJson["ValueName"] = + currValue != nullptr ? *currValue + : 0; + } +@@ -689,6 +866,12 @@ inline void requestRoutesBiosAttributeRegistry(App& app) + optionsArray.push_back(optItemJson); + } + ++ if (optionsArray.empty()) ++ { ++ BMCWEB_LOG_ERROR << "optionsArray is empty"; ++ continue; ++ } ++ + attributeItem["Value"] = optionsArray; + attributeArray.push_back(attributeItem); + } +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0007-Add-BiosAttributeRegistry-node-under-Registries.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0007-Add-BiosAttributeRegistry-node-under-Registries.patch new file mode 100644 index 000000000..dbd5700be --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/biosconfig/0007-Add-BiosAttributeRegistry-node-under-Registries.patch @@ -0,0 +1,76 @@ +From dd8160fefbac27d7f2ec99bf8b54c599083bcdbd Mon Sep 17 00:00:00 2001 +From: Snehalatha Venkatesh +Date: Tue, 28 Dec 2021 11:05:50 +0000 +Subject: [PATCH] Add BiosAttributeRegistry node under Registries. + +/redfish/v1/Registries/ is missing node BiosAttributeRegistry +under it. Added code to fix the same. + +Tested: +1.Ran Redfish Validator and passed. +2.GET - /redfish/v1/Registries/ + +Response: +{ +"@odata.id": "/redfish/v1/Registries", +"@odata.type": "#MessageRegistryFileCollection.MessageRegistryFileCollection", +"Description": "Collection of MessageRegistryFiles", +"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" +} +], +"Members@odata.count": 5, +"Name": "MessageRegistryFile Collection" +} + +Signed-off-by: Snehalatha Venkatesh +--- + redfish-core/lib/message_registries.hpp | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/redfish-core/lib/message_registries.hpp b/redfish-core/lib/message_registries.hpp +index d76f70b..4ae8383 100644 +--- a/redfish-core/lib/message_registries.hpp ++++ b/redfish-core/lib/message_registries.hpp +@@ -41,11 +41,12 @@ inline void handleMessageRegistryFileCollectionGet( + {"@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"}}}}}; + } + +@@ -89,6 +90,11 @@ inline void handleMessageRoutesMessageRegistryFileGet( + header = ®istries::resource_event::header; + url = registries::resource_event::url; + } ++ else if (registry == "BiosAttributeRegistry") ++ { ++ header = ®istries::bios::header; ++ dmtf.clear(); ++ } + else + { + messages::resourceNotFound( +-- +2.25.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/eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch new file mode 100644 index 000000000..5cb5c538c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch @@ -0,0 +1,163 @@ +From d5ade2d2032d7bb0c17c92e957c2a4f6e77af2ee Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Mon, 6 Dec 2021 19:49:01 +0000 +Subject: [PATCH] Add unmerged changes for http retry support + +The http retry support added upstream as a single patch was slpit into +3 patches, but only 2 of them was merged. +This commit pulls in the differentail changes required to complete the +entire http retry support. and also allow for other subsequent patches +to be appplied easily. + +Change-Id: I43e68eeffb8d69c289dd306c1c7cafc87ad766a0 +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/http_client.hpp | 32 ++++++++++++++++--- + .../include/event_service_manager.hpp | 26 +++++++++------ + redfish-core/lib/event_service.hpp | 1 + + 3 files changed, 45 insertions(+), 14 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 5f352d3..f0152db 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -188,6 +188,17 @@ class HttpClient : public std::enable_shared_from_this + BMCWEB_LOG_DEBUG << "recvMessage() data: " + << self->parser->get(); + ++ // Check if the response and header are received ++ if (!self->parser->is_done()) ++ { ++ // The parser failed to receive the response ++ BMCWEB_LOG_ERROR ++ << "recvMessage() parser failed to receive response"; ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } ++ + unsigned int respCode = self->parser->get().result_int(); + BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " + << respCode; +@@ -379,15 +390,17 @@ class HttpClient : public std::enable_shared_from_this + public: + explicit HttpClient(boost::asio::io_context& ioc, const std::string& id, + const std::string& destIP, const std::string& destPort, +- const std::string& destUri, +- const boost::beast::http::fields& httpHeader) : ++ const std::string& destUri) : + conn(ioc), +- timer(ioc), +- req(boost::beast::http::verb::post, destUri, 11, "", httpHeader), +- subId(id), host(destIP), port(destPort) ++ timer(ioc), req(boost::beast::http::verb::post, destUri, 11), subId(id), ++ host(destIP), port(destPort) + { ++ // Set the request header + req.set(boost::beast::http::field::host, host); ++ req.set(boost::beast::http::field::content_type, "application/json"); + req.keep_alive(true); ++ ++ requestDataQueue.set_capacity(maxRequestQueueSize); + } + + void sendData(const std::string& data) +@@ -408,6 +421,15 @@ class HttpClient : public std::enable_shared_from_this + } + } + ++ void setHeaders(const boost::beast::http::fields& httpHeaders) ++ { ++ // Set custom headers ++ for (const auto& header : httpHeaders) ++ { ++ req.set(header.name(), header.value()); ++ } ++ } ++ + void setRetryConfig(const uint32_t retryAttempts, + const uint32_t retryTimeoutInterval) + { +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index e879f9e..75380dc 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -370,7 +370,10 @@ class Subscription : public persistent_data::UserSubscription + eventSeqNum(1), + host(inHost), port(inPort), path(inPath), uriProto(inUriProto) + { +- // Subscription constructor ++ // create the HttpClient connection ++ conn = std::make_shared( ++ crow::connections::systemBus->get_io_context(), id, host, port, ++ path); + } + + Subscription(const std::shared_ptr& adaptor) : +@@ -391,17 +394,12 @@ class Subscription : public persistent_data::UserSubscription + return false; + } + +- if (conn == nullptr) ++ if (conn != nullptr) + { +- // create the HttpClient connection +- conn = std::make_shared( +- crow::connections::systemBus->get_io_context(), id, host, port, +- path, httpHeaders); ++ conn->sendData(msg); ++ eventSeqNum++; + } + +- conn->sendData(msg); +- eventSeqNum++; +- + if (sseConn != nullptr) + { + sseConn->sendData(eventSeqNum, msg); +@@ -548,6 +546,14 @@ class Subscription : public persistent_data::UserSubscription + } + } + ++ void updatehttpHeaders() ++ { ++ if (conn != nullptr) ++ { ++ conn->setHeaders(httpHeaders); ++ } ++ } ++ + uint64_t getEventSeqNum() const + { + return eventSeqNum; +@@ -661,6 +667,7 @@ class EventServiceManager + // Update retry configuration. + subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); + subValue->updateRetryPolicy(); ++ subValue->updatehttpHeaders(); + } + } + +@@ -915,6 +922,7 @@ class EventServiceManager + // Update retry configuration. + subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); + subValue->updateRetryPolicy(); ++ subValue->updatehttpHeaders(); + + return id; + } +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 9ce2f05..0903874 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -619,6 +619,7 @@ inline void requestRoutesEventDestination(App& app) + } + } + subValue->httpHeaders = fields; ++ subValue->updatehttpHeaders(); + } + + if (retryPolicy) +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch new file mode 100644 index 000000000..9f51d23d3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch @@ -0,0 +1,448 @@ +From 0dc82b56bae36aee8ebc1923e909e3ce74f6fdbc Mon Sep 17 00:00:00 2001 +From: AppaRao Puli +Date: Mon, 6 Dec 2021 21:39:05 +0000 +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: I480085344ba7bed6ec0d94876eda1d252e51cb45 +Signed-off-by: AppaRao Puli +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/http_client.hpp | 309 ++++++++++++------ + .../include/event_service_manager.hpp | 2 +- + 2 files changed, 204 insertions(+), 107 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index e69d397..7328eae 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -20,6 +20,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -44,6 +45,8 @@ enum class ConnState + resolveFailed, + connectInProgress, + connectFailed, ++ handshakeInProgress, ++ handshakeFailed, + connected, + sendInProgress, + sendFailed, +@@ -62,7 +65,9 @@ class HttpClient : public std::enable_shared_from_this + { + private: + crow::async_resolve::Resolver resolver; ++ boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client}; + boost::beast::tcp_stream conn; ++ std::optional> sslConn; + boost::asio::steady_timer timer; + boost::beast::flat_static_buffer buffer; + boost::beast::http::request req; +@@ -110,25 +115,52 @@ class HttpClient : public std::enable_shared_from_this + const std::vector& endpointList) + { + state = ConnState::connectInProgress; ++ sslConn.emplace(conn, ctx); + + BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code ec, ++ const boost::asio::ip::tcp::endpoint& endpoint) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Connect " << endpoint ++ << " failed: " << ec.message(); ++ self->state = ConnState::connectFailed; ++ self->handleConnState(); ++ return; ++ } + ++ BMCWEB_LOG_DEBUG << "Connected to: " << endpoint; ++ if (self->sslConn) ++ { ++ self->performHandshake(); ++ } ++ else ++ { ++ self->handleConnState(); ++ } ++ }; + conn.expires_after(std::chrono::seconds(30)); +- conn.async_connect( +- endpointList, [self(shared_from_this())]( +- const boost::beast::error_code ec, +- const boost::asio::ip::tcp::endpoint& endpoint) { ++ conn.async_connect(endpointList, std::move(respHandler)); ++ } ++ ++ void performHandshake() ++ { ++ state = ConnState::handshakeInProgress; ++ ++ sslConn->async_handshake( ++ boost::asio::ssl::stream_base::client, ++ [self(shared_from_this())](const boost::beast::error_code ec) { + if (ec) + { +- BMCWEB_LOG_ERROR << "Connect " +- << endpoint.address().to_string() +- << " failed: " << ec.message(); +- self->state = ConnState::connectFailed; ++ BMCWEB_LOG_ERROR << "SSL handshake failed: " ++ << ec.message(); ++ self->state = ConnState::handshakeFailed; + self->handleConnState(); + return; + } +- BMCWEB_LOG_DEBUG << "Connected to: " +- << endpoint.address().to_string(); ++ ++ BMCWEB_LOG_DEBUG << "SSL Handshake successfull"; + self->state = ConnState::connected; + self->handleConnState(); + }); +@@ -141,124 +173,182 @@ class HttpClient : public std::enable_shared_from_this + 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->state = ConnState::sendFailed; ++ self->handleConnState(); ++ return; ++ } + +- // 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->handleConnState(); +- return; +- } +- BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " +- << bytesTransferred; +- boost::ignore_unused(bytesTransferred); ++ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); ++ self->recvMessage(); ++ }; + +- self->recvMessage(); +- }); ++ // Set a timeout on the operation ++ 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() + { + state = ConnState::recvInProgress; + +- parser.emplace(std::piecewise_construct, std::make_tuple()); +- parser->body_limit(httpReadBodyLimit); ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code ec, ++ const std::size_t& bytesTransferred) { ++ if (ec && ec != boost::asio::ssl::error::stream_truncated) ++ { ++ BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); + +- // Receive the HTTP response +- boost::beast::http::async_read( +- conn, buffer, *parser, +- [self(shared_from_this())](const boost::beast::error_code& ec, +- const std::size_t& bytesTransferred) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "recvMessage() failed: " +- << ec.message(); +- self->state = ConnState::recvFailed; +- self->handleConnState(); +- return; +- } +- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " +- << bytesTransferred; +- BMCWEB_LOG_DEBUG << "recvMessage() data: " +- << self->parser->get().body(); ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } + +- // Check if the response and header are received +- if (!self->parser->is_done()) +- { +- // The parser failed to receive the response +- BMCWEB_LOG_ERROR +- << "recvMessage() parser failed to receive response"; +- self->state = ConnState::recvFailed; +- self->handleConnState(); +- return; +- } ++ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); ++ ++ // Check if the response and header are received ++ if (!self->parser->is_done()) ++ { ++ // The parser failed to receive the response ++ BMCWEB_LOG_ERROR ++ << "recvMessage() parser failed to receive response"; ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } + +- unsigned int respCode = self->parser->get().result_int(); +- BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " +- << respCode; ++ unsigned int respCode = self->parser->get().result_int(); ++ BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " ++ << respCode; + +- // 2XX response is considered to be successful +- if ((respCode < 200) || (respCode >= 300)) +- { +- // The listener failed to receive the Sent-Event +- BMCWEB_LOG_ERROR +- << "recvMessage() Listener Failed to " +- "receive Sent-Event. Header Response Code: " +- << respCode; +- self->state = ConnState::recvFailed; +- self->handleConnState(); +- return; +- } ++ // 2XX response is considered to be successful ++ if ((respCode < 200) || (respCode >= 300)) ++ { ++ // The listener failed to receive the Sent-Event ++ BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " ++ "receive Sent-Event"; ++ self->state = ConnState::recvFailed; ++ self->handleConnState(); ++ return; ++ } + +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- if (!self->requestDataQueue.empty()) +- { +- self->requestDataQueue.pop_front(); +- } +- self->state = ConnState::idle; ++ // Send is successful, Lets remove data from queue ++ // check for next request data in queue. ++ if (!self->requestDataQueue.empty()) ++ { ++ self->requestDataQueue.pop_front(); ++ } ++ self->state = ConnState::idle; ++ // Keep the connection alive if server supports it ++ // Else close the connection ++ BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " ++ << self->parser->keep_alive(); ++ if (!self->parser->keep_alive()) ++ { ++ // Abort the connection since server is not keep-alive enabled ++ self->state = ConnState::abortConnection; ++ } + +- // Keep the connection alive if server supports it +- // Else close the connection +- BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " +- << self->parser->keep_alive(); +- if (!self->parser->keep_alive()) +- { +- // Abort the connection since server is not keep-alive +- // enabled +- self->state = ConnState::abortConnection; +- } ++ // Returns ownership of the parsed message ++ self->parser->release(); + +- self->handleConnState(); +- }); +- } ++ self->handleConnState(); ++ }; ++ parser.emplace(std::piecewise_construct, std::make_tuple()); ++ parser->body_limit(httpReadBodyLimit); + ++ // Check only for the response header ++ parser->skip(true); ++ 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 doClose() + { + state = ConnState::closeInProgress; +- boost::beast::error_code ec; +- conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); +- conn.close(); + +- // not_connected happens sometimes so don't bother reporting it. +- if (ec && ec != boost::beast::errc::not_connected) ++ // Set the timeout on the tcp stream socket for the async operation ++ conn.expires_after(std::chrono::seconds(30)); ++ if (sslConn) + { +- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); +- return; ++ sslConn->async_shutdown([self = shared_from_this()]( ++ const boost::system::error_code ec) { ++ if (ec) ++ { ++ // 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_INFO << "doClose(): Connection " ++ "closed by server. "; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "doClose() failed: " ++ << ec.message(); ++ } ++ } ++ else ++ { ++ BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; ++ } ++ self->conn.close(); ++ ++ if ((self->state != ConnState::suspended) && ++ (self->state != ConnState::terminated)) ++ { ++ self->state = ConnState::closed; ++ self->handleConnState(); ++ } ++ }); + } +- BMCWEB_LOG_DEBUG << "Connection closed gracefully"; +- if ((state != ConnState::suspended) && (state != ConnState::terminated)) ++ else + { +- state = ConnState::closed; +- handleConnState(); ++ boost::beast::error_code ec; ++ conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ++ ec); ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "doClose() failed: " << ec.message(); ++ } ++ else ++ { ++ BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; ++ } ++ conn.close(); ++ ++ if ((state != ConnState::suspended) && ++ (state != ConnState::terminated)) ++ { ++ state = ConnState::closed; ++ handleConnState(); ++ } + } + } + +@@ -330,6 +420,7 @@ class HttpClient : public std::enable_shared_from_this + { + case ConnState::resolveInProgress: + case ConnState::connectInProgress: ++ case ConnState::handshakeInProgress: + case ConnState::sendInProgress: + case ConnState::recvInProgress: + case ConnState::closeInProgress: +@@ -356,6 +447,7 @@ class HttpClient : public std::enable_shared_from_this + } + case ConnState::resolveFailed: + case ConnState::connectFailed: ++ case ConnState::handshakeFailed: + case ConnState::sendFailed: + case ConnState::recvFailed: + case ConnState::retry: +@@ -392,7 +484,8 @@ class HttpClient : public std::enable_shared_from_this + public: + explicit HttpClient(boost::asio::io_context& ioc, const std::string& id, + const std::string& destIP, const std::string& destPort, +- const std::string& destUri) : ++ const std::string& destUri, ++ const std::string& uriProto) : + conn(ioc), + timer(ioc), req(boost::beast::http::verb::post, destUri, 11), subId(id), + host(destIP), port(destPort) +@@ -403,6 +496,10 @@ class HttpClient : public std::enable_shared_from_this + req.keep_alive(true); + + requestDataQueue.set_capacity(maxRequestQueueSize); ++ if (uriProto == "https") ++ { ++ sslConn.emplace(conn, ctx); ++ } + } + + void sendData(const std::string& data) +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index bc97219..64cb43a 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -372,7 +372,7 @@ class Subscription : public persistent_data::UserSubscription + // create the HttpClient connection + conn = std::make_shared( + crow::connections::systemBus->get_io_context(), id, host, port, +- path); ++ path, uriProto); + } + + Subscription(const std::shared_ptr& adaptor) : +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch new file mode 100644 index 000000000..bce9fc939 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch @@ -0,0 +1,471 @@ +From 6134487ccc46ace1943184f40c42184e3aa85881 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli +Date: Fri, 12 Mar 2021 18:53:25 +0000 +Subject: [PATCH] Add Server-Sent-Events support + +Server-Sent Events is a standard describing how servers can +initiate data transmission towards clients once an initial +client connection has been established. Unlike websockets +(which are bidirectional), Server-Sent Events are +unidirectional and commonly used to send message updates or +continuous data streams to a browser client. + +This is base patch for adding Server-Sent events support to +bmcweb. Redfish eventservice SSE style subscription uses +this and will be loaded on top of this commit. + +Tested: + - Tested using follow-up patch on top which adds + support for Redfish EventService SSE style subscription + and observed events are getting sent periodically. + +Change-Id: I36956565cbba30c2007852c9471f477f6d1736e9 +Signed-off-by: AppaRao Puli +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/http_connection.hpp | 10 +- + http/http_response.hpp | 7 +- + http/routing.hpp | 71 ++++++++++ + http/server_sent_event.hpp | 282 +++++++++++++++++++++++++++++++++++++ + 4 files changed, 365 insertions(+), 5 deletions(-) + create mode 100644 http/server_sent_event.hpp + +diff --git a/http/http_connection.hpp b/http/http_connection.hpp +index a27ec26..882a7a6 100644 +--- a/http/http_connection.hpp ++++ b/http/http_connection.hpp +@@ -374,11 +374,13 @@ class Connection : + self->completeRequest(thisRes); + }); + +- if (thisReq.isUpgrade() && +- boost::iequals( +- thisReq.getHeaderValue(boost::beast::http::field::upgrade), +- "websocket")) ++ if ((thisReq.isUpgrade() && ++ boost::iequals( ++ thisReq.getHeaderValue(boost::beast::http::field::upgrade), ++ "websocket")) || ++ (req->url == "/sse")) + { ++ BMCWEB_LOG_DEBUG << "Request: " << this << " is getting upgraded"; + handler->handleUpgrade(thisReq, res, std::move(adaptor)); + // delete lambda with self shared_ptr + // to enable connection destruction +diff --git a/http/http_response.hpp b/http/http_response.hpp +index 3c2a3f9..679dab2 100644 +--- a/http/http_response.hpp ++++ b/http/http_response.hpp +@@ -15,10 +15,15 @@ namespace crow + template + class Connection; + ++template ++class SseConnectionImpl; ++ + struct Response + { + template + friend class crow::Connection; ++ template ++ friend class crow::SseConnectionImpl; + using response_type = + boost::beast::http::response; + +@@ -173,8 +178,8 @@ struct Response + + private: + bool completed = false; +- std::function completeRequestHandler; + std::function isAliveHelper; ++ std::function completeRequestHandler; + + // In case of a JSON object, set the Content-Type header + void jsonMode() +diff --git a/http/routing.hpp b/http/routing.hpp +index 250cd33..552e1cf 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -7,6 +7,7 @@ + #include "http_response.hpp" + #include "logging.hpp" + #include "privileges.hpp" ++#include "server_sent_event.hpp" + #include "sessions.hpp" + #include "utility.hpp" + #include "websocket.hpp" +@@ -407,6 +408,68 @@ class WebSocketRule : public BaseRule + std::function errorHandler; + }; + ++class SseSocketRule : public BaseRule ++{ ++ using self_t = SseSocketRule; ++ ++ public: ++ SseSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) ++ {} ++ ++ void validate() override ++ {} ++ ++ void handle(const Request&, ++ const std::shared_ptr& asyncResp, ++ const RoutingParams&) override ++ { ++ asyncResp->res.result(boost::beast::http::status::not_found); ++ } ++ ++ void handleUpgrade(const Request& req, Response&, ++ boost::asio::ip::tcp::socket&& adaptor) override ++ { ++ std::shared_ptr> ++ myConnection = std::make_shared< ++ crow::SseConnectionImpl>( ++ req, std::move(adaptor), openHandler, closeHandler); ++ myConnection->start(); ++ } ++#ifdef BMCWEB_ENABLE_SSL ++ void handleUpgrade(const Request& req, Response&, ++ boost::beast::ssl_stream&& ++ adaptor) override ++ { ++ std::shared_ptr>> ++ myConnection = std::make_shared>>( ++ req, std::move(adaptor), openHandler, closeHandler); ++ myConnection->start(); ++ } ++#endif ++ ++ template ++ self_t& onopen(Func f) ++ { ++ openHandler = f; ++ return *this; ++ } ++ ++ template ++ self_t& onclose(Func f) ++ { ++ closeHandler = f; ++ return *this; ++ } ++ ++ private: ++ std::function&, ++ const crow::Request&, crow::Response&)> ++ openHandler; ++ std::function&)> closeHandler; ++}; ++ + template + struct RuleParameterTraits + { +@@ -419,6 +482,14 @@ struct RuleParameterTraits + return *p; + } + ++ SseSocketRule& serverSentEvent() ++ { ++ self_t* self = static_cast(this); ++ SseSocketRule* p = new SseSocketRule(self->rule); ++ self->ruleToUpgrade.reset(p); ++ return *p; ++ } ++ + self_t& name(const std::string_view name) noexcept + { + self_t* self = static_cast(this); +diff --git a/http/server_sent_event.hpp b/http/server_sent_event.hpp +new file mode 100644 +index 0000000..41d18ed +--- /dev/null ++++ b/http/server_sent_event.hpp +@@ -0,0 +1,282 @@ ++#pragma once ++#include "http_request.hpp" ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#ifdef BMCWEB_ENABLE_SSL ++#include ++#endif ++ ++namespace crow ++{ ++ ++struct SseConnection : std::enable_shared_from_this ++{ ++ public: ++ SseConnection(const crow::Request& reqIn) : req(reqIn) ++ {} ++ virtual ~SseConnection() = default; ++ ++ virtual boost::asio::io_context& getIoContext() = 0; ++ virtual void sendSSEHeader() = 0; ++ virtual void completeRequest() = 0; ++ virtual void close(const std::string_view msg = "quit") = 0; ++ virtual void sendEvent(const std::string_view id, ++ const std::string_view msg) = 0; ++ ++ crow::Request req; ++ crow::Response res; ++}; ++ ++template ++class SseConnectionImpl : public SseConnection ++{ ++ public: ++ SseConnectionImpl( ++ const crow::Request& reqIn, Adaptor adaptorIn, ++ std::function&, ++ const crow::Request&, crow::Response&)> ++ openHandler, ++ std::function&)> closeHandler) : ++ SseConnection(reqIn), ++ adaptor(std::move(adaptorIn)), openHandler(std::move(openHandler)), ++ closeHandler(std::move(closeHandler)) ++ { ++ BMCWEB_LOG_DEBUG << "SseConnectionImpl: SSE constructor " << this; ++ } ++ ++ ~SseConnectionImpl() override ++ { ++ res.setCompleteRequestHandler(nullptr); ++ BMCWEB_LOG_DEBUG << "SseConnectionImpl: SSE destructor " << this; ++ } ++ ++ boost::asio::io_context& getIoContext() override ++ { ++ return static_cast( ++ adaptor.get_executor().context()); ++ } ++ ++ void start() ++ { ++ // Register for completion callback. ++ res.setCompleteRequestHandler([this, self(shared_from_this())]( ++ crow::Response& thisRes) { ++ boost::ignore_unused(thisRes); ++ boost::asio::post(this->adaptor.get_executor(), [self] { ++ self->completeRequest(); ++ }); ++ }); ++ ++ if (openHandler) ++ { ++ std::shared_ptr self = this->shared_from_this(); ++ openHandler(self, req, res); ++ } ++ } ++ ++ void close(const std::string_view msg) override ++ { ++ BMCWEB_LOG_DEBUG << "Closing SSE connection " << this << " - " << msg; ++ boost::beast::get_lowest_layer(adaptor).close(); ++ ++ // send notification to handler for cleanup ++ if (closeHandler) ++ { ++ std::shared_ptr self = this->shared_from_this(); ++ closeHandler(self); ++ } ++ } ++ ++ void sendSSEHeader() override ++ { ++ BMCWEB_LOG_DEBUG << "Starting SSE connection"; ++ using BodyType = boost::beast::http::buffer_body; ++ auto response = ++ std::make_shared>( ++ boost::beast::http::status::ok, 11); ++ auto serializer = ++ std::make_shared>( ++ *response); ++ ++ response->set(boost::beast::http::field::server, "bmcweb"); ++ response->set(boost::beast::http::field::content_type, ++ "text/event-stream"); ++ response->body().data = nullptr; ++ response->body().size = 0; ++ response->body().more = true; ++ ++ boost::beast::http::async_write_header( ++ adaptor, *serializer, ++ [this, self(shared_from_this()), response, serializer]( ++ const boost::beast::error_code& ec, const std::size_t&) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Error sending header" << ec; ++ close("async_write_header failed"); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "SSE header sent - Connection established"; ++ ++ // SSE stream header sent, So lets setup monitor. ++ // Any read data on this stream will be error in case of SSE. ++ setupRead(); ++ }); ++ } ++ ++ void setupRead() ++ { ++ adaptor.async_read_some( ++ outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), ++ [this](const boost::system::error_code& ec, std::size_t bytesRead) { ++ BMCWEB_LOG_DEBUG << "async_read_some: Read " << bytesRead ++ << " bytes"; ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Read error: " << ec; ++ } ++ outputBuffer.commit(bytesRead); ++ outputBuffer.consume(bytesRead); ++ ++ // After establishing SSE stream, Reading data on this ++ // stream means client is disobeys the SSE protocol. ++ // Read the data to avoid buffer attacks and close connection. ++ close("Close SSE connection"); ++ return; ++ }); ++ } ++ ++ void doWrite() ++ { ++ if (doingWrite) ++ { ++ return; ++ } ++ if (inputBuffer.size() == 0) ++ { ++ BMCWEB_LOG_DEBUG << "inputBuffer is empty... Bailing out"; ++ return; ++ } ++ doingWrite = true; ++ ++ adaptor.async_write_some( ++ inputBuffer.data(), [this, self(shared_from_this())]( ++ boost::beast::error_code ec, ++ const std::size_t& bytesTransferred) { ++ doingWrite = false; ++ inputBuffer.consume(bytesTransferred); ++ ++ if (ec == boost::asio::error::eof) ++ { ++ BMCWEB_LOG_ERROR << "async_write_some() SSE stream closed"; ++ close("SSE stream closed"); ++ return; ++ } ++ ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "async_write_some() failed: " ++ << ec.message(); ++ close("async_write_some failed"); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "async_write_some() bytes transferred: " ++ << bytesTransferred; ++ ++ doWrite(); ++ }); ++ } ++ ++ void completeRequest() override ++ { ++ BMCWEB_LOG_DEBUG << "SSE completeRequest() handler"; ++ if (res.body().empty() && !res.jsonValue.empty()) ++ { ++ res.addHeader("Content-Type", "application/json"); ++ res.body() = res.jsonValue.dump( ++ 2, ' ', true, nlohmann::json::error_handler_t::replace); ++ } ++ ++ res.preparePayload(); ++ auto serializer = ++ std::make_shared>(*res.stringResponse); ++ ++ boost::beast::http::async_write( ++ adaptor, *serializer, ++ [this, self(shared_from_this()), ++ serializer](const boost::system::error_code& ec, ++ std::size_t bytesTransferred) { ++ BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred ++ << " bytes"; ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG << this << " from async_write failed"; ++ return; ++ } ++ res.clear(); ++ ++ BMCWEB_LOG_DEBUG << this ++ << " Closing SSE connection - Request invalid"; ++ close("Request invalid"); ++ }); ++ ++ // delete lambda with self shared_ptr ++ // to enable connection destruction ++ res.setCompleteRequestHandler(nullptr); ++ } ++ ++ void sendEvent(const std::string_view id, ++ const std::string_view msg) override ++ { ++ if (msg.empty()) ++ { ++ BMCWEB_LOG_DEBUG << "Empty data, bailing out."; ++ return; ++ } ++ ++ std::string rawData; ++ if (!id.empty()) ++ { ++ rawData += "id: "; ++ rawData.append(id.begin(), id.end()); ++ rawData += "\n"; ++ } ++ ++ rawData += "data: "; ++ for (char character : msg) ++ { ++ rawData += character; ++ if (character == '\n') ++ { ++ rawData += "data: "; ++ } ++ } ++ rawData += "\n\n"; ++ ++ boost::asio::buffer_copy(inputBuffer.prepare(rawData.size()), ++ boost::asio::buffer(rawData)); ++ inputBuffer.commit(rawData.size()); ++ ++ doWrite(); ++ } ++ ++ private: ++ Adaptor adaptor; ++ ++ boost::beast::flat_static_buffer<1024U * 8U> outputBuffer; ++ boost::beast::flat_static_buffer<1024U * 64U> inputBuffer; ++ bool doingWrite = false; ++ ++ std::function&, const crow::Request&, ++ crow::Response&)> ++ openHandler; ++ std::function&)> closeHandler; ++}; ++} // namespace crow +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch new file mode 100644 index 000000000..40b46e74a --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch @@ -0,0 +1,677 @@ +From 30088cc01b8068419311b171dc51c8ff11fbc185 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli +Date: Tue, 16 Mar 2021 15:37:24 +0000 +Subject: [PATCH] Add SSE style subscription support to eventservice + +This commit adds the SSE style eventservice subscription +style event. Using this, end user can subscribe for +Redfish event logs using GET on SSE usri from +browser. +URI: /redfish/v1/EventService/Subscriptions/SSE + +Tested: + - From Browser did GET on above SSE URI and + generated some Redfish event logs(power cycle) + and saw redfish event logs streaming on browser. + - After SSE registration, Check Subscription collections + and GET on individual subscription and saw desired + response. + - Ran RedfishValidation and its passed. + +Change-Id: I7f4b7a34974080739c4ba968ed570489af0474de +Signed-off-by: AppaRao Puli +Signed-off-by: Nitin Wankhade +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/http_connection.hpp | 2 +- + include/eventservice_sse.hpp | 75 +++++ + .../include/event_service_manager.hpp | 111 +++++-- + redfish-core/include/server_sent_events.hpp | 286 ------------------ + redfish-core/lib/event_service.hpp | 8 +- + src/webserver_main.cpp | 2 + + 6 files changed, 165 insertions(+), 319 deletions(-) + create mode 100644 include/eventservice_sse.hpp + delete mode 100644 redfish-core/include/server_sent_events.hpp + +diff --git a/http/http_connection.hpp b/http/http_connection.hpp +index 9986e07..0b7b341 100644 +--- a/http/http_connection.hpp ++++ b/http/http_connection.hpp +@@ -379,7 +379,7 @@ class Connection : + boost::iequals( + thisReq.getHeaderValue(boost::beast::http::field::upgrade), + "websocket")) || +- (req->url == "/sse")) ++ (req->url == "/redfish/v1/EventService/Subscriptions/SSE")) + { + BMCWEB_LOG_DEBUG << "Request: " << this << " is getting upgraded"; + handler->handleUpgrade(thisReq, res, std::move(adaptor)); +diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp +new file mode 100644 +index 0000000..14daf00 +--- /dev/null ++++ b/include/eventservice_sse.hpp +@@ -0,0 +1,75 @@ ++#pragma once ++ ++#include ++#include ++ ++namespace redfish ++{ ++namespace eventservice_sse ++{ ++ ++static bool createSubscription(std::shared_ptr& conn, ++ const crow::Request& req, crow::Response& res) ++{ ++ if ((EventServiceManager::getInstance().getNumberOfSubscriptions() >= ++ maxNoOfSubscriptions) || ++ EventServiceManager::getInstance().getNumberOfSSESubscriptions() >= ++ maxNoOfSSESubscriptions) ++ { ++ BMCWEB_LOG_ERROR << "Max SSE subscriptions reached"; ++ messages::eventSubscriptionLimitExceeded(res); ++ res.end(); ++ return false; ++ } ++ BMCWEB_LOG_DEBUG << "Request query param size: " << req.urlParams.size(); ++ ++ std::shared_ptr subValue = ++ std::make_shared(std::move(conn)); ++ ++ // GET on this URI means, Its SSE subscriptionType. ++ subValue->subscriptionType = redfish::subscriptionTypeSSE; ++ ++ // TODO: parse $filter query params and fill config. ++ subValue->protocol = "Redfish"; ++ subValue->retryPolicy = "TerminateAfterRetries"; ++ subValue->eventFormatType = "Event"; ++ ++ std::string id = ++ redfish::EventServiceManager::getInstance().addSubscription(subValue, ++ false); ++ if (id.empty()) ++ { ++ messages::internalError(res); ++ res.end(); ++ return false; ++ } ++ ++ return true; ++} ++ ++static void deleteSubscription(std::shared_ptr& conn) ++{ ++ redfish::EventServiceManager::getInstance().deleteSubscription(conn); ++} ++ ++inline void requestRoutes(App& app) ++{ ++ BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/SSE") ++ .privileges({{"ConfigureComponents", "ConfigureManager"}}) ++ .serverSentEvent() ++ .onopen([](std::shared_ptr& conn, ++ const crow::Request& req, crow::Response& res) { ++ BMCWEB_LOG_DEBUG << "Connection " << conn << " opened."; ++ if (createSubscription(conn, req, res)) ++ { ++ // All success, lets send SSE haader ++ conn->sendSSEHeader(); ++ } ++ }) ++ .onclose([](std::shared_ptr& conn) { ++ BMCWEB_LOG_DEBUG << "Connection " << conn << " closed"; ++ deleteSubscription(conn); ++ }); ++} ++} // namespace eventservice_sse ++} // namespace redfish +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 64cb43a..c64bb59 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -23,6 +23,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -30,9 +31,10 @@ + #include + #include + #include +-#include ++#include + #include + ++#include + #include + #include + #include +@@ -48,9 +50,27 @@ using ReadingsObjType = + static constexpr const char* eventFormatType = "Event"; + static constexpr const char* metricReportFormatType = "MetricReport"; + ++static constexpr const char* subscriptionTypeSSE = "SSE"; + static constexpr const char* eventServiceFile = + "/var/lib/bmcweb/eventservice_config.json"; + ++static constexpr const uint8_t maxNoOfSubscriptions = 20; ++static constexpr const uint8_t maxNoOfSSESubscriptions = 10; ++ ++#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES ++static std::optional inotifyConn; ++static constexpr const char* redfishEventLogDir = "/var/log"; ++static constexpr const char* redfishEventLogFile = "/var/log/redfish"; ++static constexpr const size_t iEventSize = sizeof(inotify_event); ++static int inotifyFd = -1; ++static int dirWatchDesc = -1; ++static int fileWatchDesc = -1; ++ ++// ++using EventLogObjectsType = ++ std::tuple>; ++ + namespace registries + { + inline std::span +@@ -70,24 +90,6 @@ inline std::span + } + return {openbmc::registry}; + } +-} // namespace registries +- +-#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES +-static std::optional inotifyConn; +-static constexpr const char* redfishEventLogDir = "/var/log"; +-static constexpr const char* redfishEventLogFile = "/var/log/redfish"; +-static constexpr const size_t iEventSize = sizeof(inotify_event); +-static int inotifyFd = -1; +-static int dirWatchDesc = -1; +-static int fileWatchDesc = -1; +- +-// +-using EventLogObjectsType = +- std::tuple>; +- +-namespace registries +-{ + static const Message* + getMsgFromRegistry(const std::string& messageKey, + const std::span& registry) +@@ -375,11 +377,9 @@ class Subscription : public persistent_data::UserSubscription + path, uriProto); + } + +- Subscription(const std::shared_ptr& adaptor) : +- eventSeqNum(1) +- { +- sseConn = std::make_shared(adaptor); +- } ++ Subscription(const std::shared_ptr& adaptor) : ++ sseConn(adaptor), eventSeqNum(1) ++ {} + + ~Subscription() = default; + +@@ -396,13 +396,14 @@ class Subscription : public persistent_data::UserSubscription + if (conn != nullptr) + { + conn->sendData(msg); +- eventSeqNum++; + } + + if (sseConn != nullptr) + { +- sseConn->sendData(eventSeqNum, msg); ++ sseConn->sendEvent(std::to_string(eventSeqNum), msg); + } ++ ++ eventSeqNum++; + return true; + } + +@@ -558,14 +559,39 @@ class Subscription : public persistent_data::UserSubscription + return eventSeqNum; + } + ++ void setSubscriptionId(const std::string& id) ++ { ++ BMCWEB_LOG_DEBUG << "Subscription ID: " << id; ++ subId = id; ++ } ++ ++ std::string getSubscriptionId() ++ { ++ return subId; ++ } ++ ++ std::optional ++ getSubscriptionId(const std::shared_ptr& connPtr) ++ { ++ if (sseConn != nullptr && connPtr == sseConn) ++ { ++ BMCWEB_LOG_DEBUG << __FUNCTION__ ++ << " conn matched, subId: " << subId; ++ return subId; ++ } ++ ++ return std::nullopt; ++ } ++ + private: ++ std::shared_ptr sseConn = nullptr; + uint64_t eventSeqNum; + std::string host; + std::string port; + std::string path; + std::string uriProto; + std::shared_ptr conn = nullptr; +- std::shared_ptr sseConn = nullptr; ++ std::string subId; + }; + + class EventServiceManager +@@ -923,6 +949,8 @@ class EventServiceManager + subValue->updateRetryPolicy(); + subValue->updatehttpHeaders(); + ++ // Set Subscription ID for back trace ++ subValue->setSubscriptionId(id); + return id; + } + +@@ -947,11 +975,40 @@ class EventServiceManager + } + } + ++ void deleteSubscription(const std::shared_ptr& connPtr) ++ { ++ for (const auto& it : this->subscriptionsMap) ++ { ++ std::shared_ptr entry = it.second; ++ if (entry->subscriptionType == subscriptionTypeSSE) ++ { ++ std::optional id = ++ entry->getSubscriptionId(connPtr); ++ if (id) ++ { ++ deleteSubscription(*id); ++ return; ++ } ++ } ++ } ++ } ++ + size_t getNumberOfSubscriptions() + { + return subscriptionsMap.size(); + } + ++ size_t getNumberOfSSESubscriptions() const ++ { ++ auto count = std::count_if( ++ subscriptionsMap.begin(), subscriptionsMap.end(), ++ [this](const std::pair>& ++ entry) { ++ return (entry.second->subscriptionType == subscriptionTypeSSE); ++ }); ++ return static_cast(count); ++ } ++ + std::vector getAllIDs() + { + std::vector idList; +diff --git a/redfish-core/include/server_sent_events.hpp b/redfish-core/include/server_sent_events.hpp +deleted file mode 100644 +index 13840d3..0000000 +--- a/redfish-core/include/server_sent_events.hpp ++++ /dev/null +@@ -1,286 +0,0 @@ +- +-/* +-// 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 +- +-#include +-#include +-#include +-#include +- +-#include +-#include +-#include +-#include +-#include +-#include +- +-namespace crow +-{ +- +-static constexpr uint8_t maxReqQueueSize = 50; +- +-enum class SseConnState +-{ +- startInit, +- initInProgress, +- initialized, +- initFailed, +- sendInProgress, +- sendFailed, +- idle, +- suspended, +- closed +-}; +- +-class ServerSentEvents : public std::enable_shared_from_this +-{ +- private: +- std::shared_ptr sseConn; +- std::queue> requestDataQueue; +- std::string outBuffer; +- SseConnState state{SseConnState::startInit}; +- int retryCount{0}; +- int maxRetryAttempts{5}; +- +- void sendEvent(const std::string& id, const std::string& msg) +- { +- if (msg.empty()) +- { +- BMCWEB_LOG_DEBUG << "Empty data, bailing out."; +- return; +- } +- +- if (state == SseConnState::sendInProgress) +- { +- return; +- } +- state = SseConnState::sendInProgress; +- +- if (!id.empty()) +- { +- outBuffer += "id: "; +- outBuffer.append(id.begin(), id.end()); +- outBuffer += "\n"; +- } +- +- outBuffer += "data: "; +- for (char character : msg) +- { +- outBuffer += character; +- if (character == '\n') +- { +- outBuffer += "data: "; +- } +- } +- outBuffer += "\n\n"; +- +- doWrite(); +- } +- +- void doWrite() +- { +- if (outBuffer.empty()) +- { +- BMCWEB_LOG_DEBUG << "All data sent successfully."; +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- requestDataQueue.pop(); +- state = SseConnState::idle; +- checkQueue(); +- return; +- } +- +- sseConn->async_write_some( +- boost::asio::buffer(outBuffer.data(), outBuffer.size()), +- [self(shared_from_this())]( +- boost::beast::error_code ec, +- [[maybe_unused]] const std::size_t& bytesTransferred) { +- self->outBuffer.erase(0, bytesTransferred); +- +- if (ec == boost::asio::error::eof) +- { +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- self->requestDataQueue.pop(); +- self->state = SseConnState::idle; +- self->checkQueue(); +- return; +- } +- +- if (ec) +- { +- BMCWEB_LOG_ERROR << "async_write_some() failed: " +- << ec.message(); +- self->state = SseConnState::sendFailed; +- self->checkQueue(); +- return; +- } +- BMCWEB_LOG_DEBUG << "async_write_some() bytes transferred: " +- << bytesTransferred; +- +- self->doWrite(); +- }); +- } +- +- void startSSE() +- { +- if (state == SseConnState::initInProgress) +- { +- return; +- } +- state = SseConnState::initInProgress; +- +- BMCWEB_LOG_DEBUG << "starting SSE connection "; +- using BodyType = boost::beast::http::buffer_body; +- auto response = +- std::make_shared>( +- boost::beast::http::status::ok, 11); +- auto serializer = +- std::make_shared>( +- *response); +- +- // TODO: Add hostname in http header. +- response->set(boost::beast::http::field::server, "iBMC"); +- response->set(boost::beast::http::field::content_type, +- "text/event-stream"); +- response->body().data = nullptr; +- response->body().size = 0; +- response->body().more = true; +- +- boost::beast::http::async_write_header( +- *sseConn, *serializer, +- [this, response, +- serializer](const boost::beast::error_code& ec, +- [[maybe_unused]] const std::size_t& bytesTransferred) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Error sending header" << ec; +- state = SseConnState::initFailed; +- checkQueue(); +- return; +- } +- +- BMCWEB_LOG_DEBUG << "startSSE Header sent."; +- state = SseConnState::initialized; +- checkQueue(); +- }); +- } +- +- void checkQueue(const bool newRecord = false) +- { +- if (requestDataQueue.empty()) +- { +- BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n"; +- return; +- } +- +- if (retryCount >= maxRetryAttempts) +- { +- BMCWEB_LOG_ERROR << "Maximum number of retries is reached."; +- +- // Clear queue. +- while (!requestDataQueue.empty()) +- { +- requestDataQueue.pop(); +- } +- +- // TODO: Take 'DeliveryRetryPolicy' action. +- // For now, doing 'SuspendRetries' action. +- state = SseConnState::suspended; +- return; +- } +- +- if ((state == SseConnState::initFailed) || +- (state == SseConnState::sendFailed)) +- { +- if (newRecord) +- { +- // We are already running async wait and retry. +- // Since record is added to queue, it gets the +- // turn in FIFO. +- return; +- } +- +- retryCount++; +- // TODO: Perform async wait for retryTimeoutInterval before proceed. +- } +- else +- { +- // reset retry count. +- retryCount = 0; +- } +- +- switch (state) +- { +- case SseConnState::initInProgress: +- case SseConnState::sendInProgress: +- case SseConnState::suspended: +- case SseConnState::startInit: +- case SseConnState::closed: +- // do nothing +- break; +- case SseConnState::initFailed: +- { +- startSSE(); +- break; +- } +- case SseConnState::initialized: +- case SseConnState::idle: +- case SseConnState::sendFailed: +- { +- std::pair reqData = +- requestDataQueue.front(); +- sendEvent(std::to_string(reqData.first), reqData.second); +- break; +- } +- } +- } +- +- public: +- ServerSentEvents(const ServerSentEvents&) = delete; +- ServerSentEvents& operator=(const ServerSentEvents&) = delete; +- ServerSentEvents(ServerSentEvents&&) = delete; +- ServerSentEvents& operator=(ServerSentEvents&&) = delete; +- +- ServerSentEvents(const std::shared_ptr& adaptor) : +- sseConn(adaptor) +- { +- startSSE(); +- } +- +- ~ServerSentEvents() = default; +- +- void sendData(const uint64_t& id, const std::string& data) +- { +- if (state == SseConnState::suspended) +- { +- return; +- } +- +- if (requestDataQueue.size() <= maxReqQueueSize) +- { +- requestDataQueue.push(std::pair(id, data)); +- checkQueue(true); +- } +- else +- { +- BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data."; +- } +- } +-}; +- +-} // namespace crow +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index c7392bd..2181346 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -40,8 +40,6 @@ static constexpr const std::array supportedResourceTypes = { + "Task"}; + #endif + +-static constexpr const uint8_t maxNoOfSubscriptions = 20; +- + inline void requestRoutesEventService(App& app) + { + BMCWEB_ROUTE(app, "/redfish/v1/EventService/") +@@ -54,6 +52,8 @@ inline void requestRoutesEventService(App& app) + {"@odata.type", "#EventService.v1_5_0.EventService"}, + {"Id", "EventService"}, + {"Name", "Event Service"}, ++ {"ServerSentEventUri", ++ "/redfish/v1/EventService/Subscriptions/SSE"}, + {"Subscriptions", + {{"@odata.id", "/redfish/v1/EventService/Subscriptions"}}}, + {"Actions", +@@ -92,9 +92,7 @@ inline void requestRoutesEventService(App& app) + .privileges(redfish::privileges::patchEventService) + .methods(boost::beast::http::verb::patch)( + [](const crow::Request& req, +- const std::shared_ptr& asyncResp) +- +- { ++ const std::shared_ptr& asyncResp) { + std::optional serviceEnabled; + std::optional retryAttemps; + std::optional retryInterval; +diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp +index 6bdce98..d8b4f4e 100644 +--- a/src/webserver_main.cpp ++++ b/src/webserver_main.cpp +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -83,6 +84,7 @@ static int run() + #endif + + #ifdef BMCWEB_ENABLE_REDFISH ++ redfish::eventservice_sse::requestRoutes(app); + redfish::requestRoutes(app); + redfish::RedfishService redfish(app); + +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch new file mode 100644 index 000000000..816456cec --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch @@ -0,0 +1,326 @@ +From e207fca58bf0d05b160ea6735f4f576737d7a9cb Mon Sep 17 00:00:00 2001 +From: AppaRao Puli +Date: Wed, 17 Mar 2021 01:16:50 +0000 +Subject: [PATCH] Add EventService SSE filter support + +This commit implements the Event Service SSE stream +filters support. As per redfish specification: +The SSE streams have these formats: + - Metric report SSE stream + - Event message SSE stream + +To reduce the amount of data, service supports $filter +query parameter in SSE URI. +Below properties support as filter criteria: + - EventFormatType( Event & MetricReport) + - MessageId + - RegistryPrefix + - MetricReportDefinition + +For more details, refer Redfish specification section 13.5.2 + +Tested: + Created SSE stream with different filters and observed + desired events on SSE stream client(browser), some examples + - To get all Redfish events, + URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(EventFormatType%20eq%20Event) + - To get Redfish events with RegistryPrefix "OpenBMC" + URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(RegistryPrefix%20eq%20OpenBMC) + - To get only DC power of Events, + URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(EventFormatType%20eq%20Event)%20and%20(MessageId%20eq%20DCPowerOff) + +Signed-off-by: AppaRao Puli +Signed-off-by: P Dheeraj Srujan Kumar +Change-Id: I55c6f53bb5e57aa1f2d1601f1a16525a33b13bd2 +--- + include/eventservice_sse.hpp | 172 +++++++++++++++++- + redfish-core/include/error_messages.hpp | 9 + + .../include/event_service_manager.hpp | 5 + + redfish-core/lib/event_service.hpp | 5 - + redfish-core/src/error_messages.cpp | 27 +++ + 5 files changed, 209 insertions(+), 9 deletions(-) + +diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp +index 14daf00..5847766 100644 +--- a/include/eventservice_sse.hpp ++++ b/include/eventservice_sse.hpp +@@ -21,18 +21,182 @@ static bool createSubscription(std::shared_ptr& conn, + res.end(); + return false; + } ++#ifdef NEW_BOOST_URL ++ BMCWEB_LOG_DEBUG << "Request query param size: " ++ << req.urlView.params().size(); ++ ++ // EventService SSE supports only "$filter" query param. ++ if (req.urlView.params().size() > 1) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ std::string eventFormatType; ++ std::string queryFilters; ++ if (req.urlView.params().size()) ++ { ++ boost::urls::params_view::iterator it = ++ req.urlView.params().find("$filter"); ++ if (it == req.urlView.params().end()) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ queryFilters = std::string((*it).value); ++ } ++ else ++ { ++ eventFormatType = "Event"; ++ } ++#else + BMCWEB_LOG_DEBUG << "Request query param size: " << req.urlParams.size(); + ++ // EventService SSE supports only "$filter" query param. ++ if (req.urlParams.size() > 1) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ std::string eventFormatType; ++ std::string queryFilters; ++ if (req.urlParams.size()) ++ { ++ boost::urls::query_params_view::iterator it = ++ req.urlParams.find("$filter"); ++ if (it == req.urlParams.end()) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ queryFilters = it->value(); ++ } ++ else ++ { ++ eventFormatType = "Event"; ++ } ++#endif ++ ++ std::vector msgIds; ++ std::vector regPrefixes; ++ std::vector mrdsArray; ++ if (!queryFilters.empty()) ++ { ++ // Reading from query params. ++ bool status = readSSEQueryParams(queryFilters, eventFormatType, msgIds, ++ regPrefixes, mrdsArray); ++ if (!status) ++ { ++ messages::invalidQueryFilter(res); ++ res.end(); ++ return false; ++ } ++ ++ // RegsitryPrefix and messageIds are mutuly exclusive as per redfish ++ // specification. ++ if (!regPrefixes.empty() && !msgIds.empty()) ++ { ++ messages::propertyValueConflict(res, "RegistryPrefix", "MessageId"); ++ res.end(); ++ return false; ++ } ++ ++ if (!eventFormatType.empty()) ++ { ++ if (std::find(supportedEvtFormatTypes.begin(), ++ supportedEvtFormatTypes.end(), ++ eventFormatType) == supportedEvtFormatTypes.end()) ++ { ++ messages::propertyValueNotInList(res, eventFormatType, ++ "EventFormatType"); ++ res.end(); ++ return false; ++ } ++ } ++ else ++ { ++ // If nothing specified, using default "Event" ++ eventFormatType = "Event"; ++ } ++ ++ if (!regPrefixes.empty()) ++ { ++ for (const std::string& it : regPrefixes) ++ { ++ if (std::find(supportedRegPrefixes.begin(), ++ supportedRegPrefixes.end(), ++ it) == supportedRegPrefixes.end()) ++ { ++ messages::propertyValueNotInList(res, it, "RegistryPrefix"); ++ res.end(); ++ return false; ++ } ++ } ++ } ++ ++ if (!msgIds.empty()) ++ { ++ std::vector registryPrefix; ++ ++ // If no registry prefixes are mentioned, consider all supported ++ // prefixes to validate message ID ++ if (regPrefixes.empty()) ++ { ++ registryPrefix.assign(supportedRegPrefixes.begin(), ++ supportedRegPrefixes.end()); ++ } ++ else ++ { ++ registryPrefix = regPrefixes; ++ } ++ ++ for (const std::string& id : msgIds) ++ { ++ bool validId = false; ++ ++ // Check for Message ID in each of the selected Registry ++ for (const std::string& it : registryPrefix) ++ { ++ const std::span ++ registry = ++ redfish::registries::getRegistryFromPrefix(it); ++ ++ if (std::any_of( ++ registry.begin(), registry.end(), ++ [&id](const redfish::registries::MessageEntry& ++ messageEntry) { ++ return !id.compare(messageEntry.first); ++ })) ++ { ++ validId = true; ++ break; ++ } ++ } ++ ++ if (!validId) ++ { ++ messages::propertyValueNotInList(res, id, "MessageIds"); ++ res.end(); ++ return false; ++ } ++ } ++ } ++ } ++ + std::shared_ptr subValue = + std::make_shared(std::move(conn)); + + // GET on this URI means, Its SSE subscriptionType. +- subValue->subscriptionType = redfish::subscriptionTypeSSE; +- +- // TODO: parse $filter query params and fill config. ++ subValue->subscriptionType = subscriptionTypeSSE; + subValue->protocol = "Redfish"; + subValue->retryPolicy = "TerminateAfterRetries"; +- subValue->eventFormatType = "Event"; ++ subValue->eventFormatType = eventFormatType; ++ subValue->registryMsgIds = msgIds; ++ subValue->registryPrefixes = regPrefixes; ++ subValue->metricReportDefinitions = mrdsArray; + + std::string id = + redfish::EventServiceManager::getInstance().addSubscription(subValue, +diff --git a/redfish-core/include/error_messages.hpp b/redfish-core/include/error_messages.hpp +index b3b2305..9b3ee49 100644 +--- a/redfish-core/include/error_messages.hpp ++++ b/redfish-core/include/error_messages.hpp +@@ -980,6 +980,15 @@ nlohmann::json invalidUpload(std::string_view arg1, std::string_view arg2); + void invalidUpload(crow::Response& res, std::string_view arg1, + std::string_view arg2); + ++/** ++ * @brief Formats InvalidQueryFilter message into JSON ++ * Message body: "The requested URL contains the invalid query filters" ++ * ++ * @returns Message InvalidQueryFilter formatted to JSON */ ++nlohmann::json invalidQueryFilter(); ++ ++void invalidQueryFilter(crow::Response& res); ++ + } // namespace messages + + } // namespace redfish +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index c64bb59..e03dd9b 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -57,6 +57,11 @@ static constexpr const char* eventServiceFile = + static constexpr const uint8_t maxNoOfSubscriptions = 20; + static constexpr const uint8_t maxNoOfSSESubscriptions = 10; + ++static constexpr const std::array supportedEvtFormatTypes = { ++ eventFormatType, metricReportFormatType}; ++static constexpr const std::array supportedRegPrefixes = { ++ "OpenBMC", "TaskEvent"}; ++ + #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES + static std::optional inotifyConn; + static constexpr const char* redfishEventLogDir = "/var/log"; +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 2181346..e9ee78f 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -24,11 +24,6 @@ + + namespace redfish + { +- +-static constexpr const std::array supportedEvtFormatTypes = { +- eventFormatType, metricReportFormatType}; +-static constexpr const std::array supportedRegPrefixes = { +- "Base", "OpenBMC", "TaskEvent"}; + static constexpr const std::array supportedRetryPolicies = { + "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; + +diff --git a/redfish-core/src/error_messages.cpp b/redfish-core/src/error_messages.cpp +index 381b8a9..cad145a 100644 +--- a/redfish-core/src/error_messages.cpp ++++ b/redfish-core/src/error_messages.cpp +@@ -1668,6 +1668,33 @@ nlohmann::json invalidUpload(std::string_view arg1, std::string_view arg2) + {"MessageSeverity", "Warning"}, + {"Resolution", "None."}}; + } ++ ++/** ++ * @internal ++ * @brief Formats InvalidQueryFilter into JSON ++ * ++ * See header file for more information ++ * @endinternal ++ */ ++nlohmann::json invalidQueryFilter() ++{ ++ return nlohmann::json{ ++ {"@odata.type", "#Message.v1_0_0.Message"}, ++ {"MessageId", "Base.1.5.0.InvalidQueryFilter"}, ++ {"Message", "The requested url contains the invalid query filter."}, ++ {"MessageArgs", nlohmann::json::array()}, ++ {"Severity", "Warning"}, ++ {"Resolution", ++ "Ensure the correct query filter is specified in requested url " ++ "and resubmit the request."}}; ++} ++ ++void invalidQueryFilter(crow::Response& res) ++{ ++ res.result(boost::beast::http::status::bad_request); ++ addMessageToErrorJson(res.jsonValue, invalidQueryFilter()); ++} ++ + } // namespace messages + + } // namespace redfish +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch new file mode 100644 index 000000000..3818b99ee --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0007-EventService-Log-events-for-subscription-actions.patch @@ -0,0 +1,133 @@ +From ec3349511d66a7136a054c1a4e7a6f24ea3b6722 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli +Date: Fri, 27 Aug 2021 16:02:01 +0000 +Subject: [PATCH] EventService: Log events for subscription actions + +Log redfish event for below 3 actions + - Add new subscription + - Update existing subscription properties + - Delete existing subscription +in order to notify the subscribed clients on the subscription related +information. + +Modified method name accordingly to indicate the clear purpose and +added updateSubscription method with subscription id param +to log event for subscription update. + +Tested: + - Performed all the above actions and verified the redfish event + messages are logged. + +Change-Id: I3745fa6357bd215379781a9818d9acc02a853d79 +Signed-off-by: AppaRao Puli +Signed-off-by: Ayushi Smriti +Signed-off-by: P Dheeraj Srujan Kumar +--- + .../include/event_service_manager.hpp | 35 ++++++++++++++++--- + redfish-core/lib/event_service.hpp | 2 +- + 2 files changed, 32 insertions(+), 5 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index f2b7803..5483a74 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -21,6 +21,7 @@ + #include "registries/task_event_message_registry.hpp" + + #include ++#include + + #include + #include +@@ -782,7 +783,7 @@ class EventServiceManager + } + } + +- void updateSubscriptionData() const ++ void persistSubscriptionData() + { + persistent_data::EventServiceStore::getInstance() + .eventServiceConfig.enabled = serviceEnabled; +@@ -829,7 +830,7 @@ class EventServiceManager + + if (updateConfig) + { +- updateSubscriptionData(); ++ persistSubscriptionData(); + } + + if (updateRetryCfg) +@@ -941,7 +942,7 @@ class EventServiceManager + + if (updateFile) + { +- updateSubscriptionData(); ++ persistSubscriptionData(); + } + + #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES +@@ -957,6 +958,13 @@ class EventServiceManager + + // Set Subscription ID for back trace + subValue->setSubscriptionId(id); ++ ++ /* Log event for subscription addition */ ++ sd_journal_send("MESSAGE=Event subscription added(Id: %s)", id.c_str(), ++ "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionAdded", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); ++ + return id; + } + +@@ -981,7 +989,14 @@ class EventServiceManager + persistent_data::EventServiceStore::getInstance() + .subscriptionsConfigMap.erase(obj2); + updateNoOfSubscribersCount(); +- updateSubscriptionData(); ++ ++ persistSubscriptionData(); ++ /* Log event for subscription delete. */ ++ sd_journal_send("MESSAGE=Event subscription removed.(Id = %s)", ++ id.c_str(), "PRIORITY=%i", LOG_INFO, ++ "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionRemoved", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); + } + } + +@@ -1003,6 +1018,18 @@ class EventServiceManager + } + } + ++ void updateSubscription(const std::string& id) ++ { ++ persistSubscriptionData(); ++ ++ /* Log event for subscription update. */ ++ sd_journal_send("MESSAGE=Event subscription updated.(Id = %s)", ++ id.c_str(), "PRIORITY=%i", LOG_INFO, ++ "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionUpdated", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); ++ } ++ + size_t getNumberOfSubscriptions() + { + return subscriptionsMap.size(); +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index ab85796..91ce22c 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -630,7 +630,7 @@ inline void requestRoutesEventDestination(App& app) + subValue->updateRetryPolicy(); + } + +- EventServiceManager::getInstance().updateSubscriptionData(); ++ EventServiceManager::getInstance().updateSubscription(param); + }); + BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions//") + // The below privilege is wrong, it should be ConfigureManager OR +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch new file mode 100644 index 000000000..65bfafdcc --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch @@ -0,0 +1,85 @@ +From a22fcc269c475b0ff8a80a6aa2c837b247eca907 Mon Sep 17 00:00:00 2001 +From: Nitin Wankhade +Date: Mon, 28 Jun 2021 19:59:57 +0000 +Subject: [PATCH] Add checks on Event Subscription input parameters + +There is no check on the size of input parameters(Context, +Destination and Header) during Event Subscription.This +creates out of memory situation. +This commit checks for the size of input parameters and +rejects if it is exceeding the input size limits. + +Tested + - Validated using POST on Event Subscription. + - When Context, Destination and Headers were too long, + received a error message denoting the same. + +Change-Id: Iec2cd766c0e137b72706fc2da468d4fefd8fbaae +Signed-off-by: Nitin Wankhade +Signed-off-by: P Dheeraj Srujan Kumar +--- + redfish-core/lib/event_service.hpp | 27 +++++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index 88ebd01..373c873 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -22,6 +22,10 @@ + + #include + ++#define MAX_CONTEXT_SIZE 256 ++#define MAX_DESTINATION_SIZE 1024 ++#define MAX_HEADER_SIZE 8096 ++ + namespace redfish + { + static constexpr const std::array supportedRetryPolicies = { +@@ -229,6 +233,12 @@ inline void requestRoutesEventDestinationCollection(App& app) + return; + } + ++ if (destUrl.size() > MAX_DESTINATION_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, "Destination"); ++ return; ++ } ++ + if (regPrefixes && msgIds) + { + if (!regPrefixes->empty() && !msgIds->empty()) +@@ -336,13 +346,30 @@ inline void requestRoutesEventDestinationCollection(App& app) + + if (context) + { ++ if (context->size() > MAX_CONTEXT_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, "Context"); ++ return; ++ } + subValue->customText = *context; + } + + if (headers) + { ++ size_t cumulativeLen = 0; ++ + for (const nlohmann::json& headerChunk : *headers) + { ++ std::string hdr{headerChunk.dump( ++ -1, ' ', true, ++ nlohmann::json::error_handler_t::replace)}; ++ cumulativeLen += hdr.length(); ++ if (cumulativeLen > MAX_HEADER_SIZE) ++ { ++ messages::propertySizeExceeded(asyncResp->res, ++ "HttpHeaders"); ++ return; ++ } + for (const auto& item : headerChunk.items()) + { + const std::string* value = +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch new file mode 100644 index 000000000..ede279868 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0010-Remove-Terminated-Event-Subscriptions.patch @@ -0,0 +1,267 @@ +From fc4b7e964f5f26e7af2eb19f120b1762b70b8179 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Tue, 12 Oct 2021 08:19:51 +0000 +Subject: [PATCH] Delete/Remove Terminated Event Subscription(s) + +Added functionality to delete/remove event subscription(s) which are +configured to Terminate after retries. + +Currently, when an Event is subscribed with Retry Policy as +"TerminateAfterRetries", the state of the connection is set to +"Terminated" after retrying, but the Subscription is not removed. +This commit adds the functionality to detect terminated connection and +remove the respective subscription. + +Tested: + - Created a Subscription with + DeliveryRetryPolicy: "TerminateAfterRetries" + - Received Events successfully on Event listener + - Once the Event listener was stopped, the Subscription was + removed/deleted after retries. + +Change-Id: If447acb2db74fb29a5d1cfe6194b77cda82bc8a1 +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/http_client.hpp | 48 +++++++++++++++---- + .../include/event_service_manager.hpp | 36 ++++++++++++++ + 2 files changed, 75 insertions(+), 9 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 7328eae..db99faa 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -56,6 +56,8 @@ enum class ConnState + closeInProgress, + closed, + suspended, ++ terminate, ++ terminateInProgress, + terminated, + abortConnection, + retry +@@ -290,7 +292,14 @@ class HttpClient : public std::enable_shared_from_this + } + void doClose() + { +- state = ConnState::closeInProgress; ++ if (state == ConnState::terminate) ++ { ++ state = ConnState::terminateInProgress; ++ } ++ else if (state != ConnState::suspended) ++ { ++ state = ConnState::closeInProgress; ++ } + + // Set the timeout on the tcp stream socket for the async operation + conn.expires_after(std::chrono::seconds(30)); +@@ -320,8 +329,11 @@ class HttpClient : public std::enable_shared_from_this + } + self->conn.close(); + +- if ((self->state != ConnState::suspended) && +- (self->state != ConnState::terminated)) ++ if (self->state == ConnState::terminateInProgress) ++ { ++ self->state = ConnState::terminated; ++ } ++ else if (self->state == ConnState::closeInProgress) + { + self->state = ConnState::closed; + self->handleConnState(); +@@ -343,8 +355,11 @@ class HttpClient : public std::enable_shared_from_this + } + conn.close(); + +- if ((state != ConnState::suspended) && +- (state != ConnState::terminated)) ++ if (state == ConnState::terminateInProgress) ++ { ++ state = ConnState::terminated; ++ } ++ else if (state == ConnState::closeInProgress) + { + state = ConnState::closed; + handleConnState(); +@@ -367,8 +382,7 @@ class HttpClient : public std::enable_shared_from_this + BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction; + if (retryPolicyAction == "TerminateAfterRetries") + { +- // TODO: delete subscription +- state = ConnState::terminated; ++ state = ConnState::terminate; + } + if (retryPolicyAction == "SuspendRetries") + { +@@ -424,6 +438,7 @@ class HttpClient : public std::enable_shared_from_this + case ConnState::sendInProgress: + case ConnState::recvInProgress: + case ConnState::closeInProgress: ++ case ConnState::terminateInProgress: + { + BMCWEB_LOG_DEBUG << "Async operation is already in progress"; + break; +@@ -440,11 +455,16 @@ class HttpClient : public std::enable_shared_from_this + break; + } + case ConnState::suspended: +- case ConnState::terminated: ++ case ConnState::terminate: + { + doClose(); + break; + } ++ case ConnState::terminated: ++ { ++ BMCWEB_LOG_DEBUG << "Connection Terminated"; ++ break; ++ } + case ConnState::resolveFailed: + case ConnState::connectFailed: + case ConnState::handshakeFailed: +@@ -504,7 +524,8 @@ class HttpClient : public std::enable_shared_from_this + + void sendData(const std::string& data) + { +- if ((state == ConnState::suspended) || (state == ConnState::terminated)) ++ if ((state == ConnState::terminate) || ++ (state == ConnState::terminated) || (state == ConnState::suspended)) + { + return; + } +@@ -520,6 +541,15 @@ class HttpClient : public std::enable_shared_from_this + } + } + ++ bool isTerminated() ++ { ++ if (state == ConnState::terminated) ++ { ++ return true; ++ } ++ return false; ++ } ++ + void setHeaders(const boost::beast::http::fields& httpHeaders) + { + // Set custom headers +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 5483a74..92abaa5 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -590,6 +590,14 @@ class Subscription : public persistent_data::UserSubscription + return std::nullopt; + } + ++ bool isTerminated() ++ { ++ if (conn != nullptr) ++ return conn->isTerminated(); ++ ++ return false; ++ } ++ + private: + std::shared_ptr sseConn = nullptr; + uint64_t eventSeqNum; +@@ -845,6 +853,22 @@ class EventServiceManager + } + } + ++ void deleteTerminatedSubcriptions() ++ { ++ boost::container::flat_map>::iterator it = ++ subscriptionsMap.begin(); ++ while (it != subscriptionsMap.end()) ++ { ++ std::shared_ptr entry = it->second; ++ if (entry->isTerminated()) ++ { ++ subscriptionsMap.erase(it); ++ } ++ it++; ++ } ++ } ++ + void updateNoOfSubscribersCount() + { + size_t eventLogSubCount = 0; +@@ -879,6 +903,7 @@ class EventServiceManager + + std::shared_ptr getSubscription(const std::string& id) + { ++ deleteTerminatedSubcriptions(); + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { +@@ -970,6 +995,7 @@ class EventServiceManager + + bool isSubscriptionExist(const std::string& id) + { ++ deleteTerminatedSubcriptions(); + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { +@@ -1032,6 +1058,7 @@ class EventServiceManager + + size_t getNumberOfSubscriptions() + { ++ deleteTerminatedSubcriptions(); + return subscriptionsMap.size(); + } + +@@ -1048,6 +1075,7 @@ class EventServiceManager + + std::vector getAllIDs() + { ++ deleteTerminatedSubcriptions(); + std::vector idList; + for (const auto& it : subscriptionsMap) + { +@@ -1058,6 +1086,7 @@ class EventServiceManager + + bool isDestinationExist(const std::string& destUrl) + { ++ deleteTerminatedSubcriptions(); + for (const auto& it : subscriptionsMap) + { + std::shared_ptr entry = it.second; +@@ -1072,6 +1101,7 @@ class EventServiceManager + + bool sendTestEventLog() + { ++ deleteTerminatedSubcriptions(); + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr entry = it.second; +@@ -1108,6 +1138,8 @@ class EventServiceManager + } + eventRecord.push_back(eventMessage); + ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr entry = it.second; +@@ -1151,6 +1183,8 @@ class EventServiceManager + } + void sendBroadcastMsg(const std::string& broadcastMsg) + { ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr entry = it.second; +@@ -1263,6 +1297,8 @@ class EventServiceManager + return; + } + ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : this->subscriptionsMap) + { + std::shared_ptr entry = it.second; +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch new file mode 100644 index 000000000..87f0a4fd9 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch @@ -0,0 +1,141 @@ +From ff562320d23e1c1e075689a636505f22eb4890d4 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Thu, 14 Oct 2021 02:56:11 +0530 +Subject: [PATCH] Fix bmcweb crash while deleting terminated subscriptions + +This commit fixes bmcweb crash while deleting the terminated +subscriptions. In the earlier implementation, detection of subscription +to be deleted and the deletion(erase) was happening in the same loop. +Due to this, if the Subscription to be deleted is the last one in the +list, the loop will enter into infinite loop. The fix is to keep the +detection and deletion loop separate. +Also, this commit adds code to : + - Delete from persistent storage + - Add journal entry for deleted entry + - update number of subcribers and update persistent storage. + +Apart from this, this commit also moves the retry timer check to the top +to avoid multiple calls to close when the retry count is 3 and timer is +running. + +Tested: + - Checked journal logs to confirm each retry is actually spanned to be + 30 secs + - Verified Journal entry for deleted subscription after retires. + - Verified Event service functionality by making three subscriptions: + retry for ever, terminate after retires and suspend after retries. + +Change-Id: I425a6c749923ce86c457a36394deb0fbbee232db +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/http_client.hpp | 11 ++-- + .../include/event_service_manager.hpp | 59 ++++++++++++++++--- + 2 files changed, 58 insertions(+), 12 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index 745eeb6..5575765 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -369,6 +369,12 @@ class HttpClient : public std::enable_shared_from_this + + void waitAndRetry() + { ++ if (runningTimer) ++ { ++ BMCWEB_LOG_DEBUG << "Retry timer is already running."; ++ return; ++ } ++ + if (retryCount >= maxRetryAttempts) + { + BMCWEB_LOG_ERROR << "Maximum number of retries reached."; +@@ -395,11 +401,6 @@ class HttpClient : public std::enable_shared_from_this + return; + } + +- if (runningTimer) +- { +- BMCWEB_LOG_DEBUG << "Retry timer is already running."; +- return; +- } + runningTimer = true; + + retryCount++; +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 5d71c63..f97909c 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -853,18 +853,63 @@ class EventServiceManager + + void deleteTerminatedSubcriptions() + { +- boost::container::flat_map>::iterator it = +- subscriptionsMap.begin(); +- while (it != subscriptionsMap.end()) ++ BMCWEB_LOG_ERROR << "Map size Before Delete : " ++ << subscriptionsMap.size(); ++ ++ std::vector deleteIds; ++ ++ // Determine Subscription ID's to be deleted. ++ for (const auto& it : subscriptionsMap) + { +- std::shared_ptr entry = it->second; ++ std::shared_ptr entry = it.second; + if (entry->isTerminated()) + { +- subscriptionsMap.erase(it); ++ deleteIds.emplace_back(it.first); ++ } ++ } ++ ++ // Delete the Terminated Subcriptions ++ for (std::string& id : deleteIds) ++ { ++ auto map1 = subscriptionsMap.find(id); ++ if (map1 != subscriptionsMap.end()) ++ { ++ subscriptionsMap.erase(map1); ++ auto map2 = persistent_data::EventServiceStore::getInstance() ++ .subscriptionsConfigMap.find(id); ++ if (map2 != persistent_data::EventServiceStore::getInstance() ++ .subscriptionsConfigMap.end()) ++ { ++ persistent_data::EventServiceStore::getInstance() ++ .subscriptionsConfigMap.erase(map2); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "Couldn't find ID: " << id ++ << " in subscriptionsConfigMap"; ++ } ++ ++ /* Log event for subscription delete. */ ++ sd_journal_send("MESSAGE=Event subscription removed.(Id = %s)", ++ id.c_str(), "PRIORITY=%i", LOG_INFO, ++ "REDFISH_MESSAGE_ID=%s", ++ "OpenBMC.0.1.EventSubscriptionRemoved", ++ "REDFISH_MESSAGE_ARGS=%s", id.c_str(), NULL); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "Couldn't find ID: " << id ++ << " in subscriptionsMap"; + } +- it++; + } ++ if (deleteIds.size()) ++ { ++ updateNoOfSubscribersCount(); ++ persistSubscriptionData(); ++ } ++ ++ BMCWEB_LOG_ERROR << "Map size After Delete : " ++ << subscriptionsMap.size(); + } + + void updateNoOfSubscribersCount() +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch new file mode 100644 index 000000000..61503904c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch @@ -0,0 +1,66 @@ +From b132263ef2f3bb3fd2766154c00b7b00c1e49fb6 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Tue, 7 Dec 2021 09:48:07 +0100 +Subject: [PATCH] Add support for deleting terminated subscriptions + +Added functionality to delete/remove event subscription(s) which are +configured to Terminate after retries. + +Currently, when an Event is subscribed with Retry Policy as +"TerminateAfterRetries", the state of the connection is set to +"Terminated" after retrying, but the Subscription is not removed. +This commit adds the functionality to detect terminated connection and +remove the respective subscription. + +This commit adds this check for metric reports. + +Tested: + - Created a Subscription with + DeliveryRetryPolicy: "TerminateAfterRetries" + - Received Events successfully on Event listener + - Once the Event listener was stopped, the Subscription was + removed/deleted after retries. + +Change-Id: I3cb0af5bc24411cddcdb3d1d9de25e8e9144106c +Signed-off-by: P Dheeraj Srujan Kumar +--- + redfish-core/include/event_service_manager.hpp | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index c68b707..6a93fc0 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -1486,7 +1486,7 @@ class EventServiceManager + } + + #endif +- static void getReadingsForReport(sdbusplus::message::message& msg) ++ void getReadingsForReport(sdbusplus::message::message& msg) + { + if (msg.is_method_error()) + { +@@ -1525,6 +1525,8 @@ class EventServiceManager + return; + } + ++ deleteTerminatedSubcriptions(); ++ + for (const auto& it : + EventServiceManager::getInstance().subscriptionsMap) + { +@@ -1560,7 +1562,10 @@ class EventServiceManager + "arg0=xyz.openbmc_project.Telemetry.Report"; + + matchTelemetryMonitor = std::make_shared( +- *crow::connections::systemBus, matchStr, getReadingsForReport); ++ *crow::connections::systemBus, matchStr, ++ [this](sdbusplus::message::message& msg) { ++ getReadingsForReport(msg); ++ }); + } + }; + +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0013-event-service-fix-added-Context-field-to-response.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0013-event-service-fix-added-Context-field-to-response.patch new file mode 100644 index 000000000..019ccbe64 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0013-event-service-fix-added-Context-field-to-response.patch @@ -0,0 +1,33 @@ +From ce9b52791e76d73050f053f8fc607c6e1eb5d8c4 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Thu, 16 Dec 2021 10:46:55 +0100 +Subject: [PATCH] event service fix, added Context field to response + +Tested: + - Context field is present + - No regression detected + +Signed-off-by: Krzysztof Grobelny +--- + redfish-core/include/event_service_manager.hpp | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 881d2db..1ba9f21 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -530,6 +530,11 @@ class Subscription : public persistent_data::UserSubscription + return; + } + ++ if (!customText.empty()) ++ { ++ msg["Context"] = customText; ++ } ++ + this->sendEvent( + msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); + } +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README new file mode 100644 index 000000000..617d6f3e8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README @@ -0,0 +1,37 @@ +Eventservice specific patches: Temporary pulling down +the upstream patches. These will be remove as soon as +thee gets merged upstream. + +Upstream revision information: + - EventService : Add unmerged changes for http retry support (Downstream patch) + file://eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch + + - EventService: https client support + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/31735/40 (Rebased on latest bmcweb) + + - Add Server-Sent-Events support + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41258/9 + + - Add SSE style subscription support to eventservice + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41319/10 + + - Add EventService SSE filter support + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41349/7 (Modified boost::urls::query_params_view to boost::urls::url_view::params_type) + + - EventService Log events for subscription actions (Downstream patch) + file://eventservice/0007-EventService-Log-events-for-subscription-actions.patch + + - Add checks on Event-Subscription input parameters (Downstream patch) + file://eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch + + - Remove Terminated Event Subscriptions (Downstream patch) + file://eventservice/0010-Remove-Terminated-Event-Subscriptions.patch + + - Fix bmcweb crash while deleting terminated subscriptions (Downstream patch) + file://eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch + + - Add support for deleting terminated subscriptions + file://eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch + + - event service fix added Context field to response + file://eventservice/0013-event-service-fix-added-Context-field-to-response.patch diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0001-Add-asyncResp-support-during-handleUpgrade.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0001-Add-asyncResp-support-during-handleUpgrade.patch new file mode 100644 index 000000000..6a6882971 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0001-Add-asyncResp-support-during-handleUpgrade.patch @@ -0,0 +1,194 @@ +From aa8aed7069c3654d288b8198d1cd21fadb6e2a36 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Mon, 18 Oct 2021 22:45:37 +0530 +Subject: [PATCH] Add asyncResp support during handleUpgrade + +The current implementation uses the earlier method of using the response +object and calling response.end() to initiate completion handler. +This commit modifies the implementation to use asyncResp, where the +completion handler gets called asynchronously as the response object +goes out of scope. + +Tested : + - websocket_test.py Passed + - KVM was functional in WebUI. + - POST to /redfish/v1/EventService/Subscriptions/SSE returned an error + message as expected and the connection was kept alive. + - GET on /redfish/v1/EventService/Subscriptions/SSE (SSE subscription) + was successful. The existing connection was successfully closed and + upgraded to SSE connection. + +Change-Id: I2d76b34a49a6432c507d939b21b37c1ced761f8e +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/app.hpp | 6 ++++-- + http/http_connection.hpp | 30 +++++++++++++++++++++++---- + http/routing.hpp | 37 +++++++++++++++++++++---------------- + 3 files changed, 51 insertions(+), 22 deletions(-) + +diff --git a/http/app.hpp b/http/app.hpp +index c1472e5..7e946dc 100644 +--- a/http/app.hpp ++++ b/http/app.hpp +@@ -51,9 +51,11 @@ class App + App& operator=(const App&&) = delete; + + template +- void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor) ++ void handleUpgrade(const Request& req, ++ const std::shared_ptr& asyncResp, ++ Adaptor&& adaptor) + { +- router.handleUpgrade(req, res, std::forward(adaptor)); ++ router.handleUpgrade(req, asyncResp, std::forward(adaptor)); + } + + void handle(Request& req, +diff --git a/http/http_connection.hpp b/http/http_connection.hpp +index 8d76b25..9911ced 100644 +--- a/http/http_connection.hpp ++++ b/http/http_connection.hpp +@@ -381,10 +381,32 @@ class Connection : + (req->url == "/redfish/v1/EventService/Subscriptions/SSE")) + { + BMCWEB_LOG_DEBUG << "Request: " << this << " is getting upgraded"; +- handler->handleUpgrade(thisReq, res, std::move(adaptor)); +- // delete lambda with self shared_ptr +- // to enable connection destruction +- asyncResp->res.setCompleteRequestHandler(nullptr); ++ asyncResp->res.setCompleteRequestHandler( ++ [self(shared_from_this())](crow::Response& thisRes) { ++ if (self->res.resultInt() != 200) ++ { ++ // When any error occurs during handle upgradation, ++ // the result in response will be set to respective ++ // error. By default the Result will be OK (200), ++ // which implies successful handle upgrade. Response ++ // needs to be sent over this connection only on ++ // failure. ++ boost::asio::post(self->adaptor.get_executor(), ++ [self, &thisRes] { ++ self->completeRequest(thisRes); ++ }); ++ } ++ else ++ { ++ // Set Complete request handler to NULL to remove ++ // the shared pointer of connection to enable ++ // connection destruction. As the connection would ++ // get upgraded, we wouldn't need this connection ++ // any longer ++ self->res.setCompleteRequestHandler(nullptr); ++ } ++ }); ++ handler->handleUpgrade(thisReq, asyncResp, std::move(adaptor)); + return; + } + handler->handle(thisReq, asyncResp); +diff --git a/http/routing.hpp b/http/routing.hpp +index 552e1cf..4bb81f2 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -1215,12 +1215,13 @@ class Router + } + + template +- void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor) ++ void handleUpgrade(const Request& req, ++ const std::shared_ptr& asyncResp, ++ Adaptor&& adaptor) + { + if (static_cast(req.method()) >= perMethods.size()) + { +- res.result(boost::beast::http::status::not_found); +- res.end(); ++ asyncResp->res.result(boost::beast::http::status::not_found); + return; + } + +@@ -1233,8 +1234,7 @@ class Router + if (ruleIndex == 0U) + { + BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url; +- res.result(boost::beast::http::status::not_found); +- res.end(); ++ asyncResp->res.result(boost::beast::http::status::not_found); + return; + } + +@@ -1247,23 +1247,24 @@ class Router + { + BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: " + << req.url; +- res.result(boost::beast::http::status::moved_permanently); ++ asyncResp->res.result( ++ boost::beast::http::status::moved_permanently); + + // TODO absolute url building + if (req.getHeaderValue("Host").empty()) + { +- res.addHeader("Location", std::string(req.url) + "/"); ++ asyncResp->res.addHeader("Location", ++ std::string(req.url) + "/"); + } + else + { +- res.addHeader( ++ asyncResp->res.addHeader( + "Location", + req.isSecure + ? "https://" + : "http://" + std::string(req.getHeaderValue("Host")) + + std::string(req.url) + "/"); + } +- res.end(); + return; + } + +@@ -1274,8 +1275,7 @@ class Router + << " with " << req.methodString() << "(" + << static_cast(req.method()) << ") / " + << rules[ruleIndex]->getMethods(); +- res.result(boost::beast::http::status::not_found); +- res.end(); ++ asyncResp->res.result(boost::beast::http::status::not_found); + return; + } + +@@ -1286,14 +1286,19 @@ class Router + // any uncaught exceptions become 500s + try + { +- rules[ruleIndex]->handleUpgrade(req, res, ++ // Creating temporary response object to call handleUpgrade ++ // We cannot pass the asyncResp as it will be destroyed ++ // The response object is not initialized as handleUpgrade wouldn't ++ // be using this object ++ crow::Response resp; ++ rules[ruleIndex]->handleUpgrade(req, resp, + std::forward(adaptor)); + } + catch (const std::exception& e) + { + BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what(); +- res.result(boost::beast::http::status::internal_server_error); +- res.end(); ++ asyncResp->res.result( ++ boost::beast::http::status::internal_server_error); + return; + } + catch (...) +@@ -1301,8 +1306,8 @@ class Router + BMCWEB_LOG_ERROR + << "An uncaught exception occurred. The type was unknown " + "so no information was available."; +- res.result(boost::beast::http::status::internal_server_error); +- res.end(); ++ asyncResp->res.result( ++ boost::beast::http::status::internal_server_error); + return; + } + } +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0002-Move-privileges-to-separate-entity.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0002-Move-privileges-to-separate-entity.patch new file mode 100644 index 000000000..1217147b4 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0002-Move-privileges-to-separate-entity.patch @@ -0,0 +1,109 @@ +From 6483f0af926391e8d1f256ba0f23f3640260cfd1 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Mon, 18 Oct 2021 22:52:17 +0530 +Subject: [PATCH] Move privileges to separate entity + +The privilege property of a rule is currently part of RuleParameterTraits +structure. Moving this property (member function) out into a separate +entity PrivilegeParameterTraits. +This move is required to enable inheriting this entity into Weksockets +and SseSockets. + +Tested: + - bmcweb is functional and is responding to Redfish URI's + - User Privilege check for URI's is functional. + +Change-Id: I288ab12258c15ae5a626f4409fc3b4a9cc574ea3 +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/routing.hpp | 53 +++++++++++++++++++++++++++--------------------- + 1 file changed, 30 insertions(+), 23 deletions(-) + +diff --git a/http/routing.hpp b/http/routing.hpp +index 858f146..acc99dc 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -102,6 +102,8 @@ class BaseRule + friend class Router; + template + friend struct RuleParameterTraits; ++ template ++ friend struct PrivilegeParameterTraits; + }; + + namespace detail +@@ -316,6 +318,33 @@ struct Wrapped + } // namespace routing_handler_call_helper + } // namespace detail + ++template ++struct PrivilegeParameterTraits ++{ ++ using self_t = T; ++ self_t& privileges( ++ const std::initializer_list>& p) ++ { ++ self_t* self = static_cast(this); ++ for (const std::initializer_list& privilege : p) ++ { ++ self->privilegesSet.emplace_back(privilege); ++ } ++ return *self; ++ } ++ ++ template ++ self_t& privileges(const std::array& p) ++ { ++ self_t* self = static_cast(this); ++ for (const redfish::Privileges& privilege : p) ++ { ++ self->privilegesSet.emplace_back(privilege); ++ } ++ return *self; ++ } ++}; ++ + class WebSocketRule : public BaseRule + { + using self_t = WebSocketRule; +@@ -462,7 +491,7 @@ class SseSocketRule : public BaseRule + }; + + template +-struct RuleParameterTraits ++struct RuleParameterTraits : public PrivilegeParameterTraits + { + using self_t = T; + WebSocketRule& websocket() +@@ -503,28 +532,6 @@ struct RuleParameterTraits + self->methodsBitfield |= 1U << static_cast(method); + return *self; + } +- +- self_t& privileges( +- const std::initializer_list>& p) +- { +- self_t* self = static_cast(this); +- for (const std::initializer_list& privilege : p) +- { +- self->privilegesSet.emplace_back(privilege); +- } +- return *self; +- } +- +- template +- self_t& privileges(const std::array& p) +- { +- self_t* self = static_cast(this); +- for (const redfish::Privileges& privilege : p) +- { +- self->privilegesSet.emplace_back(privilege); +- } +- return *self; +- } + }; + + class DynamicRule : public BaseRule, public RuleParameterTraits +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0003-Add-Support-for-privilege-check-in-handleUpgrade.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0003-Add-Support-for-privilege-check-in-handleUpgrade.patch new file mode 100644 index 000000000..4fe19e919 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0003-Add-Support-for-privilege-check-in-handleUpgrade.patch @@ -0,0 +1,220 @@ +From 93f66a039d4e840d7597288b296f68d467039006 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Mon, 18 Oct 2021 22:55:38 +0530 +Subject: [PATCH] Add Support for privilege check in handleUpgrade + +This commit enables privilege check for user(s) in case of upgraded +connections. +Currently users with no privileges will also be able to access +Websockets connections (Ex: KVM). + +Tested: + - websocket_test.py Passed + - Admin and Operator users were able to access KVM on WebUI + - Readonly User was unable to access KVM on WebUI + +Change-Id: Id9d33aeca24d8fafb2e9dcc28c46a48930740cd6 +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/app.hpp | 2 +- + http/routing.hpp | 164 +++++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 137 insertions(+), 29 deletions(-) + +diff --git a/http/app.hpp b/http/app.hpp +index 7e946dc..d63c6cb 100644 +--- a/http/app.hpp ++++ b/http/app.hpp +@@ -51,7 +51,7 @@ class App + App& operator=(const App&&) = delete; + + template +- void handleUpgrade(const Request& req, ++ void handleUpgrade(Request& req, + const std::shared_ptr& asyncResp, + Adaptor&& adaptor) + { +diff --git a/http/routing.hpp b/http/routing.hpp +index 9443657..048734d 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -1217,7 +1217,7 @@ class Router + } + + template +- void handleUpgrade(const Request& req, ++ void handleUpgrade(Request& req, + const std::shared_ptr& asyncResp, + Adaptor&& adaptor) + { +@@ -1285,33 +1285,141 @@ class Router + << "' " << static_cast(req.method()) << " / " + << rules[ruleIndex]->getMethods(); + +- // any uncaught exceptions become 500s +- try +- { +- // Creating temporary response object to call handleUpgrade +- // We cannot pass the asyncResp as it will be destroyed +- // The response object is not initialized as handleUpgrade wouldn't +- // be using this object +- crow::Response resp; +- rules[ruleIndex]->handleUpgrade(req, resp, +- std::forward(adaptor)); +- } +- catch (const std::exception& e) +- { +- BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what(); +- asyncResp->res.result( +- boost::beast::http::status::internal_server_error); +- return; +- } +- catch (...) +- { +- BMCWEB_LOG_ERROR +- << "An uncaught exception occurred. The type was unknown " +- "so no information was available."; +- asyncResp->res.result( +- boost::beast::http::status::internal_server_error); +- return; +- } ++ crow::connections::systemBus->async_method_call( ++ [&req, asyncResp, &rules, ruleIndex, &adaptor]( ++ const boost::system::error_code ec, ++ std::map>> ++ userInfo) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "GetUserInfo failed..."; ++ asyncResp->res.result( ++ boost::beast::http::status::internal_server_error); ++ return; ++ } ++ ++ const std::string* userRolePtr = nullptr; ++ auto userInfoIter = userInfo.find("UserPrivilege"); ++ if (userInfoIter != userInfo.end()) ++ { ++ userRolePtr = ++ std::get_if(&userInfoIter->second); ++ } ++ ++ std::string userRole{}; ++ if (userRolePtr != nullptr) ++ { ++ userRole = *userRolePtr; ++ BMCWEB_LOG_DEBUG << "userName = " << req.session->username ++ << " userRole = " << *userRolePtr; ++ } ++ ++ bool* remoteUserPtr = nullptr; ++ auto remoteUserIter = userInfo.find("RemoteUser"); ++ if (remoteUserIter != userInfo.end()) ++ { ++ remoteUserPtr = std::get_if(&remoteUserIter->second); ++ } ++ if (remoteUserPtr == nullptr) ++ { ++ BMCWEB_LOG_ERROR ++ << "RemoteUser property missing or wrong type"; ++ asyncResp->res.result( ++ boost::beast::http::status::internal_server_error); ++ return; ++ } ++ bool remoteUser = *remoteUserPtr; ++ ++ bool passwordExpired = false; // default for remote user ++ if (!remoteUser) ++ { ++ bool* passwordExpiredPtr = nullptr; ++ auto passwordExpiredIter = ++ userInfo.find("UserPasswordExpired"); ++ if (passwordExpiredIter != userInfo.end()) ++ { ++ passwordExpiredPtr = ++ std::get_if(&passwordExpiredIter->second); ++ } ++ if (passwordExpiredPtr != nullptr) ++ { ++ passwordExpired = *passwordExpiredPtr; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "UserPasswordExpired property is expected for" ++ " local user but is missing or wrong type"; ++ asyncResp->res.result( ++ boost::beast::http::status::internal_server_error); ++ return; ++ } ++ } ++ ++ // Get the userprivileges from the role ++ redfish::Privileges userPrivileges = ++ redfish::getUserPrivileges(userRole); ++ ++ // Set isConfigureSelfOnly based on D-Bus results. This ++ // ignores the results from both pamAuthenticateUser and the ++ // value from any previous use of this session. ++ req.session->isConfigureSelfOnly = passwordExpired; ++ ++ // Modifyprivileges if isConfigureSelfOnly. ++ if (req.session->isConfigureSelfOnly) ++ { ++ // Remove allprivileges except ConfigureSelf ++ userPrivileges = userPrivileges.intersection( ++ redfish::Privileges{"ConfigureSelf"}); ++ BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf"; ++ } ++ ++ if (!rules[ruleIndex]->checkPrivileges(userPrivileges)) ++ { ++ asyncResp->res.result( ++ boost::beast::http::status::forbidden); ++ if (req.session->isConfigureSelfOnly) ++ { ++ redfish::messages::passwordChangeRequired( ++ asyncResp->res, ++ crow::utility::urlFromPieces( ++ "redfish", "v1", "AccountService", "Accounts", ++ req.session->username)); ++ } ++ return; ++ } ++ ++ req.userRole = userRole; ++ ++ // any uncaught exceptions become 500s ++ try ++ { ++ crow::Response resp; ++ rules[ruleIndex]->handleUpgrade(req, resp, ++ std::move(adaptor)); ++ } ++ catch (std::exception& e) ++ { ++ BMCWEB_LOG_ERROR << "An uncaught exception occurred: " ++ << e.what(); ++ asyncResp->res.result( ++ boost::beast::http::status::internal_server_error); ++ return; ++ } ++ catch (...) ++ { ++ BMCWEB_LOG_ERROR ++ << "An uncaught exception occurred. The type was " ++ "unknown so no information was available."; ++ asyncResp->res.result( ++ boost::beast::http::status::internal_server_error); ++ return; ++ } ++ }, ++ "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", ++ "xyz.openbmc_project.User.Manager", "GetUserInfo", ++ req.session->username); + } + + void handle(Request& req, +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0004-Add-Privileges-to-Websockets.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0004-Add-Privileges-to-Websockets.patch new file mode 100644 index 000000000..19c671754 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0004-Add-Privileges-to-Websockets.patch @@ -0,0 +1,140 @@ +From 9b27d3e7c1670d53cfb1c0a88cc75155ebfba71a Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Mon, 18 Oct 2021 22:58:29 +0530 +Subject: [PATCH] Add Privileges to Websockets + +This commit adds Privileges to Websockets. +In the current implementation, once a rule is upgraded (i.e. from +BaseRule to WebSocket), there is no provosion to add priviliges. +In this commit, WebSocket inherits PrivilegeParameterTraits to enable +privileges. + +Also, in the earlier implementation, .privilege() was called after +BMCWEB_ROUTE(). This results in adding those privileges to the Base rule +that is created. By moving the privileges() below websocket(), the +privileges are applied to the websocket. + +Tested: + - websocket_test.py Passed + - Admin and Operator users were able to access KVM on WebUI + - Readonly User was unable to access KVM on WebUI + +Change-Id: Iff2051dbb7d363c902fd463fa446f280adc6d648 +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/routing.hpp | 4 +++- + include/dbus_monitor.hpp | 3 ++- + include/kvm_websocket.hpp | 4 +++- + include/obmc_console.hpp | 4 +++- + include/vm_websocket.hpp | 4 +++- + 5 files changed, 14 insertions(+), 5 deletions(-) + +diff --git a/http/routing.hpp b/http/routing.hpp +index e2a8fbb..6ea3185 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -345,7 +345,9 @@ struct PrivilegeParameterTraits + } + }; + +-class WebSocketRule : public BaseRule ++class WebSocketRule : ++ public BaseRule, ++ public PrivilegeParameterTraits + { + using self_t = WebSocketRule; + +diff --git a/include/dbus_monitor.hpp b/include/dbus_monitor.hpp +index a6c86c6..163f884 100644 +--- a/include/dbus_monitor.hpp ++++ b/include/dbus_monitor.hpp +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -105,8 +106,8 @@ inline int onPropertyUpdate(sd_bus_message* m, void* userdata, + inline void requestRoutes(App& app) + { + BMCWEB_ROUTE(app, "/subscribe") +- .privileges({{"Login"}}) + .websocket() ++ .privileges(redfish::privileges::privilegeSetLogin) + .onopen([&](crow::websocket::Connection& conn, + const std::shared_ptr&) { + BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; +diff --git a/include/kvm_websocket.hpp b/include/kvm_websocket.hpp +index a9dc8ea..3f124a2 100644 +--- a/include/kvm_websocket.hpp ++++ b/include/kvm_websocket.hpp +@@ -4,6 +4,7 @@ + #include + #include + #include ++#include + #include + + namespace crow +@@ -159,8 +160,9 @@ inline void requestRoutes(App& app) + sessions.reserve(maxSessions); + + BMCWEB_ROUTE(app, "/kvm/0") +- .privileges({{"ConfigureComponents", "ConfigureManager"}}) + .websocket() ++ .privileges(redfish::privileges:: ++ privilegeSetConfigureManagerOrConfigureComponents) + .onopen([](crow::websocket::Connection& conn, + const std::shared_ptr&) { + BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; +diff --git a/include/obmc_console.hpp b/include/obmc_console.hpp +index ff0a51f..22a49a8 100644 +--- a/include/obmc_console.hpp ++++ b/include/obmc_console.hpp +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + + namespace crow +@@ -136,8 +137,9 @@ inline void connectHandler(const boost::system::error_code& ec) + inline void requestRoutes(App& app) + { + BMCWEB_ROUTE(app, "/console0") +- .privileges({{"ConfigureComponents", "ConfigureManager"}}) + .websocket() ++ .privileges(redfish::privileges:: ++ privilegeSetConfigureManagerOrConfigureComponents) + .onopen([](crow::websocket::Connection& conn, + const std::shared_ptr&) { + BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; +diff --git a/include/vm_websocket.hpp b/include/vm_websocket.hpp +index 02f958a..ebbe68f 100644 +--- a/include/vm_websocket.hpp ++++ b/include/vm_websocket.hpp +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + #include + + #include +@@ -156,8 +157,9 @@ static std::shared_ptr handler; + inline void requestRoutes(App& app) + { + BMCWEB_ROUTE(app, "/vm/0/0") +- .privileges({{"ConfigureComponents", "ConfigureManager"}}) + .websocket() ++ .privileges(redfish::privileges:: ++ privilegeSetConfigureManagerOrConfigureComponents) + .onopen([](crow::websocket::Connection& conn, + const std::shared_ptr&) { + BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0005-Add-Privileges-to-SseSockets.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0005-Add-Privileges-to-SseSockets.patch new file mode 100644 index 000000000..06ffb3a46 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/http_routing/0005-Add-Privileges-to-SseSockets.patch @@ -0,0 +1,63 @@ +From 0ceb343809ff498cbfa389c54a158d255a2cca88 Mon Sep 17 00:00:00 2001 +From: P Dheeraj Srujan Kumar +Date: Mon, 18 Oct 2021 23:02:00 +0530 +Subject: [PATCH] Add Privileges to SseSockets + +This commit adds Privileges to Ssesockets. +In the current implementation, once a rule is upgraded (i.e. from +BaseRule to SseSocket), there is no provision to add priviliges. +In this commit, SseSocket inherits PrivilegeParameterTraits to +enable privileges. + +Also, in the earlier implementation, .privilege() was called after +BMCWEB_ROUTE(). This results in adding those privileges to the Base +rule that is created. By moving the privileges() below websocket(), +the privileges are applied to the Ssesocket. + +Tested: + - SSE Subscription was successful with Admin and Operator Users + - SSE Subscription was rejected while using Readonly User + - websocket_test.py Passed + - Admin and Operator users were able to access KVM on WebUI + - Readonly User was unable to access KVM on WebUI + +Change-Id: I41739401893b1c2bf718e11ec7676d69f954c98f +Signed-off-by: P Dheeraj Srujan Kumar +--- + http/routing.hpp | 4 +++- + include/eventservice_sse.hpp | 3 ++- + 2 files changed, 5 insertions(+), 2 deletions(-) + +diff --git a/http/routing.hpp b/http/routing.hpp +index 6ea3185..13174b2 100644 +--- a/http/routing.hpp ++++ b/http/routing.hpp +@@ -430,7 +430,9 @@ class WebSocketRule : + std::function errorHandler; + }; + +-class SseSocketRule : public BaseRule ++class SseSocketRule : ++ public BaseRule, ++ public PrivilegeParameterTraits + { + using self_t = SseSocketRule; + +diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp +index 2f22f98..f880344 100644 +--- a/include/eventservice_sse.hpp ++++ b/include/eventservice_sse.hpp +@@ -192,8 +192,9 @@ static void deleteSubscription(std::shared_ptr& conn) + inline void requestRoutes(App& app) + { + BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/SSE") +- .privileges({{"ConfigureComponents", "ConfigureManager"}}) + .serverSentEvent() ++ .privileges(redfish::privileges:: ++ privilegeSetConfigureManagerOrConfigureComponents) + .onopen([](std::shared_ptr& conn, + const crow::Request& req, crow::Response& res) { + BMCWEB_LOG_DEBUG << "Connection " << conn << " opened."; +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch new file mode 100644 index 000000000..6b57dc912 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch @@ -0,0 +1,26 @@ +From 71b55e9773c387d6510650e7cf64f050a853ac77 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Tue, 30 Nov 2021 16:29:12 +0100 +Subject: [PATCH] Revert "Remove LogService from TelemetryService" + +This reverts commit 2b3da45876aac57a36d3093379a992d699e7e396. +--- + redfish-core/lib/telemetry_service.hpp | 2 + + 1 files changed, 2 insertions(+), 0 deletions(-) + +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index b79a5cd..e3e4a25 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -25,6 +25,8 @@ inline void handleTelemetryServiceGet( + "/redfish/v1/TelemetryService/MetricReports"; + asyncResp->res.jsonValue["Triggers"]["@odata.id"] = + "/redfish/v1/TelemetryService/Triggers"; ++ asyncResp->res.jsonValue["LogService"]["@odata.id"] = ++ "/redfish/v1/Managers/bmc/LogServices/Journal"; + + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec, +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch new file mode 100644 index 000000000..242bec0dc --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-ref-use-url_view-for-telemetry-uris.patch @@ -0,0 +1,193 @@ +From a6d32959d5420f3fdca0d391feb54ac89f65dca3 Mon Sep 17 00:00:00 2001 +From: Szymon Dompke +Date: Tue, 1 Mar 2022 16:34:08 +0100 +Subject: [PATCH] ref: use url_view for telemetry uris + +This change refactor telemetry code to use bmcweb utility function for +uri construction, which is safe and preferred way, instead of string +operations. + +Testing done: +- Some basic GET operations done on Telemetry, no regression. + +Signed-off-by: Szymon Dompke +Change-Id: I6de5d79a078944d398357f27dc0c201c130c4302 +--- + redfish-core/include/event_service_manager.hpp | 6 ++++-- + redfish-core/include/utils/telemetry_utils.hpp | 5 +---- + redfish-core/lib/metric_report.hpp | 14 +++++++++++--- + redfish-core/lib/metric_report_definition.hpp | 13 ++++++++++--- + redfish-core/lib/trigger.hpp | 15 +++++++++------ + 5 files changed, 35 insertions(+), 18 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index e879f9e..0ed404f 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -505,14 +505,16 @@ class Subscription : public persistent_data::UserSubscription + void filterAndSendReports(const std::string& reportId, + const telemetry::TimestampReadings& var) + { +- std::string mrdUri = telemetry::metricReportDefinitionUri + ("/" + id); ++ boost::urls::url mrdUri = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", reportId); + + // Empty list means no filter. Send everything. + if (!metricReportDefinitions.empty()) + { + if (std::find(metricReportDefinitions.begin(), + metricReportDefinitions.end(), +- mrdUri) == metricReportDefinitions.end()) ++ mrdUri.string()) == metricReportDefinitions.end()) + { + return; + } +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 8aeff0d..6930d0a 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -1,6 +1,7 @@ + #pragma once + + #include "dbus_utility.hpp" ++#include "utility.hpp" + + namespace redfish + { +@@ -9,10 +10,6 @@ namespace telemetry + { + constexpr const char* service = "xyz.openbmc_project.Telemetry"; + constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; +-constexpr const char* metricReportDefinitionUri = +- "/redfish/v1/TelemetryService/MetricReportDefinitions"; +-constexpr const char* metricReportUri = +- "/redfish/v1/TelemetryService/MetricReports"; + + inline std::string getDbusReportPath(const std::string& id) + { +diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp +index 89bd8db..2fb8d82 100644 +--- a/redfish-core/lib/metric_report.hpp ++++ b/redfish-core/lib/metric_report.hpp +@@ -14,6 +14,9 @@ namespace redfish + namespace telemetry + { + ++constexpr const char* metricReportUri = ++ "/redfish/v1/TelemetryService/MetricReports"; ++ + using Readings = + std::vector>; + using TimestampReadings = std::tuple; +@@ -39,11 +42,16 @@ inline bool fillReport(nlohmann::json& json, const std::string& id, + const TimestampReadings& timestampReadings) + { + json["@odata.type"] = "#MetricReport.v1_3_0.MetricReport"; +- json["@odata.id"] = telemetry::metricReportUri + std::string("/") + id; ++ json["@odata.id"] = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReports", id) ++ .string(); + json["Id"] = id; + json["Name"] = id; + json["MetricReportDefinition"]["@odata.id"] = +- telemetry::metricReportDefinitionUri + std::string("/") + id; ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", id) ++ .string(); + + const auto& [timestamp, readings] = timestampReadings; + json["Timestamp"] = crow::utility::getDateTimeUintMs(timestamp); +@@ -62,7 +70,7 @@ inline void requestRoutesMetricReportCollection(App& app) + asyncResp->res.jsonValue["@odata.type"] = + "#MetricReportCollection.MetricReportCollection"; + asyncResp->res.jsonValue["@odata.id"] = +- "/redfish/v1/TelemetryService/MetricReports"; ++ telemetry::metricReportUri; + asyncResp->res.jsonValue["Name"] = "Metric Report Collection"; + const std::vector interfaces{ + telemetry::reportInterface}; +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index ab7c88b..1a520f3 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -18,6 +18,9 @@ namespace redfish + namespace telemetry + { + ++constexpr const char* metricReportDefinitionUri = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions"; ++ + using ReadingParameters = + std::vector>; +@@ -30,11 +33,15 @@ inline void fillReportDefinition( + asyncResp->res.jsonValue["@odata.type"] = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; + asyncResp->res.jsonValue["@odata.id"] = +- telemetry::metricReportDefinitionUri + std::string("/") + id; ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", id) ++ .string(); + asyncResp->res.jsonValue["Id"] = id; + asyncResp->res.jsonValue["Name"] = id; + asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = +- telemetry::metricReportUri + std::string("/") + id; ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReports", id) ++ .string(); + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; + asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; + +@@ -365,7 +372,7 @@ inline void requestRoutesMetricReportDefinitionCollection(App& app) + "#MetricReportDefinitionCollection." + "MetricReportDefinitionCollection"; + asyncResp->res.jsonValue["@odata.id"] = +- "/redfish/v1/TelemetryService/MetricReportDefinitions"; ++ telemetry::metricReportDefinitionUri; + asyncResp->res.jsonValue["Name"] = + "Metric Definition Collection"; + const std::vector interfaces{ +diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp +index cdd5781..da6a5db 100644 +--- a/redfish-core/lib/trigger.hpp ++++ b/redfish-core/lib/trigger.hpp +@@ -143,9 +143,11 @@ inline nlohmann::json + nlohmann::json reports = nlohmann::json::array(); + for (const std::string& name : reportNames) + { +- reports.push_back({ +- {"@odata.id", metricReportDefinitionUri + std::string("/") + name}, +- }); ++ reports.push_back( ++ {{"@odata.id", ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", name) ++ .string()}}); + } + + return reports; +@@ -214,7 +216,9 @@ inline bool fillTrigger( + } + + json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; +- json["@odata.id"] = triggerUri + std::string("/") + id; ++ json["@odata.id"] = crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", "Triggers", id) ++ .string(); + json["Id"] = id; + json["Name"] = *name; + +@@ -282,8 +286,7 @@ inline void requestRoutesTriggerCollection(App& app) + const std::shared_ptr& asyncResp) { + asyncResp->res.jsonValue["@odata.type"] = + "#TriggersCollection.TriggersCollection"; +- asyncResp->res.jsonValue["@odata.id"] = +- "/redfish/v1/TelemetryService/Triggers"; ++ asyncResp->res.jsonValue["@odata.id"] = telemetry::triggerUri; + asyncResp->res.jsonValue["Name"] = "Triggers Collection"; + const std::vector interfaces{ + telemetry::triggerInterface}; +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch new file mode 100644 index 000000000..f741142c4 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Fix-Trigger-GET-functionality.patch @@ -0,0 +1,127 @@ +From cc10d221a17e1136bf3ec7f62583a4ca44212adc Mon Sep 17 00:00:00 2001 +From: Szymon Dompke +Date: Tue, 22 Feb 2022 13:58:00 +0100 +Subject: [PATCH] Fix Trigger GET functionality + +This change is fixing 2 issues related to GET on Trigger schema: +- Links to MetricReportDefintions were not parsed properly. Dbus is + returning collection of strings prefixed with "TelemetryService/". + This prefix was supposed to be removed. +- In case of error occurring during internal data parsing, GET could + return both "Internal Error" message and incomplete Trigger + representation. By swapping few lines of code, this issue is fixed: + now either error is returned or full Trigger representation, but never + both of them. + +Testing done: +- Links are now returning proper uris of existing MRD. +- Internal Error is returned for malformed data returned by service. +- Other Trigger GET functionality remained unchanged. + +Signed-off-by: Szymon Dompke +Change-Id: I81ebbf3889a152199bef7230de56a73bb112731b +--- + redfish-core/lib/trigger.hpp | 65 ++++++++++++++++++++++-------------- + 1 file changed, 40 insertions(+), 25 deletions(-) + +diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp +index da6a5db..5e0897e 100644 +--- a/redfish-core/lib/trigger.hpp ++++ b/redfish-core/lib/trigger.hpp +@@ -137,20 +137,29 @@ inline std::optional + return std::make_optional(thresholds); + } + +-inline nlohmann::json ++inline std::optional + getMetricReportDefinitions(const std::vector& reportNames) + { + nlohmann::json reports = nlohmann::json::array(); ++ + for (const std::string& name : reportNames) + { +- reports.push_back( +- {{"@odata.id", +- crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", +- "MetricReportDefinitions", name) +- .string()}}); ++ sdbusplus::message::object_path path(name); ++ if (path.parent_path() != "TelemetryService") ++ { ++ BMCWEB_LOG_ERROR << "Property ReportNames contains invalid value: " ++ << name; ++ return std::nullopt; ++ } ++ reports.push_back({ ++ {"@odata.id", crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", path.filename()) ++ .string()}, ++ }); + } + +- return reports; ++ return std::make_optional(reports); + } + + inline std::vector +@@ -215,12 +224,23 @@ inline bool fillTrigger( + return false; + } + +- json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; +- json["@odata.id"] = crow::utility::urlFromPieces( +- "redfish", "v1", "TelemetryService", "Triggers", id) +- .string(); +- json["Id"] = id; +- json["Name"] = *name; ++ std::optional> triggerActions = ++ getTriggerActions(*actions); ++ if (!triggerActions) ++ { ++ BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: " ++ << id; ++ return false; ++ } ++ ++ std::optional linkedReports = ++ getMetricReportDefinitions(*reports); ++ if (!linkedReports) ++ { ++ BMCWEB_LOG_ERROR << "Property ReportNames is invalid in Trigger: " ++ << id; ++ return false; ++ } + + if (*discrete) + { +@@ -257,20 +277,15 @@ inline bool fillTrigger( + json["MetricType"] = "Numeric"; + } + +- std::optional> triggerActions = +- getTriggerActions(*actions); +- +- if (!triggerActions) +- { +- BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: " +- << id; +- return false; +- } +- ++ json["@odata.type"] = "#Triggers.v1_2_0.Triggers"; ++ json["@odata.id"] = crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", "Triggers", id) ++ .string(); ++ json["Id"] = id; ++ json["Name"] = *name; + json["TriggerActions"] = *triggerActions; + json["MetricProperties"] = getMetricProperties(*sensors); +- json["Links"]["MetricReportDefinitions"] = +- getMetricReportDefinitions(*reports); ++ json["Links"]["MetricReportDefinitions"] = *linkedReports; + + return true; + } +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch new file mode 100644 index 000000000..735033c70 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch @@ -0,0 +1,848 @@ +From c3da912209b3c6d8d3a3724652a09b77e85e8897 Mon Sep 17 00:00:00 2001 +From: Szymon Dompke +Date: Fri, 4 Mar 2022 13:11:38 +0100 +Subject: [PATCH] Add support for POST on TriggersCollection +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Added POST method on /redfish/v1/TelemetryService/Triggers uri, which +creates new trigger in telemetry service, by using dbus call AddTrigger. + +By DMTF, most of the properties are not required, and as such are +treated as optional. Some values can be deduced from others (like +'MetricType', depending on 'DiscreteTriggers' or 'NumericThresholds'). +All properties provided in POST body by user will be verified against +each other, and errors will be raised. Few examples of such situations: +- 'MetricType' is set to 'Discrete' but 'NumericThresholds' was passed. +- 'MetricType' is set to 'Numeric' but "DiscreteTriggers' or + 'DiscreteTriggerCondition' were passed +- 'DiscreteTriggerCondition' is set to 'Specified' but + 'DiscreteTriggers' is an empty array or was not passed. +- 'DiscreteTriggerCondition' is set to 'Changed' but 'DiscreteTriggers' + is passed and is not an empty array. + +Example 1 – Trigger with discrete values: +{ + "Id": "TestTrigger", + "MetricType": "Discrete", + "TriggerActions": [ + "RedfishEvent" + ], + "DiscreteTriggerCondition": "Specified", + "DiscreteTriggers": [ + { + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "Warning" + }, + { + "Name": "My discrete trigger", + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "OK" + }, + { + "Value": "55.88", + "DwellTime": "PT0.001S", + "Severity": "Critical" + } + ], + "MetricProperties": [ + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading" + ], + "Links": { + "MetricReportDefinitions": [] + } +} + +Example 2 – trigger with numeric threshold: +{ + "Id": "TestTrigger2", + "Name": "My Numeric Trigger", + "MetricType": "Numeric", + "TriggerActions": [ + "RedfishEvent", + "RedfishMetricReport" + ], + "NumericThresholds": { + "UpperCritical": { + "Reading": 50, + "Activation": "Increasing", + "DwellTime": "PT0.001S" + }, + "UpperWarning": { + "Reading": 48.1, + "Activation": "Increasing", + "DwellTime": "PT0.004S" + } + }, + "MetricProperties": [ + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading", + "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/17/Reading" + ], + "Links": { + "MetricReportDefinitions": [ + "/redfish/v1/TelemetryService/MetricReportDefinitions/PowerMetrics", + "/redfish/v1/TelemetryService/MetricReportDefinitions/PowerMetricStats", + "/redfish/v1/TelemetryService/MetricReportDefinitions/PlatformPowerUsage" + ] + } +} + +Tested: +- Triggers were successfully created with above example message bodies. + This can be checked by calling: + 'busctl tree xyz.openbmc_project.Telemetry'. +- Expected errors were returned for messages with incorrect or mutually + exclusive properties and incorrect values. +- Redfish service validator is passing. + +Signed-off-by: Szymon Dompke +Change-Id: Ief8c76de8aa660ae0d2dbe4610c26a28186a290a +--- + redfish-core/include/utils/finalizer.hpp | 38 ++ + .../include/utils/telemetry_utils.hpp | 77 +++ + redfish-core/lib/trigger.hpp | 544 +++++++++++++++++- + 3 files changed, 653 insertions(+), 6 deletions(-) + create mode 100644 redfish-core/include/utils/finalizer.hpp + +diff --git a/redfish-core/include/utils/finalizer.hpp b/redfish-core/include/utils/finalizer.hpp +new file mode 100644 +index 0000000..f14a34a +--- /dev/null ++++ b/redfish-core/include/utils/finalizer.hpp +@@ -0,0 +1,38 @@ ++#pragma once ++ ++#include ++ ++namespace redfish ++{ ++ ++namespace utils ++{ ++ ++class Finalizer ++{ ++ public: ++ Finalizer() = delete; ++ Finalizer(std::function finalizer) : finalizer(std::move(finalizer)) ++ {} ++ ++ Finalizer(const Finalizer&) = delete; ++ Finalizer(Finalizer&&) = delete; ++ ++ Finalizer& operator=(const Finalizer&) = delete; ++ Finalizer& operator=(Finalizer&&) = delete; ++ ++ ~Finalizer() ++ { ++ if (finalizer) ++ { ++ finalizer(); ++ } ++ } ++ ++ private: ++ std::function finalizer; ++}; ++ ++} // namespace utils ++ ++} // namespace redfish +\ No newline at end of file +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 6930d0a..908ccfc 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -25,5 +25,82 @@ inline std::string getDbusTriggerPath(const std::string& id) + return {triggersPath / id}; + } + ++inline std::optional ++ getReportNameFromReportDefinitionUri(const std::string& uri) ++{ ++ constexpr std::string_view uriPattern = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions/"; ++ if (uri.starts_with(uriPattern)) ++ { ++ return uri.substr(uriPattern.length()); ++ } ++ return std::nullopt; ++} ++ ++inline std::optional ++ getTriggerIdFromDbusPath(const std::string& dbusPath) ++{ ++ constexpr std::string_view triggerTree = ++ "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService/"; ++ if (dbusPath.starts_with(triggerTree)) ++ { ++ return dbusPath.substr(triggerTree.length()); ++ } ++ return std::nullopt; ++} ++ ++inline bool getChassisSensorNode( ++ const std::shared_ptr& asyncResp, ++ const std::vector& uris, ++ boost::container::flat_set>& matched) ++{ ++ size_t uriIdx = 0; ++ for (const std::string& uri : uris) ++ { ++ std::string chassis; ++ std::string node; ++ ++ if (!uri.starts_with("/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(uriIdx)); ++ return false; ++ } ++ ++ if (node.ends_with('#')) ++ { ++ node.pop_back(); ++ } ++ ++ matched.emplace(std::move(chassis), std::move(node)); ++ uriIdx++; ++ } ++ return true; ++} ++ ++inline std::optional ++ redfishActionToDbusAction(const std::string& redfishAction) ++{ ++ if (redfishAction == "RedfishMetricReport") ++ { ++ return "UpdateReport"; ++ } ++ if (redfishAction == "RedfishEvent") ++ { ++ return "RedfishEvent"; ++ } ++ if (redfishAction == "LogToLogService") ++ { ++ return "LogToLogService"; ++ } ++ return std::nullopt; ++} ++ + } // namespace telemetry + } // namespace redfish +diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp +index 5e0897e..67aa5e6 100644 +--- a/redfish-core/lib/trigger.hpp ++++ b/redfish-core/lib/trigger.hpp +@@ -1,7 +1,9 @@ + #pragma once + +-#include "utils/collection.hpp" ++#include "sensors.hpp" ++#include "utils/finalizer.hpp" + #include "utils/telemetry_utils.hpp" ++#include "utils/time_utils.hpp" + + #include + #include +@@ -14,9 +16,13 @@ namespace redfish + { + namespace telemetry + { ++ + constexpr const char* triggerInterface = + "xyz.openbmc_project.Telemetry.Trigger"; +-constexpr const char* triggerUri = "/redfish/v1/TelemetryService/Triggers"; ++ ++static constexpr std::array ++ supportedNumericThresholdNames = {"UpperCritical", "LowerCritical", ++ "UpperWarning", "LowerWarning"}; + + using NumericThresholdParams = + std::tuple; +@@ -24,6 +30,10 @@ using NumericThresholdParams = + using DiscreteThresholdParams = + std::tuple; + ++using TriggerThresholdParams = ++ std::variant, ++ std::vector>; ++ + using TriggerThresholdParamsExt = + std::variant, + std::vector>; +@@ -35,6 +45,463 @@ using TriggerGetParamsVariant = + std::variant>; + ++namespace add_trigger ++{ ++ ++enum class MetricType ++{ ++ Discrete, ++ Numeric ++}; ++ ++enum class DiscreteCondition ++{ ++ Specified, ++ Changed ++}; ++ ++struct Context ++{ ++ struct ++ { ++ std::string id; ++ std::string name; ++ std::vector actions; ++ std::vector> ++ sensors; ++ std::vector reportNames; ++ TriggerThresholdParams thresholds; ++ } dbusArgs; ++ ++ struct ++ { ++ std::optional discreteCondition; ++ std::optional metricType; ++ std::optional> metricProperties; ++ } parsedInfo; ++ ++ boost::container::flat_map uriToDbusMerged{}; ++}; ++ ++inline std::optional getMetricType(const std::string& metricType) ++{ ++ if (metricType == "Discrete") ++ { ++ return MetricType::Discrete; ++ } ++ if (metricType == "Numeric") ++ { ++ return MetricType::Numeric; ++ } ++ return std::nullopt; ++} ++ ++inline std::optional ++ getDiscreteCondition(const std::string& discreteTriggerCondition) ++{ ++ if (discreteTriggerCondition == "Specified") ++ { ++ return DiscreteCondition::Specified; ++ } ++ if (discreteTriggerCondition == "Changed") ++ { ++ return DiscreteCondition::Changed; ++ } ++ return std::nullopt; ++} ++ ++inline bool parseNumericThresholds(crow::Response& res, ++ nlohmann::json& numericThresholds, ++ Context& ctx) ++{ ++ if (!numericThresholds.is_object()) ++ { ++ messages::propertyValueTypeError(res, numericThresholds.dump(), ++ "NumericThresholds"); ++ return false; ++ } ++ ++ std::vector parsedParams; ++ parsedParams.reserve(numericThresholds.size()); ++ ++ for (auto& [thresholdName, thresholdData] : numericThresholds.items()) ++ { ++ if (std::find(supportedNumericThresholdNames.begin(), ++ supportedNumericThresholdNames.end(), ++ thresholdName) == supportedNumericThresholdNames.end()) ++ { ++ messages::propertyUnknown(res, thresholdName); ++ return false; ++ } ++ ++ double reading = .0; ++ std::string activation; ++ std::string dwellTimeStr; ++ ++ if (!json_util::readJson(thresholdData, res, "Reading", reading, ++ "Activation", activation, "DwellTime", ++ dwellTimeStr)) ++ { ++ return false; ++ } ++ ++ std::optional dwellTime = ++ time_utils::fromDurationString(dwellTimeStr); ++ if (!dwellTime) ++ { ++ messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); ++ return false; ++ } ++ ++ parsedParams.emplace_back(thresholdName, ++ static_cast(dwellTime->count()), ++ activation, reading); ++ } ++ ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++} ++ ++inline bool parseDiscreteTriggers( ++ crow::Response& res, ++ std::optional>& discreteTriggers, Context& ctx) ++{ ++ std::vector parsedParams; ++ if (!discreteTriggers) ++ { ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++ } ++ ++ parsedParams.reserve(discreteTriggers->size()); ++ for (nlohmann::json& thresholdInfo : *discreteTriggers) ++ { ++ std::optional name; ++ std::string value; ++ std::string dwellTimeStr; ++ std::string severity; ++ ++ if (!json_util::readJson(thresholdInfo, res, "Name", name, "Value", ++ value, "DwellTime", dwellTimeStr, "Severity", ++ severity)) ++ { ++ return false; ++ } ++ ++ std::optional dwellTime = ++ time_utils::fromDurationString(dwellTimeStr); ++ if (!dwellTime) ++ { ++ messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr); ++ return false; ++ } ++ ++ if (!name) ++ { ++ name = ""; ++ } ++ ++ parsedParams.emplace_back( ++ *name, severity, static_cast(dwellTime->count()), value); ++ } ++ ++ ctx.dbusArgs.thresholds = std::move(parsedParams); ++ return true; ++} ++ ++inline bool parseTriggerThresholds( ++ crow::Response& res, ++ std::optional>& discreteTriggers, ++ std::optional& numericThresholds, Context& ctx) ++{ ++ if (discreteTriggers && numericThresholds) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "NumericThresholds"); ++ messages::propertyValueConflict(res, "NumericThresholds", ++ "DiscreteTriggers"); ++ return false; ++ } ++ ++ if (ctx.parsedInfo.discreteCondition) ++ { ++ if (numericThresholds) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggerCondition", ++ "NumericThresholds"); ++ messages::propertyValueConflict(res, "NumericThresholds", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ ++ if (ctx.parsedInfo.metricType) ++ { ++ if (*ctx.parsedInfo.metricType == MetricType::Discrete && ++ numericThresholds) ++ { ++ messages::propertyValueConflict(res, "NumericThresholds", ++ "MetricType"); ++ return false; ++ } ++ if (*ctx.parsedInfo.metricType == MetricType::Numeric && ++ discreteTriggers) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "MetricType"); ++ return false; ++ } ++ if (*ctx.parsedInfo.metricType == MetricType::Numeric && ++ ctx.parsedInfo.discreteCondition) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ ++ if (discreteTriggers || ctx.parsedInfo.discreteCondition || ++ (ctx.parsedInfo.metricType && ++ *ctx.parsedInfo.metricType == MetricType::Discrete)) ++ { ++ if (ctx.parsedInfo.discreteCondition) ++ { ++ if (*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Specified && ++ !discreteTriggers) ++ { ++ messages::createFailedMissingReqProperties(res, ++ "DiscreteTriggers"); ++ return false; ++ } ++ if (discreteTriggers && ((*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Specified && ++ discreteTriggers->empty()) || ++ (*ctx.parsedInfo.discreteCondition == ++ DiscreteCondition::Changed && ++ !discreteTriggers->empty()))) ++ { ++ messages::propertyValueConflict(res, "DiscreteTriggers", ++ "DiscreteTriggerCondition"); ++ return false; ++ } ++ } ++ if (!parseDiscreteTriggers(res, discreteTriggers, ctx)) ++ { ++ return false; ++ } ++ } ++ else if (numericThresholds) ++ { ++ if (!parseNumericThresholds(res, *numericThresholds, ctx)) ++ { ++ return false; ++ } ++ } ++ else ++ { ++ messages::createFailedMissingReqProperties( ++ res, "'DiscreteTriggers', 'NumericThresholds', " ++ "'DiscreteTriggerCondition' or 'MetricType'"); ++ return false; ++ } ++ return true; ++} ++ ++inline bool parseLinks(crow::Response& res, nlohmann::json& links, Context& ctx) ++{ ++ if (links.empty()) ++ { ++ return true; ++ } ++ ++ std::optional> metricReportDefinitions; ++ if (!json_util::readJson(links, res, "MetricReportDefinitions", ++ metricReportDefinitions)) ++ { ++ return false; ++ } ++ ++ if (metricReportDefinitions) ++ { ++ ctx.dbusArgs.reportNames.reserve(metricReportDefinitions->size()); ++ for (std::string& reportDefinionUri : *metricReportDefinitions) ++ { ++ std::optional reportName = ++ getReportNameFromReportDefinitionUri(reportDefinionUri); ++ if (!reportName) ++ { ++ messages::propertyValueIncorrect(res, "MetricReportDefinitions", ++ reportDefinionUri); ++ return false; ++ } ++ ctx.dbusArgs.reportNames.emplace_back("TelemetryService/" + ++ *reportName); ++ } ++ } ++ return true; ++} ++ ++inline bool parseMetricProperties(crow::Response& res, Context& ctx) ++{ ++ if (!ctx.parsedInfo.metricProperties) ++ { ++ return true; ++ } ++ ++ ctx.dbusArgs.sensors.reserve(ctx.parsedInfo.metricProperties->size()); ++ ++ size_t uriIdx = 0; ++ for (const std::string& uri : *ctx.parsedInfo.metricProperties) ++ { ++ auto el = ctx.uriToDbusMerged.find(uri); ++ if (el == ctx.uriToDbusMerged.end()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corsresponding to URI " ++ << uri; ++ messages::propertyValueNotInList( ++ res, uri, "MetricProperties/" + std::to_string(uriIdx)); ++ return false; ++ } ++ ++ const std::string& dbusPath = el->second; ++ ctx.dbusArgs.sensors.emplace_back(dbusPath, uri); ++ uriIdx++; ++ } ++ return true; ++} ++ ++inline bool parsePostTriggerParams(crow::Response& res, ++ const crow::Request& req, Context& ctx) ++{ ++ std::optional id; ++ std::optional name; ++ std::optional metricType; ++ std::optional> triggerActions; ++ std::optional discreteTriggerCondition; ++ std::optional> discreteTriggers; ++ std::optional numericThresholds; ++ std::optional links; ++ if (!json_util::readJsonPatch( ++ req, res, "Id", id, "Name", name, "MetricType", metricType, ++ "TriggerActions", triggerActions, "DiscreteTriggerCondition", ++ discreteTriggerCondition, "DiscreteTriggers", discreteTriggers, ++ "NumericThresholds", numericThresholds, "MetricProperties", ++ ctx.parsedInfo.metricProperties, "Links", links)) ++ { ++ return false; ++ } ++ ++ ctx.dbusArgs.id = id.value_or(""); ++ ctx.dbusArgs.name = name.value_or(""); ++ ++ if (metricType) ++ { ++ if (!(ctx.parsedInfo.metricType = getMetricType(*metricType))) ++ { ++ messages::propertyValueIncorrect(res, "MetricType", *metricType); ++ return false; ++ } ++ } ++ ++ if (discreteTriggerCondition) ++ { ++ if (!(ctx.parsedInfo.discreteCondition = ++ getDiscreteCondition(*discreteTriggerCondition))) ++ { ++ messages::propertyValueIncorrect(res, "DiscreteTriggerCondition", ++ *discreteTriggerCondition); ++ return false; ++ } ++ } ++ ++ if (triggerActions) ++ { ++ ctx.dbusArgs.actions.reserve(triggerActions->size()); ++ for (const std::string& action : *triggerActions) ++ { ++ if (const std::optional& dbusAction = ++ redfishActionToDbusAction(action)) ++ { ++ ctx.dbusArgs.actions.emplace_back(*dbusAction); ++ } ++ else ++ { ++ messages::propertyValueNotInList(res, action, "TriggerActions"); ++ return false; ++ } ++ } ++ } ++ ++ if (!parseTriggerThresholds(res, discreteTriggers, numericThresholds, ctx)) ++ { ++ return false; ++ } ++ ++ if (links) ++ { ++ if (!parseLinks(res, *links, ctx)) ++ { ++ return false; ++ } ++ } ++ return true; ++} ++ ++inline void createTrigger(const std::shared_ptr& asyncResp, ++ Context& ctx) ++{ ++ crow::connections::systemBus->async_method_call( ++ [aResp = asyncResp, id = ctx.dbusArgs.id]( ++ const boost::system::error_code ec, const std::string& dbusPath) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists(aResp->res, "Trigger", "Id", ++ id); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(aResp->res); ++ return; ++ } ++ if (ec) ++ { ++ messages::internalError(aResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ const std::optional& triggerId = ++ getTriggerIdFromDbusPath(dbusPath); ++ if (!triggerId) ++ { ++ messages::internalError(aResp->res); ++ BMCWEB_LOG_ERROR << "Unknown data returned by " ++ "AddTrigger DBus method"; ++ return; ++ } ++ ++ messages::created(aResp->res); ++ const boost::url locationUrl = crow::utility::urlFromPieces( ++ "redfish", "v1", "TelemetryService", "Triggers", *triggerId); ++ aResp->res.addHeader("Location", ++ std::string_view(locationUrl.string().data(), ++ locationUrl.string().size())); ++ }, ++ service, "/xyz/openbmc_project/Telemetry/Triggers", ++ "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger", ++ "TelemetryService/" + ctx.dbusArgs.id, ctx.dbusArgs.name, ++ ctx.dbusArgs.actions, ctx.dbusArgs.sensors, ctx.dbusArgs.reportNames, ++ ctx.dbusArgs.thresholds); ++} ++ ++} // namespace add_trigger ++ ++namespace get_trigger ++{ ++ + inline std::optional + getRedfishFromDbusAction(const std::string& dbusAction) + { +@@ -290,6 +757,8 @@ inline bool fillTrigger( + return true; + } + ++} // namespace get_trigger ++ + } // namespace telemetry + + inline void requestRoutesTriggerCollection(App& app) +@@ -301,14 +770,77 @@ inline void requestRoutesTriggerCollection(App& app) + const std::shared_ptr& asyncResp) { + asyncResp->res.jsonValue["@odata.type"] = + "#TriggersCollection.TriggersCollection"; +- asyncResp->res.jsonValue["@odata.id"] = telemetry::triggerUri; ++ asyncResp->res.jsonValue["@odata.id"] = ++ "/redfish/v1/TelemetryService/Triggers"; + asyncResp->res.jsonValue["Name"] = "Triggers Collection"; + const std::vector interfaces{ + telemetry::triggerInterface}; + collection_util::getCollectionMembers( +- asyncResp, telemetry::triggerUri, interfaces, ++ asyncResp, "/redfish/v1/TelemetryService/Triggers", ++ interfaces, + "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService"); + }); ++ ++ BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/") ++ .privileges(redfish::privileges::postTriggersCollection) ++ .methods(boost::beast::http::verb::post)( ++ [](const crow::Request& req, ++ const std::shared_ptr& asyncResp) { ++ const auto ctx = ++ std::make_shared(); ++ if (!telemetry::add_trigger::parsePostTriggerParams( ++ asyncResp->res, req, *ctx)) ++ { ++ return; ++ } ++ ++ if (!ctx->parsedInfo.metricProperties || ++ ctx->parsedInfo.metricProperties->empty()) ++ { ++ telemetry::add_trigger::createTrigger(asyncResp, *ctx); ++ return; ++ } ++ ++ boost::container::flat_set> ++ chassisSensors; ++ if (!telemetry::getChassisSensorNode( ++ asyncResp, *ctx->parsedInfo.metricProperties, ++ chassisSensors)) ++ { ++ return; ++ } ++ ++ const auto finalizer = ++ std::make_shared([asyncResp, ctx] { ++ if ((asyncResp->res).result() != ++ boost::beast::http::status::ok) ++ { ++ return; ++ } ++ if (!telemetry::add_trigger::parseMetricProperties( ++ asyncResp->res, *ctx)) ++ { ++ return; ++ } ++ telemetry::add_trigger::createTrigger(asyncResp, *ctx); ++ }); ++ ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, ctx, ++ finalizer](const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ if (status == boost::beast::http::status::ok) ++ { ++ ctx->uriToDbusMerged.insert(uriToDbus.begin(), ++ uriToDbus.end()); ++ } ++ }); ++ } ++ }); + } + + inline void requestRoutesTrigger(App& app) +@@ -339,8 +871,8 @@ inline void requestRoutesTrigger(App& app) + return; + } + +- if (!telemetry::fillTrigger(asyncResp->res.jsonValue, +- id, ret)) ++ if (!telemetry::get_trigger::fillTrigger( ++ asyncResp->res.jsonValue, id, ret)) + { + messages::internalError(asyncResp->res); + } +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch new file mode 100644 index 000000000..0ce5a3ea6 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch @@ -0,0 +1,559 @@ +From 4bd7f53cc01315774eedef637f5d1824cd7f662a Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Thu, 17 Jun 2021 13:37:57 +0000 +Subject: [PATCH] Switched bmcweb to use new telemetry service API + +Added support for multiple MetricProperties. Added support for new +parameters: CollectionTimeScope, CollectionDuration. + +Tested: + - It is possible to create MetricReportDefinitions with multiple + MetricProperties. + - Stub values for new parameters are correctly passed to telemetry + service. + - All existing telemetry service functionalities remain unchanged. + +Change-Id: I2cd17069e3ea015c8f5571c29278f1d50536272a +Signed-off-by: Krzysztof Grobelny +Signed-off-by: Lukasz Kazmierczak +--- + include/dbus_utility.hpp | 5 +- + redfish-core/lib/metric_report_definition.hpp | 330 ++++++++++++------ + redfish-core/lib/telemetry_service.hpp | 13 + + 3 files changed, 240 insertions(+), 108 deletions(-) + +diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp +index 481b33d..32153f8 100644 +--- a/include/dbus_utility.hpp ++++ b/include/dbus_utility.hpp +@@ -50,8 +50,9 @@ using DbusVariantType = std::variant< + std::vector>, + std::vector>>, + std::vector>, +- std::vector> ++ std::vector>, ++ std::string, std::string, std::string, uint64_t>> + >; + + // clang-format on +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index 1a520f3..ac980aa 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -17,50 +17,68 @@ namespace redfish + + namespace telemetry + { +- + constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions"; + +-using ReadingParameters = +- std::vector>; ++using ReadingParameters = std::vector>, ++ std::string, std::string, std::string, uint64_t>>; ++ ++std::string toReadfishReportAction(std::string_view action) ++{ ++ if (action == "EmitsReadingsUpdate") ++ { ++ return "RedfishEvent"; ++ } ++ if (action == "LogToMetricReportsCollection") ++ { ++ return "LogToMetricReportsCollection"; ++ } ++ return ""; ++} ++ ++std::string toDbusReportAction(std::string_view action) ++{ ++ if (action == "RedfishEvent") ++ { ++ return "EmitsReadingsUpdate"; ++ } ++ if (action == "LogToMetricReportsCollection") ++ { ++ return "LogToMetricReportsCollection"; ++ } ++ return ""; ++} + + inline void fillReportDefinition( + const std::shared_ptr& asyncResp, const std::string& id, + const std::vector>& +- ret) ++ properties) + { +- asyncResp->res.jsonValue["@odata.type"] = +- "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; +- asyncResp->res.jsonValue["@odata.id"] = +- crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", +- "MetricReportDefinitions", id) +- .string(); +- asyncResp->res.jsonValue["Id"] = id; +- asyncResp->res.jsonValue["Name"] = id; +- asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = +- crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", +- "MetricReports", id) +- .string(); +- asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; +- asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; +- +- const bool* emitsReadingsUpdate = nullptr; +- const bool* logToMetricReportsCollection = nullptr; ++ const std::vector* reportActions = nullptr; + const ReadingParameters* readingParams = nullptr; + const std::string* reportingType = nullptr; ++ const std::string* reportUpdates = nullptr; ++ const std::string* name = nullptr; ++ const uint64_t* appendLimit = nullptr; + const uint64_t* interval = nullptr; +- for (const auto& [key, var] : ret) ++ const bool* enabled = nullptr; ++ ++ for (const auto& [key, var] : properties) + { +- if (key == "EmitsReadingsUpdate") ++ if (key == "ReportActions") ++ { ++ reportActions = std::get_if>(&var); ++ } ++ else if (key == "ReportUpdates") + { +- emitsReadingsUpdate = std::get_if(&var); ++ reportUpdates = std::get_if(&var); + } +- else if (key == "LogToMetricReportsCollection") ++ else if (key == "AppendLimit") + { +- logToMetricReportsCollection = std::get_if(&var); ++ appendLimit = std::get_if(&var); + } +- else if (key == "ReadingParameters") ++ else if (key == "ReadingParametersFutureVersion") + { + readingParams = std::get_if(&var); + } +@@ -72,73 +90,149 @@ inline void fillReportDefinition( + { + interval = std::get_if(&var); + } ++ else if (key == "Name") ++ { ++ name = std::get_if(&var); ++ } ++ else if (key == "Enabled") ++ { ++ enabled = std::get_if(&var); ++ } + } +- if (emitsReadingsUpdate == nullptr || +- logToMetricReportsCollection == nullptr || readingParams == nullptr || +- reportingType == nullptr || interval == nullptr) ++ ++ std::vector redfishReportActions; ++ if (reportActions != nullptr) + { +- BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; +- messages::internalError(asyncResp->res); +- return; ++ for (const std::string& action : *reportActions) ++ { ++ std::string redfishAction = toReadfishReportAction(action); ++ ++ if (redfishAction.empty()) ++ { ++ BMCWEB_LOG_ERROR << "Unknown ReportActions element: " << action; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ redfishReportActions.emplace_back(std::move(redfishAction)); ++ } + } + +- std::vector redfishReportActions; +- redfishReportActions.reserve(2); +- if (*emitsReadingsUpdate) ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; ++ asyncResp->res.jsonValue["@odata.id"] = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReportDefinitions", id) ++ .string(); ++ asyncResp->res.jsonValue["Id"] = id; ++ asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "MetricReports", id) ++ .string(); ++ ++ if (enabled != nullptr) + { +- redfishReportActions.emplace_back("RedfishEvent"); ++ asyncResp->res.jsonValue["Status"]["State"] = ++ *enabled ? "Enabled" : "Disabled"; ++ asyncResp->res.jsonValue["MetricReportDefinitionEnabled"] = *enabled; + } +- if (*logToMetricReportsCollection) ++ ++ if (appendLimit != nullptr) + { +- redfishReportActions.emplace_back("LogToMetricReportsCollection"); ++ asyncResp->res.jsonValue["AppendLimit"] = *appendLimit; + } + +- nlohmann::json metrics = nlohmann::json::array(); +- for (const auto& [sensorPath, operationType, id, metadata] : *readingParams) ++ if (reportUpdates != nullptr) + { +- metrics.push_back({ +- {"MetricId", id}, +- {"MetricProperties", {metadata}}, +- }); ++ asyncResp->res.jsonValue["ReportUpdates"] = *reportUpdates; ++ } ++ ++ if (name != nullptr) ++ { ++ asyncResp->res.jsonValue["Name"] = *name; ++ } ++ ++ if (reportActions != nullptr) ++ { ++ asyncResp->res.jsonValue["ReportActions"] = ++ std::move(redfishReportActions); ++ } ++ ++ if (reportingType != nullptr) ++ { ++ asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; ++ } ++ ++ if (interval != nullptr) ++ { ++ asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = ++ time_utils::toDurationString(std::chrono::milliseconds(*interval)); ++ } ++ ++ if (readingParams != nullptr) ++ { ++ nlohmann::json& metrics = asyncResp->res.jsonValue["Metrics"]; ++ metrics = nlohmann::json::array(); ++ for (auto& [sensorData, collectionFunction, id, collectionTimeScope, ++ collectionDuration] : *readingParams) ++ { ++ std::vector metricProperties; ++ ++ for (const auto& [sensorPath, sensorMetadata] : sensorData) ++ { ++ metricProperties.emplace_back(sensorMetadata); ++ } ++ ++ metrics.push_back( ++ {{"MetricId", id}, ++ {"MetricProperties", std::move(metricProperties)}, ++ {"CollectionFunction", collectionFunction}, ++ {"CollectionDuration", ++ time_utils::toDurationString( ++ std::chrono::milliseconds(collectionDuration))}, ++ {"CollectionTimeScope", collectionTimeScope}}); ++ } + } +- asyncResp->res.jsonValue["Metrics"] = metrics; +- asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; +- asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; +- asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = +- time_utils::toDurationString(std::chrono::milliseconds(*interval)); + } + + struct AddReportArgs + { +- std::string name; ++ struct MetricArgs ++ { ++ std::string id; ++ std::vector uris; ++ std::optional collectionFunction; ++ std::optional collectionTimeScope; ++ std::optional collectionDuration; ++ }; ++ ++ std::optional id; ++ std::optional name; + std::string reportingType; +- bool emitsReadingsUpdate = false; +- bool logToMetricReportsCollection = false; ++ std::optional reportUpdates; ++ std::optional appendLimit; ++ std::vector reportActions; + uint64_t interval = 0; +- std::vector>> metrics; ++ std::vector metrics; + }; + + inline bool toDbusReportActions(crow::Response& res, +- std::vector& actions, ++ const std::vector& actions, + AddReportArgs& args) + { + size_t index = 0; +- for (auto& action : actions) ++ for (const auto& action : actions) + { +- if (action == "RedfishEvent") +- { +- args.emitsReadingsUpdate = true; +- } +- else if (action == "LogToMetricReportsCollection") +- { +- args.logToMetricReportsCollection = true; +- } +- else ++ std::string dbusReportAction = toDbusReportAction(action); ++ ++ if (dbusReportAction.empty()) + { + messages::propertyValueNotInList( + res, action, "ReportActions/" + std::to_string(index)); + return false; + } ++ ++ args.reportActions.emplace_back(std::move(dbusReportAction)); + index++; + } + return true; +@@ -150,27 +244,17 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + std::vector metrics; + std::vector reportActions; + std::optional schedule; +- if (!json_util::readJsonPatch(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) ++ if (!json_util::readJsonPatch( ++ req, res, "Id", args.id, "Name", args.name, "Metrics", metrics, ++ "MetricReportDefinitionType", args.reportingType, "ReportUpdates", ++ args.reportUpdates, "AppendLimit", args.appendLimit, ++ "ReportActions", reportActions, "Schedule", schedule)) + { +- 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") ++ if (args.reportingType != "Periodic" && args.reportingType != "OnRequest" && ++ args.reportingType != "OnChange") + { + messages::propertyValueNotInList(res, args.reportingType, + "MetricReportDefinitionType"); +@@ -211,15 +295,35 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + args.metrics.reserve(metrics.size()); + for (auto& m : metrics) + { +- std::string id; +- std::vector uris; +- if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", +- uris)) ++ std::optional collectionDurationStr; ++ AddReportArgs::MetricArgs metricArgs; ++ if (!json_util::readJson( ++ m, res, "MetricId", metricArgs.id, "MetricProperties", ++ metricArgs.uris, "CollectionFunction", ++ metricArgs.collectionFunction, "CollectionTimeScope", ++ metricArgs.collectionTimeScope, "CollectionDuration", ++ collectionDurationStr)) + { + return false; + } + +- args.metrics.emplace_back(std::move(id), std::move(uris)); ++ if (collectionDurationStr) ++ { ++ std::optional duration = ++ time_utils::fromDurationString(*collectionDurationStr); ++ ++ if (!duration || duration->count() < 0) ++ { ++ messages::propertyValueIncorrect(res, "CollectionDuration", ++ *collectionDurationStr); ++ return false; ++ } ++ ++ metricArgs.collectionDuration = ++ static_cast(duration->count()); ++ } ++ ++ args.metrics.emplace_back(std::move(metricArgs)); + } + + return true; +@@ -227,15 +331,14 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + + inline bool getChassisSensorNode( + const std::shared_ptr& asyncResp, +- const std::vector>>& +- metrics, ++ const std::vector& metrics, + boost::container::flat_set>& matched) + { +- for (const auto& [id, uris] : metrics) ++ for (const auto& metric : metrics) + { +- for (size_t i = 0; i < uris.size(); i++) ++ for (size_t i = 0; i < metric.uris.size(); i++) + { +- const std::string& uri = uris[i]; ++ const std::string& uri = metric.uris[i]; + std::string chassis; + std::string node; + +@@ -280,11 +383,16 @@ class AddReport + telemetry::ReadingParameters readingParams; + readingParams.reserve(args.metrics.size()); + +- for (const auto& [id, uris] : args.metrics) ++ for (auto& metric : args.metrics) + { +- for (size_t i = 0; i < uris.size(); i++) ++ std::vector< ++ std::tuple> ++ sensorParams; ++ sensorParams.reserve(metric.uris.size()); ++ ++ for (size_t i = 0; i < metric.uris.size(); i++) + { +- const std::string& uri = uris[i]; ++ const std::string& uri = metric.uris[i]; + auto el = uriToDbus.find(uri); + if (el == uriToDbus.end()) + { +@@ -298,17 +406,23 @@ class AddReport + } + + const std::string& dbusPath = el->second; +- readingParams.emplace_back(dbusPath, "SINGLE", id, uri); ++ sensorParams.emplace_back(dbusPath, uri); + } ++ ++ readingParams.emplace_back( ++ std::move(sensorParams), metric.collectionFunction.value_or(""), ++ std::move(metric.id), metric.collectionTimeScope.value_or(""), ++ metric.collectionDuration.value_or(0U)); + } + const std::shared_ptr aResp = asyncResp; + crow::connections::systemBus->async_method_call( +- [aResp, name = args.name, uriToDbus = std::move(uriToDbus)]( ++ [aResp, id = args.id.value_or(""), ++ uriToDbus = std::move(uriToDbus)]( + const boost::system::error_code ec, const std::string&) { + if (ec == boost::system::errc::file_exists) + { + messages::resourceAlreadyExists( +- aResp->res, "MetricReportDefinition", "Id", name); ++ aResp->res, "MetricReportDefinition", "Id", id); + return; + } + if (ec == boost::system::errc::too_many_files_open) +@@ -338,10 +452,12 @@ class AddReport + messages::created(aResp->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); ++ "xyz.openbmc_project.Telemetry.ReportManager", ++ "AddReportFutureVersion", ++ "TelemetryService/" + args.id.value_or(""), args.name.value_or(""), ++ args.reportingType, args.reportUpdates.value_or("Overwrite"), ++ args.appendLimit.value_or(0), args.reportActions, args.interval, ++ readingParams); + } + + AddReport(const AddReport&) = delete; +@@ -436,10 +552,10 @@ inline void requestRoutesMetricReportDefinition(App& app) + const std::string& id) { + crow::connections::systemBus->async_method_call( + [asyncResp, +- id](const boost::system::error_code ec, ++ id](boost::system::error_code ec, + const std::vector>& +- ret) { ++ properties) { + if (ec.value() == EBADR || + ec == boost::system::errc::host_unreachable) + { +@@ -454,12 +570,14 @@ inline void requestRoutesMetricReportDefinition(App& app) + return; + } + +- telemetry::fillReportDefinition(asyncResp, id, ret); ++ telemetry::fillReportDefinition(asyncResp, id, ++ properties); + }, + telemetry::service, telemetry::getDbusReportPath(id), + "org.freedesktop.DBus.Properties", "GetAll", + telemetry::reportInterface); + }); ++ + BMCWEB_ROUTE(app, + "/redfish/v1/TelemetryService/MetricReportDefinitions//") + .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index c1fe7d0..64d712b 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -49,6 +49,8 @@ inline void handleTelemetryServiceGet( + + const size_t* maxReports = nullptr; + const uint64_t* minInterval = nullptr; ++ const std::vector* supportedCollectionFunction = ++ nullptr; + for (const auto& [key, var] : ret) + { + if (key == "MaxReports") +@@ -59,6 +61,11 @@ inline void handleTelemetryServiceGet( + { + minInterval = std::get_if(&var); + } ++ else if (key == "SupportedOperationTypes") ++ { ++ supportedCollectionFunction = ++ std::get_if>(&var); ++ } + } + if (maxReports == nullptr || minInterval == nullptr) + { +@@ -72,6 +79,12 @@ inline void handleTelemetryServiceGet( + asyncResp->res.jsonValue["MinCollectionInterval"] = + time_utils::toDurationString(std::chrono::milliseconds( + static_cast(*minInterval))); ++ ++ if (supportedCollectionFunction != nullptr) ++ { ++ asyncResp->res.jsonValue["SupportedCollectionFunction"] = ++ *supportedCollectionFunction; ++ } + }, + telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", + "org.freedesktop.DBus.Properties", "GetAll", +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch new file mode 100644 index 000000000..ea409fe09 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch @@ -0,0 +1,948 @@ +From 4c39922af8bf73b13150455166e7bd1fd8645a47 Mon Sep 17 00:00:00 2001 +From: Lukasz Kazmierczak +Date: Fri, 17 Dec 2021 13:02:23 +0100 +Subject: [PATCH] Add PUT and PATCH for MetricReportDefinition + +Support for PUT and PATCH methods is added to Metric Report Definition, +now Report can be replaced by PUT or selected read/write properties can +be modified by PATCH method + +Tested: +- Added new Report via PUT and extracted Report via GET checking if + received data is appropriate +- Added Report via POST, overwrite it via PUT and extracted Report via + GET checking if received data is appropriate +- Added Report via POST, overwrite editable properties via PATCH and + fetched Report via GET checking if received data is properly modified + +Signed-off-by: Lukasz Kazmierczak +Change-Id: If75110a92c55c9e4f2415f0ed4471baa802643ff +--- + redfish-core/lib/metric_report_definition.hpp | 774 ++++++++++++++++-- + 1 file changed, 692 insertions(+), 82 deletions(-) + +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index 30d83b0..e726c8c 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -8,9 +8,9 @@ + #include + #include + #include ++#include + + #include +-#include + + namespace redfish + { +@@ -22,6 +22,12 @@ using ReadingParameters = std::vector>, + std::string, std::string, std::string, uint64_t>>; + ++enum class addReportType ++{ ++ create, ++ replace ++}; ++ + std::string toReadfishReportAction(std::string_view action) + { + if (action == "EmitsReadingsUpdate") +@@ -48,6 +54,38 @@ std::string toDbusReportAction(std::string_view action) + return ""; + } + ++inline bool verifyCommonErrors(crow::Response& res, const std::string& id, ++ const boost::system::error_code ec) ++{ ++ if (ec.value() == EBADR || ec == boost::system::errc::host_unreachable) ++ { ++ messages::resourceNotFound(res, "MetricReportDefinition", id); ++ return false; ++ } ++ ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists(res, "MetricReportDefinition", "Id", ++ id); ++ return false; ++ } ++ ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(res); ++ return false; ++ } ++ ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "DBUS response error " << ec; ++ messages::internalError(res); ++ return false; ++ } ++ ++ return true; ++} ++ + inline void fillReportDefinition( + const std::shared_ptr& asyncResp, const std::string& id, + const std::vector>& +@@ -193,17 +231,17 @@ inline void fillReportDefinition( + } + } + +-struct AddReportArgs ++struct MetricArgs + { +- struct MetricArgs +- { +- std::string id; +- std::vector uris; +- std::optional collectionFunction; +- std::optional collectionTimeScope; +- std::optional collectionDuration; +- }; ++ std::string id; ++ std::vector uris; ++ std::optional collectionFunction; ++ std::optional collectionTimeScope; ++ std::optional collectionDuration; ++}; + ++struct AddReportArgs ++{ + std::optional id; + std::optional name; + std::string reportingType; +@@ -215,22 +253,22 @@ struct AddReportArgs + }; + + inline bool toDbusReportActions(crow::Response& res, +- const std::vector& actions, +- AddReportArgs& args) ++ const std::vector& redfishActions, ++ std::vector& dbusActions) + { + size_t index = 0; +- for (const auto& action : actions) ++ for (const auto& redfishAction : redfishActions) + { +- std::string dbusReportAction = toDbusReportAction(action); ++ std::string dbusAction = toDbusReportAction(redfishAction); + +- if (dbusReportAction.empty()) ++ if (dbusAction.empty()) + { + messages::propertyValueNotInList( +- res, action, "ReportActions/" + std::to_string(index)); ++ res, redfishAction, "ReportActions/" + std::to_string(index)); + return false; + } + +- args.reportActions.emplace_back(std::move(dbusReportAction)); ++ dbusActions.emplace_back(std::move(dbusAction)); + index++; + } + return true; +@@ -259,7 +297,7 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + return false; + } + +- if (!toDbusReportActions(res, reportActions, args)) ++ if (!toDbusReportActions(res, reportActions, args.reportActions)) + { + return false; + } +@@ -294,7 +332,7 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + for (auto& m : metrics) + { + std::optional collectionDurationStr; +- AddReportArgs::MetricArgs metricArgs; ++ MetricArgs metricArgs; + if (!json_util::readJson( + m, res, "MetricId", metricArgs.id, "MetricProperties", + metricArgs.uris, "CollectionFunction", +@@ -329,7 +367,7 @@ inline bool getUserParameters(crow::Response& res, const crow::Request& req, + + inline bool getChassisSensorNode( + const std::shared_ptr& asyncResp, +- const std::vector& metrics, ++ const std::vector& metrics, + boost::container::flat_set>& matched) + { + for (const auto& metric : metrics) +@@ -363,13 +401,122 @@ inline bool getChassisSensorNode( + return true; + } + ++inline bool getReadingParametersFromMetrics( ++ crow::Response& res, const std::vector& metrics, ++ const boost::container::flat_map& uriToDbus, ++ ReadingParameters& readingParams) ++{ ++ if (metrics.empty()) ++ { ++ return true; ++ } ++ ++ readingParams.reserve(metrics.size()); ++ for (const auto& metric : metrics) ++ { ++ std::vector> ++ sensorParams; ++ sensorParams.reserve(metric.uris.size()); ++ ++ for (size_t i = 0; i < metric.uris.size(); i++) ++ { ++ const std::string& uri = metric.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( ++ res, uri, "MetricProperties/" + std::to_string(i)); ++ return false; ++ } ++ ++ const std::string& dbusPath = el->second; ++ sensorParams.emplace_back(dbusPath, uri); ++ } ++ ++ readingParams.emplace_back( ++ std::move(sensorParams), metric.collectionFunction.value_or(""), ++ metric.id, metric.collectionTimeScope.value_or(""), ++ metric.collectionDuration.value_or(0U)); ++ } ++ ++ return true; ++} ++ ++class UpdateMetrics ++{ ++ public: ++ UpdateMetrics(const std::string& idIn, std::vector metricsIn, ++ const std::shared_ptr& asyncResp) : ++ asyncResp(asyncResp), ++ id(idIn), metrics{std::move(metricsIn)} ++ {} ++ ++ ~UpdateMetrics() ++ { ++ setReadingParams(); ++ } ++ ++ UpdateMetrics(const UpdateMetrics&) = delete; ++ UpdateMetrics(UpdateMetrics&&) = delete; ++ UpdateMetrics& operator=(const UpdateMetrics&) = delete; ++ UpdateMetrics& operator=(UpdateMetrics&&) = delete; ++ ++ void insert(const boost::container::flat_map& el) ++ { ++ uriToDbus.insert(el.begin(), el.end()); ++ } ++ ++ void setReadingParams() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ ++ if (!getReadingParametersFromMetrics(asyncResp->res, metrics, uriToDbus, ++ readingParams)) ++ { ++ return; ++ } ++ ++ const std::shared_ptr aResp = asyncResp; ++ crow::connections::systemBus->async_method_call( ++ [aResp, id = id](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified(aResp->res, "Metrics", ++ "Updated"); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", ++ "ReadingParametersFutureVersion", ++ dbus::utility::DbusVariantType{readingParams}); ++ } ++ ++ private: ++ const std::shared_ptr asyncResp; ++ std::string id; ++ std::vector metrics; ++ boost::container::flat_map uriToDbus{}; ++ ReadingParameters readingParams{}; ++}; ++ + class AddReport + { + public: + AddReport(AddReportArgs argsIn, +- const std::shared_ptr& asyncResp) : ++ const std::shared_ptr& asyncResp, ++ addReportType type) : + asyncResp(asyncResp), +- args{std::move(argsIn)} ++ args{std::move(argsIn)}, type(type) + {} + ~AddReport() + { +@@ -378,7 +525,7 @@ class AddReport + return; + } + +- telemetry::ReadingParameters readingParams; ++ ReadingParameters readingParams; + readingParams.reserve(args.metrics.size()); + + for (auto& metric : args.metrics) +@@ -412,22 +559,12 @@ class AddReport + std::move(metric.id), metric.collectionTimeScope.value_or(""), + metric.collectionDuration.value_or(0U)); + } ++ + const std::shared_ptr aResp = asyncResp; + crow::connections::systemBus->async_method_call( +- [aResp, id = args.id.value_or(""), +- uriToDbus = std::move(uriToDbus)]( +- const boost::system::error_code ec, const std::string&) { +- if (ec == boost::system::errc::file_exists) +- { +- messages::resourceAlreadyExists( +- aResp->res, "MetricReportDefinition", "Id", id); +- return; +- } +- if (ec == boost::system::errc::too_many_files_open) +- { +- messages::createLimitReachedForResource(aResp->res); +- return; +- } ++ [aResp, id = args.id.value_or(""), uriToDbus = std::move(uriToDbus), ++ type = type](const boost::system::error_code ec, ++ const std::string&) { + if (ec == boost::system::errc::argument_list_too_long) + { + nlohmann::json metricProperties = nlohmann::json::array(); +@@ -440,16 +577,22 @@ class AddReport + "MetricProperties"); + return; + } +- if (ec) ++ ++ if (!verifyCommonErrors(aResp->res, id, ec)) + { +- messages::internalError(aResp->res); +- BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; + return; + } + +- messages::created(aResp->res); ++ if (type == addReportType::create) ++ { ++ messages::created(aResp->res); ++ } ++ else ++ { ++ messages::success(aResp->res); ++ } + }, +- telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", ++ service, "/xyz/openbmc_project/Telemetry/Reports", + "xyz.openbmc_project.Telemetry.ReportManager", + "AddReportFutureVersion", + "TelemetryService/" + args.id.value_or(""), args.name.value_or(""), +@@ -471,8 +614,491 @@ class AddReport + private: + const std::shared_ptr asyncResp; + AddReportArgs args; ++ const addReportType type; + boost::container::flat_map uriToDbus{}; + }; ++ ++inline void setReportEnabled(const std::shared_ptr& aResp, ++ const std::string& id, bool enabled) ++{ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ enabled](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currEnabledVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const bool* currEnabled = std::get_if(&currEnabledVar); ++ if (currEnabled == nullptr || *currEnabled == enabled) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, enabled](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified( ++ aResp->res, "MetricReportDefinitionEnabled", ++ enabled ? "True" : "False"); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "Enabled", ++ dbus::utility::DbusVariantType{enabled}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "Enabled"); ++} ++ ++inline void setReportType(const std::shared_ptr& aResp, ++ const std::string& id, const std::string& type) ++{ ++ if (type != "Periodic" && type != "OnChange" && type != "OnRequest") ++ { ++ messages::propertyValueNotInList(aResp->res, type, ++ "MetricReportDefinitionType"); ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, type](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currTypeVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const std::string* currType = ++ std::get_if(&currTypeVar); ++ if (currType == nullptr || *currType == type) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, type](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified( ++ aResp->res, "MetricReportDefinitionType", type); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "ReportingType", ++ dbus::utility::DbusVariantType{type}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportingType"); ++} ++ ++inline void setReportUpdates(const std::shared_ptr& aResp, ++ const std::string& id, const std::string& updates) ++{ ++ if (updates != "Overwrite" && updates != "AppendWrapsWhenFull" && ++ updates != "AppendStopsWhenFull") ++ { ++ messages::propertyValueNotInList(aResp->res, updates, "ReportUpdates"); ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ updates](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currUpdatesVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const std::string* currUpdates = ++ std::get_if(&currUpdatesVar); ++ if (currUpdates == nullptr || *currUpdates == updates) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, updates](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified(aResp->res, "ReportUpdates", ++ updates); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "ReportUpdates", ++ dbus::utility::DbusVariantType{updates}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportUpdates"); ++} ++ ++inline void setReportActions(const std::shared_ptr& aResp, ++ const std::string& id, ++ std::vector& redfishActions) ++{ ++ if (redfishActions.size() > 1) ++ { ++ stl_utils::removeDuplicate(redfishActions); ++ } ++ ++ std::vector newDbusActions; ++ if (!toDbusReportActions(aResp->res, redfishActions, newDbusActions)) ++ { ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, redfishActions = std::move(redfishActions), ++ newDbusActions = std::move(newDbusActions)]( ++ const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currDbusActionsVar) mutable { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ std::vector currDbusActions; ++ if (const std::vector* tmp = ++ std::get_if>(&currDbusActionsVar)) ++ { ++ currDbusActions = *tmp; ++ } ++ else ++ { ++ messages::internalError(aResp->res); ++ return; ++ } ++ ++ if (newDbusActions.size() == currDbusActions.size()) ++ { ++ std::sort(newDbusActions.begin(), newDbusActions.end()); ++ if (newDbusActions == currDbusActions) ++ { ++ return; ++ } ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ redfishActions](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ std::string redfishActionsStr; ++ for (const auto& redfishAction : redfishActions) ++ { ++ redfishActionsStr += redfishAction + std::string(" "); ++ } ++ if (!redfishActionsStr.empty()) ++ { ++ redfishActionsStr.pop_back(); ++ } ++ messages::propertyValueModified(aResp->res, "ReportActions", ++ redfishActionsStr); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "ReportActions", ++ dbus::utility::DbusVariantType{newDbusActions}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportActions"); ++} ++ ++inline void setReportInterval(const std::shared_ptr& aResp, ++ const std::string& id, nlohmann::json& schedule) ++{ ++ crow::connections::systemBus->async_method_call( ++ [aResp, schedule = std::move(schedule), ++ id](const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& typeVar) mutable { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const std::string* reportingType = ++ std::get_if(&typeVar); ++ if (reportingType == nullptr || *reportingType != "Periodic") ++ { ++ return; ++ } ++ ++ std::string durationStr; ++ if (!json_util::readJson(schedule, aResp->res, "RecurrenceInterval", ++ durationStr)) ++ { ++ return; ++ } ++ ++ std::optional durationNum = ++ time_utils::fromDurationString(durationStr); ++ uint64_t interval = static_cast(durationNum->count()); ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, interval, durationStr]( ++ const boost::system::error_code ec, ++ const dbus::utility::DbusVariantType& currIntervalVar) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ const uint64_t* currInterval = ++ std::get_if(&currIntervalVar); ++ if (currInterval == nullptr || *currInterval == interval) ++ { ++ return; ++ } ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, ++ durationStr](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ ++ messages::propertyValueModified( ++ aResp->res, "RecurrenceInterval", durationStr); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/" ++ "TelemetryService/" + ++ id, ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.Telemetry.Report", "Interval", ++ dbus::utility::DbusVariantType{interval}); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "Interval"); ++ }, ++ "xyz.openbmc_project.Telemetry", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id, ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.Telemetry.Report", "ReportingType"); ++} ++ ++inline void setReportMetrics(const std::shared_ptr& aResp, ++ const std::string& id, ++ std::vector& metricJsons) ++{ ++ std::vector metrics; ++ metrics.reserve(metricJsons.size()); ++ ++ for (auto& m : metricJsons) ++ { ++ MetricArgs metricArgs; ++ std::optional collectionDurationStr; ++ if (!json_util::readJson( ++ m, aResp->res, "MetricId", metricArgs.id, "MetricProperties", ++ metricArgs.uris, "CollectionFunction", ++ metricArgs.collectionFunction, "CollectionTimeScope", ++ metricArgs.collectionTimeScope, "CollectionDuration", ++ collectionDurationStr)) ++ { ++ return; ++ } ++ ++ if (collectionDurationStr) ++ { ++ std::optional duration = ++ time_utils::fromDurationString(*collectionDurationStr); ++ ++ if (!duration || duration->count() < 0) ++ { ++ messages::propertyValueIncorrect( ++ aResp->res, "CollectionDuration", *collectionDurationStr); ++ return; ++ } ++ ++ metricArgs.collectionDuration = ++ static_cast(duration->count()); ++ } ++ ++ metrics.emplace_back(std::move(metricArgs)); ++ } ++ ++ boost::container::flat_set> ++ chassisSensors; ++ if (!getChassisSensorNode(aResp, metrics, chassisSensors)) ++ { ++ return; ++ } ++ ++ auto updateMetricsReq = ++ std::make_shared(id, std::move(metrics), aResp); ++ ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [aResp, updateMetricsReq]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map& ++ uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR ++ << "Failed to retrieve URI to dbus sensors map with err " ++ << static_cast(status); ++ return; ++ } ++ updateMetricsReq->insert(uriToDbus); ++ }); ++ } ++} ++ ++inline void handleReportPatch(const crow::Request& req, ++ const std::shared_ptr& aResp, ++ const std::string& id) ++{ ++ std::optional enabled; ++ std::optional type; ++ std::optional updates; ++ std::optional> actions; ++ std::optional schedule; ++ std::optional> metrics; ++ ++ if (!json_util::readJsonPatch( ++ req, aResp->res, "MetricReportDefinitionEnabled", enabled, ++ "Schedule", schedule, "ReportActions", actions, "Metrics", metrics, ++ "MetricReportDefinitionType", type, "ReportUpdates", updates)) ++ { ++ return; ++ } ++ ++ if (enabled) ++ { ++ setReportEnabled(aResp, id, *enabled); ++ } ++ if (type) ++ { ++ setReportType(aResp, id, *type); ++ } ++ if (updates) ++ { ++ setReportUpdates(aResp, id, *updates); ++ } ++ if (actions) ++ { ++ setReportActions(aResp, id, *actions); ++ } ++ if (schedule) ++ { ++ setReportInterval(aResp, id, *schedule); ++ } ++ if (metrics) ++ { ++ setReportMetrics(aResp, id, *metrics); ++ } ++} ++ ++inline void handleReportPut(const crow::Request& req, ++ const std::shared_ptr& aResp, ++ const std::string& id) ++{ ++ AddReportArgs args; ++ if (!getUserParameters(aResp->res, req, args)) ++ { ++ return; ++ } ++ ++ boost::container::flat_set> ++ chassisSensors; ++ if (!getChassisSensorNode(aResp, args.metrics, chassisSensors)) ++ { ++ return; ++ } ++ ++ const std::string reportPath = getDbusReportPath(id); ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id, args = std::move(args), ++ chassisSensors = ++ std::move(chassisSensors)](const boost::system::error_code ec) { ++ addReportType addReportMode = addReportType::replace; ++ if (ec) ++ { ++ if (ec.value() != EBADR) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(aResp->res); ++ return; ++ } ++ BMCWEB_LOG_INFO << "Report not found, creating new report: " ++ << id; ++ addReportMode = addReportType::create; ++ } ++ ++ auto addReportReq = ++ std::make_shared(args, aResp, addReportMode); ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [aResp, ++ 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(status); ++ return; ++ } ++ addReportReq->insert(uriToDbus); ++ }); ++ } ++ }, ++ service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete"); ++} ++ ++inline void handleReportDelete(const std::shared_ptr& aResp, ++ const std::string& id) ++{ ++ const std::string reportPath = getDbusReportPath(id); ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, id](const boost::system::error_code ec) { ++ if (!verifyCommonErrors(aResp->res, id, ec)) ++ { ++ return; ++ } ++ aResp->res.result(boost::beast::http::status::no_content); ++ }, ++ service, reportPath, "xyz.openbmc_project.Object.Delete", "Delete"); ++} + } // namespace telemetry + + inline void requestRoutesMetricReportDefinitionCollection(App& app) +@@ -517,7 +1143,7 @@ inline void requestRoutesMetricReportDefinitionCollection(App& app) + } + + auto addReportReq = std::make_shared( +- std::move(args), asyncResp); ++ std::move(args), asyncResp, telemetry::addReportType::create); + for (const auto& [chassis, sensorType] : chassisSensors) + { + retrieveUriToDbusMap( +@@ -554,17 +1180,9 @@ inline void requestRoutesMetricReportDefinition(App& app) + const std::vector>& + properties) { +- if (ec.value() == EBADR || +- ec == boost::system::errc::host_unreachable) ++ if (!redfish::telemetry::verifyCommonErrors( ++ asyncResp->res, id, ec)) + { +- messages::resourceNotFound( +- asyncResp->res, "MetricReportDefinition", id); +- return; +- } +- if (ec) +- { +- BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; +- messages::internalError(asyncResp->res); + return; + } + +@@ -578,40 +1196,32 @@ inline void requestRoutesMetricReportDefinition(App& app) + + BMCWEB_ROUTE(app, + "/redfish/v1/TelemetryService/MetricReportDefinitions//") +- .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) ++ .privileges(redfish::privileges::deleteMetricReportDefinition) + .methods(boost::beast::http::verb::delete_)( + [](const crow::Request&, + const std::shared_ptr& asyncResp, +- const std::string& id) +- +- { +- 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; +- } ++ const std::string& id) { ++ telemetry::handleReportDelete(asyncResp, id); ++ }); + +- if (ec) +- { +- BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; +- messages::internalError(asyncResp->res); +- return; +- } ++ BMCWEB_ROUTE(app, ++ "/redfish/v1/TelemetryService/MetricReportDefinitions//") ++ .privileges(redfish::privileges::putMetricReportDefinition) ++ .methods(boost::beast::http::verb::put)( ++ [](const crow::Request& req, ++ const std::shared_ptr& asyncResp, ++ const std::string& id) { ++ telemetry::handleReportPut(req, asyncResp, id); ++ }); + +- asyncResp->res.result( +- boost::beast::http::status::no_content); +- }, +- telemetry::service, reportPath, +- "xyz.openbmc_project.Object.Delete", "Delete"); ++ BMCWEB_ROUTE(app, ++ "/redfish/v1/TelemetryService/MetricReportDefinitions//") ++ .privileges(redfish::privileges::patchMetricReportDefinition) ++ .methods(boost::beast::http::verb::patch)( ++ [](const crow::Request& req, ++ const std::shared_ptr& asyncResp, ++ const std::string& id) { ++ telemetry::handleReportPatch(req, asyncResp, id); + }); + } + } // namespace redfish +-- +2.25.1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch new file mode 100644 index 000000000..e60da9421 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch @@ -0,0 +1,107 @@ +From 0343399b0a609384da302272576a9c409f3b2794 Mon Sep 17 00:00:00 2001 +From: Szymon Dompke +Date: Mon, 21 Mar 2022 17:40:36 +0100 +Subject: [PATCH] Add Links/Triggers to MetricReportDefinition + +This change is adding Triggers property to Links when GET is called on +MetricReportDefinition. It contains array of @odata.id pointing to +Trigger resource if it is also linking to given MRD. + +Testing done: +- Links/Trigger property is returned by GET request on + /redfish/v1/TelemetryService/MetricReportDefinitions// + +Signed-off-by: Szymon Dompke +Change-Id: I5accf4b50324437b0b185003200078ad2c7020b0 +--- + redfish-core/lib/metric_report_definition.hpp | 46 +++++++++++++++++++ + 1 file changed, 46 insertions(+) + +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index e6b08a1..9ee6013 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -88,6 +88,31 @@ inline bool verifyCommonErrors(crow::Response& res, const std::string& id, + return true; + } + ++inline std::optional ++ getLinkedTriggers(const std::vector& triggerIds) ++{ ++ nlohmann::json triggers = nlohmann::json::array(); ++ ++ for (const std::string& id : triggerIds) ++ { ++ sdbusplus::message::object_path path(id); ++ if (path.parent_path() != "TelemetryService") ++ { ++ BMCWEB_LOG_ERROR << "Property TriggerIds contains invalid value: " ++ << id; ++ return std::nullopt; ++ } ++ triggers.push_back({ ++ {"@odata.id", ++ crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", ++ "Triggers", path.filename()) ++ .string()}, ++ }); ++ } ++ ++ return std::make_optional(triggers); ++} ++ + inline void fillReportDefinition( + const std::shared_ptr& asyncResp, const std::string& id, + const std::vector>& +@@ -101,6 +126,7 @@ inline void fillReportDefinition( + const uint64_t* appendLimit = nullptr; + const uint64_t* interval = nullptr; + const bool* enabled = nullptr; ++ const std::vector* triggerIds = nullptr; + + for (const auto& [key, var] : properties) + { +@@ -136,6 +162,10 @@ inline void fillReportDefinition( + { + enabled = std::get_if(&var); + } ++ else if (key == "TriggerIds") ++ { ++ triggerIds = std::get_if>(&var); ++ } + } + + std::vector redfishReportActions; +@@ -156,6 +186,17 @@ inline void fillReportDefinition( + } + } + ++ std::optional linkedTriggers; ++ if (triggerIds != nullptr) ++ { ++ linkedTriggers = getLinkedTriggers(*triggerIds); ++ if (!linkedTriggers) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ } ++ + asyncResp->res.jsonValue["@odata.type"] = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; + asyncResp->res.jsonValue["@odata.id"] = +@@ -231,6 +272,11 @@ inline void fillReportDefinition( + {"CollectionTimeScope", collectionTimeScope}}); + } + } ++ ++ if (triggerIds != nullptr) ++ { ++ asyncResp->res.jsonValue["Links"]["Triggers"] = *linkedTriggers; ++ } + } + + struct MetricArgs +-- +2.25.1 + 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..224790df8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README @@ -0,0 +1,24 @@ +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: +- LogService field, actual implementation will be upstreamed with triggers feature + file://telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch + +- ref: use url_view for telemetry uris + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/51650/7 + +- Fix Trigger GET functionality + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/51521/9 + +- Add support for POST on TriggersCollection + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44935/27 + +- Switched bmcweb to use new telemetry service API + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/44270/39 + +- Add PUT and PATCH for MetricReportDefinition + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/49796/13 + +- Add Links/Triggers to MetricReportDefinition + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/51723/1 diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0001-Revert-Disable-nbd-proxy-from-the-build.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0001-Revert-Disable-nbd-proxy-from-the-build.patch new file mode 100644 index 000000000..9225d20f1 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0001-Revert-Disable-nbd-proxy-from-the-build.patch @@ -0,0 +1,61 @@ +From e614dec3e007d3ceaa697fd7bb264dbc1ef496e5 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Wed, 1 Dec 2021 12:25:07 +0100 +Subject: [PATCH] Revert "Disable nbd proxy from the build" + +NBD Proxy has been disabled upstream. Reenable as we use it for Virtual +Media + +This reverts commit efb8062c306474942bc94f15d748b2eb0b58fbb6. + +Change-Id: I19a88b30c1074dd376f2df8f5668245b638b881f +--- + meson.build | 3 ++- + meson_options.txt | 10 ++-------- + 2 files changed, 4 insertions(+), 9 deletions(-) + +diff --git a/meson.build b/meson.build +index c9066d4..51c7f9d 100644 +--- a/meson.build ++++ b/meson.build +@@ -86,7 +86,8 @@ feature_map = { + 'static-hosting' : '-DBMCWEB_ENABLE_STATIC_HOSTING', + 'vm-websocket' : '-DBMCWEB_ENABLE_VM_WEBSOCKET', + 'xtoken-auth' : '-DBMCWEB_ENABLE_XTOKEN_AUTHENTICATION', +- #'vm-nbdproxy' : '-DBMCWEB_ENABLE_VM_NBDPROXY', ++ 'vm-nbdproxy' : '-DBMCWEB_ENABLE_VM_NBDPROXY', ++ 'validate-unsecure-feature' : '-DBMCWEB_ENABLE_VALIDATION_UNSECURE_FEATURE', + } + + # Get the options status and build a project summary to show which flags are +diff --git a/meson_options.txt b/meson_options.txt +index 4661658..435f382 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -2,14 +2,7 @@ option('yocto-deps', type: 'feature', value: 'disabled', description : 'Use YOCT + option('kvm', type : 'feature',value : 'enabled', description : 'Enable the KVM host video WebSocket. Path is \'/kvm/0\'. Video is from the BMC\'s \'/dev/video\' device.') + option ('tests', type : 'feature', value : 'enabled', description : 'Enable Unit tests for bmcweb') + option('vm-websocket', type : 'feature', value : 'enabled', description : '''Enable the Virtual Media WebSocket. Path is \'/vm/0/0\'to open the websocket. See https://github.com/openbmc/jsnbd/blob/master/README.''') +- +-# if you use this option and are seeing this comment, please comment here: +-# https://github.com/openbmc/bmcweb/issues/188 and put forward your intentions +-# for this code. At this point, no daemon has been upstreamed that implements +-# this interface, so for the moment this appears to be dead code; In leiu of +-# removing it, it has been disabled to try to give those that use it the +-# opportunity to upstream their backend implementation +-#option('vm-nbdproxy', type: 'feature', value : 'disabled', description : 'Enable the Virtual Media WebSocket.') ++option('vm-nbdproxy', type: 'feature', value : 'disabled', description : 'Enable the Virtual Media WebSocket.') + option('rest', type : 'feature', value : 'disabled', description : '''Enable Phosphor REST (D-Bus) APIs. Paths directly map Phosphor D-Bus object paths, for example, \'/xyz/openbmc_project/logging/entry/enumerate\'. See https://github.com/openbmc/docs/blob/master/rest-api.md.''') + option('redfish', type : 'feature',value : 'enabled', description: 'Enable Redfish APIs. Paths are under \'/redfish/v1/\'. See https://github.com/openbmc/bmcweb/blob/master/DEVELOPING.md#redfish.') + option('host-serial-socket', type : 'feature', value : 'enabled', description : 'Enable host serial console WebSocket. Path is \'/console0\'. See https://github.com/openbmc/docs/blob/master/console.md.') +@@ -38,6 +31,7 @@ option ('https_port', type : 'integer', min : 1, max : 65535, value : 443, descr + # the implications of doing so.In general, enabling these options will cause security + # problems of varying degrees + ++option ('validate-unsecure-feature', type : 'feature', value : 'disabled', description : '''Enables unsecure features required by validation. Note: mustbe turned off for production images.''') + option ('insecure-disable-csrf', type : 'feature', value : 'disabled', description : 'Disable CSRF prevention checks.Should be set to false for production systems.') + option ('insecure-disable-ssl', type : 'feature', value : 'disabled', description : 'Disable SSL ports. Should be set to false for production systems.') + option ('insecure-disable-auth', type : 'feature', value : 'disabled', description : 'Disable authentication on all ports. Should be set to false for production systems') +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0002-bmcweb-handle-device-or-resource-busy-exception.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0002-bmcweb-handle-device-or-resource-busy-exception.patch new file mode 100644 index 000000000..e364fd887 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0002-bmcweb-handle-device-or-resource-busy-exception.patch @@ -0,0 +1,215 @@ +From d951041df370dd4c7068b3883d5cc8ef39d044da Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Fri, 23 Jul 2021 12:07:02 +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 +Change-Id: Ica62c34db0cce24c4c6169fc661edfde49e948d0 +--- + redfish-core/lib/virtual_media.hpp | 143 +++++++++++++++++++++-------- + 1 file changed, 106 insertions(+), 37 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index b0e3b38..779e948 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -26,6 +26,8 @@ + #include + #include + ++#include ++ + namespace redfish + { + /** +@@ -143,6 +145,26 @@ inline void + } + } + ++/** ++ * @brief parses Timeout property and converts to microseconds ++ */ ++static std::optional ++ vmParseTimeoutProperty(const std::variant& timeoutProperty) ++{ ++ const int* timeoutValue = std::get_if(&timeoutProperty); ++ if (timeoutValue) ++ { ++ constexpr int timeoutMarginSeconds = 10; ++ return std::chrono::duration_cast( ++ std::chrono::seconds(*timeoutValue + timeoutMarginSeconds)) ++ .count(); ++ } ++ else ++ { ++ return std::nullopt; ++ } ++} ++ + /** + * @brief Fill template for Virtual Media Item. + */ +@@ -702,22 +724,58 @@ inline void doMountVmLegacy(const std::shared_ptr& asyncResp, + } + + 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 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 if (ec == boost::system::errc::permission_denied) ++ { ++ messages::accessDenied(asyncResp->res, ++ crow::utility::urlFromPieces( ++ "VirtualMedia.Insert")); ++ } ++ 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"); + } + + /** +@@ -729,38 +787,49 @@ inline void doVmAction(const std::shared_ptr& asyncResp, + const std::string& service, const std::string& name, + bool legacy) + { ++ const std::string vmMode = legacy ? "Legacy" : "Proxy"; ++ const std::string objectPath = ++ "/xyz/openbmc_project/VirtualMedia/" + vmMode + "/" + name; ++ const std::string ifaceName = "xyz.openbmc_project.VirtualMedia." + vmMode; + +- // Legacy mount requires parameter with image +- 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"); +- } +- else // proxy +- { +- crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, service, name, objectPath, ++ ifaceName](const boost::system::error_code ec, ++ const std::variant timeoutProperty) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } + +- messages::internalError(asyncResp->res); +- return; +- } +- }, +- service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, +- "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); +- } ++ 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"); + } + + struct InsertMediaActionParams +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0003-Add-ConnectedVia-property-to-virtual-media-item-temp.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0003-Add-ConnectedVia-property-to-virtual-media-item-temp.patch new file mode 100644 index 000000000..c8af3a659 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0003-Add-ConnectedVia-property-to-virtual-media-item-temp.patch @@ -0,0 +1,28 @@ +From 1abf9a1d336eed835472fe933210d3be7ad5ba7a Mon Sep 17 00:00:00 2001 +From: Karol Wachowski +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 +--- + 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 57c2bd2..de1cc94 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -200,6 +200,7 @@ inline nlohmann::json vmItemTemplate(const std::string& name, + item["@odata.id"] = std::move(id); + + item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia"; ++ item["ConnectedVia"] = "NotConnected"; + item["Name"] = "Virtual Removable Media"; + item["Id"] = resName; + item["WriteProtected"] = true; +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0004-Invalid-status-code-from-InsertMedia-REST-methods.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0004-Invalid-status-code-from-InsertMedia-REST-methods.patch new file mode 100644 index 000000000..e3684f406 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0004-Invalid-status-code-from-InsertMedia-REST-methods.patch @@ -0,0 +1,175 @@ +From b75728a55016289d7a0eabaaee453051e4d29742 Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Wed, 1 Dec 2021 12:27:22 +0100 +Subject: [PATCH] Invalid status code from InsertMedia REST methods GET, PUT, + DELETE, PATCH in proxy mode + +Add handlers for GET, PUT, DELETE, PATCH method and function that +checks which mode is used and set suitable status code: +Not allowed for Legacy and Not found for Proxy. + +Change-Id: Ib4c0a3e9a2a8853caa74c59239d9fcfed99c5e8b +Signed-off-by: Alicja Rybak +Signed-off-by: P Dheeraj Srujan Kumar +--- + redfish-core/lib/virtual_media.hpp | 137 +++++++++++++++++++++++++++++ + 1 file changed, 137 insertions(+) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index dc829b3..5df7b5e 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -30,6 +30,117 @@ + + namespace redfish + { ++ ++/** ++ * @brief Function checks if insert media request is Legacy or Proxy type ++ * and sets suitable response code for unsupported REST method. ++ * ++ */ ++void checkProxyMode(const std::shared_ptr& aResp, ++ const crow::Request& req, const std::string& name, ++ const std::string& resName) ++{ ++ if (name != "bmc") ++ { ++ messages::resourceNotFound(aResp->res, "VirtualMedia.Insert", resName); ++ ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [aResp, req, resName](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " ++ << ec; ++ aResp->res.result(boost::beast::http::status::not_found); ++ ++ return; ++ } ++ ++ if (getObjectType.size() == 0) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper : No Service found"; ++ aResp->res.result(boost::beast::http::status::not_found); ++ return; ++ } ++ ++ std::string service = getObjectType.begin()->first; ++ BMCWEB_LOG_DEBUG << "GetObjectType: " << service; ++ ++ crow::connections::systemBus->async_method_call( ++ [service, resName, req, ++ aResp](const boost::system::error_code ec, ++ dbus::utility::ManagedObjectType& subtree) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG << "DBUS response error"; ++ ++ return; ++ } ++ ++ for (auto& item : subtree) ++ { ++ std::string thispath = item.first.filename(); ++ if (thispath.empty()) ++ { ++ continue; ++ } ++ ++ if (thispath != resName) ++ { ++ continue; ++ } ++ ++ auto mode = item.first.parent_path(); ++ auto type = mode.parent_path(); ++ if (mode.filename().empty() || type.filename().empty()) ++ { ++ continue; ++ } ++ ++ if (type.filename() != "VirtualMedia") ++ { ++ continue; ++ } ++ ++ // Check if dbus path is Legacy type ++ if (mode.filename() == "Legacy") ++ { ++ BMCWEB_LOG_DEBUG << "InsertMedia only allowed " ++ "with POST method " ++ "in legacy mode"; ++ aResp->res.result( ++ boost::beast::http::status::method_not_allowed); ++ ++ return; ++ } ++ // Check if dbus path is Proxy type ++ if (mode.filename() == "Proxy") ++ { ++ // Not possible in proxy mode ++ BMCWEB_LOG_DEBUG << "InsertMedia not " ++ "allowed in proxy mode"; ++ aResp->res.result( ++ boost::beast::http::status::not_found); ++ ++ return; ++ } ++ } ++ ++ BMCWEB_LOG_DEBUG << "Parent item not found"; ++ aResp->res.result(boost::beast::http::status::not_found); ++ }, ++ service, "/xyz/openbmc_project/VirtualMedia", ++ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/VirtualMedia", std::array()); ++} ++ + /** + * @brief Function extracts transfer protocol name from URI. + */ +@@ -840,6 +951,32 @@ struct InsertMediaActionParams + + inline void requestNBDVirtualMediaRoutes(App& app) + { ++ BMCWEB_ROUTE(app, "/redfish/v1/Managers//VirtualMedia//Actions/" ++ "VirtualMedia.InsertMedia") ++ .privileges({{"Login"}}) ++ .methods(boost::beast::http::verb::get)( ++ [](const crow::Request& req, ++ const std::shared_ptr& asyncResp, ++ const std::string& name, const std::string& resName) { ++ checkProxyMode(asyncResp, req, name, resName); ++ }); ++ ++ for (auto method : ++ {boost::beast::http::verb::patch, boost::beast::http::verb::put, ++ boost::beast::http::verb::delete_}) ++ { ++ BMCWEB_ROUTE(app, ++ "/redfish/v1/Managers//VirtualMedia//Actions/" ++ "VirtualMedia.InsertMedia") ++ .privileges({{"ConfigureManager"}}) ++ .methods(method)( ++ [](const crow::Request& req, ++ const std::shared_ptr& asyncResp, ++ const std::string& name, const std::string& resName) { ++ checkProxyMode(asyncResp, req, name, resName); ++ }); ++ } ++ + BMCWEB_ROUTE( + app, + "/redfish/v1/Managers//VirtualMedia//Actions/VirtualMedia.InsertMedia") +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0006-Bmcweb-handle-permission-denied-exception.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0006-Bmcweb-handle-permission-denied-exception.patch new file mode 100644 index 000000000..a74b6e9fb --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0006-Bmcweb-handle-permission-denied-exception.patch @@ -0,0 +1,38 @@ +From 232c7dbf21570aa0581d7c8cff71b793555f10cf Mon Sep 17 00:00:00 2001 +From: Krzysztof Grobelny +Date: Thu, 1 Jul 2021 10:41:47 +0000 +Subject: [PATCH] Bmcweb handle permission denied exception + +Add handling of permission denied exception (EPERM) that +can be thrown by VirtualMedia service during Mount/Unmount dbus operations. + +Tested: +Verified that after mounting/unmounting HTTPS resource twice in a row in legacy mode, +VirtualMedia returns EPERM, which bmcweb handles as 403 status code. + +Change-Id: Ibc18d5ec822c5072605b1fc4651389982002798b +Signed-off-by: Alicja Rybak +--- + redfish-core/lib/virtual_media.hpp | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 3eb585f..1168939 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -931,6 +931,12 @@ inline void doVmAction(const std::shared_ptr& asyncResp, + { + messages::resourceInUse(asyncResp->res); + } ++ else if (ec == boost::system::errc::permission_denied) ++ { ++ messages::accessDenied(asyncResp->res, ++ crow::utility::urlFromPieces( ++ "VirtualMedia.Insert")); ++ } + else + { + messages::internalError(asyncResp->res); +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0007-Fix-unmounting-image-in-proxy-mode.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0007-Fix-unmounting-image-in-proxy-mode.patch new file mode 100644 index 000000000..88fa89465 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0007-Fix-unmounting-image-in-proxy-mode.patch @@ -0,0 +1,35 @@ +From 6f4b5fc1879f39b0f5fee0838f0ecbc481275d5e Mon Sep 17 00:00:00 2001 +From: Alicja Rybak +Date: Fri, 23 Apr 2021 17:35:52 +0200 +Subject: [PATCH] Fix unmounting image in proxy mode. + +Sometimes Slot0 got higher key than Slot1 and erase function for Slot1 +invalidates elements with keys not less than the erased element. +In that case invalid slot0 will be unmounted. +Change order of calling close() and erase() functions to +unmount correct device. + +Change-Id: I7a40a4518982f697d3eed635cde6d06978149cf0 +Signed-off-by: Alicja Rybak +--- + include/nbd_proxy.hpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/include/nbd_proxy.hpp b/include/nbd_proxy.hpp +index 3b28823..897bcf2 100644 +--- a/include/nbd_proxy.hpp ++++ b/include/nbd_proxy.hpp +@@ -439,9 +439,9 @@ inline void requestRoutes(App& app) + BMCWEB_LOG_DEBUG << "No session to close"; + return; + } ++ session->second->close(); + // Remove reference to session in global map + sessions.erase(session); +- session->second->close(); + }) + .onmessage([](crow::websocket::Connection& conn, + const std::string& data, bool) { +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Apply-async-dbus-API.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Apply-async-dbus-API.patch new file mode 100644 index 000000000..f05629776 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Apply-async-dbus-API.patch @@ -0,0 +1,369 @@ +From 5512b7bcdb691133d0321779f7d20f5cc1eb3188 Mon Sep 17 00:00:00 2001 +From: Przemyslaw Czarnowski +Date: Mon, 7 Mar 2022 19:22:18 +0100 +Subject: [PATCH] Apply async dbus API + +In order to support new asynchronous dbus API for Virtual Media generic +listener for signals has been introduced. +Using AsynsResp object lifetime expanded by listener waiting for signal +response is passed to user once signal arrives. +This patch covers mounting legacy mode and unmounting both legacy and +proxy mode. + +Tested: +Manually in companion with updated service signal is catched and redfish +releases connection with appropriate reply + +Signed-off-by: Przemyslaw Czarnowski +--- + redfish-core/lib/virtual_media.hpp | 203 ++++++++++++++++++++++++----- + 1 file changed, 174 insertions(+), 29 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 8da40eea..e6ee2b6a 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -15,10 +15,18 @@ + */ + #pragma once + ++#include "logging.hpp" ++ + #include ++#include ++#include ++#include ++#include + #include + #include + #include ++#include ++#include + #include + #include + // for GetObjectType and ManagedObjectType +@@ -28,10 +36,24 @@ + #include + + #include ++#include ++#include + + namespace redfish + { + ++const char* legacyMode = "Legacy"; ++const char* proxyMode = "Proxy"; ++ ++std::string getModeName(bool isLegacy) ++{ ++ if (isLegacy) ++ { ++ return legacyMode; ++ } ++ return proxyMode; ++} ++ + /** + * @brief Function parses getManagedObject response, finds item, makes generic + * validation and invokes callback handler on this item. +@@ -109,6 +131,7 @@ void findItemAndRunHandler(const std::shared_ptr& aResp, + + if (handler(service, aResp, item) == true) + { ++ + return; + } + } +@@ -382,7 +405,7 @@ inline void getVmData(const std::shared_ptr& aResp, + actionsId += "/Actions"; + + // Check if dbus path is Legacy type +- if (mode.filename() == "Legacy") ++ if (mode.filename() == legacyMode) + { + aResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"] + ["target"] = +@@ -760,6 +783,111 @@ class Pipe + Buffer buffer; + }; + ++/** ++ * @brief holder for dbus signal matchers ++ */ ++struct MatchWrapper ++{ ++ void stop() ++ { ++ timer->cancel(); ++ matcher = std::nullopt; ++ } ++ ++ ~MatchWrapper() ++ { ++ BMCWEB_LOG_DEBUG << "~MatchWrapper()"; ++ }; ++ std::optional matcher{}; ++ std::optional timer; ++}; ++ ++/** ++ * @brief Function starts waiting for signal completion ++ */ ++static inline std::shared_ptr ++ doListenForCompletion(const std::string& name, ++ const std::string& objectPath, ++ const std::string& action, bool legacy, ++ std::shared_ptr asyncResp) ++{ ++ BMCWEB_LOG_DEBUG << "Start Listening for completion : " << action; ++ std::string matcherString = sdbusplus::bus::match::rules::type::signal(); ++ ++ std::string interface = ++ std::string("xyz.openbmc_project.VirtualMedia.") + getModeName(legacy); ++ ++ matcherString += sdbusplus::bus::match::rules::interface(interface); ++ matcherString += sdbusplus::bus::match::rules::member("Completion"); ++ matcherString += sdbusplus::bus::match::rules::sender( ++ "xyz.openbmc_project.VirtualMedia"); ++ matcherString += sdbusplus::bus::match::rules::path(objectPath); ++ ++ auto matchWrapper = std::make_shared(); ++ auto matchHandler = [asyncResp = std::move(asyncResp), name, action, ++ objectPath, ++ matchWrapper](sdbusplus::message::message& m) { ++ int errorCode; ++ try ++ { ++ BMCWEB_LOG_INFO << "Completion signal from " << m.get_path() ++ << " has been received"; ++ ++ m.read(errorCode); ++ switch (errorCode) ++ { ++ case 0: // success ++ BMCWEB_LOG_INFO << "Signal received: Success"; ++ messages::success(asyncResp->res); ++ break; ++ case EPERM: ++ BMCWEB_LOG_ERROR << "Signal received: EPERM"; ++ messages::accessDenied( ++ asyncResp->res, crow::utility::urlFromPieces(action)); ++ break; ++ case EBUSY: ++ BMCWEB_LOG_ERROR << "Signal received: EAGAIN"; ++ messages::resourceInUse(asyncResp->res); ++ break; ++ default: ++ BMCWEB_LOG_ERROR << "Signal received: Other: " << errorCode; ++ messages::operationFailed(asyncResp->res); ++ break; ++ }; ++ } ++ catch (sdbusplus::exception::SdBusError& e) ++ { ++ BMCWEB_LOG_ERROR << e.what(); ++ }; ++ // postpone matcher deletion after callback finishes ++ boost::asio::post(crow::connections::systemBus->get_io_context(), ++ [name, matchWrapper = matchWrapper]() ++ ++ { ++ BMCWEB_LOG_DEBUG << "Removing matcher for " ++ << name << " node."; ++ matchWrapper->stop(); ++ }); ++ }; ++ matchWrapper->timer.emplace(crow::connections::systemBus->get_io_context()); ++ ++ // Safety valve. Clean itself after 3 minutes without signal ++ matchWrapper->timer->expires_after(std::chrono::minutes(3)); ++ matchWrapper->timer->async_wait( ++ [matchWrapper](const boost::system::error_code& ec) { ++ if (ec != boost::asio::error::operation_aborted) ++ { ++ BMCWEB_LOG_DEBUG << "Timer expired! Signal did not come"; ++ matchWrapper->matcher = std::nullopt; ++ return; ++ } ++ }); ++ ++ matchWrapper->matcher.emplace(*crow::connections::systemBus, matcherString, ++ matchHandler); ++ return matchWrapper; ++} ++ + /** + * @brief Function transceives data with dbus directly. + * +@@ -835,9 +963,15 @@ inline void doMountVmLegacy(const std::shared_ptr& asyncResp, + return; + } + ++ const std::string objectPath = ++ "/xyz/openbmc_project/VirtualMedia/Legacy/" + name; ++ const std::string action = "VirtualMedia.Insert"; ++ auto wrapper = doListenForCompletion(name, objectPath, action, true, ++ asyncResp); ++ + crow::connections::systemBus->async_method_call_timed( +- [asyncResp, secretPipe](const boost::system::error_code ec, +- bool success) { ++ [asyncResp, secretPipe, name, action, wrapper, ++ objectPath](const boost::system::error_code ec, bool success) { + if (ec) + { + BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; +@@ -847,24 +981,25 @@ inline void doMountVmLegacy(const std::shared_ptr& asyncResp, + } + else if (ec == boost::system::errc::permission_denied) + { +- messages::accessDenied(asyncResp->res, +- crow::utility::urlFromPieces( +- "VirtualMedia.Insert")); ++ messages::accessDenied( ++ asyncResp->res, ++ crow::utility::urlFromPieces(action)); + } + else + { + messages::internalError(asyncResp->res); + } ++ wrapper->stop(); + } + else if (!success) + { + BMCWEB_LOG_ERROR << "Service responded with error "; +- messages::generalError(asyncResp->res); ++ messages::operationFailed(asyncResp->res); ++ wrapper->stop(); + } + }, +- service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, +- "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", *timeout, +- imageUrl, rw, unixFd); ++ service, objectPath, "xyz.openbmc_project.VirtualMedia.Legacy", ++ "Mount", *timeout, imageUrl, rw, unixFd); + }, + service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, + "org.freedesktop.DBus.Properties", "Get", +@@ -876,17 +1011,17 @@ inline void doMountVmLegacy(const std::shared_ptr& asyncResp, + * + * All BMC state properties will be retrieved before sending reset request. + */ +-inline void doVmAction(const std::shared_ptr& asyncResp, +- const std::string& service, const std::string& name, +- bool legacy) ++inline void doEjectAction(const std::shared_ptr& asyncResp, ++ const std::string& service, const std::string& name, ++ bool legacy) + { +- const std::string vmMode = legacy ? "Legacy" : "Proxy"; ++ const std::string vmMode = getModeName(legacy); + const std::string objectPath = + "/xyz/openbmc_project/VirtualMedia/" + vmMode + "/" + name; + const std::string ifaceName = "xyz.openbmc_project.VirtualMedia." + vmMode; + + crow::connections::systemBus->async_method_call( +- [asyncResp, service, name, objectPath, ++ [asyncResp, service, name, objectPath, legacy, + ifaceName](const boost::system::error_code ec, + const std::variant timeoutProperty) { + if (ec) +@@ -903,8 +1038,12 @@ inline void doVmAction(const std::shared_ptr& asyncResp, + messages::internalError(asyncResp->res); + return; + } ++ std::string action = "VirtualMedia.Eject"; ++ auto wrapper = doListenForCompletion(name, objectPath, action, ++ legacy, asyncResp); + crow::connections::systemBus->async_method_call_timed( +- [asyncResp](const boost::system::error_code ec) { ++ [asyncResp, name, action, objectPath, ++ wrapper](const boost::system::error_code ec, bool success) { + if (ec) + { + BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; +@@ -914,15 +1053,20 @@ inline void doVmAction(const std::shared_ptr& asyncResp, + } + else if (ec == boost::system::errc::permission_denied) + { +- messages::accessDenied(asyncResp->res, +- crow::utility::urlFromPieces( +- "VirtualMedia.Insert")); ++ messages::accessDenied( ++ asyncResp->res, ++ crow::utility::urlFromPieces(action)); + } + else + { + messages::internalError(asyncResp->res); + } +- return; ++ wrapper->stop(); ++ } ++ else if (!success) ++ { ++ messages::operationFailed(asyncResp->res); ++ wrapper->stop(); + } + }, + service, objectPath, ifaceName, "Unmount", *timeout); +@@ -951,7 +1095,7 @@ inline void requestNBDVirtualMediaRoutes(App& app) + auto mode = item.first.parent_path(); + auto type = mode.parent_path(); + // Check if dbus path is Legacy type +- if (mode.filename() == "Legacy") ++ if (mode.filename() == legacyMode) + { + BMCWEB_LOG_DEBUG << "InsertMedia only allowed " + "with POST method " +@@ -961,7 +1105,7 @@ inline void requestNBDVirtualMediaRoutes(App& app) + return true; + } + // Check if dbus path is Proxy type +- if (mode.filename() == "Proxy") ++ if (mode.filename() == proxyMode) + { + // Not possible in proxy mode + BMCWEB_LOG_DEBUG << "InsertMedia not " +@@ -1019,7 +1163,7 @@ inline void requestNBDVirtualMediaRoutes(App& app) + dbus::utility::DBusInteracesMap>& item) { + auto mode = item.first.parent_path(); + auto type = mode.parent_path(); +- if (mode.filename() == "Proxy") ++ if (mode.filename() == proxyMode) + { + // Not possible in proxy mode + BMCWEB_LOG_DEBUG << "InsertMedia not " +@@ -1131,20 +1275,21 @@ inline void requestNBDVirtualMediaRoutes(App& app) + + if (path.substr(lastIndex) == resName) + { +- lastIndex = path.rfind("Proxy"); ++ lastIndex = path.rfind(proxyMode); + if (lastIndex != std::string::npos) + { + // Proxy mode +- doVmAction(asyncResp, service, +- resName, false); ++ doEjectAction(asyncResp, service, ++ resName, false); + } + +- lastIndex = path.rfind("Legacy"); ++ lastIndex = path.rfind(legacyMode); ++ + if (lastIndex != std::string::npos) + { + // Legacy mode +- doVmAction(asyncResp, service, +- resName, true); ++ doEjectAction(asyncResp, service, ++ resName, true); + } + + return; +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Return-404-for-POST-on-Proxy-InsertMedia.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Return-404-for-POST-on-Proxy-InsertMedia.patch new file mode 100644 index 000000000..a6fef8b3f --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/vm/0008-Return-404-for-POST-on-Proxy-InsertMedia.patch @@ -0,0 +1,380 @@ +From d80cd6b54604f688832674b4fc2f3cb715350266 Mon Sep 17 00:00:00 2001 +From: Przemyslaw Czarnowski +Date: Fri, 4 Feb 2022 15:41:54 +0100 +Subject: [PATCH] Return 404 for POST on Proxy InsertMedia + +.../Actions/VirtualMedia.InsertMedia does not exist for proxy mode. But +it does for legacy, so handler needs to return 404 for proxy and run +mounting in legacy. Similar action has to be done for get, delete and +patch. + +As the check shares the same codebase besides the action itself, this +patch creates generic function for parsing GetManagedObjects, finding +valid VirtualMedia endpoint and invoking specific action handler. + +Tested: +Manually, with the following set of commands: +POST /Proxy /InsertAction : returned 404 +POST /Legacy/InsertAction : returned 200 (mounted) +GET /Proxy /InsertAction : returned 404 +GET /Legacy/InsertAction : returned 405 +PATCH /Proxy /InsertAction : returned 404 +PATCH /Legacy/InsertAction : returned 405 +DELETE/Proxy /InsertAction : returned 404 +DELETE/Legacy/InsertAction : returned 405 + +Signed-off-by: Przemyslaw Czarnowski +--- + redfish-core/lib/virtual_media.hpp | 266 ++++++++++++----------------- + 1 file changed, 108 insertions(+), 158 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 045ca10..9b1898c 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + #include + // for GetObjectType and ManagedObjectType + +@@ -32,13 +33,14 @@ namespace redfish + { + + /** +- * @brief Function checks if insert media request is Legacy or Proxy type +- * and sets suitable response code for unsupported REST method. ++ * @brief Function parses getManagedObject response, finds item, makes generic ++ * validation and invokes callback handler on this item. + * + */ +-void checkProxyMode(const std::shared_ptr& aResp, +- const crow::Request& req, const std::string& name, +- const std::string& resName) ++template ++void findItemAndRunHandler(const std::shared_ptr& aResp, ++ const crow::Request& req, const std::string& name, ++ const std::string& resName, T&& handler) + { + if (name != "bmc") + { +@@ -48,8 +50,8 @@ void checkProxyMode(const std::shared_ptr& aResp, + } + + crow::connections::systemBus->async_method_call( +- [aResp, req, resName](const boost::system::error_code ec, +- const GetObjectType& getObjectType) { ++ [aResp, req, resName, handler](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { + if (ec) + { + BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " +@@ -70,9 +72,9 @@ void checkProxyMode(const std::shared_ptr& aResp, + BMCWEB_LOG_DEBUG << "GetObjectType: " << service; + + crow::connections::systemBus->async_method_call( +- [service, resName, req, +- aResp](const boost::system::error_code ec, +- dbus::utility::ManagedObjectType& subtree) { ++ [service, resName, req, aResp, ++ handler](const boost::system::error_code ec, ++ dbus::utility::ManagedObjectType& subtree) { + if (ec) + { + BMCWEB_LOG_DEBUG << "DBUS response error"; +@@ -105,26 +107,8 @@ void checkProxyMode(const std::shared_ptr& aResp, + continue; + } + +- // Check if dbus path is Legacy type +- if (mode.filename() == "Legacy") +- { +- BMCWEB_LOG_DEBUG << "InsertMedia only allowed " +- "with POST method " +- "in legacy mode"; +- aResp->res.result( +- boost::beast::http::status::method_not_allowed); +- +- return; +- } +- // Check if dbus path is Proxy type +- if (mode.filename() == "Proxy") ++ if (handler(service, aResp, item) == true) + { +- // Not possible in proxy mode +- BMCWEB_LOG_DEBUG << "InsertMedia not " +- "allowed in proxy mode"; +- aResp->res.result( +- boost::beast::http::status::not_found); +- + return; + } + } +@@ -270,10 +254,7 @@ static std::optional + std::chrono::seconds(*timeoutValue + timeoutMarginSeconds)) + .count(); + } +- else +- { +- return std::nullopt; +- } ++ return std::nullopt; + } + + /** +@@ -961,14 +942,43 @@ struct InsertMediaActionParams + + inline void requestNBDVirtualMediaRoutes(App& app) + { ++ auto handler = []([[maybe_unused]] const std::string& service, ++ const std::shared_ptr& aResp, ++ std::pair& item) { ++ auto mode = item.first.parent_path(); ++ auto type = mode.parent_path(); ++ // Check if dbus path is Legacy type ++ if (mode.filename() == "Legacy") ++ { ++ BMCWEB_LOG_DEBUG << "InsertMedia only allowed " ++ "with POST method " ++ "in legacy mode"; ++ aResp->res.result(boost::beast::http::status::method_not_allowed); ++ ++ return true; ++ } ++ // Check if dbus path is Proxy type ++ if (mode.filename() == "Proxy") ++ { ++ // Not possible in proxy mode ++ BMCWEB_LOG_DEBUG << "InsertMedia not " ++ "allowed in proxy mode"; ++ aResp->res.result(boost::beast::http::status::not_found); ++ ++ return true; ++ } ++ return false; ++ }; + BMCWEB_ROUTE(app, "/redfish/v1/Managers//VirtualMedia//Actions/" + "VirtualMedia.InsertMedia") + .privileges({{"Login"}}) + .methods(boost::beast::http::verb::get)( +- [](const crow::Request& req, +- const std::shared_ptr& asyncResp, +- const std::string& name, const std::string& resName) { +- checkProxyMode(asyncResp, req, name, resName); ++ [handler](const crow::Request& req, ++ const std::shared_ptr& asyncResp, ++ const std::string& name, const std::string& resName) { ++ findItemAndRunHandler(asyncResp, req, name, resName, ++ std::move(handler)); + }); + + for (auto method : +@@ -980,10 +990,12 @@ inline void requestNBDVirtualMediaRoutes(App& app) + "VirtualMedia.InsertMedia") + .privileges({{"ConfigureManager"}}) + .methods(method)( +- [](const crow::Request& req, +- const std::shared_ptr& asyncResp, +- const std::string& name, const std::string& resName) { +- checkProxyMode(asyncResp, req, name, resName); ++ [handler = std::move(handler)]( ++ const crow::Request& req, ++ const std::shared_ptr& asyncResp, ++ const std::string& name, const std::string& resName) { ++ findItemAndRunHandler(asyncResp, req, name, resName, ++ std::move(handler)); + }); + } + +@@ -995,130 +1007,68 @@ inline void requestNBDVirtualMediaRoutes(App& app) + [](const crow::Request& req, + const std::shared_ptr& asyncResp, + const std::string& name, const std::string& resName) { +- if (name != "bmc") +- { +- messages::resourceNotFound(asyncResp->res, +- "VirtualMedia.Insert", resName); +- +- return; +- } +- InsertMediaActionParams actionParams; +- +- // Read obligatory parameters (url of +- // image) +- if (!json_util::readJsonAction( +- req, asyncResp->res, "Image", actionParams.imageUrl, +- "WriteProtected", actionParams.writeProtected, +- "UserName", actionParams.userName, "Password", +- actionParams.password, "Inserted", +- actionParams.inserted, "TransferMethod", +- actionParams.transferMethod, "TransferProtocolType", +- actionParams.transferProtocolType)) +- { +- BMCWEB_LOG_DEBUG << "Image is not provided"; +- return; +- } +- +- bool paramsValid = validateParams( +- asyncResp->res, actionParams.imageUrl, +- actionParams.inserted, actionParams.transferMethod, +- actionParams.transferProtocolType); +- +- if (!paramsValid) +- { +- return; +- } +- +- crow::connections::systemBus->async_method_call( +- [asyncResp, actionParams, +- resName](const boost::system::error_code ec, +- const GetObjectType& getObjectType) mutable { +- if (ec) ++ // handle legacy mode (parse parameters and start action ++ // for proxy mode return 404. ++ auto handler = ++ [req, resName]( ++ const std::string& service, ++ const std::shared_ptr& asyncResp, ++ std::pair& item) { ++ auto mode = item.first.parent_path(); ++ auto type = mode.parent_path(); ++ if (mode.filename() == "Proxy") + { +- BMCWEB_LOG_ERROR +- << "ObjectMapper::GetObject call failed: " +- << ec; +- messages::internalError(asyncResp->res); ++ // Not possible in proxy mode ++ BMCWEB_LOG_DEBUG << "InsertMedia not " ++ "allowed in proxy mode"; ++ messages::resourceNotFound( ++ asyncResp->res, "VirtualMedia.InsertMedia", ++ resName); + +- return; ++ return true; + } +- std::string service = getObjectType.begin()->first; +- BMCWEB_LOG_DEBUG << "GetObjectType: " << service; +- +- crow::connections::systemBus->async_method_call( +- [service, resName, actionParams, +- asyncResp](const boost::system::error_code ec, +- dbus::utility::ManagedObjectType& +- subtree) mutable { +- if (ec) +- { +- BMCWEB_LOG_DEBUG << "DBUS response error"; + +- return; +- } +- +- for (const auto& object : subtree) +- { +- const std::string& path = +- static_cast( +- object.first); +- +- std::size_t lastIndex = path.rfind('/'); +- if (lastIndex == std::string::npos) +- { +- continue; +- } +- +- lastIndex += 1; +- +- if (path.substr(lastIndex) == resName) +- { +- lastIndex = path.rfind("Proxy"); +- if (lastIndex != std::string::npos) +- { +- // Not possible in proxy mode +- BMCWEB_LOG_DEBUG +- << "InsertMedia not " +- "allowed in proxy mode"; +- messages::resourceNotFound( +- asyncResp->res, +- "VirtualMedia.InsertMedia", +- resName); +- +- return; +- } ++ InsertMediaActionParams actionParams; ++ ++ // Read obligatory parameters (url of ++ // image) ++ if (!json_util::readJsonAction( ++ req, asyncResp->res, "Image", ++ actionParams.imageUrl, "WriteProtected", ++ actionParams.writeProtected, "UserName", ++ actionParams.userName, "Password", ++ actionParams.password, "Inserted", ++ actionParams.inserted, "TransferMethod", ++ actionParams.transferMethod, ++ "TransferProtocolType", ++ actionParams.transferProtocolType)) ++ { ++ BMCWEB_LOG_DEBUG << "Image is not provided"; ++ return true; ++ } + +- lastIndex = path.rfind("Legacy"); +- if (lastIndex == std::string::npos) +- { +- continue; +- } ++ bool paramsValid = validateParams( ++ asyncResp->res, actionParams.imageUrl, ++ actionParams.inserted, actionParams.transferMethod, ++ actionParams.transferProtocolType); + +- // manager is irrelevant for +- // VirtualMedia dbus calls +- doMountVmLegacy( +- asyncResp, service, resName, +- actionParams.imageUrl, +- !(*actionParams.writeProtected), +- std::move(*actionParams.userName), +- std::move(*actionParams.password)); ++ if (paramsValid == false) ++ { ++ return true; ++ } + +- return; +- } +- } +- BMCWEB_LOG_DEBUG << "Parent item not found"; +- messages::resourceNotFound( +- asyncResp->res, "VirtualMedia", resName); +- }, +- service, "/xyz/openbmc_project/VirtualMedia", +- "org.freedesktop.DBus.ObjectManager", +- "GetManagedObjects"); +- }, +- "xyz.openbmc_project.ObjectMapper", +- "/xyz/openbmc_project/object_mapper", +- "xyz.openbmc_project.ObjectMapper", "GetObject", +- "/xyz/openbmc_project/VirtualMedia", +- std::array()); ++ // manager is irrelevant for ++ // VirtualMedia dbus calls ++ doMountVmLegacy(asyncResp, service, resName, ++ actionParams.imageUrl, ++ !(*actionParams.writeProtected), ++ std::move(*actionParams.userName), ++ std::move(*actionParams.password)); ++ ++ return true; ++ }; ++ findItemAndRunHandler(asyncResp, req, name, resName, handler); + }); + + BMCWEB_ROUTE( +-- +2.25.1 + 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..ff5c514af --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend @@ -0,0 +1,96 @@ +# The URI is required for the autobump script but keep it commented +# to not override the upstream value +# SRC_URI = "git://github.com/openbmc/bmcweb.git;branch=master;protocol=https" +SRCREV = "f7725d79fa9b5fcffcc368008fa029ea1a16c4b9" + +DEPENDS += "boost-url" + +FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:" + +SRC_URI += "file://0001-Firmware-update-configuration-changes.patch \ + file://0002-Use-chip-id-based-UUID-for-Service-Root.patch \ + file://0010-managers-add-attributes-for-Manager.CommandShell.patch \ + file://0011-bmcweb-Add-PhysicalContext-to-Thermal-resources.patch \ + file://0012-Log-RedFish-event-for-Invalid-login-attempt.patch \ + file://0013-Add-UART-routing-logic-into-host-console-connection-.patch \ + file://0014-recommended-fixes-by-crypto-review-team.patch \ + file://0015-Add-state-sensor-messages-to-the-registry.patch \ + file://0016-Fix-bmcweb-crashes-if-socket-directory-not-present.patch \ + file://0017-Add-msg-registry-for-subscription-related-actions.patch \ + file://0018-bmcweb-Add-BMC-Time-update-log-to-the-registry.patch \ + file://0019-Add-generic-message-PropertySizeExceeded.patch \ + file://0020-Redfish-Deny-set-AccountLockDuration-to-zero.patch \ + file://0023-Add-get-IPMI-session-id-s-to-Redfish.patch \ + file://0024-Add-count-sensor-type.patch \ + file://0025-Add-Model-to-ProcessorSummary.patch \ + file://0026-Revert-Delete-the-copy-constructor-on-the-Request.patch \ + file://0028-Add-Redfish-schema-files-for-OEM-PECI.patch \ +" + + +# OOB Bios Config: +SRC_URI += "file://biosconfig/0001-Define-Redfish-interface-Registries-Bios.patch \ + file://biosconfig/0002-BaseBiosTable-Add-support-for-PATCH-operation.patch \ + file://biosconfig/0003-Add-support-to-ResetBios-action.patch \ + file://biosconfig/0004-Add-support-to-ChangePassword-action.patch \ + file://biosconfig/0005-Fix-remove-bios-user-pwd-change-option-via-Redfish.patch \ + file://biosconfig/0006-Add-fix-for-broken-feature-Pending-Attributes.patch \ + file://biosconfig/0007-Add-BiosAttributeRegistry-node-under-Registries.patch \ +" + +# Virtual Media: Backend code is not upstreamed so downstream only patches. +SRC_URI += " \ + file://vm/0001-Revert-Disable-nbd-proxy-from-the-build.patch \ + file://vm/0002-bmcweb-handle-device-or-resource-busy-exception.patch \ + file://vm/0003-Add-ConnectedVia-property-to-virtual-media-item-temp.patch \ + file://vm/0004-Invalid-status-code-from-InsertMedia-REST-methods.patch \ + file://vm/0006-Bmcweb-handle-permission-denied-exception.patch \ + file://vm/0007-Fix-unmounting-image-in-proxy-mode.patch \ + file://vm/0008-Return-404-for-POST-on-Proxy-InsertMedia.patch \ + file://vm/0008-Apply-async-dbus-API.patch \ +" + +# EventService: Temporary pulled to downstream. See eventservice\README for details +SRC_URI += "file://eventservice/0001-Add-unmerged-changes-for-http-retry-support.patch \ + file://eventservice/0002-EventService-https-client-support.patch \ + file://eventservice/0004-Add-Server-Sent-Events-support.patch \ + file://eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch \ + file://eventservice/0006-Add-EventService-SSE-filter-support.patch \ + file://eventservice/0007-EventService-Log-events-for-subscription-actions.patch \ + file://eventservice/0008-Add-checks-on-Event-Subscription-input-parameters.patch \ + file://eventservice/0010-Remove-Terminated-Event-Subscriptions.patch \ + file://eventservice/0011-Fix-bmcweb-crash-while-deleting-terminated-subscriptions.patch \ + file://eventservice/0012-Add-support-for-deleting-terminated-subscriptions.patch \ + file://eventservice/0013-event-service-fix-added-Context-field-to-response.patch \ +" + +# Temporary downstream mirror of upstream patches, see telemetry\README for details +SRC_URI += " file://telemetry/0001-Revert-Remove-LogService-from-TelemetryService.patch \ + file://telemetry/0002-ref-use-url_view-for-telemetry-uris.patch \ + file://telemetry/0003-Fix-Trigger-GET-functionality.patch \ + file://telemetry/0004-Add-support-for-POST-on-TriggersCollection.patch \ + file://telemetry/0005-Switched-bmcweb-to-use-new-telemetry-service-API.patch \ + file://telemetry/0006-Add-PUT-and-PATCH-for-MetricReportDefinition.patch \ + file://telemetry/0007-Add-Links-Triggers-to-MetricReportDefinition.patch \ +" + +# Temporary downstream patch for routing and privilege changes +SRC_URI += "file://http_routing/0001-Add-asyncResp-support-during-handleUpgrade.patch \ + file://http_routing/0002-Move-privileges-to-separate-entity.patch \ + file://http_routing/0003-Add-Support-for-privilege-check-in-handleUpgrade.patch \ + file://http_routing/0004-Add-Privileges-to-Websockets.patch \ + file://http_routing/0005-Add-Privileges-to-SseSockets.patch \ + " + +# 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" +EXTRA_OEMESON += " -Dredfish-host-logger=disabled" +RDEPENDS:${PN}:remove = " jsnbd" + + -- cgit v1.2.3