代码仓
中

持久化机制介绍 ​

在 openUBMC 固件中,数据持久化机制是指将关键的配置、状态及监控数据从易失性内存写入非易失性存储介质的过程。该机制主要基于 SQLite 数据库实现,为运行在 openUBMC 上的各类应用组件提供了一个统一、可靠的结构化数据存储方案。

持久化机制的核心作用 ​

  1. 状态保持与系统可靠性

    确保 BMC 在重启、断电或发生故障后,所有关键配置和状态信息不会丢失。系统重新启动后能够从数据库中读取并恢复到断电前的状态,保障管理的连续性和系统的自愈能力。

  2. 数据一致性与完整性

    通过 SQLite 数据库的事务特性,可以确保在多进程、多线程同时访问数据时的原子性、一致性、隔离性和持久性,有效防止数据损坏或不一致。

  3. 支持带外管理

    即使服务器主机处于关机状态,管理员仍能通过 BMC 查询到存储在 SQLite 中的历史传感器数据、事件日志和配置信息,实现完全独立的系统监控与管理。

  4. 性能与资源优化

    SQLite 作为一个轻量级数据库,在保证功能强大的同时,对 BMC 的 CPU 和内存资源占用极低,实现了功能性与资源消耗之间的最佳平衡。

模型与自动生成代码基础概念 ​

MDS模型(Module Description Source): MDS 是组件模型的描述源文件,对组件管理和依赖的数据及其扩展和定制能力提供统一建模。

mdb_interface: 资源协作接口的模型定义集合。这些接口基于 D-Bus 总线协议实现,通过此机制,各组件能够发现、调用由其他服务提供的功能接口,实现跨进程的资源访问与协同工作。

自动生成代码:openUBMC 集成 Bingo 工具的代码生成能力。通过执行bingo gen命令,工具能够将抽象的模型定义自动转化为可直接调用的组件代码。这一流程显著提升了开发效率,使开发者能够专注于业务逻辑的实现,而无需手动编写大量结构固定、重复性高的底层代码。

根据MDS模型定义,生成数据库表格创建代码: 根据mdb_interface模型定义,生成资源协作接口注册代码:

持久化机制介绍 ​

组件数据完成模型定义后,可通过 SQLite 数据库实现持久化存储。SQLite 数据库以后缀为.db的文件形式存在,每个数据库对应一个独立的物理文件。

组件的持久化配置以 MDS 模型中model.json定义的类为基本单位。每个类在数据库中映射为一张独立的数据表,而类中声明为持久化的属性则对应表中的字段。

持久化模式 ​

关于持久化实现方式,系统根据 MDS 模型中类定义的tableLocation字段进行配置,支持两种管理模式:

  1. 本地持久化模式

    数据存储在以组件名称命名的专属持久化数据库中。在该模式下,组件直接对本地持久化数据库执行读写操作,实现数据的自主管理。

  2. 远程持久化模式

    组件启动时创建内存数据库作为临时工作区,所有数据操作仅针对内存数据库。框架通过预置的钩子机制自动捕获内存数据库的变更事件,通过 RPC 将数据变更请求转发至 persistence 服务,最终由该服务统一将变更数据写入持久化数据库,实现集中化的数据管理。

持久化类型 ​

持久化类型指持久化数据的存储介质配置,在 MDS 模型中通过类定义的tableType字段或属性定义的usage字段进行设置。

  1. 不持久化 (Memory)

    数据仅存储于内存数据库中,组件所在进程重启后数据将丢失。

  2. 临时持久化 (TemporaryPer)

    进程重启后需保留的数据,存储于 tmpfs 虚拟内存文件系统中,BMC 复位后数据将被清除。

  3. 复位持久化 (ResetPer)

    BMC复位后需保留的数据,存储于pram文件系统中,BMC 掉电后数据将被清除。

  4. 掉电持久化 (PoweroffPer)

    BMC掉电后需保留的数据,存储于nandflash文件系统中,BMC 深度还原或恢复出厂设置后数据将被清除。

  5. 永久持久化 (PermanentPer)

    BMC深度还原或恢复出厂设置后仍需保留的数据,存储于字符设备中(非数据库形式)。

自动生成代码与模型的对应关系 ​

