Redfish OEM Extensions
Design, implement, and validate vendor-specific Redfish OEM extensions in OpenBMC bmcweb.
Table of Contents
- Overview
- OEM Namespace Design
- CSDL and OpenAPI Schema Creation
- Regenerating Redfish Schemas and Generated Code
- OEM Route Implementation
- Building and Deploying
- Redfish Service Validator Testing
- Extending Existing Resources with OEM Properties
- Troubleshooting
- Examples
- References
Overview
The Redfish standard provides a well-defined mechanism for vendors to expose platform-specific functionality through OEM extensions. In OpenBMC, OEM extensions are implemented in bmcweb and follow the /Oem/<Vendor> namespace pattern defined by the DMTF Redfish specification.
This guide covers the complete lifecycle: designing the namespace, creating schemas, implementing C++ route handlers with D-Bus backends, and validating with the Redfish Service Validator.
---
title: Redfish OEM Extension Architecture
---
flowchart TB
client["Redfish Client"]
client -->|"GET /redfish/v1/Oem/MyVendor/..."| bmcweb
subgraph bmcweb["bmcweb"]
direction LR
router["OEM Router"]
handler["OEM Handler<br/>(C++ route)"]
router --> handler
end
handler -->|"D-Bus call"| dbus["D-Bus Backend<br/>(phosphor-* or custom service)"]
handler -->|"JSON response"| client
ASCII-art version (for comparison)
+---------------------------------------------------------------------+
| Redfish OEM Extension Architecture |
+---------------------------------------------------------------------+
| +-----------------+ |
| | Redfish Client | |
| +--------+--------+ |
| | GET /redfish/v1/Oem/MyVendor/... |
| v |
| +--------+------------------------------------------+ |
| | bmcweb | |
| | +--------------+ +--------------------+ | |
| | | OEM Router | ----> | OEM Handler | | |
| | +--------------+ +---------+----------+ | |
| +------------------------------------+---------------+ |
| | D-Bus call |
| +------------------------------------v---------------+ |
| | D-Bus Backend | |
| | (phosphor-* services or custom daemon) | |
| +----------------------------------------------------+ |
+---------------------------------------------------------------------+
OEM Namespace Design
The /Oem/ Pattern
Redfish uses a structured namespace to prevent collisions between vendor extensions. OEM extensions live either under the Oem property of a standard resource or under a dedicated /redfish/v1/Oem/<Vendor>/ top-level route.
The DMTF requires that vendors use a unique identifier (typically your company name) as the
<Vendor>portion. This prevents conflicts between different vendors’ extensions on the same BMC.
Approach 1: Embedded OEM properties – Add vendor-specific properties to an existing resource:
{
"@odata.id": "/redfish/v1/Systems/system",
"Oem": {
"MyVendor": {
"@odata.type": "#OemComputerSystem.v1_0_0.MyVendor",
"FirmwareProvisioning": { "ProvisioningStatus": "Provisioned" }
}
}
}
Approach 2: Standalone OEM resources – Create new resource trees:
/redfish/v1/Oem/MyVendor/
BoardInfo # Hardware details
DiagnosticService/ # Diagnostics
Actions/
DiagnosticService.RunTest # Run a test (POST)
bmcweb OEM Router
bmcweb uses the BMCWEB_ROUTE macro to register HTTP routes. The routing flow for an OEM request:
GET /redfish/v1/Oem/MyVendor/BoardInfo
--> Route match: "/redfish/v1/Oem/<str>/BoardInfo"
--> Privilege check: privileges::getManager
--> Vendor validation: reject if vendorName != oemVendorName
--> D-Bus queries (async) --> JSON response
Always validate the vendor name in your handler. The route pattern
/redfish/v1/Oem/<str>/...matches any string, so your handler must reject unknown vendor namespaces with a 404 response.
Namespace Design Guidelines
| Guideline | Reason |
|---|---|
Use your company name as <Vendor> |
Avoids collisions with other vendors |
Version your schema types (e.g., v1_0_0) |
Enables backward-compatible evolution |
| Group related resources logically | Improves discoverability |
| Follow Redfish naming conventions | PascalCase for properties |
Define actions under standard Actions property |
Consistent with Redfish patterns |
CSDL and OpenAPI Schema Creation
OEM schemas document your extensions and enable automated validation.
JSON Schema
Source Reference: The example schema follows the pattern from the examples/redfish/oem-schema.json file in this repository.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://myvendor.com/schemas/v1/OemBoardInfo.v1_0_0.json",
"title": "#OemBoardInfo.v1_0_0.BoardInfo",
"description": "MyVendor OEM Board Information Resource",
"type": "object",
"properties": {
"@odata.type": { "type": "string", "const": "#OemBoardInfo.v1_0_0.BoardInfo" },
"@odata.id": { "type": "string", "format": "uri" },
"Id": { "type": "string" },
"Name": { "type": "string" },
"BoardType": { "type": "string", "enum": ["Server", "Workstation", "Storage", "Network"] },
"BoardRevision": { "type": "string" },
"MaxPowerWatts": { "type": "integer", "minimum": 0 },
"Status": { "$ref": "http://redfish.dmtf.org/schemas/v1/Resource.json#/definitions/Status" }
},
"required": ["@odata.type", "@odata.id", "Id", "Name"]
}
CSDL Schema
CSDL (Common Schema Definition Language) is the XML format used by the DMTF. The Redfish Service Validator uses CSDL to validate OEM resources.
<?xml version="1.0" encoding="UTF-8"?>
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:Reference Uri="http://redfish.dmtf.org/schemas/v1/Resource_v1.xml">
<edmx:Include Namespace="Resource"/>
<edmx:Include Namespace="Resource.v1_0_0"/>
</edmx:Reference>
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="OemBoardInfo">
<EntityType Name="BoardInfo" BaseType="Resource.v1_0_0.Resource" Abstract="true">
<Annotation Term="OData.Description" String="MyVendor board information resource."/>
</EntityType>
</Schema>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="OemBoardInfo.v1_0_0">
<EntityType Name="BoardInfo" BaseType="OemBoardInfo.BoardInfo">
<Property Name="BoardType" Type="OemBoardInfo.v1_0_0.BoardType"/>
<Property Name="BoardRevision" Type="Edm.String"/>
<Property Name="MaxPowerWatts" Type="Edm.Int64"/>
</EntityType>
<EnumType Name="BoardType">
<Member Name="Server"/><Member Name="Workstation"/>
<Member Name="Storage"/><Member Name="Network"/>
</EnumType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
Hosting Schemas in bmcweb
Place schema files in the bmcweb source tree so clients can discover them:
bmcweb/
static/redfish/v1/schema/
OemBoardInfo_v1.xml # CSDL schema
static/redfish/v1/JsonSchemas/
OemBoardInfo.v1_0_0.json # JSON schema
You can host schemas externally if you prefer not to embed them in the BMC image. Set the
@odata.typeto reference your external schema URL instead.
Regenerating Redfish Schemas and Generated Code
Much of bmcweb is generated from DMTF schema files rather than hand-written: C++ enums, the aggregation collection table, and the message/privilege registries are all produced from CSDL/JSON by scripts in bmcweb’s scripts/ directory. Run this workflow after you add or upgrade a standard schema or author an OEM CSDL extension, then rebuild bmcweb so the generated headers pick up your changes.
The Generation Pipeline
Run these from the root of the bmcweb source tree, in order:
| Step | Script | What it does |
|---|---|---|
| 1 | scripts/update_schemas.py |
Clones the DMTF Redfish-Publications repo at a pinned tag, copies the CSDL/JSON schemas in, selects the newest version of each schema with a semantic SchemaVersion sort, and refreshes the symlinks for the installed schemas. |
| 2 | scripts/generate_schema_enums.py |
Parses CSDL EnumType definitions into C++ enum class types under redfish-core/include/generated/enums/, emitting a NLOHMANN_JSON_SERIALIZE_ENUM macro for each so values (de)serialize to/from JSON automatically. |
| 3 | scripts/generate_schema_collections.py |
Extracts the top-level collection URIs and writes them to redfish-core/include/aggregation_utils.hpp, which Redfish Aggregation uses. |
| 4 | scripts/parse_registries.py |
Generates the message-registry headers (for example base_message_registry.hpp) and privilege_registry.hpp from the DMTF message registries and the privilege registry. |
| 5 | scripts/csdl-to-json-converter/ |
Holds the OpenBMC config (openbmc-config.json) for the DMTF csdl-to-json.py converter, which turns OEM/local CSDL into JSON schema so your OEM types are served as JSON. See the directory’s README.md. |
scripts/update_schemas.py runs the downstream generators for you after it refreshes the schemas, so a full upgrade is usually a single command. Run the individual generators only when you are iterating on one artifact (for example, regenerating just the enums after editing an EnumType).
A separate helper, scripts/generate_auth_certificates.py, creates mTLS test certificates (a CA plus server and client certificates) for exercising client-certificate authentication against bmcweb – handy when validating OEM routes that require mTLS.
Running the Workflow
cd bmcweb
# 1. Pull the latest DMTF schemas, re-select versions, refresh symlinks
# (this also invokes the generators below on your behalf)
python3 scripts/update_schemas.py
# 2-4. Regenerate individual artifacts while iterating
python3 scripts/generate_schema_enums.py
python3 scripts/generate_schema_collections.py
python3 scripts/parse_registries.py
# 5. (OEM) Convert your OEM CSDL to JSON with the DMTF converter,
# using OpenBMC's config -- see scripts/csdl-to-json-converter/README.md
# csdl-to-json.py --input <oem-csdl> --output <json-out> \
# --config scripts/csdl-to-json-converter/openbmc-config.json
# Rebuild so the regenerated headers are compiled in
bitbake bmcweb -c cleansstate && bitbake bmcweb
When adding an OEM CSDL schema, add it to the input consumed by
scripts/csdl-to-json-converter/openbmc-config.json, then re-run the pipeline. The generated JSON is what the Redfish Service Validator (below) uses to validate your OEM resources against their schema.
The generated headers under
redfish-core/include/generated/are checked into the bmcweb tree. Regenerate them with these scripts rather than editing them by hand – manual edits are overwritten the next time anyone runs the pipeline.
OEM Route Implementation
C++ Handler Structure
OEM handlers in bmcweb follow the same async pattern as standard Redfish routes. Each handler receives a request, queries D-Bus for data, and populates a JSON response.
Source Reference: Pattern based on bmcweb
- Route patterns: redfish-core/lib/managers.hpp
- Privilege definitions: registries/privilege_registry.hpp
Step 1: Create the OEM Header File
Create redfish-core/lib/oem_myvendor.hpp:
#pragma once
#include "app.hpp"
#include "dbus_utility.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "utils/dbus_utils.hpp"
#include <nlohmann/json.hpp>
#include <sdbusplus/asio/property.hpp>
namespace redfish
{
constexpr const char* oemVendorName = "MyVendor";
// OEM Root: GET /redfish/v1/Oem/MyVendor/
inline void requestRoutesOemRoot(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Oem/<str>/")
.privileges(redfish::privileges::getManager)
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& vendorName) {
if (vendorName != oemVendorName)
{
messages::resourceNotFound(asyncResp->res,
"OemRoot", vendorName);
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#OemServiceRoot.v1_0_0.OemServiceRoot";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Oem/MyVendor";
asyncResp->res.jsonValue["Id"] = "MyVendor";
asyncResp->res.jsonValue["Name"] =
"MyVendor OEM Extensions";
asyncResp->res.jsonValue["BoardInfo"]["@odata.id"] =
"/redfish/v1/Oem/MyVendor/BoardInfo";
});
}
} // namespace redfish
Step 2: Implement a Resource with D-Bus Backend
Read properties from a D-Bus service and map them to JSON:
// Board Info: GET /redfish/v1/Oem/MyVendor/BoardInfo
inline void requestRoutesBoardInfo(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Oem/<str>/BoardInfo")
.privileges(redfish::privileges::getManager)
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& vendorName) {
if (vendorName != oemVendorName)
{
messages::resourceNotFound(asyncResp->res,
"BoardInfo", vendorName);
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#OemBoardInfo.v1_0_0.BoardInfo";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Oem/MyVendor/BoardInfo";
asyncResp->res.jsonValue["Id"] = "BoardInfo";
asyncResp->res.jsonValue["Name"] = "Board Information";
// Async D-Bus query for manufacturer
sdbusplus::asio::getProperty<std::string>(
*crow::connections::systemBus,
"xyz.openbmc_project.Inventory.Manager",
"/xyz/openbmc_project/inventory/system/board",
"xyz.openbmc_project.Inventory.Decorator.Asset",
"Manufacturer",
[asyncResp](const boost::system::error_code& ec,
const std::string& manufacturer) {
if (ec)
{
asyncResp->res.jsonValue["Manufacturer"] = "Unknown";
return;
}
asyncResp->res.jsonValue["Manufacturer"] = manufacturer;
});
asyncResp->res.jsonValue["BoardType"] = "Server";
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
asyncResp->res.jsonValue["Status"]["Health"] = "OK";
});
}
Step 3: Implement a POST Action
Actions allow clients to trigger operations on the BMC:
// POST /redfish/v1/Oem/MyVendor/DiagnosticService/Actions/DiagnosticService.RunTest
inline void requestRoutesRunTest(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Oem/<str>/DiagnosticService/Actions/"
"DiagnosticService.RunTest")
.privileges(redfish::privileges::postManager)
.methods(boost::beast::http::verb::post)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& vendorName) {
if (vendorName != oemVendorName)
{
messages::resourceNotFound(asyncResp->res, "Action", "RunTest");
return;
}
std::optional<uint32_t> testId;
if (!json_util::readJsonAction(req, asyncResp->res, "TestId", testId))
{
return;
}
if (!testId || *testId < 1 || *testId > 3)
{
messages::actionParameterValueError(asyncResp->res,
testId ? std::to_string(*testId) : "null",
"TestId", "DiagnosticService.RunTest");
return;
}
crow::connections::systemBus->async_method_call(
[asyncResp](const boost::system::error_code& ec) {
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.result(boost::beast::http::status::accepted);
asyncResp->res.jsonValue["TaskState"] = "Running";
},
"xyz.openbmc_project.OEM.DiagService",
"/xyz/openbmc_project/oem/diag",
"xyz.openbmc_project.OEM.DiagService",
"RunTest", *testId);
});
}
Register Routes in bmcweb
// In src/webserver_main.cpp
#include "oem_myvendor.hpp"
// In the route registration section:
redfish::requestRoutesOemRoot(app);
redfish::requestRoutesBoardInfo(app);
redfish::requestRoutesRunTest(app);
D-Bus Backend
Your OEM handler reads from or writes to a D-Bus service. You can use an existing phosphor-* service or create a custom daemon:
bmcweb OEM Handler <-- D-Bus --> Backend Service (xyz.openbmc_project.OEM.MyService)
sdbusplus::asio::getProperty() |
async_method_call() v
Hardware / sysfs / devmem
For simple OEM properties that map to existing inventory or sensor D-Bus objects, you do not need a custom D-Bus service. Use
xyz.openbmc_project.Inventory.Manageror sensor services directly.
JSON Response Best Practices
| Property | Requirement | Example |
|---|---|---|
@odata.type |
Required. Versioned type. | #OemBoardInfo.v1_0_0.BoardInfo |
@odata.id |
Required. Full URI. | /redfish/v1/Oem/MyVendor/BoardInfo |
Id |
Required. Short identifier. | BoardInfo |
Name |
Required. Human-readable. | Board Information |
Status |
Recommended. Standard Redfish. | {"State":"Enabled","Health":"OK"} |
Actions |
If applicable. | {"#MyAction.Do":{"target":"..."}} |
Building and Deploying
Yocto Integration
Create a bbappend to include your OEM handler in the bmcweb build:
# recipes-phosphor/bmcweb/bmcweb_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += " \
file://oem_myvendor.hpp \
file://0001-Add-MyVendor-OEM-routes.patch \
"
do_configure:prepend() {
cp ${WORKDIR}/oem_myvendor.hpp ${S}/redfish-core/lib/oem_myvendor.hpp
}
The patch file registers your routes in webserver_main.cpp.
Build and Test
# Build bmcweb with OEM changes
bitbake bmcweb -c cleansstate && bitbake bmcweb
# Full image build for QEMU
bitbake obmc-phosphor-image
# Or copy binary directly for quick testing
scp build/tmp/work/*/bmcweb/*/build/bmcweb root@<bmc-ip>:/usr/bin/
ssh root@<bmc-ip> systemctl restart bmcweb
Redfish Service Validator Testing
The DMTF Redfish Service Validator verifies that your Redfish implementation conforms to the specification.
Install and Run
git clone https://github.com/DMTF/Redfish-Service-Validator.git
cd Redfish-Service-Validator
pip3 install -r requirements.txt
# Validate the entire service
python3 RedfishServiceValidator.py \
--ip https://<bmc-ip> --authtype Basic \
--username root --password 0penBmc --nochkcert
# Validate a specific OEM resource
python3 RedfishServiceValidator.py \
--ip https://<bmc-ip> --authtype Basic \
--username root --password 0penBmc --nochkcert \
--uri /redfish/v1/Oem/MyVendor/BoardInfo
Validating with Custom OEM Schemas
Provide a local schema directory to validate OEM resources against your custom schemas:
mkdir -p oem-schemas/
cp OemBoardInfo_v1.xml oem-schemas/
python3 RedfishServiceValidator.py \
--ip https://<bmc-ip> --authtype Basic \
--username root --password 0penBmc --nochkcert \
--schemadir ./oem-schemas/ --logdir ./validation-results/
The validator reports OEM resources as “unknown type” if you do not provide custom schemas. This is expected. Supply your CSDL schemas via
--schemadirto get full property validation.
Interpreting Results
| Check | What It Validates |
|---|---|
@odata.type present |
Every resource must declare its type |
@odata.id matches URI |
The @odata.id value must match the request URI |
| Required properties | All required properties in the schema are present |
| Property types | Values match declared types (string, integer, etc.) |
| Enum values | Enum properties contain only allowed values |
| Links resolve | Navigation properties return valid resources |
Manual Curl Validation
alias bmcurl='curl -k -s -u root:0penBmc'
# Test OEM root
bmcurl https://localhost/redfish/v1/Oem/MyVendor/ | jq .
# Test a specific resource
bmcurl https://localhost/redfish/v1/Oem/MyVendor/BoardInfo | jq .
# Test an action
bmcurl -X POST -H "Content-Type: application/json" \
-d '{"TestId": 1}' \
https://localhost/redfish/v1/Oem/MyVendor/DiagnosticService/Actions/DiagnosticService.RunTest | jq .
Automated Test Script
cd docs/examples/redfish/
./test_oem_resource.sh <bmc-ip> root 0penBmc
Extending Existing Resources with OEM Properties
You can add OEM properties to existing standard Redfish resources by patching the handler:
// In redfish-core/lib/systems.cpp (or via bbappend patch)
// Inside handleSystemsGet, after standard properties:
nlohmann::json& oem = asyncResp->res.jsonValue["Oem"]["MyVendor"];
oem["@odata.type"] = "#OemComputerSystem.v1_0_0.MyVendor";
sdbusplus::asio::getProperty<std::string>(
*crow::connections::systemBus,
"xyz.openbmc_project.OEM.MyService",
"/xyz/openbmc_project/oem/system",
"xyz.openbmc_project.OEM.SystemInfo", "ProvisioningStatus",
[asyncResp](const boost::system::error_code& ec,
const std::string& status) {
if (!ec)
asyncResp->res.jsonValue["Oem"]["MyVendor"]
["FirmwareProvisioning"]["ProvisioningStatus"] = status;
});
Troubleshooting
Route Not Found (404)
systemctl status bmcweb
journalctl -u bmcweb | grep -i "route\|oem"
bmcurl https://localhost/redfish/v1/Oem/MyVendor/ # trailing slash matters
D-Bus Errors
busctl list | grep -i oem
busctl tree xyz.openbmc_project.OEM.MyService
busctl get-property xyz.openbmc_project.Inventory.Manager \
/xyz/openbmc_project/inventory/system/board \
xyz.openbmc_project.Inventory.Decorator.Asset Manufacturer
Debug Logging
# Edit /lib/systemd/system/bmcweb.service, add:
# Environment="BMCWEB_LOG_LEVEL=DEBUG"
systemctl daemon-reload && systemctl restart bmcweb
journalctl -u bmcweb -f
Common Mistakes
| Issue | Cause | Solution |
|---|---|---|
| 404 on OEM route | Route not registered | Add registration in webserver_main.cpp |
| Empty JSON response | Handler returns before async callbacks | Use asyncResp shared_ptr pattern |
| 500 Internal Server Error | D-Bus service not running | Verify with busctl |
Missing @odata.type |
Forgot to set type in handler | Always set @odata.type first |
| Validator warns “unknown type” | No custom schema provided | Supply CSDL via --schemadir |
Examples
Working examples are available in the examples/redfish directory:
oem_resource.hpp– Complete OEM handler with root, BoardInfo, DiagnosticService, and RunTest actionoem-schema.json– JSON Schema for the OEM BoardInfo resourcetest_oem_resource.sh– Automated test script for validating OEM endpoints
Use the example files as a starting point. Copy
oem_resource.hpp, replaceMyVendorwith your vendor name, and modify the resource properties and D-Bus paths to match your hardware platform.
References
- bmcweb – OpenBMC Redfish/Web server
- DMTF Redfish Specification – Official specification
- Redfish Schema Index – All standard Redfish schemas
- Redfish Service Validator – DMTF validation tool
- Redfish Resource and Schema Guide – OEM extension guidance
- OpenBMC Redfish Design – Design documents
Tested on: OpenBMC master, QEMU ast2600-evb