Before reading this document, you can quickly understand how to add a component and extend related interfaces and hardware adaptation in the new component by referring to Adding a Component.
The external interfaces of openUBMC components are defined by resource collaboration interfaces. Components are full-feature providers that implement the resource collaboration interfaces. The resource collaboration interfaces are implemented using the D-Bus protocol. Each component can add, view, or call available interfaces provided by various services through the resource collaboration interfaces without needing to focus on the internal implementation of the interfaces in other components. Before reading this introduction, learn about the D-Bus protocol.
In addition to unifying the management of external interfaces for each component, openUBMC has established a universal error handling mechanism by introducing an error engine framework. This centralizes the management of error definitions for all components. Using the error engine framework, components only need to automatically and dynamically register error information with the error engine, which then takes over the error handling process for each component.
openUBMC manages the resource collaboration interfaces and the error engine through the mdb_interface component repository. Developers can use these resources by depending on the mdb_interface component. They can also add custom error mechanisms to the error engine or expose the external interfaces of their developed components to others through the mdb_interface repository. You can refer to the mdb_interface guide to quickly understand the mdb_interface component repository.
The following sections describe how to view, add, and call the resource collaboration interfaces using the mdb_interface component during development, as well as how to use the error engine.
Viewing Resource Collaboration Interfaces
The busctl CLI tool, which interacts with D-Bus, provides many functions for managing and controlling D-Bus. You can use busctl to view and control all objects, interfaces, and methods on D-Bus. Therefore, developers can use the busctl command to view resource collaboration interface information in an environment that supports openUBMC.
NOTE
If the environment uses a product package with build_type of release, the busctl command is not supported. You must use a product package built with build_type set to debug to use the busctl command in the environment.
Common busctl commands are as follows:
View all service information on the user bus of D-Bus.
shellbusctl --user treeView objects of a specified service on the user bus of D-Bus.
shellbusctl --user tree <service>View interfaces of a specified object on the user bus of D-Bus.
shellbusctl --user introspect <service> <object>Monitor message passing on the user bus of D-Bus.
shellbusctl --user monitor [service] [object]View properties of a specified object on the user bus of D-Bus:
shellbusctl --user get-property <service> <object> <interface> <property>Set properties of a specified object on the user bus of D-Bus:
shellbusctl --user set-property <service> <object> <interface> <property> <SIGNATURE ARGUMENT...>Call methods using the user bus of D-Bus:
shellbusctl --user call <service> <object> <interface> <method> [SIGNATURE [ARGUMENT...]]
Taking the /bmc/kepler/chassis/MicroComponent interface of the chassis service as an example, run the following command to view all properties and methods mounted on the interface:
busctl --user introspect bmc.kepler.chassis /bmc/kepler/chassis/MicroComponentIf the command runs successfully, it returns information similar to the following (only part of the information is shown here due to limited space):
NAME TYPE SIGNATURE RESULT/VALUE FLAGS
bmc.kepler.MicroComponent interface - - -
.HealthCheck method a{ss} i -
.Author property s "openUBMC" const
.Description property s "chassis management component" const
.License property s "Mulan PSL v2" const
.Name property s "chassis" const
.Pid property i 2543 const
.Status property s "InitCompleted" -
.Version property s "1.60.16" const
bmc.kepler.MicroComponent.ConfigManage interface - - -
.Backup method a{ss}s a(ss) -
.Export method a{ss}s s -
.GetTrustedConfig method a{ss} s -
.Import method a{ss}ss - -
.Recover method a{ss}a{sb} - -
.Verify method a{ss}s s -In the returned information, the NAME header indicates the interface, property, or method name. The TYPE header indicates the data type. The SIGNATURE header indicates the data type of the property or the input parameter data type of the method. For SIGNATURE type definitions, see the D-Bus Specification Documentation. The RESULT/VALUE header indicates the property value or the return value of the method. The FLAGS header indicates whether the item is readable or writable.
Adding Resource Collaboration Interfaces
Note:
In openUBMC, if you need to add D-Bus interface resources, you must clearly define the new property information, such as the service, object, and interface name, as well as the methods and properties mounted under the interface. This information must be submitted to the review meeting of the open-source SIG for review. Upon approval, you can configure the resource collaboration interface in themdb_interfacecomponent repository and implement the corresponding functions in the component code repository.
openUBMC uses a data model to define resource collaboration interfaces. This data model uniformly defines the resource collaboration interfaces and their mounted public properties and RPC methods in the mdb_interface component. Components define interfaces through the model and implement interface methods. Then, the system automatically generates code to register the RPC methods mounted on the resource collaboration interface. Since openUBMC does not allow users to change interface definitions in the mdb_interface component repository at will, the mdb_interface repository provides an additional resource collaboration interface definition for community demonstration, debugging, and learning. This section describes the code implementation process for adding a resource collaboration interface based on this definition.
Step 1: Adding the Resource Collaboration Interface Definition
After approved by the open source SIG, you can add the data model definition of the resource collaboration interface in the mdb_interface component repository based on the review results. Object and interface definitions are stored in the json/path/ and json/intf/ paths, respectively. Objects and their mounted interfaces are defined in the json/path/ directory, while specific interface definitions (including methods and properties mounted on the interface) are stored in the json/intf/ directory.
To support the demonstration in this document, openUBMC has added the /bmc/demo/MyMDSModel object definition and the bmc.demo.OpenUBMC.Community interface definition mounted under this object in the mdb_interface repository. The code definitions are as follows:
Add an object definition: You need to add an object definition file in the
json/path/path of themdb_interfacecomponent repository. For example, to add the/bmc/demo/MyMDSModel/object definition filejson/path/mdb/bmc/demo/MyMDSModel/MyMDSModel.json. This file path corresponds to thepathproperty in the object definition. Removing the parameter variable level from the path represented by thepathproperty gives the storage path in thejson/path/mdbdirectory, and the created file name must match the class name. The object definition code is as follows:json{ "MyMDSModel":{ "path":"/bmc/demo/MyMDSModel/", "interfaces": [ "bmc.demo.OpenUBMC.Community" ] } }If the object definition already exists in the
mdb_interfacecomponent, you only need to add the new interface to theinterfacesproperty of that object definition.Create the
bmc.demo.OpenUBMC.Communityinterface definition fileintf/mdb/bmc/demo/OpenUBMC/Community.jsonin thejson/intf/path of themdb_interfacerepository. This file path corresponds to the interface definition. Converting the value of theinterfacesproperty into a path representation gives the storage path in thejson/intf/mdbdirectory, with the last part as the file name. Add the interface definition information:json{ "bmc.demo.OpenUBMC.Community": { "properties": { "WelcomeMessage": { "baseType": "String", "description": "openUBMC welcome message" } }, "methods": { "GetRepoURL": { "description": "Get the URL of the openUBMC code repository.", "req": { "SecretNumber": { "baseType": "U32", "description": "openUBMC mysterious code" } } , "rsp": { "OutData": { "baseType": "String", "description": "URL of openUBMC" } } } } } }After completing the preceding steps, modify the version number defined in
service.json(by incrementing the last part of the three-part version number by 1) and add a modification log inCHANGELOG.md. Then, you can build the package in themdb_interfacerepository path using the following command:shellbingo buildAfter the build is complete, the system adds the
/bmc/demo/MyMDSModel/object definition and mounts thebmc.demo.OpenUBMC.Communityinterface definition (which includes theWelcomeMessageproperty and theGetRepoURLmethod) onto it.
Step 2: Automatically Generating Code
openUBMC uses model definition files to simplify development tasks. You only need to specify dependencies on the required interface definitions in the component model description file to automatically generate the code implementation. For detailed information on writing model description files, refer to MDS. The following example uses the my_app component to implement the bmc.demo.OpenUBMC.Community interface:
Reference the
mdb_interfacecomponent dependency in themds/service.jsonfile of themy_appcomponent:json{ ... "dependencies": { ... "build": [ ... { "conan": "mdb_interface/[>=0.0.1]" } ] } }Define the interface in the
mds/model.jsonfile of themy_appcomponent. Ensure that the interface definition path, property types, and method types are the same as those in themdb_interfacecomponent repository. In this context,MyMDSModelis the class name defined by the service model, matching the file name corresponding to thepathproperty.interfacesrepresents the collection of resource collaboration interfaces supported by this class. Thepropertiesproperty under the interface definition represents the collection of properties to be mounted on the resource collaboration interface. Thepropertiesunder theMyMDSModelproperty represent the private properties of this class, which do not need to be mounted on the resource collaboration interface and must not share names with properties or property aliases mounted on the resource collaboration interface.json{ "MyMDSModel": { "path": "/bmc/demo/MyMDSModel/${id}", "interfaces": { "bmc.demo.OpenUBMC.Community": { "properties":{ "WelcomeMessage": {} } } }, "properties": { "SecretNumber": { "baseType": "U32" } } } }The class definition data model for the
my_appcomponent is now complete. You can use the commands provided by the BMC Studio CLI to automatically generate relevant code based on the data model definition files.shellcd <my_app component directory> bingo genAfter the command is executed, the system generates a
genfolder in themy_appcomponent directory. This folder contains multiple automatically generated code files that implement object registration callback functions, service initialization, and subscription functions for interface and property monitoring. For details about automatically generated code files, see Automatic Code Generation.
Step 3: Implementing the Component
The automatically generated code file my_app/service.lua is the base class of the component. You can register and implement interfaces in the source code file by importing this base class. Example:
local class = require 'mc.class' -- Enhanced type system provided by the Lua development framework
local service = require 'my_app.service' -- Base class of the component generated by automatically generated code
local log = require 'mc.logging'
local object_manage = require 'mc.mdb.object_manage'
local app = class(service) -- Creates a component type.
function app:ctor() -- Constructor of the component
end
function app:init() -- Initialization function of the component
app.super.init(self) -- Calls the base class initialization function first.
self.my_mds_model = self:CreateMyMDSModel(1, function(object) -- Creates an mds object instance.
object.ObjectName = "MyMDSModel_1" -- Assigns object properties in the callback function.
object.WelcomeMessage = "Hello OpenUBMC!"
object.SecretNumber = 330
end)
log:notice("SecretNumber")
log:notice(self.my_mds_model.SecretNumber)
self:register_rpc_methods()
end
function app:register_rpc_methods() -- Implements the RPC methods mounted on the interface through callback functions in the base class.
self:ImplMyMDSModelCommunityGetRepoURL(function(obj, ctx, ...)
log:notice("welcome to the method")
return 'https://gitcode.com'
end)
end
return appIn openUBMC, the Lua development framework specifies that when a class is instantiated, its constructor ctor() is automatically executed, followed by its initialization function init().
Step 4: Building the Component
After completing the preceding steps, the code for the new RPC method is fully implemented. Modify the version number defined in the service.json file in the my_app repository (by incrementing the last part of the three-part version number by 1, for example, from 0.0.1 to 0.0.2) and record a modification log in CHANGELOG.md. Then, you can run the following command in the my_app repository path to build the component package:
bingo buildIf the build is successful, the console outputs the component package name my_app/0.0.2@openubmc.dev/dev.
Step 5: Building the Product
After obtaining the Conan package of the component, you can add this component to the openUBMC product repository manifest to build the openUBMC product package. Pull the manifest component repository. Then, add the component package dependency in the manifest.yml file corresponding to the environment model. For example, if the TaiShan200_2280v2 model environment is selected, add the my_app dependency in the manifest repository file build/product/BMC/openUBMC/manifest.yml:
dependencies:
- conan: "my_app/0.0.2@openubmc.dev/dev"
...After completing the preceding steps, run the following command in the manifest component repository directory to build the openUBMC product package for the TaiShan200_2280v2 model:
bingo buildAfter the command is executed, the system generates the product package rootfs_TaiShan200_2280v2.hpm in the output directory of the manifest repository. Then, you can use this product package to update the openUBMC environment and perform verification.
Example:
After upgrading the product package in an environment that supports openUBMC, run the following command to view all interfaces mounted on the
/bmc/demo/MyMDSModelobject:shellbusctl --user introspect bmc.kepler.my_app /bmc/demo/MyMDSModel/1After upgrading the product package in an environment that supports openUBMC, run the following command to call the
GetRepoURLmethod of thebmc.demo.OpenUBMC.Communityinterface:shellbusctl --user call bmc.kepler.my_app /bmc/demo/MyMDSModel/1 bmc.demo.OpenUBMC.Community GetRepoURL a{ss}u 0 1
Note:
To improve efficiency, openUBMC supports components started in order based on their dependencies and in parallel based on subsystems. However, according to the code implementation above, themy_appcomponent will be started independently in the environment. If there is a specific order requirement for startingmy_appand other components, you can modify thehicacomponent repository and update thehicaversion depended on in the product repository. This allows you to place themy_appcomponent in a subsystem process for startup. After the package is built in themanifestrepository, components can be started in parallel and order. For details about how to modify thehicacomponent, see hica.
Calling Resource Collaboration Interface RPC Methods
When developing component service code, you need to call the RPC methods of other components. The following describes how to call the GetRepoURL method of the bmc.demo.OpenUBMC.Community interface mounted to the resource collaboration interface in a new component new_app.
Step 1: Referencing the Interface
Add the dependent interface in the required property of the service.json file for the new_app component. In this case, setting path to * indicates a global search for this interface on the resource collaboration interface. You can also reference interfaces in a specific path by defining the path property.
"required": [
{
"path": "*",
"interface": "bmc.demo.OpenUBMC.Community"
}
]Step 2: Automatically Generating Code
Run the bingo gen command to automatically generate code in the /gen path. For details about automatically generated code files, see Automatic Code Generation. The gen/new_app/client.lua file is automatically generated based on the service.json and mdb_interface interface definition files. It generates functions for interface method calls, object acquisition, and signal subscription for the client resource collaboration interface based on the interface dependencies defined in the required section of the service.json file. For example, this file defines a method to obtain the bmc.demo.OpenUBMC.Community interface object:
function new_app_client:GetCommunityObjects()
return get_non_virtual_interface_objects(self:get_bus(), 'bmc.demo.OpenUBMC.Community', true)
endStep 3: Developing Code
Using the preceding automatically generated code, you can use the corresponding functions in the client to obtain the interface object and call the RPC methods mounted on it when writing service code. The following example shows the entry file /src/lualib/new_app_app.lua for the new_app component service code:
local class = require 'mc.class'
local service = require 'new_app.service'
local new_app_client = require 'new_app.client'
local log = require 'mc.logging'
local context = require 'mc.context'
local new_app = class(service)
function new_app:ctor()
end
function new_app:init()
self.super.init(self)
-- Obtains the interface object.
local objs = new_app_client:GetCommunityObjects()
-- Calls the GetRepoURL() method mounted on the interface object.
local err , repoURL = self.bus:pcall("bmc.kepler.my_app","/bmc/demo/MyMDSModel/1", "bmc.demo.OpenUBMC.Community","GetRepoURL", "a{ss}u", context.new(), 1)
log:notice(repoURL)
end
return new_appThe implementation of how to call interfaces and RPC methods on the resource collaboration interface within a component is now complete.
Error Engine
In openUBMC, external errors for components are uniformly managed by the mdb_interface component. The JSON files for error definitions are stored in the message directory of the mdb_interface repository. The structure of the message directory is as follows:
.
├── base.json -- Redfish standard errors
└── custom.json -- openUBMC custom errorsYou can add directories and files for error definitions in the message directory as required.
Step 1: Automatically Generating Code
Before using the error engine, ensure that the mdb_interface dependency is included in the component, that is, add the build dependency in the service.json file. After building in the local component repository path, deployment code can be obtained in the temp/opt/bmc/lualib/messsages directory. The structure of the temp/opt/bmc/lualib/messsages directory is as follows:
.
├── base.lua
├── custom.lua
└── init.luaAn example of the code is as follows:
M.PropertyValueTypeErrorMessage = {
Original = {
Description = [=[Indicates that a property was given the wrong valu]=] ..
[=[e type, such as when a number is supplied for a pr]=] .. [=[operty that requires a string.]=],
Message = [=[The value %1 for the property %2 is of a different]=] .. [=[ type than the property can accept.]=],
Severity = [=[Warning]=],
NumberOfArgs = 2,
ParamTypes = {[=[string]=], [=[string]=]},
Resolution = [=[Correct the value for the property in the request ]=] ..
[=[body and resubmit the request if the operation fai]=] .. [=[led.]=],
HttpStatusCode = 400,
IpmiCompletionCode = [=[0xFF]=],
SnmpStatusCode = 7,
TraceDepth = 0
},
Name = 'PropertyValueTypeError',
Format = [=[The value %s for the property %s is of a different]=] .. [=[ type than the property can accept.]=],
BacktraceLevel = 0,
Severity = log.WARN,
HttpResponse = 400,
IpmiResponse = 0xFF,
RegistryPrefix = 'Base'
}
---@return Error
function M.PropertyValueTypeError(val1, val2)
return create_error(M.PropertyValueTypeErrorMessage, val1, val2)
endThis example defines a type error function. You can directly call this method to throw an exception for error information when writing code.
Step 2: Applying the Error Engine
Take the M.PropertyValueTypeError function as an example, which throws a property type inconsistency error in the preceding example. Use the error engine to check the input parameter type of the GetRepoURL method added to the bmc.demo.OpenUBMC.Community interface. Since the input parameter of the GetRepoURL method defined in the resource collaboration interface is an unsigned integer, the system should throw a parameter type inconsistency error when a non-integer parameter is passed. Add the use of this error engine in the my_app component, which implements this interface. Write the implementation of the RPC method in the service code entry file /src/lualib/new_app_app.lua of the my_app component and add the error engine in the implementation code. The code example is as follows:
local base_messages = require 'messages.base'
function my_app:MyMDSModelCommunityGetRepoURL(attribute, value)
if type(value) ~= 'number' then
error(base_messages.PropertyValueTypeError(value, 'Attributes/' .. attribute.AttributeName))
end
return 'https://gitcode.com'
endIn methods that directly interface with northbound interfaces, when errors thrown by other components are received, you can report the error in the middle layer and throw an error exception after converting the error. For example, modify the function body for calling the GetRepoURL method in the new_app component. The code example is as follows:
local base_messages = require 'messages.base'
function new_app:MyMDSModelCommunityGetRepoURL(obj, ctx, attribute, value)
local err , repoURL = self.bus:pcall(obj.service,obj.path, obj.interface,"GetRepoURL", "a{ss}u", ctx, 1)
if err then
if err.name == base_messages.PropertyValueTypeError.Name then
error(base_messages.PropertyValueTypeError(value, 'Attributes/' .. attribute.AttributeName))
end
end
end