MDS模型 ​

  1. service.json : 组件服务模型,描述组件的基本信息与依赖关系。
  2. model.json : 组件数据模型,定义组件管理的数据结构,包括对象路径、接口、属性等定义。
  3. ipmi.json : 组件 IPMI 命令模型,描述组件实现的 IPMI 命令请求、响应参数等信息。
  4. types.json : 组件自定义类型定义,提供给model.json中的属性或方法参数引用。

mdb_interface模型 ​

  1. 接口定义文件 (json / intf 目录下) : 接口的属性、方法、信号定义。
  2. 路径定义文件 (json / path 目录下) : 对象路径下允许配置的接口。如果model.json中类定义的 path 配置了该路径,类名需要与该文件名一致,类定义的 interfaces 下配置的接口也需要在该文件指定的接口范围内。
目录文件名依据的文件说明
gen/classmodel.luamodel.json以及mdb_interface接口定义文件包含每个类的属性(包括资源树属性和私有属性)校验信息,以及服务端资源树方法注册函数
gen/class/types类名.luamodel.json每个类一个文件,完成私有属性的校验和默认值配置,会被model.lua引用
gen/class/typestypes.luatypes.json包含枚举、结构体、字典等自定义类型的定义
gen/App名client.luaservice.json以及mdb_interface接口定义文件根据required部分定义的接口依赖生成,客户端资源树接口方法调用、对象获取和信号订阅等函数
gen/App名datas.luadatas.yaml远程持久化内存数据库/本地持久化数据库初始化加载的默认数据
gen/App名db.luamodel.json远程持久化内存数据库表格创建和数据库操作函数
gen/App名local_db.luamodel.json本地持久化数据库表格创建和数据库操作函数(model.json中配置了“tableLocation”:“Local”的表)
gen/App名orm_classes.luamodel.json为配置了远程持久化的每个类初始化orm对象
gen/App名service.luamodel.json,service.json以及mdb_interface接口定义文件服务入口,包含App初始化和服务资源树方法注册、对象创建等
gen/App名/ipmiipmi.luaipmi.jsonipmi命令定义,是ipmi模块的入口
gen/App名/ipmiipmi_message.luaipmi.jsonipmi命令请求和响应汇总,会被ipmi.lua引用
gen/App名/ipmi/cmds命名名.luaipmi.json每个ipmi命令一个文件,包含请求和响应参数定义和校验,会被ipmi_message.lua引用
gen/App名/json_types接口末段.luamodel.json,service.json以及mdb_interface接口定义文件每个接口一个文件,包含资源树接口注册属性定义方法参数校验

使用注意事项 ​

  • 永久持久化不支持本地模式:永久持久化不支持在本地持久化模式下使用。

  • 永久持久化存储容量限制:永久持久化总可用空间为 2MB,仅适用于存储数量小、内容稳定不变的关键数据(如 MAC 地址)。

  • 掉电持久化的写入考量:配置掉电持久化时,需评估数据写入量与写入频率,以避免影响 Flash 存储介质的使用寿命。

  • 本地持久化的配置约束:本地持久化模式下,仅支持通过 tableType 配置整表持久化类型,属性级别定义的持久化类型将视为无效。

  • 本地持久化的默认类型:若未在本地持久化中配置 tableType,系统将默认采用 PoweroffPer(掉电持久化)类型。

  • 远程持久化的类型优先级:在远程持久化模式下,若 tableType 与属性 usage 中同时配置了持久化类型,系统将优先采用属性级别所定义的持久化类型。

  • 远程持久化的默认行为:若未在远程持久化中配置任何持久化类型,数据将不会被持久化存储,仅存在于内存数据库中。

应用举例 ​

在 service.json 的 required 中声明对其他组件接口的依赖,在 client.lua 中生成对依赖接口的调用方法:

在 model.json 的 tabelName 声明数据库表名,对应db.lua中创建数据库表格和数据库操作函数

MDS持久化配置方式(model.json) ​

类定义 ​

  1. tableName: 表示该类通过 ORM 映射的数据库表名.
  2. tableLocation: 持久化方式,配置tableLocation字段值为Local表示本地持久化,不配tableLocation 字段或者配置其它取值时为远程持久化。
  3. tableType: 表示该类所有属性的持久化类型。
  4. tableMaxRows: 数据库表中最多允许存储多少条数据。

