| # gBMCWeb Plugin | 
 |  | 
 | This document outlines the design of a plugin system for the Google BMC Redfish | 
 | server. It also provides a user guide for developers. | 
 |  | 
 | ## Background | 
 |  | 
 | ### gBMCWeb and Crow | 
 |  | 
 | [CrowCpp/Crow](https://github.com/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](https://flask.palletsprojects.com/en/2.3.x/). | 
 | The core HTTP functionality of gBMCWeb is derived from Crow. | 
 |  | 
 | #### Route: URL, Method, and Handler | 
 |  | 
 | 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/<string>” 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. | 
 |  | 
 | ### Lack of Plugin System | 
 |  | 
 | 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. | 
 |  | 
 | ## Overview | 
 |  | 
 | 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: | 
 |  | 
 | - Faster iteration: machine-specific logic can now be implemented as plugins by | 
 |   Google or vendors, which speeds up the process. | 
 | - Isolation: The main branch of gBMCWeb will become cleaner as hacks or | 
 |   technical debts can be platform-specific plugins. Rest of the platforms won’t | 
 |   be affected as they don’t include these plugins. | 
 |  | 
 | ## Detailed Design | 
 |  | 
 | ### Append | 
 |  | 
 | 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](https://gbmc.googlesource.com/gbmcweb/+/fa9601da5c5f414af4542c79df1d0a2d7d26b1a4) | 
 |  | 
 | Users are expected to use the following macro to register an "Append" handler | 
 | for a specific route. | 
 |  | 
 | ```c++ | 
 | #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. | 
 |  | 
 | ```c++ | 
 | 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); | 
 | ``` | 
 |  | 
 | ### Replace | 
 |  | 
 | 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](https://gbmc.googlesource.com/gbmcweb/+/c2761daf22230759e69ad30be61f466ecdee2f1f). | 
 |  | 
 | Users are expected to use the following macro to register a "Replace" handler | 
 | for a specific route. | 
 |  | 
 | ```c++ | 
 | #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. | 
 |  | 
 | ```c++ | 
 | 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 | 
 |  | 
 | ```shell | 
 | GET  http://localhost:18080/redfish/ | 
 |  | 
 | { | 
 |   "dbus_call_result": "failed", | 
 |   "plugin_is_working": "true", | 
 |   "v1": "hacked" | 
 | } | 
 | ``` | 
 |  | 
 | ### Add | 
 |  | 
 | 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](https://gbmc.googlesource.com/gbmcweb/+/1249173411d3d064a2d20bbaf39183d94d41437e). | 
 |  | 
 | Users are expected to use the following macro to add a Redfish route. | 
 |  | 
 | ```c++ | 
 | #define REDFISH_HANDLER_ADD(url, verb, privileges, func)                       \ | 
 |     plugins::addRedfishHandler<crow::black_magic::getParameterTag(url)>(       \ | 
 |         url, verb, privileges, func); | 
 | ``` | 
 |  | 
 | ### Remove | 
 |  | 
 | 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](https://gbmc.googlesource.com/gbmcweb/+/53d6d73c9d06f6a2400615bb71a61728f9af4abc). | 
 |  | 
 | Users are expected to use the following macro to remove a Redfish route. | 
 |  | 
 | ```c++ | 
 | #define REDFISH_HANDLER_REMOVE(url, verb)                                      \ | 
 |     plugins::removeRedfishHandler<crow::black_magic::getParameterTag(url)>(    \ | 
 |         url, verb); | 
 | ``` | 
 |  | 
 | ### Load Plugins | 
 |  | 
 | gBMCWeb, which inherits from Crow, leverages a Trie-like structure in the | 
 | following sequence: | 
 |  | 
 | 1. All routes are added by the “BMCWEB_ROUTE” macro and stored in a container of | 
 |    type Rule. | 
 | 2. In the main thread, when the HTTP App starts running, the list of rules is | 
 |    iterated and added to a Trie. Each leaf node contains a pointer to the Rule | 
 |    object. | 
 | 3. The Trie is looked up when new HTTP requests arrive. If there is a match, the | 
 |    callback function stored in the leaf node will be invoked. | 
 |  | 
 | 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](https://gbmc.googlesource.com/gbmcweb/+/c783047b85f0d041e77c4a58c22cf9641c9e7b7e). | 
 |  | 
 | ### Install Headers | 
 |  | 
 | 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>. | 
 |  | 
 | ## Unit test | 
 |  | 
 | See [macros_test.cpp](plugins/macros_test.cpp) for the implemented unit tests. | 
 | Feel free to contribute if you found a case is not coverred. | 
 |  | 
 | ## Plugin Repo and Recipe | 
 |  | 
 | 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>. | 
 |  | 
 | ## Enable Plugin | 
 |  | 
 | To enable gBMCWeb Plugin, firstly you need to enable the Meson flag in the | 
 | bmcweb recipe. | 
 |  | 
 | ```python | 
 | # 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. | 
 |  | 
 | ```python | 
 | # 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. |