持久化机制介绍

在 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 gen命令生成 gen/mdb/bmc/kepler/Example/TestA/TestA.lua 在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';"