属性定义 ​

  1. usage

    • 描述:定义属性的用途。
    • 说明:其配置值为数组,可用于指定属性的持久化类型。
  2. primaryKey

    • 描述:标识该属性是否为数据库表的主键。
    • 取值:true 表示是主键;false 或未配置表示非主键。
    • 规则:通常选择在其所属表内具有唯一性的一个或多个属性作为主键。当指定多个属性为主键时,其组合值必须唯一。
  3. uniquekey

    • 描述:标识该属性是否为唯一键。
    • 取值:true 表示是唯一键;false 或未配置表示非唯一键。
    • 说明:被定义为主键的属性自动具备唯一键约束。
  4. baseType

    • 描述:指定属性的数据类型。
    • 取值范围:包括 U8, U16, U32, U64, S8, S16, S32, S64, String 等。
  5. default

    • 描述:指定属性的默认值。
    • 规则:当向数据库表中插入新数据行且未给该属性赋值时,将自动使用此处定义的默认值。
  6. notAllowNull

    • 描述:规定该属性的值在数据库中是否允许为 NULL(空值)。
    • 取值:true 表示不允许为空;false 或未配置表示允许为空。
    • 默认行为:主键属性默认不允许为空;非主键属性默认允许为空。
  7. sensitive

    • 描述:标识该属性是否包含敏感数据。
    • 取值:true 表示是敏感数据;false 或未配置表示非敏感数据。
    • 安全处理:在执行一键收集和导出持久化数据等操作时,被标记为敏感数据的属性值将被替换为 *****,以确保真实数据不被泄露。
  8. critical

    • 描述:标识该属性是否为关键数据。
    • 取值:true 表示是关键数据;false 或未配置表示非关键数据。
    • 可靠性保障:对于被标记为关键数据且持久化类型为掉电持久化的属性,在每次数据更新时,系统会将其同步写入备份数据库,以增强数据的可靠性。

演示案例 ​

新增接口并配置属性持久化 ​

在 mdb_interface 仓库新增接口定义文件json/intf/mdb/bmc/kepler/Example/Test.json

json
{
    "bmc.kepler.Example.Test": {
        "properties": {
            "Id": {
                "baseType": "U8",
                "description": "测试ID"
            },
            "Name": {
                "baseType": "String",
                "description": "测试名字"
            }
        },
        "methods": {
            "TestMethodA": {
                "description": "测试方法A",
                "req": {
                    "TestParam": {
                        "baseType": "String",
                        "description": "测试方法A参数"
                    }
                },
                "rsp": {
                    "Response": {
                        "baseType": "String",
                        "description": "函数调用结果"
                    }
                }
            }
        }
    }
}

在 mdb_interface 仓库新增路径定义文件json/path/mdb/bmc/kepler/Example/TestA/TestA.json

json
{
    "TestA": {
        "path": "/bmc/kepler/Example/TestA",
        "interfaces": [
            "bmc.kepler.Example.Test"
        ]
    }
}

在mdb_interface 仓库执行bingo build命令,并记录 conan 包名称 在 sensor 组件仓库的 model.json 新增TestA类定义 修改 sensor 组件仓库的 service.json 配置的 mdb_interface 依赖为前一步得到的 conan 包 在sensor组件仓库执行bingo gen自动生成代码,执行完成后,可看到数据库表格创建以及操作函数已生成 在sensor组件仓库执行bingo build编译代码,并记录 conan 包名称 修改 manifest.yml 中 sensor 和 mdb_interface 仓库的版本,执行bingo build 升级BMC固件后,在BMC系统下执行busctl --user introspect bmc.kepler.sensor /bmc/kepler/Example/TestA

持久化问题定位方法 ​

一键收集 ​

release版本只能通过一键收集查看持久化数据库内容,收集完成后,持久化数据存放在 dump_info/AppDump/persistence 目录下,各类持久化数据将分别导出为独立的 JSON 文件,用户可通过表名进行检索定位。

命令行查询 ​

临时持久化、复位持久化及掉电持久化数据库均基于 SQLite 数据库实现,可通过 sqlite3 命令行工具执行相应的 SQL 语句进行数据查询。需要注意的是,该操作需具备 root 权限,可通过升级 telnet 破解包以获取相应访问能力。

