Open
feat: 支持snmp组件使用cpp版本映射器 #9
ouwenjianAdd on Aug 31
feat: 支持snmp组件使用cpp版本映射器 #9
Open
Merged fromvyasa/snmpmainintoopenUBMC/snmpmain
Total: 11 files changed+670-442
| @@ -86,7 +86,7 @@ SNMP Manager ──SNMP v1/v2c/v3──> net-snmp Agent(snmpd) ──> libsnmp.s | |||
| 86 | # 遍历本组件注册的 SNMP 接口(OEM OID 子树) | 86 | # 遍历本组件注册的 SNMP 接口(OEM OID 子树) |
| 87 | snmpwalk -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1 | 87 | snmpwalk -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1 |
| 88 | 88 | ||
| 89 | -# 读取标量接口(OID 后追加 .0;示例 trapEnable,见 test/unit/data/mapping_config/config.lua) | 89 | +# 读取标量接口(OID 后追加 .0;示例 trapEnable,路由样例见 test/unit/mock_routemapper.lua) |
| 90 | snmpget -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1.4.1.0 | 90 | snmpget -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1.4.1.0 |
| 91 | # 示意响应(需环境验证):iso.3.6.1.4.1.2011.2.235.1.1.4.1.0 = INTEGER: 1 | 91 | # 示意响应(需环境验证):iso.3.6.1.4.1.2011.2.235.1.1.4.1.0 = INTEGER: 1 |
| 92 | 92 | ||
| @@ -247,7 +247,7 @@ Received SNMP packet(s) from UDP: [<客户端IP>]:<端口>. oid:<OID> | |||
| 247 | 247 | ||
| 248 | **Q2:新增 SNMP 接口需要改代码吗?** | 248 | **Q2:新增 SNMP 接口需要改代码吗?** |
| 249 | 一句话答案:不需要,在映射配置目录新增 URI 配置即可。 | 249 | 一句话答案:不需要,在映射配置目录新增 URI 配置即可。 |
| 250 | -根因:组件经 `route_mapper` 读取 `interface_config/<product>/mapping_config` 动态注册。 | 250 | +根因:组件经进程内路由映射器读取 `interface_config/<product>` 配置根(`mapping_config`/`script`/`plugins`)动态注册。 |
| 251 | 解决方案:按 [3](#3-组件扩展案例) 示例配置,注意 URI 校验规则,重启 snmpd 后用 `snmpwalk`/`snmpget` 验证。 | 251 | 解决方案:按 [3](#3-组件扩展案例) 示例配置,注意 URI 校验规则,重启 snmpd 后用 `snmpwalk`/`snmpget` 验证。 |
| 252 | 252 | ||
| 253 | **Q3:表接口 `snmpwalk` 遍历不到数据?** | 253 | **Q3:表接口 `snmpwalk` 遍历不到数据?** |
| @@ -264,3 +264,40 @@ Received SNMP packet(s) from UDP: [<客户端IP>]:<端口>. oid:<OID> | |||
| 264 | 一句话答案:通过替换关系配置 `SnmpOemIdentifier` 设置。 | 264 | 一句话答案:通过替换关系配置 `SnmpOemIdentifier` 设置。 |
| 265 | 根因:默认 OEM 前缀 `1.3.6.1.4.1.2011.2.235.1.1.`,组件会替换为 `1.3.6.1.4.1.<替换值>.`。 | 265 | 根因:默认 OEM 前缀 `1.3.6.1.4.1.2011.2.235.1.1.`,组件会替换为 `1.3.6.1.4.1.<替换值>.`。 |
| 266 | 解决方案:设置 `SnmpOemIdentifier` 并保证映射配置 OID 前缀一致。 | 266 | 解决方案:设置 `SnmpOemIdentifier` 并保证映射配置 OID 前缀一致。 |
| 267 | + | ||
| 268 | +--- | ||
| 269 | + | ||
| 270 | +## 7. 映射器机制(进程内路由映射器) | ||
| 271 | + | ||
| 272 | +snmp 已接入新版「进程内路由映射器」(libroute_mapper + libmcpp 提供的路由映射机制):不再依赖旧的 | ||
| 273 | +`route_mapper.*` / `libroutemapper.*` 运行库与独立 worker 服务,而是在 `snmpd` 进程内直接创建路由映射器实例, | ||
| 274 | +复用组件已有的 SDBus(同步阻塞模式)完成 MDB、认证、策略与 Task 请求。 | ||
| 275 | + | ||
| 276 | +**接入内容**: | ||
| 277 | + | ||
| 278 | +- `src/lualib/snmp_mapper.lua`:SNMP 侧映射器门面。基于 `routemapper.sync` 在进程内加载映射配置, | ||
| 279 | + 提供 `execute` / `describe` / `list_routes` / `reload` 等能力,并把映射器错误统一规范化为 SNMP 错误消息 | ||
| 280 | + (`MessageName` / `Message` / `SnmpStatusCode`)。snmp 进程没有 skynet 调度器,映射器调用统一走 | ||
| 281 | + `*_async` 接口 + `mc.actor.step()` 驱动循环 + `take_ready` 的同步等待模式(`await`), | ||
| 282 | + 等待期间驱动本 VM 的 actor 队列以执行 Script/Plugin 等扩展回调。 | ||
| 283 | +- `src/lualib/config.lua`:通过 `list_routes()` + `describe()`(编译后的公开元数据)构建接口资源表, | ||
| 284 | + 不再访问映射器内部路由表。 | ||
| 285 | +- `src/lualib/interface.lua`、`task_mgmt.lua`:请求执行改为 `mapper:execute()`,任务信息取自执行结果 | ||
| 286 | + `effects.Tasks`,不再依赖旧 `mapper:match()` 返回的 `extra_info`。 | ||
| 287 | +- 通用 MDB 访问、`SnmpOemIdentifier` 占位符替换(`{{SnmpOemIdentifier}}`)由映射器内部完成; | ||
| 288 | + 组件自身读取 `config.json` 的 `SnmpOemIdentifier` 仅用于 URI 校验。 | ||
| 289 | + | ||
| 290 | +**配置根目录语义**:进程内路由映射器的 `load()` 接收部署配置根目录(`mapping_config`、`script`、`plugins` | ||
| 291 | +的公共父目录),即 `/opt/bmc/apps/snmp/interface_config[/<platform_id>_<board_id>]`,不再传 `mapping_config` | ||
| 292 | +子目录。 | ||
| 293 | + | ||
| 294 | +**运行依赖**:设备上需要安装与目标工具链匹配的新版 `libroute_mapper` 与 `libmcpp` 运行包,至少包括: | ||
| 295 | + | ||
| 296 | +- `/usr/lib64/libroute_mapper_runtime.so`; | ||
| 297 | +- `/usr/lib64/routemapper/core.so`; | ||
| 298 | +- `/usr/share/lua/routemapper/init.lua`、`runtime.lua`、`host.lua`、`sync.lua`、`sdbus_transport.lua`(及 `skynet.lua`、`cjson.lua`); | ||
| 299 | +- 底层 `libmcpp` 运行库(mcbase/mcengine/mcapp/mcdbus/lmc 等)。 | ||
| 300 | + | ||
| 301 | +**验证**:启动 snmpd 后日志应出现 `initialize in-process route mapper successfully`,且不再加载旧的 | ||
| 302 | +`route_mapper.worker_service`;单元测试 `test/unit` 通过 mock 路由映射器门面(`test/unit/mock_routemapper.lua`) | ||
| 303 | +校验接口注册、请求体、主键与 Sequence 约束。 | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "snmp", | 2 | "name": "snmp", |
| 3 | "type": "library", | 3 | "type": "library", |
| 4 | - "version": "1.120.5", | 4 | + "version": "1.120.6", |
| 5 | "author": "Huawei", | 5 | "author": "Huawei", |
| 6 | "license": "Mulan PSL v2", | 6 | "license": "Mulan PSL v2", |
| 7 | "dependencies": { | 7 | "dependencies": { |
| @@ -13,6 +13,10 @@ local log = require('mc.logging') | |||
| 13 | local innerbus_ok, innerbus_drv = pcall(require, 'libsoc_adapter.innerbus') | 13 | local innerbus_ok, innerbus_drv = pcall(require, 'libsoc_adapter.innerbus') |
| 14 | innerbus_drv = innerbus_ok and innerbus_drv or {} | 14 | innerbus_drv = innerbus_ok and innerbus_drv or {} |
| 15 | local utils_core = require('utils.core') | 15 | local utils_core = require('utils.core') |
| 16 | +local file_sec_ok, file_sec = pcall(require, 'utils.file') | ||
| 17 | +file_sec = file_sec_ok and file_sec or nil | ||
| 18 | +local cjson = require('cjson') | ||
| 19 | +local snmp_mapper_ok = pcall(require, 'snmp_mapper') | ||
| 16 | 20 | ||
| 17 | local m = class() | 21 | local m = class() |
| 18 | 22 | ||
| @@ -28,9 +32,15 @@ function m:init() | |||
| 28 | end | 32 | end |
| 29 | 33 | ||
| 30 | local function is_task_get_interface(res) | 34 | local function is_task_get_interface(res) |
| 35 | + local methods | ||
| 36 | + if not snmp_mapper_ok then | ||
| 37 | + methods = res.methods | ||
| 38 | + else | ||
| 39 | + methods = res | ||
| 40 | + end | ||
| 31 | local ok, ret = pcall(function() | 41 | local ok, ret = pcall(function() |
| 32 | local method_cnt = 0 | 42 | local method_cnt = 0 |
| 33 | - for _ in pairs(res.methods) do | 43 | + for _ in pairs(methods) do |
| 34 | method_cnt = method_cnt + 1 | 44 | method_cnt = method_cnt + 1 |
| 35 | end | 45 | end |
| 36 | 46 | ||
| @@ -38,7 +48,7 @@ local function is_task_get_interface(res) | |||
| 38 | return false | 48 | return false |
| 39 | end | 49 | end |
| 40 | 50 | ||
| 41 | - local interface = res.methods.get | 51 | + local interface = methods.get |
| 42 | if not interface then | 52 | if not interface then |
| 43 | return false | 53 | return false |
| 44 | end | 54 | end |
| @@ -58,16 +68,104 @@ local function is_task_get_interface(res) | |||
| 58 | return ret | 68 | return ret |
| 59 | end | 69 | end |
| 60 | 70 | ||
| 71 | +-- 从编译后的端点描述中提取请求体配置(与原始 ReqBody 结构一致) | ||
| 72 | +local function extract_reqbody(description) | ||
| 73 | + return description.RequestSchema or nil | ||
| 74 | +end | ||
| 75 | + | ||
| 76 | +-- 从编译后的响应字段构建默认响应体 {Name = Default} | ||
| 77 | +local function extract_default_rsp_body(description) | ||
| 78 | + local rsp_body = {} | ||
| 79 | + for _, field in ipairs(description.ResponseFields or {}) do | ||
| 80 | + rsp_body[field.Name] = field.Default | ||
| 81 | + end | ||
| 82 | + | ||
| 83 | + return rsp_body | ||
| 84 | +end | ||
| 85 | + | ||
| 86 | +-- 用编译后的端点描述构建一个方法的接口配置 | ||
| 87 | +function m:describe_interface(uri, method, description) | ||
| 88 | + description = description or self.mapper:describe(uri, method, {}) | ||
| 89 | + if type(description) ~= 'table' then | ||
| 90 | + return nil | ||
| 91 | + end | ||
| 92 | + | ||
| 93 | + local intf = {} | ||
| 94 | + if description.TaskReferenceUri and #description.TaskReferenceUri ~= 0 then | ||
| 95 | + intf.TaskReferenceUri = description.TaskReferenceUri | ||
| 96 | + end | ||
| 97 | + | ||
| 98 | + intf.ReqBody = extract_reqbody(description) | ||
| 99 | + intf.RspBody = extract_default_rsp_body(description) | ||
| 100 | + | ||
| 101 | + return intf | ||
| 102 | +end | ||
| 103 | + | ||
| 61 | function m:load_mapping() | 104 | function m:load_mapping() |
| 62 | - if not (self.mapper and self.mapper.routers) then | 105 | + local configuration_missing |
| 106 | + if snmp_mapper_ok then | ||
| 107 | + configuration_missing = not self.mapper | ||
| 108 | + else | ||
| 109 | + configuration_missing = not (self.mapper and self.mapper.routers) | ||
| 110 | + end | ||
| 111 | + if configuration_missing then | ||
| 63 | log:error('The route configuration is missing.') | 112 | log:error('The route configuration is missing.') |
| 64 | return | 113 | return |
| 65 | end | 114 | end |
| 66 | 115 | ||
| 67 | - for uri, config in pairs(self.mapper.routers) do | 116 | + if not snmp_mapper_ok then |
| 68 | - config.methods.IsTaskGet = is_task_get_interface(config) | 117 | + for uri, config in pairs(self.mapper.routers) do |
| 69 | - self.resources[uri] = config.methods | 118 | + config.methods.IsTaskGet = is_task_get_interface(config) |
| 70 | - self.resources[uri].Sequence = config.Sequence | 119 | + self.resources[uri] = config.methods |
| 120 | + self.resources[uri].Sequence = config.Sequence | ||
| 121 | + end | ||
| 122 | + return | ||
| 123 | + end | ||
| 124 | + | ||
| 125 | + local route_infos = self.mapper:list_routes() | ||
| 126 | + if type(route_infos) ~= 'table' then | ||
| 127 | + log:error('The route configuration is missing.') | ||
| 128 | + return | ||
| 129 | + end | ||
| 130 | + | ||
| 131 | + for _, route_info in ipairs(route_infos) do | ||
| 132 | + local uri = route_info.Uri | ||
| 133 | + if not uri then | ||
| 134 | + goto continue | ||
| 135 | + end | ||
| 136 | + | ||
| 137 | + -- 以 GET 描述为准填充 Sequence 与任务标识 | ||
| 138 | + local get_description = self.mapper:describe(uri, 'get', {}) | ||
| 139 | + local resource = {} | ||
| 140 | + | ||
| 141 | + for _, method in ipairs(route_info.Methods or {}) do | ||
| 142 | + local lower_method = string.lower(method) | ||
| 143 | + local intf | ||
| 144 | + if lower_method == 'get' then | ||
| 145 | + intf = self:describe_interface(uri, method, get_description) | ||
| 146 | + else | ||
| 147 | + intf = self:describe_interface(uri, method) | ||
| 148 | + end | ||
| 149 | + | ||
| 150 | + if intf then | ||
| 151 | + resource[lower_method] = intf | ||
| 152 | + end | ||
| 153 | + end | ||
| 154 | + | ||
| 155 | + if next(resource) == nil then | ||
| 156 | + goto continue | ||
| 157 | + end | ||
| 158 | + | ||
| 159 | + -- 任务判断需在填充 Sequence 之前进行(避免把 Sequence 误算为方法) | ||
| 160 | + resource.IsTaskGet = is_task_get_interface(resource) | ||
| 161 | + | ||
| 162 | + if type(get_description) == 'table' and get_description.Sequence and #get_description.Sequence ~= 0 then | ||
| 163 | + resource.Sequence = get_description.Sequence | ||
| 164 | + end | ||
| 165 | + | ||
| 166 | + self.resources[uri] = resource | ||
| 167 | + | ||
| 168 | + ::continue:: | ||
| 71 | end | 169 | end |
| 72 | end | 170 | end |
| 73 | 171 | ||
| @@ -106,4 +204,38 @@ function m.get_interface_config_path() | |||
| 106 | return intf_path | 204 | return intf_path |
| 107 | end | 205 | end |
| 108 | 206 | ||
| 207 | +-- 读取 config.json 中的 OEM OID 变量,用于校验映射配置中的 OID 前缀 | ||
| 208 | +function m.get_snmp_oem_identifier() | ||
| 209 | + if not file_sec then | ||
| 210 | + return nil | ||
| 211 | + end | ||
| 212 | + | ||
| 213 | + local config_path = m.get_interface_config_path() .. '/config.json' | ||
| 214 | + local file = file_sec.open_s(config_path, 'r') | ||
| 215 | + if not file then | ||
| 216 | + log:info('open %s failed.', config_path) | ||
| 217 | + return nil | ||
| 218 | + end | ||
| 219 | + | ||
| 220 | + local ok, content = pcall(function() | ||
| 221 | + return file:read('*all') | ||
| 222 | + end) | ||
| 223 | + file:close() | ||
| 224 | + if not ok or not content then | ||
| 225 | + return nil | ||
| 226 | + end | ||
| 227 | + | ||
| 228 | + local is_ok, config = pcall(cjson.json_object_ordered_decode, content) | ||
| 229 | + if not is_ok or not config or not config.GlobalVariable then | ||
| 230 | + return nil | ||
| 231 | + end | ||
| 232 | + | ||
| 233 | + local identifier = config.GlobalVariable.SnmpOemIdentifier | ||
| 234 | + if type(identifier) ~= 'string' or identifier == '' then | ||
| 235 | + return nil | ||
| 236 | + end | ||
| 237 | + | ||
| 238 | + return identifier | ||
| 239 | +end | ||
| 240 | + | ||
| 109 | return m | 241 | return m |
| @@ -15,14 +15,18 @@ local utils = require('mc.utils') | |||
| 15 | local utils_vos = require('utils.vos') | 15 | local utils_vos = require('utils.vos') |
| 16 | local json = require('cjson') | 16 | local json = require('cjson') |
| 17 | local messsage = require('message') | 17 | local messsage = require('message') |
| 18 | -local c_mapper = require('route_mapper.route_mapper') | 18 | +local _, c_mapper = pcall(require, 'route_mapper.route_mapper') |
| 19 | local c_config = require('config') | 19 | local c_config = require('config') |
| 20 | local c_task_mgmt = require('task_mgmt') | 20 | local c_task_mgmt = require('task_mgmt') |
| 21 | -local mapper_intf = require('route_mapper.interface') | 21 | +local _, mapper_intf = pcall(require, 'route_mapper.interface') |
| 22 | local context = require('mc.context') | 22 | local context = require('mc.context') |
| 23 | local traces = require('telemetry.trace') | 23 | local traces = require('telemetry.trace') |
| 24 | local tracer = traces.get_tracer('snmp') | 24 | local tracer = traces.get_tracer('snmp') |
| 25 | local telemetry = require('telemetry') | 25 | local telemetry = require('telemetry') |
| 26 | +local cpp_mapper_ok, cpp_mapper = pcall(require, 'snmp_mapper') | ||
| 27 | +if cpp_mapper_ok then | ||
| 28 | + c_mapper = cpp_mapper | ||
| 29 | +end | ||
| 26 | 30 | ||
| 27 | local SNMP_URI_PREFIX <const> = '/snmp/' | 31 | local SNMP_URI_PREFIX <const> = '/snmp/' |
| 28 | local MDB_PATH_PREFIX <const> = '/bmc/kepler/' | 32 | local MDB_PATH_PREFIX <const> = '/bmc/kepler/' |
| @@ -94,26 +98,51 @@ function m:interface_match(uri, method, address, username, reqbody) | |||
| 94 | if self.task_mgmt:is_task_get_interface(uri) then | 98 | if self.task_mgmt:is_task_get_interface(uri) then |
| 95 | local task_uri = self.task_mgmt:get_task_uri(uri) | 99 | local task_uri = self.task_mgmt:get_task_uri(uri) |
| 96 | if not task_uri then | 100 | if not task_uri then |
| 97 | - return nil, self.task_mgmt:get_default_task_info(uri) | 101 | + if not cpp_mapper_ok then |
| 102 | + return nil, self.task_mgmt:get_default_task_info(uri) | ||
| 103 | + else | ||
| 104 | + return { | ||
| 105 | + body = self.task_mgmt:get_default_task_info(uri), | ||
| 106 | + errors = {}, | ||
| 107 | + effects = {}, | ||
| 108 | + } | ||
| 109 | + end | ||
| 98 | end | 110 | end |
| 99 | 111 | ||
| 100 | uri = task_uri | 112 | uri = task_uri |
| 101 | end | 113 | end |
| 102 | 114 | ||
| 103 | local body = reqbody and json.json_object_ordered_decode(reqbody) | 115 | local body = reqbody and json.json_object_ordered_decode(reqbody) |
| 104 | - return self.mapper:match(uri, method, { | 116 | + if not cpp_mapper_ok then |
| 105 | - body = body, | 117 | + return self.mapper:match(uri, method, { |
| 106 | - user = { | 118 | + body = body, |
| 107 | - Interface = 'SNMP', | 119 | + user = { |
| 108 | - ClientIp = address, | 120 | + Interface = 'SNMP', |
| 109 | - UserName = username, | 121 | + ClientIp = address, |
| 110 | - }, | 122 | + UserName = username, |
| 111 | - }) | 123 | + }, |
| 124 | + }) | ||
| 125 | + else | ||
| 126 | + return self.mapper:execute(uri, method, { | ||
| 127 | + body = body, | ||
| 128 | + user = { | ||
| 129 | + Interface = 'SNMP', | ||
| 130 | + ClientIp = address, | ||
| 131 | + UserName = username, | ||
| 132 | + }, | ||
| 133 | + }) | ||
| 134 | + end | ||
| 112 | end) | 135 | end) |
| 113 | if not ok then | 136 | if not ok then |
| 114 | return messsage.SNMP_ERR_GENERR, 'string', errs | 137 | return messsage.SNMP_ERR_GENERR, 'string', errs |
| 115 | end | 138 | end |
| 116 | 139 | ||
| 140 | + if cpp_mapper_ok and errs then | ||
| 141 | + rsp_body = errs.body | ||
| 142 | + extra_info = errs.effects | ||
| 143 | + errs = errs.errors | ||
| 144 | + end | ||
| 145 | + | ||
| 117 | if errs and #errs ~= 0 then | 146 | if errs and #errs ~= 0 then |
| 118 | local err_code, err_message = messsage.parse_error_message(errs) | 147 | local err_code, err_message = messsage.parse_error_message(errs) |
| 119 | if err_code == messsage.SNMP_ERR_NOERROR then | 148 | if err_code == messsage.SNMP_ERR_NOERROR then |
| @@ -228,7 +257,17 @@ function m:parse_interface_response(uri, rsp_body, task_info) | |||
| 228 | local span = tracer:start_span('snmp_parse_interface_response', { uri = uri }, { kind = 'Server' }) | 257 | local span = tracer:start_span('snmp_parse_interface_response', { uri = uri }, { kind = 'Server' }) |
| 229 | -- 解析Get接口返回值 | 258 | -- 解析Get接口返回值 |
| 230 | local ok, err = pcall(function() | 259 | local ok, err = pcall(function() |
| 231 | - if task_info and task_info.TaskInfo and task_info.TaskInfo.TaskId > 0 and task_info.TaskInfo.Path then | 260 | + local need_create_new_task |
| 261 | + if not cpp_mapper_ok then | ||
| 262 | + need_create_new_task = task_info | ||
| 263 | + and task_info.TaskInfo | ||
| 264 | + and task_info.TaskInfo.TaskId > 0 | ||
| 265 | + and task_info.TaskInfo.Path | ||
| 266 | + else | ||
| 267 | + task_info = task_info and task_info.Tasks and task_info.Tasks[1] | ||
| 268 | + need_create_new_task = task_info and task_info.TaskId > 0 and task_info.Path | ||
| 269 | + end | ||
| 270 | + if need_create_new_task then | ||
| 232 | self.task_mgmt:create_new_task(uri, task_info) | 271 | self.task_mgmt:create_new_task(uri, task_info) |
| 233 | end | 272 | end |
| 234 | 273 | ||
| @@ -276,9 +315,16 @@ function m:get_all_interface_uri_config() | |||
| 276 | local data = {} | 315 | local data = {} |
| 277 | 316 | ||
| 278 | local ibmc_prefix = IBMC_OID_PREFIX | 317 | local ibmc_prefix = IBMC_OID_PREFIX |
| 279 | - local replace_relation = mapper_intf.get_replace_relation(self.bus, c_config.get_interface_config_path()) | 318 | + if not cpp_mapper_ok then |
| 280 | - if replace_relation ~= nil and replace_relation['SnmpOemIdentifier'] ~= nil then | 319 | + local replace_relation = mapper_intf.get_replace_relation(self.bus, c_config.get_interface_config_path()) |
| 281 | - ibmc_prefix = '1.3.6.1.4.1.' .. replace_relation['SnmpOemIdentifier'] .. '.' | 320 | + if replace_relation ~= nil and replace_relation['SnmpOemIdentifier'] ~= nil then |
| 321 | + ibmc_prefix = '1.3.6.1.4.1.' .. replace_relation['SnmpOemIdentifier'] .. '.' | ||
| 322 | + end | ||
| 323 | + else | ||
| 324 | + local snmp_oem_identifier = c_config.get_snmp_oem_identifier() | ||
| 325 | + if snmp_oem_identifier ~= nil then | ||
| 326 | + ibmc_prefix = '1.3.6.1.4.1.' .. snmp_oem_identifier .. '.' | ||
| 327 | + end | ||
| 282 | end | 328 | end |
| 283 | 329 | ||
| 284 | for uri, _ in pairs(self.config.resources) do | 330 | for uri, _ in pairs(self.config.resources) do |
| @@ -0,0 +1,169 @@ | |||
| 1 | +-- Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 2 | +-- openUBMC is licensed under Mulan PSL v2. | ||
| 3 | +-- You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 4 | +-- You may obtain a copy of Mulan PSL v2 at: | ||
| 5 | +-- http://license.coscl.org.cn/MulanPSL2 | ||
| 6 | +-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 7 | +-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 8 | +-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 9 | +-- See the Mulan PSL v2 for more details. | ||
| 10 | + | ||
| 11 | +-- Description: SNMP facade for the in-process route mapper | ||
| 12 | + | ||
| 13 | +local class = require('mc.class') | ||
| 14 | +local log = require('mc.logging') | ||
| 15 | +local cjson = require('cjson') | ||
| 16 | +local mc = require('routemapper.runtime') | ||
| 17 | +local sync_mapper = require('routemapper.sync') | ||
| 18 | +local utils_vos = require('utils.vos') | ||
| 19 | +local message = require('message') | ||
| 20 | + | ||
| 21 | +local mapper_facade = class() | ||
| 22 | + | ||
| 23 | +-- 同步等待路由映射器的异步结果。 | ||
| 24 | +-- snmp 进程没有 skynet 调度器,无法直接调用可 yield 的同步方法(future 未就绪时 | ||
| 25 | +-- 会在 lmc 的 yieldable 守卫处报错),因此改用 *_async 接口 + take_ready: | ||
| 26 | +-- 等待期间驱动本 VM 的 actor 队列,执行 Script/Plugin 等扩展回调推进 future。 | ||
| 27 | +local AWAIT_TIMEOUT_MS = 15000 | ||
| 28 | + | ||
| 29 | +local function await(native, future) | ||
| 30 | + local begin = utils_vos.vos_tick_get() | ||
| 31 | + while not future:is_ready() do | ||
| 32 | + local progressed = mc.actor.step() | ||
| 33 | + if progressed == 0 and utils_vos.vos_tick_get() - begin >= AWAIT_TIMEOUT_MS then | ||
| 34 | + error('route mapper future await timeout', 0) | ||
| 35 | + end | ||
| 36 | + end | ||
| 37 | + | ||
| 38 | + mc.actor.step() | ||
| 39 | + | ||
| 40 | + return native:take_ready(future) | ||
| 41 | +end | ||
| 42 | + | ||
| 43 | +-- 把 sd_bus 包装成 route mapper transport 需要的对象: | ||
| 44 | +-- 必须提供 bus:timeout_call(timeout_ms, destination, path, interface, member, signature, ...) | ||
| 45 | +local function make_bus_adapter(sd_bus_obj) | ||
| 46 | + local adapter = {} | ||
| 47 | + | ||
| 48 | + function adapter:timeout_call(timeout_ms, destination, path, interface, member, signature, ...) | ||
| 49 | + return sd_bus_obj.bus:timeout_call(timeout_ms, destination, path, interface, member, signature, ...) | ||
| 50 | + end | ||
| 51 | + | ||
| 52 | + return adapter | ||
| 53 | +end | ||
| 54 | + | ||
| 55 | +local function to_plain_value(value) | ||
| 56 | + if type(value) ~= 'userdata' then | ||
| 57 | + return value | ||
| 58 | + end | ||
| 59 | + | ||
| 60 | + if cjson.json_object_is_object(value) or cjson.json_object_is_array(value) then | ||
| 61 | + return cjson.json_object_to_table(value) | ||
| 62 | + end | ||
| 63 | + | ||
| 64 | + return value | ||
| 65 | +end | ||
| 66 | + | ||
| 67 | +-- execute 返回的 body/action_body 是 C++ 侧按映射配置插入序序列化的 JSON | ||
| 68 | +-- 字符串,直接保序解码,避免 Lua 表键序哈希化;SNMP 消费端按 table 消费。 | ||
| 69 | +-- 纯文本响应(非 JSON)解码失败时原样返回字符串。 | ||
| 70 | +local function to_snmp_value(value) | ||
| 71 | + if type(value) == 'string' then | ||
| 72 | + local ok, decoded = pcall(cjson.json_object_ordered_decode, value) | ||
| 73 | + if ok then | ||
| 74 | + return decoded | ||
| 75 | + end | ||
| 76 | + return value | ||
| 77 | + end | ||
| 78 | + if type(value) ~= 'table' then | ||
| 79 | + return value | ||
| 80 | + end | ||
| 81 | + | ||
| 82 | + return cjson.json_object_ordered_decode(cjson.encode(value)) | ||
| 83 | +end | ||
| 84 | + | ||
| 85 | +-- 把 route mapper 的错误统一转换为 SNMP 错误消息结构 | ||
| 86 | +local function normalize_error(err) | ||
| 87 | + if type(err) ~= 'table' then | ||
| 88 | + err = {} | ||
| 89 | + end | ||
| 90 | + | ||
| 91 | + local name = err.MessageName or err.Code or err.Name | ||
| 92 | + if type(name) ~= 'string' or name == '' then | ||
| 93 | + name = 'InternalError' | ||
| 94 | + end | ||
| 95 | + | ||
| 96 | + return { | ||
| 97 | + MessageName = name, | ||
| 98 | + Message = err.Message or name, | ||
| 99 | + SnmpStatusCode = err.SnmpStatusCode or message.SNMP_ERR_GENERR, | ||
| 100 | + } | ||
| 101 | +end | ||
| 102 | + | ||
| 103 | +local function normalize_errors(errors) | ||
| 104 | + local result = {} | ||
| 105 | + for index, err in ipairs(errors or {}) do | ||
| 106 | + result[index] = normalize_error(err) | ||
| 107 | + end | ||
| 108 | + return result | ||
| 109 | +end | ||
| 110 | + | ||
| 111 | +function mapper_facade:ctor(config_root, bus) | ||
| 112 | + self.config_root = config_root | ||
| 113 | + self.bus = make_bus_adapter(bus) | ||
| 114 | + self.mapper = nil | ||
| 115 | +end | ||
| 116 | + | ||
| 117 | +function mapper_facade:init() | ||
| 118 | + log:notice('initialize in-process route mapper: compile configuration') | ||
| 119 | + -- customer 为空时不加载 customer 目录,使用默认配置 | ||
| 120 | + self.mapper = sync_mapper.load(self.config_root, self.bus, '', false, 'snmp') | ||
| 121 | + log:notice('initialize in-process route mapper successfully, generation=%d', self.mapper:generation()) | ||
| 122 | +end | ||
| 123 | + | ||
| 124 | +function mapper_facade:execute(uri, method, params) | ||
| 125 | + params = params or {} | ||
| 126 | + | ||
| 127 | + local future = self.mapper:execute_async( | ||
| 128 | + string.upper(method), | ||
| 129 | + uri, | ||
| 130 | + to_plain_value(params.body), | ||
| 131 | + to_plain_value(params.header), | ||
| 132 | + to_plain_value(params.user), | ||
| 133 | + to_plain_value(params.query) | ||
| 134 | + ) | ||
| 135 | + local result = await(self.mapper, future) | ||
| 136 | + result.body = to_snmp_value(result.body) | ||
| 137 | + result.errors = normalize_errors(result.errors) | ||
| 138 | + | ||
| 139 | + return result | ||
| 140 | +end | ||
| 141 | + | ||
| 142 | +function mapper_facade:describe(uri, method, params) | ||
| 143 | + params = params or {} | ||
| 144 | + | ||
| 145 | + local future = self.mapper:describe_async( | ||
| 146 | + string.upper(method), | ||
| 147 | + uri, | ||
| 148 | + to_plain_value(params.body), | ||
| 149 | + to_plain_value(params.header), | ||
| 150 | + to_plain_value(params.user), | ||
| 151 | + to_plain_value(params.query) | ||
| 152 | + ) | ||
| 153 | + | ||
| 154 | + return await(self.mapper, future) | ||
| 155 | +end | ||
| 156 | + | ||
| 157 | +function mapper_facade:list_routes() | ||
| 158 | + return self.mapper:list_routes() | ||
| 159 | +end | ||
| 160 | + | ||
| 161 | +function mapper_facade:generation() | ||
| 162 | + return self.mapper:generation() | ||
| 163 | +end | ||
| 164 | + | ||
| 165 | +function mapper_facade:reload() | ||
| 166 | + return self.mapper:reload(self.config_root, '') | ||
| 167 | +end | ||
| 168 | + | ||
| 169 | +return mapper_facade | ||
| @@ -11,6 +11,7 @@ | |||
| 11 | local class = require('mc.class') | 11 | local class = require('mc.class') |
| 12 | local log = require('mc.logging') | 12 | local log = require('mc.logging') |
| 13 | local cjson = require('cjson') | 13 | local cjson = require('cjson') |
| 14 | +local cpp_mapper_ok = pcall(require, 'snmp_mapper') | ||
| 14 | 15 | ||
| 15 | local TASK_PATH <const> = '%s/TaskService/Tasks/%d' | 16 | local TASK_PATH <const> = '%s/TaskService/Tasks/%d' |
| 16 | 17 | ||
| @@ -25,8 +26,24 @@ end | |||
| 25 | 26 | ||
| 26 | function m:create_new_task(uri, task_info) | 27 | function m:create_new_task(uri, task_info) |
| 27 | self.task_index = self.task_index + 1 | 28 | self.task_index = self.task_index + 1 |
| 29 | + local task_path | ||
| 30 | + if not cpp_mapper_ok then | ||
| 31 | + task_path = string.format(TASK_PATH, task_info.TaskInfo.Path, task_info.TaskInfo.TaskId) | ||
| 32 | + else | ||
| 33 | + -- 任务信息来自映射器执行结果 effects.Tasks[1], | ||
| 34 | + -- Path 为任务资源路径,兼容是否包含 /TaskService/Tasks 两种形态 | ||
| 35 | + task_path = task_info.Path | ||
| 36 | + if task_info.TaskId and tostring(task_info.TaskId) ~= '' then | ||
| 37 | + if string.match(task_path, '/TaskService/Tasks$') then | ||
| 38 | + task_path = task_path .. '/' .. tostring(task_info.TaskId) | ||
| 39 | + else | ||
| 40 | + task_path = string.format(TASK_PATH, task_path, task_info.TaskId) | ||
| 41 | + end | ||
| 42 | + end | ||
| 43 | + end | ||
| 44 | + | ||
| 28 | self.task_list[uri] = { | 45 | self.task_list[uri] = { |
| 29 | - Path = string.format(TASK_PATH, task_info.TaskInfo.Path, task_info.TaskInfo.TaskId), | 46 | + Path = task_path, |
| 30 | Index = self.task_index, | 47 | Index = self.task_index, |
| 31 | } | 48 | } |
| 32 | end | 49 | end |
| @@ -24,7 +24,14 @@ log:set_log_module_name('snmp') | |||
| 24 | local c_interface = require('interface') | 24 | local c_interface = require('interface') |
| 25 | local config = require('config') | 25 | local config = require('config') |
| 26 | 26 | ||
| 27 | -local MAP_CONFIG_PATH = config.get_interface_config_path() .. '/mapping_config' | 27 | +local cpp_mapper_ok = pcall(require, 'snmp_mapper') |
| 28 | +local MAP_CONFIG_PATH | ||
| 29 | +if not cpp_mapper_ok then | ||
| 30 | + MAP_CONFIG_PATH = config.get_interface_config_path() .. '/mapping_config' | ||
| 31 | +else | ||
| 32 | + -- 进程内路由映射器的配置根目录(mapping_config/script/plugins 的公共父目录) | ||
| 33 | + MAP_CONFIG_PATH = config.get_interface_config_path() | ||
| 34 | +end | ||
| 28 | 35 | ||
| 29 | local interface = c_interface.new(MAP_CONFIG_PATH, true) | 36 | local interface = c_interface.new(MAP_CONFIG_PATH, true) |
| 30 | 37 | ||
| @@ -1,384 +0,0 @@ | |||
| 1 | --- Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 2 | --- openUBMC is licensed under Mulan PSL v2. | ||
| 3 | --- You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 4 | --- You may obtain a copy of Mulan PSL v2 at: | ||
| 5 | --- http://license.coscl.org.cn/MulanPSL2 | ||
| 6 | --- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 7 | --- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 8 | --- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 9 | --- See the Mulan PSL v2 for more details. | ||
| 10 | - | ||
| 11 | -Input.data = { | ||
| 12 | - ['/snmp/1.3.6.1.4.1.2011.2.235.1.1.4.1/trapEnable/Readwrite'] = { | ||
| 13 | - methods = { | ||
| 14 | - get = { | ||
| 15 | - RspBody = { | ||
| 16 | - ['Enabled'] = '${Statements/Enabled()}', | ||
| 17 | - }, | ||
| 18 | - Statements = { | ||
| 19 | - ['Enabled'] = { | ||
| 20 | - ['Input'] = '${ProcessingFlow[1]/Destination/Enabled}', | ||
| 21 | - ['Steps'] = { | ||
| 22 | - [1] = { | ||
| 23 | - ['Type'] = 'Switch', | ||
| 24 | - ['Formula'] = { | ||
| 25 | - [1] = { | ||
| 26 | - ['Case'] = false, | ||
| 27 | - ['To'] = 1, | ||
| 28 | - }, | ||
| 29 | - [2] = { | ||
| 30 | - ['Case'] = true, | ||
| 31 | - ['To'] = 2, | ||
| 32 | - }, | ||
| 33 | - }, | ||
| 34 | - }, | ||
| 35 | - }, | ||
| 36 | - }, | ||
| 37 | - }, | ||
| 38 | - ProcessingFlow = { | ||
| 39 | - [1] = { | ||
| 40 | - ['Type'] = 'Property', | ||
| 41 | - ['Path'] = '/bmc/kepler/EventService/Subscriptions/Snmp', | ||
| 42 | - ['Interface'] = 'bmc.kepler.EventService.Subscriptions.Snmp', | ||
| 43 | - ['Destination'] = { | ||
| 44 | - ['Enabled'] = 'Enabled', | ||
| 45 | - }, | ||
| 46 | - }, | ||
| 47 | - }, | ||
| 48 | - }, | ||
| 49 | - patch = { | ||
| 50 | - ReqBody = { | ||
| 51 | - ['Type'] = 'object', | ||
| 52 | - ['Required'] = true, | ||
| 53 | - ['Properties'] = { | ||
| 54 | - ['Enabled'] = { | ||
| 55 | - ['Required'] = true, | ||
| 56 | - ['Type'] = 'integer', | ||
| 57 | - ['Validator'] = { | ||
| 58 | - [1] = { | ||
| 59 | - ['Type'] = 'Enum', | ||
| 60 | - ['Formula'] = { | ||
| 61 | - [1] = 1, | ||
| 62 | - [2] = 2, | ||
| 63 | - }, | ||
| 64 | - }, | ||
| 65 | - }, | ||
| 66 | - }, | ||
| 67 | - }, | ||
| 68 | - }, | ||
| 69 | - Statements = { | ||
| 70 | - ['Enabled'] = { | ||
| 71 | - ['Input'] = '${ReqBody/Enabled}', | ||
| 72 | - ['Steps'] = { | ||
| 73 | - [1] = { | ||
| 74 | - ['Type'] = 'Switch', | ||
| 75 | - ['Formula'] = { | ||
| 76 | - [1] = { | ||
| 77 | - ['Case'] = 1, | ||
| 78 | - ['To'] = false, | ||
| 79 | - }, | ||
| 80 | - [2] = { | ||
| 81 | - ['Case'] = 2, | ||
| 82 | - ['To'] = true, | ||
| 83 | - }, | ||
| 84 | - }, | ||
| 85 | - }, | ||
| 86 | - }, | ||
| 87 | - }, | ||
| 88 | - }, | ||
| 89 | - ProcessingFlow = { | ||
| 90 | - [1] = { | ||
| 91 | - ['Type'] = 'Property', | ||
| 92 | - ['Path'] = '/bmc/kepler/EventService/Subscriptions/Snmp', | ||
| 93 | - ['Interface'] = 'bmc.kepler.EventService.Subscriptions.Snmp', | ||
| 94 | - ['Source'] = { | ||
| 95 | - ['Enabled'] = '${Statements/Enabled()}', | ||
| 96 | - }, | ||
| 97 | - }, | ||
| 98 | - }, | ||
| 99 | - }, | ||
| 100 | - }, | ||
| 101 | - }, | ||
| 102 | - ['/snmp/1.3.6.1.4.1.2011.2.235.1.1.4.50/trapInfoDescriptionTable/Readwrite'] = { | ||
| 103 | - Sequence = { | ||
| 104 | - [1] = { | ||
| 105 | - ['Name'] = 'trapReceiverIndex', | ||
| 106 | - ['Type'] = 'integer', | ||
| 107 | - ['Access'] = 'Readonly', | ||
| 108 | - ['Primary'] = true, | ||
| 109 | - }, | ||
| 110 | - [2] = { | ||
| 111 | - ['Name'] = 'trapReceiverEnable', | ||
| 112 | - ['Type'] = 'integer', | ||
| 113 | - ['Access'] = 'Readwrite', | ||
| 114 | - }, | ||
| 115 | - [3] = { | ||
| 116 | - ['Name'] = 'trapReceiverAddress', | ||
| 117 | - ['Type'] = 'string', | ||
| 118 | - ['Access'] = 'Readwrite', | ||
| 119 | - }, | ||
| 120 | - [4] = { | ||
| 121 | - ['Name'] = 'trapReceiverPort', | ||
| 122 | - ['Type'] = 'integer', | ||
| 123 | - ['Access'] = 'Readwrite', | ||
| 124 | - }, | ||
| 125 | - [5] = { | ||
| 126 | - ['Name'] = 'trapSendType', | ||
| 127 | - ['Type'] = 'integer', | ||
| 128 | - ['Access'] = 'Readwrite', | ||
| 129 | - }, | ||
| 130 | - [6] = { | ||
| 131 | - ['Name'] = 'trapTest', | ||
| 132 | - ['Type'] = 'string', | ||
| 133 | - ['Access'] = 'Setonly', | ||
| 134 | - }, | ||
| 135 | - [7] = { | ||
| 136 | - ['Name'] = 'trapBobEnable', | ||
| 137 | - ['Type'] = 'integer', | ||
| 138 | - ['Access'] = 'Readwrite', | ||
| 139 | - }, | ||
| 140 | - }, | ||
| 141 | - methods = { | ||
| 142 | - get = { | ||
| 143 | - RspBody = { | ||
| 144 | - ['Sequence'] = '${Statements/TrapServer()}', | ||
| 145 | - }, | ||
| 146 | - Statements = { | ||
| 147 | - ['TrapServer'] = { | ||
| 148 | - ['Input'] = '${ProcessingFlow[1]/Destination/TrapServer}', | ||
| 149 | - ['Steps'] = { | ||
| 150 | - [1] = { | ||
| 151 | - ['Type'] = 'Expand', | ||
| 152 | - ['Formula'] = '1', | ||
| 153 | - }, | ||
| 154 | - }, | ||
| 155 | - }, | ||
| 156 | - }, | ||
| 157 | - ProcessingFlow = { | ||
| 158 | - [1] = { | ||
| 159 | - ['Type'] = 'List', | ||
| 160 | - ['Path'] = '/bmc/kepler/EventService/Subscriptions/Snmp/Nmses', | ||
| 161 | - ['Destination'] = { | ||
| 162 | - ['Members'] = 'TrapServer', | ||
| 163 | - }, | ||
| 164 | - }, | ||
| 165 | - }, | ||
| 166 | - }, | ||
| 167 | - patch = { | ||
| 168 | - ReqBody = { | ||
| 169 | - ['Type'] = 'object', | ||
| 170 | - ['Required'] = true, | ||
| 171 | - ['Properties'] = { | ||
| 172 | - ['trapReceiverIndex'] = { | ||
| 173 | - ['Type'] = 'integer', | ||
| 174 | - ['Required'] = true, | ||
| 175 | - ['Validator'] = { | ||
| 176 | - [1] = { | ||
| 177 | - ['Type'] = 'Range', | ||
| 178 | - ['Formula'] = { | ||
| 179 | - [1] = 1, | ||
| 180 | - [2] = 4, | ||
| 181 | - }, | ||
| 182 | - }, | ||
| 183 | - }, | ||
| 184 | - }, | ||
| 185 | - ['trapReceiverEnable'] = { | ||
| 186 | - ['Type'] = 'integer', | ||
| 187 | - ['Validator'] = { | ||
| 188 | - [1] = { | ||
| 189 | - ['Type'] = 'Enum', | ||
| 190 | - ['Formula'] = { | ||
| 191 | - [1] = 1, | ||
| 192 | - [2] = 2, | ||
| 193 | - }, | ||
| 194 | - }, | ||
| 195 | - }, | ||
| 196 | - }, | ||
| 197 | - ['trapReceiverAddress'] = { | ||
| 198 | - ['Type'] = 'string', | ||
| 199 | - }, | ||
| 200 | - ['trapReceiverPort'] = { | ||
| 201 | - ['Type'] = 'integer', | ||
| 202 | - ['Validator'] = { | ||
| 203 | - [1] = { | ||
| 204 | - ['Type'] = 'Range', | ||
| 205 | - ['Formula'] = { | ||
| 206 | - [1] = 1, | ||
| 207 | - [2] = 65535, | ||
| 208 | - }, | ||
| 209 | - }, | ||
| 210 | - }, | ||
| 211 | - }, | ||
| 212 | - ['trapSendType'] = { | ||
| 213 | - ['Type'] = 'integer', | ||
| 214 | - ['Validator'] = { | ||
| 215 | - [1] = { | ||
| 216 | - ['Type'] = 'Enum', | ||
| 217 | - ['Formula'] = { | ||
| 218 | - [1] = 1, | ||
| 219 | - }, | ||
| 220 | - }, | ||
| 221 | - }, | ||
| 222 | - }, | ||
| 223 | - ['trapTest'] = { | ||
| 224 | - ['Type'] = 'integer', | ||
| 225 | - ['Validator'] = { | ||
| 226 | - [1] = { | ||
| 227 | - ['Type'] = 'Enum', | ||
| 228 | - ['Formula'] = { | ||
| 229 | - [1] = 1, | ||
| 230 | - }, | ||
| 231 | - }, | ||
| 232 | - }, | ||
| 233 | - }, | ||
| 234 | - ['trapBobEnable'] = { | ||
| 235 | - ['Type'] = 'integer', | ||
| 236 | - ['Validator'] = { | ||
| 237 | - [1] = { | ||
| 238 | - ['Type'] = 'Enum', | ||
| 239 | - ['Formula'] = { | ||
| 240 | - [1] = 1, | ||
| 241 | - [2] = 2, | ||
| 242 | - }, | ||
| 243 | - }, | ||
| 244 | - }, | ||
| 245 | - }, | ||
| 246 | - }, | ||
| 247 | - }, | ||
| 248 | - Statements = { | ||
| 249 | - ['Enabled'] = { | ||
| 250 | - ['Input'] = '${ReqBody/trapReceiverEnable}', | ||
| 251 | - ['Steps'] = { | ||
| 252 | - [1] = { | ||
| 253 | - ['Type'] = 'Switch', | ||
| 254 | - ['Formula'] = { | ||
| 255 | - [1] = { | ||
| 256 | - ['Case'] = 1, | ||
| 257 | - ['To'] = false, | ||
| 258 | - }, | ||
| 259 | - [2] = { | ||
| 260 | - ['Case'] = 2, | ||
| 261 | - ['To'] = true, | ||
| 262 | - }, | ||
| 263 | - }, | ||
| 264 | - }, | ||
| 265 | - }, | ||
| 266 | - }, | ||
| 267 | - ['BobEnabled'] = { | ||
| 268 | - ['Input'] = '${ReqBody/trapBobEnable}', | ||
| 269 | - ['Steps'] = { | ||
| 270 | - [1] = { | ||
| 271 | - ['Type'] = 'Switch', | ||
| 272 | - ['Formula'] = { | ||
| 273 | - [1] = { | ||
| 274 | - ['Case'] = 1, | ||
| 275 | - ['To'] = false, | ||
| 276 | - }, | ||
| 277 | - [2] = { | ||
| 278 | - ['Case'] = 2, | ||
| 279 | - ['To'] = true, | ||
| 280 | - }, | ||
| 281 | - }, | ||
| 282 | - }, | ||
| 283 | - }, | ||
| 284 | - }, | ||
| 285 | - }, | ||
| 286 | - ProcessingFlow = { | ||
| 287 | - [1] = { | ||
| 288 | - ['Type'] = 'Property', | ||
| 289 | - ['Path'] = '/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/${ReqBody/trapReceiverIndex}', | ||
| 290 | - ['Interface'] = 'bmc.kepler.EventService.Subscriptions.Snmp.Nms', | ||
| 291 | - ['Source'] = { | ||
| 292 | - ['Enabled'] = '${Statements/Enabled()}', | ||
| 293 | - ['BobEnabled'] = '${Statements/BobEnabled()}', | ||
| 294 | - ['Address'] = '${ReqBody/trapReceiverAddress}', | ||
| 295 | - ['Port'] = '${ReqBody/trapReceiverPort}', | ||
| 296 | - }, | ||
| 297 | - }, | ||
| 298 | - [2] = { | ||
| 299 | - ['Type'] = 'Method', | ||
| 300 | - ['Path'] = '/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/${ReqBody/trapReceiverIndex}', | ||
| 301 | - ['Interface'] = 'bmc.kepler.EventService.Subscriptions.Snmp.Nms', | ||
| 302 | - ['Name'] = 'SubmitTestEvent', | ||
| 303 | - ['CallIf'] = '{"${ReqBody/trapTest}": "#WITH"}', | ||
| 304 | - }, | ||
| 305 | - }, | ||
| 306 | - }, | ||
| 307 | - }, | ||
| 308 | - }, | ||
| 309 | - ['/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/:id'] = { | ||
| 310 | - methods = { | ||
| 311 | - get = { | ||
| 312 | - RspBody = { | ||
| 313 | - ['trapReceiverIndex'] = '${Statements/MemberId()}', | ||
| 314 | - ['trapReceiverEnable'] = '${Statements/Enabled()}', | ||
| 315 | - ['trapBobEnable'] = '${Statements/BobEnabled()}', | ||
| 316 | - ['trapReceiverAddress'] = '${ProcessingFlow[1]/Destination/TrapServerAddress}', | ||
| 317 | - ['trapReceiverPort'] = '${ProcessingFlow[1]/Destination/TrapServerPort}', | ||
| 318 | - ['trapSendType'] = 1, | ||
| 319 | - ['trapTest'] = 'write-only', | ||
| 320 | - }, | ||
| 321 | - Statements = { | ||
| 322 | - ['MemberId'] = { | ||
| 323 | - ['Input'] = '${Uri/id}', | ||
| 324 | - ['Steps'] = { | ||
| 325 | - [1] = { | ||
| 326 | - ['Type'] = 'Convert', | ||
| 327 | - ['Formula'] = 'StringToNumber', | ||
| 328 | - }, | ||
| 329 | - }, | ||
| 330 | - }, | ||
| 331 | - ['Enabled'] = { | ||
| 332 | - ['Input'] = '${ProcessingFlow[1]/Destination/Enabled}', | ||
| 333 | - ['Steps'] = { | ||
| 334 | - [1] = { | ||
| 335 | - ['Type'] = 'Switch', | ||
| 336 | - ['Formula'] = { | ||
| 337 | - [1] = { | ||
| 338 | - ['Case'] = false, | ||
| 339 | - ['To'] = 1, | ||
| 340 | - }, | ||
| 341 | - [2] = { | ||
| 342 | - ['Case'] = true, | ||
| 343 | - ['To'] = 2, | ||
| 344 | - }, | ||
| 345 | - }, | ||
| 346 | - }, | ||
| 347 | - }, | ||
| 348 | - }, | ||
| 349 | - ['BobEnabled'] = { | ||
| 350 | - ['Input'] = '${ProcessingFlow[1]/Destination/BobEnabled}', | ||
| 351 | - ['Steps'] = { | ||
| 352 | - [1] = { | ||
| 353 | - ['Type'] = 'Switch', | ||
| 354 | - ['Formula'] = { | ||
| 355 | - [1] = { | ||
| 356 | - ['Case'] = false, | ||
| 357 | - ['To'] = 1, | ||
| 358 | - }, | ||
| 359 | - [2] = { | ||
| 360 | - ['Case'] = true, | ||
| 361 | - ['To'] = 2, | ||
| 362 | - }, | ||
| 363 | - }, | ||
| 364 | - }, | ||
| 365 | - }, | ||
| 366 | - }, | ||
| 367 | - }, | ||
| 368 | - ProcessingFlow = { | ||
| 369 | - [1] = { | ||
| 370 | - ['Type'] = 'Property', | ||
| 371 | - ['Path'] = '/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/${Uri/id}', | ||
| 372 | - ['Interface'] = 'bmc.kepler.EventService.Subscriptions.Snmp.Nms', | ||
| 373 | - ['Destination'] = { | ||
| 374 | - ['Enabled'] = 'Enabled', | ||
| 375 | - ['BobEnabled'] = 'BobEnabled', | ||
| 376 | - ['Address'] = 'TrapServerAddress', | ||
| 377 | - ['Port'] = 'TrapServerPort', | ||
| 378 | - }, | ||
| 379 | - }, | ||
| 380 | - }, | ||
| 381 | - }, | ||
| 382 | - }, | ||
| 383 | - }, | ||
| 384 | -} | ||
| @@ -1,27 +0,0 @@ | |||
| 1 | --- Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 2 | --- openUBMC is licensed under Mulan PSL v2. | ||
| 3 | --- You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 4 | --- You may obtain a copy of Mulan PSL v2 at: | ||
| 5 | --- http://license.coscl.org.cn/MulanPSL2 | ||
| 6 | --- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 7 | --- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 8 | --- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 9 | --- See the Mulan PSL v2 for more details. | ||
| 10 | - | ||
| 11 | -Input.data = { | ||
| 12 | - ['trap'] = { | ||
| 13 | - ['trap.json'] = { | ||
| 14 | - ['/snmp/1.3.6.1.4.1.2011.2.235.1.1.4.1/trapEnable/Readwrite'] = { | ||
| 15 | - ['get'] = true, | ||
| 16 | - ['patch'] = true, | ||
| 17 | - }, | ||
| 18 | - ['/snmp/1.3.6.1.4.1.2011.2.235.1.1.4.50/trapInfoDescriptionTable/Readwrite'] = { | ||
| 19 | - ['get'] = true, | ||
| 20 | - ['patch'] = true, | ||
| 21 | - }, | ||
| 22 | - ['/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/:id'] = { | ||
| 23 | - ['get'] = true, | ||
| 24 | - }, | ||
| 25 | - }, | ||
| 26 | - }, | ||
| 27 | -} | ||
| @@ -0,0 +1,227 @@ | |||
| 1 | +-- Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 2 | +-- openUBMC is licensed under Mulan PSL v2. | ||
| 3 | +-- You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 4 | +-- You may obtain a copy of Mulan PSL v2 at: | ||
| 5 | +-- http://license.coscl.org.cn/MulanPSL2 | ||
| 6 | +-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 7 | +-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 8 | +-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 9 | +-- See the Mulan PSL v2 for more details. | ||
| 10 | + | ||
| 11 | +-- 单元测试用 mock:模拟进程内路由映射器门面(routemapper.sync), | ||
| 12 | +-- 数据格式与编译后的路由/端点描述一致(list_routes/describe 的返回结构) | ||
| 13 | + | ||
| 14 | +local mock = {} | ||
| 15 | + | ||
| 16 | +local TRAP_ENABLE_URI = '/snmp/1.3.6.1.4.1.2011.2.235.1.1.4.1/trapEnable/Readwrite' | ||
| 17 | +local TRAP_TABLE_URI = '/snmp/1.3.6.1.4.1.2011.2.235.1.1.4.50/trapInfoDescriptionTable/Readwrite' | ||
| 18 | +local NMSES_URI = '/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/:id' | ||
| 19 | + | ||
| 20 | +local ROUTES = { | ||
| 21 | + { | ||
| 22 | + Uri = TRAP_ENABLE_URI, | ||
| 23 | + Methods = { 'GET', 'PATCH' }, | ||
| 24 | + IgnoreEtags = {}, | ||
| 25 | + }, | ||
| 26 | + { | ||
| 27 | + Uri = TRAP_TABLE_URI, | ||
| 28 | + Methods = { 'GET', 'PATCH' }, | ||
| 29 | + IgnoreEtags = {}, | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + Uri = NMSES_URI, | ||
| 33 | + Methods = { 'GET' }, | ||
| 34 | + IgnoreEtags = {}, | ||
| 35 | + }, | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +local TRAP_TABLE_SEQUENCE = { | ||
| 39 | + { Name = 'trapReceiverIndex', Type = 'integer', Access = 'Readonly', Primary = true }, | ||
| 40 | + { Name = 'trapReceiverEnable', Type = 'integer', Access = 'Readwrite', Primary = false }, | ||
| 41 | + { Name = 'trapReceiverAddress', Type = 'string', Access = 'Readwrite', Primary = false }, | ||
| 42 | + { Name = 'trapReceiverPort', Type = 'integer', Access = 'Readwrite', Primary = false }, | ||
| 43 | + { Name = 'trapSendType', Type = 'integer', Access = 'Readwrite', Primary = false }, | ||
| 44 | + { Name = 'trapTest', Type = 'string', Access = 'Setonly', Primary = false }, | ||
| 45 | + { Name = 'trapBobEnable', Type = 'integer', Access = 'Readwrite', Primary = false }, | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +local function base_description(uri, method, extra) | ||
| 49 | + local description = { | ||
| 50 | + Source = 'mock_mapping_config', | ||
| 51 | + RoutePattern = uri, | ||
| 52 | + Method = method, | ||
| 53 | + UriParameters = {}, | ||
| 54 | + Mapper = { Type = 'Ordinary' }, | ||
| 55 | + TaskReferenceUri = {}, | ||
| 56 | + Parameters = {}, | ||
| 57 | + RequestErrors = {}, | ||
| 58 | + ResponseFields = {}, | ||
| 59 | + IgnoreEtags = {}, | ||
| 60 | + Sequence = {}, | ||
| 61 | + DataSources = {}, | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + for key, value in pairs(extra or {}) do | ||
| 65 | + description[key] = value | ||
| 66 | + end | ||
| 67 | + | ||
| 68 | + return description | ||
| 69 | +end | ||
| 70 | + | ||
| 71 | +local DESCRIPTIONS = { | ||
| 72 | + [TRAP_ENABLE_URI] = { | ||
| 73 | + GET = base_description(TRAP_ENABLE_URI, 'GET', { | ||
| 74 | + ResponseFields = { { Name = 'Enabled', Default = 1 } }, | ||
| 75 | + }), | ||
| 76 | + PATCH = base_description(TRAP_ENABLE_URI, 'PATCH', { | ||
| 77 | + RequestSchema = { | ||
| 78 | + Type = 'object', | ||
| 79 | + Required = true, | ||
| 80 | + Properties = { | ||
| 81 | + Enabled = { | ||
| 82 | + Required = true, | ||
| 83 | + Type = 'integer', | ||
| 84 | + Validator = {}, | ||
| 85 | + }, | ||
| 86 | + }, | ||
| 87 | + }, | ||
| 88 | + }), | ||
| 89 | + }, | ||
| 90 | + [TRAP_TABLE_URI] = { | ||
| 91 | + GET = base_description(TRAP_TABLE_URI, 'GET', { | ||
| 92 | + Sequence = TRAP_TABLE_SEQUENCE, | ||
| 93 | + ResponseFields = { | ||
| 94 | + { Name = 'trapReceiverIndex', Default = 1 }, | ||
| 95 | + { Name = 'trapReceiverEnable', Default = 1 }, | ||
| 96 | + { Name = 'trapReceiverAddress', Default = '' }, | ||
| 97 | + { Name = 'trapReceiverPort', Default = 162 }, | ||
| 98 | + { Name = 'trapSendType', Default = 1 }, | ||
| 99 | + { Name = 'trapTest', Default = '' }, | ||
| 100 | + { Name = 'trapBobEnable', Default = 1 }, | ||
| 101 | + }, | ||
| 102 | + }), | ||
| 103 | + PATCH = base_description(TRAP_TABLE_URI, 'PATCH', { | ||
| 104 | + RequestSchema = { | ||
| 105 | + Type = 'object', | ||
| 106 | + Required = true, | ||
| 107 | + Properties = { | ||
| 108 | + trapReceiverIndex = { | ||
| 109 | + Required = true, | ||
| 110 | + Type = 'integer', | ||
| 111 | + Validator = {}, | ||
| 112 | + }, | ||
| 113 | + trapReceiverEnable = { | ||
| 114 | + Required = false, | ||
| 115 | + Type = 'integer', | ||
| 116 | + Validator = {}, | ||
| 117 | + }, | ||
| 118 | + trapReceiverAddress = { | ||
| 119 | + Required = false, | ||
| 120 | + Type = 'string', | ||
| 121 | + Validator = {}, | ||
| 122 | + }, | ||
| 123 | + trapReceiverPort = { | ||
| 124 | + Required = false, | ||
| 125 | + Type = 'integer', | ||
| 126 | + Validator = {}, | ||
| 127 | + }, | ||
| 128 | + trapSendType = { | ||
| 129 | + Required = false, | ||
| 130 | + Type = 'integer', | ||
| 131 | + Validator = {}, | ||
| 132 | + }, | ||
| 133 | + trapTest = { | ||
| 134 | + Required = false, | ||
| 135 | + Type = 'string', | ||
| 136 | + Validator = {}, | ||
| 137 | + }, | ||
| 138 | + trapBobEnable = { | ||
| 139 | + Required = false, | ||
| 140 | + Type = 'integer', | ||
| 141 | + Validator = {}, | ||
| 142 | + }, | ||
| 143 | + }, | ||
| 144 | + }, | ||
| 145 | + }), | ||
| 146 | + }, | ||
| 147 | + [NMSES_URI] = { | ||
| 148 | + GET = base_description(NMSES_URI, 'GET', { | ||
| 149 | + ResponseFields = { { Name = 'Nmses', Default = {} } }, | ||
| 150 | + }), | ||
| 151 | + }, | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +-- 模拟 core.so 的异步接口:*_async 返回 mc.future,take_ready 取结果 | ||
| 155 | +local function ready_future(value) | ||
| 156 | + return { | ||
| 157 | + is_ready = function() | ||
| 158 | + return true | ||
| 159 | + end, | ||
| 160 | + value = value, | ||
| 161 | + } | ||
| 162 | +end | ||
| 163 | + | ||
| 164 | +local mapper = {} | ||
| 165 | + | ||
| 166 | +function mapper:generation() | ||
| 167 | + return 1 | ||
| 168 | +end | ||
| 169 | + | ||
| 170 | +function mapper:list_routes() | ||
| 171 | + return ROUTES | ||
| 172 | +end | ||
| 173 | + | ||
| 174 | +function mapper:describe(method, uri) | ||
| 175 | + local route = DESCRIPTIONS[uri] | ||
| 176 | + if route then | ||
| 177 | + return route[string.upper(method)] | ||
| 178 | + end | ||
| 179 | + | ||
| 180 | + return nil | ||
| 181 | +end | ||
| 182 | + | ||
| 183 | +function mapper:describe_async(method, uri) | ||
| 184 | + return ready_future(self:describe(method, uri)) | ||
| 185 | +end | ||
| 186 | + | ||
| 187 | +function mapper:execute(method, uri) | ||
| 188 | + if not (DESCRIPTIONS[uri] and DESCRIPTIONS[uri][string.upper(method)]) then | ||
| 189 | + return { | ||
| 190 | + status = 404, | ||
| 191 | + body = {}, | ||
| 192 | + errors = { { Code = 'ResourceNotFound', Message = 'the mapped resource does not exist' } }, | ||
| 193 | + headers = {}, | ||
| 194 | + effects = {}, | ||
| 195 | + } | ||
| 196 | + end | ||
| 197 | + | ||
| 198 | + return { | ||
| 199 | + status = 200, | ||
| 200 | + body = {}, | ||
| 201 | + errors = {}, | ||
| 202 | + headers = {}, | ||
| 203 | + effects = {}, | ||
| 204 | + } | ||
| 205 | +end | ||
| 206 | + | ||
| 207 | +function mapper:execute_async(method, uri) | ||
| 208 | + return ready_future(self:execute(method, uri)) | ||
| 209 | +end | ||
| 210 | + | ||
| 211 | +function mapper:take_ready(future) | ||
| 212 | + return future.value | ||
| 213 | +end | ||
| 214 | + | ||
| 215 | +function mapper:reload() | ||
| 216 | + return 2 | ||
| 217 | +end | ||
| 218 | + | ||
| 219 | +function mock.load() | ||
| 220 | + return mapper | ||
| 221 | +end | ||
| 222 | + | ||
| 223 | +function mock.new() | ||
| 224 | + return mapper | ||
| 225 | +end | ||
| 226 | + | ||
| 227 | +return mock | ||
| @@ -10,13 +10,17 @@ | |||
| 10 | 10 | ||
| 11 | local lu = require('luaunit') | 11 | local lu = require('luaunit') |
| 12 | local json = require('cjson') | 12 | local json = require('cjson') |
| 13 | -local c_interface = require('interface') | ||
| 14 | 13 | ||
| 15 | -local account = require('route_mapper.account') | 14 | +-- mock 进程内路由映射器的底层门面(routemapper.sync), |
| 16 | -account.get_package_customer = function() | 15 | +-- 使 snmp_mapper 在单元测试环境无需真实路由映射器运行库; |
| 17 | - return '' | 16 | +-- 必须在 require('interface') 之前注入,因为 interface 的 require 链会加载 snmp_mapper |
| 17 | +local mock_routemapper = require('mock_routemapper') | ||
| 18 | +package.preload['routemapper.sync'] = function() | ||
| 19 | + return mock_routemapper | ||
| 18 | end | 20 | end |
| 19 | 21 | ||
| 22 | +local c_interface = require('interface') | ||
| 23 | + | ||
| 20 | local project_dir = os.getenv('PROJECT_DIR') | 24 | local project_dir = os.getenv('PROJECT_DIR') |
| 21 | local MAPPER_CONF_PATH <const> = project_dir .. '/test/unit/data/mapping_config' | 25 | local MAPPER_CONF_PATH <const> = project_dir .. '/test/unit/data/mapping_config' |
| 22 | local MDB_PATH_PREFIX <const> = '/bmc/kepler/' | 26 | local MDB_PATH_PREFIX <const> = '/bmc/kepler/' |