diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces')
16 files changed, 3386 insertions, 1902 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Define-Redfish-interface-Registries-Bios.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Define-Redfish-interface-Registries-Bios.patch new file mode 100644 index 000000000..dd2f3483d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Define-Redfish-interface-Registries-Bios.patch @@ -0,0 +1,850 @@ +From 5e3b0c1f8add50acca911a927ba8a1f4864cb315 Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Fri, 4 Sep 2020 19:24:25 +0800 +Subject: [PATCH] Define Redfish interface "/Registries/Bios" and enable + Attributes property + +1. Define Redfish interface "/Registries/Bios" for BIOS Attribute Registry + RBC Daemon provide method to get BIOS attribute registry. +2. Eanble Attributes property for BIOS resource +3. Define Redfish interface "/Systems/system/Bios/Settings" for BIOS +settings +4. RBC daemon is at +https://gerrit.openbmc-project.xyz/#/c/openbmc/bios-settings-mgr/+/35563/ +5. IPMI command implementation is at +https://gerrit.openbmc-project.xyz/#/c/openbmc/intel-ipmi-oem/+/30827/ +6. Property design is at +https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/xyz/openbmc_project/BIOSConfig +7. Design doc is at +https://github.com/openbmc/docs/blob/master/designs/remote-bios-configuration.md +8. There will be 95 test cases for this feature in the validation team. + +Tested: + +1. Use postman (Redfish tool) could get all the attributes in bios +resouce, get bios settings, get bios attribute +registry. +https://IP_ADDR/redfish/v1/Systems/system/Bios +{ + "@Redfish.Settings": { + "@odata.type": "#Settings.v1_3_0.Settings", + "SettingsObject": { + "@odata.id": "/redfish/v1/Systems/system/Bios/Settings" + } + }, + "@odata.id": "/redfish/v1/Systems/system/Bios", + "@odata.type": "#Bios.v1_1_0.Bios", + "Actions": { + "#Bios.ChangePassword": { + "target": "/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword" + }, + "#Bios.ResetBios": { + "target": "/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios" + } + }, + "AttributeRegistry": "BiosAttributeRegistry", + "Attributes": { + "attr0": "current value" + }, + "Description": "BIOS Configuration Service", + "Id": "BIOS", + "Links": { + "ActiveSoftwareImage": { + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/bios_active" + }, + "SoftwareImages": [ + { + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/bios_active" + } + ], + "SoftwareImages@odata.count": 1 + }, + "Name": "BIOS Configuration" +} + +Redfish interface: https://BMCIP/redfish/v1/Registries/BiosAttributeRegistry +{ + "@odata.id": "/redfish/v1/Registries/BiosAttributeRegistry", + "@odata.type": "#MessageRegistryFile.v1_1_0.MessageRegistryFile", + "Description": "BiosAttributeRegistry Message Registry File Location", + "Id": "BiosAttributeRegistry", + "Languages": [ + "en" + ], + "Languages@odata.count": 1, + "Location": [ + { + "Language": "en", + "Uri": "/redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry" + } + ], + "Location@odata.count": 1, + "Name": "BiosAttributeRegistry Message Registry File", + "Registry": "BiosAttributeRegistry.1.0.0" +} + +Redfish interface: https://BMCIP/redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry +{ + "@odata.id": "/redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry", + "@odata.type": "#AttributeRegistry.v1_3_2.AttributeRegistry", + "Id": "BiosAttributeRegistry", + "Language": "en", + "Name": "Bios Attribute Registry", + "OwningEntity": "OpenBMC", + "RegistryEntries": { + "Attributes": [ + { + "AttributeName": "attr0", + "CurrentValue": "current value", + "DefaultValue": "default value", + "DisplayName": "display name for attr0", + "HelpText": "description for attr0", + "MenuPath": "./menu/path/for/attr0", + "ReadOnly": false, + "Type": "String", + "Value": [] + } + ] + }, + "RegistryVersion": "1.0.0" +} + +https://BMC_IPADDR/redfish/v1/Systems/system/Bios/Settings +{ + "@odata.id": "/redfish/v1/Systems/system/Bios/Settings", + "@odata.type": "#Bios.v1_1_0.Bios", + "AttributeRegistry": "BiosAttributeRegistry", + "Attributes": { + "QuietBoot": "0x0" + }, + "Id": "BiosSettingsV1", + "Name": "Bios Settings Version 1" +} + +2. Passed Validator check for bios resource and bios attribute registry +*** /redfish/v1/Systems/system/Bios +INFO - Type (#Bios.v1_1_0.Bios), GET SUCCESS (time: 1.57377) +INFO - PASS +*** /redfish/v1/Registries/BiosAttributeRegistry +INFO - Type (#MessageRegistryFile.v1_1_0.MessageRegistryFile), GET SUCCESS (time: 0.075438) +INFO - PASS +INFO - +*** /redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry +INFO - Type (#AttributeRegistry.v1_3_2.AttributeRegistry), GET SUCCESS (time: 0.075751) +INFO - PASS + +@odata.id /redfish/v1/Systems/system/Bios odata Exists PASS +@odata.type #Settings.v1_3_0.Settings odata Exists PASS +Links [JSON Object] Bios.v1_1_0.Links Yes complex +Links.ActiveSoftwareImage Link: /redfish/v1/UpdateService/FirmwareInventory/bios_active link to: SoftwareInventory Yes PASS +Links.SoftwareImages Array (size: 1) array of: SoftwareInventory Yes ... +Links.SoftwareImages[0] Link: /redfish/v1/UpdateService/FirmwareInventory/bios_active SoftwareInventory Yes PASS +Links.Oem - Resource.Oem No Optional +SoftwareImages@odata.count 1 odata Exists PASS +AttributeRegistry BiosAttributeRegistry string Yes PASS +Actions [JSON Object] Bios.v1_0_0.Actions Yes complex +Actions.#Bios.ResetBios Action - Yes PASS +Actions.#Bios.ChangePassword Action - Yes PASS +Attributes [JSON Object] Bios.v1_0_0.Attributes Yes complex +Attributes.attr0 current value primitive Yes PASS +Id BIOS string Yes PASS +Description BIOS Configuration Service string Yes PASS +Name BIOS Configuration string Yes PASS +Oem - Resource.Oem No Optional +@Redfish.Settings [JSON Object] Settings.Settings Yes complex +@Redfish.Settings.MaintenanceWindowResource - link to: ItemOrCollection No Optional +@Redfish.Settings.SupportedApplyTimes - string (enum) No Optional +@Redfish.Settings.Time - date No Optional +@Redfish.Settings.ETag - string No Optional +@Redfish.Settings.SettingsObject Link: /redfish/v1/Systems/system/Bios/Settings link to: Item Yes PASS +@Redfish.Settings.Messages - Message No Optional + +@odata.id /redfish/v1/Registries/BiosAttributeRegistry odata Exists PASS +@odata.type #MessageRegistryFile.v1_1_0.MessageRegistryFile odata Exists PASS +Languages@odata.count 1 odata Exists PASS +Location@odata.count 1 odata Exists PASS +Actions - MessageRegistryFile.v1_1_0.Actions No Optional +Languages Array (size: 1) string Yes ... +Languages[0] en string Yes PASS +Registry BiosAttributeRegistry.1.0.0 string Yes PASS +Location Array (size: 1) array of: Location Yes ... +Location[0] [JSON Object] Location Yes complex +Location[0].Language en string Yes PASS +Location[0].Uri /redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry string Yes PASS +Location[0].ArchiveUri - string No Optional +Location[0].PublicationUri - string No Optional +Location[0].ArchiveFile - string No Optional +Id BiosAttributeRegistry string Yes PASS +Description BiosAttributeRegistry Message Registry File Location string Yes PASS +Name BiosAttributeRegistry Message Registry File string Yes PASS +Oem - Resource.Oem No Optional + +@odata.id /redfish/v1/Registries/BiosAttributeRegistry/BiosAttributeRegistry odata Exists PASS +@odata.type #AttributeRegistry.v1_3_2.AttributeRegistry odata Exists PASS +Actions - AttributeRegistry.v1_1_0.Actions No Optional +Language en string Yes PASS +RegistryVersion 1.0.0 string Yes PASS +OwningEntity OpenBMC string Yes PASS +SupportedSystems - SupportedSystems No Optional +RegistryEntries [JSON Object] AttributeRegistry.v1_0_0.RegistryEntries Yes complex +RegistryEntries.Attributes Array (size: 1) array of: Attributes Yes ... +RegistryEntries.Attributes[0] [JSON Object] Attributes Yes complex +RegistryEntries.Attributes[0].Oem - Resource.Oem No Optional +RegistryEntries.Attributes[0].ResetRequired - boolean No Optional +RegistryEntries.Attributes[0].UefiDevicePath - string No Optional +RegistryEntries.Attributes[0].UefiKeywordName - string No Optional +RegistryEntries.Attributes[0].UefiNamespaceId - string No Optional +RegistryEntries.Attributes[0].AttributeName attr0 string Yes PASS +RegistryEntries.Attributes[0].Type String string (enum) Yes PASS +RegistryEntries.Attributes[0].Value Array (size: 0) array of: AttributeValue Yes ... +RegistryEntries.Attributes[0].DisplayName display name for attr0 string Yes PASS +RegistryEntries.Attributes[0].HelpText description for attr0 string Yes PASS +RegistryEntries.Attributes[0].WarningText - string No Optional +RegistryEntries.Attributes[0].CurrentValue current value primitive Yes PASS +RegistryEntries.Attributes[0].DefaultValue default value primitive Yes PASS +RegistryEntries.Attributes[0].DisplayOrder - number No Optional +RegistryEntries.Attributes[0].MenuPath ./menu/path/for/attr0 string Yes PASS +RegistryEntries.Attributes[0].ReadOnly False boolean Yes PASS +RegistryEntries.Attributes[0].WriteOnly - boolean No Optional +RegistryEntries.Attributes[0].GrayOut - boolean No Optional +RegistryEntries.Attributes[0].Hidden - boolean No Optional +RegistryEntries.Attributes[0].Immutable - boolean No Optional +RegistryEntries.Attributes[0].IsSystemUniqueProperty - boolean No Optional +RegistryEntries.Attributes[0].MaxLength - number No Optional +RegistryEntries.Attributes[0].MinLength - number No Optional +RegistryEntries.Attributes[0].ScalarIncrement - number No Optional +RegistryEntries.Attributes[0].UpperBound - number No Optional +RegistryEntries.Attributes[0].LowerBound - number No Optional +RegistryEntries.Attributes[0].ValueExpression - string No Optional +RegistryEntries.Menus - Menus No Optional +RegistryEntries.Dependencies - Dependencies No Optional +Id BiosAttributeRegistry string Yes PASS +Description - string No Optional +Name Bios Attribute Registry string Yes PASS +Oem - Resource.Oem No Optional + +Change-Id: Iecc61018c350f0b8c89df59b2864b941508b1916 +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> +--- + redfish-core/include/redfish.hpp | 2 + + .../include/registries/bios_registry.hpp | 31 ++ + redfish-core/lib/bios.hpp | 503 ++++++++++++++++++ + redfish-core/lib/message_registries.hpp | 9 +- + 4 files changed, 544 insertions(+), 1 deletion(-) + create mode 100644 redfish-core/include/registries/bios_registry.hpp + +diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp +index 5d5eb7b..a8e5cf2 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -157,6 +157,8 @@ class RedfishService + nodes.emplace_back(std::make_unique<SystemActionsReset>(app)); + nodes.emplace_back(std::make_unique<SystemResetActionInfo>(app)); + nodes.emplace_back(std::make_unique<BiosService>(app)); ++ nodes.emplace_back(std::make_unique<BiosSettings>(app)); ++ nodes.emplace_back(std::make_unique<BiosAttributeRegistry>(app)); + nodes.emplace_back(std::make_unique<BiosReset>(app)); + #ifdef BMCWEB_ENABLE_VM_NBDPROXY + nodes.emplace_back(std::make_unique<VirtualMedia>(app)); +diff --git a/redfish-core/include/registries/bios_registry.hpp b/redfish-core/include/registries/bios_registry.hpp +new file mode 100644 +index 0000000..88ef782 +--- /dev/null ++++ b/redfish-core/include/registries/bios_registry.hpp +@@ -0,0 +1,31 @@ ++/* ++// Copyright (c) 2020 Intel Corporation ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++*/ ++#pragma once ++ ++namespace redfish::message_registries::bios ++{ ++const Header header = { ++ "Copyright 2020 OpenBMC. All rights reserved.", ++ "#MessageRegistry.v1_4_0.MessageRegistry", ++ "BiosAttributeRegistry.1.0.0", ++ "Bios Attribute Registry", ++ "en", ++ "This registry defines the messages for bios attribute registry.", ++ "BiosAttributeRegistry", ++ "1.0.0", ++ "OpenBMC", ++}; ++} // namespace redfish::message_registries::bios +\ No newline at end of file +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 2c31077..5f8c91b 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -3,8 +3,140 @@ + #include "node.hpp" + + #include <utils/fw_utils.hpp> ++ + namespace redfish + { ++ ++/*baseBIOSTable ++map{attributeName,struct{attributeType,readonlyStatus,displayname, ++ description,menuPath,current,default, ++ array{struct{optionstring,optionvalue}}}} ++*/ ++using BiosBaseTableType = std::vector<std::pair< ++ std::string, ++ std::tuple< ++ std::string, bool, std::string, std::string, std::string, ++ std::variant<int64_t, std::string>, std::variant<int64_t, std::string>, ++ std::vector< ++ std::tuple<std::string, std::variant<int64_t, std::string>>>>>>; ++using BiosBaseTableItemType = std::pair< ++ std::string, ++ std::tuple< ++ std::string, bool, std::string, std::string, std::string, ++ std::variant<int64_t, std::string>, std::variant<int64_t, std::string>, ++ std::vector< ++ std::tuple<std::string, std::variant<int64_t, std::string>>>>>; ++using OptionsItemType = ++ std::tuple<std::string, std::variant<int64_t, std::string>>; ++ ++enum BiosBaseTableIndex ++{ ++ biosBaseAttrType = 0, ++ biosBaseReadonlyStatus, ++ biosBaseDisplayName, ++ biosBaseDescription, ++ biosBaseMenuPath, ++ biosBaseCurrValue, ++ biosBaseDefaultValue, ++ biosBaseOptions ++}; ++enum OptionsItemIndex ++{ ++ optItemType = 0, ++ optItemValue ++}; ++/* ++ The Pending attribute name and new value. ++ ex- { {"QuietBoot",Type.Integer, 0x1}, ++ { "DdrFreqLimit",Type.String,"2933"} ++ } ++*/ ++using PendingAttributesType = std::vector<std::pair< ++ std::string, std::tuple<std::string, std::variant<int64_t, std::string>>>>; ++using PendingAttributesItemType = ++ std::pair<std::string, ++ std::tuple<std::string, std::variant<int64_t, std::string>>>; ++enum PendingAttributesIndex ++{ ++ pendingAttrType = 0, ++ pendingAttrValue ++}; ++static std::string mapAttrTypeToRedfish(const std::string_view typeDbus) ++{ ++ std::string ret; ++ if (typeDbus == "xyz.openbmc_project.BIOSConfig.Manager." ++ "AttributeType.Enumeration") ++ { ++ ret = "Enumeration"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.String") ++ { ++ ret = "String"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.Password") ++ { ++ ret = "Password"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.Integer") ++ { ++ ret = "Integer"; ++ } ++ else if (typeDbus == "xyz.openbmc_project.BIOSConfig." ++ "Manager.AttributeType.Boolean") ++ { ++ ret = "Boolean"; ++ } ++ else ++ { ++ ret = "UNKNOWN"; ++ } ++ ++ return ret; ++} ++static std::string mapBoundTypeToRedfish(const std::string_view typeDbus) ++{ ++ std::string ret; ++ if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.ScalarIncrement") ++ { ++ ret = "ScalarIncrement"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.LowerBound") ++ { ++ ret = "LowerBound"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.UpperBound") ++ { ++ ret = "UpperBound"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.MinStringLength") ++ { ++ ret = "MinStringLength"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.MaxStringLength") ++ { ++ ret = "MaxStringLength"; ++ } ++ else if (typeDbus == ++ "xyz.openbmc_project.BIOSConfig.Manager.BoundType.OneOf") ++ { ++ ret = "OneOf"; ++ } ++ else ++ { ++ ret = "UNKNOWN"; ++ } ++ ++ return ret; ++} ++ + /** + * BiosService class supports handle get method for bios. + */ +@@ -35,6 +167,377 @@ class BiosService : public Node + // Get the ActiveSoftwareImage and SoftwareImages + fw_util::populateFirmwareInformation(asyncResp, fw_util::biosPurpose, + "", true); ++ asyncResp->res.jsonValue["@Redfish.Settings"] = { ++ {"@odata.type", "#Settings.v1_3_0.Settings"}, ++ {"SettingsObject", ++ {{"@odata.id", "/redfish/v1/Systems/system/Bios/Settings"}}}}; ++ asyncResp->res.jsonValue["AttributeRegistry"] = "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["Attributes"] = {}; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ const std::string& service = getObjectType.begin()->first; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const std::variant<BiosBaseTableType>& retBiosTable) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "getBiosAttributes DBUS error: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ const BiosBaseTableType* baseBiosTable = ++ std::get_if<BiosBaseTableType>(&retBiosTable); ++ nlohmann::json& attributesJson = ++ asyncResp->res.jsonValue["Attributes"]; ++ if (baseBiosTable == nullptr) ++ { ++ BMCWEB_LOG_ERROR << "baseBiosTable == nullptr "; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ for (const BiosBaseTableItemType& item : *baseBiosTable) ++ { ++ const std::string& key = item.first; ++ const std::string& itemType = ++ std::get<biosBaseAttrType>(item.second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<biosBaseCurrValue>( ++ item.second)); ++ attributesJson.emplace(key, currValue != nullptr ++ ? *currValue ++ : ""); ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = std::get_if<int64_t>( ++ &std::get<biosBaseCurrValue>(item.second)); ++ attributesJson.emplace( ++ key, currValue != nullptr ? *currValue : 0); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "Unsupported attribute type."; ++ messages::internalError(asyncResp->res); ++ } ++ } ++ }, ++ service, "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.BIOSConfig.Manager", "BaseBIOSTable"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/bios_config/manager", ++ std::array<const char*, 0>()); ++ } ++}; ++ ++/** ++ * BiosSettings class supports handle GET/PATCH method for ++ * BIOS configuration pending settings. ++ */ ++class BiosSettings : public Node ++{ ++ public: ++ BiosSettings(App& app) : ++ Node(app, "/redfish/v1/Systems/system/Bios/Settings") ++ { ++ entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ asyncResp->res.jsonValue["@odata.id"] = ++ "/redfish/v1/Systems/system/Bios/Settings"; ++ asyncResp->res.jsonValue["@odata.type"] = "#Bios.v1_1_0.Bios"; ++ asyncResp->res.jsonValue["Name"] = "Bios Settings Version 1"; ++ asyncResp->res.jsonValue["Id"] = "BiosSettingsV1"; ++ asyncResp->res.jsonValue["AttributeRegistry"] = "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["Attributes"] = {}; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ std::string service = getObjectType.begin()->first; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const std::variant<PendingAttributesType>& ++ retPendingAttributes) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "getBiosSettings DBUS error: " ++ << ec; ++ messages::resourceNotFound(asyncResp->res, ++ "Systems/system/Bios", ++ "Settings"); ++ return; ++ } ++ const PendingAttributesType* pendingAttributes = ++ std::get_if<PendingAttributesType>( ++ &retPendingAttributes); ++ nlohmann::json& attributesJson = ++ asyncResp->res.jsonValue["Attributes"]; ++ if (pendingAttributes == nullptr) ++ { ++ BMCWEB_LOG_ERROR << "pendingAttributes == nullptr "; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ for (const PendingAttributesItemType& item : ++ *pendingAttributes) ++ { ++ const std::string& key = item.first; ++ const std::string& itemType = ++ std::get<pendingAttrType>(item.second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<pendingAttrValue>( ++ item.second)); ++ attributesJson.emplace(key, currValue != nullptr ++ ? *currValue ++ : ""); ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = std::get_if<int64_t>( ++ &std::get<pendingAttrValue>(item.second)); ++ attributesJson.emplace( ++ key, currValue != nullptr ? *currValue : 0); ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "Unsupported attribute type."; ++ messages::internalError(asyncResp->res); ++ } ++ } ++ }, ++ service, "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.BIOSConfig.Manager", ++ "PendingAttributes"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/bios_config/manager", ++ std::array<const char*, 0>()); ++ } ++}; ++/** ++ * BiosAttributeRegistry class supports handle get method for BIOS attribute ++ * registry. ++ */ ++class BiosAttributeRegistry : public Node ++{ ++ public: ++ BiosAttributeRegistry(App& app) : ++ Node(app, "/redfish/v1/Registries/BiosAttributeRegistry/" ++ "BiosAttributeRegistry") ++ { ++ entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ asyncResp->res.jsonValue["@odata.id"] = ++ "/redfish/v1/Registries/BiosAttributeRegistry/" ++ "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["@odata.type"] = ++ "#AttributeRegistry.v1_3_2.AttributeRegistry"; ++ asyncResp->res.jsonValue["Name"] = "Bios Attribute Registry"; ++ asyncResp->res.jsonValue["Id"] = "BiosAttributeRegistry"; ++ asyncResp->res.jsonValue["RegistryVersion"] = "1.0.0"; ++ asyncResp->res.jsonValue["Language"] = "en"; ++ asyncResp->res.jsonValue["OwningEntity"] = "OpenBMC"; ++ asyncResp->res.jsonValue["RegistryEntries"]["Attributes"] = ++ nlohmann::json::array(); ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec, ++ const GetObjectType& getObjectType) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " ++ << ec; ++ messages::internalError(asyncResp->res); ++ ++ return; ++ } ++ std::string service = getObjectType.begin()->first; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const std::variant<BiosBaseTableType>& retBiosTable) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR ++ << "getBiosAttributeRegistry DBUS error: " ++ << ec; ++ messages::resourceNotFound( ++ asyncResp->res, "Registries/Bios", "Bios"); ++ return; ++ } ++ const BiosBaseTableType* baseBiosTable = ++ std::get_if<BiosBaseTableType>(&retBiosTable); ++ nlohmann::json& attributeArray = ++ asyncResp->res ++ .jsonValue["RegistryEntries"]["Attributes"]; ++ nlohmann::json optionsArray = nlohmann::json::array(); ++ if (baseBiosTable == nullptr) ++ { ++ BMCWEB_LOG_ERROR << "baseBiosTable == nullptr "; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ for (const BiosBaseTableItemType& item : *baseBiosTable) ++ { ++ const std::string& itemType = ++ std::get<biosBaseAttrType>(item.second); ++ std::string attrType = ++ mapAttrTypeToRedfish(itemType); ++ if (attrType == "UNKNOWN") ++ { ++ BMCWEB_LOG_ERROR << "attrType == UNKNOWN"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ nlohmann::json attributeItem; ++ attributeItem["AttributeName"] = item.first; ++ attributeItem["Type"] = attrType; ++ attributeItem["ReadOnly"] = ++ std::get<biosBaseReadonlyStatus>(item.second); ++ attributeItem["DisplayName"] = ++ std::get<biosBaseDisplayName>(item.second); ++ attributeItem["HelpText"] = ++ std::get<biosBaseDescription>(item.second); ++ attributeItem["MenuPath"] = ++ std::get<biosBaseMenuPath>(item.second); ++ ++ if (attrType == "String") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<biosBaseCurrValue>( ++ item.second)); ++ const std::string* defValue = ++ std::get_if<std::string>( ++ &std::get<biosBaseDefaultValue>( ++ item.second)); ++ attributeItem["CurrentValue"] = ++ currValue != nullptr ? *currValue : ""; ++ attributeItem["DefaultValue"] = ++ defValue != nullptr ? *defValue : ""; ++ } ++ else if (attrType == "Integer") ++ { ++ const int64_t* currValue = std::get_if<int64_t>( ++ &std::get<biosBaseCurrValue>(item.second)); ++ const int64_t* defValue = std::get_if<int64_t>( ++ &std::get<biosBaseDefaultValue>( ++ item.second)); ++ attributeItem["CurrentValue"] = ++ currValue != nullptr ? *currValue : 0; ++ attributeItem["DefaultValue"] = ++ defValue != nullptr ? *defValue : 0; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR ++ << "Unsupported attribute type."; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::vector<OptionsItemType>& optionsVector = ++ std::get<biosBaseOptions>(item.second); ++ for (const OptionsItemType& optItem : optionsVector) ++ { ++ nlohmann::json optItemJson; ++ const std::string& strOptItemType = ++ std::get<optItemType>(optItem); ++ std::string optItemTypeRedfish = ++ mapBoundTypeToRedfish(strOptItemType); ++ if (optItemTypeRedfish == "UNKNOWN") ++ { ++ BMCWEB_LOG_ERROR ++ << "optItemTypeRedfish == UNKNOWN"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ if (optItemTypeRedfish == "OneOf") ++ { ++ const std::string* currValue = ++ std::get_if<std::string>( ++ &std::get<optItemValue>(optItem)); ++ optItemJson[optItemTypeRedfish] = ++ currValue != nullptr ? *currValue : ""; ++ } ++ else ++ { ++ const int64_t* currValue = ++ std::get_if<int64_t>( ++ &std::get<optItemValue>(optItem)); ++ optItemJson[optItemTypeRedfish] = ++ currValue != nullptr ? *currValue : 0; ++ } ++ ++ optionsArray.push_back(optItemJson); ++ } ++ ++ attributeItem["Value"] = optionsArray; ++ attributeArray.push_back(attributeItem); ++ } ++ }, ++ service, "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.BIOSConfig.Manager", "BaseBIOSTable"); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetObject", ++ "/xyz/openbmc_project/bios_config/manager", ++ std::array<const char*, 0>()); + } + }; + /** +diff --git a/redfish-core/lib/message_registries.hpp b/redfish-core/lib/message_registries.hpp +index 77fc10e..0caf01c 100644 +--- a/redfish-core/lib/message_registries.hpp ++++ b/redfish-core/lib/message_registries.hpp +@@ -18,6 +18,7 @@ + #include "node.hpp" + #include "registries.hpp" + #include "registries/base_message_registry.hpp" ++#include "registries/bios_registry.hpp" + #include "registries/openbmc_message_registry.hpp" + #include "registries/resource_event_message_registry.hpp" + #include "registries/task_event_message_registry.hpp" +@@ -56,11 +57,12 @@ class MessageRegistryFileCollection : public Node + {"@odata.id", "/redfish/v1/Registries"}, + {"Name", "MessageRegistryFile Collection"}, + {"Description", "Collection of MessageRegistryFiles"}, +- {"Members@odata.count", 4}, ++ {"Members@odata.count", 5}, + {"Members", + {{{"@odata.id", "/redfish/v1/Registries/Base"}}, + {{"@odata.id", "/redfish/v1/Registries/TaskEvent"}}, + {{"@odata.id", "/redfish/v1/Registries/ResourceEvent"}}, ++ {{"@odata.id", "/redfish/v1/Registries/BiosAttributeRegistry"}}, + {{"@odata.id", "/redfish/v1/Registries/OpenBMC"}}}}}; + + res.end(); +@@ -118,6 +120,11 @@ class MessageRegistryFile : public Node + header = &message_registries::resource_event::header; + url = message_registries::resource_event::url; + } ++ else if (registry == "BiosAttributeRegistry") ++ { ++ header = &message_registries::bios::header; ++ dmtf.clear(); ++ } + else + { + messages::resourceNotFound( +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0007-BIOS-config-Add-support-for-PATCH-operation.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0007-BIOS-config-Add-support-for-PATCH-operation.patch new file mode 100755 index 000000000..18403446d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0007-BIOS-config-Add-support-for-PATCH-operation.patch @@ -0,0 +1,154 @@ +From 8f823ad555b67b228413a0fcb46771d86108dcb3 Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Wed, 23 Dec 2020 16:50:45 +0800 +Subject: [PATCH] BaseBiosTable: Add support for PATCH operation + +This commit brings in support for PATCH operation of the +bios variables that updates the BaseBiosTable. + +Tested-By: +* Passed Redfish validator + +* Single Attribute: +PATCH https://${bmc}/redfish/v1/Systems/system/Bios/Settings -d +'{"data":[{"AttributeName": <attribute name>, "AttributeType": +<attribute type>, "AttributeValue": <attribute value>}]}' + +* Multiple Attributes: +PATCH https://${bmc}/redfish/v1/Systems/system/Bios/Settings -d +'{"data":[{"AttributeName": <attribute name>, "AttributeType": +<attribute type>, "AttributeValue": <attribute value>}, +{"AttributeName": <attribute name>, "AttributeType": +<attribute type>, "AttributeValue": <attribute value>}]}' + +This makes use of the "Set" of "PendingAttributes" in the +backend and that updates the BaseBiosTable. + + +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> +--- + redfish-core/lib/bios.hpp | 94 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 93 insertions(+), 1 deletion(-) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 9fe8c6e..e9c8969 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; +@@ -263,7 +286,9 @@ class BiosSettings : public Node + BiosSettings(App& app) : + Node(app, "/redfish/v1/Systems/system/Bios/Settings") + { +- entityPrivileges = {{boost::beast::http::verb::get, {{"Login"}}}}; ++ entityPrivileges = { ++ {boost::beast::http::verb::get, {{"Login"}}}, ++ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}}; + } + + private: +@@ -361,6 +386,73 @@ class BiosSettings : public Node + "/xyz/openbmc_project/bios_config/manager", + std::array<const char*, 0>()); + } ++ ++ void doPatch(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ ++ nlohmann::json inpJson; ++ ++ if (!redfish::json_util::readJson(req, asyncResp->res, "data", inpJson)) ++ { ++ return; ++ } ++ ++ for (auto& attrInfo : inpJson) ++ { ++ std::optional<std::string> attrName; ++ std::optional<std::string> attrType; ++ std::optional<std::string> attrValue; ++ if (!json_util::getValueFromJsonObject(attrInfo, "AttributeName", ++ attrName)) ++ { ++ messages::propertyMissing(asyncResp->res, "AttributeName"); ++ return; ++ } ++ if (!json_util::getValueFromJsonObject(attrInfo, "AttributeType", ++ attrType)) ++ { ++ messages::propertyMissing(asyncResp->res, "AttributeType"); ++ return; ++ } ++ if (!json_util::getValueFromJsonObject(attrInfo, "AttributeValue", ++ attrValue)) ++ { ++ messages::propertyMissing(asyncResp->res, "AttributeValue"); ++ return; ++ } ++ std::string biosAttrType = mapRedfishToAttrType(*attrType); ++ ++ if (biosAttrType == "UNKNOWN") ++ { ++ BMCWEB_LOG_ERROR << "Invalid attribute type"; ++ messages::propertyValueNotInList(asyncResp->res, ++ "AttributeType", *attrType); ++ return; ++ } ++ ++ PendingAttributesType pendingAttributes; ++ pendingAttributes.emplace_back(std::make_pair( ++ *attrName, std::make_tuple(biosAttrType, *attrValue))); ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "doPatch resp_handler got error " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ }, ++ "xyz.openbmc_project.BIOSConfigManager", ++ "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.BIOSConfig.Manager", "PendingAttributes", ++ std::variant<PendingAttributesType>(pendingAttributes)); ++ } ++ } + }; + /** + * BiosAttributeRegistry class supports handle get method for BIOS attribute +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0008-Add-support-to-ResetBios-action.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0008-Add-support-to-ResetBios-action.patch new file mode 100644 index 000000000..983eb170a --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0008-Add-support-to-ResetBios-action.patch @@ -0,0 +1,62 @@ +From 2f5b401bb36be7a1f800bea20fb489a7b2ac6d45 Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Wed, 23 Dec 2020 22:47:56 +0800 +Subject: [PATCH] Add support to ResetBios action + +Tested: + +Bios reset flag can be modified throw redfish +POST https://IP_ADDR/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios + +Change-Id: I5e5fbdd70d4a3ce3b976cc2eb0a7d9a2a3adb124 +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> + +--- + redfish-core/lib/bios.hpp | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 4727ef2..888c511 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -663,7 +663,7 @@ class BiosReset : public Node + Node(app, "/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios/") + { + entityPrivileges = { +- {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; ++ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; + } + + private: +@@ -675,19 +675,23 @@ class BiosReset : public Node + const std::vector<std::string>&) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); +- ++ std::string resetFlag = ++ "xyz.openbmc_project.BIOSConfig.Manager.ResetFlag.FactoryDefaults"; + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec) { + if (ec) + { +- BMCWEB_LOG_ERROR << "Failed to reset bios: " << ec; ++ BMCWEB_LOG_ERROR << "doPost bios reset got error " << ec; + messages::internalError(asyncResp->res); + return; + } ++ BMCWEB_LOG_DEBUG << "bios reset action is done"; + }, +- "org.open_power.Software.Host.Updater", +- "/xyz/openbmc_project/software", +- "xyz.openbmc_project.Common.FactoryReset", "Reset"); ++ "xyz.openbmc_project.BIOSConfigManager", ++ "/xyz/openbmc_project/bios_config/manager", ++ "org.freedesktop.DBus.Properties", "Set", ++ "xyz.openbmc_project.BIOSConfig.Manager", "ResetBIOSSettings", ++ std::variant<std::string>(resetFlag)); + } + }; + } // namespace redfish +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0009-Add-support-to-ChangePassword-action.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0009-Add-support-to-ChangePassword-action.patch new file mode 100644 index 000000000..c603615f1 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0009-Add-support-to-ChangePassword-action.patch @@ -0,0 +1,139 @@ +From 0192a2217eda578ca058cdc3a289ada3ee39e47a Mon Sep 17 00:00:00 2001 +From: Kuiying Wang <kuiying.wang@intel.com> +Date: Wed, 23 Dec 2020 14:41:23 +0800 +Subject: [PATCH] Add support to ChangePassword action + +Tested: + +Passed Redfish validator. +Bios change password: +root@intel-obmc:~# cat /var/lib/bios-settings-manager/seedData +{ +"UserPwdHash": "08D91157785366CDC3AA64D87E5E3C621EDAB13E26B6E484397EBA5E459E54C567BF5B1FFB36A43B6142B18F8D642E9D", +"AdminPwdHash": "08D91157785366CDC3AA64D87E5E3C621EDAB13E26B6E484397EBA5E459E54C567BF5B1FFB36A43B6142B18F8D642E9D", +"Seed": "123456", +"HashAlgo": "SHA384" +} +POST https://IP_ADDR/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword +{ + "NewPassword": "12345678", + "OldPassword": "1234567890", + "PasswordName": "Administrator" +} +root@intel-obmc:~# cat /var/lib/bios-settings-manager/passwordData +{ + "CurrentPassword": "1234567890", + "IsAdminPwdChanged": 1, + "IsUserPwdChanged": 0, + "NewPassword": "2DD65D57EB60B1D92C5F3D2DC84724FCEE7BC02E57AA75E834712266ED94CAC704047B2FF7CEC1C36BED280B36BB5AC6", + "UserName": "Administrator" +} + +Change-Id: I90319a68da0b0a7f9c5cd65a8cb8cf52269a5f52 +Signed-off-by: Kuiying Wang <kuiying.wang@intel.com> +--- + redfish-core/include/redfish.hpp | 1 + + redfish-core/lib/bios.hpp | 70 ++++++++++++++++++++++++++++++++ + 2 files changed, 71 insertions(+) + +diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp +index a8e5cf2..dabf78e 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -160,6 +160,7 @@ class RedfishService + nodes.emplace_back(std::make_unique<BiosSettings>(app)); + nodes.emplace_back(std::make_unique<BiosAttributeRegistry>(app)); + nodes.emplace_back(std::make_unique<BiosReset>(app)); ++ nodes.emplace_back(std::make_unique<BiosChangePassword>(app)); + #ifdef BMCWEB_ENABLE_VM_NBDPROXY + nodes.emplace_back(std::make_unique<VirtualMedia>(app)); + nodes.emplace_back(std::make_unique<VirtualMediaCollection>(app)); +diff --git a/redfish-core/lib/bios.hpp b/redfish-core/lib/bios.hpp +index 7b4306b..52ee356 100644 +--- a/redfish-core/lib/bios.hpp ++++ b/redfish-core/lib/bios.hpp +@@ -186,6 +186,9 @@ class BiosService : public Node + asyncResp->res.jsonValue["Actions"]["#Bios.ResetBios"] = { + {"target", + "/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios"}}; ++ asyncResp->res.jsonValue["Actions"]["#Bios.ChangePassword"] = { ++ {"target", ++ "/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword"}}; + + // Get the ActiveSoftwareImage and SoftwareImages + fw_util::populateFirmwareInformation(asyncResp, fw_util::biosPurpose, +@@ -674,4 +677,71 @@ class BiosReset : public Node + std::variant<std::string>(resetFlag)); + } + }; ++ ++/** ++ * BiosChangePassword class supports handle POST method for change bios ++ * password. The class retrieves and sends data directly to D-Bus. ++ */ ++class BiosChangePassword : public Node ++{ ++ public: ++ BiosChangePassword(App& app) : ++ Node(app, ++ "/redfish/v1/Systems/system/Bios/Actions/Bios.ChangePassword/") ++ { ++ entityPrivileges = { ++ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; ++ } ++ ++ private: ++ /** ++ * Function handles POST method request. ++ * Analyzes POST body message before sends Reset request data to D-Bus. ++ */ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ std::string currentPassword, newPassword, userName; ++ if (!json_util::readJson(req, res, "NewPassword", newPassword, ++ "OldPassword", currentPassword, "PasswordName", ++ userName)) ++ { ++ return; ++ } ++ if (currentPassword.empty()) ++ { ++ messages::actionParameterUnknown(asyncResp->res, "ChangePassword", ++ "OldPassword"); ++ return; ++ } ++ if (newPassword.empty()) ++ { ++ messages::actionParameterUnknown(asyncResp->res, "ChangePassword", ++ "NewPassword"); ++ return; ++ } ++ if (userName.empty()) ++ { ++ messages::actionParameterUnknown(asyncResp->res, "ChangePassword", ++ "PasswordName"); ++ return; ++ } ++ crow::connections::systemBus->async_method_call( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_CRITICAL << "Failed in doPost(BiosChangePassword) " ++ << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ }, ++ "xyz.openbmc_project.BIOSConfigPassword", ++ "/xyz/openbmc_project/bios_config/password", ++ "xyz.openbmc_project.BIOSConfig.Password", "ChangePassword", ++ userName, currentPassword, newPassword); ++ } ++}; ++ + } // namespace redfish +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch new file mode 100644 index 000000000..520007d41 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0010-managers-add-attributes-for-Manager.CommandShell.patch @@ -0,0 +1,57 @@ +From 01a5c12e350c04f8ed94c7e49a6030a4699eac6e Mon Sep 17 00:00:00 2001 +From: Jayaprakash Mutyala <mutyalax.jayaprakash@intel.com> +Date: Mon, 28 Dec 2020 18:55:57 +0000 +Subject: [PATCH] managers: add attributes for Manager.CommandShell + +Issue: ConnectTypesSupported, ServiceEnabled and + MaxConcurrentSessions Attributes are missing for + Manager.CommandShell, though Requirement mandates it. + +Fix: Added missing attributes to Manager.CommandShell + +Tested: +1. Verified redfish validator passed +2. Get bmc details from Redfish +Redfish URI: https://<BMC IP>/redfish/v1/Managers/bmc +Response: +{ + "@odata.id": "/redfish/v1/Managers/bmc", + "@odata.type": "#Manager.v1_9_0.Manager", +.... +.... + "CommandShell": { + "ConnectTypesSupported": [ + "SSH", + "IPMI" + ], + "MaxConcurrentSessions": 4, + "ServiceEnabled": true + }, +.... +.... + +Signed-off-by: Jayaprakash Mutyala <mutyalax.jayaprakash@intel.com> +--- + redfish-core/lib/managers.hpp | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp +index 6347caf..c401ca9 100644 +--- a/redfish-core/lib/managers.hpp ++++ b/redfish-core/lib/managers.hpp +@@ -1767,6 +1767,12 @@ class Manager : public Node + res.jsonValue["SerialConsole"]["MaxConcurrentSessions"] = 15; + res.jsonValue["SerialConsole"]["ConnectTypesSupported"] = {"IPMI", + "SSH"}; ++ // Fill in CommandShell info ++ res.jsonValue["CommandShell"]["ServiceEnabled"] = true; ++ res.jsonValue["CommandShell"]["MaxConcurrentSessions"] = 4; ++ res.jsonValue["CommandShell"]["ConnectTypesSupported"] = {"SSH", ++ "IPMI"}; ++ + #ifdef BMCWEB_ENABLE_KVM + // Fill in GraphicalConsole info + res.jsonValue["GraphicalConsole"]["ServiceEnabled"] = true; +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0034-recommended-fixes-by-crypto-review-team.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0034-recommended-fixes-by-crypto-review-team.patch new file mode 100644 index 000000000..f3235c7cf --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0034-recommended-fixes-by-crypto-review-team.patch @@ -0,0 +1,75 @@ +From a170675fafc1ee8bfee502672e65be9ad379a3d1 Mon Sep 17 00:00:00 2001 +From: Radivoje Jovanovic <radivoje.jovanovic@intel.com> +Date: Thu, 10 Dec 2020 13:42:20 -0800 +Subject: [PATCH] recommended fixes by crypto review team + +some curves/cyphers are forbiden to be used by +Intel crypto team. +Only enable approved ones. +the patch was created by aleksandr.v.tereschenko@intel.com + +Signed-off-by: Radivoje Jovanovic <radivoje.jovanovic@intel.com> +--- + include/ssl_key_handler.hpp | 39 ++++++++++++++++++++----------------- + 1 file changed, 21 insertions(+), 18 deletions(-) + +diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp +index deb3a76..8063858 100644 +--- a/include/ssl_key_handler.hpp ++++ b/include/ssl_key_handler.hpp +@@ -326,31 +326,34 @@ inline std::shared_ptr<boost::asio::ssl::context> + mSslContext->use_private_key_file(ssl_pem_file, + 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/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch index 9157f1bf1..b1334a420 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch @@ -1,7 +1,7 @@ -From d8b7e2f4eae85cd76d480970e888a50548523fc2 Mon Sep 17 00:00:00 2001 +From c7fce288802ece4a6e1ff71ee060a44e0b8fe992 Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" <jozef.wludzik@intel.com> Date: Mon, 27 Apr 2020 17:24:15 +0200 -Subject: [PATCH 05/10] Redfish TelemetryService schema implementation +Subject: [PATCH 1/4] Redfish TelemetryService schema implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -9,73 +9,41 @@ Content-Transfer-Encoding: 8bit Added TelemetryService, MetricReports, MetricReportCollection, MetricReportDefinition and MetricReportDefinitionCollection schemas with GET method support. Added TelemetryService URI to root service. -Implemented communication with backend - MonitoringService. -Added schemes attributes that are supported by MonitoringService +Implemented communication with backend - Telemetry. +Added schemes attributes that are supported by Telemetry service design. User is able to fetch basic information about reports if -MonitoringService is present in OpenBMC. +Telemetry service is present in OpenBMC. +Added util function that converts decimal value into duration format +that is described by ISO 8601 and Redfish specification. Tested: - - Succesfully passed RedfishServiceValidator.py - - Validated conversion to duration format using whole - range of uint32_t type - - Validated assigning value to JSON response using different - closures and std::functions types + - Succesfully passed RedfishServiceValidator.py + - Verified DBus method calls to Telemetry service + - Verified all possible pages that are displayed to user when: + - Reports are fully defined in Telemetry + - Reports are partially available in Telemetry + - Telemetry is disabled + - Verified time_utils::toDurationString() output Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> Signed-off-by: Adrian Ambrożewicz <adrian.ambrozewicz@linux.intel.com> +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> Change-Id: Ie6b0b49f4ef5eeaef07d1209b6c349270c04d570 - -%% original patch: 0001-Redfish-TelemetryService-schema-implementation.patch - -Change-Id: I547073faef9228e8dc5350ea28d06cdd3c5341f6 --- - include/dbus_utility.hpp | 21 +++ redfish-core/include/redfish.hpp | 10 ++ - redfish-core/include/utils/json_utils.hpp | 101 +++++++++++++ - redfish-core/include/utils/telemetry_utils.hpp | 100 +++++++++++++ - redfish-core/include/utils/time_utils.hpp | 97 +++++++++++++ - redfish-core/lib/metric_report.hpp | 149 +++++++++++++++++++ - redfish-core/lib/metric_report_definition.hpp | 191 +++++++++++++++++++++++++ + redfish-core/include/utils/telemetry_utils.hpp | 71 ++++++++++ + redfish-core/include/utils/time_utils.hpp | 78 +++++++++++ + redfish-core/lib/metric_report.hpp | 162 +++++++++++++++++++++ + redfish-core/lib/metric_report_definition.hpp | 186 +++++++++++++++++++++++++ redfish-core/lib/service_root.hpp | 2 + - redfish-core/lib/telemetry_service.hpp | 92 ++++++++++++ - 9 files changed, 763 insertions(+) + redfish-core/lib/telemetry_service.hpp | 93 +++++++++++++ + 7 files changed, 602 insertions(+) create mode 100644 redfish-core/include/utils/telemetry_utils.hpp create mode 100644 redfish-core/include/utils/time_utils.hpp create mode 100644 redfish-core/lib/metric_report.hpp create mode 100644 redfish-core/lib/metric_report_definition.hpp create mode 100644 redfish-core/lib/telemetry_service.hpp -diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp -index 8ba9a57..ef3438b 100644 ---- a/include/dbus_utility.hpp -+++ b/include/dbus_utility.hpp -@@ -99,5 +99,26 @@ inline void checkDbusPathExists(const std::string& path, Callback&& callback) - std::array<std::string, 0>()); - } - -+template <typename Array, typename Callback> -+inline void getSubTreePaths(Callback&& callback, const std::string& path, -+ int depth, Array& interfaces) -+{ -+ crow::connections::systemBus->async_method_call( -+ callback, "xyz.openbmc_project.ObjectMapper", -+ "/xyz/openbmc_project/object_mapper", -+ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", path, depth, -+ interfaces); -+} -+ -+template <typename Callback> -+inline void getAllProperties(Callback&& callback, const std::string& service, -+ const std::string& path, -+ const std::string& interface) -+{ -+ crow::connections::systemBus->async_method_call( -+ callback, service, path, "org.freedesktop.DBus.Properties", "GetAll", -+ interface); -+} -+ - } // namespace utility - } // namespace dbus diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp index 54d5d0e..2587b37 100644 --- a/redfish-core/include/redfish.hpp @@ -111,154 +79,12 @@ index 54d5d0e..2587b37 100644 for (const auto& node : nodes) { node->initPrivileges(); -diff --git a/redfish-core/include/utils/json_utils.hpp b/redfish-core/include/utils/json_utils.hpp -index c355000..c866a2f 100644 ---- a/redfish-core/include/utils/json_utils.hpp -+++ b/redfish-core/include/utils/json_utils.hpp -@@ -13,14 +13,18 @@ - // See the License for the specific language governing permissions and - // limitations under the License. - */ -+ - #pragma once - -+#include <boost/container/flat_map.hpp> - #include <error_messages.hpp> - #include <http_request.hpp> - #include <http_response.hpp> - #include <nlohmann/json.hpp> - - #include <bitset> -+#include <string> -+#include <variant> - - namespace redfish - { -@@ -425,5 +429,102 @@ bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key, - return details::unpackValue(jsonValue, key, value); - } - -+template <class T> -+struct IsStdFunction -+{ -+ static constexpr bool value = false; -+}; -+ -+template <class T> -+struct IsStdFunction<std::function<T>> -+{ -+ static constexpr bool value = true; -+}; -+ -+template <class T> -+constexpr bool is_std_function_v = IsStdFunction<T>::value; -+ -+/** -+ * @brief Assign dbus property to http response attribute if property is stored -+ * on the map. -+ */ -+template <typename T, typename S, typename... V> -+bool assignIfPresent( -+ const boost::container::flat_map<std::string, std::variant<V...>>& ret, -+ const char* propertyName, nlohmann::json& attribute, const S& convert) -+{ -+ if constexpr (is_std_function_v<S>) -+ { -+ if (!convert) -+ { -+ BMCWEB_LOG_ERROR << "Passed empty target as convert argument"; -+ return false; -+ } -+ } -+ -+ auto found = ret.find(propertyName); -+ if (found != ret.end()) -+ { -+ auto property = std::get_if<T>(&found->second); -+ if (property) -+ { -+ attribute = convert(*property); -+ return true; -+ } -+ else -+ { -+ BMCWEB_LOG_ERROR << "Variant does not contain this type"; -+ } -+ } -+ else -+ { -+ BMCWEB_LOG_ERROR << "Element not found in map"; -+ } -+ -+ return false; -+} -+ -+template <typename T, typename... V> -+bool assignIfPresent( -+ const boost::container::flat_map<std::string, std::variant<V...>>& ret, -+ const char* propertyName, nlohmann::json& attribute) -+{ -+ return assignIfPresent<T>(ret, propertyName, attribute, -+ [](const T& v) -> T { return v; }); -+} -+ -+template <typename T, typename... V> -+bool assignIfPresent( -+ const boost::container::flat_map<std::string, std::variant<V...>>& ret, -+ const char* attributeName, crow::Response& res) -+{ -+ return assignIfPresent<T>(ret, attributeName, res.jsonValue[attributeName]); -+} -+ -+/** -+ * @brief Translate dbusPaths received from ObjectMapper into Redfish -+ * collection members and fill http response with those information. -+ */ -+inline void dbusPathsToMembersArray(crow::Response& res, -+ const std::vector<std::string>& reports, -+ const char* path) -+{ -+ nlohmann::json& members = res.jsonValue["Members"]; -+ members = nlohmann::json::array(); -+ -+ for (const std::string& objpath : reports) -+ { -+ std::size_t lastPos = objpath.rfind("/"); -+ if (lastPos == std::string::npos) -+ { -+ BMCWEB_LOG_ERROR << "Failed to find '/' in " << objpath; -+ continue; -+ } -+ members.push_back({{"@odata.id", path + objpath.substr(lastPos + 1)}}); -+ } -+ -+ res.jsonValue["Members@odata.count"] = members.size(); -+} -+ - } // namespace json_util - } // namespace redfish diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp new file mode 100644 -index 0000000..05ed00f +index 0000000..8caee2d --- /dev/null +++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -0,0 +1,100 @@ -+/* -+// Copyright (c) 2018-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. -+*/ -+ +@@ -0,0 +1,71 @@ +#pragma once + +namespace redfish @@ -267,112 +93,79 @@ index 0000000..05ed00f +namespace telemetry +{ + -+static constexpr const char* metricReportDefinitionUri = ++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/"; -+static constexpr const char* metricReportUri = ++constexpr const char* metricReportUri = + "/redfish/v1/TelemetryService/MetricReports/"; -+static constexpr const char* reportInterface = -+ "xyz.openbmc_project.MonitoringService.Report"; -+static constexpr const char* telemetryPath = -+ "/xyz/openbmc_project/MonitoringService/Reports/TelemetryService"; + -+static void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, -+ const char* uri) ++inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::string& uri) +{ + const std::array<const char*, 1> interfaces = {reportInterface}; + -+ dbus::utility::getSubTreePaths( ++ crow::connections::systemBus->async_method_call( + [asyncResp, uri](const boost::system::error_code ec, -+ const std::vector<std::string>& reports) { -+ if (ec == boost::system::errc::no_such_file_or_directory) ++ const std::vector<std::string>& reportPaths) { ++ if (ec) + { + asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); + asyncResp->res.jsonValue["Members@odata.count"] = 0; + return; + } + -+ if (ec) ++ nlohmann::json& members = asyncResp->res.jsonValue["Members"]; ++ members = nlohmann::json::array(); ++ ++ for (const std::string& path : reportPaths) + { -+ messages::internalError(asyncResp->res); -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ return; ++ std::size_t pos = path.rfind('/'); ++ if (pos == std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find '/' in " << path; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ if (path.size() <= (pos + 1)) ++ { ++ BMCWEB_LOG_ERROR << "Failed to parse path " << path; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ members.push_back({{"@odata.id", uri + path.substr(pos + 1)}}); + } + -+ json_util::dbusPathsToMembersArray(asyncResp->res, reports, uri); ++ asyncResp->res.jsonValue["Members@odata.count"] = members.size(); + }, -+ telemetryPath, 1, interfaces); ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService", 1, ++ interfaces); +} + -+template <typename Callback> -+static void getReport(const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::string& id, const char* schemaType, -+ const Callback&& callback) ++inline std::string getDbusReportPath(const std::string& id) +{ -+ const std::array<const char*, 1> interfaces = {reportInterface}; -+ -+ dbus::utility::getSubTreePaths( -+ [asyncResp, id, schemaType, -+ callback](const boost::system::error_code ec, -+ const std::vector<std::string>& reports) { -+ if (ec == boost::system::errc::no_such_file_or_directory) -+ { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); -+ return; -+ } -+ -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ return; -+ } -+ -+ const std::string target = "/xyz/openbmc_project/" -+ "MonitoringService/Reports/" -+ "TelemetryService/" + -+ id; -+ auto path = std::find(reports.begin(), reports.end(), target); -+ if (path == std::end(reports)) -+ { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); -+ return; -+ } -+ callback(asyncResp, *path, id); -+ }, -+ telemetryPath, 1, interfaces); ++ std::string path = ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id; ++ dbus::utility::escapePathForDbus(path); ++ return path; +} ++ +} // namespace telemetry +} // namespace redfish diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp new file mode 100644 -index 0000000..0256b3f +index 0000000..dd4ea75 --- /dev/null +++ b/redfish-core/include/utils/time_utils.hpp -@@ -0,0 +1,97 @@ -+/* -+// 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. -+*/ -+ +@@ -0,0 +1,78 @@ +#pragma once + -+#include <boost/algorithm/string/trim.hpp> -+ +#include <chrono> -+#include <cstdint> +#include <string> -+#include <type_traits> + +namespace redfish +{ @@ -383,63 +176,64 @@ index 0000000..0256b3f +namespace details +{ + -+template <typename T> -+std::string toDurationFormatItem(std::chrono::milliseconds& duration, -+ const char* postfix) ++inline void leftZeroPadding(std::string& str, const std::size_t padding) +{ -+ const auto t = std::chrono::duration_cast<T>(duration); -+ if (t.count() == 0) -+ { -+ return ""; -+ } -+ -+ std::stringstream ss; -+ if constexpr (std::is_same<T, std::chrono::milliseconds>::value) ++ if (str.size() < padding) + { -+ ss << static_cast<float>(t.count()) / -+ static_cast<float>(std::chrono::milliseconds::period::den); ++ str.insert(0, padding - str.size(), '0'); + } -+ else -+ { -+ ss << t.count(); -+ } -+ ss << postfix; -+ duration -= t; -+ return ss.str(); +} -+ +} // namespace details + +/** + * @brief Convert time value into duration format that is based on ISO 8601. -+ * Pattern: "-?P(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+(.\\d+)?S)?)?" -+ * Reference: "Redfish Telemetry White Paper". ++ * Example output: "P12DT1M5.5S" ++ * Ref: Redfish Specification, Section 9.4.4. Duration values + */ -+std::string toDurationFormat(const uint32_t ms) ++std::string toDurationString(std::chrono::milliseconds ms) +{ -+ std::chrono::milliseconds duration(ms); -+ if (duration.count() == 0) ++ if (ms < std::chrono::milliseconds::zero()) + { -+ return "PT0S"; ++ return ""; + } + + std::string fmt; -+ fmt.reserve(sizeof("PxxxDTxxHxxMxx.xxxxxxS")); ++ fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); ++ ++ using Days = std::chrono::duration<long, std::ratio<24 * 60 * 60>>; ++ Days days = std::chrono::floor<Days>(ms); ++ ms -= days; ++ ++ std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); ++ ms -= hours; ++ ++ std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms); ++ ms -= minutes; + -+ using Days = std::chrono::duration<int, std::ratio<24 * 60 * 60>>; ++ std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms); ++ ms -= seconds; + -+ fmt += "P"; -+ fmt += details::toDurationFormatItem<Days>(duration, "D"); -+ if (duration.count() == 0) ++ fmt = "P"; ++ if (days.count() > 0) + { -+ return fmt; ++ fmt += std::to_string(days.count()) + "D"; + } -+ + fmt += "T"; -+ fmt += details::toDurationFormatItem<std::chrono::hours>(duration, "H"); -+ fmt += details::toDurationFormatItem<std::chrono::minutes>(duration, "M"); -+ fmt += -+ details::toDurationFormatItem<std::chrono::milliseconds>(duration, "S"); ++ if (hours.count() > 0) ++ { ++ fmt += std::to_string(hours.count()) + "H"; ++ } ++ if (minutes.count() > 0) ++ { ++ fmt += std::to_string(minutes.count()) + "M"; ++ } ++ if (seconds.count() != 0 || ms.count() != 0) ++ { ++ fmt += std::to_string(seconds.count()) + "."; ++ std::string msStr = std::to_string(ms.count()); ++ details::leftZeroPadding(msStr, 3); ++ fmt += msStr + "S"; ++ } + + return fmt; +} @@ -448,43 +242,23 @@ index 0000000..0256b3f +} // namespace redfish diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp new file mode 100644 -index 0000000..4d1c4e5 +index 0000000..050304c --- /dev/null +++ b/redfish-core/lib/metric_report.hpp -@@ -0,0 +1,149 @@ -+/* -+// Copyright (c) 2018-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. -+*/ -+ +@@ -0,0 +1,162 @@ +#pragma once + +#include "node.hpp" +#include "utils/telemetry_utils.hpp" + -+#include <boost/container/flat_map.hpp> -+ -+#include <system_error> -+#include <variant> -+ +namespace redfish +{ + +class MetricReportCollection : public Node +{ + public: -+ MetricReportCollection(App& app) : Node(app, telemetry::metricReportUri) ++ MetricReportCollection(App& app) : ++ Node(app, "/redfish/v1/TelemetryService/MetricReports/") + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, @@ -514,7 +288,7 @@ index 0000000..4d1c4e5 +{ + public: + MetricReport(App& app) : -+ Node(app, std::string(telemetry::metricReportUri) + "<str>/", ++ Node(app, "/redfish/v1/TelemetryService/MetricReports/<str>/", + std::string()) + { + entityPrivileges = { @@ -539,33 +313,74 @@ index 0000000..4d1c4e5 + } + + const std::string& id = params[0]; -+ telemetry::getReport(asyncResp, id, schemaType, getReportProperties); ++ const std::string reportPath = telemetry::getDbusReportPath(id); ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, id, reportPath](const boost::system::error_code& ec) { ++ if (ec.value() == EBADR) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, ++ id](const boost::system::error_code ec, ++ const std::variant<TimestampReadings>& ret) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ fillReport(asyncResp, id, ret); ++ }, ++ telemetry::service, reportPath, ++ "org.freedesktop.DBus.Properties", "Get", ++ telemetry::reportInterface, "Readings"); ++ }, ++ telemetry::service, reportPath, telemetry::reportInterface, ++ "Update"); + } + + using Readings = -+ std::vector<std::tuple<std::string, std::string, double, int32_t>>; -+ using MetricValues = std::vector<std::map<std::string, std::string>>; ++ std::vector<std::tuple<std::string, std::string, double, uint64_t>>; ++ using TimestampReadings = std::tuple<uint64_t, Readings>; + -+ static MetricValues toMetricValues(const Readings& readings) ++ static nlohmann::json toMetricValues(const Readings& readings) + { -+ MetricValues metricValues; ++ nlohmann::json metricValues = nlohmann::json::array_t(); + + for (auto& [id, metadata, sensorValue, timestamp] : readings) + { ++ nlohmann::json metadataJson = nlohmann::json::parse(metadata); + metricValues.push_back({ + {"MetricId", id}, -+ {"MetricProperty", metadata}, ++ {"MetricDefinition", metadataJson.contains("MetricDefinition") ++ ? metadataJson["MetricDefinition"] ++ : nlohmann::json()}, ++ {"MetricProperty", metadataJson.contains("MetricProperty") ++ ? metadataJson["MetricProperty"] ++ : nlohmann::json()}, + {"MetricValue", std::to_string(sensorValue)}, -+ {"Timestamp", crow::utility::getDateTime(timestamp)}, ++ {"Timestamp", ++ crow::utility::getDateTime(static_cast<time_t>(timestamp))}, + }); + } + + return metricValues; + } + -+ static void getReportProperties(const std::shared_ptr<AsyncResp> asyncResp, -+ const std::string& reportPath, -+ const std::string& id) ++ static void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::string& id, ++ const std::variant<TimestampReadings>& var) + { + asyncResp->res.jsonValue["@odata.type"] = schemaType; + asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + id; @@ -574,27 +389,19 @@ index 0000000..4d1c4e5 + asyncResp->res.jsonValue["MetricReportDefinition"]["@odata.id"] = + telemetry::metricReportDefinitionUri + id; + -+ dbus::utility::getAllProperties( -+ [asyncResp]( -+ const boost::system::error_code ec, -+ const boost::container::flat_map< -+ std::string, std::variant<Readings, int32_t>>& ret) { -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ return; -+ } ++ const TimestampReadings* timestampReadings = ++ std::get_if<TimestampReadings>(&var); ++ if (!timestampReadings) ++ { ++ BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; ++ messages::internalError(asyncResp->res); ++ return; ++ } + -+ json_util::assignIfPresent<int32_t>( -+ ret, "Timestamp", asyncResp->res.jsonValue["Timestamp"], -+ crow::utility::getDateTime); -+ json_util::assignIfPresent<Readings>( -+ ret, "Readings", asyncResp->res.jsonValue["MetricValues"], -+ toMetricValues); -+ }, -+ "xyz.openbmc_project.MonitoringService", reportPath, -+ "xyz.openbmc_project.MonitoringService.Report"); ++ const auto& [timestamp, readings] = *timestampReadings; ++ asyncResp->res.jsonValue["Timestamp"] = ++ crow::utility::getDateTime(static_cast<time_t>(timestamp)); ++ asyncResp->res.jsonValue["MetricValues"] = toMetricValues(readings); + } + + static constexpr const char* schemaType = @@ -603,35 +410,17 @@ index 0000000..4d1c4e5 +} // namespace redfish diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp new file mode 100644 -index 0000000..72e62e9 +index 0000000..48c56e6 --- /dev/null +++ b/redfish-core/lib/metric_report_definition.hpp -@@ -0,0 +1,191 @@ -+/* -+// Copyright (c) 2018-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. -+*/ -+ +@@ -0,0 +1,186 @@ +#pragma once + +#include "node.hpp" +#include "utils/telemetry_utils.hpp" +#include "utils/time_utils.hpp" + -+#include <boost/container/flat_map.hpp> -+ -+#include <system_error> ++#include <tuple> +#include <variant> + +namespace redfish @@ -641,7 +430,7 @@ index 0000000..72e62e9 +{ + public: + MetricReportDefinitionCollection(App& app) : -+ Node(app, telemetry::metricReportDefinitionUri) ++ Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, @@ -672,7 +461,7 @@ index 0000000..72e62e9 +{ + public: + MetricReportDefinition(App& app) : -+ Node(app, std::string(telemetry::metricReportDefinitionUri) + "<str>/", ++ Node(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/", + std::string()) + { + entityPrivileges = { @@ -697,55 +486,40 @@ index 0000000..72e62e9 + } + + const std::string& id = params[0]; ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, ++ id](const boost::system::error_code ec, ++ const std::vector<std::pair< ++ std::string, std::variant<bool, ReadingParameters, ++ std::string, uint64_t>>>& ret) { ++ if (ec.value() == EBADR) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } + -+ telemetry::getReport(asyncResp, id, schemaType, -+ getReportDefinitonProperties); -+ } -+ -+ static std::vector<std::string> -+ toReportActions(const std::vector<std::string>& actions) -+ { -+ const boost::container::flat_map<std::string, std::string> -+ reportActions = { -+ {"Event", "RedfishEvent"}, -+ {"Log", "LogToMetricReportsCollection"}, -+ }; -+ -+ std::vector<std::string> out; -+ for (auto& action : actions) -+ { -+ auto found = reportActions.find(action); -+ if (found != reportActions.end()) -+ { -+ out.emplace_back(found->second); -+ } -+ } -+ return out; ++ fillReportDefinition(asyncResp, id, ret); ++ }, ++ telemetry::service, telemetry::getDbusReportPath(id), ++ "org.freedesktop.DBus.Properties", "GetAll", ++ telemetry::reportInterface); + } + + using ReadingParameters = + std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, + std::string, std::string, std::string>>; + -+ static nlohmann::json toMetrics(const ReadingParameters& params) -+ { -+ nlohmann::json metrics = nlohmann::json::array(); -+ -+ for (auto& [sensorPaths, operationType, id, metadata] : params) -+ { -+ metrics.push_back({ -+ {"MetricId", id}, -+ {"MetricProperties", std::vector<std::string>() = {metadata}}, -+ }); -+ } -+ -+ return metrics; -+ } -+ -+ static void -+ getReportDefinitonProperties(const std::shared_ptr<AsyncResp> asyncResp, -+ const std::string& reportPath, -+ const std::string& id) ++ static void fillReportDefinition( ++ const std::shared_ptr<AsyncResp>& asyncResp, const std::string& id, ++ const std::vector< ++ std::pair<std::string, std::variant<bool, ReadingParameters, ++ std::string, uint64_t>>>& ret) + { + asyncResp->res.jsonValue["@odata.type"] = schemaType; + asyncResp->res.jsonValue["@odata.id"] = @@ -755,45 +529,73 @@ index 0000000..72e62e9 + asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = + telemetry::metricReportUri + id; + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; ++ asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; ++ ++ const bool* emitsReadingsUpdate = nullptr; ++ const bool* logToMetricReportsCollection = nullptr; ++ const ReadingParameters* readingParams = nullptr; ++ const std::string* reportingType = nullptr; ++ const uint64_t* interval = nullptr; ++ for (const auto& [key, var] : ret) ++ { ++ if (key == "EmitsReadingsUpdate") ++ { ++ emitsReadingsUpdate = std::get_if<bool>(&var); ++ } ++ else if (key == "LogToMetricReportsCollection") ++ { ++ logToMetricReportsCollection = std::get_if<bool>(&var); ++ } ++ else if (key == "ReadingParameters") ++ { ++ readingParams = std::get_if<ReadingParameters>(&var); ++ } ++ else if (key == "ReportingType") ++ { ++ reportingType = std::get_if<std::string>(&var); ++ } ++ else if (key == "Interval") ++ { ++ interval = std::get_if<uint64_t>(&var); ++ } ++ } ++ if (!emitsReadingsUpdate || !logToMetricReportsCollection || ++ !readingParams || !reportingType || !interval) ++ { ++ BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; ++ messages::internalError(asyncResp->res); ++ return; ++ } + -+ dbus::utility::getAllProperties( -+ [asyncResp](const boost::system::error_code ec, -+ const boost::container::flat_map< -+ std::string, -+ std::variant<std::string, std::vector<std::string>, -+ uint32_t, ReadingParameters>>& ret) { -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ return; -+ } ++ std::vector<std::string> redfishReportActions; ++ redfishReportActions.reserve(2); ++ if (*emitsReadingsUpdate) ++ { ++ redfishReportActions.emplace_back("RedfishEvent"); ++ } ++ if (*logToMetricReportsCollection) ++ { ++ redfishReportActions.emplace_back("LogToMetricReportsCollection"); ++ } + -+ json_util::assignIfPresent<std::vector<std::string>>( -+ ret, "ReportAction", -+ asyncResp->res.jsonValue["ReportActions"], toReportActions); -+ auto assigned = json_util::assignIfPresent<std::string>( -+ ret, "ReportingType", -+ asyncResp->res.jsonValue["MetricReportDefinitionType"]); -+ if (assigned && -+ asyncResp->res.jsonValue["MetricReportDefinitionType"] == -+ "Periodic") -+ { -+ json_util::assignIfPresent<uint32_t>( -+ ret, "ScanPeriod", -+ asyncResp->res -+ .jsonValue["Schedule"]["RecurrenceInterval"], -+ time_utils::toDurationFormat); -+ } -+ json_util::assignIfPresent<ReadingParameters>( -+ ret, "ReadingParameters", -+ asyncResp->res.jsonValue["Metrics"], toMetrics); -+ }, -+ "xyz.openbmc_project.MonitoringService", reportPath, -+ "xyz.openbmc_project.MonitoringService.Report"); ++ nlohmann::json metrics = nlohmann::json::array(); ++ for (auto& [sensorPaths, operationType, id, metadata] : *readingParams) ++ { ++ nlohmann::json metadataJson = nlohmann::json::parse(metadata); ++ metrics.push_back({ ++ {"MetricId", id}, ++ {"MetricProperties", metadataJson.contains("MetricProperties") ++ ? metadataJson["MetricProperties"] ++ : nlohmann::json()}, ++ }); ++ } ++ 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)); + } + -+ public: + static constexpr const char* schemaType = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; +}; @@ -813,32 +615,14 @@ index 629280c..3df5ec5 100644 diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp new file mode 100644 -index 0000000..b849781 +index 0000000..a6acc34 --- /dev/null +++ b/redfish-core/lib/telemetry_service.hpp -@@ -0,0 +1,92 @@ -+/* -+// Copyright (c) 2018-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. -+*/ -+ +@@ -0,0 +1,93 @@ +#pragma once + +#include "node.hpp" -+#include "utils/time_utils.hpp" -+ -+#include <boost/container/flat_map.hpp> ++#include "utils/telemetry_utils.hpp" + +#include <variant> + @@ -864,7 +648,7 @@ index 0000000..b849781 + const std::vector<std::string>&) override + { + res.jsonValue["@odata.type"] = -+ "#TelemetryService.v1_2_0.TelemetryService"; ++ "#TelemetryService.v1_2_1.TelemetryService"; + res.jsonValue["@odata.id"] = "/redfish/v1/TelemetryService"; + res.jsonValue["Id"] = "TelemetryService"; + res.jsonValue["Name"] = "Telemetry Service"; @@ -876,36 +660,55 @@ index 0000000..b849781 + res.jsonValue["MetricReports"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricReports"; + -+ getMonitoringServiceProperties(res); -+ } -+ -+ void getMonitoringServiceProperties(crow::Response& res) -+ { + auto asyncResp = std::make_shared<AsyncResp>(res); -+ dbus::utility::getAllProperties( ++ crow::connections::systemBus->async_method_call( + [asyncResp]( + const boost::system::error_code ec, -+ const boost::container::flat_map<std::string, -+ std::variant<uint32_t>>& ret) { -+ if (ec) ++ const std::vector<std::pair< ++ std::string, std::variant<uint32_t, uint64_t>>>& ret) { ++ if (ec == boost::system::errc::host_unreachable) + { + asyncResp->res.jsonValue["Status"]["State"] = "Absent"; ++ return; ++ } ++ if (ec) ++ { + BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(asyncResp->res); + return; + } + + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; + -+ json_util::assignIfPresent<uint32_t>(ret, "MaxReports", -+ asyncResp->res); -+ json_util::assignIfPresent<uint32_t>( -+ ret, "PollRateResolution", -+ asyncResp->res.jsonValue["MinCollectionInterval"], -+ time_utils::toDurationFormat); ++ const size_t* maxReports = nullptr; ++ const uint64_t* minInterval = nullptr; ++ for (const auto& [key, var] : ret) ++ { ++ if (key == "MaxReports") ++ { ++ maxReports = std::get_if<size_t>(&var); ++ } ++ else if (key == "MinInterval") ++ { ++ minInterval = std::get_if<uint64_t>(&var); ++ } ++ } ++ if (!maxReports || !minInterval) ++ { ++ BMCWEB_LOG_ERROR ++ << "Property type mismatch or property is missing"; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ asyncResp->res.jsonValue["MaxReports"] = *maxReports; ++ asyncResp->res.jsonValue["MinCollectionInterval"] = ++ time_utils::toDurationString(std::chrono::milliseconds( ++ static_cast<time_t>(*minInterval))); + }, -+ "xyz.openbmc_project.MonitoringService", -+ "/xyz/openbmc_project/MonitoringService/Reports", -+ "xyz.openbmc_project.MonitoringService.ReportsManagement"); ++ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", ++ "org.freedesktop.DBus.Properties", "GetAll", ++ "xyz.openbmc_project.Telemetry.ReportManager"); + } +}; +} // namespace redfish diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch new file mode 100644 index 000000000..b04a72c9f --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch @@ -0,0 +1,683 @@ +From 0784af276b72e5df9c545d83bc989833ac2935c4 Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" <jozef.wludzik@intel.com> +Date: Mon, 18 May 2020 11:56:57 +0200 +Subject: [PATCH 2/4] Add POST and DELETE in MetricReportDefinitions + +Added POST action in MetricReportDefinitions node to allow user +to add new MetricReportDefinition. Using minimal set of +MetricReportDefinition parameters from user bmcweb converts it to +DBus call "AddReport" to Telemetry that serves as a backend +for Redfish TelemetryService. +Added DELETE request in MetricReportDefinitions node to allow user +to remove report from Telemetry. +Added conversion from string that represents duration format into +its numeric equivalent. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Validated good cases with different parameters for POST action + - Validated bad cases with different parameters for POST action + - Verified time_utils::fromDurationString() + - Verified that reports are removed on DELETE request + +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a +--- + redfish-core/include/utils/telemetry_utils.hpp | 5 +- + redfish-core/include/utils/time_utils.hpp | 145 +++++++++- + redfish-core/lib/metric_report_definition.hpp | 382 ++++++++++++++++++++++++- + 3 files changed, 516 insertions(+), 16 deletions(-) + +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index 8caee2d..acb739d 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -12,6 +12,8 @@ constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions/"; + constexpr const char* metricReportUri = + "/redfish/v1/TelemetryService/MetricReports/"; ++constexpr const char* reportDir = ++ "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/"; + + inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, + const std::string& uri) +@@ -61,8 +63,7 @@ inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, + + inline std::string getDbusReportPath(const std::string& id) + { +- std::string path = +- "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/" + id; ++ std::string path = reportDir + id; + dbus::utility::escapePathForDbus(path); + return path; + } +diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp +index dd4ea75..d8985ab 100644 +--- a/redfish-core/include/utils/time_utils.hpp ++++ b/redfish-core/include/utils/time_utils.hpp +@@ -1,7 +1,12 @@ + #pragma once + ++#include "logging.hpp" ++ ++#include <charconv> + #include <chrono> ++#include <optional> + #include <string> ++#include <system_error> + + namespace redfish + { +@@ -12,6 +17,8 @@ namespace time_utils + namespace details + { + ++using Days = std::chrono::duration<long long, std::ratio<24 * 60 * 60>>; ++ + inline void leftZeroPadding(std::string& str, const std::size_t padding) + { + if (str.size() < padding) +@@ -19,8 +26,143 @@ inline void leftZeroPadding(std::string& str, const std::size_t padding) + str.insert(0, padding - str.size(), '0'); + } + } ++ ++inline bool fromChars(const char* start, const char* end, ++ std::chrono::milliseconds::rep& val) ++{ ++ auto [ptr, ec] = std::from_chars(start, end, val); ++ if (ptr != end) ++ { ++ BMCWEB_LOG_ERROR ++ << "Failed to convert string to decimal because of unexpected sign"; ++ return false; ++ } ++ if (ec != std::errc()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " ++ << static_cast<int>(ec) << "(" ++ << std::make_error_code(ec).message() << ")"; ++ return false; ++ } ++ return true; ++} ++ ++template <typename T> ++bool fromDurationItem(std::string_view& fmt, const char postfix, ++ std::chrono::milliseconds& out) ++{ ++ const size_t pos = fmt.find(postfix); ++ if (pos == std::string::npos) ++ { ++ return true; ++ } ++ if ((pos + 1U) > fmt.size()) ++ { ++ return false; ++ } ++ ++ std::chrono::milliseconds::rep v = 0; ++ if constexpr (std::is_same_v<T, std::chrono::milliseconds>) ++ { ++ std::string str(fmt.data(), std::min<size_t>(pos, 3U)); ++ while (str.size() < 3U) ++ { ++ str += '0'; ++ } ++ if (!fromChars(str.data(), str.data() + str.size(), v)) ++ { ++ return false; ++ } ++ } ++ else ++ { ++ if (!fromChars(fmt.data(), fmt.data() + pos, v)) ++ { ++ return false; ++ } ++ } ++ ++ out += T(v); ++ if (out < T(v) || ++ std::chrono::duration_cast<T>(std::chrono::milliseconds::max()) ++ .count() < v) ++ { ++ return false; ++ } ++ ++ fmt.remove_prefix(pos + 1U); ++ return true; ++} + } // namespace details + ++/** ++ * @brief Convert string that represents value in Duration Format to its numeric ++ * equivalent. ++ */ ++std::optional<std::chrono::milliseconds> ++ fromDurationString(const std::string& str) ++{ ++ std::chrono::milliseconds out = std::chrono::milliseconds::zero(); ++ std::string_view v = str; ++ ++ if (v.empty()) ++ { ++ return out; ++ } ++ if (v.front() != 'P') ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ v.remove_prefix(1); ++ if (!details::fromDurationItem<details::Days>(v, 'D', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ if (v.empty()) ++ { ++ return out; ++ } ++ if (v.front() != 'T') ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ v.remove_prefix(1); ++ if (!details::fromDurationItem<std::chrono::hours>(v, 'H', out) || ++ !details::fromDurationItem<std::chrono::minutes>(v, 'M', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ if (v.find('.') != std::string::npos && v.find('S') != std::string::npos) ++ { ++ if (!details::fromDurationItem<std::chrono::seconds>(v, '.', out) || ++ !details::fromDurationItem<std::chrono::milliseconds>(v, 'S', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ } ++ else if (!details::fromDurationItem<std::chrono::seconds>(v, 'S', out)) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ ++ if (!v.empty()) ++ { ++ BMCWEB_LOG_ERROR << "Invalid duration format: " << str; ++ return std::nullopt; ++ } ++ return out; ++} ++ + /** + * @brief Convert time value into duration format that is based on ISO 8601. + * Example output: "P12DT1M5.5S" +@@ -36,8 +178,7 @@ std::string toDurationString(std::chrono::milliseconds ms) + std::string fmt; + fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); + +- using Days = std::chrono::duration<long, std::ratio<24 * 60 * 60>>; +- Days days = std::chrono::floor<Days>(ms); ++ details::Days days = std::chrono::floor<details::Days>(ms); + ms -= days; + + std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index 48c56e6..d5a540d 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -1,15 +1,26 @@ + #pragma once + + #include "node.hpp" ++#include "sensors.hpp" + #include "utils/telemetry_utils.hpp" + #include "utils/time_utils.hpp" + ++#include <boost/container/flat_map.hpp> ++ + #include <tuple> + #include <variant> + + namespace redfish + { + ++namespace telemetry ++{ ++ ++using ReadingParameters = ++ std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, ++ std::string, std::string, std::string>>; ++} // namespace telemetry ++ + class MetricReportDefinitionCollection : public Node + { + public: +@@ -39,6 +50,318 @@ class MetricReportDefinitionCollection : public Node + telemetry::getReportCollection(asyncResp, + telemetry::metricReportDefinitionUri); + } ++ ++ struct AddReportArgs ++ { ++ std::string name; ++ std::string reportingType; ++ bool emitsReadingsUpdate = false; ++ bool logToMetricReportsCollection = false; ++ uint64_t interval = 0; ++ std::vector<std::pair<std::string, std::vector<std::string>>> metrics; ++ }; ++ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector<std::string>&) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ AddReportArgs args; ++ if (!getUserParameters(res, req, args)) ++ { ++ return; ++ } ++ ++ boost::container::flat_set<std::pair<std::string, std::string>> ++ chassisSensors; ++ if (!getChassisSensorNode(asyncResp, args.metrics, chassisSensors)) ++ { ++ return; ++ } ++ ++ auto addReportReq = ++ std::make_shared<AddReport>(std::move(args), asyncResp); ++ for (const auto& [chassis, sensorType] : chassisSensors) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, addReportReq]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map<std::string, std::string>& ++ uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ addReportReq->insert(uriToDbus); ++ }); ++ } ++ } ++ ++ static bool toDbusReportActions(crow::Response& res, ++ std::vector<std::string>& actions, ++ AddReportArgs& args) ++ { ++ size_t index = 0; ++ for (auto& action : actions) ++ { ++ if (action == "RedfishEvent") ++ { ++ args.emitsReadingsUpdate = true; ++ } ++ else if (action == "LogToMetricReportsCollection") ++ { ++ args.logToMetricReportsCollection = true; ++ } ++ else ++ { ++ messages::propertyValueNotInList( ++ res, action, "ReportActions/" + std::to_string(index)); ++ return false; ++ } ++ index++; ++ } ++ return true; ++ } ++ ++ static bool getUserParameters(crow::Response& res, const crow::Request& req, ++ AddReportArgs& args) ++ { ++ std::vector<nlohmann::json> metrics; ++ std::vector<std::string> reportActions; ++ std::optional<nlohmann::json> schedule; ++ if (!json_util::readJson(req, res, "Id", args.name, "Metrics", metrics, ++ "MetricReportDefinitionType", ++ args.reportingType, "ReportActions", ++ reportActions, "Schedule", schedule)) ++ { ++ return false; ++ } ++ ++ constexpr const char* allowedCharactersInName = ++ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; ++ if (args.name.empty() || ++ args.name.find_first_not_of(allowedCharactersInName) != ++ std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to match " << args.name ++ << " with allowed character " ++ << allowedCharactersInName; ++ messages::propertyValueIncorrect(res, "Id", args.name); ++ return false; ++ } ++ ++ if (args.reportingType != "Periodic" && ++ args.reportingType != "OnRequest") ++ { ++ messages::propertyValueNotInList(res, args.reportingType, ++ "MetricReportDefinitionType"); ++ return false; ++ } ++ ++ if (!toDbusReportActions(res, reportActions, args)) ++ { ++ return false; ++ } ++ ++ if (args.reportingType == "Periodic") ++ { ++ if (!schedule) ++ { ++ messages::createFailedMissingReqProperties(res, "Schedule"); ++ return false; ++ } ++ ++ std::string durationStr; ++ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", ++ durationStr)) ++ { ++ return false; ++ } ++ ++ std::optional<std::chrono::milliseconds> durationNum = ++ time_utils::fromDurationString(durationStr); ++ if (!durationNum) ++ { ++ messages::propertyValueIncorrect(res, "RecurrenceInterval", ++ durationStr); ++ return false; ++ } ++ args.interval = static_cast<uint64_t>(durationNum->count()); ++ } ++ ++ args.metrics.reserve(metrics.size()); ++ for (auto& m : metrics) ++ { ++ std::string id; ++ std::vector<std::string> uris; ++ if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties", ++ uris)) ++ { ++ return false; ++ } ++ ++ args.metrics.emplace_back(std::move(id), std::move(uris)); ++ } ++ ++ return true; ++ } ++ ++ static bool getChassisSensorNode( ++ const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::vector<std::pair<std::string, std::vector<std::string>>>& ++ metrics, ++ boost::container::flat_set<std::pair<std::string, std::string>>& ++ matched) ++ { ++ for (const auto& [id, uris] : metrics) ++ { ++ for (size_t i = 0; i < uris.size(); i++) ++ { ++ const std::string& uri = uris[i]; ++ std::string chassis; ++ std::string node; ++ ++ if (!boost::starts_with(uri, "/redfish/v1/Chassis/") || ++ !dbus::utility::getNthStringFromPath(uri, 3, chassis) || ++ !dbus::utility::getNthStringFromPath(uri, 4, node)) ++ { ++ BMCWEB_LOG_ERROR << "Failed to get chassis and sensor Node " ++ "from " ++ << uri; ++ messages::propertyValueIncorrect(asyncResp->res, uri, ++ "MetricProperties/" + ++ std::to_string(i)); ++ return false; ++ } ++ ++ if (boost::ends_with(node, "#")) ++ { ++ node.pop_back(); ++ } ++ ++ matched.emplace(std::move(chassis), std::move(node)); ++ } ++ } ++ return true; ++ } ++ ++ class AddReport ++ { ++ public: ++ AddReport(AddReportArgs argsIn, std::shared_ptr<AsyncResp> asyncResp) : ++ asyncResp{std::move(asyncResp)}, args{std::move(argsIn)} ++ {} ++ ~AddReport() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ ++ telemetry::ReadingParameters readingParams; ++ readingParams.reserve(args.metrics.size()); ++ ++ for (const auto& [id, uris] : args.metrics) ++ { ++ std::vector<sdbusplus::message::object_path> dbusPaths; ++ dbusPaths.reserve(uris.size()); ++ ++ for (size_t i = 0; i < uris.size(); i++) ++ { ++ const std::string& uri = uris[i]; ++ auto el = uriToDbus.find(uri); ++ if (el == uriToDbus.end()) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " ++ "corresponding to URI " ++ << uri; ++ messages::propertyValueNotInList(asyncResp->res, uri, ++ "MetricProperties/" + ++ std::to_string(i)); ++ return; ++ } ++ ++ dbusPaths.emplace_back(el->second); ++ } ++ ++ nlohmann::json metadata; ++ metadata["MetricProperties"] = uris; ++ if (uris.size() == 1) ++ { ++ metadata["MetricProperty"] = uris[0]; ++ } ++ readingParams.emplace_back(std::move(dbusPaths), "SINGLE", id, ++ metadata.dump()); ++ } ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp = asyncResp, name = args.name]( ++ const boost::system::error_code ec, const std::string&) { ++ if (ec == boost::system::errc::file_exists) ++ { ++ messages::resourceAlreadyExists( ++ asyncResp->res, "MetricReportDefinition", "Id", ++ name); ++ return; ++ } ++ if (ec == boost::system::errc::too_many_files_open) ++ { ++ messages::createLimitReachedForResource(asyncResp->res); ++ return; ++ } ++ if (ec == boost::system::errc::argument_list_too_long) ++ { ++ messages::propertyValueNotInList( ++ asyncResp->res, "/Exceeds supported size/", ++ "Metrics"); ++ return; ++ } ++ if (ec == boost::system::errc::not_supported) ++ { ++ messages::propertyValueNotInList( ++ asyncResp->res, ++ "/Only single property per metric is supported/", ++ "MetricProperties"); ++ return; ++ } ++ if (ec == boost::system::errc::invalid_argument) ++ { ++ messages::propertyValueNotInList( ++ asyncResp->res, "/Less then MinInterval/", ++ "RecurrenceInterval"); ++ return; ++ } ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ messages::created(asyncResp->res); ++ }, ++ telemetry::service, "/xyz/openbmc_project/Telemetry/Reports", ++ "xyz.openbmc_project.Telemetry.ReportManager", "AddReport", ++ "TelemetryService/" + args.name, args.reportingType, ++ args.emitsReadingsUpdate, args.logToMetricReportsCollection, ++ args.interval, readingParams); ++ } ++ ++ void insert( ++ const boost::container::flat_map<std::string, std::string>& el) ++ { ++ uriToDbus.insert(el.begin(), el.end()); ++ } ++ ++ private: ++ std::shared_ptr<AsyncResp> asyncResp; ++ AddReportArgs args; ++ boost::container::flat_map<std::string, std::string> uriToDbus{}; ++ }; + }; + + class MetricReportDefinition : public Node +@@ -73,9 +396,10 @@ class MetricReportDefinition : public Node + crow::connections::systemBus->async_method_call( + [asyncResp, + id](const boost::system::error_code ec, +- const std::vector<std::pair< +- std::string, std::variant<bool, ReadingParameters, +- std::string, uint64_t>>>& ret) { ++ const std::vector< ++ std::pair<std::string, ++ std::variant<bool, telemetry::ReadingParameters, ++ std::string, uint64_t>>>& ret) { + if (ec.value() == EBADR) + { + messages::resourceNotFound(asyncResp->res, schemaType, id); +@@ -95,15 +419,11 @@ class MetricReportDefinition : public Node + telemetry::reportInterface); + } + +- using ReadingParameters = +- std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, +- std::string, std::string, std::string>>; +- + static void fillReportDefinition( + const std::shared_ptr<AsyncResp>& asyncResp, const std::string& id, +- const std::vector< +- std::pair<std::string, std::variant<bool, ReadingParameters, +- std::string, uint64_t>>>& ret) ++ const std::vector<std::pair< ++ std::string, std::variant<bool, telemetry::ReadingParameters, ++ std::string, uint64_t>>>& ret) + { + asyncResp->res.jsonValue["@odata.type"] = schemaType; + asyncResp->res.jsonValue["@odata.id"] = +@@ -117,7 +437,7 @@ class MetricReportDefinition : public Node + + const bool* emitsReadingsUpdate = nullptr; + const bool* logToMetricReportsCollection = nullptr; +- const ReadingParameters* readingParams = nullptr; ++ const telemetry::ReadingParameters* readingParams = nullptr; + const std::string* reportingType = nullptr; + const uint64_t* interval = nullptr; + for (const auto& [key, var] : ret) +@@ -132,7 +452,7 @@ class MetricReportDefinition : public Node + } + else if (key == "ReadingParameters") + { +- readingParams = std::get_if<ReadingParameters>(&var); ++ readingParams = std::get_if<telemetry::ReadingParameters>(&var); + } + else if (key == "ReportingType") + { +@@ -180,6 +500,44 @@ class MetricReportDefinition : public Node + time_utils::toDurationString(std::chrono::milliseconds(*interval)); + } + ++ void doDelete(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>& params) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ const std::string reportPath = telemetry::getDbusReportPath(id); ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, id](const boost::system::error_code ec) { ++ /* ++ * boost::system::errc and std::errc are missing value for ++ * EBADR error that is defined in Linux. ++ */ ++ if (ec.value() == EBADR) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ asyncResp->res.result(boost::beast::http::status::no_content); ++ }, ++ telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete", ++ "Delete"); ++ } ++ + static constexpr const char* schemaType = + "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; + }; +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch deleted file mode 100644 index c24352de5..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch +++ /dev/null @@ -1,598 +0,0 @@ -From 00806052b1e9440809ce727523ffcc66083f6417 Mon Sep 17 00:00:00 2001 -From: "Wludzik, Jozef" <jozef.wludzik@intel.com> -Date: Mon, 18 May 2020 11:56:57 +0200 -Subject: [PATCH 06/10] Add support for POST in MetricReportDefinitions - -Added POST action in MetricReportDefinitions node to allow user -to add new MetricReportDefinition. Using minimal set of -MetricReportDefinition parameters from user bmcweb converts it to -DBus call "AddReport" to MonitoringService that serves as a backend -for TelemetryService. - -Tested: - - Succesfully passed RedfishServiceValidator.py - - Validated good cases with different parameters for POST action - - Validated bad cases with different parameters for POST action - - Validated fromDurationFormat() with range of arguments starting - from PT0.0S up to P49D (it is an upper limit for uint32_t) - -Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> -Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Change-Id: I2fed96848594451e22fde686f8c066d7770cc65a - -%% original patch: 0002-Add-support-for-POST-in-MetricReportDefinitions.patch - -Change-Id: I55032bc1086b60800d19bd1c0fa14fdb891f5a5b ---- - redfish-core/include/utils/time_utils.hpp | 49 +++ - .../include/utils/validate_params_length.hpp | 109 +++++++ - redfish-core/lib/metric_report_definition.hpp | 347 +++++++++++++++++++++ - 3 files changed, 505 insertions(+) - create mode 100644 redfish-core/include/utils/validate_params_length.hpp - -diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp -index 0256b3f..c365585 100644 ---- a/redfish-core/include/utils/time_utils.hpp -+++ b/redfish-core/include/utils/time_utils.hpp -@@ -57,6 +57,32 @@ std::string toDurationFormatItem(std::chrono::milliseconds& duration, - return ss.str(); - } - -+template <typename T> -+static long long fromDurationFormatItem(std::string_view& fmt, -+ const char* postfix) -+{ -+ auto pos = fmt.find(postfix); -+ if (pos == std::string::npos) -+ { -+ return 0; -+ } -+ -+ long out; -+ if constexpr (std::is_same<T, std::chrono::milliseconds>::value) -+ { -+ /* Half point is added to avoid numeric error on rounding */ -+ out = static_cast<long>(std::strtof(fmt.data(), nullptr) * -+ std::chrono::milliseconds::period::den + -+ 0.5f); -+ } -+ else -+ { -+ out = std::strtol(fmt.data(), nullptr, 10); -+ } -+ fmt.remove_prefix(pos + 1); -+ return std::chrono::milliseconds(T(out)).count(); -+} -+ - } // namespace details - - /** -@@ -93,5 +119,28 @@ std::string toDurationFormat(const uint32_t ms) - return fmt; - } - -+static uint32_t fromDurationFormat(std::string_view fmt) -+{ -+ if (fmt.empty() || fmt[0] != 'P') -+ { -+ return 0; -+ } -+ using Days = std::chrono::duration<int, std::ratio<24 * 60 * 60>>; -+ -+ fmt.remove_prefix(1); -+ auto out = details::fromDurationFormatItem<Days>(fmt, "D"); -+ if (fmt[0] != 'T') -+ { -+ return static_cast<uint32_t>(out); -+ } -+ -+ fmt.remove_prefix(1); -+ out += details::fromDurationFormatItem<std::chrono::hours>(fmt, "H"); -+ out += details::fromDurationFormatItem<std::chrono::minutes>(fmt, "M"); -+ out += details::fromDurationFormatItem<std::chrono::milliseconds>(fmt, "S"); -+ -+ return static_cast<uint32_t>(out); -+} -+ - } // namespace time_utils - } // namespace redfish -diff --git a/redfish-core/include/utils/validate_params_length.hpp b/redfish-core/include/utils/validate_params_length.hpp -new file mode 100644 -index 0000000..c4e0569 ---- /dev/null -+++ b/redfish-core/include/utils/validate_params_length.hpp -@@ -0,0 +1,109 @@ -+#pragma once -+ -+namespace redfish -+{ -+namespace detail -+{ -+template <class Limits, size_t... Seq> -+bool validateParamsLength(crow::Response& res, Limits&& limits, -+ std::index_sequence<Seq...>) -+{ -+ return (... && std::get<Seq>(limits).validate(res)); -+} -+} // namespace detail -+ -+template <class T> -+struct ItemSizeValidator -+{ -+ ItemSizeValidator(const T&& item, std::string_view name, size_t limit) : -+ item(std::forward<const T>(item)), name(name), limit(limit) -+ {} -+ -+ bool validate(crow::Response& res) const -+ { -+ return ItemSizeValidator<T>::validateItem(res, item, name, limit); -+ } -+ -+ private: -+ static bool validateItem(crow::Response& res, size_t item, -+ std::string_view name, size_t limit) -+ { -+ if (item > static_cast<size_t>(limit)) -+ { -+ messages::stringValueTooLong(res, std::string(name), -+ static_cast<int>(limit)); -+ return false; -+ } -+ return true; -+ } -+ -+ static bool validateItem(crow::Response& res, std::string_view item, -+ std::string_view name, size_t limit) -+ { -+ return validateItem(res, item.size(), name, limit); -+ } -+ -+ static bool validateItem(crow::Response& res, const std::string& item, -+ std::string_view name, size_t limit) -+ { -+ return validateItem(res, item.size(), name, limit); -+ } -+ -+ static bool validateItem(crow::Response& res, -+ const sdbusplus::message::object_path& item, -+ std::string_view name, size_t limit) -+ { -+ return validateItem(res, item.str.size(), name, limit); -+ } -+ -+ T item; -+ std::string_view name; -+ size_t limit; -+}; -+ -+template <class T> -+ItemSizeValidator(const T&, std::string_view, size_t) -+ -> ItemSizeValidator<const T&>; -+ -+ItemSizeValidator(const char*, std::string_view, size_t) -+ ->ItemSizeValidator<std::string_view>; -+ -+template <class ContainerT> -+struct ArrayItemsValidator -+{ -+ ArrayItemsValidator(const ContainerT& item, std::string_view name, -+ size_t limit) : -+ item(item), -+ name(name), limit(limit) -+ {} -+ -+ bool validate(crow::Response& res) const -+ { -+ return std::all_of( -+ item.begin(), item.end(), [&res, this](const auto& item) { -+ return ItemSizeValidator(item, name, limit).validate(res); -+ }); -+ } -+ -+ private: -+ const ContainerT& item; -+ std::string_view name; -+ size_t limit; -+}; -+ -+template <class T> -+bool validateParamLength(crow::Response& res, T&& item, std::string_view name, -+ size_t limit) -+{ -+ return ItemSizeValidator(std::forward<T>(item), name, limit).validate(res); -+} -+ -+template <class Limits> -+bool validateParamsLength(crow::Response& res, Limits&& limits) -+{ -+ return detail::validateParamsLength( -+ res, std::forward<Limits>(limits), -+ std::make_index_sequence<std::tuple_size_v<std::decay_t<Limits>>>()); -+} -+ -+} // namespace redfish -diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index 72e62e9..c6b09f8 100644 ---- a/redfish-core/lib/metric_report_definition.hpp -+++ b/redfish-core/lib/metric_report_definition.hpp -@@ -17,16 +17,29 @@ - #pragma once - - #include "node.hpp" -+#include "sensors.hpp" - #include "utils/telemetry_utils.hpp" - #include "utils/time_utils.hpp" -+#include "utils/validate_params_length.hpp" - -+#include <boost/algorithm/string/join.hpp> -+#include <boost/algorithm/string/split.hpp> - #include <boost/container/flat_map.hpp> - -+#include <regex> - #include <system_error> -+#include <tuple> - #include <variant> - - namespace redfish - { -+static constexpr size_t maxShortParamLength = 255; -+static constexpr size_t maxLongParamLength = 1024; -+static constexpr size_t maxDbusNameLength = 255; -+static constexpr size_t maxArraySize = 100; -+static constexpr size_t maxReportIdLen = -+ maxDbusNameLength - std::string_view(telemetry::telemetryPath).size() - -+ std::string_view("/").size(); - - class MetricReportDefinitionCollection : public Node - { -@@ -57,6 +70,339 @@ class MetricReportDefinitionCollection : public Node - telemetry::getReportCollection(asyncResp, - telemetry::metricReportDefinitionUri); - } -+ -+ using ChassisSensorNode = std::pair<std::string, std::string>; -+ using DbusSensor = sdbusplus::message::object_path; -+ using DbusSensors = std::vector<DbusSensor>; -+ using MetricParam = -+ std::tuple<DbusSensors, std::string, std::string, std::string>; -+ using MetricParams = std::vector<MetricParam>; -+ /* -+ * AddReportArgs misses "Domain" parameter because it is constant for -+ * TelemetryService and equals "TelemetryService". -+ */ -+ using AddReportArgs = -+ std::tuple<std::string, std::string, std::vector<std::string>, uint32_t, -+ MetricParams>; -+ -+ void doPost(crow::Response& res, const crow::Request& req, -+ const std::vector<std::string>&) override -+ { -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ AddReportArgs addReportArgs; -+ if (!getUserParameters(res, req, addReportArgs)) -+ { -+ return; -+ } -+ -+ boost::container::flat_set<ChassisSensorNode> chassisSensorSet; -+ auto unmatched = getChassisSensorNode( -+ std::get<MetricParams>(addReportArgs), chassisSensorSet); -+ if (unmatched) -+ { -+ messages::resourceNotFound(asyncResp->res, "MetricProperties", -+ *unmatched); -+ return; -+ } -+ -+ auto addReportReq = -+ std::make_shared<AddReport>(addReportArgs, asyncResp); -+ for (auto& [chassis, sensorType] : chassisSensorSet) -+ { -+ retrieveUriToDbusMap( -+ chassis, sensorType, -+ [asyncResp, addReportReq]( -+ const boost::beast::http::status, -+ const boost::container::flat_map<std::string, std::string>& -+ uriToDbus) { *addReportReq += uriToDbus; }); -+ } -+ } -+ -+ static std::optional<std::string> -+ replaceReportActions(std::vector<std::string>& actions) -+ { -+ const boost::container::flat_map<std::string, std::string> -+ reportActions = { -+ {"RedfishEvent", "Event"}, -+ {"LogToMetricReportsCollection", "Log"}, -+ }; -+ -+ for (auto& action : actions) -+ { -+ auto found = reportActions.find(action); -+ if (found == reportActions.end()) -+ { -+ return action; -+ } -+ action = found->second; -+ } -+ return std::nullopt; -+ } -+ -+ static constexpr const std::array<const char*, 2> supportedDefinitionType = -+ {"Periodic", "OnRequest"}; -+ -+ static bool getUserParameters(crow::Response& res, const crow::Request& req, -+ AddReportArgs& params) -+ { -+ std::vector<nlohmann::json> metrics; -+ std::optional<nlohmann::json> schedule; -+ auto& [name, reportingType, reportActions, scanPeriod, metricParams] = -+ params; -+ if (!json_util::readJson(req, res, "Id", name, "Metrics", metrics, -+ "MetricReportDefinitionType", reportingType, -+ "ReportActions", reportActions, "Schedule", -+ schedule)) -+ { -+ return false; -+ } -+ -+ auto limits = std::make_tuple( -+ ItemSizeValidator(name, "Id", maxReportIdLen), -+ ItemSizeValidator(reportingType, "MetricReportDefinitionType", -+ maxShortParamLength), -+ ItemSizeValidator(reportActions.size(), "ReportActions.size()", -+ maxArraySize), -+ ArrayItemsValidator(reportActions, "ReportActions", -+ maxShortParamLength), -+ ItemSizeValidator(metrics.size(), "Metrics.size()", maxArraySize)); -+ -+ if (!validateParamsLength(res, std::move(limits))) -+ { -+ return false; -+ } -+ -+ constexpr const char* allowedCharactersInName = -+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -+ "_"; -+ if (name.empty() || name.find_first_not_of(allowedCharactersInName) != -+ std::string::npos) -+ { -+ BMCWEB_LOG_ERROR << "Failed to match " << name -+ << " with allowed character " -+ << allowedCharactersInName; -+ messages::propertyValueFormatError(res, name, "Id"); -+ return false; -+ } -+ -+ if (!std::any_of( -+ supportedDefinitionType.begin(), supportedDefinitionType.end(), -+ [reportingType](auto& x) { return reportingType == x; })) -+ { -+ messages::propertyValueNotInList(res, reportingType, -+ "MetricReportDefinitionType"); -+ return false; -+ } -+ -+ auto unmatched = replaceReportActions(reportActions); -+ if (unmatched) -+ { -+ messages::propertyValueNotInList(res, *unmatched, "ReportActions"); -+ return false; -+ } -+ -+ if (reportingType == "Periodic") -+ { -+ if (!schedule) -+ { -+ messages::createFailedMissingReqProperties(res, "Schedule"); -+ return false; -+ } -+ -+ std::string interval; -+ if (!json_util::readJson(*schedule, res, "RecurrenceInterval", -+ interval)) -+ { -+ return false; -+ } -+ -+ if (!validateParamLength(res, interval, "RecurrenceInterval", -+ maxShortParamLength)) -+ { -+ return false; -+ } -+ -+ constexpr const char* durationPattern = -+ "-?P(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+(.\\d+)?S)?)?"; -+ if (!std::regex_match(interval, std::regex(durationPattern))) -+ { -+ messages::propertyValueFormatError(res, interval, -+ "RecurrenceInterval"); -+ return false; -+ } -+ -+ scanPeriod = time_utils::fromDurationFormat(interval); -+ } -+ -+ return fillMetricParams(metrics, res, metricParams); -+ } -+ -+ static bool fillMetricParams(std::vector<nlohmann::json>& metrics, -+ crow::Response& res, -+ MetricParams& metricParams) -+ { -+ metricParams.reserve(metrics.size()); -+ for (auto& m : metrics) -+ { -+ std::string metricId; -+ std::vector<std::string> metricProperties; -+ if (!json_util::readJson(m, res, "MetricId", metricId, -+ "MetricProperties", metricProperties)) -+ { -+ return false; -+ } -+ -+ auto limits = std::make_tuple( -+ ItemSizeValidator(metricId, "MetricId", maxLongParamLength), -+ ItemSizeValidator(metricProperties.size(), -+ "MetricProperties.size()", maxArraySize), -+ ArrayItemsValidator(metricProperties, "MetricProperties", -+ maxLongParamLength)); -+ -+ if (!validateParamsLength(res, std::move(limits))) -+ { -+ return false; -+ } -+ -+ DbusSensors dbusSensors; -+ dbusSensors.reserve(metricProperties.size()); -+ std::for_each( -+ metricProperties.begin(), metricProperties.end(), -+ [&dbusSensors](auto& x) { dbusSensors.emplace_back(x); }); -+ -+ metricParams.emplace_back( -+ dbusSensors, "SINGLE", metricId, -+ boost::algorithm::join(metricProperties, ", ")); -+ } -+ return true; -+ } -+ -+ static std::optional<std::string> getChassisSensorNode( -+ const MetricParams& metricParams, -+ boost::container::flat_set<ChassisSensorNode>& matched) -+ { -+ for (const auto& metricParam : metricParams) -+ { -+ const auto& sensors = std::get<DbusSensors>(metricParam); -+ for (const auto& sensor : sensors) -+ { -+ /* -+ * Support only following paths: -+ * "/redfish/v1/Chassis/<chassis>/Power#/..." -+ * "/redfish/v1/Chassis/<chassis>/Sensors/..." -+ * "/redfish/v1/Chassis/<chassis>/Thermal#/..." -+ */ -+ constexpr const char* uriPattern = -+ "\\/redfish\\/v1\\/Chassis\\/(\\w+)\\/" -+ "(Power|Sensors|Thermal)[#]?\\/.*"; -+ std::smatch m; -+ if (!std::regex_match(sensor.str, m, std::regex(uriPattern)) || -+ m.size() != 3) -+ { -+ BMCWEB_LOG_ERROR << "Failed to match " << sensor.str -+ << " with pattern " << uriPattern; -+ return sensor; -+ } -+ -+ BMCWEB_LOG_DEBUG << "Chassis=" << m[1] << ", Type=" << m[2]; -+ matched.emplace(m[1], m[2]); -+ } -+ } -+ return std::nullopt; -+ } -+ -+ static std::optional<std::string> replaceUriWithDbus( -+ const boost::container::flat_map<std::string, std::string>& uriToDbus, -+ MetricParams& metricParams) -+ { -+ for (auto& metricParam : metricParams) -+ { -+ auto& dbusSensors = std::get<DbusSensors>(metricParam); -+ for (auto& uri : dbusSensors) -+ { -+ auto dbus = uriToDbus.find(uri); -+ if (dbus == uriToDbus.end()) -+ { -+ BMCWEB_LOG_ERROR << "Failed to find DBus sensor " -+ "corresponding to URI " -+ << uri.str; -+ return uri; -+ } -+ uri = dbus->second; -+ } -+ } -+ return std::nullopt; -+ } -+ -+ static void addReport(std::shared_ptr<AsyncResp> asyncResp, -+ AddReportArgs args) -+ { -+ constexpr const char* domain = "TelemetryService"; -+ auto& [name, reportingType, reportActions, scanPeriod, metricParams] = -+ args; -+ -+ crow::connections::systemBus->async_method_call( -+ [asyncResp, name](const boost::system::error_code ec, -+ const std::string) { -+ if (ec == boost::system::errc::file_exists) -+ { -+ messages::resourceAlreadyExists( -+ asyncResp->res, "MetricReportDefinition", "Id", name); -+ return; -+ } -+ if (ec == boost::system::errc::too_many_files_open) -+ { -+ messages::createLimitReachedForResource(asyncResp->res); -+ return; -+ } -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ return; -+ } -+ -+ messages::created(asyncResp->res); -+ }, -+ "xyz.openbmc_project.MonitoringService", -+ "/xyz/openbmc_project/MonitoringService/Reports", -+ "xyz.openbmc_project.MonitoringService.ReportsManagement", -+ "AddReport", name, domain, reportingType, reportActions, scanPeriod, -+ metricParams); -+ } -+ -+ class AddReport -+ { -+ public: -+ AddReport(AddReportArgs& args, std::shared_ptr<AsyncResp>& asyncResp) : -+ asyncResp{asyncResp}, addReportArgs{args} -+ {} -+ ~AddReport() -+ { -+ auto unmatched = replaceUriWithDbus( -+ uriToDbus, std::get<MetricParams>(addReportArgs)); -+ if (unmatched) -+ { -+ messages::resourceNotFound(asyncResp->res, "MetricProperties", -+ *unmatched); -+ return; -+ } -+ -+ addReport(asyncResp, addReportArgs); -+ } -+ -+ AddReport& operator+=( -+ const boost::container::flat_map<std::string, std::string>& rhs) -+ { -+ this->uriToDbus.insert(rhs.begin(), rhs.end()); -+ return *this; -+ } -+ -+ private: -+ std::shared_ptr<AsyncResp> asyncResp; -+ AddReportArgs addReportArgs; -+ boost::container::flat_map<std::string, std::string> uriToDbus{}; -+ }; - }; - - class MetricReportDefinition : public Node -@@ -146,6 +492,7 @@ class MetricReportDefinition : public Node - asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = - telemetry::metricReportUri + id; - asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; -+ asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; - - dbus::utility::getAllProperties( - [asyncResp](const boost::system::error_code ec, --- -2.16.6 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch deleted file mode 100644 index dee5a158b..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 5a1eef4a6c74c29d9b9026676e59e6cdf0a7e8bd Mon Sep 17 00:00:00 2001 -From: "Wludzik, Jozef" <jozef.wludzik@intel.com> -Date: Mon, 18 May 2020 12:40:15 +0200 -Subject: [PATCH 07/10] Add support for DELETE in MetricReportDefinitions/<str> - -Added support for DELETE action in MetricReportDefinitions/<str> -node. It allows user to remove MetricReportDefinition together -with MetricReport connected to it. - -Tested: - - Succesfully passed RedfishServiceValidator.py - - Validated DELETE action by removing exisiting - MetricReportDefinitions from MonitoringService - - Validated DELETE action with negative cases when - MetricReportDefinition does not exist - -Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> -Change-Id: Iffde9f7bbf2955376e9714ac8d833967bd25eaa3 - -%% original patch: 0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch - -Change-Id: I2930b9354fd4cf1f8d9a97af33b81c7b689fe0ef ---- - redfish-core/lib/metric_report_definition.hpp | 32 +++++++++++++++++++++++++++ - 1 file changed, 32 insertions(+) - -diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp -index c6b09f8..3191a8f 100644 ---- a/redfish-core/lib/metric_report_definition.hpp -+++ b/redfish-core/lib/metric_report_definition.hpp -@@ -531,6 +531,38 @@ class MetricReportDefinition : public Node - "xyz.openbmc_project.MonitoringService.Report"); - } - -+ void doDelete(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>& params) override -+ { -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ if (params.size() != 1) -+ { -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ const std::string& id = params[0]; -+ telemetry::getReport(asyncResp, id, schemaType, deleteReport); -+ } -+ -+ static void deleteReport(const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::string& path, const std::string&) -+ { -+ crow::connections::systemBus->async_method_call( -+ [asyncResp](const boost::system::error_code ec) { -+ if (ec) -+ { -+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ asyncResp->res.result(boost::beast::http::status::no_content); -+ }, -+ "xyz.openbmc_project.MonitoringService", path, -+ "xyz.openbmc_project.Object.Delete", "Delete"); -+ } -+ - public: - static constexpr const char* schemaType = - "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; --- -2.16.6 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch new file mode 100644 index 000000000..d81d654f1 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-MetricDefinition-scheme.patch @@ -0,0 +1,709 @@ +From b074a84560349fdbd46604ab0b8c75804de09fef Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" <jozef.wludzik@intel.com> +Date: Mon, 8 Jun 2020 17:15:54 +0200 +Subject: [PATCH 3/4] Add support for MetricDefinition scheme + +Added MetricDefinition node to redfish core. Now user is able to +get all possible metrics that are present in system and are +supported by TelemetryService. +Added generic function to fill ReadingUnits and ReadingType +in Sensor scheme. + +Tested: + - Succesfully passed RedfishServiceValidator.py + - Validated a presence of MetricDefinition members + +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> +Change-Id: I3086e1302e1ba2e5442d1367939fd5507a0cbc00 +--- + redfish-core/include/redfish.hpp | 3 + + redfish-core/include/utils/telemetry_utils.hpp | 56 ++--- + redfish-core/lib/metric_definition.hpp | 269 +++++++++++++++++++++++++ + redfish-core/lib/metric_report_definition.hpp | 22 ++ + redfish-core/lib/power.hpp | 4 +- + redfish-core/lib/sensors.hpp | 104 +++++++--- + redfish-core/lib/telemetry_service.hpp | 2 + + redfish-core/lib/thermal.hpp | 4 +- + 8 files changed, 410 insertions(+), 54 deletions(-) + create mode 100644 redfish-core/lib/metric_definition.hpp + +diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp +index 2587b37..705f490 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -25,6 +25,7 @@ + #include "../lib/managers.hpp" + #include "../lib/memory.hpp" + #include "../lib/message_registries.hpp" ++#include "../lib/metric_definition.hpp" + #include "../lib/metric_report.hpp" + #include "../lib/metric_report_definition.hpp" + #include "../lib/network_protocol.hpp" +@@ -211,6 +212,8 @@ class RedfishService + nodes.emplace_back(std::make_unique<HypervisorSystem>(app)); + + nodes.emplace_back(std::make_unique<TelemetryService>(app)); ++ nodes.emplace_back(std::make_unique<MetricDefinitionCollection>(app)); ++ nodes.emplace_back(std::make_unique<MetricDefinition>(app)); + nodes.emplace_back( + std::make_unique<MetricReportDefinitionCollection>(app)); + nodes.emplace_back(std::make_unique<MetricReportDefinition>(app)); +diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp +index acb739d..c13a79b 100644 +--- a/redfish-core/include/utils/telemetry_utils.hpp ++++ b/redfish-core/include/utils/telemetry_utils.hpp +@@ -8,6 +8,8 @@ namespace telemetry + + constexpr const char* service = "xyz.openbmc_project.Telemetry"; + constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; ++constexpr const char* metricDefinitionUri = ++ "/redfish/v1/TelemetryService/MetricDefinitions/"; + constexpr const char* metricReportDefinitionUri = + "/redfish/v1/TelemetryService/MetricReportDefinitions/"; + constexpr const char* metricReportUri = +@@ -15,6 +17,36 @@ constexpr const char* metricReportUri = + constexpr const char* reportDir = + "/xyz/openbmc_project/Telemetry/Reports/TelemetryService/"; + ++inline void dbusPathsToMembers(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::vector<std::string>& paths, ++ const std::string& uri) ++{ ++ nlohmann::json& members = asyncResp->res.jsonValue["Members"]; ++ members = nlohmann::json::array(); ++ ++ for (const std::string& path : paths) ++ { ++ std::size_t pos = path.rfind('/'); ++ if (pos == std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Failed to find '/' in " << path; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ if (path.size() <= (pos + 1)) ++ { ++ BMCWEB_LOG_ERROR << "Failed to parse path " << path; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ members.push_back({{"@odata.id", uri + path.substr(pos + 1)}}); ++ } ++ ++ asyncResp->res.jsonValue["Members@odata.count"] = members.size(); ++} ++ + inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, + const std::string& uri) + { +@@ -30,29 +62,7 @@ inline void getReportCollection(const std::shared_ptr<AsyncResp>& asyncResp, + return; + } + +- nlohmann::json& members = asyncResp->res.jsonValue["Members"]; +- members = nlohmann::json::array(); +- +- for (const std::string& path : reportPaths) +- { +- std::size_t pos = path.rfind('/'); +- if (pos == std::string::npos) +- { +- BMCWEB_LOG_ERROR << "Failed to find '/' in " << path; +- messages::internalError(asyncResp->res); +- return; +- } +- if (path.size() <= (pos + 1)) +- { +- BMCWEB_LOG_ERROR << "Failed to parse path " << path; +- messages::internalError(asyncResp->res); +- return; +- } +- +- members.push_back({{"@odata.id", uri + path.substr(pos + 1)}}); +- } +- +- asyncResp->res.jsonValue["Members@odata.count"] = members.size(); ++ dbusPathsToMembers(asyncResp, reportPaths, uri); + }, + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", +diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp +new file mode 100644 +index 0000000..f037ed2 +--- /dev/null ++++ b/redfish-core/lib/metric_definition.hpp +@@ -0,0 +1,269 @@ ++#pragma once ++ ++#include "node.hpp" ++#include "sensors.hpp" ++#include "utils/telemetry_utils.hpp" ++ ++namespace redfish ++{ ++ ++namespace utils ++{ ++ ++template <typename F> ++inline void getChassisNames(F&& cb) ++{ ++ const std::array<const char*, 2> interfaces = { ++ "xyz.openbmc_project.Inventory.Item.Board", ++ "xyz.openbmc_project.Inventory.Item.Chassis"}; ++ ++ crow::connections::systemBus->async_method_call( ++ [callback = std::move(cb)](const boost::system::error_code ec, ++ std::vector<std::string>& chassisList) { ++ if (ec) ++ { ++ BMCWEB_LOG_DEBUG << "DBus call error: " << ec.value(); ++ return; ++ } ++ ++ std::vector<std::string> chassisNames; ++ chassisNames.reserve(chassisList.size()); ++ for (const std::string& chassisPath : chassisList) ++ { ++ size_t pos = chassisPath.rfind('/'); ++ if (pos == std::string::npos) ++ { ++ continue; ++ } ++ chassisNames.push_back(chassisPath.substr(pos + 1)); ++ } ++ ++ callback(chassisNames); ++ }, ++ "xyz.openbmc_project.ObjectMapper", ++ "/xyz/openbmc_project/object_mapper", ++ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", ++ "/xyz/openbmc_project/inventory", 0, interfaces); ++} ++} // namespace utils ++ ++class MetricDefinitionCollection : public Node ++{ ++ public: ++ MetricDefinitionCollection(App& app) : ++ Node(app, "/redfish/v1/TelemetryService/MetricDefinitions/") ++ { ++ entityPrivileges = { ++ {boost::beast::http::verb::get, {{"Login"}}}, ++ {boost::beast::http::verb::head, {{"Login"}}}, ++ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>&) override ++ { ++ res.jsonValue["@odata.type"] = "#MetricDefinitionCollection." ++ "MetricDefinitionCollection"; ++ res.jsonValue["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricDefinitions"; ++ res.jsonValue["Name"] = "Metric Definition Collection"; ++ res.jsonValue["Members"] = nlohmann::json::array(); ++ res.jsonValue["Members@odata.count"] = 0; ++ ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ auto collectionReduce = std::make_shared<CollectionGather>(asyncResp); ++ utils::getChassisNames( ++ [asyncResp, ++ collectionReduce](const std::vector<std::string>& chassisNames) { ++ for (const std::string& chassisName : chassisNames) ++ { ++ for (const auto& [sensorNode, _] : sensors::dbus::paths) ++ { ++ BMCWEB_LOG_INFO << "Chassis: " << chassisName ++ << " sensor: " << sensorNode; ++ retrieveUriToDbusMap( ++ chassisName, sensorNode.data(), ++ [asyncResp, collectionReduce]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR ++ << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ collectionReduce->insert(uriToDbus); ++ }); ++ } ++ } ++ }); ++ } ++ ++ class CollectionGather ++ { ++ public: ++ CollectionGather(const std::shared_ptr<AsyncResp>& asyncResp) : ++ asyncResp{asyncResp} ++ {} ++ ++ ~CollectionGather() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ ++ telemetry::dbusPathsToMembers( ++ asyncResp, ++ std::vector<std::string>(dbusTypes.begin(), dbusTypes.end()), ++ telemetry::metricDefinitionUri); ++ } ++ ++ void insert( ++ const boost::container::flat_map<std::string, std::string>& el) ++ { ++ for (const auto& [_, dbusSensor] : el) ++ { ++ size_t pos = dbusSensor.rfind('/'); ++ if (pos == std::string::npos) ++ { ++ BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " ++ << dbusSensor; ++ continue; ++ } ++ ++ dbusTypes.insert(dbusSensor.substr(0, pos)); ++ } ++ } ++ ++ private: ++ const std::shared_ptr<AsyncResp> asyncResp; ++ boost::container::flat_set<std::string> dbusTypes; ++ }; ++}; ++ ++class MetricDefinition : public Node ++{ ++ public: ++ MetricDefinition(App& app) : ++ Node(app, "/redfish/v1/TelemetryService/MetricDefinitions/<str>/", ++ std::string()) ++ { ++ entityPrivileges = { ++ {boost::beast::http::verb::get, {{"Login"}}}, ++ {boost::beast::http::verb::head, {{"Login"}}}, ++ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, ++ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; ++ } ++ ++ private: ++ void doGet(crow::Response& res, const crow::Request&, ++ const std::vector<std::string>& params) override ++ { ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ if (params.size() != 1) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ const std::string& id = params[0]; ++ auto definitionGather = ++ std::make_shared<DefinitionGather>(asyncResp, id); ++ utils::getChassisNames( ++ [asyncResp, ++ definitionGather](const std::vector<std::string>& chassisNames) { ++ for (const std::string& chassisName : chassisNames) ++ { ++ for (const auto& [sensorNode, dbusPaths] : ++ sensors::dbus::paths) ++ { ++ retrieveUriToDbusMap( ++ chassisName, sensorNode.data(), ++ [asyncResp, definitionGather]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map< ++ std::string, std::string>& uriToDbus) { ++ if (status != boost::beast::http::status::ok) ++ { ++ BMCWEB_LOG_ERROR ++ << "Failed to retrieve URI to dbus " ++ "sensors map with err " ++ << static_cast<unsigned>(status); ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ definitionGather->insert(uriToDbus); ++ }); ++ } ++ } ++ }); ++ } ++ ++ class DefinitionGather ++ { ++ public: ++ DefinitionGather(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::string& id) : ++ id(id), ++ pattern{'/' + id + '/'}, asyncResp{asyncResp} ++ {} ++ ~DefinitionGather() ++ { ++ if (asyncResp->res.result() != boost::beast::http::status::ok) ++ { ++ return; ++ } ++ if (redfishSensors.empty()) ++ { ++ messages::resourceNotFound(asyncResp->res, schemaType, id); ++ return; ++ } ++ ++ asyncResp->res.jsonValue["MetricProperties"] = redfishSensors; ++ asyncResp->res.jsonValue["Id"] = id; ++ asyncResp->res.jsonValue["Name"] = id; ++ asyncResp->res.jsonValue["@odata.id"] = ++ telemetry::metricDefinitionUri + id; ++ asyncResp->res.jsonValue["@odata.type"] = schemaType; ++ asyncResp->res.jsonValue["MetricDataType"] = "Decimal"; ++ asyncResp->res.jsonValue["MetricType"] = "Numeric"; ++ asyncResp->res.jsonValue["IsLinear"] = true; ++ asyncResp->res.jsonValue["Units"] = sensors::toReadingUnits(id); ++ } ++ ++ void insert( ++ const boost::container::flat_map<std::string, std::string>& el) ++ { ++ for (const auto& [redfishSensor, dbusSensor] : el) ++ { ++ if (dbusSensor.find(pattern) != std::string::npos) ++ { ++ redfishSensors.push_back(redfishSensor); ++ } ++ } ++ } ++ ++ const std::string id; ++ const std::string pattern; ++ ++ private: ++ const std::shared_ptr<AsyncResp> asyncResp; ++ std::vector<std::string> redfishSensors; ++ }; ++ ++ static constexpr const char* schemaType = ++ "#MetricDefinition.v1_0_3.MetricDefinition"; ++}; ++ ++} // namespace redfish +diff --git a/redfish-core/lib/metric_report_definition.hpp b/redfish-core/lib/metric_report_definition.hpp +index d5a540d..03f0b82 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -269,6 +269,8 @@ class MetricReportDefinitionCollection : public Node + { + std::vector<sdbusplus::message::object_path> dbusPaths; + dbusPaths.reserve(uris.size()); ++ std::string sensorType; ++ bool invalidType = false; + + for (size_t i = 0; i < uris.size(); i++) + { +@@ -286,6 +288,21 @@ class MetricReportDefinitionCollection : public Node + } + + dbusPaths.emplace_back(el->second); ++ ++ if (invalidType) ++ { ++ continue; ++ } ++ std::string tmp; ++ dbus::utility::getNthStringFromPath(el->second, 3, tmp); ++ if (sensorType.empty()) ++ { ++ sensorType = std::move(tmp); ++ } ++ else if (sensorType != tmp) ++ { ++ invalidType = true; ++ } + } + + nlohmann::json metadata; +@@ -294,6 +311,11 @@ class MetricReportDefinitionCollection : public Node + { + metadata["MetricProperty"] = uris[0]; + } ++ if (!sensorType.empty() && !invalidType) ++ { ++ metadata["MetricDefinition"]["@odata.id"] = ++ telemetry::metricDefinitionUri + sensorType; ++ } + readingParams.emplace_back(std::move(dbusPaths), "SINGLE", id, + metadata.dump()); + } +diff --git a/redfish-core/lib/power.hpp b/redfish-core/lib/power.hpp +index 1c7a009..99c45ef 100644 +--- a/redfish-core/lib/power.hpp ++++ b/redfish-core/lib/power.hpp +@@ -153,7 +153,7 @@ class Power : public Node + res.jsonValue["PowerControl"] = nlohmann::json::array(); + + auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::power), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::power), + sensors::node::power); + + getChassisData(sensorAsyncResp); +@@ -336,7 +336,7 @@ class Power : public Node + + const std::string& chassisName = params[0]; + auto asyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::power), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::power), + sensors::node::power); + + std::optional<std::vector<nlohmann::json>> voltageCollections; +diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp +index 567cb0c..363713d 100644 +--- a/redfish-core/lib/sensors.hpp ++++ b/redfish-core/lib/sensors.hpp +@@ -54,9 +54,10 @@ static constexpr std::string_view thermal = "Thermal"; + + namespace dbus + { ++ + static const boost::container::flat_map<std::string_view, + std::vector<const char*>> +- types = {{node::power, ++ paths = {{node::power, + {"/xyz/openbmc_project/sensors/voltage", + "/xyz/openbmc_project/sensors/power"}}, + {node::sensors, +@@ -67,6 +68,64 @@ static const boost::container::flat_map<std::string_view, + {"/xyz/openbmc_project/sensors/fan_tach", + "/xyz/openbmc_project/sensors/temperature", + "/xyz/openbmc_project/sensors/fan_pwm"}}}; ++} // namespace dbus ++ ++inline const char* toReadingType(const std::string& sensorType) ++{ ++ if (sensorType == "voltage") ++ { ++ return "Voltage"; ++ } ++ if (sensorType == "power") ++ { ++ return "Power"; ++ } ++ if (sensorType == "current") ++ { ++ return "Current"; ++ } ++ if (sensorType == "fan_tach") ++ { ++ return "Rotational"; ++ } ++ if (sensorType == "temperature") ++ { ++ return "Temperature"; ++ } ++ if (sensorType == "fan_pwm" || sensorType == "utilization") ++ { ++ return "Percent"; ++ } ++ return ""; ++} ++ ++inline const char* toReadingUnits(const std::string& sensorType) ++{ ++ if (sensorType == "voltage") ++ { ++ return "V"; ++ } ++ if (sensorType == "power") ++ { ++ return "W"; ++ } ++ if (sensorType == "current") ++ { ++ return "A"; ++ } ++ if (sensorType == "fan_tach") ++ { ++ return "RPM"; ++ } ++ if (sensorType == "temperature") ++ { ++ return "Cel"; ++ } ++ if (sensorType == "fan_pwm" || sensorType == "utilization") ++ { ++ return "%"; ++ } ++ return ""; + } + } // namespace sensors + +@@ -90,19 +149,20 @@ class SensorsAsyncResp + }; + + SensorsAsyncResp(crow::Response& response, const std::string& chassisIdIn, +- const std::vector<const char*>& typesIn, ++ const std::vector<const char*>& matchPathsIn, + const std::string_view& subNode) : + res(response), +- chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode) ++ chassisId(chassisIdIn), matchPaths(matchPathsIn), ++ chassisSubNode(subNode) + {} + + // Store extra data about sensor mapping and return it in callback + SensorsAsyncResp(crow::Response& response, const std::string& chassisIdIn, +- const std::vector<const char*>& typesIn, ++ const std::vector<const char*>& matchPathsIn, + const std::string_view& subNode, + DataCompleteCb&& creationComplete) : + res(response), +- chassisId(chassisIdIn), types(typesIn), ++ chassisId(chassisIdIn), matchPaths(matchPathsIn), + chassisSubNode(subNode), metadata{std::vector<SensorData>()}, + dataComplete{std::move(creationComplete)} + {} +@@ -161,7 +221,7 @@ class SensorsAsyncResp + + crow::Response& res; + const std::string chassisId; +- const std::vector<const char*> types; ++ const std::vector<const char*> matchPaths; + const std::string chassisSubNode; + + private: +@@ -320,20 +380,20 @@ void getConnections( + * made, and eliminate Power sensors when a Thermal request is made. + */ + inline void reduceSensorList( +- const std::shared_ptr<SensorsAsyncResp>& SensorsAsyncResp, ++ const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, + const std::vector<std::string>* allSensors, + const std::shared_ptr<boost::container::flat_set<std::string>>& + activeSensors) + { +- if (SensorsAsyncResp == nullptr) ++ if (sensorsAsyncResp == nullptr) + { + return; + } + if ((allSensors == nullptr) || (activeSensors == nullptr)) + { + messages::resourceNotFound( +- SensorsAsyncResp->res, SensorsAsyncResp->chassisSubNode, +- SensorsAsyncResp->chassisSubNode == sensors::node::thermal ++ sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode, ++ sensorsAsyncResp->chassisSubNode == sensors::node::thermal + ? "Temperatures" + : "Voltages"); + +@@ -345,11 +405,11 @@ inline void reduceSensorList( + return; + } + +- for (const char* type : SensorsAsyncResp->types) ++ for (const char* path : sensorsAsyncResp->matchPaths) + { + for (const std::string& sensor : *allSensors) + { +- if (boost::starts_with(sensor, type)) ++ if (boost::starts_with(sensor, path)) + { + activeSensors->emplace(sensor); + } +@@ -853,18 +913,8 @@ inline void objectInterfacesToJson( + if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) + { + sensor_json["@odata.type"] = "#Sensor.v1_0_0.Sensor"; +- if (sensorType == "power") +- { +- sensor_json["ReadingUnits"] = "Watts"; +- } +- else if (sensorType == "current") +- { +- sensor_json["ReadingUnits"] = "Amperes"; +- } +- else if (sensorType == "utilization") +- { +- sensor_json["ReadingUnits"] = "Percent"; +- } ++ sensor_json["ReadingType"] = sensors::toReadingType(sensorType); ++ sensor_json["ReadingUnits"] = sensors::toReadingUnits(sensorType); + } + else if (sensorType == "temperature") + { +@@ -2976,8 +3026,8 @@ inline void retrieveUriToDbusMap(const std::string& chassis, + const std::string& node, + SensorsAsyncResp::DataCompleteCb&& mapComplete) + { +- auto typesIt = sensors::dbus::types.find(node); +- if (typesIt == sensors::dbus::types.end()) ++ auto typesIt = sensors::dbus::paths.find(node); ++ if (typesIt == sensors::dbus::paths.end()) + { + BMCWEB_LOG_ERROR << "Wrong node provided : " << node; + mapComplete(boost::beast::http::status::bad_request, {}); +@@ -3027,7 +3077,7 @@ class SensorCollection : public Node + const std::string& chassisId = params[0]; + std::shared_ptr<SensorsAsyncResp> asyncResp = + std::make_shared<SensorsAsyncResp>( +- res, chassisId, sensors::dbus::types.at(sensors::node::sensors), ++ res, chassisId, sensors::dbus::paths.at(sensors::node::sensors), + sensors::node::sensors); + + auto getChassisCb = +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +index a6acc34..8105d86 100644 +--- a/redfish-core/lib/telemetry_service.hpp ++++ b/redfish-core/lib/telemetry_service.hpp +@@ -34,6 +34,8 @@ class TelemetryService : public Node + + res.jsonValue["LogService"]["@odata.id"] = + "/redfish/v1/Managers/bmc/LogServices/Journal"; ++ res.jsonValue["MetricDefinitions"]["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricDefinitions"; + res.jsonValue["MetricReportDefinitions"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricReportDefinitions"; + res.jsonValue["MetricReports"]["@odata.id"] = +diff --git a/redfish-core/lib/thermal.hpp b/redfish-core/lib/thermal.hpp +index 8e01bee..00acdf9 100644 +--- a/redfish-core/lib/thermal.hpp ++++ b/redfish-core/lib/thermal.hpp +@@ -48,7 +48,7 @@ class Thermal : public Node + } + const std::string& chassisName = params[0]; + auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::thermal), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::thermal), + sensors::node::thermal); + + // TODO Need to get Chassis Redundancy information. +@@ -71,7 +71,7 @@ class Thermal : public Node + allCollections; + + auto asyncResp = std::make_shared<SensorsAsyncResp>( +- res, chassisName, sensors::dbus::types.at(sensors::node::thermal), ++ res, chassisName, sensors::dbus::paths.at(sensors::node::thermal), + sensors::node::thermal); + + if (!json_util::readJson(req, asyncResp->res, "Temperatures", +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch deleted file mode 100644 index c6c6a8f09..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch +++ /dev/null @@ -1,171 +0,0 @@ -From d206ea5049057fe4842186777231b9eb8468ec86 Mon Sep 17 00:00:00 2001 -From: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Date: Mon, 8 Jun 2020 15:16:10 +0200 -Subject: [PATCH 08/10] Add support for "OnRequest" in MetricReportDefinition - -Added support for "OnRequest" of ReportingType property in -MetricReportDefinition node. Now user is able to create -MetricReportDefinition that is updated on every GET request -on MetricReport. - -Tested: - - Succesfully passed RedfishServiceValidator.py - - Manually tested via curl - -Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Change-Id: I1cdfe47e56fdc5ec9753558145d0bf3645160aaf - -%% original patch: 0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch ---- - include/dbus_utility.hpp | 30 +++++++++++++++ - redfish-core/include/utils/telemetry_utils.hpp | 8 ++-- - redfish-core/lib/metric_report.hpp | 53 +++++++++++++++++++++++++- - 3 files changed, 87 insertions(+), 4 deletions(-) - -diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp -index ef3438b..80f8bcd 100644 ---- a/include/dbus_utility.hpp -+++ b/include/dbus_utility.hpp -@@ -18,6 +18,7 @@ - #include <sdbusplus/message.hpp> - - #include <filesystem> -+#include <functional> - #include <regex> - - namespace dbus -@@ -120,5 +121,34 @@ inline void getAllProperties(Callback&& callback, const std::string& service, - interface); - } - -+template <typename T> -+static void getProperty( -+ std::function<void(const boost::system::error_code&, const T&)> callback, -+ const std::string& service, const std::string& path, -+ const std::string& interface, const std::string& property) -+{ -+ crow::connections::systemBus->async_method_call( -+ [callback](const boost::system::error_code ec, -+ const std::variant<T>& value) { -+ if (ec) -+ { -+ callback(ec, T{}); -+ return; -+ } -+ -+ if (auto v = std::get_if<T>(&value)) -+ { -+ callback(ec, *v); -+ return; -+ } -+ -+ callback(boost::system::errc::make_error_code( -+ boost::system::errc::io_error), -+ T{}); -+ }, -+ service, path, "org.freedesktop.DBus.Properties", "Get", interface, -+ property); -+} -+ - } // namespace utility - } // namespace dbus -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index 05ed00f..6c4e810 100644 ---- a/redfish-core/include/utils/telemetry_utils.hpp -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -26,6 +26,8 @@ static constexpr const char* metricReportDefinitionUri = - "/redfish/v1/TelemetryService/MetricReportDefinitions/"; - static constexpr const char* metricReportUri = - "/redfish/v1/TelemetryService/MetricReports/"; -+static constexpr const char* monitoringService = -+ "xyz.openbmc_project.MonitoringService"; - static constexpr const char* reportInterface = - "xyz.openbmc_project.MonitoringService.Report"; - static constexpr const char* telemetryPath = -@@ -66,9 +68,9 @@ static void getReport(const std::shared_ptr<AsyncResp>& asyncResp, - const std::array<const char*, 1> interfaces = {reportInterface}; - - dbus::utility::getSubTreePaths( -- [asyncResp, id, schemaType, -- callback](const boost::system::error_code ec, -- const std::vector<std::string>& reports) { -+ [asyncResp, id, schemaType, callback = std::move(callback)]( -+ const boost::system::error_code ec, -+ const std::vector<std::string>& reports) { - if (ec == boost::system::errc::no_such_file_or_directory) - { - messages::resourceNotFound(asyncResp->res, schemaType, id); -diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp -index 4d1c4e5..768cce9 100644 ---- a/redfish-core/lib/metric_report.hpp -+++ b/redfish-core/lib/metric_report.hpp -@@ -85,7 +85,7 @@ class MetricReport : public Node - } - - const std::string& id = params[0]; -- telemetry::getReport(asyncResp, id, schemaType, getReportProperties); -+ telemetry::getReport(asyncResp, id, schemaType, updateReportIfRequired); - } - - using Readings = -@@ -143,6 +143,57 @@ class MetricReport : public Node - "xyz.openbmc_project.MonitoringService.Report"); - } - -+ template <typename Callback> -+ static void updateReport(Callback&& callback, -+ const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::string& path) -+ { -+ crow::connections::systemBus->async_method_call( -+ [asyncResp, callback{std::move(callback)}]( -+ const boost::system::error_code& ec) { -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ callback(); -+ }, -+ telemetry::monitoringService, path, telemetry::reportInterface, -+ "Update"); -+ } -+ -+ static void -+ updateReportIfRequired(const std::shared_ptr<AsyncResp> asyncResp, -+ const std::string& reportPath, -+ const std::string& id) -+ { -+ dbus::utility::getProperty<std::string>( -+ [asyncResp, id, reportPath](const boost::system::error_code& ec, -+ const std::string& reportingType) { -+ if (ec) -+ { -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ if (reportingType == "OnRequest") -+ { -+ updateReport( -+ [asyncResp, reportPath, id] { -+ getReportProperties(asyncResp, reportPath, id); -+ }, -+ asyncResp, reportPath); -+ } -+ else -+ { -+ getReportProperties(asyncResp, reportPath, id); -+ } -+ }, -+ telemetry::monitoringService, reportPath, -+ telemetry::reportInterface, "ReportingType"); -+ } -+ - static constexpr const char* schemaType = - "#MetricReport.v1_3_0.MetricReport"; - }; --- -2.16.6 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch new file mode 100644 index 000000000..08dcb385d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Sync-Telmetry-service-with-EventService.patch @@ -0,0 +1,330 @@ +From 5b775e33221638a34c4aad0e2edeffc447d50fab Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" <jozef.wludzik@intel.com> +Date: Fri, 4 Dec 2020 14:48:41 +0100 +Subject: [PATCH 4/4] Sync Telmetry service with EventService + +Now assembling MetricReport is done properly and is +covered in one place - MetricReport node. +Updated method of fetching Readings from Telemetry by +EventService. Using ReportUpdate signal is no longer +supported. + +Tested: + - Received MetricReport in EventListener server after + adding subscription to EventService. + +Change-Id: I2fc1841a6c9259a8bff30b34bddc0d4aabd41912 +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +--- + redfish-core/include/event_service_manager.hpp | 156 +++++++++---------------- + redfish-core/lib/metric_report.hpp | 35 +++--- + 2 files changed, 74 insertions(+), 117 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 54dafb4..1cdb9a6 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -14,6 +14,7 @@ + // limitations under the License. + */ + #pragma once ++#include "metric_report.hpp" + #include "node.hpp" + #include "registries.hpp" + #include "registries/base_message_registry.hpp" +@@ -510,48 +511,29 @@ class Subscription + } + #endif + +- void filterAndSendReports(const std::string& id2, +- const std::string& readingsTs, +- const ReadingsObjType& readings) ++ void filterAndSendReports( ++ const std::string& id, ++ const std::variant<MetricReport::TimestampReadings>& var) + { +- std::string metricReportDef = +- "/redfish/v1/TelemetryService/MetricReportDefinitions/" + id2; ++ std::string mrdUri = telemetry::metricReportDefinitionUri + id; + + // Empty list means no filter. Send everything. + if (metricReportDefinitions.size()) + { + if (std::find(metricReportDefinitions.begin(), + metricReportDefinitions.end(), +- metricReportDef) == metricReportDefinitions.end()) ++ mrdUri) == metricReportDefinitions.end()) + { + return; + } + } + +- nlohmann::json metricValuesArray = nlohmann::json::array(); +- for (const auto& it : readings) ++ nlohmann::json json; ++ if (!MetricReport::fillReport(json, id, var)) + { +- metricValuesArray.push_back({}); +- nlohmann::json& entry = metricValuesArray.back(); +- +- auto& [id, property, value, timestamp] = it; +- +- entry = {{"MetricId", id}, +- {"MetricProperty", property}, +- {"MetricValue", std::to_string(value)}, +- {"Timestamp", crow::utility::getDateTime(timestamp)}}; ++ return; + } +- +- nlohmann::json msg = { +- {"@odata.id", "/redfish/v1/TelemetryService/MetricReports/" + id}, +- {"@odata.type", "#MetricReport.v1_3_0.MetricReport"}, +- {"Id", id2}, +- {"Name", id2}, +- {"Timestamp", readingsTs}, +- {"MetricReportDefinition", {{"@odata.id", metricReportDef}}}, +- {"MetricValues", metricValuesArray}}; +- +- this->sendEvent(msg.dump()); ++ this->sendEvent(json.dump()); + } + + void updateRetryConfig(const uint32_t retryAttempts, +@@ -1342,56 +1324,71 @@ class EventServiceManager + } + + #endif +- +- void getMetricReading(const std::string& service, +- const std::string& objPath, const std::string& intf) ++ void unregisterMetricReportSignal() + { +- std::size_t found = objPath.find_last_of('/'); +- if (found == std::string::npos) ++ if (matchTelemetryMonitor) + { +- BMCWEB_LOG_DEBUG << "Invalid objPath received"; +- return; ++ BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; ++ matchTelemetryMonitor.reset(); ++ matchTelemetryMonitor = nullptr; + } ++ } + +- std::string idStr = objPath.substr(found + 1); +- if (idStr.empty()) ++ void registerMetricReportSignal() ++ { ++ if (!serviceEnabled || matchTelemetryMonitor) + { +- BMCWEB_LOG_DEBUG << "Invalid ID in objPath"; ++ BMCWEB_LOG_DEBUG << "Not registering metric report signal."; + return; + } + +- crow::connections::systemBus->async_method_call( +- [idStr{std::move(idStr)}]( +- const boost::system::error_code ec, +- boost::container::flat_map< +- std::string, std::variant<int32_t, ReadingsObjType>>& +- resp) { +- if (ec) ++ BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; ++ std::string matchStr = "type='signal',member='PropertiesChanged'," ++ "interface='org.freedesktop.DBus.Properties'," ++ "path_namespace=/xyz/openbmc_project/Telemetry/" ++ "Reports/TelemetryService," ++ "arg0=xyz.openbmc_project.Telemetry.Report"; ++ ++ matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( ++ *crow::connections::systemBus, matchStr, ++ [this](sdbusplus::message::message& msg) { ++ if (msg.is_method_error()) + { +- BMCWEB_LOG_DEBUG +- << "D-Bus call failed to GetAll metric readings."; ++ BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; + return; + } + +- const int32_t* timestampPtr = +- std::get_if<int32_t>(&resp["Timestamp"]); +- if (!timestampPtr) ++ std::string intf; ++ std::vector<std::pair< ++ std::string, std::variant<MetricReport::TimestampReadings>>> ++ props; ++ std::vector<std::string> invalidProp; ++ ++ msg.read(intf, props, invalidProp); ++ if (intf != "xyz.openbmc_project.Telemetry.Report") + { +- BMCWEB_LOG_DEBUG << "Failed to Get timestamp."; + return; + } + +- ReadingsObjType* readingsPtr = +- std::get_if<ReadingsObjType>(&resp["Readings"]); +- if (!readingsPtr) ++ const std::variant<MetricReport::TimestampReadings>* varPtr = ++ nullptr; ++ for (const auto& [key, var] : props) ++ { ++ if (key == "Readings") ++ { ++ varPtr = &var; ++ break; ++ } ++ } ++ if (!varPtr) + { +- BMCWEB_LOG_DEBUG << "Failed to Get Readings property."; + return; + } + +- if (!readingsPtr->size()) ++ std::string id; ++ if (!dbus::utility::getNthStringFromPath(msg.get_path(), 5, id)) + { +- BMCWEB_LOG_DEBUG << "No metrics report to be transferred"; ++ BMCWEB_LOG_ERROR << "Failed to get Id from path"; + return; + } + +@@ -1401,52 +1398,9 @@ class EventServiceManager + std::shared_ptr<Subscription> entry = it.second; + if (entry->eventFormatType == metricReportFormatType) + { +- entry->filterAndSendReports( +- idStr, crow::utility::getDateTime(*timestampPtr), +- *readingsPtr); ++ entry->filterAndSendReports(id, *varPtr); + } + } +- }, +- service, objPath, "org.freedesktop.DBus.Properties", "GetAll", +- intf); +- } +- +- void unregisterMetricReportSignal() +- { +- if (matchTelemetryMonitor) +- { +- BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; +- matchTelemetryMonitor.reset(); +- matchTelemetryMonitor = nullptr; +- } +- } +- +- void registerMetricReportSignal() +- { +- if (!serviceEnabled || matchTelemetryMonitor) +- { +- BMCWEB_LOG_DEBUG << "Not registering metric report signal."; +- return; +- } +- +- BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; +- std::string matchStr( +- "type='signal',member='ReportUpdate', " +- "interface='xyz.openbmc_project.MonitoringService.Report'"); +- +- matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( +- *crow::connections::systemBus, matchStr, +- [this](sdbusplus::message::message& msg) { +- if (msg.is_method_error()) +- { +- BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; +- return; +- } +- +- std::string service = msg.get_sender(); +- std::string objPath = msg.get_path(); +- std::string intf = msg.get_interface(); +- getMetricReading(service, objPath, intf); + }); + } + +diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp +index 050304c..c2013cc 100644 +--- a/redfish-core/lib/metric_report.hpp ++++ b/redfish-core/lib/metric_report.hpp +@@ -52,6 +52,10 @@ class MetricReport : public Node + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + ++ using Readings = ++ std::vector<std::tuple<std::string, std::string, double, uint64_t>>; ++ using TimestampReadings = std::tuple<uint64_t, Readings>; ++ + private: + void doGet(crow::Response& res, const crow::Request&, + const std::vector<std::string>& params) override +@@ -92,7 +96,10 @@ class MetricReport : public Node + return; + } + +- fillReport(asyncResp, id, ret); ++ if (!fillReport(asyncResp->res.jsonValue, id, ret)) ++ { ++ messages::internalError(asyncResp->res); ++ } + }, + telemetry::service, reportPath, + "org.freedesktop.DBus.Properties", "Get", +@@ -102,10 +109,6 @@ class MetricReport : public Node + "Update"); + } + +- using Readings = +- std::vector<std::tuple<std::string, std::string, double, uint64_t>>; +- using TimestampReadings = std::tuple<uint64_t, Readings>; +- + static nlohmann::json toMetricValues(const Readings& readings) + { + nlohmann::json metricValues = nlohmann::json::array_t(); +@@ -130,15 +133,15 @@ class MetricReport : public Node + return metricValues; + } + +- static void fillReport(const std::shared_ptr<AsyncResp>& asyncResp, +- const std::string& id, ++ public: ++ static bool fillReport(nlohmann::json& json, const std::string& id, + const std::variant<TimestampReadings>& var) + { +- asyncResp->res.jsonValue["@odata.type"] = schemaType; +- asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportUri + id; +- asyncResp->res.jsonValue["Id"] = id; +- asyncResp->res.jsonValue["Name"] = id; +- asyncResp->res.jsonValue["MetricReportDefinition"]["@odata.id"] = ++ json["@odata.type"] = schemaType; ++ json["@odata.id"] = telemetry::metricReportUri + id; ++ json["Id"] = id; ++ json["Name"] = id; ++ json["MetricReportDefinition"]["@odata.id"] = + telemetry::metricReportDefinitionUri + id; + + const TimestampReadings* timestampReadings = +@@ -146,14 +149,14 @@ class MetricReport : public Node + if (!timestampReadings) + { + BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; +- messages::internalError(asyncResp->res); +- return; ++ return false; + } + + const auto& [timestamp, readings] = *timestampReadings; +- asyncResp->res.jsonValue["Timestamp"] = ++ json["Timestamp"] = + crow::utility::getDateTime(static_cast<time_t>(timestamp)); +- asyncResp->res.jsonValue["MetricValues"] = toMetricValues(readings); ++ json["MetricValues"] = toMetricValues(readings); ++ return true; + } + + static constexpr const char* schemaType = +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch deleted file mode 100644 index 5b1d93664..000000000 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch +++ /dev/null @@ -1,539 +0,0 @@ -From b369c09460b46902878da10a106e3b3400fd776e Mon Sep 17 00:00:00 2001 -From: "Wludzik, Jozef" <jozef.wludzik@intel.com> -Date: Mon, 8 Jun 2020 17:15:54 +0200 -Subject: [PATCH 09/10] Add support for MetricDefinition scheme - -Added MetricDefinition node to redfish core. Now user is able to -get all possible metrics that are present in system and are -supported by TelemetryService. - -Tested: - - Succesfully passed RedfishServiceValidator.py - - Validated a presence of MetricDefinition members - -Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> -Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> -Change-Id: I3086e1302e1ba2e5442d1367939fd5507a0cbc00 - -%% original patch: 0005-Add-support-for-MetricDefinition-scheme.patch - -Change-Id: Ibcb7a858c9118c8af5ff1167a055b044f0d8db77 ---- - redfish-core/include/redfish.hpp | 3 + - redfish-core/include/utils/telemetry_utils.hpp | 2 + - redfish-core/lib/metric_definition.hpp | 300 +++++++++++++++++++++++++ - redfish-core/lib/metric_report.hpp | 65 +++++- - redfish-core/lib/sensors.hpp | 43 +++- - redfish-core/lib/telemetry_service.hpp | 2 + - 6 files changed, 402 insertions(+), 13 deletions(-) - create mode 100644 redfish-core/lib/metric_definition.hpp - -diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp -index 2587b37..705f490 100644 ---- a/redfish-core/include/redfish.hpp -+++ b/redfish-core/include/redfish.hpp -@@ -25,6 +25,7 @@ - #include "../lib/managers.hpp" - #include "../lib/memory.hpp" - #include "../lib/message_registries.hpp" -+#include "../lib/metric_definition.hpp" - #include "../lib/metric_report.hpp" - #include "../lib/metric_report_definition.hpp" - #include "../lib/network_protocol.hpp" -@@ -211,6 +212,8 @@ class RedfishService - nodes.emplace_back(std::make_unique<HypervisorSystem>(app)); - - nodes.emplace_back(std::make_unique<TelemetryService>(app)); -+ nodes.emplace_back(std::make_unique<MetricDefinitionCollection>(app)); -+ nodes.emplace_back(std::make_unique<MetricDefinition>(app)); - nodes.emplace_back( - std::make_unique<MetricReportDefinitionCollection>(app)); - nodes.emplace_back(std::make_unique<MetricReportDefinition>(app)); -diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp -index 6c4e810..bb747c4 100644 ---- a/redfish-core/include/utils/telemetry_utils.hpp -+++ b/redfish-core/include/utils/telemetry_utils.hpp -@@ -22,6 +22,8 @@ namespace redfish - namespace telemetry - { - -+static constexpr const char* metricDefinitionUri = -+ "/redfish/v1/TelemetryService/MetricDefinitions/"; - static constexpr const char* metricReportDefinitionUri = - "/redfish/v1/TelemetryService/MetricReportDefinitions/"; - static constexpr const char* metricReportUri = -diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp -new file mode 100644 -index 0000000..1417efa ---- /dev/null -+++ b/redfish-core/lib/metric_definition.hpp -@@ -0,0 +1,300 @@ -+/* -+// Copyright (c) 2018-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 "node.hpp" -+#include "sensors.hpp" -+#include "utils/telemetry_utils.hpp" -+ -+namespace redfish -+{ -+ -+namespace chassis -+{ -+template <typename F> -+static inline void getChassisNames(F&& callback) -+{ -+ const std::array<const char*, 2> interfaces = { -+ "xyz.openbmc_project.Inventory.Item.Board", -+ "xyz.openbmc_project.Inventory.Item.Chassis"}; -+ -+ dbus::utility::getSubTreePaths( -+ [callback{std::move(callback)}](const boost::system::error_code ec, -+ std::vector<std::string>& chassisList) { -+ if (ec) -+ { -+ return; -+ } -+ -+ std::vector<std::string> chassisNames; -+ chassisNames.reserve(chassisList.size()); -+ for (auto& chassisPath : chassisList) -+ { -+ auto pos = chassisPath.rfind("/"); -+ if (pos == std::string::npos) -+ { -+ continue; -+ } -+ chassisNames.push_back(chassisPath.substr(pos + 1)); -+ } -+ -+ callback(chassisNames); -+ }, -+ "/xyz/openbmc_project/inventory", 0, interfaces); -+} -+} // namespace chassis -+ -+class MetricDefinitionCollection : public Node -+{ -+ public: -+ MetricDefinitionCollection(App& app) : -+ Node(app, "/redfish/v1/TelemetryService/MetricDefinitions") -+ { -+ entityPrivileges = { -+ {boost::beast::http::verb::get, {{"Login"}}}, -+ {boost::beast::http::verb::head, {{"Login"}}}, -+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; -+ } -+ -+ private: -+ void doGet(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>&) override -+ { -+ res.jsonValue["@odata.type"] = "#MetricDefinitionCollection." -+ "MetricDefinitionCollection"; -+ res.jsonValue["@odata.id"] = -+ "/redfish/v1/TelemetryService/MetricDefinitions"; -+ res.jsonValue["Name"] = "Metric Definition Collection"; -+ res.jsonValue["Members"] = nlohmann::json::array(); -+ res.jsonValue["Members@odata.count"] = sensors::dbus::types.size(); -+ -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ auto collectionReduce = std::make_shared<CollectionGather>(asyncResp); -+ chassis::getChassisNames( -+ [asyncResp, -+ collectionReduce](const std::vector<std::string>& chassisNames) { -+ for (auto& chassisName : chassisNames) -+ { -+ for (auto& [sensorNode, _] : sensors::dbus::types) -+ { -+ BMCWEB_LOG_INFO << "Chassis: " << chassisName -+ << " sensor: " << sensorNode; -+ retrieveUriToDbusMap( -+ chassisName, sensorNode.data(), -+ [asyncResp, collectionReduce]( -+ const boost::beast::http::status, -+ const boost::container::flat_map< -+ std::string, std::string>& uriToDbus) { -+ *collectionReduce += uriToDbus; -+ }); -+ } -+ } -+ }); -+ } -+ -+ class CollectionGather -+ { -+ public: -+ CollectionGather(const std::shared_ptr<AsyncResp>& asyncResp) : -+ asyncResp{asyncResp} -+ { -+ dbusTypes.reserve(sensors::dbus::paths.size()); -+ } -+ -+ ~CollectionGather() -+ { -+ json_util::dbusPathsToMembersArray( -+ asyncResp->res, -+ std::vector<std::string>(dbusTypes.begin(), dbusTypes.end()), -+ telemetry::metricDefinitionUri); -+ } -+ -+ CollectionGather& operator+=( -+ const boost::container::flat_map<std::string, std::string>& rhs) -+ { -+ for (auto& [_, dbusSensor] : rhs) -+ { -+ auto pos = dbusSensor.rfind("/"); -+ if (pos == std::string::npos) -+ { -+ BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " -+ << dbusSensor; -+ continue; -+ } -+ -+ this->dbusTypes.insert(dbusSensor.substr(0, pos)); -+ } -+ return *this; -+ } -+ -+ private: -+ const std::shared_ptr<AsyncResp> asyncResp; -+ boost::container::flat_set<std::string> dbusTypes; -+ }; -+}; -+ -+class MetricDefinition : public Node -+{ -+ public: -+ MetricDefinition(App& app) : -+ Node(app, std::string(telemetry::metricDefinitionUri) + "<str>/", -+ std::string()) -+ { -+ entityPrivileges = { -+ {boost::beast::http::verb::get, {{"Login"}}}, -+ {boost::beast::http::verb::head, {{"Login"}}}, -+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::put, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, -+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; -+ } -+ -+ private: -+ void doGet(crow::Response& res, const crow::Request&, -+ const std::vector<std::string>& params) override -+ { -+ auto asyncResp = std::make_shared<AsyncResp>(res); -+ if (params.size() != 1) -+ { -+ messages::internalError(asyncResp->res); -+ return; -+ } -+ -+ const std::string& id = params[0]; -+ -+ size_t sensorIndex = 0; -+ for (auto& name : sensors::dbus::names) -+ { -+ if (name == id) -+ { -+ break; -+ } -+ sensorIndex++; -+ } -+ if (sensorIndex >= sensors::dbus::max) -+ { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); -+ return; -+ } -+ -+ auto definitionGather = -+ std::make_shared<DefinitionGather>(asyncResp, id); -+ chassis::getChassisNames( -+ [asyncResp, definitionGather, -+ sensorIndex](const std::vector<std::string>& chassisNames) { -+ for (auto& chassisName : chassisNames) -+ { -+ for (auto& [sensorNode, dbusPaths] : sensors::dbus::types) -+ { -+ auto found = -+ std::find(dbusPaths.begin(), dbusPaths.end(), -+ sensors::dbus::paths[sensorIndex]); -+ if (found == dbusPaths.end()) -+ { -+ continue; -+ } -+ -+ retrieveUriToDbusMap( -+ chassisName, sensorNode.data(), -+ [asyncResp, definitionGather]( -+ const boost::beast::http::status, -+ const boost::container::flat_map< -+ std::string, std::string>& uriToDbus) { -+ *definitionGather += uriToDbus; -+ }); -+ } -+ } -+ }); -+ } -+ -+ class DefinitionGather -+ { -+ public: -+ DefinitionGather(const std::shared_ptr<AsyncResp>& asyncResp, -+ const std::string& id) : -+ id(id), -+ asyncResp{asyncResp} -+ {} -+ ~DefinitionGather() -+ { -+ if (redfishSensors.empty()) -+ { -+ messages::resourceNotFound(asyncResp->res, schemaType, id); -+ return; -+ } -+ -+ asyncResp->res.jsonValue["MetricProperties"] = -+ nlohmann::json::array(); -+ auto& members = asyncResp->res.jsonValue["MetricProperties"]; -+ for (auto& redfishSensor : redfishSensors) -+ { -+ members.push_back(redfishSensor); -+ } -+ -+ asyncResp->res.jsonValue["Id"] = id; -+ asyncResp->res.jsonValue["Name"] = id; -+ asyncResp->res.jsonValue["@odata.id"] = -+ telemetry::metricDefinitionUri + id; -+ asyncResp->res.jsonValue["@odata.type"] = schemaType; -+ asyncResp->res.jsonValue["MetricDataType"] = "Decimal"; -+ asyncResp->res.jsonValue["MetricType"] = "Numeric"; -+ asyncResp->res.jsonValue["Implementation"] = "PhysicalSensor"; -+ asyncResp->res.jsonValue["IsLinear"] = true; -+ asyncResp->res.jsonValue["TimestampAccuracy"] = "PT0.1S"; -+ auto unit = sensorUnits.find(id); -+ if (unit != sensorUnits.end()) -+ { -+ asyncResp->res.jsonValue["Units"] = unit->second; -+ } -+ } -+ -+ DefinitionGather& operator+=( -+ const boost::container::flat_map<std::string, std::string>& rhs) -+ { -+ for (auto& [redfishSensor, dbusSensor] : rhs) -+ { -+ if (dbusSensor.find(id) != std::string::npos) -+ { -+ this->redfishSensors.push_back(redfishSensor); -+ } -+ } -+ return *this; -+ } -+ -+ const std::string id; -+ -+ private: -+ const std::shared_ptr<AsyncResp> asyncResp; -+ std::vector<std::string> redfishSensors; -+ const boost::container::flat_map<std::string, std::string> sensorUnits = -+ {{sensors::dbus::names[sensors::dbus::voltage], "V"}, -+ {sensors::dbus::names[sensors::dbus::power], "W"}, -+ {sensors::dbus::names[sensors::dbus::current], "A"}, -+ {sensors::dbus::names[sensors::dbus::fan_tach], "RPM"}, -+ {sensors::dbus::names[sensors::dbus::temperature], "Cel"}, -+ {sensors::dbus::names[sensors::dbus::utilization], "%"}, -+ {sensors::dbus::names[sensors::dbus::fan_pwm], "Duty cycle"}}; -+ }; -+ -+ static constexpr const char* schemaType = -+ "#MetricDefinition.v1_0_3.MetricDefinition"; -+}; -+ -+} // namespace redfish -diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp -index 768cce9..bcb0d3e 100644 ---- a/redfish-core/lib/metric_report.hpp -+++ b/redfish-core/lib/metric_report.hpp -@@ -91,6 +91,9 @@ class MetricReport : public Node - using Readings = - std::vector<std::tuple<std::string, std::string, double, int32_t>>; - using MetricValues = std::vector<std::map<std::string, std::string>>; -+ using ReadingParameters = -+ std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, -+ std::string, std::string, std::string>>; - - static MetricValues toMetricValues(const Readings& readings) - { -@@ -109,6 +112,49 @@ class MetricReport : public Node - return metricValues; - } - -+ static void addMetricDefinition(nlohmann::json& metrics, -+ const ReadingParameters& params) -+ { -+ for (auto& metric : metrics) -+ { -+ if (!metric.contains("MetricId")) -+ { -+ continue; -+ } -+ -+ auto& id = metric["MetricId"].get_ref<std::string&>(); -+ auto param = -+ std::find_if(params.begin(), params.end(), [id](const auto& x) { -+ return id == std::get<2>(x); -+ }); -+ if (param == params.end()) -+ { -+ continue; -+ } -+ -+ auto& dbusPaths = -+ std::get<std::vector<sdbusplus::message::object_path>>(*param); -+ if (dbusPaths.size() > 1) -+ { -+ continue; -+ } -+ -+ auto dbusPath = dbusPaths.begin(); -+ for (size_t i = 0; i < sensors::dbus::paths.size(); i++) -+ { -+ if (dbusPath->str.find(sensors::dbus::paths[i]) == -+ std::string::npos) -+ { -+ continue; -+ } -+ metric["MetricDefinition"]["@odata.id"] = -+ telemetry::metricDefinitionUri + -+ std::string(sensors::dbus::names[i]); -+ break; -+ } -+ } -+ } -+ - static void getReportProperties(const std::shared_ptr<AsyncResp> asyncResp, - const std::string& reportPath, - const std::string& id) -@@ -124,7 +170,8 @@ class MetricReport : public Node - [asyncResp]( - const boost::system::error_code ec, - const boost::container::flat_map< -- std::string, std::variant<Readings, int32_t>>& ret) { -+ std::string, -+ std::variant<Readings, int32_t, ReadingParameters>>& ret) { - if (ec) - { - messages::internalError(asyncResp->res); -@@ -138,6 +185,22 @@ class MetricReport : public Node - json_util::assignIfPresent<Readings>( - ret, "Readings", asyncResp->res.jsonValue["MetricValues"], - toMetricValues); -+ -+ auto found = ret.find("ReadingParameters"); -+ if (found != ret.end()) -+ { -+ auto params = -+ std::get_if<ReadingParameters>(&found->second); -+ if (params) -+ { -+ auto& jsonValue = asyncResp->res.jsonValue; -+ if (jsonValue.contains("MetricValues")) -+ { -+ addMetricDefinition(jsonValue["MetricValues"], -+ *params); -+ } -+ } -+ } - }, - "xyz.openbmc_project.MonitoringService", reportPath, - "xyz.openbmc_project.MonitoringService.Report"); -diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp -index 567cb0c..2f7f70b 100644 ---- a/redfish-core/lib/sensors.hpp -+++ b/redfish-core/lib/sensors.hpp -@@ -54,20 +54,39 @@ static constexpr std::string_view thermal = "Thermal"; - - namespace dbus - { -+ -+enum Index -+{ -+ voltage = 0, -+ power, -+ current, -+ fan_tach, -+ temperature, -+ fan_pwm, -+ utilization, -+ max -+}; -+ -+static constexpr std::array<const char*, max> names = { -+ "voltage", "power", "current", "fan_tach", -+ "temperature", "fan_pwm", "utilization"}; -+ -+static constexpr std::array<const char*, max> paths = { -+ "/xyz/openbmc_project/sensors/voltage", -+ "/xyz/openbmc_project/sensors/power", -+ "/xyz/openbmc_project/sensors/current", -+ "/xyz/openbmc_project/sensors/fan_tach", -+ "/xyz/openbmc_project/sensors/temperature", -+ "/xyz/openbmc_project/sensors/fan_pwm", -+ "/xyz/openbmc_project/sensors/utilization"}; -+ - static const boost::container::flat_map<std::string_view, - std::vector<const char*>> -- types = {{node::power, -- {"/xyz/openbmc_project/sensors/voltage", -- "/xyz/openbmc_project/sensors/power"}}, -- {node::sensors, -- {"/xyz/openbmc_project/sensors/power", -- "/xyz/openbmc_project/sensors/current", -- "/xyz/openbmc_project/sensors/utilization"}}, -- {node::thermal, -- {"/xyz/openbmc_project/sensors/fan_tach", -- "/xyz/openbmc_project/sensors/temperature", -- "/xyz/openbmc_project/sensors/fan_pwm"}}}; --} -+ types = { -+ {node::power, {paths[voltage], paths[power]}}, -+ {node::sensors, {paths[power], paths[current], paths[utilization]}}, -+ {node::thermal, {paths[fan_tach], paths[temperature], paths[fan_pwm]}}}; -+} // namespace dbus - } // namespace sensors - - /** -diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp -index b849781..efbef6e 100644 ---- a/redfish-core/lib/telemetry_service.hpp -+++ b/redfish-core/lib/telemetry_service.hpp -@@ -52,6 +52,8 @@ class TelemetryService : public Node - - res.jsonValue["LogService"]["@odata.id"] = - "/redfish/v1/Managers/bmc/LogServices/Journal"; -+ res.jsonValue["MetricDefinitions"]["@odata.id"] = -+ "/redfish/v1/TelemetryService/MetricDefinitions"; - res.jsonValue["MetricReportDefinitions"]["@odata.id"] = - "/redfish/v1/TelemetryService/MetricReportDefinitions"; - res.jsonValue["MetricReports"]["@odata.id"] = --- -2.16.6 - diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README index 2929b0aec..833fabfec 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README @@ -3,19 +3,14 @@ Until change is integrated they will be manually merged here to enable feature i Current revisions: - Redfish TelemetryService schema implementation - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/31692/29 + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/31692/54 -- Add support for POST in MetricReportDefinitions - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32536/24 - -- Add support for DELETE in MetricReportDefinitions/<str> - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32537/23 - -- Add support for "OnRequest" in MetricReportDefinition - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33358/17 +- Add POST and DELETE in MetricReportDefinitions + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/32536/46 - Add support for MetricDefinition scheme - https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/20 + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/42 + +- Sync Telmetry service with EventService + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/38798/9 -- Temporary patch for EventService because of change in design - 0006-Fix-MetricReport-timestamp-for-EventService.patch diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend index 80005f5cd..ac095ba61 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend @@ -1,7 +1,8 @@ SRC_URI = "git://github.com/openbmc/bmcweb.git" -SRCREV = "a8fe54f09be1deefc119d8dcf100da922496d46d" +SRCREV = "f16f62633a64f386fd0382703ff0949ea177f457" DEPENDS += "boost-url" +RDEPENDS_${PN} += "phosphor-nslcd-authority-cert-config" FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" @@ -15,14 +16,20 @@ SRC_URI += "file://0001-Firmware-update-configuration-changes.patch \ file://0002-Use-chip-id-based-UUID-for-Service-Root.patch \ file://0004-bmcweb-handle-device-or-resource-busy-exception.patch \ file://0005-EventService-https-client-support.patch \ + file://0006-Define-Redfish-interface-Registries-Bios.patch \ + file://0007-BIOS-config-Add-support-for-PATCH-operation.patch \ + file://0008-Add-support-to-ResetBios-action.patch \ + file://0009-Add-support-to-ChangePassword-action.patch \ + file://0010-managers-add-attributes-for-Manager.CommandShell.patch \ + file://0034-recommended-fixes-by-crypto-review-team.patch \ " + # Temporary downstream mirror of upstream patches, see telemetry\README for details SRC_URI += "file://telemetry/0001-Redfish-TelemetryService-schema-implementation.patch \ - file://telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch \ - file://telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch \ - file://telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch \ - file://telemetry/0005-Add-support-for-MetricDefinition-scheme.patch \ + file://telemetry/0002-Add-POST-and-DELETE-in-MetricReportDefinitions.patch \ + file://telemetry/0003-Add-support-for-MetricDefinition-scheme.patch \ + file://telemetry/0004-Sync-Telmetry-service-with-EventService.patch \ " # Temporary fix: Move it to service file |