远程持久化数据存放路径如下:

远程持久化类型路径
临时持久化(TemporaryPer)/run/persistence/per_temporary.db
复位持久化(ResetPer)/opt/bmc/pram/persistence/per_reset.db
掉电持久化(PoweroffPer)/data/trust/persistence/per_poweroff.db
永久持久化(PermanentPer)/dev/mmcblk0p8

本地持久化(配置了"tableLocation": "local")数据存放路径如下:

本地持久化类型路径
临时持久化(TemporaryPer)/dev/shm/persistence.local/*.db
复位持久化(ResetPer)/opt/bmc/pram/persistence.local/*.db
掉电持久化(PoweroffPer)[非最小系统组件]/data/opt/bmc/persistence.local/*.db
掉电持久化(PoweroffPer)[最小系统组件]/data/trust/persistence.local/*.db
  • 可以通过环境上的 /opt/bmc/trust/mini_system.json 文件查看最小系统组件清单

临时持久化 ​

  • 远程持久化:
bash
/usr/sbin/sqlite3 /run/persistence/per_temporary.db "SELECT * FROM persist_table WHERE table_name = 't_iptables_nat' ;"
  • 本地持久化(数据库文件以组件名称命名):
bash
/usr/sbin/sqlite3 /dev/shm/persistence.local/hwdiscovery.db "SELECT * FROM 't_hw_component' ;"

复位持久化 ​

  • 远程持久化:
bash
/usr/sbin/sqlite3 /opt/bmc/pram/persistence/per_reset.db "SELECT * FROM persist_table WHERE table_name = 't_soc_management' ;"
  • 本地持久化(数据库文件名是组件名称):
bash
/usr/sbin/sqlite3 /opt/bmc/pram/persistence.local/fault_diagnosis.db "select * from t_system_ras"

掉电持久化 ​

  • 远程持久化:

    按表名查询数据

bash
/usr/sbin/sqlite3 /data/trust/persistence/per_poweroff.db "SELECT * FROM persist_table WHERE table_name = 't_banner_config';"
  • 本地持久化(数据库文件名是组件名称):

    非最小系统组件

bash
/usr/sbin/sqlite3 /data/opt/bmc/persistence.local/cooling.db "select * from t_inlet_temp_record"
最小系统组件
bash
/usr/sbin/sqlite3 /data/trust/persistence.local/maca.db "select * from t_package"

永久持久化 ​

永久持久化数据以json格式存放在字符设备/dev/mmcblk0p8中,可以通过hexdump命令查看,永久持久化不支持本地持久化。

bash
busybox hexdump -n 1048576 /dev/mmcblk0p8 -C

内存数据库内容查看 ​

在启用基于 MDS 的属性持久化特性且 skynet 进程开启 debug_console 的前提下,可通过代码注入方式查看组件内存数据库内容。

以下以查看组件 bmc_time 内存数据库中 t_time_config 表格为例:

1)在data分区下创建 lua 文件(如 /data/test.lua),填入以下内容

lua
local c_object_manage = require 'mc.orm.object_manage'
local cjson = require 'cjson'
local sqlite3 = require 'lsqlite3'
local db = c_object_manage.get_instance().db.db
local vm = db:prepare('SELECT * FROM t_time_config')
while vm:step() ~= sqlite3.DONE do
    print(cjson.encode(vm:get_named_values()))
end
vm:finalize()

2)找到对应进程的 debug_console 端口号

可以在 manifest 仓库查看对应子系统的launch_control.json文件;

例如,bmc_time 组件在 bmc_core 子系统,对应的 debug_console 端口号是 40020:

3)telnet 连接 debug_console,找到服务地址

telnet 连接到环境 IP,使用第 2 步找到的 debug_console 端口号

先执行 list 命令,找到组件对应的服务地址

4)注入代码,查看输出

执行inject 服务地址 lua文件注入代码,查看内存数据库内容

调试方法 ​

删除数据 ​

以下介绍在开发调试过程中,获取环境权限后如何删除持久化数据。

远程持久化 ​

远程持久化指的是 MDS 类定义没有配置 tableLocation: Local,通过 persistence 服务统一管理的持久化方式

  1. 用 sqlite 命令行工具

    组件通过内存数据库间接读写远程持久化数据,如果用 sqlite 命令直接删除持久化数据库内容,删除后需要重启组件所在进程才能生效 (例如 hardware 进程下的组件,删除之后执行 systemctl restart hardware) 临时、复位、掉电持久化可以对相应的数据库文件的 persist_table 执行 sql 语句删除数据。

    以下命令以掉电持久化数据库为例

  • 删除整表数据示例(t_banner_config 表所有数据):
bash
/usr/sbin/sqlite3 /data/trust/persistence/per_poweroff.db "DELETE FROM persist_table WHERE table_name = 't_banner_config';"
  • 删除表中某条记录示例(t_banner_config表中 Id 为 1 的记录):
bash
/usr/sbin/sqlite3 /data/trust/persistence/per_poweroff.db "DELETE FROM persist_table WHERE table_name = 't_banner_config' AND prime_key = 'Id:1';"
  • 删除表中某条记录某个属性值示例(t_banner_config 表中 Id 为 1 的 Content 属性值):
bash
/usr/sbin/sqlite3 /data/trust/persistence/per_poweroff.db "DELETE FROM persist_table WHERE table_name = 't_banner_config' AND prime_key = 'Id:1' AND persist_param='Content';"

永久持久化存储在字符设备,不能用sqlite命令删除数据,只能通过注入代码进行删除

  1. 通过代码注入方式删除

    组件如果开启了 ORM 特性,可以通过 skynet debug console 注入代码操作内存数据库进行删除(框架通过钩子触发持久化数据库同步删除,不需要重启进程)。

    例如,删除 bmc_network 组件的 t_iptables_ipv4_nat 表数据,可以注入以下代码进行数据删除(注入代码具体操作见上文章节)

lua
local c_object_manage = require 'mc.orm.object_manage'
local cjson = require 'cjson'
local sqlite3 = require 'lsqlite3'
local db = c_object_manage.get_instance().db
db:delete(db.tables['t_iptables_ipv4_nat']):exec()

本地持久化 ​

本地持久化指的是MDS类定义配置了 tableLocation: Local,通过组件自行管理的持久化方式。

组件直接操作本地持久化数据库,不经过内存数据库映射,可以直接对本地持久化数据库文件执行 sqlite 命令进行数据删除,删除后立即生效不需要重启进程

例如,删除 hwdiscovery 组件的掉电本地持久化数据库表 t_hw_component:

  • 删除整表数据示例(t_hw_component 表所有数据):
bash
/usr/sbin/sqlite3 /data/trust/persistence.local/hwdiscovery.db "DELETE FROM t_hw_component;"
  • 删除单条记录示例(t_hw_component表中主键值Position等于01010A的记录):
bash
/usr/sbin/sqlite3 /data/trust/persistence.local/hwdiscovery.db "DELETE FROM t_hw_component WHERE Position='01010A';"

注意:对于存在新增字段的本地持久化数据表,删除时需要使用表名加_v_前缀的视图名,否则扩展表会存在数据残留。

因为本地持久化新增字段是存储在扩展表,组件需要对主表和扩展表联合构成的视图进行操作。 代码中对local_db.类名映射的数据表进行操作时,框架已对视图进行封装,但直接通过表名来删除数据时仍需要使用带_v_前缀的视图名而不是原表名。

新增字段可以通过自动生成的local_db.lua中的extend_field()来鉴别。

例如,event 组件的 t_event_list_en 表存在新增字段,删除时需要使用视图名_v_t_event_list_en

bash
/usr/sbin/sqlite3 /data/opt/bmc/persistence.local/event.db "DELETE FROM _v_t_event_list_en;"

恢复删除过的datas.yaml预置数据 ​

远程持久化删除过的数据,在数据库中的deleted_data_table保留主键值记录。组件初始化加载datas.yaml预置数据到内存数据库时,会将这部分已删除的数据去掉。如果调试时需要恢复删除过的datas.yaml预置数据,则需要手动删除deleted_data_table中对应表格的记录。

例如,恢复掉电持久化表t_manager_account的datas.yaml预置数据,可以执行以下命令,然后重启组件所在进程

bash
/usr/sbin/sqlite3 /data/trust/persistence/per_poweroff.db "DELETE FROM deleted_data_table WHERE table_name = 't_manager_account';"