local log = require('mc.logging')
local cjson = require('cjson')
local mdb_service = require('mc.mdb.mdb_service')
local gsub = string.gsub
local utils = require('mc.utils')
local utils_core = require('utils.core')
local file_sec = require('utils.file')
local safe_close = utils.safe_close_file
local skynet = require('skynet')
local m = {}
function m.trim(s)
return gsub(gsub(s, '^%s+', ''), '%s+$', '')
end
function m.get_valid_string_property(val)
if val == '' or val == 'N/A' or val == 'NA' or val == 'Unknown' or val == 'null' then
return null
end
return val
end
function m.is_valid_managers_id(id)
local path = '/bmc/kepler/Managers/' .. id
local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
if not ok then
log:error('is_valid_managers_id failed, err(%s)', rsp.message)
error(rsp)
end
return rsp.Result
end
function m.is_valid_certificate_id(manager_id, cert_id, certificates_list)
local path
if not certificates_list or certificates_list == nil then
return false
end
if cert_id == 'InterChassisAuthCertificate' then
local tmp_cert_id
if #certificates_list == 0 then
return false
end
for _, v in ipairs(certificates_list) do
tmp_cert_id = tonumber(string.match(v, 'InterChassisAuth/Certificates/(%d+)'))
if tmp_cert_id == 2 then
break
end
end
path = '/bmc/kepler/Managers/' .. manager_id .. '/InterChassisAuth/Certificates/' .. tmp_cert_id
else
path = '/bmc/kepler/Managers/' .. manager_id .. '/NetworkProtocol/HTTPS/Certificates/' .. cert_id
end
local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
if not ok then
log:error('is_valid_managers_id failed, err(%s)', rsp.message)
error(rsp)
end
return rsp.Result
end
function m.is_valid_system_id(id)
local path = '/bmc/kepler/Systems/' .. id
local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
if not ok then
log:error('Invalid SystemId(%s), err(%s)', id, rsp.message)
error(rsp)
end
return rsp.Result
end
function m.get_bios(id)
local path = '/bmc/kepler/Systems/' .. id .. '/Bios'
local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
if not ok or not rsp.Result then
return lua_nil
end
local res = {}
res['@odata.id'] = '/redfish/v1/Systems/' .. id .. '/Bios'
return res
end
function m.is_valid_chassis_id(id)
local path = '/bmc/kepler/Chassis/' .. id
local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
if not ok then
log:error('Invalid Chassis(%s), err(%s)', id, rsp.message)
error(rsp)
end
return rsp.Result
end
function m.is_valid_ssu_chassis_id(id)
local dev_path = '/bmc/dev'
local dev_interface = 'bmc.dev.Chassis'
local ok, rsp = pcall(mdb_service.get_sub_objects, bus, dev_path, 4, { dev_interface })
if not ok then
log:error('Invalid Chassis(%s), err(%s)', id, rsp.message)
error(rsp)
end
local expansion_objs = rsp.SubObjects
for path, v in pairs(expansion_objs) do
ok, rsp = pcall(mdb.get_object, bus, path, dev_interface)
if not ok then
goto continue
end
if rsp.Id == 'SSU' .. id then
return true
end
::continue::
end
return false
end
function m.is_valid_path(path, ignore_case)
local ok, rsp = pcall(mdb_service.is_valid_path, bus, path, ignore_case or false)
if not ok then
log:info('Invalid Path, err(%s)', rsp.message)
error(rsp)
end
return rsp.Result
end
function m.cstr_to_luastr(cstr)
local s = cstr or ''
return string.match(s, '(.-)\0') or s
end
function m.is_path_exist(interface, querys)
local params = { interface, cjson.encode(querys), true }
local ok, rsp = pcall(mdb_service.get_path, bus, table.unpack(params))
if not ok then
return false
end
return rsp.Path ~= ''
end
function m.is_valid_board_id(paths, id)
for _, v in ipairs(paths) do
local ok, obj = pcall(mdb.get_object, bus, v, 'bmc.kepler.Systems.Board')
if not ok then
return false
end
local nodeid = gsub(obj.NodeId, '%s+', '')
if string.lower(nodeid) == string.lower(id) then
return true
end
end
return false
end
function m.get_status(health, state, is_oem)
if health == 255 then
return null
end
local status = {}
status.State = state or null
local health_severity = {
[0] = { 'OK', 'Informational' },
[1] = { 'Warning', 'Minor' },
[2] = { 'Warning', 'Major' },
[3] = { 'Critical', 'Critical' },
}
local hs = health_severity[health]
status.Health = hs and hs[1] or null
if is_oem then
status.Oem = {}
status.Oem['{{OemIdentifier}}'] = {}
status.Oem['{{OemIdentifier}}'].Severity = hs and hs[2] or null
else
status.Severity = hs and hs[2] or null
end
return status
end
function m.get_associated_resource(protocol, resource_id)
if protocol ~= 6 then
return null
end
return string.format('CPU%s', resource_id)
end
function m.is_valid_bios_attr_id(id)
id = string.lower(id)
local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Bios', 'bmc.kepler.Systems.Bios')
local version = obj.RegistryVersion
local a, b, c = string.match(version, 'V(%d).(%d)(%d)')
if a ~= nil and b ~= nil and c ~= nil then
return id == string.format('v%s_%s_%s', a, b, c)
else
return false
end
end
function m.is_valid_bios_attrjson_id(id)
id = string.lower(id)
local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Bios', 'bmc.kepler.Systems.Bios')
local version = obj.RegistryVersion
local a, b, c = string.match(version, 'V(%d).(%d)(%d)')
if a ~= nil and b ~= nil and c ~= nil then
return id == string.format('v%s_%s_%s.json', a, b, c)
else
return false
end
end
function m.is_valid_event_id(id)
id = string.lower(id)
local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Events', 'bmc.kepler.Systems.Events')
local major_version = obj.MajorVersion
local minor_version = obj.MinorVersion
local aux_version = obj.AuxVersion
if major_version and minor_version and aux_version then
return id == string.format('v%s_%s_%s', major_version, minor_version, aux_version)
else
return false
end
end
function m.is_valid_event_message_id(id)
local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Events', 'bmc.kepler.Systems.Events')
local major_version = obj.MajorVersion
local minor_version = obj.MinorVersion
local aux_version = obj.AuxVersion
if major_version and minor_version and aux_version then
return id == string.format('v%s_%s_%s.json', major_version, minor_version, aux_version)
else
return false
end
end
function m.unique_array(arr)
local set = {}
local res = {}
for _, v in ipairs(arr) do
if not set[v] then
res[#res + 1] = v
set[v] = true
end
end
return res
end
function m.split_string(str, delimiter)
return utils.split(str, delimiter)
end
function m.flatten_2d_table(t)
local ok, ret = pcall(function()
local ret = {}
for _, v in ipairs(t) do
for _, vv in ipairs(v) do
ret[#ret + 1] = vv
end
end
return ret
end)
if not ok then
log:error('Flatten table failed, err=%s', tostring(ret))
return {}
end
return ret
end
function m.get_valid_lane(val)
if val == '' or val == 'N/A' or val == 'NA' or val == 'Unknown' or val == 'null' then
return null
end
return tonumber(string.sub(val, 2))
end
function m.ends_with(str, ending)
return str:sub(-#ending) == ending
end
function m.get_res_file_content(res_path, file_type)
res_path = string.lower(res_path)
if not utils_core.is_file(res_path) then
return
end
local file = file_sec.open_s(res_path, 'r')
if not file then
return
end
local content = safe_close(file, function()
return file:read('*all')
end)
if file_type ~= 'json' then
return content
end
local is_ok, json_obj = pcall(cjson.json_object_ordered_decode, content)
if not is_ok then
json_obj = cjson.json_object_new_object()
end
return json_obj
end
function m.get_schema_file(file_name, file_type)
file_name = string.lower(file_name)
file_type = string.lower(file_type)
local file_dir = 'default_dir'
if file_type == 'xml' then
file_dir = 'csdl'
elseif file_type == 'json' then
file_dir = 'json_schema'
end
local RESOURCE_PREFIX = '/data/opt/bmc/redfish/static_resource'
local STANDARD_PREFIX = '/redfish/v1/schemastore/en/dmtf/' .. file_dir
local CUSTOM_PREFIX = '/redfish/v1/schemastore/en/oem/openubmc/' .. file_dir
local res_path = RESOURCE_PREFIX .. STANDARD_PREFIX .. '/' .. file_name
if utils_core.is_file(res_path) then
return m.get_res_file_content(res_path, file_type)
else
res_path = RESOURCE_PREFIX .. CUSTOM_PREFIX .. '/' .. file_name
if utils_core.is_file(res_path) then
return m.get_res_file_content(res_path, file_type)
end
end
end
function m.get_content_from_tpl(path, file_type)
local RESOURCE_PREFIX = skynet.getenv('RESOURCE_PREFIX') or '/opt/bmc/apps/redfish/interface_config/static_resource'
local RESOURCE_SUFFIX = file_type == 'json' and '/index.json' or '/index.xml'
path = string.lower(path)
local suffix = m.ends_with(path, '.json') and '' or RESOURCE_SUFFIX
local res_path = RESOURCE_PREFIX .. path .. suffix
return m.get_res_file_content(res_path, file_type)
end
function m.get_xml_content(path)
path = path:lower()
if path:find('%$metadata') then
path = path:gsub('%$', '')
end
return m.get_content_from_tpl(path, 'xml')
end
function m.get_json_content(path, pattern)
local content = m.get_content_from_tpl(path, 'json')
if pattern and not content then
local pattern_tmp = string.gsub(pattern, ':([a-z]+)', 'template')
content = m.get_content_from_tpl(pattern_tmp, 'json')
if not content then
pattern_tmp = string.gsub(pattern_tmp, 'template', '1', 1)
content = m.get_content_from_tpl(pattern_tmp, 'json')
end
end
return content or cjson.json_object_new_object()
end
local OP_MAP = {
['+'] = function(a, b)
return a - b
end,
['-'] = function(a, b)
return a + b
end,
}
local LOCAL_OP_MAP = {
['-'] = function(a, b)
return a - b
end,
['+'] = function(a, b)
return a + b
end,
}
function m.str2timestamp(date_time, local_offset)
if not date_time or #date_time == 0 then
log:error('Input data cannot be empty.')
return 0
end
local ok, y, mon, d, _hour, _min, _sec, op, h, _m = pcall(function()
return string.match(date_time, '(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)(.)(%d+):(%d+)')
end)
if not ok then
log:error('The format of the input data is incorrect.')
return 0
end
local timestamp = utils.get_timestamp(y, mon, d, _hour, _min, _sec)
local diff_time = h * 3600 + _m * 60
timestamp = OP_MAP[op](timestamp, diff_time)
if not local_offset or #local_offset == 0 or local_offset == cjson.null then
return timestamp
end
ok, op, h, _m = pcall(string.match, local_offset, '(.)(%d+):(%d+)')
if not ok then
log:error('The format of the local_offset is incorrect.')
return 0
end
diff_time = h * 3600 + _m * 60
return LOCAL_OP_MAP[op](timestamp, diff_time)
end
function m.get_registry_prefix()
return custom_messages.RegistryPrefix
end
function m.get_min_pcie_type(pcie_type1, pcie_type2)
if string.match(pcie_type1, '^Gen[1-6]$') == nil or string.match(pcie_type2, '^Gen[1-6]$') == nil then
return null
elseif pcie_type1 < pcie_type2 then
return pcie_type1
else
return pcie_type2
end
end
function m.iso8601_duration_to_seconds(duration_str)
local date_part = { years = 0, months = 0, days = 0 }
local time_part = { hours = 0, minutes = 0, seconds = 0 }
local date_str, time_str = duration_str:match('^P([^T]*)T?(.*)$')
if not date_str then
date_str = duration_str:sub(2)
time_str = ''
end
for unit, value in date_str:gmatch('(%d+)([YMD])') do
unit = tonumber(unit)
if value == 'Y' then
date_part.years = unit
elseif value == 'M' then
date_part.months = unit
elseif value == 'D' then
date_part.days = unit
end
end
if time_str and time_str ~= '' then
for key, value in time_str:gmatch('(%d+)([HMS])') do
local num = tonumber(key)
if value == 'H' then
time_part.hours = num
elseif value == 'M' then
time_part.minutes = num
elseif value == 'S' then
time_part.seconds = num
end
end
end
local date_seconds = date_part.years * 365 * 24 * 60 * 60
date_seconds = date_seconds + date_part.months * 30 * 24 * 60 * 60
date_seconds = date_seconds + date_part.days * 24 * 60 * 60
local time_seconds = time_part.hours * 60 * 60
time_seconds = time_seconds + time_part.minutes * 60
time_seconds = time_seconds + time_part.seconds
local total_seconds = date_seconds + time_seconds
if total_seconds > 28800 then
local err = custom_messages.PropertyValueOutOfRange(duration_str, 'EjectTimeout')
err.RelatedProperties = { '#/EjectTimeout' }
error(err)
end
return total_seconds
end
function m.seconds_to_iso8601_duration(seconds)
if type(seconds) ~= 'number' or seconds < 0 then
return 'null'
end
local remaining = seconds
local years = math.floor(remaining / (365 * 24 * 60 * 60))
remaining = remaining % (365 * 24 * 60 * 60)
local months = math.floor(remaining / (30 * 24 * 60 * 60))
remaining = remaining % (30 * 24 * 60 * 60)
local days = math.floor(remaining / (24 * 60 * 60))
remaining = remaining % (24 * 60 * 60)
local hours = math.floor(remaining / (60 * 60))
remaining = remaining % (60 * 60)
local minutes = math.floor(remaining / 60)
remaining = remaining % 60
local secs = remaining
local parts = {}
if years > 0 then
table.insert(parts, years .. 'Y')
end
if months > 0 then
table.insert(parts, months .. 'M')
end
if days > 0 then
table.insert(parts, days .. 'D')
end
local time_parts = {}
if hours > 0 then
table.insert(time_parts, hours .. 'H')
end
if minutes > 0 then
table.insert(time_parts, minutes .. 'M')
end
if secs > 0 or (#time_parts == 0 and #parts > 0) then
table.insert(time_parts, secs .. 'S')
end
if #time_parts > 0 then
table.insert(parts, 'T')
for _, part in ipairs(time_parts) do
table.insert(parts, part)
end
end
if #parts == 0 then
return 'PT0S'
end
return 'P' .. table.concat(parts)
end
function m.check_any_property_is_exsit(properties)
if not properties or properties == cjson.null or #properties == 0 then
return false
end
for _, prop in ipairs(properties) do
if prop ~= cjson.null then
return true
end
end
return false
end
function m.check_file_exist(file_name, file_type)
file_name = string.lower(file_name)
file_type = string.lower(file_type)
local RESOURCE_PREFIX = '/data/opt/bmc/redfish/static_resource'
local base_paths = {
RESOURCE_PREFIX .. '/redfish/v1/schemastore/en/dmtf/' .. file_type,
RESOURCE_PREFIX .. '/redfish/v1/schemastore/en/oem/openubmc/' .. file_type,
}
for _, path_prefix in ipairs(base_paths) do
local full_path = path_prefix .. '/' .. file_name
local res = utils_core.is_file(full_path)
if res == true then
return true
end
end
return false
end
function m.is_has_active_and_standby(local_state, remote_id_paths)
local has_active = false
local has_standby = false
if local_state == 1 then
has_active = true
elseif local_state == 0 then
has_standby = true
end
for _, remote_id_path in ipairs(remote_id_paths) do
local ok, obj = pcall(mdb.get_object, bus, remote_id_path, 'bmc.kepler.Managers.ActiveStandby.Status')
if ok then
local state = obj.ActiveStatus
if state == 1 then
has_active = true
elseif state == 0 then
has_standby = true
end
end
end
return (has_active and has_standby)
end
return m