The Lua development framework of openUBMC is provided by the MC module and automatically generated code. The current programming framework provides programming capabilities in multiple languages, with Lua being the most fully supported. Lua is a lightweight and compact script language written in standard C. It is open in source code form and can be easily embedded into other programs. For more information about Lua, see Lua official documentation.
Typed Programming in Lua
The openUBMC programming framework implements a set of basic type mechanisms based on Lua and provides common functions such as inheritance and singleton based on Lua tables. Note that this framework is not a complete object-oriented programming framework. Functions such as polymorphism, encapsulation, overloading, and rewriting are not supported.
Typed programming can be introduced through local class = require 'mc.class'.
Lua Object Lifecycle
An object is created using the <type>.new() method. During object creation, the ctor() constructor of the parent class on the inheritance chain is executed in order from the top parent class to the bottom, and then the self ctor() method is executed. If defined, the self pre_init() and init() methods are executed in sequence.
Notes:
The ctor() method can take parameters. The initialization parameters are also used to initialize the parent class. The pre_init() and init() methods must be parameterless.
The pre_init() and init() methods may not call functions corresponding to the parent class. However, a subclass can access the pre_init() and init() methods of the parent class through the
.superproperty to manually call them in the initialization phase.According to the Lua syntactic sugar rules, Lua can access member properties through
.and define and call methods through:. When the:is used to access methods, the first argumentobject selfis automatically passed in. When thefunction obj:func()is used to define a new member method, the first parameterobject selfis automatically passed in.
Object Inheritance
If a subclass app needs to be defined to inherit the parent class base_app, it can be defined in app.lua after requiring class and parent class base_app.
local app = class(base_app)
function app:ctor()
app.prop1 = "prop1"
-- set prop here
end
function app:pre_init()
self.parent:pre_init()
-- pre_init here
end
function app:init()
self.parent:init()
-- init here
end
function app:func1()
-- do something
end
-- define functions here
return appAfter the definition is completed, a new subclass object can be created using app.new() after local app = require 'app'.
Singleton Pattern
The singleton pattern is a design pattern that ensures that a class has only one instance and provides a global access point to it. The singleton pattern prevents global classes from being frequently created and destroyed, avoiding redundant resource usage.
Creating a Singleton
To make an object a singleton, first define the object, and then encapsulate it using the singleton pattern.
-- demo_class_singleton.lua
local singleton = require 'mc.singleton'
local class = require 'mc.class'
demo_class = class()
return singleton(demo_class)If demo_class.new(...) or demo_class.get_instance(...) is called, singlenton() is used to define demo_class as a singleton, that is, the demo_class variable is globally unique.
local lu = require 'luaunit'
local demo_class_singleton = require 'demo_class_singleton'
lu.assertEquals(
demo_class_singleton.new(),
demo_class_singleton.new()
)Log Framework
Log Level Specifications
Error: error information during program running. The current processing must be stopped and the error must be thrown upward. For example, the database cannot be connected, the JSON string fails to be parsed, or the file fails to be created.
Warning: information or event that has negative impact during program running. The current processing can continue, but severer faults may occur. For example, the memory usage exceeds the specified threshold, a process takes longer than expected, or the amount of data written to the flash memory exceeds the threshold.
Notice: important information or events during program running, which have no negative impact on program running. For example, a key system signal or event is received, a scheduled task is successfully executed, or a program starts to listen on a port.
Info: general information or events during program running, which are less important than Notice. For example, a part of a task is completed.
Debug: detailed information about program running, such as packet data and function call tracks (entry and exit).
Notes:
By default, openUBMC logs only track messages at the notice level or higher.
All logs generated by openUBMC are flushed to disks. Frequent log output will shorten the service life of disks. Exercise caution when setting the log level to INFO or higher in scenarios where logs are frequently printed.
Setting the Log Output Level
Setting through busctl
The command format of the busctl is as follows:
busctl --user call bmc.kepler.<component> /bmc/kepler/<component>/MicroComponent bmc.kepler.MicroComponent.Debug SetDlogLevel a{ss}sy 0 <log level> <effective duration>Replace <component> with the target component and specify the log level and effective duration to set the log output level.
- Valid log levels:
debug, info, notice, warning, error. - The unit of the effective duration is hour.
For example, run the following command to change the log output level of the network_adapter component to debug within 2 hours.
busctl --user call bmc.kepler.network_adapter /bmc/kepler/network_adapter/MicroComponent bmc.kepler.MicroComponent.Debug SetDlogLevel a{ss}sy 0 debug 2Setting through mdbctl
SSH into the environment and run the
mdbctlcommand to go to the mdbctl CLI.Run the
lsmccommand to view the started component, and run theattach <component>command to select the component to be set. This command can be run for multiple times to select multiple components.Run the
dloglevel <log level>command to set the log output level for the attached components. Valid log levels aredebug, info, notice, warning, error. Only logs of the selected level and higher will be output.To monitor logs on the CLI, run the
dlogtype <output type>command with the output type set tofile, local. If it is set tolocal, logs are directly output to the console, which applies to the scenario where debugging is performed in the environment. In this case, logs are not recorded in files. If it is set tofile, logs are output to log files.
Logging Interface Usage Guide
Debug Logging Interface
| Interface | Function |
|---|---|
| log:debug(fmt, ...) | Record debug-level debug logs. |
| log:info(fmt, ...) | Record info-level debug logs. |
| log:notice(fmt, ...) | Record notice-level debug logs. |
| log:warning(fmt, ...) | Record warning-level debug logs. |
| log:error(fmt, ...) | Record error-level debug logs. |
Example:
local log = require 'mc.logging'
-- It is recommended to use %s to print variables of the number type. This prevents the program from throwing an error when the variable unexpectedly becomes nil.
log:debug('test value:%s', value)
-- Objects can be directly output.
log:debug(data_obj)Feature
- Logs generated by components are recorded in the app log file /var/log/app.log. The /var/log/framework.log file records openUBMC framework logs, including component startup and health check information.
Filtering Duplicated Logs
Repeated log entries are coalesced into a single record. The system logs only one entry until the content differs from the previous repeated one, at which point it outputs both the number of repetitions and the content of the new log. For example, if the following log is recorded for four times within 2 seconds, only one log is recorded.
2024-12-20 02:20:03.117404 event NOTICE: object_manage.lua(222): add objects callback, path: /bmc/kepler/ObjectGroup/0101, position: 0101, life
cycle id: 1, count: 15, took 420ms [repeated 4 times in 2s][flush]Storm suppression mechanism is used for logging. If more than 500 logs are generated within 10 seconds, the suppression is triggered and no more logs can be recorded. After 10 seconds, logging is restored.
An unrestricted logging interface is provided for scenarios where rate limiting is not required.
| Interface | Function |
|---|---|
| log:debug_easy(fmt, ...) | Record debug-level debug logs without rate limiting. |
| log:info_easy(fmt, ...) | Record info-level debug logs without rate limiting. |
| log:notice_easy(fmt, ...) | Record notice-level debug logs without rate limiting. |
| log:warning_easy(fmt, ...) | Record warning-level debug logs without rate limiting. |
| log:error_easy(fmt, ...) | Record error-level debug logs without rate limiting. |
Operation Logging Interface
Operation log location: /var/log/operation.log
This system operation logs record settings and user interactions only, which is used for operation non-repudiation.
| Interface | Function |
|---|---|
| log:operation(initializer, executor, fmt, ...) | Record operation logs. |
| log:system(system_id):operation(initializer, executor, fmt, ...) | Record operation logs that are differentiated by multihost. |
Maintenance Logging Interface
Maintenance log location: /var/log/maintenance.log
This log records maintenance events during system running.
| Interface | Function |
|---|---|
| log:maintenance(level, fault_code, fmt, ...) | Record maintenance logs. |
| log:system(system_id):maintenance(level, fault_code, fmt, ...) | Record maintenance logs that are differentiated by multihost. |
Running Logging Interface
Running log location: /var/log/running.log
Running logs record abnormal status and actions during system running, key events during system process running, and information about system resource usage.
| Interface | Function |
|---|---|
| log:running(level, fmt, ...) | Record running logs. |
| log:system(system_id):running(level, fmt, ...) | Record running logs that are differentiated by multihost. |
Security Logging Interface
Security log location: /var/log/security.log
Security logs record security events that occur during system running, such as login authentication, account management, access control, and network attacks.
| Interface | Function |
|---|---|
| log:security(fmt, ...) | Record security logs. |
| log:system(system_id):security(fmt, ...) | Record security logs that are differentiated by multihost. |
Serial Port Logging Interface
Used to output logs through the serial port.
| Interface | Function |
|---|---|
| log:debug_printf(fmt, ...) | Record debug-level serial port logs. |
| log:info_printf(fmt, ...) | Record info-level serial port logs. |
| log:notice_printf(fmt, ...) | Record notice-level serial port logs. |
| log:warn_printf(fmt, ...) | Record warning-level serial port logs. |
| log:error_printf(fmt, ...) | Record error-level serial port logs. |
mdbctl Logging Interface
Used to output logs in the mdbctl CLI.
| Interface | Function |
|---|---|
| log:mdbctl_log(fmt, ...) | Record logs to the mdbctl terminal (Prerequisite: The component must be attached in mdbctl. Otherwise, logs will not be displayed on the mdbctl terminal.) |
C Logging Interface
In the scenario where C is encapsulated by Lua, note the following:
In DT scenarios, C logs are recorded in the /dev/shm/debug_log_for_test file.
Currently, the C logging interface does not provide an external interface for changing log levels. The log level can be changed only through code.
Example of changing the C log level
#include "logging.h"
set_debug_log_level(DLOG_DEBUG);Logging Backend
Rsyslog Backend
Currently, framework logs (framework.log) and debug logs (app.log) are recorded by Rsyslog.
Overview
Rsyslog is a logging service used in the Linux system. It is installed by default and automatically enabled. As a multi-thread enhanced version of Syslogd, Rsyslog processes and forwards system logs based on the Syslog protocol.
Rsyslog-Related File Paths and Restart Modes
Executable file path: /usr/sbin/rsyslogd
Path of main configuration files (including the main log file configuration used by openUBMC, such as app debug logs): /etc/rsyslog.conf
Path of custom configuration files (including some dynamically generated configurations): /etc/rsyslog.d/*.conf
Restart mode: The Baseboard Management Controller (BMC) uses systemctl to manage the log service. To restart the Rsyslog service, run the systemctl restart rsyslog command.
Integration Between Lua and C/C++
The C/C++ code can be called from Lua code as follows:
Create and compile a CMakeLists.txt in the C/C++ file directory.
Add the Lua interface in the C/C++ file.
#define LUA_LIB
#define GNU_SOURCE
#include <lua.h>
#include <lauxlib.h>
static int l_do_sth_in_c_func(lua_State *L) {
//Parse the C-type variable from the Lua VM.
const gchar *str_param = (gchar *)luaL_checkstring(L, 1);
guint32 uint_param = (guint32)luaL_checkinteger(L, 2);
gboolean boolean_param = (gboolean)luaL_checkinteger(L, 3);
//Pass parameters and call the C function.
gint32 ret = c_func(str_param, uint_param, boolean_param);
//Return the return value to Lua.
lua_pushinteger(L, ret);
return 1;
}
LUAMOD_API int luaopen_demo_lib_intf(lua_State *L)
{
luaL_checkversion(L);
//Register the Lua function name.
luaL_Reg l[] = {
{"do_sth_in_c_func", l_do_sth_in_c_func},
{NULL, NULL},
};
luaL_newlib(L, l);
return 1;
}- Modify the CMakeLists.txt file in the module directory.
Add add_subdirectory("src/lualib-src") to the file so that the C/C++ code can be compiled during the build.
add_subdirectory("src/lualib-src")
install(DIRECTORY src/lualib DESTINATION ${APP_INSTALL_DIR} OPTIONAL)
install(DIRECTORY include DESTINATION opt/bmc/lualib OPTIONAL)- Compiling the C library
Run the** build** command to compile the C library interface code. The .so file corresponding to the C library can be found in the install path specified in the CMakeList.txt file.
- Calling the C library
The compiled .so file can be called in the Lua code, for example:
-- Import the C library to the Lua code.
local demo_lib_intf = require "demo_lib_intf"
-- Call the C library code.
demo_lib_intf.do_sth_in_c_func("test", 0, true)Persistence
Configuring Persistence in MDS
Local/Remote Persistence
When tableLocation is set to Local, the class adopts local persistence. When tableLocation is not specified or is set to any value other than Local, the class uses remote persistence.
Local persistence indicates that a component directly operates the component database for persistence.
Remote persistence indicates that a component operates the memory database. The framework captures changes using hooks and sends the changes to the persistence service through RPC for centralized persistence.
Local persistence can be configured for resource collaboration interface properties. However, the ORM mechanism supports only remote persistence for automatic persistence of resource collaboration interface object property values. If local persistence is configured for resource collaboration interface properties, the synchronization between resource collaboration interface property values and persistent data needs to be managed.
Persistence Type
tableType configured in the class definition indicates the persistence type of all class properties. Property persistence must be configured in the usage field of each property.
The options of the persistence type are as follows:
| Persistence Type | Description |
|---|---|
| TemporaryPer | Temporary persistence (data that is lost on reset but must be retained across process restarts) |
| TemporaryPerRetain | Temporary persistence with retention on object deletion |
| ResetPer | Reset persistence (data that is lost on power loss but must be retained across system resets) |
| ResetPerRetain | Reset persistence with retention on object deletion |
| PoweroffPer | Power-off persistence (data that is lost on factory reset but must be retained across power loss) |
| PoweroffPerRetain | Power-off persistence with retention on object deletion |
| PermanentPer | Permanent persistence (Data that must be retained across factory resets) |
Notes
Permanent persistence does not support local persistence.
For permanent persistence, the total writable space is only 2 MB. Only a small amount of stable data such as MAC addresses needs to be configured with permanent persistence.
For power-off persistence, the write volume and frequency must be considered because they affect the lifespan of the flash memory.
Local persistence supports setting the persistence type only at the table level using tableType. The persistence type settings at the property level is invalid.
If tableType is not set for local persistence, PoweroffPer (power-off persistence) is used by default.
For remote persistence, if the persistence type is configured in both tableType and property usage, the property-level configuration takes precedence.
If no persistence type is configured for remote persistence, the data will not be persisted and stored only in the in-memory database.
Persistence Configuration Method
Table Name
The tableName field in the class definition indicates the name of the database table mapped to the class through ORM.
Property Configurations
primaryKey indicates whether the property is a primary key in the database. true indicates that the property is a primary key while false or omitted indicates that it is not a primary key.
uniqueKey indicates whether the property is a unique key. true indicates that the property is a unique key while false or omitted indicates that it is not a unique key. Primary key are unique by default.
baseType indicates the property data type. The value can be U8, U16, U32, U64, S8, S16, S32, S64, or String.
default indicates the default value of the property. If no value is specified, the default value is generated based on the type (0 for numbers and "" for character strings).
notAllowNull indicates whether the property can be empty in the database. true indicates that the property cannot be empty while false or no value indicates that the property can be empty. The primary key cannot be empty by default.
sensitive indicates whether the property is sensitive data. true indicates that the property is sensitive data while false or omitted indicates that it is not sensitive data. For properties specified as sensitive data, the property values are replaced with ****** during one-click ingestion and export of persistent data. The real data is not exposed.
critical indicates whether the property is critical data. true indicates that the property is critical data while false or omitted indicates that it is not critical data. For the power-off persistence property specified as critical data, the data is written to the backup database each time the data is updated.
Constraint Description
If no sensitive data or critical data is involved, it is recommended to configure the property to be local persistence. Remote persistence requires components to send data to the persistence service through RPC for persistence, which requires higher performance than local persistence. Especially in scenarios where the data volume is large, data changes frequently, or data needs to be flushed to disks in real time, local persistence should be configured.
If a persistence type is configured, a table name (tableName) must also be configured. If the remote persistence is used, the table name cannot conflict with the table name configured for other components. Remote persistent data is stored in a centralized database. If remote persistent table names of different components conflict, data read and write will interfere with each other.
Deprecated persistent properties must not be directly deleted. They should be set with deprecated: true. Deleting a persistence property directly will cause compatibility issues.
Added and deprecated persistence properties must not be primary keys or unique keys, and must be set to allow null values or have default values. Otherwise, in scenarios such as version rollback, the absence of explicit value assignment during data additions or modifications can cause data operations to fail.
A class configured with remote persistence must have at least one property set as the primary key (primaryKey: true). The remote persistent database is a columnar database. Each record of a component is split into multiple column entries for storage, and the data is assembled into a complete piece of data based on the primary key value. If no primary key is set, data access errors occur when there are multiple data records.
If the class with persistence configured contains any property with "usage from CSR", its primary key must be defined in a way that ensures unique key values across different CSR objects. Duplicate primary-key values among CSR objects can cause conflicts when auto-discovered objects are added, leading to data interference between objects.
Using the Persistent Database
After the following configurations are completed in model.json, run the automatically generated code. Then the corresponding definition can be viewed in the db.lua file under the gen directory.
model.json configuration:
{
"Account": {
"tableName": "t_account",
"tableType": "PoweroffPer",
"properties": {
"Id": {
"baseType": "U8",
"primaryKey": true
},
"UserName": {
"baseType": "String"
},
"Items": {
"baseType": "Array",
"items": {
"baseType": "String"
}
}
}
}
}Table structure:
|t_account| |--------|-|-| |Id|UserName|Items| |1|name1|["ab", "cd"]| |2|name2|["ab", "cd"]|
Using Statement Objects to Operate the Database
The db:select, db:insert, db:update, db:delete method can be used to create a Statement object.
:first()method
Use db:select (Table object):first () to query the first data record that matches the conditions.
If a matching record exists, the row object is returned. The field value can be obtained through
.<field name>.If no matching record exist, nil is returned.
local user1 = db:select(db.Account):where(db.Account.Id:eq(1)):first()
print(user1.UserName) -- name1:all()method
Use db:select (Table object):all () to query all data that matches the conditions. The returned value is an array of row objects (empty if no records match).
local users = db:select(db.Account):all()
print(#users) -- 2
print(users[1].UserName) -- name1
print(users[2].UserName) -- name2:fold()method
Use db:select (Table object):fold (callback function) to execute the callback on all queried row objects.
local users = {}
db:select(db.Account):fold(function(user)
print(user.UserName)
users[#users + 1] = user
end):order_by()method
Sort query results with the usage method of order_by (column object, descending order). By default, the query results are sorted in ascending order (from small to large) and can be sorted by multiple columns.
-- Sort by user ID in ascending order and by user name in descending order.
local users = db:select(db.Account):order_by(db.Account.Id):order_by(db.Account.UserName, true):all()- Slicing query results
Skip x data records and then slice the first y data records.:limit(y):offset(x). The limit must be set before the offset. limit can be used independently, but offset cannot be used independently.
-- Query the users whose IDs rank from 3rd to 5th.
local users = db:select(db.Account):order_by(db.Account.Id):limit(3):offset(2):all():insert()method
-- Insert a data record.
db:insert(db.Account):value({Id = 3, UserName = 'name3'}):exec()
-- Insert multiple data records.
local users = {
{Id = 3, UserName = 'name3'},
{Id = 4, UserName = 'name4'}
}
db:insert(db.Account):values(users):exec():update()method
-- Change the name of the user whose **Id** is **1** to **new_name**.
db:update(db.Account):value({UserName = 'new_name'}):where({Id = 1}):exec()
-- Change all user names to **abc**.
db:update(db.Account):value({UserName = 'abc'}):exec():delete()method
-- Delete the user whose Id is 1.
db:delete(db.Account):where({Id = 1}):exec()
-- Delete all users.
db:delete(db.Account):exec()where Conditions
Conditions can be added by calling the:where() method of the Statement object. The dictionary, array, and condition modes are supported.
- Dictionary mode
The where method passes in a Lua table in the format of {field name = field value}, indicating that the data must match all field values passed in.
local users = db:select(db.Account):where({UserName = 'name1'}):all() -- Query all users whose **UserName** is **name1**.
db:delete(db.Account):where({Id = 1, UserName = 'name1'}):exec() -- Delete all users whose **Id** is 1 and **UserName** is **name1**.- Array mode
The where method passes in a Lua table in the format of {{field name, field value}}, indicating that the data must match all field values passed in.
local users = db:select(db.Account):where({{'UserName', 'name1'}}):all() -- Query all users whose **UserName** is **name1**.
db:delete(db.Account):where({{'Id', 1}, {'UserName', 'name1'}}):exec() -- Delete all users whose **Id** is 1 and **UserName** is **name1**.- Condition mode
The where method passes in one or more Condition objects, indicating that data must meet all conditions passed in.
| Method | SQL Operator | Description |
|---|---|---|
| Field:eq(value) | = | Equal to |
| Field:ne(value) | != | Not equal to |
| Field:lt(value) | < | Less than |
| Field:le(value) | <= | Less than or equal to |
| Field:gt(value) | > | Greater than |
| Field:ge(value) | >= | Greater than or equal to |
| Field:like(value) | LIKE | Fuzzy search |
| Field:in_(...) | IN | Matching multiple values |
| or_(...) | OR | Or |
local statement = require 'database.statement'
local or_ = statement.or_
-- Change the user name whose Id is 1 to new_name: UPDATE t_account SET UserName='new_name' WHERE Id=1
local users = db:update(db.Account):value({UserName = 'new_name'}):where(db.Account.UserName:eq('name1')):exec()
-- Query users whose Id is between 2 and 5: SELECT * FROM t_account WHERE Id >= 2 AND Id <= 5
local users = db:select(db.Account):where(db.Account.Id:ge(2), db.Account.Id:le(5)):all()
-- Query users whose Id is 1, 3, or 5: SELECT * FROM t_account WHERE Id IN (1, 3, 5)
local users = db:select(db.Account):where(db.Account.Id:in_(1, 3, 5)):all()
-- Delete users whose UserName starts with name or Id is greater than 3: DELETE FROM t_account WHERE UserName LIKE 'name%' OR Id > 3
db:delete(db.Account):where(or_(db.Account.UserName:like('name%'), db.Account.Id:gt(3))):exec()Note: When
:like()is used for fuzzy match, if the data volume is large (more than 10,000 records), the matching may take a long time and the CPU usage may increase.
Example
The following example shows how to query and delete persistent data using db. The content comes from the thermal_mgmt component repository.
function fan_obj_manager:clear_persist_fan_info()
if self.db == nil or self.db.FanInfo == nil then
return
end
local tbl = self.db.FanInfo
local fan_id = self.persist_fan_id
local system_id = self.SystemId
local record = self.db:select(tbl)
:where(tbl.SystemId:eq(system_id), tbl.FanId:eq(fan_id), tbl.FanPosition:eq(self:get_position()))
:first()
if record ~= nil then
log:notice('[%s]delete persistent info of fan%d', self.ObjectName, fan_id)
record:delete()
end
endORM Mechanism
Object Relational Mapping (ORM) is an advanced object management framework of openUBMC that is still in the experimental stage. It extends the persistence capability. For details, see ORM Object Management Framework User Guide.
bitstring
The programming framework provides some practical tool libraries, such as the common tool function library utils, json encapsulated based on the c-json library, and bitstring for bit stream encoding and decoding. This section describes how to use bitstring.
bitstring: A Module for Binary Encoding and Decoding
Function Overview
BMC development often involves binary data processing. For example, IPMI and SMBIOS are binary protocols, and the hardware drivers read binary data.
The bitstring module is designed to simulate the Erlang bit syntax to simplify binary processing for the BMC.
Encoding and Decoding
Decoding refers to extracting values from binary data based on the configured offset, length, type, and endianness. Encoding is the reverse operation of decoding. It packs some values into a string of consecutive binary data based on the configuration.
Binary Matching
What is binary matching? Consider the following scenario: During binary decoding, it is often necessary to match whether some positions are equal to specific protocol constants, or whether several bytes represent the length of the subsequent encoding. The common method is to decode some bytes first, and then use the code to implement condition judgment or registry table lookup to complete protocol parsing.
If binary matching is used as an intrinsic feature of binary encoding and decoding, additional code logic can be omitted. In addition, the part to be matched is usually a protocol constant, which is a part of the protocol. It is more intuitive to be embedded in the binary encoding and decoding process.
bitstring Module Bit Syntax
The bit syntax of bitstring is based on the Erlang bit syntax. For details, see Erlang official documentation.
<<E1, E2, ..., En>>Each Ei element identifies a segment of a binary type or bit string. A single Ei element may have four forms.
Ei = Value |
Value:Size |
Value/TypeSpecifierList |
Value:Size/TypeSpecifierListThe summary is as follows:
A pair of double angle brackets indicates a string of binaries, and elements are separated by commas (,): <<E1,...,En>>.
The complete syntax of each element is Value:Size/TypeSpecifierList, where Size and TypeSpecifierList can be ignored.
The value of Size ranges from 1 to 255. The default value is 8.
TypeSpecifierList is a type specifier list, which is separated by hyphens (-). Each specifier can be Type | Signedness | Endianness | Unit.
Type = integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32
Signedness = signed | unsigned
Endianness = big | little | native
Unit = unit:IntegerLiteral. Unit indicates the Size unit. The default value is 1 for integer, float, and bitstring types, and 8 for binary type.
Binary Operations
After a bistring object is created using a pattern string, the object can be used for binary encoding and decoding operations. The core operations are pack and unpack, which are used to pack and unpack binary data respectively.
- Pattern:pack(params)
- Pattern:unpack(data, ret_some, raw_binary)
Since the binary matching syntax is integrated, protocol constants can now be encoded into format strings. The following example shows how to use bitstring.
local bs = require 'mc.bitstring'
local s_pack = string.pack
local function b_unpack(pattn, data)
local p = bs.new(pattn)
return p:unpack(data)
end
function TestBitString:test_unpack_integer_endiannes()
local i8, i16, i32, i64 = 0x12, 0x1234, 0x12345678, 0x1234567800ABCDEF
local data = s_pack('<bhi4i8', i8, i16, i32, i64)
local r = b_unpack('<<var1:1/integer-unit:8, var2:2/big-unit:8, var3:4/little-unit:8, var4:8/big-unit:8>>', data)
lu.assertEquals(r.var1, 0x12)
lu.assertEquals(r.var2, 0x3412)
lu.assertEquals(r.var3, 0x12345678)
lu.assertEquals(r.var4, 0xEFCDAB0078563412)
endIPMI Library
What Is IPMI?
The Intelligent Platform Management Interface (IPMI) is an industry standard used to manage peripherals in Intel-based enterprise systems. This interface standard was developed by Intel, HP, NEC, Dell, and SuperMicro. Users can use the IPMI to monitor the physical health status of servers, including the temperature, voltage, fan status, and power status.
Sending IPMI Commands
In addition to registering the IPMI command callbacks, openUBMC also provides the capability to send IPMI commands for communicating with the IMU. The IMU can access information such as CPU, memory, and PCIe in-band, and supply it to the BMC for out-of-band management.
The IPMI sending capability is provided by the ipmi_core component and is introduced by require 'ipmi'. The following is an example:
local ipmi = require 'ipmi'
local bs = require 'mc.bitstring'
local enums = require 'ipmi.enums'
local bs_req = bs.new([[<<
param_a:48,
param_b:4,
param_c:4>>]])
local channel_type = enums.ChannelType
local comp_code = ipmi.types.Cc
def get_info_with_ipmi(bus, a, b, c)
local req_data = bs_req:pack({
param_a = a,
param_b = b,
param_c = c,
})
local result, payload
local info_arr
for _ = 1, 3 do
result, payload = ipmi.request(bus, channel_type.CT_ME:value(),
{DestNetFn = <replace with actual NetFn>, Cmd = <replace with actual Cmd>, Payload = req_data})
if result == comp_code.Success then
return info_arr
end
end
endEncoding and decoding can be performed by using the aforementioned bitstring. Then the encoding result is passed in as part of the parameters of ipmi.request() function, and the actual NetFn and Cmd are specified in the parameters to send the IPMI command. This function is implemented by calling the resource collaboration interface. Therefore, the D-Bus instance needs to be passed in.
Error Engine
Error Definition
Define the error boundaries, especially distinguishing errors from events. The specific definition is as follows:
Error refers to the failure of the BMC to execute services based on the normal process.
Event refers to a service exception detected when the BMC is running properly.
Errors are BMC system's error. External errors of components need to be managed and controlled in a unified manner.
Errors are defined based on the Redfish standard (all errors defined by Redfish are inherited). The uniqueness of errors must be ensured.
Errors are managed by mdb_interface.
- Define errors in the messages directory. You can add directories and files as required. *The code generated during the build is deployed in the /opt/bmc/lualib/messsages directory.
How to Define An Error
Define Redfish standard errors in mdb_interface/messages/base.json and openUBMC custom errors in mdb_interface/messages/custom.json. Errors are added to the Messages field.
If an undefined error is thrown in the code, it will be converted into InternalError and returned to the user.
The definition format is as follows:
"ErrorName": {
"Description": "error description",
"Message": "error message,args %1,%2",
"Severity": "Warning",
"NumberOfArgs": 2,
"ArgTypes": [
"string",
"string"
],
"Resolution": "sth to fix",
"HttpStatusCode": 400,
"IpmiCompletionCode": "0xFF"
}Field description:
Description: message description
Message: message content. If args exist, they are filled in the message.
Severity: severity level
NumberOfArgs: number of arguments
ArgTypes: argument type table. If the number of arguments is 0, this field does not exist.
Resolution: actions or solutions to address the issue
HttpStatusCode: HTTP return code
IpmiCompletionCode: IPMI completion code
How to Throw An Error
First, require the error engine module
local base_messages = require 'messages.base'Then, use this module to throw the configured error and fill in the content in the argument list.
error(base_messages.ErrorName("Arg1", "Arg2"))