diff options
author | jmbills <42755197+jmbills@users.noreply.github.com> | 2020-09-08 23:40:52 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-08 23:40:52 +0300 |
commit | c16fb8893b19075db4bcf3b5bf33c1db8c3ca2bd (patch) | |
tree | ca75379d317be9cc1757a00e0352a048b5d3200b /meta-openbmc-mods/meta-common/recipes-phosphor | |
parent | d81f2708c4462a62e3b5e8d08643eb2b8895dde7 (diff) | |
parent | f99301c1a626951ee7feee081a1494e795d0e243 (diff) | |
download | openbmc-c16fb8893b19075db4bcf3b5bf33c1db8c3ca2bd.tar.xz |
Merge pull request #47 from Intel-BMC/update
Update to internal 0.74
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor')
28 files changed, 3884 insertions, 40 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/configuration/entity-manager_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/configuration/entity-manager_%.bbappend index e76c23fa6..cf402206b 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/configuration/entity-manager_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/configuration/entity-manager_%.bbappend @@ -1,6 +1,6 @@ # this is here just to bump faster than upstream SRC_URI = "git://github.com/openbmc/entity-manager.git" -SRCREV = "ac09fe44dba113aacee1d2fd995e137c0d5ea18e" +SRCREV = "e18edb5badc2e16181cfc464a6ccd0ef51dc4548" FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/dbus/phosphor-dbus-interfaces/0028-MCTP-Daemon-D-Bus-interface-definition.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/dbus/phosphor-dbus-interfaces/0028-MCTP-Daemon-D-Bus-interface-definition.patch index dc44d7a0d..e6afc0117 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/dbus/phosphor-dbus-interfaces/0028-MCTP-Daemon-D-Bus-interface-definition.patch +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/dbus/phosphor-dbus-interfaces/0028-MCTP-Daemon-D-Bus-interface-definition.patch @@ -1,4 +1,4 @@ -From 9bcc45f1a4d872e9968fdebf675c5fff6921c2f3 Mon Sep 17 00:00:00 2001 +From 65d1772312a76ebfdac6391bb97287c62f18c43f Mon Sep 17 00:00:00 2001 From: "Kowalski, Mariusz" <mariusz.kowalski@intel.com> Date: Thu, 27 Feb 2020 15:48:56 +0100 Subject: [PATCH] MCTP Daemon D-Bus interface definition. @@ -12,14 +12,14 @@ Signed-off-by: Mariusz Kowalski <mariusz.kowalski@intel.com> Signed-off-by: Karol Wachowski <karol.wachowski@intel.com> Change-Id: Ida66f8ffcf00003655edcb0fb0112202797b8e1a --- - xyz/openbmc_project/MCTP/Base.interface.yaml | 231 ++++++++++++++++++ + xyz/openbmc_project/MCTP/Base.interface.yaml | 227 ++++++++++++++++++ .../MCTP/Binding/PCIe.interface.yaml | 29 +++ .../MCTP/Binding/SMBus.interface.yaml | 17 ++ .../MCTP/BusOwner.interface.yaml | 17 ++ - .../MCTP/Endpoint.interface.yaml | 19 ++ - xyz/openbmc_project/MCTP/README.md | 38 +++ + .../MCTP/Endpoint.interface.yaml | 13 + + xyz/openbmc_project/MCTP/README.md | 43 ++++ .../MCTP/SupportedMessageTypes.interface.yaml | 36 +++ - 7 files changed, 387 insertions(+) + 7 files changed, 382 insertions(+) create mode 100644 xyz/openbmc_project/MCTP/Base.interface.yaml create mode 100644 xyz/openbmc_project/MCTP/Binding/PCIe.interface.yaml create mode 100644 xyz/openbmc_project/MCTP/Binding/SMBus.interface.yaml @@ -30,10 +30,10 @@ Change-Id: Ida66f8ffcf00003655edcb0fb0112202797b8e1a diff --git a/xyz/openbmc_project/MCTP/Base.interface.yaml b/xyz/openbmc_project/MCTP/Base.interface.yaml new file mode 100644 -index 0000000..f27c1c7 +index 0000000..9438551 --- /dev/null +++ b/xyz/openbmc_project/MCTP/Base.interface.yaml -@@ -0,0 +1,231 @@ +@@ -0,0 +1,227 @@ +description: > + Mandatory interface for each instance of the MCTP Daemon to expose + the base MCTP daemon and medium type interfaces. @@ -153,10 +153,6 @@ index 0000000..f27c1c7 + type: boolean + description: Support for statically/dynamicly allocated IDs + -+ - name: Uuid -+ type: array[byte] -+ description: Guid - 16bytes -+ + - name: BindingMode + type: enum[self.BindingModeTypes] + description: Bus Owner / Endpoint / Bridge @@ -348,20 +344,14 @@ index 0000000..d46298e + description: Indicates Eid pool is managed by self diff --git a/xyz/openbmc_project/MCTP/Endpoint.interface.yaml b/xyz/openbmc_project/MCTP/Endpoint.interface.yaml new file mode 100644 -index 0000000..b7f9a67 +index 0000000..e4ba4d0 --- /dev/null +++ b/xyz/openbmc_project/MCTP/Endpoint.interface.yaml -@@ -0,0 +1,19 @@ +@@ -0,0 +1,13 @@ +description: + Interface exposed by discovered MCTP endpoints. + +properties: -+ - name: Uuid -+ type: array[byte] -+ description: > -+ Universally unique identifier (UUID), also referred to as a globally -+ unique ID (GUID), for the management controller or management device. -+ + - name: Mode + type: enum[xyz.openbmc_project.MCTP.Base.BindingModeTypes] + description: Endpoint / BusOwner / Bridge @@ -373,10 +363,10 @@ index 0000000..b7f9a67 + MCTP network within a platform. diff --git a/xyz/openbmc_project/MCTP/README.md b/xyz/openbmc_project/MCTP/README.md new file mode 100644 -index 0000000..3255ef2 +index 0000000..c819dbb --- /dev/null +++ b/xyz/openbmc_project/MCTP/README.md -@@ -0,0 +1,38 @@ +@@ -0,0 +1,43 @@ +# MCTP Daemon + +## Overview @@ -405,16 +395,21 @@ index 0000000..3255ef2 +4. Binding interface `xyz.openbmc_project.MCTP.Binding.PCIe` or +`xyz.openbmc_project.MCTP.Binding.SMBus` as per the physical medium in which +this MCTP Daemon is instantiated. ++5. Common UUID interface `xyz.openbmc_project.Common.UUID` which exposes UUID ++in RFC4122 format. + +#### Endpoint object +Exposed under the path `/xyz/openbmc_project/mctp/device/<eid>` with the +following interfaces. +1. `xyz.openbmc_project.MCTP.SupportedMessageTypes` which exposes supported MCTP +message types for the discovered MCTP Endpoint. -+2. `xyz.openbmc_project.MCTP.Endpoint` which exposes properties like UUID and endpoint -+mode (to identify Bus Owner or Bridge or Endpoint) for the discovered MCTP Endpoint. ++2. `xyz.openbmc_project.MCTP.Endpoint` which exposes properties like Network ID ++and endpoint mode (to identify Bus Owner or Bridge or Endpoint) for the discovered ++MCTP Endpoint. +3. `xyz.openbmc_project.MCTP.Bridge` available only for discovered MCTP Bridges to +expose properties like EID pool. (TBD) ++4. Common UUID interface `xyz.openbmc_project.Common.UUID` which exposes UUID ++in RFC4122 format. diff --git a/xyz/openbmc_project/MCTP/SupportedMessageTypes.interface.yaml b/xyz/openbmc_project/MCTP/SupportedMessageTypes.interface.yaml new file mode 100644 index 0000000..fa447ee diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/fans/phosphor-pid-control_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/fans/phosphor-pid-control_%.bbappend index 5efc383f0..2e4e23d40 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/fans/phosphor-pid-control_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/fans/phosphor-pid-control_%.bbappend @@ -5,6 +5,6 @@ SYSTEMD_SERVICE_${PN} = "phosphor-pid-control.service" EXTRA_OECONF = "--enable-configure-dbus=yes" SRC_URI = "git://github.com/openbmc/phosphor-pid-control.git" -SRCREV = "4b36f265a10048127d93e4b70916c181827c9af2" +SRCREV = "6fc301fbc3775730a0e69f215110ec93bd9026f3" FILES_${PN} = "${bindir}/swampd ${bindir}/setsensor" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/flash/phosphor-software-manager/0013-remove-image-file-on-pre-script-failures.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/flash/phosphor-software-manager/0013-remove-image-file-on-pre-script-failures.patch new file mode 100644 index 000000000..d5d0f513e --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/flash/phosphor-software-manager/0013-remove-image-file-on-pre-script-failures.patch @@ -0,0 +1,40 @@ +From ae3a9616b44677f20b4ca534c3f55ccb478fdf55 Mon Sep 17 00:00:00 2001 +From: Vernon Mauery <vernon.mauery@intel.com> +Date: Thu, 16 Jul 2020 14:16:28 -0700 +Subject: [PATCH] remove image file on pre-script failures + +Multiple back-to-back updates of bad images will cause the software +manager to leave junk images hanging around. This is part of a fix that +will remove them if the software manager never gets around to launching +the fwupd.sh script. The other part is that the fwupd.sh script must +always delete the image file on exit, success or failure. + +Tested: posted a garbage file, saw that it was deleted even though + fwupd.sh was never invoked. + +Change-Id: I6b049916a3edcb48f9d4ebe0d4715b94214b4feb +Signed-off-by: Vernon Mauery <vernon.mauery@intel.com> +--- + watch.cpp | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/watch.cpp b/watch.cpp +index ccdf594..c738945 100644 +--- a/watch.cpp ++++ b/watch.cpp +@@ -106,6 +106,15 @@ int Watch::callback(sd_event_source* /* s */, int fd, uint32_t revents, + { + log<level::ERR>("Error processing image", + entry("IMAGE=%s", tarballPath.c_str())); ++ std::error_code ec{}; ++ fs::remove_all(tarballPath, ec); ++ if (!ec) ++ { ++ log<level::ERR>( ++ "Unable to remove image on processing failure", ++ entry("ERROR=%s", ec.message().c_str()), ++ entry("IMAGE=%s", tarballPath.c_str())); ++ } + } + } + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/flash/phosphor-software-manager_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/flash/phosphor-software-manager_%.bbappend index ae108bf70..623c4f77e 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/flash/phosphor-software-manager_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/flash/phosphor-software-manager_%.bbappend @@ -12,12 +12,13 @@ SRC_URI += "file://0001-Add-more-error-types.patch \ file://0006-Modify-the-ID-of-software-image-updater-object-on-DB.patch \ file://0007-Adding-StandBySpare-for-firmware-activation.patch \ file://0008-item_updater-update-the-bmc_active-objectPath.patch \ + file://0009-Add-ApplyOptions-D-bus-property-under-Software.patch \ file://0011-Fix-for-RedudancyPriority-in-item_updater.patch \ + file://0013-remove-image-file-on-pre-script-failures.patch \ " SRC_URI_PFR = "file://0007-PFR-images-support.patch \ file://0008-PFR-image-HASH-verification.patch \ - file://0009-Add-ApplyOptions-D-bus-property-under-Software.patch \ file://0010-Add-error-reporting-to-pfr_image_manager.patch \ " diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-Remove-QueryString.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-Remove-QueryString.patch new file mode 100644 index 000000000..238fb83c7 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-Remove-QueryString.patch @@ -0,0 +1,621 @@ +From f8749b5898403ee04a623a5bc534bd939865e221 Mon Sep 17 00:00:00 2001 +From: James Feist <james.feist@linux.intel.com> +Date: Wed, 22 Jul 2020 09:08:38 -0700 +Subject: [PATCH 1/1] Remove QueryString + +QueryString is an error-prone library that was +leftover from crow. Replace it with boost::url, +a header only library based and written by the +one of the authors of boost beast. + +Tested: Verified logging paging still worked +as expected + +Change-Id: I47c225089aa7d0f7d2299142f91806294f879381 +Signed-off-by: James Feist <james.feist@linux.intel.com> +--- + CMakeLists.txt | 2 + + CMakeLists.txt.in | 10 + + http/http_connection.h | 21 +- + http/http_request.h | 5 +- + http/query_string.h | 421 ----------------------------- + redfish-core/lib/event_service.hpp | 7 +- + redfish-core/lib/log_services.hpp | 20 +- + 7 files changed, 45 insertions(+), 441 deletions(-) + delete mode 100644 http/query_string.h + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 2886438..50483ad 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -280,6 +280,8 @@ add_definitions (-DBOOST_ALL_NO_LIB) + add_definitions (-DBOOST_NO_RTTI) + add_definitions (-DBOOST_NO_TYPEID) + add_definitions (-DBOOST_COROUTINES_NO_DEPRECATION_WARNING) ++add_definitions (-DBOOST_URL_STANDALONE) ++add_definitions (-DBOOST_URL_HEADER_ONLY) + + # sdbusplus + if (NOT ${YOCTO_DEPENDENCIES}) +diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in +index d14910f..5cd73f6 100644 +--- a/CMakeLists.txt.in ++++ b/CMakeLists.txt.in +@@ -53,3 +53,13 @@ externalproject_add ( + cp -r "${CMAKE_BINARY_DIR}/nlohmann-json-src/include/nlohmann" + "${CMAKE_BINARY_DIR}/prefix/include" + ) ++ ++externalproject_add ( ++ Boost-URL GIT_REPOSITORY "https://github.com/CPPAlliance/url.git" GIT_TAG ++ a56ae0df6d3078319755fbaa67822b4fa7fd352b SOURCE_DIR ++ "${CMAKE_BINARY_DIR}/boost-url-src" BINARY_DIR ++ "${CMAKE_BINARY_DIR}/boost-url-build" CONFIGURE_COMMAND "" BUILD_COMMAND ++ "" INSTALL_COMMAND mkdir -p "${CMAKE_BINARY_DIR}/prefix/include" && ++ cp -r "${CMAKE_BINARY_DIR}/boost-url-src/include/boost" ++ "${CMAKE_BINARY_DIR}/prefix/include" ++) +diff --git a/http/http_connection.h b/http/http_connection.h +index 35bf99c..8dba3d6 100644 +--- a/http/http_connection.h ++++ b/http/http_connection.h +@@ -728,13 +728,9 @@ class Connection : + return; + } + +- // Compute the url parameters for the request +- req->url = req->target(); +- std::size_t index = req->url.find("?"); +- if (index != std::string_view::npos) +- { +- req->url = req->url.substr(0, index); +- } ++ req->urlView = boost::urls::url_view(req->target()); ++ req->url = req->urlView.encoded_path(); ++ + crow::authorization::authenticate(*req, res, session); + + bool loggedIn = req && req->session; +@@ -743,7 +739,16 @@ class Connection : + startDeadline(loggedInAttempts); + BMCWEB_LOG_DEBUG << "Starting slow deadline"; + +- req->urlParams = QueryString(std::string(req->target())); ++ req->urlParams = req->urlView.params(); ++ ++#ifdef BMCWEB_ENABLE_DEBUG ++ std::string paramList = ""; ++ for (const auto param : req->urlParams) ++ { ++ paramList += param->key() + " " + param->value() + " "; ++ } ++ BMCWEB_LOG_DEBUG << "QueryParams: " << paramList; ++#endif + } + else + { +diff --git a/http/http_request.h b/http/http_request.h +index 0691465..95f88c7 100644 +--- a/http/http_request.h ++++ b/http/http_request.h +@@ -1,7 +1,6 @@ + #pragma once + + #include "common.h" +-#include "query_string.h" + + #include "sessions.hpp" + +@@ -9,6 +8,7 @@ + #include <boost/beast/http.hpp> + #include <boost/beast/ssl/ssl_stream.hpp> + #include <boost/beast/websocket.hpp> ++#include <boost/url/url_view.hpp> + + namespace crow + { +@@ -24,7 +24,8 @@ struct Request + boost::beast::http::request<boost::beast::http::string_body>& req; + boost::beast::http::fields& fields; + std::string_view url{}; +- QueryString urlParams{}; ++ boost::urls::url_view urlView{}; ++ boost::urls::url_view::params_type urlParams{}; + bool isSecure{false}; + + const std::string& body; +diff --git a/http/query_string.h b/http/query_string.h +deleted file mode 100644 +index e980280..0000000 +--- a/http/query_string.h ++++ /dev/null +@@ -1,421 +0,0 @@ +-#pragma once +- +-#include <cstdio> +-#include <cstring> +-#include <iostream> +-#include <string> +-#include <vector> +- +-namespace crow +-{ +-// ---------------------------------------------------------------------------- +-// qs_parse (modified) +-// https://github.com/bartgrantham/qs_parse +-// ---------------------------------------------------------------------------- +-/* Similar to strncmp, but handles URL-encoding for either string */ +-int qsStrncmp(const char* s, const char* qs, size_t n); +- +-/* Finds the beginning of each key/value pair and stores a pointer in qs_kv. +- * Also decodes the value portion of the k/v pair *in-place*. In a future +- * enhancement it will also have a compile-time option of sorting qs_kv +- * alphabetically by key. */ +-size_t qsParse(char* qs, char* qs_kv[], size_t qs_kv_size); +- +-/* Used by qs_parse to decode the value portion of a k/v pair */ +-int qsDecode(char* qs); +- +-/* Looks up the value according to the key on a pre-processed query string +- * A future enhancement will be a compile-time option to look up the key +- * in a pre-sorted qs_kv array via a binary search. */ +-// char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size); +-char* qsK2v(const char* key, char* const* qs_kv, int qs_kv_size, int nth); +- +-/* Non-destructive lookup of value, based on key. User provides the +- * destinaton string and length. */ +-char* qsScanvalue(const char* key, const char* qs, char* val, size_t val_len); +- +-// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled +-#undef _qsSORTING +- +-// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead +-#define BMCWEB_QS_ISHEX(x) \ +- ((((x) >= '0' && (x) <= '9') || ((x) >= 'A' && (x) <= 'F') || \ +- ((x) >= 'a' && (x) <= 'f')) \ +- ? 1 \ +- : 0) +-#define BMCWEB_QS_HEX2DEC(x) \ +- (((x) >= '0' && (x) <= '9') \ +- ? (x)-48 \ +- : ((x) >= 'A' && (x) <= 'F') \ +- ? (x)-55 \ +- : ((x) >= 'a' && (x) <= 'f') ? (x)-87 : 0) +-#define BMCWEB_QS_ISQSCHR(x) \ +- ((((x) == '=') || ((x) == '#') || ((x) == '&') || ((x) == '\0')) ? 0 : 1) +- +-inline int qsStrncmp(const char* s, const char* qs, size_t n) +-{ +- int i = 0; +- char u1, u2; +- char unyb, lnyb; +- +- while (n-- > 0) +- { +- u1 = *s++; +- u2 = *qs++; +- +- if (!BMCWEB_QS_ISQSCHR(u1)) +- { +- u1 = '\0'; +- } +- if (!BMCWEB_QS_ISQSCHR(u2)) +- { +- u2 = '\0'; +- } +- +- if (u1 == '+') +- { +- u1 = ' '; +- } +- if (u1 == '%') // easier/safer than scanf +- { +- unyb = static_cast<char>(*s++); +- lnyb = static_cast<char>(*s++); +- if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb)) +- { +- u1 = static_cast<char>((BMCWEB_QS_HEX2DEC(unyb) * 16) + +- BMCWEB_QS_HEX2DEC(lnyb)); +- } +- else +- { +- u1 = '\0'; +- } +- } +- +- if (u2 == '+') +- { +- u2 = ' '; +- } +- if (u2 == '%') // easier/safer than scanf +- { +- unyb = static_cast<char>(*qs++); +- lnyb = static_cast<char>(*qs++); +- if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb)) +- { +- u2 = static_cast<char>((BMCWEB_QS_HEX2DEC(unyb) * 16) + +- BMCWEB_QS_HEX2DEC(lnyb)); +- } +- else +- { +- u2 = '\0'; +- } +- } +- +- if (u1 != u2) +- { +- return u1 - u2; +- } +- if (u1 == '\0') +- { +- return 0; +- } +- i++; +- } +- if (BMCWEB_QS_ISQSCHR(*qs)) +- { +- return -1; +- } +- else +- { +- return 0; +- } +-} +- +-inline size_t qsParse(char* qs, char* qs_kv[], size_t qs_kv_size) +-{ +- size_t i; +- size_t j; +- char* substrPtr; +- +- for (i = 0; i < qs_kv_size; i++) +- { +- qs_kv[i] = nullptr; +- } +- +- // find the beginning of the k/v substrings or the fragment +- substrPtr = qs + strcspn(qs, "?#"); +- if (substrPtr[0] != '\0') +- { +- substrPtr++; +- } +- else +- { +- return 0; // no query or fragment +- } +- +- i = 0; +- while (i < qs_kv_size) +- { +- qs_kv[i++] = substrPtr; +- j = strcspn(substrPtr, "&"); +- if (substrPtr[j] == '\0') +- { +- break; +- } +- substrPtr += j + 1; +- } +- +- // we only decode the values in place, the keys could have '='s in them +- // which will hose our ability to distinguish keys from values later +- for (j = 0; j < i; j++) +- { +- substrPtr = qs_kv[j] + strcspn(qs_kv[j], "=&#"); +- if (substrPtr[0] == '&' || substrPtr[0] == '\0') +- { // blank value: skip decoding +- substrPtr[0] = '\0'; +- } +- else +- { +- qsDecode(++substrPtr); +- } +- } +- +-#ifdef _qsSORTING +-// TODO: qsort qs_kv, using qs_strncmp() for the comparison +-#endif +- +- return i; +-} +- +-inline int qsDecode(char* qs) +-{ +- int i = 0, j = 0; +- +- while (BMCWEB_QS_ISQSCHR(qs[j])) +- { +- if (qs[j] == '+') +- { +- qs[i] = ' '; +- } +- else if (qs[j] == '%') // easier/safer than scanf +- { +- if (!BMCWEB_QS_ISHEX(qs[j + 1]) || !BMCWEB_QS_ISHEX(qs[j + 2])) +- { +- qs[i] = '\0'; +- return i; +- } +- qs[i] = static_cast<char>((BMCWEB_QS_HEX2DEC(qs[j + 1]) * 16) + +- BMCWEB_QS_HEX2DEC(qs[j + 2])); +- j += 2; +- } +- else +- { +- qs[i] = qs[j]; +- } +- i++; +- j++; +- } +- qs[i] = '\0'; +- +- return i; +-} +- +-inline char* qsK2v(const char* key, char* const* qs_kv, int qs_kv_size, +- int nth = 0) +-{ +- int i; +- size_t keyLen, skip; +- +- keyLen = strlen(key); +- +-#ifdef _qsSORTING +-// TODO: binary search for key in the sorted qs_kv +-#else // _qsSORTING +- for (i = 0; i < qs_kv_size; i++) +- { +- // we rely on the unambiguous '=' to find the value in our k/v pair +- if (qsStrncmp(key, qs_kv[i], keyLen) == 0) +- { +- skip = strcspn(qs_kv[i], "="); +- if (qs_kv[i][skip] == '=') +- { +- skip++; +- } +- // return (zero-char value) ? ptr to trailing '\0' : ptr to value +- if (nth == 0) +- { +- return qs_kv[i] + skip; +- } +- else +- { +- --nth; +- } +- } +- } +-#endif // _qsSORTING +- +- return nullptr; +-} +- +-inline char* qsScanvalue(const char* key, const char* qs, char* val, +- size_t val_len) +-{ +- size_t i, keyLen; +- const char* tmp; +- +- // find the beginning of the k/v substrings +- if ((tmp = strchr(qs, '?')) != nullptr) +- { +- qs = tmp + 1; +- } +- +- keyLen = strlen(key); +- while (qs[0] != '#' && qs[0] != '\0') +- { +- if (qsStrncmp(key, qs, keyLen) == 0) +- { +- break; +- } +- qs += strcspn(qs, "&") + 1; +- } +- +- if (qs[0] == '\0') +- { +- return nullptr; +- } +- +- qs += strcspn(qs, "=&#"); +- if (qs[0] == '=') +- { +- qs++; +- i = strcspn(qs, "&=#"); +- strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1)); +- qsDecode(val); +- } +- else +- { +- if (val_len > 0) +- { +- val[0] = '\0'; +- } +- } +- +- return val; +-} +-} // namespace crow +-// ---------------------------------------------------------------------------- +- +-namespace crow +-{ +-class QueryString +-{ +- public: +- static const size_t maxKeyValuePairsCount = 256; +- +- QueryString() = default; +- +- QueryString(const QueryString& qs) : url(qs.url) +- { +- for (auto p : qs.keyValuePairs) +- { +- keyValuePairs.push_back( +- const_cast<char*>(p - qs.url.c_str() + url.c_str())); +- } +- } +- +- QueryString& operator=(const QueryString& qs) +- { +- if (this == &qs) +- { +- return *this; +- } +- +- url = qs.url; +- keyValuePairs.clear(); +- for (auto p : qs.keyValuePairs) +- { +- keyValuePairs.push_back( +- const_cast<char*>(p - qs.url.c_str() + url.c_str())); +- } +- return *this; +- } +- +- QueryString& operator=(QueryString&& qs) +- { +- keyValuePairs = std::move(qs.keyValuePairs); +- auto* oldData = const_cast<char*>(qs.url.c_str()); +- url = std::move(qs.url); +- for (auto& p : keyValuePairs) +- { +- p += const_cast<char*>(url.c_str()) - oldData; +- } +- return *this; +- } +- +- explicit QueryString(std::string newUrl) : url(std::move(newUrl)) +- { +- if (url.empty()) +- { +- return; +- } +- +- keyValuePairs.resize(maxKeyValuePairsCount); +- +- size_t count = +- qsParse(&url[0], &keyValuePairs[0], maxKeyValuePairsCount); +- keyValuePairs.resize(count); +- } +- +- void clear() +- { +- keyValuePairs.clear(); +- url.clear(); +- } +- +- friend std::ostream& operator<<(std::ostream& os, const QueryString& qs) +- { +- os << "[ "; +- for (size_t i = 0; i < qs.keyValuePairs.size(); ++i) +- { +- if (i != 0u) +- { +- os << ", "; +- } +- os << qs.keyValuePairs[i]; +- } +- os << " ]"; +- return os; +- } +- +- char* get(const std::string& name) const +- { +- char* ret = qsK2v(name.c_str(), keyValuePairs.data(), +- static_cast<int>(keyValuePairs.size())); +- return ret; +- } +- +- std::vector<char*> getList(const std::string& name) const +- { +- std::vector<char*> ret; +- std::string plus = name + "[]"; +- char* element = nullptr; +- +- int count = 0; +- while (true) +- { +- element = qsK2v(plus.c_str(), keyValuePairs.data(), +- static_cast<int>(keyValuePairs.size()), count++); +- if (element == nullptr) +- { +- break; +- } +- ret.push_back(element); +- } +- return ret; +- } +- +- private: +- std::string url; +- std::vector<char*> keyValuePairs; +-}; +- +-} // namespace crow +diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp +index b27c6e0..8bd30f5 100644 +--- a/redfish-core/lib/event_service.hpp ++++ b/redfish-core/lib/event_service.hpp +@@ -445,13 +445,16 @@ class EventServiceSSE : public Node + subValue->protocol = "Redfish"; + subValue->retryPolicy = "TerminateAfterRetries"; + +- char* filters = req.urlParams.get("$filter"); +- if (filters == nullptr) ++ boost::urls::url_view::params_type::iterator it = ++ req.urlParams.find("$filter"); ++ if (it == req.urlParams.end()) + { + subValue->eventFormatType = "Event"; + } ++ + else + { ++ std::string filters = it->value(); + // Reading from query params. + bool status = readSSEQueryParams( + filters, subValue->eventFormatType, subValue->registryMsgIds, +diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp +index bee1a92..590243c 100644 +--- a/redfish-core/lib/log_services.hpp ++++ b/redfish-core/lib/log_services.hpp +@@ -218,12 +218,14 @@ static bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp) + static bool getSkipParam(crow::Response& res, const crow::Request& req, + uint64_t& skip) + { +- char* skipParam = req.urlParams.get("$skip"); +- if (skipParam != nullptr) ++ boost::urls::url_view::params_type::iterator it = ++ req.urlParams.find("$skip"); ++ if (it != req.urlParams.end()) + { ++ std::string skipParam = it->value(); + char* ptr = nullptr; +- skip = std::strtoul(skipParam, &ptr, 10); +- if (*skipParam == '\0' || *ptr != '\0') ++ skip = std::strtoul(skipParam.c_str(), &ptr, 10); ++ if (skipParam.empty() || *ptr != '\0') + { + + messages::queryParameterValueTypeError(res, std::string(skipParam), +@@ -238,12 +240,14 @@ static constexpr const uint64_t maxEntriesPerPage = 1000; + static bool getTopParam(crow::Response& res, const crow::Request& req, + uint64_t& top) + { +- char* topParam = req.urlParams.get("$top"); +- if (topParam != nullptr) ++ boost::urls::url_view::params_type::iterator it = ++ req.urlParams.find("$top"); ++ if (it != req.urlParams.end()) + { ++ std::string topParam = it->value(); + char* ptr = nullptr; +- top = std::strtoul(topParam, &ptr, 10); +- if (*topParam == '\0' || *ptr != '\0') ++ top = std::strtoul(topParam.c_str(), &ptr, 10); ++ if (topParam.empty() || *ptr != '\0') + { + messages::queryParameterValueTypeError(res, std::string(topParam), + "$top"); +-- +2.17.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-bmcweb-handle-device-or-resource-busy-exception.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-bmcweb-handle-device-or-resource-busy-exception.patch new file mode 100644 index 000000000..761caabb7 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-bmcweb-handle-device-or-resource-busy-exception.patch @@ -0,0 +1,219 @@ +From 5fa2fb4bd766b9c74b9edff4701408a002466b2a Mon Sep 17 00:00:00 2001 +From: Karol Wachowski <karol.wachowski@intel.com> +Date: Fri, 10 Jul 2020 09:54:06 +0000 +Subject: [PATCH] bmcweb handle device or resource busy exception + +Use async_method_call_timed() for mount/unmount dbus oprations. +Long mount/unmount times are supported by VirtualMedia service, +this works because of settable timeout property, available for each block +device. +Default dbus calls will timeout when mount/unmount timeout is long enough. + +Get mount/unmount timeout property and use it for mount/unmount calls. +Add handling of device or resource busy exception (EBUSY) that +can be thrown by VirtualMedia service during Mount/Unmount dbus operations. + +Tested: Verified that after mounting non-existing HTTPS resource + in proxy mode, VirtualMedia recovers restoring ready state + and returns EBUSY during that transition. + Verfied that resources can be mounted/unmounted in both legacy + and proxy mode. +Signed-off-by: Karol Wachowski <karol.wachowski@intel.com> +Change-Id: Ica62c34db0cce24c4c6169fc661edfde49e948d0 +--- + redfish-core/lib/virtual_media.hpp | 144 ++++++++++++++++++++++------- + 1 file changed, 110 insertions(+), 34 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 0b5eb1a..a0c63ad 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -23,6 +23,8 @@ + // for GetObjectType and ManagedObjectType + #include <account_service.hpp> + ++#include <chrono> ++ + namespace redfish + + { +@@ -109,6 +111,26 @@ static void vmParseInterfaceObject(const DbusInterfaceType& interface, + } + } + ++/** ++ * @brief parses Timeout property and converts to microseconds ++ */ ++static std::optional<uint64_t> ++ vmParseTimeoutProperty(const std::variant<int>& timeoutProperty) ++{ ++ const int* timeoutValue = std::get_if<int>(&timeoutProperty); ++ if (timeoutValue) ++ { ++ constexpr int timeoutMarginSeconds = 10; ++ return std::chrono::duration_cast<std::chrono::microseconds>( ++ std::chrono::seconds(*timeoutValue + timeoutMarginSeconds)) ++ .count(); ++ } ++ else ++ { ++ return std::nullopt; ++ } ++} ++ + /** + * @brief Fill template for Virtual Media Item. + */ +@@ -806,22 +828,54 @@ class VirtualMediaActionInsertMedia : public Node + } + + crow::connections::systemBus->async_method_call( +- [asyncResp, secretPipe](const boost::system::error_code ec, +- bool success) { ++ [asyncResp, service, name, imageUrl, rw, unixFd, ++ secretPipe](const boost::system::error_code ec, ++ const std::variant<int> timeoutProperty) { + if (ec) + { + BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; + messages::internalError(asyncResp->res); ++ return; + } +- else if (!success) ++ ++ auto timeout = vmParseTimeoutProperty(timeoutProperty); ++ if (timeout == std::nullopt) + { +- BMCWEB_LOG_ERROR << "Service responded with error"; +- messages::generalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "Timeout property is empty."; ++ messages::internalError(asyncResp->res); ++ return; + } ++ ++ crow::connections::systemBus->async_method_call_timed( ++ [asyncResp, secretPipe](const boost::system::error_code ec, ++ bool success) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Bad D-Bus request error: " ++ << ec; ++ if (ec == ++ boost::system::errc::device_or_resource_busy) ++ { ++ messages::resourceInUse(asyncResp->res); ++ } ++ else ++ { ++ messages::internalError(asyncResp->res); ++ } ++ } ++ else if (!success) ++ { ++ BMCWEB_LOG_ERROR << "Service responded with error"; ++ messages::generalError(asyncResp->res); ++ } ++ }, ++ service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, ++ "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", ++ *timeout, imageUrl, rw, unixFd); + }, + service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, +- "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw, +- unixFd); ++ "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.VirtualMedia.MountPoint", "Timeout"); + } + }; + +@@ -955,38 +1009,60 @@ class VirtualMediaActionEjectMedia : public Node + const std::string& service, const std::string& name, + bool legacy) + { +- +- // Legacy mount requires parameter with image ++ std::string objectPath = "/xyz/openbmc_project/VirtualMedia/"; ++ std::string ifaceName = "xyz.openbmc_project.VirtualMedia"; + if (legacy) + { +- crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; +- +- messages::internalError(asyncResp->res); +- return; +- } +- }, +- service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, +- "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); ++ objectPath += "Legacy/"; ++ ifaceName += ".Legacy"; + } +- else // proxy ++ else + { +- crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; +- +- messages::internalError(asyncResp->res); +- return; +- } +- }, +- service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, +- "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); ++ objectPath += "Proxy/"; ++ ifaceName += ".Proxy"; + } ++ objectPath += name; ++ ++ crow::connections::systemBus->async_method_call( ++ [asyncResp, service, name, objectPath, ++ ifaceName](const boost::system::error_code ec, ++ const std::variant<int> timeoutProperty) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ ++ auto timeout = vmParseTimeoutProperty(timeoutProperty); ++ if (timeout == std::nullopt) ++ { ++ BMCWEB_LOG_ERROR << "Timeout property is empty."; ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ crow::connections::systemBus->async_method_call_timed( ++ [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Bad D-Bus request error: " ++ << ec; ++ if (ec == ++ boost::system::errc::device_or_resource_busy) ++ { ++ messages::resourceInUse(asyncResp->res); ++ } ++ else ++ { ++ messages::internalError(asyncResp->res); ++ } ++ return; ++ } ++ }, ++ service, objectPath, ifaceName, "Unmount", *timeout); ++ }, ++ service, objectPath, "org.freedesktop.DBus.Properties", "Get", ++ "xyz.openbmc_project.VirtualMedia.MountPoint", "Timeout"); + } + }; + +-- +2.25.1 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch new file mode 100644 index 000000000..274dd044a --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch @@ -0,0 +1,405 @@ +From f388587781c3d874b13b50ad39e8674f0bc08049 Mon Sep 17 00:00:00 2001 +From: AppaRao Puli <apparao.puli@linux.intel.com> +Date: Mon, 25 May 2020 16:14:39 +0530 +Subject: [PATCH] EventService: https client support + +Add https client support for push style +eventing. Using this BMC can push the event +logs/telemetry data to event listener over +secure http channel. + +Tested: + - Created subscription with https destination + url. Using SubmitTestEvent action set the + event and can see event on event listener. + - Validator passed. + +Change-Id: I44c3918b39baa2eb5fddda9d635f99aa280a422a +Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com> +--- + http/http_client.hpp | 270 +++++++++++++++++-------- + redfish-core/include/event_service_manager.hpp | 2 +- + 2 files changed, 186 insertions(+), 86 deletions(-) + +diff --git a/http/http_client.hpp b/http/http_client.hpp +index e6a7db1..27d2af3 100644 +--- a/http/http_client.hpp ++++ b/http/http_client.hpp +@@ -17,6 +17,7 @@ + #include <boost/asio/strand.hpp> + #include <boost/beast/core.hpp> + #include <boost/beast/http.hpp> ++#include <boost/beast/ssl.hpp> + #include <boost/beast/version.hpp> + + #include <cstdlib> +@@ -49,7 +50,10 @@ enum class ConnState + class HttpClient : public std::enable_shared_from_this<HttpClient> + { + private: +- boost::beast::tcp_stream conn; ++ boost::asio::io_context& ioc; ++ boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client}; ++ std::shared_ptr<boost::beast::ssl_stream<boost::beast::tcp_stream>> sslConn; ++ std::shared_ptr<boost::beast::tcp_stream> conn; + boost::asio::steady_timer timer; + boost::beast::flat_buffer buffer; + boost::beast::http::request<boost::beast::http::string_body> req; +@@ -62,14 +66,37 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + std::string host; + std::string port; + std::string uri; ++ bool useSsl; + uint32_t retryCount; + uint32_t maxRetryAttempts; + uint32_t retryIntervalSecs; + std::string retryPolicyAction; + bool runningTimer; + ++ inline boost::beast::tcp_stream& getConn() ++ { ++ if (useSsl) ++ { ++ return (boost::beast::get_lowest_layer(*sslConn)); ++ } ++ else ++ { ++ return (*conn); ++ } ++ } ++ + void doConnect() + { ++ if (useSsl) ++ { ++ sslConn = std::make_shared< ++ boost::beast::ssl_stream<boost::beast::tcp_stream>>(ioc, ctx); ++ } ++ else ++ { ++ conn = std::make_shared<boost::beast::tcp_stream>(ioc); ++ } ++ + if (state == ConnState::connectInProgress) + { + return; +@@ -77,25 +104,53 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + state = ConnState::connectInProgress; + + BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; +- // Set a timeout on the operation +- conn.expires_after(std::chrono::seconds(30)); +- conn.async_connect(endpoint, [self(shared_from_this())]( +- const boost::beast::error_code& ec, +- const boost::asio::ip::tcp::resolver:: +- results_type::endpoint_type& ep) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "Connect " << ep +- << " failed: " << ec.message(); +- self->state = ConnState::connectFailed; +- self->checkQueue(); +- return; +- } +- self->state = ConnState::connected; +- BMCWEB_LOG_DEBUG << "Connected to: " << ep; + +- self->checkQueue(); +- }); ++ auto respHandler = ++ [self(shared_from_this())](const boost::beast::error_code& ec, ++ const boost::asio::ip::tcp::resolver:: ++ results_type::endpoint_type& ep) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "Connect " << ep ++ << " failed: " << ec.message(); ++ self->state = ConnState::connectFailed; ++ self->checkQueue(); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "Connected to: " << ep; ++ if (self->useSsl) ++ { ++ self->performHandshake(); ++ } ++ else ++ { ++ self->state = ConnState::connected; ++ self->checkQueue(); ++ } ++ }; ++ ++ getConn().expires_after(std::chrono::seconds(30)); ++ getConn().async_connect(endpoint, std::move(respHandler)); ++ } ++ ++ void performHandshake() ++ { ++ sslConn->async_handshake( ++ boost::asio::ssl::stream_base::client, ++ [self(shared_from_this())](const boost::beast::error_code& ec) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "SSL handshake failed: " ++ << ec.message(); ++ self->state = ConnState::connectFailed; ++ self->doCloseAndCheckQueue(); ++ return; ++ } ++ self->state = ConnState::connected; ++ BMCWEB_LOG_DEBUG << "SSL Handshake successfull \n"; ++ ++ self->checkQueue(); ++ }); + } + + void sendMessage(const std::string& data) +@@ -108,7 +163,10 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + + BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; + +- req.version(static_cast<int>(11)); // HTTP 1.1 ++ req = {}; ++ res = {}; ++ ++ req.version(11); // HTTP 1.1 + req.target(uri); + req.method(boost::beast::http::verb::post); + +@@ -123,83 +181,121 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + req.body() = data; + req.prepare_payload(); + +- // Set a timeout on the operation +- conn.expires_after(std::chrono::seconds(30)); ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code& ec, ++ const std::size_t& bytesTransferred) { ++ if (ec) ++ { ++ BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message(); ++ self->state = ConnState::sendFailed; ++ self->doCloseAndCheckQueue(); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); + +- // Send the HTTP request to the remote host +- boost::beast::http::async_write( +- conn, req, +- [self(shared_from_this())](const boost::beast::error_code& ec, +- const std::size_t& bytesTransferred) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "sendMessage() failed: " +- << ec.message(); +- self->state = ConnState::sendFailed; +- self->checkQueue(); +- return; +- } +- BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " +- << bytesTransferred; +- boost::ignore_unused(bytesTransferred); ++ self->recvMessage(); ++ }; + +- self->recvMessage(); +- }); ++ getConn().expires_after(std::chrono::seconds(30)); ++ if (useSsl) ++ { ++ boost::beast::http::async_write(*sslConn, req, ++ std::move(respHandler)); ++ } ++ else ++ { ++ boost::beast::http::async_write(*conn, req, std::move(respHandler)); ++ } + } + + void recvMessage() + { +- // Receive the HTTP response +- boost::beast::http::async_read( +- conn, buffer, res, +- [self(shared_from_this())](const boost::beast::error_code& ec, +- const std::size_t& bytesTransferred) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << "recvMessage() failed: " +- << ec.message(); +- self->state = ConnState::recvFailed; +- self->checkQueue(); +- return; +- } +- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " +- << bytesTransferred; +- boost::ignore_unused(bytesTransferred); ++ auto respHandler = [self(shared_from_this())]( ++ const boost::beast::error_code& ec, ++ const std::size_t& bytesTransferred) { ++ if (ec && ec != boost::beast::http::error::partial_message) ++ { ++ BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); ++ self->state = ConnState::recvFailed; ++ self->doCloseAndCheckQueue(); ++ return; ++ } ++ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " ++ << bytesTransferred; ++ boost::ignore_unused(bytesTransferred); + +- // Discard received data. We are not interested. +- BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res; ++ // Discard received data. We are not interested. ++ BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res; + +- // Send is successful, Lets remove data from queue +- // check for next request data in queue. +- self->requestDataQueue.pop(); +- self->state = ConnState::idle; +- self->checkQueue(); +- }); ++ // Send is successful, Lets remove data from queue ++ // check for next request data in queue. ++ self->requestDataQueue.pop(); ++ self->state = ConnState::idle; ++ ++ if (ec == boost::beast::http::error::partial_message) ++ { ++ // Least bothered about recv message. Partial ++ // message means, already data is sent. Lets close ++ // connection and let next request open connection ++ // to avoid truncated stream. ++ self->state = ConnState::closed; ++ self->doCloseAndCheckQueue(); ++ return; ++ } ++ ++ self->checkQueue(); ++ }; ++ ++ getConn().expires_after(std::chrono::seconds(30)); ++ if (useSsl) ++ { ++ boost::beast::http::async_read(*sslConn, buffer, res, ++ std::move(respHandler)); ++ } ++ else ++ { ++ boost::beast::http::async_read(*conn, buffer, res, ++ std::move(respHandler)); ++ } + } + +- void doClose() ++ void doCloseAndCheckQueue() + { + boost::beast::error_code ec; +- conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); ++ getConn().cancel(); ++ getConn().expires_after(std::chrono::seconds(30)); ++ getConn().socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ++ ec); + +- state = ConnState::closed; +- // not_connected happens sometimes so don't bother reporting it. +- if (ec && ec != boost::beast::errc::not_connected) ++ if (ec && ec != boost::asio::error::eof) + { +- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); +- return; ++ // Many https server closes connection abruptly ++ // i.e witnout close_notify. More details are at ++ // https://github.com/boostorg/beast/issues/824 ++ if (ec == boost::asio::ssl::error::stream_truncated) ++ { ++ BMCWEB_LOG_DEBUG ++ << "doCloseAndCheckQueue(): Connection closed by server."; ++ } ++ else ++ { ++ BMCWEB_LOG_ERROR << "doCloseAndCheckQueue() failed: " ++ << ec.message(); ++ } + } ++ ++ getConn().close(); + BMCWEB_LOG_DEBUG << "Connection closed gracefully"; ++ checkQueue(); ++ return; + } + + void checkQueue(const bool newRecord = false) + { + if (requestDataQueue.empty()) + { +- // TODO: Having issue in keeping connection alive. So lets close if +- // nothing to be transferred. +- doClose(); +- + BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n"; + return; + } +@@ -257,17 +353,20 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs + << " seconds. RetryCount = " << retryCount; + timer.expires_after(std::chrono::seconds(retryIntervalSecs)); +- timer.async_wait([self = shared_from_this()]( +- const boost::system::error_code& ec) { +- self->runningTimer = false; +- self->connStateCheck(); +- }); ++ timer.async_wait( ++ [self = shared_from_this()](boost::system::error_code) { ++ self->runningTimer = false; ++ self->connStateCheck(); ++ }); + return; + } + else + { +- // reset retry count. +- retryCount = 0; ++ if (state == ConnState::idle) ++ { ++ // State idle means, previous attempt is successful. ++ retryCount = 0; ++ } + } + connStateCheck(); + +@@ -310,10 +409,11 @@ class HttpClient : public std::enable_shared_from_this<HttpClient> + public: + explicit HttpClient(boost::asio::io_context& ioc, const std::string& id, + const std::string& destIP, const std::string& destPort, +- const std::string& destUri) : +- conn(ioc), ++ const std::string& destUri, ++ const bool inUseSsl = true) : ++ ioc(ioc), + timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri), +- retryCount(0), maxRetryAttempts(5), ++ useSsl(inUseSsl), retryCount(0), maxRetryAttempts(5), + retryPolicyAction("TerminateAfterRetries"), runningTimer(false) + { + boost::asio::ip::tcp::resolver resolver(ioc); +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index 6362112..3ab2605 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -383,7 +383,7 @@ class Subscription + { + conn = std::make_shared<crow::HttpClient>( + crow::connections::systemBus->get_io_context(), id, host, port, +- path); ++ path, (uriProto == "https" ? true : false)); + } + + Subscription(const std::shared_ptr<crow::Request::Adaptor>& adaptor) : +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-VirtualMedia-fixes-for-Redfish-Service-Validator.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-VirtualMedia-fixes-for-Redfish-Service-Validator.patch new file mode 100644 index 000000000..52ff4e531 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-VirtualMedia-fixes-for-Redfish-Service-Validator.patch @@ -0,0 +1,122 @@ +From 49dc25100ab8a4220f81bc8f9b54808850fe1267 Mon Sep 17 00:00:00 2001 +From: Przemyslaw Czarnowski <przemyslaw.hawrylewicz.czarnowski@intel.com> +Date: Wed, 8 Jul 2020 15:17:31 +0200 +Subject: [PATCH] VirtualMedia fixes for Redfish Service Validator + +Removes all warnings and errors for VirtualMedia +- rework for OemVirtualMedia +- minor adjustments for jsons + +Tested: +Redfish Service Validator ran with no errors and/or warnings + +Change-Id: Ic027166153a807a8bd3a6c04f042969f16e0dc6a +Signed-off-by: Przemyslaw Czarnowski <przemyslaw.hawrylewicz.czarnowski@intel.com> +--- + redfish-core/lib/virtual_media.hpp | 4 +-- + .../v1/JsonSchemas/OemVirtualMedia/index.json | 28 +++---------------- + .../redfish/v1/schema/OemVirtualMedia_v1.xml | 12 ++++---- + 3 files changed, 12 insertions(+), 32 deletions(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 552e255..183abbe 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -129,7 +129,7 @@ static nlohmann::json vmItemTemplate(const std::string& name, + item["MediaTypes"] = {"CD", "USBStick"}; + item["TransferMethod"] = "Stream"; + item["TransferProtocolType"] = nullptr; +- item["Oem"]["OpenBmc"]["WebSocketEndpoint"] = nullptr; ++ item["Oem"]["OpenBMC"]["WebSocketEndpoint"] = nullptr; + item["Oem"]["OpenBMC"]["@odata.type"] = + "#OemVirtualMedia.v1_0_0.VirtualMedia"; + +@@ -1039,7 +1039,7 @@ class VirtualMediaCollection : public Node + "#VirtualMediaCollection.VirtualMediaCollection"; + res.jsonValue["Name"] = "Virtual Media Services"; + res.jsonValue["@odata.id"] = +- "/redfish/v1/Managers/" + name + "/VirtualMedia/"; ++ "/redfish/v1/Managers/" + name + "/VirtualMedia"; + + crow::connections::systemBus->async_method_call( + [asyncResp, name](const boost::system::error_code ec, +diff --git a/static/redfish/v1/JsonSchemas/OemVirtualMedia/index.json b/static/redfish/v1/JsonSchemas/OemVirtualMedia/index.json +index 78bd8b7..9ae641a 100644 +--- a/static/redfish/v1/JsonSchemas/OemVirtualMedia/index.json ++++ b/static/redfish/v1/JsonSchemas/OemVirtualMedia/index.json +@@ -3,9 +3,10 @@ + "$schema": "http://redfish.dmtf.org/schemas/v1/redfish-schema-v1.json", + "copyright": "Copyright 2014-2019 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright", + "definitions": { +- "OpenBmc": { +- "additionalProperties": true, +- "description": "Oem properties for OpenBmc.", ++ "VirtualMedia": { ++ "additionalProperties": false, ++ "description": "OEM Extension for VirtualMedia", ++ "longDescription": "OEM Extension for VirtualMedia to support Proxy mode.", + "patternProperties": { + "^([a-zA-Z_][a-zA-Z0-9_]*)?@(odata|Redfish|Message)\\.[a-zA-Z_][a-zA-Z0-9_]*$": { + "description": "This property shall specify a valid odata or Redfish property.", +@@ -32,27 +33,6 @@ + } + }, + "type": "object" +- }, +- "VirtualMedia": { +- "additionalProperties": false, +- "description": "OEM Extension for VirtualMedia", +- "longDescription": "OEM Extension for VirtualMedia to support Proxy mode.", +- "patternProperties": { +- "^([a-zA-Z_][a-zA-Z0-9_]*)?@(odata|Redfish|Message)\\.[a-zA-Z_][a-zA-Z0-9_]*$": { +- "description": "This property shall specify a valid odata or Redfish property.", +- "type": [ +- "array", +- "boolean", +- "integer", +- "number", +- "null", +- "object", +- "string" +- ] +- } +- }, +- "properties": {}, +- "type": "object" + } + }, + "owningEntity": "OpenBMC", +diff --git a/static/redfish/v1/schema/OemVirtualMedia_v1.xml b/static/redfish/v1/schema/OemVirtualMedia_v1.xml +index 2b03a67..84afe73 100644 +--- a/static/redfish/v1/schema/OemVirtualMedia_v1.xml ++++ b/static/redfish/v1/schema/OemVirtualMedia_v1.xml +@@ -25,20 +25,20 @@ + <Annotation Term="Redfish.OwningEntity" String="OpenBMC"/> + <Annotation Term="Redfish.Release" String="1.0"/> + +- <ComplexType Name="OpenBmc" BaseType="Resource.OemObject"> +- <Annotation Term="OData.AdditionalProperties" Bool="true"/> +- <Annotation Term="OData.Description" String="Oem properties for OpenBmc." /> +- +- <Property Name="WebSocketEndpoint" Type="Edm.String"> ++ <Property Name="WebSocketEndpoint" Type="Edm.String"> + <Annotation Term="OData.Permissions" EnumMember="OData.Permission/Read"/> + <Annotation Term="OData.Description" String="Indicates endpoint socket name and location."/> + <Annotation Term="OData.LongDescription" String="The value of this property shall be a structure ring indicating location and name of the socket used to communicate with nbd server."/> + </Property> +- </ComplexType> + + <EntityType Name="VirtualMedia" BaseType="Resource.OemObject" Abstract="true"> + <Annotation Term="OData.Description" String="OEM Extension for VirtualMedia"/> + <Annotation Term="OData.LongDescription" String="OEM Extension for VirtualMedia to support Proxy mode."/> ++ <Property Name="WebSocketEndpoint" Type="Edm.String"> ++ <Annotation Term="OData.Permissions" EnumMember="OData.Permission/Read"/> ++ <Annotation Term="OData.Description" String="Indicates endpoint socket name and location."/> ++ <Annotation Term="OData.LongDescription" String="The value of this property shall be a structure ring indicating location and name of the socket used to communicate with nbd server."/> ++ </Property> + </EntityType> + </Schema> + +-- +2.25.0 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Fix-Image-and-ImageName-values-in-schema.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Fix-Image-and-ImageName-values-in-schema.patch new file mode 100644 index 000000000..c182822a6 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Fix-Image-and-ImageName-values-in-schema.patch @@ -0,0 +1,38 @@ +From 15305d3a9db371af924482e5a6959bbf7812cf6c Mon Sep 17 00:00:00 2001 +From: Przemyslaw Czarnowski <przemyslaw.hawrylewicz.czarnowski@intel.com> +Date: Wed, 29 Jul 2020 15:56:57 +0200 +Subject: [PATCH] Fix Image and ImageName values in schema + +According to design document and schema Image shall contain URL of +image location and ImageName only name of the image. + +Change-Id: Ie1a906c66aa2a10113c307eb1e7d2d7da2810fbd +Signed-off-by: Przemyslaw Czarnowski <przemyslaw.hawrylewicz.czarnowski@intel.com> +--- + redfish-core/lib/virtual_media.hpp | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp +index 183abbe..0345e7b 100644 +--- a/redfish-core/lib/virtual_media.hpp ++++ b/redfish-core/lib/virtual_media.hpp +@@ -97,7 +97,15 @@ static void vmParseInterfaceObject(const DbusInterfaceType& interface, + std::get_if<std::string>(&imageUrlProperty->second); + if (imageUrlValue && !imageUrlValue->empty()) + { +- aResp->res.jsonValue["ImageName"] = *imageUrlValue; ++ std::size_t lastIndex = imageUrlValue->rfind("/"); ++ if (lastIndex == std::string::npos) ++ { ++ aResp->res.jsonValue["ImageName"] = *imageUrlValue; ++ } ++ ++ aResp->res.jsonValue["ImageName"] = ++ imageUrlValue->substr(lastIndex + 1); ++ aResp->res.jsonValue["Image"] = *imageUrlValue; + aResp->res.jsonValue["Inserted"] = *activeValue; + if (*activeValue == true) + { +-- +2.25.0 + 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 new file mode 100644 index 000000000..3850c8fa8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch @@ -0,0 +1,913 @@ +From 7820421433349df28bd393e8d610d1848af0f1c8 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 1/5] Redfish TelemetryService schema implementation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +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 +design. User is able to fetch basic information about reports if +MonitoringService is present in OpenBMC. + +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 + +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +Signed-off-by: Adrian Ambrożewicz <adrian.ambrozewicz@linux.intel.com> +Change-Id: Ie6b0b49f4ef5eeaef07d1209b6c349270c04d570 +--- + 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 | 193 +++++++++++++++++++++++++ + redfish-core/lib/service_root.hpp | 2 + + redfish-core/lib/telemetry_service.hpp | 92 ++++++++++++ + 9 files changed, 765 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 e1360f7..3df88d8 100644 +--- a/include/dbus_utility.hpp ++++ b/include/dbus_utility.hpp +@@ -109,5 +109,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 cc98e1a..3d4c117 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -25,6 +25,8 @@ + #include "../lib/log_services.hpp" + #include "../lib/managers.hpp" + #include "../lib/message_registries.hpp" ++#include "../lib/metric_report.hpp" ++#include "../lib/metric_report_definition.hpp" + #include "../lib/network_protocol.hpp" + #include "../lib/pcie.hpp" + #include "../lib/power.hpp" +@@ -35,6 +37,7 @@ + #include "../lib/storage.hpp" + #include "../lib/systems.hpp" + #include "../lib/task.hpp" ++#include "../lib/telemetry_service.hpp" + #include "../lib/thermal.hpp" + #include "../lib/update_service.hpp" + #ifdef BMCWEB_ENABLE_VM_NBDPROXY +@@ -202,6 +205,13 @@ class RedfishService + nodes.emplace_back(std::make_unique<HypervisorInterface>(app)); + nodes.emplace_back(std::make_unique<HypervisorSystem>(app)); + ++ nodes.emplace_back(std::make_unique<TelemetryService>(app)); ++ nodes.emplace_back( ++ std::make_unique<MetricReportDefinitionCollection>(app)); ++ nodes.emplace_back(std::make_unique<MetricReportDefinition>(app)); ++ nodes.emplace_back(std::make_unique<MetricReportCollection>(app)); ++ nodes.emplace_back(std::make_unique<MetricReport>(app)); ++ + 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 d578de4..fbb259d 100644 +--- a/redfish-core/include/utils/json_utils.hpp ++++ b/redfish-core/include/utils/json_utils.hpp +@@ -13,15 +13,19 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + */ ++ + #pragma once + + #include <http_request.h> + #include <http_response.h> + ++#include <boost/container/flat_map.hpp> + #include <error_messages.hpp> + #include <nlohmann/json.hpp> + + #include <bitset> ++#include <string> ++#include <variant> + + namespace redfish + { +@@ -436,5 +440,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 +--- /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. ++*/ ++ ++#pragma once ++ ++namespace redfish ++{ ++ ++namespace telemetry ++{ ++ ++static constexpr const char* metricReportDefinitionUri = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions/"; ++static 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) ++{ ++ const std::array<const char*, 1> interfaces = {reportInterface}; ++ ++ dbus::utility::getSubTreePaths( ++ [asyncResp, uri](const boost::system::error_code ec, ++ const std::vector<std::string>& reports) { ++ if (ec == boost::system::errc::no_such_file_or_directory) ++ { ++ asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); ++ asyncResp->res.jsonValue["Members@odata.count"] = 0; ++ return; ++ } ++ ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ json_util::dbusPathsToMembersArray(asyncResp->res, reports, uri); ++ }, ++ telemetryPath, 1, interfaces); ++} ++ ++template <typename Callback> ++static void getReport(const std::shared_ptr<AsyncResp>& asyncResp, ++ const std::string& id, const char* schemaType, ++ const Callback&& callback) ++{ ++ 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); ++} ++} // 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 +--- /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. ++*/ ++ ++#pragma once ++ ++#include <boost/algorithm/string/trim.hpp> ++ ++#include <chrono> ++#include <cstdint> ++#include <string> ++#include <type_traits> ++ ++namespace redfish ++{ ++ ++namespace time_utils ++{ ++ ++namespace details ++{ ++ ++template <typename T> ++std::string toDurationFormatItem(std::chrono::milliseconds& duration, ++ const char* postfix) ++{ ++ 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) ++ { ++ ss << static_cast<float>(t.count()) / ++ static_cast<float>(std::chrono::milliseconds::period::den); ++ } ++ 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". ++ */ ++std::string toDurationFormat(const uint32_t ms) ++{ ++ std::chrono::milliseconds duration(ms); ++ if (duration.count() == 0) ++ { ++ return "PT0S"; ++ } ++ ++ std::string fmt; ++ fmt.reserve(sizeof("PxxxDTxxHxxMxx.xxxxxxS")); ++ ++ using Days = std::chrono::duration<int, std::ratio<24 * 60 * 60>>; ++ ++ fmt += "P"; ++ fmt += details::toDurationFormatItem<Days>(duration, "D"); ++ if (duration.count() == 0) ++ { ++ return fmt; ++ } ++ ++ 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"); ++ ++ return fmt; ++} ++ ++} // namespace time_utils ++} // namespace redfish +diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp +new file mode 100644 +index 0000000..a52d680 +--- /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. ++*/ ++ ++#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(CrowApp& app) : Node(app, telemetry::metricReportUri) ++ { ++ 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& req, ++ const std::vector<std::string>& params) override ++ { ++ res.jsonValue["@odata.type"] = ++ "#MetricReportCollection.MetricReportCollection"; ++ res.jsonValue["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricReports"; ++ res.jsonValue["Name"] = "Metric Report Collection"; ++ ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ telemetry::getReportCollection(asyncResp, telemetry::metricReportUri); ++ } ++}; ++ ++class MetricReport : public Node ++{ ++ public: ++ MetricReport(CrowApp& app) : ++ Node(app, std::string(telemetry::metricReportUri) + "<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& req, ++ 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, getReportProperties); ++ } ++ ++ using Readings = ++ std::vector<std::tuple<std::string, std::string, double, int32_t>>; ++ using MetricValues = std::vector<std::map<std::string, std::string>>; ++ ++ static MetricValues toMetricValues(const Readings& readings) ++ { ++ MetricValues metricValues; ++ ++ for (auto& [id, metadata, sensorValue, timestamp] : readings) ++ { ++ metricValues.push_back({ ++ {"MetricId", id}, ++ {"MetricProperty", metadata}, ++ {"MetricValue", std::to_string(sensorValue)}, ++ {"Timestamp", crow::utility::getDateTime(timestamp)}, ++ }); ++ } ++ ++ return metricValues; ++ } ++ ++ static void getReportProperties(const std::shared_ptr<AsyncResp> asyncResp, ++ const std::string& reportPath, ++ const std::string& id) ++ { ++ 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"] = ++ 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; ++ } ++ ++ 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"); ++ } ++ ++ static constexpr const char* schemaType = ++ "#MetricReport.v1_3_0.MetricReport"; ++}; ++} // 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..d82ae59 +--- /dev/null ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -0,0 +1,193 @@ ++/* ++// 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 "utils/telemetry_utils.hpp" ++#include "utils/time_utils.hpp" ++ ++#include <boost/container/flat_map.hpp> ++ ++#include <system_error> ++#include <variant> ++ ++namespace redfish ++{ ++ ++class MetricReportDefinitionCollection : public Node ++{ ++ public: ++ MetricReportDefinitionCollection(CrowApp& app) : ++ Node(app, telemetry::metricReportDefinitionUri) ++ { ++ 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& req, ++ const std::vector<std::string>& params) override ++ { ++ res.jsonValue["@odata.type"] = "#MetricReportDefinitionCollection." ++ "MetricReportDefinitionCollection"; ++ res.jsonValue["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions"; ++ res.jsonValue["Name"] = "Metric Definition Collection"; ++ ++ auto asyncResp = std::make_shared<AsyncResp>(res); ++ telemetry::getReportCollection(asyncResp, ++ telemetry::metricReportDefinitionUri); ++ } ++}; ++ ++class MetricReportDefinition : public Node ++{ ++ public: ++ MetricReportDefinition(CrowApp& app) : ++ Node(app, std::string(telemetry::metricReportDefinitionUri) + "<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& req, ++ 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, ++ 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; ++ } ++ ++ using ReadingParameters = ++ std::vector<std::tuple<std::vector<sdbusplus::message::object_path>, ++ std::string, std::string, std::string>>; ++ using Metrics = std::vector<std::map< ++ std::string, std::variant<std::string, std::vector<std::string>>>>; ++ ++ static Metrics toMetrics(const ReadingParameters& params) ++ { ++ Metrics metrics; ++ ++ 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) ++ { ++ asyncResp->res.jsonValue["@odata.type"] = schemaType; ++ asyncResp->res.jsonValue["@odata.id"] = ++ telemetry::metricReportDefinitionUri + id; ++ asyncResp->res.jsonValue["Id"] = id; ++ asyncResp->res.jsonValue["Name"] = id; ++ asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = ++ telemetry::metricReportUri + id; ++ asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; ++ ++ 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; ++ } ++ ++ 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"); ++ } ++ ++ public: ++ static constexpr const char* schemaType = ++ "#MetricReportDefinition.v1_3_0.MetricReportDefinition"; ++}; ++} // namespace redfish +diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp +index b6bd6e0..3302390 100644 +--- a/redfish-core/lib/service_root.hpp ++++ b/redfish-core/lib/service_root.hpp +@@ -69,6 +69,8 @@ class ServiceRoot : public Node + res.jsonValue["Tasks"] = {{"@odata.id", "/redfish/v1/TaskService"}}; + res.jsonValue["EventService"] = { + {"@odata.id", "/redfish/v1/EventService"}}; ++ res.jsonValue["TelemetryService"] = { ++ {"@odata.id", "/redfish/v1/TelemetryService"}}; + res.end(); + } + +diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp +new file mode 100644 +index 0000000..a410700 +--- /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. ++*/ ++ ++#pragma once ++ ++#include "node.hpp" ++#include "utils/time_utils.hpp" ++ ++#include <boost/container/flat_map.hpp> ++ ++#include <variant> ++ ++namespace redfish ++{ ++ ++class TelemetryService : public Node ++{ ++ public: ++ TelemetryService(CrowApp& app) : Node(app, "/redfish/v1/TelemetryService/") ++ { ++ 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& req, ++ const std::vector<std::string>& params) override ++ { ++ res.jsonValue["@odata.type"] = ++ "#TelemetryService.v1_2_0.TelemetryService"; ++ res.jsonValue["@odata.id"] = "/redfish/v1/TelemetryService"; ++ res.jsonValue["Id"] = "TelemetryService"; ++ res.jsonValue["Name"] = "Telemetry Service"; ++ ++ res.jsonValue["LogService"]["@odata.id"] = ++ "/redfish/v1/Managers/bmc/LogServices/Journal"; ++ res.jsonValue["MetricReportDefinitions"]["@odata.id"] = ++ "/redfish/v1/TelemetryService/MetricReportDefinitions"; ++ 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( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const boost::container::flat_map<std::string, ++ std::variant<uint32_t>>& ret) { ++ if (ec) ++ { ++ asyncResp->res.jsonValue["Status"]["State"] = "Absent"; ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ 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); ++ }, ++ "xyz.openbmc_project.MonitoringService", ++ "/xyz/openbmc_project/MonitoringService/Reports", ++ "xyz.openbmc_project.MonitoringService.ReportsManagement"); ++ } ++}; ++} // namespace redfish +-- +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 new file mode 100644 index 000000000..8a8690bf3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch @@ -0,0 +1,594 @@ +From 941be2c7d819b4a55d5a8b67948e53658d907789 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/5] 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 +--- + 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 d82ae59..ecbab0c 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>& params) 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 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 ret) { ++ 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 +@@ -148,6 +494,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 new file mode 100644 index 000000000..4c49b0cd3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch @@ -0,0 +1,68 @@ +From 8b2f4a6fe57bf2410cdb22f8c3c695e98d583040 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 3/5] 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 +--- + 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 ecbab0c..8e04ac8 100644 +--- a/redfish-core/lib/metric_report_definition.hpp ++++ b/redfish-core/lib/metric_report_definition.hpp +@@ -533,6 +533,38 @@ class MetricReportDefinition : public Node + "xyz.openbmc_project.MonitoringService.Report"); + } + ++ void doDelete(crow::Response& res, const crow::Request& req, ++ 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& id) ++ { ++ 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/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 new file mode 100644 index 000000000..e996ac585 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch @@ -0,0 +1,169 @@ +From 9fc7d722b3192df9940062185b40ebb0fabad518 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 4/5] 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 +--- + 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 3df88d8..029d8d8 100644 +--- a/include/dbus_utility.hpp ++++ b/include/dbus_utility.hpp +@@ -17,6 +17,7 @@ + + #include <sdbusplus/message.hpp> + ++#include <functional> + #include <regex> + + namespace dbus +@@ -130,5 +131,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 a52d680..877e7f1 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/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 new file mode 100644 index 000000000..f7da8a556 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch @@ -0,0 +1,535 @@ +From b1da8901b5985d6a77b63ca9eb0570b46528f0bd 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 5/5] 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 +--- + 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 3d4c117..2a12bf9 100644 +--- a/redfish-core/include/redfish.hpp ++++ b/redfish-core/include/redfish.hpp +@@ -25,6 +25,7 @@ + #include "../lib/log_services.hpp" + #include "../lib/managers.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" +@@ -206,6 +207,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..837a068 +--- /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(CrowApp& 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& req, ++ const std::vector<std::string>& params) 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 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(CrowApp& 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& req, ++ 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 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 877e7f1..be72b18 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 f12bbe0..1fa1009 100644 +--- a/redfish-core/lib/sensors.hpp ++++ b/redfish-core/lib/sensors.hpp +@@ -53,20 +53,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 a410700..79e4154 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/0006-Fix-MetricReport-timestamp-for-EventService.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Fix-MetricReport-timestamp-for-EventService.patch new file mode 100644 index 000000000..75d49b6d6 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Fix-MetricReport-timestamp-for-EventService.patch @@ -0,0 +1,78 @@ +From b71f087a173c36a16526156fa34581673e2b860c Mon Sep 17 00:00:00 2001 +From: "Wludzik, Jozef" <jozef.wludzik@intel.com> +Date: Fri, 24 Jul 2020 17:05:38 +0200 +Subject: [PATCH 6/6] Fix MetricReport timestamp for EventService + +Changed MetricReport timestamp type from std::string to int32_t. + +Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com> +Change-Id: I0a52b6963e7bedda89a216256f64764cd8799bf1 +--- + redfish-core/include/event_service_manager.hpp | 23 +++++++++++++---------- + 1 file changed, 13 insertions(+), 10 deletions(-) + +diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp +index d2f4f2a..dc04ccb 100644 +--- a/redfish-core/include/event_service_manager.hpp ++++ b/redfish-core/include/event_service_manager.hpp +@@ -38,7 +38,7 @@ namespace redfish + { + + using ReadingsObjType = +- std::vector<std::tuple<std::string, std::string, double, std::string>>; ++ std::vector<std::tuple<std::string, std::string, double, int32_t>>; + using EventServiceConfig = std::tuple<bool, uint32_t, uint32_t>; + + static constexpr const char* eventFormatType = "Event"; +@@ -532,10 +532,12 @@ class Subscription + metricValuesArray.push_back({}); + nlohmann::json& entry = metricValuesArray.back(); + +- entry = {{"MetricId", std::get<0>(it)}, +- {"MetricProperty", std::get<1>(it)}, +- {"MetricValue", std::to_string(std::get<2>(it))}, +- {"Timestamp", std::get<3>(it)}}; ++ auto& [id, property, value, timestamp] = it; ++ ++ entry = {{"MetricId", id}, ++ {"MetricProperty", property}, ++ {"MetricValue", value}, ++ {"Timestamp", crow::utility::getDateTime(timestamp)}}; + } + + nlohmann::json msg = { +@@ -1266,7 +1268,7 @@ class EventServiceManager + [idStr{std::move(idStr)}]( + const boost::system::error_code ec, + boost::container::flat_map< +- std::string, std::variant<std::string, ReadingsObjType>>& ++ std::string, std::variant<int32_t, ReadingsObjType>>& + resp) { + if (ec) + { +@@ -1275,8 +1277,8 @@ class EventServiceManager + return; + } + +- const std::string* timestampPtr = +- std::get_if<std::string>(&resp["Timestamp"]); ++ const int32_t* timestampPtr = ++ std::get_if<int32_t>(&resp["Timestamp"]); + if (!timestampPtr) + { + BMCWEB_LOG_DEBUG << "Failed to Get timestamp."; +@@ -1303,8 +1305,9 @@ class EventServiceManager + std::shared_ptr<Subscription> entry = it.second; + if (entry->eventFormatType == metricReportFormatType) + { +- entry->filterAndSendReports(idStr, *timestampPtr, +- *readingsPtr); ++ entry->filterAndSendReports( ++ idStr, crow::utility::getDateTime(*timestampPtr), ++ *readingsPtr); + } + } + }, +-- +2.16.6 + diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README new file mode 100644 index 000000000..2929b0aec --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README @@ -0,0 +1,21 @@ +These patches are mirror of upstream TelemetryService implementation. +Until change is integrated they will be manually merged here to enable feature in Intel builds. + +Current revisions: +- Redfish TelemetryService schema implementation + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/31692/29 + +- 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 support for MetricDefinition scheme + https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/33363/20 + +- 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 5c70f25d7..5a44eec78 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb_%.bbappend @@ -1,6 +1,8 @@ # todo(james) remove nobranch SRC_URI = "git://github.com/openbmc/bmcweb.git" -SRCREV = "a502de3d661acf95613d4e4d27c9611f2c8148ea" +SRCREV = "6964c9820ad101d6fc30badd1ae353efea3dd094" + +DEPENDS += "boost-url" FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" @@ -13,14 +15,37 @@ GROUPADD_PARAM_${PN} = "web; redfish " SRC_URI += "file://0001-Firmware-update-support-for-StandBySpare.patch \ file://0002-Use-chip-id-based-UUID-for-Service-Root.patch \ file://0003-bmcweb-changes-for-setting-ApplyOptions-ClearCfg.patch \ + file://0004-Remove-QueryString.patch \ + file://0004-bmcweb-handle-device-or-resource-busy-exception.patch \ + file://0005-EventService-https-client-support.patch \ + file://0005-VirtualMedia-fixes-for-Redfish-Service-Validator.patch \ + file://0006-Fix-Image-and-ImageName-values-in-schema.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/0006-Fix-MetricReport-timestamp-for-EventService.patch \ " +# Temporary fix: Move it to service file +do_install_append() { + install -d ${D}/var/lib/bmcweb +} + # Enable PFR support EXTRA_OECMAKE += "${@bb.utils.contains('IMAGE_FSTYPES', 'intel-pfr', '-DBMCWEB_ENABLE_REDFISH_PROVISIONING_FEATURE=ON', '', d)}" -# Enable NBD_PROXY +# Enable NBD proxy embedded in bmcweb EXTRA_OECMAKE += " -DBMCWEB_ENABLE_VM_NBDPROXY=ON" +# Disable dependency on external nbd-proxy application +EXTRA_OECMAKE += " -DBMCWEB_ENABLE_VM_WEBSOCKET=OFF" +RDEPENDS_${PN}_remove += "jsnbd" + # Enable Validation unsecure based on IMAGE_FEATURES EXTRA_OECMAKE += "${@bb.utils.contains('EXTRA_IMAGE_FEATURES', 'validation-unsecure', '-DBMCWEB_ENABLE_VALIDATION_UNSECURE_FEATURE=ON', '', d)}" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/libmctp-intel_git.bb b/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/libmctp-intel_git.bb index 055e9710e..43f0785e5 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/libmctp-intel_git.bb +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/libmctp-intel_git.bb @@ -2,7 +2,7 @@ SUMMARY = "libmctp_intel" DESCRIPTION = "Implementation of MCTP(DMTF DSP0236)" SRC_URI = "git://github.com/Intel-BMC/libmctp.git;protocol=ssh" -SRCREV = "58581630af62fce296206e9d09da38cf1d6d1a0f" +SRCREV = "467717ed557169e86be2c694e616fbfa9f5982c7" S = "${WORKDIR}/git/" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctp-emulator.bb b/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctp-emulator.bb index 829bc1636..98d2d11e4 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctp-emulator.bb +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctp-emulator.bb @@ -5,7 +5,7 @@ LICENSE = "Apache-2.0" LIC_FILES_CHKSUM = "file://LICENSE;md5=bcd9ada3a943f58551867d72893cc9ab" SRC_URI = "git://github.com/Intel-BMC/pmci.git;protocol=ssh" -SRCREV = "cd15b3175e2dd8315d1e01634f2c8a24174c9b21" +SRCREV = "1bc2bddcff7104cc9040e9b2688761b2d9b32099" S = "${WORKDIR}/git/mctp_emulator/" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctpd.bb b/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctpd.bb index f20426ba1..740fd460e 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctpd.bb +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/pmci/mctpd.bb @@ -5,7 +5,7 @@ LICENSE = "Apache-2.0" LIC_FILES_CHKSUM = "file://LICENSE;md5=e3fc50a88d0a364313df4b21ef20c29e" SRC_URI = "git://github.com/Intel-BMC/pmci.git;protocol=ssh" -SRCREV = "cd15b3175e2dd8315d1e01634f2c8a24174c9b21" +SRCREV = "1bc2bddcff7104cc9040e9b2688761b2d9b32099" S = "${WORKDIR}/git/mctpd/" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/sel-logger/phosphor-sel-logger_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/sel-logger/phosphor-sel-logger_%.bbappend index 67d8854d2..8ebca0b9e 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/sel-logger/phosphor-sel-logger_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/sel-logger/phosphor-sel-logger_%.bbappend @@ -1,4 +1,4 @@ # Enable downstream autobump SRC_URI = "git://github.com/openbmc/phosphor-sel-logger.git" -SRCREV = "761bf202ba9db9fe644f8f400a5e768abe1a70cf" +SRCREV = "25b26e162bd109b51aa09b16f26f9aa3d9d940fa" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/sensors/dbus-sensors_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/sensors/dbus-sensors_%.bbappend index 882547ea7..ac45a8114 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/sensors/dbus-sensors_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/sensors/dbus-sensors_%.bbappend @@ -1,4 +1,4 @@ -SRCREV = "5591cf0860895607bda0b8ae713e6b05ac0623b1" +SRCREV = "623723b9e827f52a05cfe2dac8b4ef5d285fb6af" #SRC_URI = "git://github.com/openbmc/dbus-sensors.git" DEPENDS_append = " libgpiod libmctp" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/service-config-manager/service-config-manager_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/service-config-manager/service-config-manager_%.bbappend index 6d26a95a9..c34d7af43 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/service-config-manager/service-config-manager_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/service-config-manager/service-config-manager_%.bbappend @@ -1,5 +1,6 @@ inherit meson pkgconfig systemd -SRC_URI = "git://github.com/openbmc/service-config-manager" -SRCREV = "1a885d98eea964bd9a3d57152dcaae7d4808211f" +SRC_URI = "git://github.com/openbmc/service-config-manager;nobranch=1" +# Move to latest version, only when sdbusplus is bumped up +SRCREV = "0f2b1414d0dda4534008fc5eb0e939d99287c0eb" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/settings/settings_git.bb b/meta-openbmc-mods/meta-common/recipes-phosphor/settings/settings_git.bb index e15525bba..3fe7a1782 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/settings/settings_git.bb +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/settings/settings_git.bb @@ -1,7 +1,7 @@ SUMMARY = "Settings" SRC_URI = "git://github.com/Intel-BMC/settings.git;protocol=ssh" -SRCREV = "cf55f85c9cd676736356f06fc47a7e98abd297f3" +SRCREV = "5d2150656d23412c59acd4da7b9f155902f089d9" PV = "0.1+git${SRCPV}" LICENSE = "Apache-2.0" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/special-mode-mgr/special-mode-mgr_git.bb b/meta-openbmc-mods/meta-common/recipes-phosphor/special-mode-mgr/special-mode-mgr_git.bb index ea8971eb4..e27f2295c 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/special-mode-mgr/special-mode-mgr_git.bb +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/special-mode-mgr/special-mode-mgr_git.bb @@ -9,7 +9,7 @@ LICENSE = "Apache-2.0" LIC_FILES_CHKSUM = "file://LICENSE;md5=e3fc50a88d0a364313df4b21ef20c29e" SRC_URI = "git://github.com/Intel-BMC/special-mode-manager.git;protocol=ssh" -SRCREV = "658d313be17b971aedae69e9edbba5f26667dd03" +SRCREV = "e9bcb94f78ec9dad22bc008e1abdb18f16f39a40" EXTRA_OECMAKE += "${@bb.utils.contains('EXTRA_IMAGE_FEATURES', 'validation-unsecure', '-DBMC_VALIDATION_UNSECURE_FEATURE=ON', '', d)}" diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/virtual-media/virtual-media.bb b/meta-openbmc-mods/meta-common/recipes-phosphor/virtual-media/virtual-media.bb index 333611dbb..2b0f167c4 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/virtual-media/virtual-media.bb +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/virtual-media/virtual-media.bb @@ -14,8 +14,7 @@ SYSTEMD_SERVICE_${PN} += "xyz.openbmc_project.VirtualMedia.service" DEPENDS = "udev boost nlohmann-json systemd sdbusplus" -# Needed for legacy mode -RDEPENDS_${PN} = "nbdkit" +RDEPENDS_${PN} = "nbd-client nbdkit" inherit cmake systemd diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/webui/phosphor-webui_%.bbappend b/meta-openbmc-mods/meta-common/recipes-phosphor/webui/phosphor-webui_%.bbappend index ce46ec5cd..4a188f28e 100644 --- a/meta-openbmc-mods/meta-common/recipes-phosphor/webui/phosphor-webui_%.bbappend +++ b/meta-openbmc-mods/meta-common/recipes-phosphor/webui/phosphor-webui_%.bbappend @@ -1,4 +1,4 @@ SRC_URI = "git://github.com/Intel-BMC/phosphor-webui;protocol=ssh;branch=intel2" FILESEXTRAPATHS_prepend_intel := "${THISDIR}/${PN}:" -SRCREV = "7d6650577d28a52e7be47dd2106d9e0f3b6e1c59" +SRCREV = "3348cfbfd54e52a795516bc4a906e128c1bc6bcf" |