This document outlines the design of a plugin system for the Google BMC Redfish server. It also provides a user guide for developers.
CrowCpp/Crow is a C++ framework for creating HTTP or Websocket web services. It is easy to use, thanks to its routing system which is similar to Python's Flask. The core HTTP functionality of gBMCWeb is derived from Crow.
In Crow, a route determines what happens when your client connects to a specific URL. gBMCWeb supports not only Redfish routes, but also other routes such as WebSocket or dynamic routes, although there are no use cases of non-Redfish routes inside Google. The three main components of a Redfish route are:
URL, the relative path assigned to the route. In Redfish, a URL is corresponding to a Redfish resource. A URL can have parameters. For instance, the template “/redfish/v1/Chassis/” matches requests to any Chassis.
Method, the HTTP method of the route. In Redfish, a server must implement GET, POST, DELETE, and PATCH.
Handler, a piece of code that is executed whenever the client calls the associated route. In gBMCWeb, a Redfish handler is typically a lambda expression or a function that takes a “Request” object, a “Response” object, and any parameters in the URL template.
In BMCWeb, you can use the BMCWEB_ROUTE
macro to register a route.
One of the most frustrating drawbacks of OpenBMC/BMCweb is that it does not support plugins. This means that, without merging code into the upstream, there is no way to add new components to or modify its existing behaviors, without patches in the Yocto meta layer.
This document proposes a plugin system that can be used to solve the problems mentioned in the background section. The proposed system has the following features:
It can be loaded as static libraries at compile, which means that the logic in a plugin will not affect the main gBMCWeb daemon if it is not loaded.
It can extend existing behaviors, such as adding an OEM property to an existing Redfish resource.
It can replace existing functionality, such as replacing the execution codes of an existing Redfish resource completely.
It can add new Redfish resources or actions, such as implementing new Redfish resources and its associated actions, especially OEM or experimental resources that don’t affect other platforms.
Using gBMCWeb plugins, you can achieve the following:
The append plugin is supposed to be the most commonly used plugin by developers. It allows inserting another asynchronous callback function after the original handler.
The implementation first locates the corresponding rule based on the given URL and HTTP method. Then, it replaces the callback function with a new function, inside which the old callback is executed first. When the AsyncResp is destructed, the newly appended logic is executed before the Redfish parameter handling codes. Finally, the resulting response is written back to the clients. The following code snippet is the proposed implementation.
The implementation can be found in this commit
Users are expected to use the following macro to register an “Append” handler for a specific route.
#define REDFISH_HANDLER_APPEND(url, verb, func) \ appendRedfishHandler<crow::black_magic::getParameterTag(url)>(url, verb, \ func);
For example, you can override a property of a Redfish handler.
void crashdumpServiceGetPlugin( const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string&) { asyncResp->res.jsonValue["MaxNumberOfRecords"] = 10; } REDFISH_HANDLER_APPEND("/redfish/v1/Systems/<str>/LogServices/Crashdump/", boost::beast::http::verb::get, crashdumpServiceGetPlugin);
The replace handler can supersede the original callback of an existing route. The implementation is rather straightforward. It first locates the corresponding rule, as before. Then, it completely replaces the callback function with the newly fed callback functions.
The implementation can be found in this commit.
Users are expected to use the following macro to register a “Replace” handler for a specific route.
#define REDFISH_HANDLER_REPLACE(url, verb, func) \ replaceRedfishHandler<crow::black_magic::getParameterTag(url)>(url, verb, \ func);
We shall demonstrate the Append handler and Replace handler together with the following code snippet. The following code first replaces the existing static handler of “GET /redfish” with an asynchronous DBus call that is always going to fail via the replace plugin. Then it injects an extra code to run after the asynchronous call finishes via the append plugin.
void redfishGetReplaced(const crow::Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { if (!redfish::setUpRedfishRoute(req, asyncResp)) { return; } managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( [asyncResp](const boost::system::error_code ec, const std::variant<std::string>&) { if (ec) { asyncResp->res.jsonValue["dbus_call_result"] = "failed"; return; } }, "non-exist-service", "/no/exist/object", "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.Non.Exist.Interface", "Property"); asyncResp->res.jsonValue["v1"] = "hacked"; } void redfishGetAppended(const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { if (asyncResp->res.jsonValue["dbus_call_result"] != "failed") { asyncResp->res.jsonValue["plugin_is_working"] = "false"; return; } asyncResp->res.jsonValue["plugin_is_working"] = "true"; } REDFISH_HANDLER_REPLACE("/redfish/", boost::beast::http::verb::get, redfishGetReplaced); REDFISH_HANDLER_APPEND("/redfish/", boost::beast::http::verb::get, redfishGetAppended);
With a correctly implemented plugin system, we will see this result
GET http://localhost:18080/redfish/ { "dbus_call_result": "failed", "plugin_is_working": "true", "v1": "hacked" }
The add plugin is another frequently used tool that allows users to add a new route. The implementation of this plugin is the most straightforward. We simply need to leverage the existing “newRuleTagged” template function.
The implementation can be found in this commit.
Users are expected to use the following macro to add a Redfish route.
#define REDFISH_HANDLER_ADD(url, verb, privileges, func) \ plugins::addRedfishHandler<crow::black_magic::getParameterTag(url)>( \ url, verb, privileges, func);
The remove plugin is the inverse of the add plugin and should be used sparingly. It permits users to delete an existing route.
The implementation can be found in this commit.
Users are expected to use the following macro to remove a Redfish route.
#define REDFISH_HANDLER_REMOVE(url, verb) \ plugins::removeRedfishHandler<crow::black_magic::getParameterTag(url)>( \ url, verb);
gBMCWeb, which inherits from Crow, leverages a Trie-like structure in the following sequence:
To load plugins, we need to inject code that can modify the rule container before the trie is built and the HTTP App runs.
The implementation can be found in this commit.
To make compilation of a plugin work, a few symbols have to be declared and exposed to plugin libraries through headers. We have cleaned up the required headers so they form a complete set.
A distinct recipe has been developed for a plugin repository. Please see https://gbmc.googlesource.com/meta-gbmc-staging/+/refs/heads/master/recipes-phosphor/interfaces/gbmcweb-headers_git.bb.
See macros_test.cpp for the implemented unit tests. Feel free to contribute if you found a case is not coverred.
A plugin needs to hosted by a GIT repo. We have developed an example repo for users' reference. Please see https://gbmc.googlesource.com/gbmcweb-platform-plugins-example/+/refs/heads/master.
Its recipe is available in https://gbmc.googlesource.com/meta-gbmc-staging/+/refs/heads/master/recipes-phosphor/interfaces/gbmcweb-platform-plugins_git.bb.
To enable gBMCWeb Plugin, firstly you need to enable the Meson flag in the bmcweb recipe.
# recipes-phosphor/interfaces/bmcweb_%.bbappend PACKAGECONFIG: append = "gbmcweb-platform-plugins"
Secondly, replace the SRC_URI and SRCREV of the gbmcweb-platform-plugins
recipe such that it points out to your plugin repo.
# recipes-phosphor/interfaces/gbmcweb-platform-plugins_%.bbappend SRC_URI = "git://$YOUR_HOST/$YOUR_REPO;branch=master;protocol=https" SRCREV = "$YOUR_COMMIT"
With above steps, the BMCWeb binary now will include and run the plugin at startup.