OEM Route Handling Infrastructure
Goal of the MR is to provide infrastructure support in bmcweb to manage
the OEM fragment handling separately. OEM schema are vendor defined and
per DMTF resource we could have multiple vendor defined OEM schema to be
enabled.
The feature allows registration of route handler per schema per OEM
namespace.
Example
```
REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(service,
HttpVerb::Get)(oemOpenBmcCallback);
REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/Nvidia">(service,
HttpVerb::Get)(oemNidiaCallback);
```
We can have separate vendor defined route handlers per resource. Each of
these route handlers can populate their own vendor specific OEM data.
The OEM code can be better organized and enabled/disabled as per the
platform needs. The current MR has the code changes related to handling
GET requests alone. The feature only supports requests
where the response payload is JSON.
Tests
- All UT cases passes
- New UT added for RF OEM router passes
- Service Validator passes on qemu
- GET Response on Manager/bmc resource contains the OEM fragment
```
curl -c cjar -b cjar -k -X GET https://127.0.0.1:2443/redfish/v1/Managers/bmc
{
"@odata.id": "/redfish/v1/Managers/bmc",
"@odata.type": "#Manager.v1_14_0.Manager",
"Oem": {
"OpenBmc": {
"@odata.id": "/redfish/v1/Managers/bmc#/Oem/OpenBmc",
"@odata.type": "#OpenBMCManager.v1_0_0.Manager",
"Certificates": {
"@odata.id": "/redfish/v1/Managers/bmc/Truststore/Certificates"
}
}
},
"UUID": "40575e98-90d7-4c10-9eb5-8d8a7156c9b9"
}
```
Change-Id: Ic82aa5fe760eda31e2792fbdfb6884ac3ea613dc
Signed-off-by: Rohit PAI <rohitpai77@gmail.com>
diff --git a/.clang-tidy b/.clang-tidy
index 479c65b..cdfa821 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -394,7 +394,7 @@
- { key: readability-identifier-naming.ParameterCase, value: camelBack }
- { key: readability-identifier-naming.NamespaceCase, value: lower_case }
- { key: readability-identifier-naming.StructCase, value: CamelCase }
- - { key: readability-identifier-naming.FunctionIgnoredRegexp, value: (BMCWEB_LOG_DEBUG|BMCWEB_LOG_INFO|BMCWEB_LOG_WARNING|BMCWEB_LOG_ERROR|BMCWEB_LOG_CRITICAL) }
+ - { key: readability-identifier-naming.FunctionIgnoredRegexp, value: (BMCWEB_LOG_DEBUG|BMCWEB_LOG_INFO|BMCWEB_LOG_WARNING|BMCWEB_LOG_ERROR|BMCWEB_LOG_CRITICAL|REDFISH_SUB_ROUTE) }
- { key: readability-identifier-naming.StructIgnoredRegexp, value: (BMCWEB_LOG_DEBUG|BMCWEB_LOG_INFO|BMCWEB_LOG_WARNING|BMCWEB_LOG_ERROR|BMCWEB_LOG_CRITICAL) }
- { key: cppcoreguidelines-macro-usage.AllowedRegexp, value: DEBUG*|NLOHMANN_JSON_SERIALIZE_ENUM }
- { key: cppcoreguidelines-rvalue-reference-param-not-moved.IgnoreUnnamedParams, value: true }
diff --git a/http/routing/baserule.hpp b/http/routing/baserule.hpp
index 356d345..a96abcb 100644
--- a/http/routing/baserule.hpp
+++ b/http/routing/baserule.hpp
@@ -15,6 +15,7 @@
#include <limits>
#include <memory>
#include <string>
+#include <string_view>
#include <utility>
#include <vector>
@@ -23,7 +24,7 @@
class BaseRule
{
public:
- explicit BaseRule(const std::string& thisRule) : rule(thisRule) {}
+ explicit BaseRule(std::string_view thisRule) : rule(thisRule) {}
virtual ~BaseRule() = default;
diff --git a/http/routing/trie.hpp b/http/routing/trie.hpp
index cf1a4537..bfa441a 100644
--- a/http/routing/trie.hpp
+++ b/http/routing/trie.hpp
@@ -308,7 +308,7 @@
debugNodePrint(head(), 0U);
}
- private:
+ protected:
const ContainedType& head() const
{
return nodes.front();
diff --git a/meson.build b/meson.build
index 186c92c..4549ab2 100644
--- a/meson.build
+++ b/meson.build
@@ -455,6 +455,7 @@
'test/redfish-core/include/filter_expr_parser_test.cpp',
'test/redfish-core/include/privileges_test.cpp',
'test/redfish-core/include/redfish_aggregator_test.cpp',
+ 'test/redfish-core/include/redfish_oem_routing_test.cpp',
'test/redfish-core/include/redfish_test.cpp',
'test/redfish-core/include/registries_test.cpp',
'test/redfish-core/include/submit_test_event_test.cpp',
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index a9722d0..f92e4ef 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -3,6 +3,12 @@
#pragma once
#include "app.hpp"
+#include "async_resp.hpp"
+#include "http_request.hpp"
+#include "redfish_oem_routing.hpp"
+#include "verb.hpp"
+
+#include <memory>
namespace redfish
{
@@ -20,6 +26,40 @@
* @param[in] app Crow app on which Redfish will initialize
*/
explicit RedfishService(App& app);
+
+ // Temporary change to make redfish instance available in other places
+ // like query delegation.
+ static RedfishService& getInstance(App& app)
+ {
+ static RedfishService redfish(app);
+ return redfish;
+ }
+
+ void validate()
+ {
+ oemRouter.validate();
+ }
+
+ template <StringLiteral Rule>
+ auto& newRoute(HttpVerb method)
+ {
+ return oemRouter.newRule<Rule>(method);
+ }
+
+ void handleSubRoute(
+ const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const
+ {
+ oemRouter.handle(req, asyncResp);
+ }
+
+ OemRouter oemRouter;
};
+template <StringLiteral Path>
+auto& REDFISH_SUB_ROUTE(RedfishService& service, HttpVerb method)
+{
+ return service.newRoute<Path>(method);
+}
+
} // namespace redfish
diff --git a/redfish-core/include/redfish_oem_routing.hpp b/redfish-core/include/redfish_oem_routing.hpp
new file mode 100644
index 0000000..3133ccb
--- /dev/null
+++ b/redfish-core/include/redfish_oem_routing.hpp
@@ -0,0 +1,257 @@
+#pragma once
+
+#include "async_resp.hpp"
+#include "http_request.hpp"
+#include "http_response.hpp"
+#include "logging.hpp"
+#include "redfishoemrule.hpp"
+#include "sub_route_trie.hpp"
+#include "utility.hpp"
+#include "utils/query_param.hpp"
+#include "verb.hpp"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace redfish
+{
+
+// Helper struct to allow parsing string literals at compile time until
+// std::string is supported in constexpr context.
+// NOLINTBEGIN
+template <size_t N>
+struct StringLiteral
+{
+ constexpr StringLiteral(const char (&str)[N])
+ {
+ std::copy_n(str, N, value);
+ }
+
+ constexpr operator std::string_view() const
+ {
+ return std::string_view(std::data(value), N - 1);
+ }
+
+ char value[N];
+};
+// Explicit deduction guide to prevent Clang warnings
+template <size_t N>
+StringLiteral(const char (&)[N]) -> StringLiteral<N>;
+// NOLINTEND
+
+class OemRouter
+{
+ using SubRouteTrie = crow::SubRouteTrie<crow::SubRouteNode>;
+
+ public:
+ OemRouter() = default;
+
+ template <StringLiteral URI>
+ constexpr auto& newRule(HttpVerb method)
+ {
+ auto& perMethod = perMethods[static_cast<size_t>(method)];
+ constexpr std::string_view rule = URI;
+ constexpr uint64_t numArgs = crow::utility::getParameterTag(rule);
+
+ if constexpr (numArgs == 0)
+ {
+ using RuleT = OemRule<>;
+ std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
+ RuleT* ptr = ruleObject.get();
+ perMethod.internalAdd(rule, std::move(ruleObject));
+ return *ptr;
+ }
+ else if constexpr (numArgs == 1)
+ {
+ using RuleT = OemRule<std::string>;
+ std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
+ RuleT* ptr = ruleObject.get();
+ perMethod.internalAdd(rule, std::move(ruleObject));
+ return *ptr;
+ }
+ else if constexpr (numArgs == 2)
+ {
+ using RuleT = OemRule<std::string, std::string>;
+ std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
+ RuleT* ptr = ruleObject.get();
+ perMethod.internalAdd(rule, std::move(ruleObject));
+ return *ptr;
+ }
+ else if constexpr (numArgs == 3)
+ {
+ using RuleT = OemRule<std::string, std::string, std::string>;
+ std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
+ RuleT* ptr = ruleObject.get();
+ perMethod.internalAdd(rule, std::move(ruleObject));
+ return *ptr;
+ }
+ else if constexpr (numArgs == 4)
+ {
+ using RuleT =
+ OemRule<std::string, std::string, std::string, std::string>;
+ std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
+ RuleT* ptr = ruleObject.get();
+ perMethod.internalAdd(rule, std::move(ruleObject));
+ return *ptr;
+ }
+ else
+ {
+ using RuleT = OemRule<std::string, std::string, std::string,
+ std::string, std::string>;
+ std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
+ RuleT* ptr = ruleObject.get();
+ perMethod.internalAdd(rule, std::move(ruleObject));
+ return *ptr;
+ }
+ }
+
+ struct PerMethod
+ {
+ std::vector<std::unique_ptr<OemBaseRule>> rules;
+ SubRouteTrie trie;
+ // rule index 0 has special meaning; preallocate it to avoid
+ // duplication.
+ PerMethod() : rules(1) {}
+
+ void internalAdd(std::string_view rule,
+ std::unique_ptr<OemBaseRule>&& ruleObject)
+ {
+ rules.emplace_back(std::move(ruleObject));
+ trie.add(rule, static_cast<unsigned>(rules.size() - 1U));
+ // request to /resource/#/frag matches /resource#/frag
+ size_t hashPos = rule.find("/#/");
+ if (hashPos != std::string_view::npos)
+ {
+ std::string url(rule.substr(0, hashPos));
+ url += '#';
+ url += rule.substr(hashPos + 2); // Skip "/#" (2 characters)
+ std::string_view fragRule = url;
+ trie.add(fragRule, static_cast<unsigned>(rules.size() - 1U));
+ }
+ }
+ };
+
+ struct FindRoute
+ {
+ std::vector<OemBaseRule*> fragmentRules;
+ std::vector<std::string> params;
+ };
+
+ struct FindRouteResponse
+ {
+ FindRoute route;
+ };
+
+ static FindRoute findRouteByPerMethod(std::string_view url,
+ const PerMethod& perMethod)
+ {
+ FindRoute route;
+
+ SubRouteTrie::FindResult found = perMethod.trie.find(url);
+ route.params = std::move(found.params);
+ for (auto fragmentRuleIndex : found.fragmentRuleIndexes)
+ {
+ if (fragmentRuleIndex >= perMethod.rules.size())
+ {
+ throw std::runtime_error("Trie internal structure corrupted!");
+ }
+ route.fragmentRules.emplace_back(
+ (perMethod.rules[fragmentRuleIndex]).get());
+ }
+
+ return route;
+ }
+
+ FindRouteResponse findRoute(const crow::Request& req) const
+ {
+ FindRouteResponse findRoute;
+ std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
+ if (!verb)
+ {
+ return findRoute;
+ }
+ size_t reqMethodIndex = static_cast<size_t>(*verb);
+ if (reqMethodIndex >= perMethods.size())
+ {
+ return findRoute;
+ }
+
+ FindRoute route = findRouteByPerMethod(req.url().encoded_path(),
+ perMethods[reqMethodIndex]);
+ if (!route.fragmentRules.empty())
+ {
+ findRoute.route = route;
+ }
+ else
+ {
+ BMCWEB_LOG_DEBUG(
+ "No fragments for for url {}, method {}",
+ req.url().encoded_path(),
+ httpVerbToString(static_cast<HttpVerb>(reqMethodIndex)));
+ }
+
+ return findRoute;
+ }
+
+ void validate()
+ {
+ for (PerMethod& perMethod : perMethods)
+ {
+ perMethod.trie.validate();
+ }
+ }
+
+ void debugPrint()
+ {
+ for (size_t i = 0; i < perMethods.size(); i++)
+ {
+ BMCWEB_LOG_CRITICAL("{}",
+ httpVerbToString(static_cast<HttpVerb>(i)));
+ perMethods[i].trie.debugPrint();
+ }
+ }
+
+ void handle(const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const
+ {
+ BMCWEB_LOG_DEBUG("Checking OEM routes");
+ FindRouteResponse foundRoute = findRoute(req);
+ std::vector<OemBaseRule*> fragments =
+ std::move(foundRoute.route.fragmentRules);
+ std::vector<std::string> params = std::move(foundRoute.route.params);
+ if (!fragments.empty())
+ {
+ std::function<void(crow::Response&)> handler =
+ asyncResp->res.releaseCompleteRequestHandler();
+ auto multiResp = std::make_shared<bmcweb::AsyncResp>();
+ multiResp->res.setCompleteRequestHandler(std::move(handler));
+
+ // Copy so that they exists when completion handler is called.
+ auto uriFragments =
+ std::make_shared<std::vector<OemBaseRule*>>(fragments);
+ auto uriParams = std::make_shared<std::vector<std::string>>(params);
+
+ asyncResp->res.setCompleteRequestHandler(std::bind_front(
+ query_param::MultiAsyncResp::startMultiFragmentHandle,
+ std::make_shared<crow::Request>(req), multiResp, uriFragments,
+ uriParams));
+ }
+ else
+ {
+ BMCWEB_LOG_DEBUG("No OEM routes found");
+ }
+ }
+
+ private:
+ std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods;
+};
+} // namespace redfish
diff --git a/redfish-core/include/redfishoemrule.hpp b/redfish-core/include/redfishoemrule.hpp
new file mode 100644
index 0000000..018a18e
--- /dev/null
+++ b/redfish-core/include/redfishoemrule.hpp
@@ -0,0 +1,104 @@
+#pragma once
+#include "async_resp.hpp"
+#include "http_request.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+namespace redfish
+{
+class OemBaseRule
+{
+ public:
+ explicit OemBaseRule(std::string_view thisRule) : rule(thisRule) {}
+ virtual ~OemBaseRule() = default;
+ OemBaseRule(const OemBaseRule&) = delete;
+ OemBaseRule(OemBaseRule&&) = delete;
+ OemBaseRule& operator=(const OemBaseRule&) = delete;
+ OemBaseRule& operator=(const OemBaseRule&&) = delete;
+
+ virtual void handle(const crow::Request& /*req*/,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::vector<std::string>& /*params*/) = 0;
+ std::string rule;
+};
+
+template <typename... Args>
+class OemRule : public OemBaseRule
+{
+ public:
+ using self_t = OemRule<Args...>;
+
+ explicit OemRule(std::string_view ruleIn) : OemBaseRule(ruleIn) {}
+
+ void validate()
+ {
+ if (!handler)
+ {
+ throw std::runtime_error(
+ "no OEM fragment handler for the rule {}" + rule);
+ }
+ }
+
+ template <typename Func>
+ void operator()(Func&& f)
+ {
+ static_assert(
+ std::is_invocable_v<Func, crow::Request,
+ std::shared_ptr<bmcweb::AsyncResp>&, Args...>,
+ "Handler type is mismatched with URL parameters");
+ static_assert(
+ std::is_same_v<
+ void, std::invoke_result_t<Func, crow::Request,
+ std::shared_ptr<bmcweb::AsyncResp>&,
+ Args...>>,
+ "Handler function with response argument should have void return type");
+
+ handler = std::forward<Func>(f);
+ }
+
+ void handle(const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::vector<std::string>& params) override
+ {
+ if constexpr (sizeof...(Args) == 0)
+ {
+ handler(req, asyncResp);
+ }
+ else if constexpr (sizeof...(Args) == 1)
+ {
+ handler(req, asyncResp, params[0]);
+ }
+ else if constexpr (sizeof...(Args) == 2)
+ {
+ handler(req, asyncResp, params[0], params[1]);
+ }
+ else if constexpr (sizeof...(Args) == 3)
+ {
+ handler(req, asyncResp, params[0], params[1], params[2]);
+ }
+ else if constexpr (sizeof...(Args) == 4)
+ {
+ handler(req, asyncResp, params[0], params[1], params[2], params[3]);
+ }
+ else if constexpr (sizeof...(Args) == 5)
+ {
+ handler(req, asyncResp, params[0], params[1], params[2], params[3],
+ params[4]);
+ }
+ static_assert(sizeof...(Args) <= 5, "More args than are supported");
+ }
+
+ private:
+ std::function<void(const crow::Request&,
+ const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>
+ handler;
+};
+} // namespace redfish
diff --git a/redfish-core/include/sub_route_trie.hpp b/redfish-core/include/sub_route_trie.hpp
new file mode 100644
index 0000000..3071fe7
--- /dev/null
+++ b/redfish-core/include/sub_route_trie.hpp
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+
+#include "logging.hpp"
+#include "routing/trie.hpp"
+
+#include <cerrno>
+#include <cstdlib>
+#include <format>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace crow
+{
+
+struct SubRouteNode : public crow::Node
+{
+ using ChildMap = crow::Node::ChildMap;
+ ChildMap fragmentChildren;
+
+ bool isSimpleNode() const
+ {
+ return crow::Node::isSimpleNode() && fragmentChildren.empty();
+ }
+};
+
+template <typename ContainedType>
+class SubRouteTrie : public crow::Trie<ContainedType>
+{
+ public:
+ struct FindResult
+ {
+ std::vector<std::string> params;
+ std::vector<unsigned> fragmentRuleIndexes;
+ };
+
+ private:
+ FindResult findHelper(const std::string_view reqUrl,
+ const ContainedType& node,
+ std::vector<std::string>& params) const
+ {
+ if (reqUrl.empty())
+ {
+ FindResult result = {params, {}};
+ for (const auto& [fragment, fragmentRuleIndex] :
+ node.fragmentChildren)
+ {
+ result.fragmentRuleIndexes.push_back(fragmentRuleIndex);
+ }
+ return result;
+ }
+
+ if (node.stringParamChild != 0U)
+ {
+ size_t epos = reqUrl.find('/');
+ if (epos == std::string_view::npos)
+ {
+ params.emplace_back(reqUrl);
+ FindResult ret =
+ findHelper("", this->nodes[node.stringParamChild], params);
+ if (!ret.fragmentRuleIndexes.empty())
+ {
+ return ret;
+ }
+ params.pop_back();
+ }
+ else
+ {
+ params.emplace_back(reqUrl.substr(0, epos));
+ FindResult ret =
+ findHelper(reqUrl.substr(epos),
+ this->nodes[node.stringParamChild], params);
+ if (!ret.fragmentRuleIndexes.empty())
+ {
+ return ret;
+ }
+ params.pop_back();
+ }
+ }
+
+ if (node.pathParamChild != 0U)
+ {
+ params.emplace_back(reqUrl);
+ FindResult ret =
+ findHelper("", this->nodes[node.pathParamChild], params);
+ if (!ret.fragmentRuleIndexes.empty())
+ {
+ return ret;
+ }
+ params.pop_back();
+ }
+
+ for (const typename ContainedType::ChildMap::value_type& kv :
+ node.children)
+ {
+ const std::string& fragment = kv.first;
+ const ContainedType& child = this->nodes[kv.second];
+
+ if (reqUrl.starts_with(fragment))
+ {
+ FindResult ret =
+ findHelper(reqUrl.substr(fragment.size()), child, params);
+ if (!ret.fragmentRuleIndexes.empty())
+ {
+ return ret;
+ }
+ }
+ }
+
+ return {std::vector<std::string>(), {}};
+ }
+
+ public:
+ FindResult find(const std::string_view reqUrl) const
+ {
+ std::vector<std::string> start;
+ return findHelper(reqUrl, this->head(), start);
+ }
+
+ void add(std::string_view urlIn, unsigned ruleIndex)
+ {
+ size_t idx = 0;
+
+ std::string_view url = urlIn;
+
+ std::string_view fragment;
+ // Check if the URL contains a fragment (#)
+ size_t fragmentPos = urlIn.find('#');
+ size_t queryPos = urlIn.find('?');
+ if (fragmentPos != std::string::npos && queryPos == std::string::npos &&
+ fragmentPos != urlIn.length() - 1)
+ {
+ fragment = urlIn.substr(fragmentPos + 1);
+ url = urlIn.substr(0, fragmentPos);
+ }
+
+ if (fragment.empty())
+ {
+ BMCWEB_LOG_CRITICAL("empty fragment on rule \"{}\"", urlIn);
+ throw std::runtime_error(
+ std::format("empty fragment on rule \"{}\"", urlIn));
+ }
+
+ while (!url.empty())
+ {
+ char c = url[0];
+ if (c == '<')
+ {
+ bool found = false;
+ for (const std::string_view str1 :
+ {"<str>", "<string>", "<path>"})
+ {
+ if (!url.starts_with(str1))
+ {
+ continue;
+ }
+ found = true;
+ ContainedType& node = this->nodes[idx];
+ size_t* param = &node.stringParamChild;
+ if (str1 == "<path>")
+ {
+ param = &node.pathParamChild;
+ }
+ if (*param == 0U)
+ {
+ *param = this->newNode();
+ }
+ idx = *param;
+
+ url.remove_prefix(str1.size());
+ break;
+ }
+ if (found)
+ {
+ continue;
+ }
+
+ BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn);
+ return;
+ }
+ std::string piece(&c, 1);
+ if (!this->nodes[idx].children.contains(piece))
+ {
+ unsigned newNodeIdx = this->newNode();
+ this->nodes[idx].children.emplace(piece, newNodeIdx);
+ }
+ idx = this->nodes[idx].children[piece];
+ url.remove_prefix(1);
+ }
+ ContainedType& node = this->nodes[idx];
+ if (node.fragmentChildren.find(fragment) != node.fragmentChildren.end())
+ {
+ BMCWEB_LOG_CRITICAL(
+ R"(fragment handler already exists for "{}" fragment "{}")",
+ urlIn, fragment);
+ throw std::runtime_error(std::format(
+ R"(handler already exists for url "{}" fragment "{}")", urlIn,
+ fragment));
+ }
+
+ node.fragmentChildren.emplace(fragment, ruleIndex);
+ }
+
+ private:
+ void debugNodePrint(ContainedType& n, size_t level)
+ {
+ std::string spaces(level, ' ');
+ if (n.stringParamChild != 0U)
+ {
+ BMCWEB_LOG_DEBUG("{}<str>", spaces);
+ debugNodePrint(this->nodes[n.stringParamChild], level + 5);
+ }
+ if (n.pathParamChild != 0U)
+ {
+ BMCWEB_LOG_DEBUG("{} <path>", spaces);
+ debugNodePrint(this->nodes[n.pathParamChild], level + 6);
+ }
+ for (const typename ContainedType::ChildMap::value_type& kv :
+ n.fragmentChildren)
+ {
+ BMCWEB_LOG_DEBUG("{}#{}", spaces, kv.first);
+ }
+ for (const typename ContainedType::ChildMap::value_type& kv :
+ n.children)
+ {
+ BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first);
+ debugNodePrint(this->nodes[kv.second], level + kv.first.size());
+ }
+ }
+
+ public:
+ void debugPrint()
+ {
+ debugNodePrint(this->head(), 0U);
+ }
+};
+
+} // namespace crow
diff --git a/redfish-core/include/utils/json_utils.hpp b/redfish-core/include/utils/json_utils.hpp
index 17d8990..60994fe 100644
--- a/redfish-core/include/utils/json_utils.hpp
+++ b/redfish-core/include/utils/json_utils.hpp
@@ -722,6 +722,21 @@
std::forward<UnpackTypes&&>(in)...);
}
+inline std::optional<nlohmann::json::json_pointer>
+ createJsonPointerFromFragment(std::string_view input)
+{
+ auto hashPos = input.find('#');
+ if (hashPos == std::string_view::npos || hashPos + 1 >= input.size())
+ {
+ BMCWEB_LOG_ERROR(
+ "createJsonPointerFromFragment() No fragment found after #");
+ return std::nullopt;
+ }
+
+ std::string_view fragment = input.substr(hashPos + 1);
+ return nlohmann::json::json_pointer(std::string(fragment));
+}
+
template <typename... UnpackTypes>
bool readJsonAction(const crow::Request& req, crow::Response& res,
const char* key, UnpackTypes&&... in)
diff --git a/redfish-core/include/utils/query_param.hpp b/redfish-core/include/utils/query_param.hpp
index 9b53f54..3acb511 100644
--- a/redfish-core/include/utils/query_param.hpp
+++ b/redfish-core/include/utils/query_param.hpp
@@ -14,7 +14,9 @@
#include "http_response.hpp"
#include "json_formatters.hpp"
#include "logging.hpp"
+#include "redfishoemrule.hpp"
#include "str_utility.hpp"
+#include "utils/json_utils.hpp"
#include <unistd.h>
@@ -748,7 +750,11 @@
// class manages the final "merge" of the json resources.
MultiAsyncResp(crow::App& appIn,
std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
- app(appIn), finalRes(std::move(finalResIn))
+ app(&appIn), finalRes(std::move(finalResIn))
+ {}
+
+ explicit MultiAsyncResp(std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
+ app(nullptr), finalRes(std::move(finalResIn))
{}
void addAwaitingResponse(
@@ -806,7 +812,41 @@
logPtr(&asyncResp->res));
addAwaitingResponse(asyncResp, node.location);
- app.handle(newReq, asyncResp);
+ if (app != nullptr)
+ {
+ app->handle(newReq, asyncResp);
+ }
+ }
+ }
+
+ static void startMultiFragmentHandle(
+ const std::shared_ptr<crow::Request>& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::shared_ptr<std::vector<OemBaseRule*>>& fragments,
+ const std::shared_ptr<std::vector<std::string>>& params,
+ const crow::Response& resIn)
+ {
+ asyncResp->res.jsonValue = resIn.jsonValue;
+ auto multi = std::make_shared<MultiAsyncResp>(asyncResp);
+ for (OemBaseRule* fragment : *fragments)
+ {
+ if (fragment != nullptr)
+ {
+ OemBaseRule& fragmentRule = *fragment;
+ auto rsp = std::make_shared<bmcweb::AsyncResp>();
+ BMCWEB_LOG_DEBUG("Matched fragment GET rule '{}' {}",
+ fragmentRule.rule, req->methodString());
+ BMCWEB_LOG_DEBUG(
+ "Handling fragment rules: setting completion handler on {}",
+ logPtr(&rsp->res));
+ std::optional<nlohmann::json::json_pointer> jsonFragmentPtr =
+ json_util::createJsonPointerFromFragment(fragmentRule.rule);
+ if (jsonFragmentPtr)
+ {
+ multi->addAwaitingResponse(rsp, *jsonFragmentPtr);
+ fragmentRule.handle(*req, rsp, *params);
+ }
+ }
}
}
@@ -819,7 +859,7 @@
multi->placeResult(locationToPlace, res);
}
- crow::App& app;
+ crow::App* app;
std::shared_ptr<bmcweb::AsyncResp> finalRes;
};
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index e5a89f0..72b091c 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -18,6 +18,7 @@
#include "openbmc/openbmc_managers.hpp"
#include "persistent_data.hpp"
#include "query.hpp"
+#include "redfish.hpp"
#include "redfish_util.hpp"
#include "registries/privilege_registry.hpp"
#include "utils/dbus_utils.hpp"
@@ -641,8 +642,6 @@
BMCWEB_REDFISH_MANAGER_URI_NAME);
}
- getHandleOemOpenBmc(req, asyncResp, managerId);
-
// Manager.Reset (an action) can be many values, OpenBMC only
// supports BMC reboot.
nlohmann::json& managerReset =
@@ -710,12 +709,6 @@
"/redfish/v1/Managers/{}/ManagerDiagnosticData",
BMCWEB_REDFISH_MANAGER_URI_NAME);
- if constexpr (BMCWEB_REDFISH_OEM_MANAGER_FAN_DATA)
- {
- auto pids = std::make_shared<GetPIDValues>(asyncResp);
- pids->run();
- }
-
getMainChassisId(asyncResp, [](const std::string& chassisId,
const std::shared_ptr<
bmcweb::AsyncResp>& aRsp) {
@@ -878,6 +871,8 @@
}
}
});
+
+ RedfishService::getInstance(app).handleSubRoute(req, asyncResp);
});
BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/")
diff --git a/redfish-core/lib/openbmc/openbmc_managers.hpp b/redfish-core/lib/openbmc/openbmc_managers.hpp
index eaa61ee..8bd5625 100644
--- a/redfish-core/lib/openbmc/openbmc_managers.hpp
+++ b/redfish-core/lib/openbmc/openbmc_managers.hpp
@@ -1,4 +1,3 @@
-
#pragma once
#include "bmcweb_config.h"
@@ -9,8 +8,10 @@
#include "error_messages.hpp"
#include "http_request.hpp"
#include "logging.hpp"
+#include "redfish.hpp"
#include "utils/dbus_utils.hpp"
#include "utils/json_utils.hpp"
+#include "verb.hpp"
#include <boost/asio/post.hpp>
#include <boost/beast/http/status.hpp>
@@ -1473,13 +1474,13 @@
size_t objectCount = 0;
};
-inline void getHandleOemOpenBmc(
+inline void handleGetManagerOpenBmc(
const crow::Request& /*req*/,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /*managerId*/)
{
// Default OEM data
- nlohmann::json& oemOpenbmc = asyncResp->res.jsonValue["Oem"]["OpenBmc"];
+ nlohmann::json& oemOpenbmc = asyncResp->res.jsonValue;
oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager";
oemOpenbmc["@odata.id"] =
boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc",
@@ -1498,4 +1499,10 @@
}
}
+inline void requestRoutesOpenBmcManager(RedfishService& service)
+{
+ REDFISH_SUB_ROUTE<"/redfish/v1/Managers/<str>/#/Oem/OpenBmc">(
+ service, HttpVerb::Get)(handleGetManagerOpenBmc);
+}
+
} // namespace redfish
diff --git a/redfish-core/src/redfish.cpp b/redfish-core/src/redfish.cpp
index eb857af..ac06746 100644
--- a/redfish-core/src/redfish.cpp
+++ b/redfish-core/src/redfish.cpp
@@ -29,6 +29,7 @@
#include "metric_report_definition.hpp"
#include "network_protocol.hpp"
#include "odata.hpp"
+#include "openbmc/openbmc_managers.hpp"
#include "pcie.hpp"
#include "power.hpp"
#include "power_subsystem.hpp"
@@ -233,6 +234,10 @@
// Note, this must be the last route registered
requestRoutesRedfish(app);
+
+ requestRoutesOpenBmcManager(*this);
+
+ validate();
}
} // namespace redfish
diff --git a/src/webserver_run.cpp b/src/webserver_run.cpp
index 12c82fe..1f95959 100644
--- a/src/webserver_run.cpp
+++ b/src/webserver_run.cpp
@@ -81,7 +81,7 @@
if constexpr (BMCWEB_REDFISH)
{
- redfish::RedfishService redfish(app);
+ redfish::RedfishService::getInstance(app);
// Create EventServiceManager instance and initialize Config
redfish::EventServiceManager::getInstance();
diff --git a/test/redfish-core/include/redfish_oem_routing_test.cpp b/test/redfish-core/include/redfish_oem_routing_test.cpp
new file mode 100644
index 0000000..68dd09b
--- /dev/null
+++ b/test/redfish-core/include/redfish_oem_routing_test.cpp
@@ -0,0 +1,70 @@
+
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "http_request.hpp"
+#include "redfish.hpp"
+#include "verb.hpp"
+
+#include <boost/beast/http/verb.hpp>
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <system_error>
+
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+namespace
+{
+
+TEST(OemRouter, FragmentRoutes)
+{
+ std::error_code ec;
+ App app;
+ RedfishService service(app);
+
+ // Callback handler that does nothing
+ bool oemCalled = false;
+ auto oemCallback = [&oemCalled](const crow::Request&,
+ const std::shared_ptr<bmcweb::AsyncResp>&,
+ const std::string& bar) {
+ oemCalled = true;
+ EXPECT_EQ(bar, "bar");
+ };
+ bool standardCalled = false;
+ auto standardCallback =
+ [&standardCalled,
+ &service](const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& bar) {
+ service.handleSubRoute(req, asyncResp);
+ standardCalled = true;
+ EXPECT_EQ(bar, "bar");
+ };
+ // Need the normal route registered for OEM to work
+ BMCWEB_ROUTE(app, "/foo/<str>/")
+ .methods(boost::beast::http::verb::get)(standardCallback);
+ REDFISH_SUB_ROUTE<"/foo/<str>/#/Oem">(service, HttpVerb::Get)(oemCallback);
+
+ app.validate();
+ service.validate();
+
+ {
+ constexpr std::string_view reqUrl = "/foo/bar";
+
+ std::shared_ptr<crow::Request> req = std::make_shared<crow::Request>(
+ crow::Request::Body{boost::beast::http::verb::get, reqUrl, 11}, ec);
+
+ std::shared_ptr<bmcweb::AsyncResp> asyncResp =
+ std::make_shared<bmcweb::AsyncResp>();
+
+ app.handle(req, asyncResp);
+ }
+ EXPECT_TRUE(oemCalled);
+ EXPECT_TRUE(standardCalled);
+}
+
+} // namespace
+} // namespace redfish