在阅读本章节之前,您可以通过《新增一个组件》快速了解如何实现新增组件,以及在新组件中扩展相关接口和硬件适配等。
openUBMC各个组件对外的接口统一由资源协作接口定义。组件是全量功能提供者,来实现资源协作接口。资源协作接口采用D-Bus
总线协议实现。各组件可以通过资源协作接口新增、查看或调用各服务提供的可用接口,而无需关注接口在其他组件中的内部实现。在阅读本篇介绍之前,请先了解D-Bus总线协议。
除了统一管理各组件对外的接口。openUBMC通过引入错误引擎框架,也建立了一套通用的错误处理机制,将各组件的错误定义统一管理。利用错误引擎框架,各组件只需自动动态将错误信息注册到错误引擎,后续由错误引擎框架接管各组件的错误处理流程。
openUBMC将资源协作接口和错误引擎统一由mdb_interface
组件仓管理。开发者可以通过依赖mdb_interface
组件仓,使用资源协作接口和错误引擎。也可以通过mdb_interface
组件仓将自定义的错误机制添加到错误引擎中,或将自己所开发组件的对外接口添加到资源协作接口中,暴露给其他组件使用。开发者可参考《mdb_interface》 快速了解mdb_interface
组件仓。
下面简单介绍组件开发过程中,如何利用mdb_interface
组件仓实现查看、新增和调用资源协作接口。以及组件开发过程中,错误引擎的使用。
资源协作接口查看
与D-Bus
总线进行交互的命令行工具busctl
,提供了许多管理和控制D-Bus
总线的功能。使用busctl
命令,可以查看和控制D-Bus
总线上的所有对象,接口和方法。因此,开发者可以在支持openUBMC的环境中,使用busctl
命令查看资源协作接口信息。 **注意:**若环境中使用的是stable
或rc
类型的制品包,则不支持busctl
命令,只有dev
类型的制品包才支持busctl
命令。
常用busctl
命令如下:
查看
D-Bus
用户总线上的所有服务信息shellbusctl --user tree
查看
D-Bus
用户总线上指定服务的对象shellbusctl --user tree <service>
查看
D-Bus
用户总线上指定对象的接口shellbusctl --user introspect <service> <object>
监视
D-Bus
用户总线上的消息传递shellbusctl --user monitor [service] [object]
查看
D-Bus
用户总线上指定对象的属性shellbusctl --user get-property <service> <object> <interface> <property>
查看
D-Bus
用户总线上指定对象的属性shellbusctl --user set-property <service> <object> <interface> <property> <SIGNATURE ARGUMENT...>
使用
D-Bus
用户总线进行方法调用shellbusctl --user call <service> <object> <interface> <method> [SIGNATURE [ARGUMENT...]]
这里以chassis
服务的/bmc/kepler/chassis/MicroComponent
接口为例,使用以下命令查看该接口上挂载的所有属性和方法等。
busctl --user introspect bmc.kepler.chassis /bmc/kepler/chassis/MicroComponent
命令执行成功,会返回以下信息(由于篇幅限制,这里只展示部分返回信息):
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 -
如上述返回信息所示,表头信息NAME表示此列为接口名、属性或方法名;表头信息TYPE
表示数据类型;表头信息SIGNATURE
表示属性的数据类型或方法的入参数据类型,SIGNATURE
类型定义可参考D-Bus Specification官方文档;表头信息RESULT/VALUE
表示属性值数或方法的返回值;表头信息FLAGS
表示是否可读写。
新增资源协作接口
注意:
在openUBMC中,开发者如需新增D-Bus
的接口资源,需要明确新增的属性信息,例如接口所在的服务、对象、以及接口名称、接口下所挂载的方法、属性等,并交由开源SIG组织的评审会议进行评审。评审通过后,可在mdb_interface
组件仓中进行资源协作接口配置,并在组件代码仓中实现相应的功能。
openUBMC采用数据模型的方式定义资源协作接口。此数据模型在mdb_interface
组件中统一定义资源协作接口及其挂载的公共属性和RPC方法等。组件通过模型定义接口、具体实现接口方法,再由自动生成代码注册资源协作接口上所挂载的RPC方法。由于openUBMC不支持用户对mdb_interface
组件仓中的接口定义随意更改,mdb_interface
组件仓提供了一个额外的资源协作接口的定义,用于社区演示和开发人员调试学习。在本章节中,将根据此接口定义介绍openUBMC新增一个资源协作接口的代码实现流程。
步骤一:新增资源协作接口定义
通过开源SIG组织评审后,开发人员根据评审结果可在mdb_interface
组件仓中新增资源协作接口数据模型的定义。对象和接口的定义分别存放在json/path/
和json/intf/
路径下。其中,在json/path/
目录下定义对象以及该对象下所挂载的接口,而接口的具体定义(即接口上所挂载的方法、属性等信息)存放在json/intf/
目录下。
为支持本文档的示例演示,openUBMC在资源协作接口定义的组件仓mdb_interface
中已经添加了/bmc/demo/MyMDSModel
对象定义和在该对象下挂在的bmc.demo.OpenUBMC.Community
接口定义。此部分的代码定义如下:
新增对象定义:开发者需要在
mdb_interface
组件代码仓的json/path/
路径下添加对象定义文件,如新增/bmc/demo/MyMDSModel/
对象定义文件json/path/mdb/bmc/demo/MyMDSModel/MyMDSModel.json
(此文件路径和对象定义中的path
属性对应,将path属性表示的路径中删除参数变量的层级即为该文件在json/path/mdb
目录下存放的路径,且创建的文件名与类名一致)。对象定义代码如下:json{ "MyMDSModel":{ "path":"/bmc/demo/MyMDSModel/", "interfaces": [ "bmc.demo.OpenUBMC.Community" ] } }
若在
mdb_interface
组件中已经存在此对象定义,开发者只需在该对象定义的interfaces
属性中添加需要新增的接口即可。在
mdb_interface
代码仓的json/intf/
路径下新建bmc.demo.OpenUBMC.Community
接口的定义文件intf/mdb/bmc/demo/OpenUBMC/Community.json
文件(此文件路径与接口定义对应,将interfaces
属性表示的值转换为路径表示,即为该文件在json/intf/mdb
目录下存放的路径,最后一层为文件名),并添加接口定义信息:json{ "bmc.demo.OpenUBMC.Community": { "properties": { "WelcomeMessage": { "baseType": "String", "description": "OpenUBMC欢迎消息" } }, "methods": { "GetRepoURL": { "description": "获取OpenUBMC代码仓的URL", "req": { "SercretNumber": { "baseType": "U32", "description": "OpenUBMC的神秘代码" } } , "rsp": { "OutData": { "baseType": "String", "description": "OpenUBMC的URL" } } } } } }
完成以上步骤后,开发者修改
service.json
中定义的版本号(即将三段式版本号的最后一段值加1),并在CHANGELOG.md
中添加修改日志,便可在mdb_interface
代码仓路径下通过以下命令构建出包:shellbingo build
构建成功后便实现了在资源协作接口定义中新增
/bmc/demo/MyMDSModel/
对象定义、并在该对象定义上挂载bmc.demo.OpenUBMC.Community
接口定义,此接口定义上挂载WelcomeMessage
属性和GetRepoURL
方法的定义。
步骤二:自动生成代码
openUBMC采用的模型定义文件简化了开发任务,只需要在组件中的模型描述文件中依赖与组件所需实现的接口定义,便可通过代码自动生成实现相关代码的实现。模型描述文件的详细代码编写可参考《MDS》。这里以my_app
组件为例,实现bmc.demo.OpenUBMC.Community
接口:
首先,需要在
my_app
组件的mds/service.json
文件中引用mdb_interface
组件依赖:json{ ... "dependencies": { ... "build": [ ... { "conan": "mdb_interface/[>=0.0.1]" } ] } }
在
my_app
组件的mds/model.json
文件中定义该接口。注意接口定义路径和属性、方法类型等与mdb_interface
组件仓中的接口定义相同。其中,MyMDSModel
为业务模型定义的类名,与path
属性对应的文件名保持一致。interfaces
表示该类支持的资源协作接口的接口集合。接口定义下的properties
属性表示需要挂到资源协作接口下的属性的集合。MyMDSModel
属性下的properties
表示此类的私有属性,无需挂到资源协作接口下,且不能与挂到资源协作接口下的属性和属性别名重名。json{ "MyMDSModel": { "path": "/bmc/demo/MyMDSModel/${id}", "interfaces": { "bmc.demo.OpenUBMC.Community": { "properties":{ "WelcomeMessage": {} } } }, "properties": { "SecretNumber": { "baseType": "U32" } } } }
以上已经完成
my_app
组件的类定义数据模型。根据BMC Studio CLI
提供的命令便可以根据上述数据模型定义文件自动生成相关代码。shellcd <my_app组件目录> bingo gen
命令执行成功,会在
my_app
组件目录下生成gen
文件夹,此文件夹下包含了多个自动生成的代码文件。自动生成代码实现了对象注册的回调函数、服务的初始化、接口和属性监听的订阅函数等。关于自动生成的代码文件介绍可参考代码自动生成。
步骤三:组件实现
自动生成的代码文件my_app/service.lua
为组件的基类,开发者可以通过引入此基类,在源码文件中注册并实现接口。示例:
local class = require 'mc.class' -- Lua开发框架提供的增强类型系统
local service = require 'my_app.service' -- 自动生成代码生成的组件基类
local log = require 'mc.logging'
local object_manage = require 'mc.mdb.object_manage'
local app = class(service) -- 创建一个组件类型
function app:ctor() -- 组件的构造函数
end
function app:init() -- 组件的初始化函数
app.super.init(self) -- 先调用基类的初始化函数
self.my_mds_model = self:CreateMyMDSModel(1, function(object) -- 创建一个mds对象实例
object.ObjectName = "MyMDSModel_1" -- 在回调函数中进行对象的属性赋值
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() --通过基类中的回调函数实现接口上挂载的RPC方法
self:ImplMyMDSModelCommunityGetRepoURL(function(obj, ctx, ...)
log:notice("welcome to the method")
return 'https://gitcode.com'
end)
end
return app
在openUBMC中,Lua开发框架定义了类实例化时,会自动执行此类的构造函数ctor()
,再执行此类的初始化函数init()
。
步骤四:组件构建
完成以上步骤,新增RPC方法的代码已经全部完成。开发者修改my_app
代码仓中service.json
文件中定义的版本号(将三段式版本号的最后一段值加1,即my_app
组件版本号从0.0.1修改为0.0.2),并在CHANGELOG.md
中记录修改日志,便可在my_app
代码仓的路径下执行以下命令构建出my_app
的组件包:
bingo build
构建成功,控制台会输出组件包名my_app/0.0.2@openUBMC.dev/dev
。开发者可以在root/.conan/data
路径下查看构建出的组件Conan包。
步骤五:产品构建
获取组件的Conan包后,开发者在openUBMC的制品仓manifest
中添加此组件,便可构建出openUBMC的制品包。 这里开发者需要先拉取manifest
组件仓。根据环境机型在对应的manifest.yml
文件中添加组件包依赖。例如,这里选择TaiShan200_2280v2
机型的环境。则应该在manifest
组件仓的build/product/BMC/openUBMC/manifest.yml
文件中添加my_app
依赖:
dependencies:
- conan: "my_app/0.0.2@openUBMC.dev/dev"
...
执行完以上步骤,在manifest
组件仓目录下执行以下命令构建TaiShan200_2280v2
机型的openUBMC制品包:
bingo build
命令执行成功,便可在manifest
组件仓的output
目录下生成制品包rootfs_TaiShan200_2280v2.hpm
。之后,开发者便可使用此制品包更新openUBMC环境,并进行验证。
示例:
- 在支持openUBMC的环境中升级此制品包后,可执行以下命令查看
/bmc/demo/MyMDSModel
对象上挂载的所有接口:shellbusctl --user introspect bmc.kepler.my_app /bmc/demo/MyMDSModel/1
- 在支持openUBMC的环境中升级此制品包后,可执行以下命令调用
bmc.demo.OpenUBMC.Community
接口的GetRepoURL
方法:shellbusctl --user call bmc.kepler.my_app /bmc/demo/MyMDSModel/1 bmc.demo.OpenUBMC.Community GetRepoURL a{ss}u 0 1
注意:
为了提升效率,openUBMC支持组件根据组件的依赖关系有序拉起,且根据不同子系统并行被拉起。但根据以上的代码实现,my_app
组件将会在环境中单独被拉起。若my_app
和其他组件被拉起的顺序有要求时,可以修改hica
组件仓,并在制品仓中修改制品中依赖的hica
版本,将my_app
组件放入子系统进程中启动。之后在manifest
仓中构建出包便支持组件并行有序被拉起。关于hica
组件的修改请参考《hica》。
调用资源协作接口RPC接口
在开发组件的业务代码时,不可避免的需要调用其他组件的RPC方法。下面简要介绍如何在另一个新增组件new_app
中调用资源协作接口上挂载的bmc.demo.OpenUBMC.Community
接口的GetRepoURL
方法:
步骤一:引用接口
首先在new_app
组件的service.json
文件的required
属性值中添加依赖的接口。这里,path
值为“*”,表示会在资源协作接口上全局搜索此接口。当然,开发者也可以通过定义path
属性,引用特定路径下的接口。
"required": [
{
"path": "*",
"interface": "bmc.demo.OpenUBMC.Community"
}
]
步骤二:代码自动生成
执行bingo gen
命令后,在/gen
路径下自动生成代码。关于自动生成代码的详细介绍请参考《代码自动生成》。其中,gen/new_app/client.lua
是依据service.json
以及mdb_interface
接口定义文件自动生成的代码文件。根据service.json
文件required
部分定义的接口依赖生成客户端资源协作接口的接口方法调用、对象获取和信号订阅等函数。例如此文件中定义了获取bmc.demo.OpenUBMC.Community
接口对象的方法:
function new_app_client:GetCommunityObjects()
return get_non_virtual_interface_objects(self:get_bus(), 'bmc.demo.OpenUBMC.Community', true)
end
步骤三:代码开发
通过以上自动生成代码,开发者在编写业务代码时,可以使用client
中相应的函数获取接口对象,并通过接口对象调用此对象下挂载的RPC方法。这里以new_app
组件业务代码的入口文件/src/lualib/new_app_app.lua
为示例,编写以下代码实现:
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)
-- 获取接口对象
local objs = new_app_client:GetCommunityObjects()
-- 调用接口对象下挂载的GetRepoURL()方法
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_app
以上已经实现在组件中如何调用资源协作接口上的接口和调用资源协作接口RPC方法。
错误引擎
在openUBMC中,组件对外的错误由mdb_interface
组件统一管理。错误定义的json文件存放在mdb_interface
代码仓的message
目录下。message目录结构如下:
.
├── base.json -- Redfish标准错误
└── custom.json -- openUBMC自定义错误
开发者可以按需在message
目录下,新增错误定义的目录和文件。
步骤一:代码自动生成
开发者在使用错误引擎前,首先要确认在组件中引入了mdb_interface
依赖。即在service.json
文件中添加build
依赖。然后,在本地组件仓路径下构建后可在temp/opt/bmc/lualib/messsages
目录下获取到部署态的代码。temp/opt/bmc/lualib/messsages
目录结构如下:
.
├── base.lua
├── custom.lua
└── init.lua
其中部分代码样例如下:
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)
end
该样例中定义了类型错误函数。开发者在代码编写时,可直接调用此方法对错误信息抛出异常。
步骤二:错误引擎的应用
以上述样例中抛出属性类型不一致错误的M.PropertyValueTypeError
函数为例。使用错误引擎对上述添加的bmc.demo.OpenUBMC.Community
接口的GetRepoURL
方法的入参参数类型进行检查。由于资源协作接口中定义的GetRepoURL
方法的入参为无符号整型,当传入参数类型为非整型时,应该抛出参数类型不一致错误。这里在此接口的实现组件my_app
中,添加此错误引擎的使用。 在my_app
组件的业务代码入口文件/src/lualib/new_app_app.lua
中编写RPC方法的实现,并在实现代码中添加错误引擎。代码示例如下:
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'
end
与北向接口直接对接的方法中,接收到其他组件抛出的错误,可在中间层报错,将错误转换后再抛出错误异常。例如,在new_app
组件中修改调用GetRepoURL
方法的函数体。代码示例如下:
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