From f99301c1a626951ee7feee081a1494e795d0e243 Mon Sep 17 00:00:00 2001 From: "Jason M. Bills" Date: Mon, 31 Aug 2020 13:56:28 -0700 Subject: Update to internal 0.74 Signed-off-by: Jason M. Bills --- .../bmcweb/0004-Remove-QueryString.patch | 621 ++++++++++++++ ...-handle-device-or-resource-busy-exception.patch | 219 +++++ .../0005-EventService-https-client-support.patch | 405 +++++++++ ...Media-fixes-for-Redfish-Service-Validator.patch | 122 +++ ...-Fix-Image-and-ImageName-values-in-schema.patch | 38 + ...sh-TelemetryService-schema-implementation.patch | 913 +++++++++++++++++++++ ...pport-for-POST-in-MetricReportDefinitions.patch | 594 ++++++++++++++ ...-for-DELETE-in-MetricReportDefinitions-st.patch | 68 ++ ...t-for-OnRequest-in-MetricReportDefinition.patch | 169 ++++ ...5-Add-support-for-MetricDefinition-scheme.patch | 535 ++++++++++++ ...x-MetricReport-timestamp-for-EventService.patch | 78 ++ .../interfaces/bmcweb/telemetry/README | 21 + .../recipes-phosphor/interfaces/bmcweb_%.bbappend | 29 +- 13 files changed, 3810 insertions(+), 2 deletions(-) create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-Remove-QueryString.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0004-bmcweb-handle-device-or-resource-busy-exception.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-VirtualMedia-fixes-for-Redfish-Service-Validator.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0006-Fix-Image-and-ImageName-values-in-schema.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0001-Redfish-TelemetryService-schema-implementation.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0002-Add-support-for-POST-in-MetricReportDefinitions.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0003-Add-support-for-DELETE-in-MetricReportDefinitions-st.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0004-Add-support-for-OnRequest-in-MetricReportDefinition.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0005-Add-support-for-MetricDefinition-scheme.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/0006-Fix-MetricReport-timestamp-for-EventService.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/telemetry/README (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces') 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 +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 +--- + 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 + #include + #include ++#include + + namespace crow + { +@@ -24,7 +24,8 @@ struct Request + boost::beast::http::request& 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 +-#include +-#include +-#include +-#include +- +-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 , 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(*s++); +- lnyb = static_cast(*s++); +- if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb)) +- { +- u1 = static_cast((BMCWEB_QS_HEX2DEC(unyb) * 16) + +- BMCWEB_QS_HEX2DEC(lnyb)); +- } +- else +- { +- u1 = '\0'; +- } +- } +- +- if (u2 == '+') +- { +- u2 = ' '; +- } +- if (u2 == '%') // easier/safer than scanf +- { +- unyb = static_cast(*qs++); +- lnyb = static_cast(*qs++); +- if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb)) +- { +- u2 = static_cast((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((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(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(p - qs.url.c_str() + url.c_str())); +- } +- return *this; +- } +- +- QueryString& operator=(QueryString&& qs) +- { +- keyValuePairs = std::move(qs.keyValuePairs); +- auto* oldData = const_cast(qs.url.c_str()); +- url = std::move(qs.url); +- for (auto& p : keyValuePairs) +- { +- p += const_cast(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(keyValuePairs.size())); +- return ret; +- } +- +- std::vector getList(const std::string& name) const +- { +- std::vector ret; +- std::string plus = name + "[]"; +- char* element = nullptr; +- +- int count = 0; +- while (true) +- { +- element = qsK2v(plus.c_str(), keyValuePairs.data(), +- static_cast(keyValuePairs.size()), count++); +- if (element == nullptr) +- { +- break; +- } +- ret.push_back(element); +- } +- return ret; +- } +- +- private: +- std::string url; +- std::vector 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 +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 +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 + ++#include ++ + namespace redfish + + { +@@ -109,6 +111,26 @@ static void vmParseInterfaceObject(const DbusInterfaceType& interface, + } + } + ++/** ++ * @brief parses Timeout property and converts to microseconds ++ */ ++static std::optional ++ vmParseTimeoutProperty(const std::variant& timeoutProperty) ++{ ++ const int* timeoutValue = std::get_if(&timeoutProperty); ++ if (timeoutValue) ++ { ++ constexpr int timeoutMarginSeconds = 10; ++ return std::chrono::duration_cast( ++ std::chrono::seconds(*timeoutValue + timeoutMarginSeconds)) ++ .count(); ++ } ++ else ++ { ++ return std::nullopt; ++ } ++} ++ + /** + * @brief Fill template for Virtual Media Item. + */ +@@ -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 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 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 +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 +--- + 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 + #include + #include ++#include + #include + + #include +@@ -49,7 +50,10 @@ enum class ConnState + class HttpClient : public std::enable_shared_from_this + { + 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> sslConn; ++ std::shared_ptr conn; + boost::asio::steady_timer timer; + boost::beast::flat_buffer buffer; + boost::beast::http::request req; +@@ -62,14 +66,37 @@ class HttpClient : public std::enable_shared_from_this + 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>(ioc, ctx); ++ } ++ else ++ { ++ conn = std::make_shared(ioc); ++ } ++ + if (state == ConnState::connectInProgress) + { + return; +@@ -77,25 +104,53 @@ class HttpClient : public std::enable_shared_from_this + 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 + + BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; + +- req.version(static_cast(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 + 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 + 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 + 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::connections::systemBus->get_io_context(), id, host, port, +- path); ++ path, (uriProto == "https" ? true : false)); + } + + Subscription(const std::shared_ptr& 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 +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 +--- + 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 @@ + + + +- +- +- +- +- ++ + + + + +- + + + + ++ ++ ++ ++ ++ + + + +-- +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 +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 +--- + 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(&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" +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 +Signed-off-by: Adrian Ambrożewicz +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()); + } + ++template ++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 ++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(app)); + nodes.emplace_back(std::make_unique(app)); + ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back( ++ std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(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 + #include + ++#include + #include + #include + + #include ++#include ++#include + + namespace redfish + { +@@ -436,5 +440,102 @@ bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key, + return details::unpackValue(jsonValue, key, value); + } + ++template ++struct IsStdFunction ++{ ++ static constexpr bool value = false; ++}; ++ ++template ++struct IsStdFunction> ++{ ++ static constexpr bool value = true; ++}; ++ ++template ++constexpr bool is_std_function_v = IsStdFunction::value; ++ ++/** ++ * @brief Assign dbus property to http response attribute if property is stored ++ * on the map. ++ */ ++template ++bool assignIfPresent( ++ const boost::container::flat_map>& ret, ++ const char* propertyName, nlohmann::json& attribute, const S& convert) ++{ ++ if constexpr (is_std_function_v) ++ { ++ 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(&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 ++bool assignIfPresent( ++ const boost::container::flat_map>& ret, ++ const char* propertyName, nlohmann::json& attribute) ++{ ++ return assignIfPresent(ret, propertyName, attribute, ++ [](const T& v) -> T { return v; }); ++} ++ ++template ++bool assignIfPresent( ++ const boost::container::flat_map>& ret, ++ const char* attributeName, crow::Response& res) ++{ ++ return assignIfPresent(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& 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, ++ const char* uri) ++{ ++ const std::array interfaces = {reportInterface}; ++ ++ dbus::utility::getSubTreePaths( ++ [asyncResp, uri](const boost::system::error_code ec, ++ const std::vector& 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 ++static void getReport(const std::shared_ptr& asyncResp, ++ const std::string& id, const char* schemaType, ++ const Callback&& callback) ++{ ++ const std::array interfaces = {reportInterface}; ++ ++ dbus::utility::getSubTreePaths( ++ [asyncResp, id, schemaType, ++ callback](const boost::system::error_code ec, ++ const std::vector& 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 ++ ++#include ++#include ++#include ++#include ++ ++namespace redfish ++{ ++ ++namespace time_utils ++{ ++ ++namespace details ++{ ++ ++template ++std::string toDurationFormatItem(std::chrono::milliseconds& duration, ++ const char* postfix) ++{ ++ const auto t = std::chrono::duration_cast(duration); ++ if (t.count() == 0) ++ { ++ return ""; ++ } ++ ++ std::stringstream ss; ++ if constexpr (std::is_same::value) ++ { ++ ss << static_cast(t.count()) / ++ static_cast(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>; ++ ++ fmt += "P"; ++ fmt += details::toDurationFormatItem(duration, "D"); ++ if (duration.count() == 0) ++ { ++ return fmt; ++ } ++ ++ fmt += "T"; ++ fmt += details::toDurationFormatItem(duration, "H"); ++ fmt += details::toDurationFormatItem(duration, "M"); ++ fmt += ++ details::toDurationFormatItem(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 ++ ++#include ++#include ++ ++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& 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(res); ++ telemetry::getReportCollection(asyncResp, telemetry::metricReportUri); ++ } ++}; ++ ++class MetricReport : public Node ++{ ++ public: ++ MetricReport(CrowApp& app) : ++ Node(app, std::string(telemetry::metricReportUri) + "/", ++ 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& params) override ++ { ++ auto asyncResp = std::make_shared(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>; ++ using MetricValues = std::vector>; ++ ++ 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, ++ 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>& ret) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ json_util::assignIfPresent( ++ ret, "Timestamp", asyncResp->res.jsonValue["Timestamp"], ++ crow::utility::getDateTime); ++ json_util::assignIfPresent( ++ 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 ++ ++#include ++#include ++ ++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& 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(res); ++ telemetry::getReportCollection(asyncResp, ++ telemetry::metricReportDefinitionUri); ++ } ++}; ++ ++class MetricReportDefinition : public Node ++{ ++ public: ++ MetricReportDefinition(CrowApp& app) : ++ Node(app, std::string(telemetry::metricReportDefinitionUri) + "/", ++ 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& params) override ++ { ++ auto asyncResp = std::make_shared(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 ++ toReportActions(const std::vector& actions) ++ { ++ const boost::container::flat_map ++ reportActions = { ++ {"Event", "RedfishEvent"}, ++ {"Log", "LogToMetricReportsCollection"}, ++ }; ++ ++ std::vector 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::string, std::string, std::string>>; ++ using Metrics = std::vector>>>; ++ ++ static Metrics toMetrics(const ReadingParameters& params) ++ { ++ Metrics metrics; ++ ++ for (auto& [sensorPaths, operationType, id, metadata] : params) ++ { ++ metrics.push_back({ ++ {"MetricId", id}, ++ {"MetricProperties", std::vector() = {metadata}}, ++ }); ++ } ++ ++ return metrics; ++ } ++ ++ static void ++ getReportDefinitonProperties(const std::shared_ptr 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, ++ uint32_t, ReadingParameters>>& ret) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec; ++ return; ++ } ++ ++ json_util::assignIfPresent>( ++ ret, "ReportAction", ++ asyncResp->res.jsonValue["ReportActions"], toReportActions); ++ auto assigned = json_util::assignIfPresent( ++ ret, "ReportingType", ++ asyncResp->res.jsonValue["MetricReportDefinitionType"]); ++ if (assigned && ++ asyncResp->res.jsonValue["MetricReportDefinitionType"] == ++ "Periodic") ++ { ++ json_util::assignIfPresent( ++ ret, "ScanPeriod", ++ asyncResp->res ++ .jsonValue["Schedule"]["RecurrenceInterval"], ++ time_utils::toDurationFormat); ++ } ++ json_util::assignIfPresent( ++ 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 ++ ++#include ++ ++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& 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(res); ++ dbus::utility::getAllProperties( ++ [asyncResp]( ++ const boost::system::error_code ec, ++ const boost::container::flat_map>& 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(ret, "MaxReports", ++ asyncResp->res); ++ json_util::assignIfPresent( ++ 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" +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 +Signed-off-by: Krzysztof Grobelny +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 ++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::value) ++ { ++ /* Half point is added to avoid numeric error on rounding */ ++ out = static_cast(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>; ++ ++ fmt.remove_prefix(1); ++ auto out = details::fromDurationFormatItem(fmt, "D"); ++ if (fmt[0] != 'T') ++ { ++ return static_cast(out); ++ } ++ ++ fmt.remove_prefix(1); ++ out += details::fromDurationFormatItem(fmt, "H"); ++ out += details::fromDurationFormatItem(fmt, "M"); ++ out += details::fromDurationFormatItem(fmt, "S"); ++ ++ return static_cast(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 ++bool validateParamsLength(crow::Response& res, Limits&& limits, ++ std::index_sequence) ++{ ++ return (... && std::get(limits).validate(res)); ++} ++} // namespace detail ++ ++template ++struct ItemSizeValidator ++{ ++ ItemSizeValidator(const T&& item, std::string_view name, size_t limit) : ++ item(std::forward(item)), name(name), limit(limit) ++ {} ++ ++ bool validate(crow::Response& res) const ++ { ++ return ItemSizeValidator::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(limit)) ++ { ++ messages::stringValueTooLong(res, std::string(name), ++ static_cast(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 ++ItemSizeValidator(const T&, std::string_view, size_t) ++ -> ItemSizeValidator; ++ ++ItemSizeValidator(const char*, std::string_view, size_t) ++ ->ItemSizeValidator; ++ ++template ++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 ++bool validateParamLength(crow::Response& res, T&& item, std::string_view name, ++ size_t limit) ++{ ++ return ItemSizeValidator(std::forward(item), name, limit).validate(res); ++} ++ ++template ++bool validateParamsLength(crow::Response& res, Limits&& limits) ++{ ++ return detail::validateParamsLength( ++ res, std::forward(limits), ++ std::make_index_sequence>>()); ++} ++ ++} // 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 ++#include + #include + ++#include + #include ++#include + #include + + 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; ++ using DbusSensor = sdbusplus::message::object_path; ++ using DbusSensors = std::vector; ++ using MetricParam = ++ std::tuple; ++ using MetricParams = std::vector; ++ /* ++ * AddReportArgs misses "Domain" parameter because it is constant for ++ * TelemetryService and equals "TelemetryService". ++ */ ++ using AddReportArgs = ++ std::tuple, uint32_t, ++ MetricParams>; ++ ++ void doPost(crow::Response& res, const crow::Request& req, ++ const std::vector& params) override ++ { ++ auto asyncResp = std::make_shared(res); ++ AddReportArgs addReportArgs; ++ if (!getUserParameters(res, req, addReportArgs)) ++ { ++ return; ++ } ++ ++ boost::container::flat_set chassisSensorSet; ++ auto unmatched = getChassisSensorNode( ++ std::get(addReportArgs), chassisSensorSet); ++ if (unmatched) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricProperties", ++ *unmatched); ++ return; ++ } ++ ++ auto addReportReq = ++ std::make_shared(addReportArgs, asyncResp); ++ for (auto& [chassis, sensorType] : chassisSensorSet) ++ { ++ retrieveUriToDbusMap( ++ chassis, sensorType, ++ [asyncResp, addReportReq]( ++ const boost::beast::http::status status, ++ const boost::container::flat_map& ++ uriToDbus) { *addReportReq += uriToDbus; }); ++ } ++ } ++ ++ static std::optional ++ replaceReportActions(std::vector& actions) ++ { ++ const boost::container::flat_map ++ 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 supportedDefinitionType = ++ {"Periodic", "OnRequest"}; ++ ++ static bool getUserParameters(crow::Response& res, const crow::Request& req, ++ AddReportArgs& params) ++ { ++ std::vector metrics; ++ std::optional 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& metrics, ++ crow::Response& res, ++ MetricParams& metricParams) ++ { ++ metricParams.reserve(metrics.size()); ++ for (auto& m : metrics) ++ { ++ std::string metricId; ++ std::vector 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 getChassisSensorNode( ++ const MetricParams& metricParams, ++ boost::container::flat_set& matched) ++ { ++ for (const auto& metricParam : metricParams) ++ { ++ const auto& sensors = std::get(metricParam); ++ for (const auto& sensor : sensors) ++ { ++ /* ++ * Support only following paths: ++ * "/redfish/v1/Chassis//Power#/..." ++ * "/redfish/v1/Chassis//Sensors/..." ++ * "/redfish/v1/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 replaceUriWithDbus( ++ const boost::container::flat_map& uriToDbus, ++ MetricParams& metricParams) ++ { ++ for (auto& metricParam : metricParams) ++ { ++ auto& dbusSensors = std::get(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, ++ 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}, addReportArgs{args} ++ {} ++ ~AddReport() ++ { ++ auto unmatched = replaceUriWithDbus( ++ uriToDbus, std::get(addReportArgs)); ++ if (unmatched) ++ { ++ messages::resourceNotFound(asyncResp->res, "MetricProperties", ++ *unmatched); ++ return; ++ } ++ ++ addReport(asyncResp, addReportArgs); ++ } ++ ++ AddReport& operator+=( ++ const boost::container::flat_map& rhs) ++ { ++ this->uriToDbus.insert(rhs.begin(), rhs.end()); ++ return *this; ++ } ++ ++ private: ++ std::shared_ptr asyncResp; ++ AddReportArgs addReportArgs; ++ boost::container::flat_map 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" +Date: Mon, 18 May 2020 12:40:15 +0200 +Subject: [PATCH 3/5] Add support for DELETE in MetricReportDefinitions/ + +Added support for DELETE action in MetricReportDefinitions/ +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 +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& params) override ++ { ++ auto asyncResp = std::make_shared(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, ++ 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 +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 +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 + ++#include + #include + + namespace dbus +@@ -130,5 +131,34 @@ inline void getAllProperties(Callback&& callback, const std::string& service, + interface); + } + ++template ++static void getProperty( ++ std::function 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& value) { ++ if (ec) ++ { ++ callback(ec, T{}); ++ return; ++ } ++ ++ if (auto v = std::get_if(&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, + const std::array interfaces = {reportInterface}; + + dbus::utility::getSubTreePaths( +- [asyncResp, id, schemaType, +- callback](const boost::system::error_code ec, +- const std::vector& reports) { ++ [asyncResp, id, schemaType, callback = std::move(callback)]( ++ const boost::system::error_code ec, ++ const std::vector& 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 ++ static void updateReport(Callback&& callback, ++ const std::shared_ptr& 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, ++ const std::string& reportPath, ++ const std::string& id) ++ { ++ dbus::utility::getProperty( ++ [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" +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 +Signed-off-by: Krzysztof Grobelny +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(app)); + + nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); ++ nodes.emplace_back(std::make_unique(app)); + nodes.emplace_back( + std::make_unique(app)); + nodes.emplace_back(std::make_unique(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 ++static inline void getChassisNames(F&& callback) ++{ ++ const std::array 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& chassisList) { ++ if (ec) ++ { ++ return; ++ } ++ ++ std::vector 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& 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(res); ++ auto collectionReduce = std::make_shared(asyncResp); ++ chassis::getChassisNames( ++ [asyncResp, ++ collectionReduce](const std::vector& 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} ++ { ++ dbusTypes.reserve(sensors::dbus::paths.size()); ++ } ++ ++ ~CollectionGather() ++ { ++ json_util::dbusPathsToMembersArray( ++ asyncResp->res, ++ std::vector(dbusTypes.begin(), dbusTypes.end()), ++ telemetry::metricDefinitionUri); ++ } ++ ++ CollectionGather& operator+=( ++ const boost::container::flat_map& 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; ++ boost::container::flat_set dbusTypes; ++ }; ++}; ++ ++class MetricDefinition : public Node ++{ ++ public: ++ MetricDefinition(CrowApp& app) : ++ Node(app, std::string(telemetry::metricDefinitionUri) + "/", ++ 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& params) override ++ { ++ auto asyncResp = std::make_shared(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(asyncResp, id); ++ chassis::getChassisNames( ++ [asyncResp, definitionGather, ++ sensorIndex](const std::vector& 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, ++ 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& 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; ++ std::vector redfishSensors; ++ const boost::container::flat_map 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>; + using MetricValues = std::vector>; ++ using ReadingParameters = ++ std::vector, ++ 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(); ++ 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>(*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, + 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>& ret) { ++ std::string, ++ std::variant>& ret) { + if (ec) + { + messages::internalError(asyncResp->res); +@@ -138,6 +185,22 @@ class MetricReport : public Node + json_util::assignIfPresent( + ret, "Readings", asyncResp->res.jsonValue["MetricValues"], + toMetricValues); ++ ++ auto found = ret.find("ReadingParameters"); ++ if (found != ret.end()) ++ { ++ auto params = ++ std::get_if(&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 names = { ++ "voltage", "power", "current", "fan_tach", ++ "temperature", "fan_pwm", "utilization"}; ++ ++static constexpr std::array 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> +- 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" +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 +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::vector>; + using EventServiceConfig = std::tuple; + + 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, std::variant>& + resp) { + if (ec) + { +@@ -1275,8 +1277,8 @@ class EventServiceManager + return; + } + +- const std::string* timestampPtr = +- std::get_if(&resp["Timestamp"]); ++ const int32_t* timestampPtr = ++ std::get_if(&resp["Timestamp"]); + if (!timestampPtr) + { + BMCWEB_LOG_DEBUG << "Failed to Get timestamp."; +@@ -1303,8 +1305,9 @@ class EventServiceManager + std::shared_ptr 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/ + 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)}" -- cgit v1.2.3