Add custom OEM inventory parsing for Lenovo ThinkServers
Parse OEM data for CPU, DIMM, PCI, drive and PSU inventory. Change-Id: Ic75d03fb1b05c13702321d174c8dbb9a96a08ae4
This commit is contained in:
parent
74f7c4d936
commit
ea3960c226
@ -64,6 +64,9 @@ def docommand(result, ipmisession):
|
||||
print repr(reading)
|
||||
elif cmmand == 'health':
|
||||
print repr(ipmisession.get_health())
|
||||
elif cmmand == 'inventory':
|
||||
for item in ipmisession.get_inventory():
|
||||
print repr(item)
|
||||
elif cmmand == 'raw':
|
||||
print ipmisession.raw_command(netfn=int(args[0]),
|
||||
command=int(args[1]),
|
||||
|
@ -1417,7 +1417,7 @@ class Command(object):
|
||||
channel = self.get_network_channel()
|
||||
names = {}
|
||||
max_ids = self.get_channel_max_user_count(channel)
|
||||
for uid in range(1, max_ids):
|
||||
for uid in range(1, max_ids+1):
|
||||
name = self.get_user_name(uid=uid)
|
||||
if name is not None:
|
||||
names[uid] = self.get_user(uid=uid, channel=channel)
|
||||
|
0
pyghmi/ipmi/oem/lenovo/__init__.py
Executable file
0
pyghmi/ipmi/oem/lenovo/__init__.py
Executable file
48
pyghmi/ipmi/oem/lenovo/cpu.py
Executable file
48
pyghmi/ipmi/oem/lenovo/cpu.py
Executable file
@ -0,0 +1,48 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyghmi.ipmi.oem.lenovo.inventory import EntryField, \
|
||||
parse_inventory_category_entry
|
||||
|
||||
cpu_fields = (
|
||||
EntryField("index", "B"),
|
||||
EntryField("Cores", "B"),
|
||||
EntryField("Threads", "B"),
|
||||
EntryField("Manufacturer", "13s"),
|
||||
EntryField("Family", "30s"),
|
||||
EntryField("Model", "30s"),
|
||||
EntryField("Stepping", "3s"),
|
||||
EntryField("Maximum Frequency", "<I",
|
||||
valuefunc=lambda v: str(v) + " MHz"),
|
||||
EntryField("Reserved", "h", include=False))
|
||||
|
||||
|
||||
def parse_cpu_info(raw):
|
||||
return parse_inventory_category_entry(raw, cpu_fields)
|
||||
|
||||
|
||||
def get_categories():
|
||||
return {
|
||||
"cpu": {
|
||||
"idstr": "CPU {0}",
|
||||
"parser": parse_cpu_info,
|
||||
"command": {
|
||||
"netfn": 0x06,
|
||||
"command": 0x59,
|
||||
"data": (0x00, 0xc1, 0x01, 0x00)
|
||||
}
|
||||
}
|
||||
}
|
52
pyghmi/ipmi/oem/lenovo/dimm.py
Executable file
52
pyghmi/ipmi/oem/lenovo/dimm.py
Executable file
@ -0,0 +1,52 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyghmi.ipmi.oem.lenovo.inventory import EntryField, \
|
||||
parse_inventory_category_entry
|
||||
|
||||
dimm_fields = (
|
||||
EntryField("index", "B"),
|
||||
EntryField("manufacture_location", "B"),
|
||||
EntryField("channel_number", "B"),
|
||||
EntryField("module_type", "10s"),
|
||||
EntryField("ddr_voltage", "10s"),
|
||||
EntryField("speed", "<h",
|
||||
valuefunc=lambda v: str(v) + " MHz"),
|
||||
EntryField("capacity_mb", "<h",
|
||||
valuefunc=lambda v: v*1024),
|
||||
EntryField("manufacturer", "30s"),
|
||||
EntryField("serial", "I"),
|
||||
EntryField("model", "21s"),
|
||||
EntryField("reserved", "h", include=False)
|
||||
)
|
||||
|
||||
|
||||
def parse_dimm_info(raw):
|
||||
return parse_inventory_category_entry(raw, dimm_fields)
|
||||
|
||||
|
||||
def get_categories():
|
||||
return {
|
||||
"dimm": {
|
||||
"idstr": "DIMM {0}",
|
||||
"parser": parse_dimm_info,
|
||||
"command": {
|
||||
"netfn": 0x06,
|
||||
"command": 0x59,
|
||||
"data": (0x00, 0xc1, 0x02, 0x00)
|
||||
}
|
||||
}
|
||||
}
|
73
pyghmi/ipmi/oem/lenovo/drive.py
Executable file
73
pyghmi/ipmi/oem/lenovo/drive.py
Executable file
@ -0,0 +1,73 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyghmi.ipmi.oem.lenovo.inventory import EntryField, \
|
||||
parse_inventory_category_entry
|
||||
|
||||
drive_fields = (
|
||||
EntryField("index", "B"),
|
||||
EntryField("VendorID", "64s"),
|
||||
EntryField("Size", "I",
|
||||
valuefunc=lambda v: str(v) + " MB"),
|
||||
EntryField("MediaType", "B", mapper={
|
||||
0x00: "HDD",
|
||||
0x01: "SSD"
|
||||
}),
|
||||
EntryField("InterfaceType", "B", mapper={
|
||||
0x00: "Unknown",
|
||||
0x01: "ParallelSCSI",
|
||||
0x02: "SAS",
|
||||
0x03: "SATA",
|
||||
0x04: "FC"
|
||||
}),
|
||||
EntryField("FormFactor", "B", mapper={
|
||||
0x00: "Unknown",
|
||||
0x01: "2.5in",
|
||||
0x02: "3.5in"
|
||||
}),
|
||||
EntryField("LinkSpeed", "B", mapper={
|
||||
0x00: "Unknown",
|
||||
0x01: "1.5 Gb/s",
|
||||
0x02: "3.0 Gb/s",
|
||||
0x03: "6.0 Gb/s",
|
||||
0x04: "12.0 Gb/s"
|
||||
}),
|
||||
EntryField("SlotNumber", "B"),
|
||||
EntryField("DeviceState", "B", mapper={
|
||||
0x00: "active",
|
||||
0x01: "stopped",
|
||||
0xff: "transitioning"
|
||||
}),
|
||||
# There seems to be an undocumented byte at the end
|
||||
EntryField("Reserved", "B", include=False))
|
||||
|
||||
|
||||
def parse_drive_info(raw):
|
||||
return parse_inventory_category_entry(raw, drive_fields)
|
||||
|
||||
|
||||
def get_categories():
|
||||
return {
|
||||
"drive": {
|
||||
"idstr": "Drive {0}",
|
||||
"parser": parse_drive_info,
|
||||
"command": {
|
||||
"netfn": 0x06,
|
||||
"command": 0x59,
|
||||
"data": (0x00, 0xc1, 0x04, 0x00)
|
||||
}
|
||||
}
|
||||
}
|
248
pyghmi/ipmi/oem/lenovo/handler.py
Executable file
248
pyghmi/ipmi/oem/lenovo/handler.py
Executable file
@ -0,0 +1,248 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import traceback
|
||||
|
||||
import pyghmi.constants as pygconst
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.ipmi.oem.generic as generic
|
||||
import pyghmi.ipmi.private.constants as ipmiconst
|
||||
import pyghmi.ipmi.private.util as util
|
||||
|
||||
from pyghmi.ipmi.oem.lenovo import cpu
|
||||
from pyghmi.ipmi.oem.lenovo import dimm
|
||||
from pyghmi.ipmi.oem.lenovo import drive
|
||||
from pyghmi.ipmi.oem.lenovo import inventory
|
||||
from pyghmi.ipmi.oem.lenovo import pci
|
||||
from pyghmi.ipmi.oem.lenovo import psu
|
||||
|
||||
inventory.register_inventory_category(cpu)
|
||||
inventory.register_inventory_category(dimm)
|
||||
inventory.register_inventory_category(pci)
|
||||
inventory.register_inventory_category(drive)
|
||||
inventory.register_inventory_category(psu)
|
||||
|
||||
|
||||
firmware_types = {
|
||||
1: 'Management Controller',
|
||||
2: 'UEFI/BIOS',
|
||||
3: 'CPLD',
|
||||
4: 'Power Supply',
|
||||
5: 'Storage Adapter',
|
||||
6: 'Add-in Adapter',
|
||||
}
|
||||
|
||||
firmware_event = {
|
||||
0: ('Update failed', pygconst.Health.Failed),
|
||||
1: ('Update succeeded', pygconst.Health.Ok),
|
||||
2: ('Update aborted', pygconst.Health.Ok),
|
||||
3: ('Unknown', pygconst.Health.Warning),
|
||||
}
|
||||
|
||||
me_status = {
|
||||
0: ('Recovery GPIO forced', pygconst.Health.Warning),
|
||||
1: ('ME Image corrupt', pygconst.Health.Critical),
|
||||
2: ('Flash erase error', pygconst.Health.Critical),
|
||||
3: ('Unspecified flash state', pygconst.Health.Warning),
|
||||
4: ('ME watchdog timeout', pygconst.Health.Critical),
|
||||
5: ('ME platform reboot', pygconst.Health.Critical),
|
||||
6: ('ME update', pygconst.Health.Ok),
|
||||
7: ('Manufacturing error', pygconst.Health.Critical),
|
||||
8: ('ME Flash storage integrity error', pygconst.Health.Critical),
|
||||
9: ('ME firmware exception', pygconst.Health.Critical), # event data 3..
|
||||
0xa: ('ME firmware worn', pygconst.Health.Warning),
|
||||
0xc: ('Invalid SCMP state', pygconst.Health.Warning),
|
||||
0xd: ('PECI over DMI failure', pygconst.Health.Warning),
|
||||
0xe: ('MCTP interface failure', pygconst.Health.Warning),
|
||||
0xf: ('Auto configuration completed', pygconst.Health.Ok),
|
||||
}
|
||||
|
||||
me_flash_status = {
|
||||
0: ('ME flash corrupted', pygconst.Health.Critical),
|
||||
1: ('ME flash erase limit reached', pygconst.Health.Critical),
|
||||
2: ('ME flash write limit reached', pygconst.Health.Critical),
|
||||
3: ('ME flash write enabled', pygconst.Health.Ok),
|
||||
}
|
||||
|
||||
|
||||
class OEMHandler(generic.OEMHandler):
|
||||
# noinspection PyUnusedLocal
|
||||
def __init__(self, oemid, ipmicmd):
|
||||
# will need to retain data to differentiate
|
||||
# variations. For example System X versus Thinkserver
|
||||
self.oemid = oemid
|
||||
self.ipmicmd = ipmicmd
|
||||
self.oem_inventory_info = None
|
||||
|
||||
def process_event(self, event, ipmicmd, seldata):
|
||||
if 'oemdata' in event:
|
||||
oemtype = seldata[2]
|
||||
oemdata = event['oemdata']
|
||||
if oemtype == 0xd0: # firmware update
|
||||
event['component'] = firmware_types.get(oemdata[0], None)
|
||||
event['component_type'] = ipmiconst.sensor_type_codes[0x2b]
|
||||
slotnumber = (oemdata[1] & 0b11111000) >> 3
|
||||
if slotnumber:
|
||||
event['component'] += ' {0}'.format(slotnumber)
|
||||
event['event'], event['severity'] = \
|
||||
firmware_event[oemdata[1] & 0b111]
|
||||
event['event_data'] = '{0}.{1}'.format(oemdata[2], oemdata[3])
|
||||
elif oemtype == 0xd1: # BIOS recovery
|
||||
event['severity'] = pygconst.Health.Warning
|
||||
event['component'] = 'BIOS/UEFI'
|
||||
event['component_type'] = ipmiconst.sensor_type_codes[0xf]
|
||||
status = oemdata[0]
|
||||
method = (status & 0b11110000) >> 4
|
||||
status = (status & 0b1111)
|
||||
if method == 1:
|
||||
event['event'] = 'Automatic recovery'
|
||||
elif method == 2:
|
||||
event['event'] = 'Manual recovery'
|
||||
if status == 0:
|
||||
event['event'] += '- Failed'
|
||||
event['severity'] = pygconst.Health.Failed
|
||||
if oemdata[1] == 0x1:
|
||||
event['event'] += '- BIOS recovery image not found'
|
||||
event['event_data'] = '{0}.{1}'.format(oemdata[2], oemdata[3])
|
||||
elif oemtype == 0xd2: # eMMC status
|
||||
if oemdata[0] == 1:
|
||||
event['component'] = 'eMMC'
|
||||
event['component_type'] = ipmiconst.sensor_type_codes[0xc]
|
||||
if oemdata[0] == 1:
|
||||
event['event'] = 'eMMC Format error'
|
||||
event['severity'] = pygconst.Health.Failed
|
||||
elif oemtype == 0xd3:
|
||||
if oemdata[0] == 1:
|
||||
event['event'] = 'User privilege modification'
|
||||
event['severity'] = pygconst.Health.Ok
|
||||
event['component'] = 'User Privilege'
|
||||
event['component_type'] = ipmiconst.sensor_type_codes[6]
|
||||
event['event_data'] = \
|
||||
'User {0} on channel {1} had privilege changed ' \
|
||||
'from {2} to {3}'.format(
|
||||
oemdata[2], oemdata[1], oemdata[3] & 0b1111,
|
||||
(oemdata[3] & 0b11110000) >> 4
|
||||
)
|
||||
else:
|
||||
event['event'] = 'OEM event: {0}'.format(
|
||||
' '.join(format(x, '02x') for x in event['oemdata']))
|
||||
del event['oemdata']
|
||||
return
|
||||
evdata = event['event_data_bytes']
|
||||
if event['event_type_byte'] == 0x75: # ME event
|
||||
event['component'] = 'ME Firmware'
|
||||
event['component_type'] = ipmiconst.sensor_type_codes[0xf]
|
||||
event['event'], event['severity'] = me_status.get(
|
||||
evdata[1], ('Unknown', pygconst.Health.Warning))
|
||||
if evdata[1] == 3:
|
||||
event['event'], event['severity'] = me_flash_status.get(
|
||||
evdata[2], ('Unknown state', pygconst.Health.Warning))
|
||||
elif evdata[1] == 9:
|
||||
event['event'] += ' (0x{0:2x})'.format(evdata[2])
|
||||
elif evdata[1] == 0xf and evdata[2] & 0b10000000:
|
||||
event['event'] = 'Auto configuration failed'
|
||||
event['severity'] = pygconst.Health.Critical
|
||||
# For HDD bay events, the event data 2 is the bay, modify
|
||||
# the description to be more specific
|
||||
if (event['event_type_byte'] == 0x6f and
|
||||
(evdata[0] & 0b11000000) == 0b10000000 and
|
||||
event['component_type_id'] == 13):
|
||||
event['component'] += ' {0}'.format(evdata[1] & 0b11111)
|
||||
|
||||
@property
|
||||
def has_tsm(self):
|
||||
"""True if this particular server have a TSM based service processor
|
||||
"""
|
||||
return (self.oemid['manufacturer_id'] == 19046 and
|
||||
self.oemid['device_id'] == 32)
|
||||
|
||||
def get_oem_inventory_descriptions(self):
|
||||
if self.has_tsm:
|
||||
# Thinkserver with TSM
|
||||
if not self.oem_inventory_info:
|
||||
self._collect_tsm_inventory()
|
||||
return iter(self.oem_inventory_info)
|
||||
return ()
|
||||
|
||||
def get_oem_inventory(self):
|
||||
if self.has_tsm:
|
||||
self._collect_tsm_inventory()
|
||||
for compname in self.oem_inventory_info:
|
||||
yield (compname, self.oem_inventory_info[compname])
|
||||
|
||||
def get_inventory_of_component(self, component):
|
||||
if self.has_tsm:
|
||||
self._collect_tsm_inventory()
|
||||
return self.oem_inventory_info.get(component, None)
|
||||
|
||||
def _collect_tsm_inventory(self):
|
||||
self.oem_inventory_info = {}
|
||||
for catid, catspec in inventory.categories.items():
|
||||
try:
|
||||
rsp = self.ipmicmd.xraw_command(**catspec["command"])
|
||||
except pygexc.IpmiException:
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
items = inventory.parse_inventory_category(catid, rsp)
|
||||
except Exception:
|
||||
# If we can't parse an inventory category, ignore it
|
||||
print traceback.print_exc()
|
||||
continue
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
key = catspec["idstr"].format(item["index"])
|
||||
del item["index"]
|
||||
self.oem_inventory_info[key] = item
|
||||
except Exception:
|
||||
# If we can't parse an inventory item, ignore it
|
||||
print traceback.print_exc()
|
||||
continue
|
||||
|
||||
def process_fru(self, fru):
|
||||
if fru is None:
|
||||
return fru
|
||||
if self.has_tsm:
|
||||
fru['oem_parser'] = 'lenovo'
|
||||
# Thinkserver lays out specific interpretation of the
|
||||
# board extra fields
|
||||
_, _, wwn1, wwn2, mac1, mac2 = fru['board_extra']
|
||||
if wwn1 not in ('0000000000000000', ''):
|
||||
fru['WWN 1'] = wwn1
|
||||
if wwn2 not in ('0000000000000000', ''):
|
||||
fru['WWN 2'] = wwn2
|
||||
if mac1 not in ('00:00:00:00:00:00', ''):
|
||||
fru['MAC Address 1'] = mac1
|
||||
if mac2 not in ('00:00:00:00:00:00', ''):
|
||||
fru['MAC Address 2'] = mac2
|
||||
try:
|
||||
# The product_extra field is UUID as the system would present
|
||||
# in DMI. This is different than the two UUIDs that
|
||||
# it returns for get device and get system uuid...
|
||||
byteguid = fru['product_extra'][0]
|
||||
# It can present itself as claiming to be ASCII when it
|
||||
# is actually raw hex. As a result it triggers the mechanism
|
||||
# to strip \x00 from the end of text strings. Work around this
|
||||
# by padding with \x00 to the right if less than 16 long
|
||||
byteguid.extend('\x00' * (16 - len(byteguid)))
|
||||
fru['UUID'] = util.decode_wireformat_uuid(byteguid)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
return fru
|
||||
else:
|
||||
fru['oem_parser'] = None
|
||||
return fru
|
124
pyghmi/ipmi/oem/lenovo/inventory.py
Executable file
124
pyghmi/ipmi/oem/lenovo/inventory.py
Executable file
@ -0,0 +1,124 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import struct
|
||||
|
||||
categories = {}
|
||||
|
||||
|
||||
def register_inventory_category(module):
|
||||
c = module.get_categories()
|
||||
for id in c:
|
||||
categories[id] = c[id]
|
||||
|
||||
|
||||
class EntryField(object):
|
||||
"""Store inventory field parsing options.
|
||||
|
||||
Represents an inventory field and its options for the custom requests to a
|
||||
ThinkServer's BMC.
|
||||
|
||||
:param name: the name of the field
|
||||
:param fmt: the format of the field (see struct module for details)
|
||||
:param include: whether to include the field in the parse output
|
||||
:param mapper: a dictionary mapping values to new values for the parse
|
||||
output
|
||||
:param valuefunc: a function to be called to change the value in the last
|
||||
step of the build process.
|
||||
"""
|
||||
def __init__(self, name, fmt, include=True, mapper=None, valuefunc=None,
|
||||
multivaluefunc=False):
|
||||
self.name = name
|
||||
self.fmt = fmt
|
||||
self.include = include
|
||||
self.mapper = mapper
|
||||
self.valuefunc = valuefunc
|
||||
self.multivaluefunc = multivaluefunc
|
||||
|
||||
|
||||
# General parameter parsing functions
|
||||
def parse_inventory_category(name, info):
|
||||
"""Parses every entry in an inventory category (CPU, memory, PCI, drives,
|
||||
etc).
|
||||
|
||||
Expects the first byte to be a count of the number of entries, followed
|
||||
by a list of elements to be parsed by a dedicated parser (below).
|
||||
|
||||
:param name: the name of the parameter (e.g.: "cpu")
|
||||
:param info: a list of integers with raw data read from an IPMI requests
|
||||
|
||||
:returns: dict -- a list of entries in the category.
|
||||
"""
|
||||
raw = info["data"][1:]
|
||||
|
||||
cur = 0
|
||||
count = struct.unpack("B", raw[cur])[0]
|
||||
cur += 1
|
||||
|
||||
entries = []
|
||||
while cur < len(raw):
|
||||
read, cpu = categories[name]["parser"](raw[cur:])
|
||||
cur = cur + read
|
||||
entries.append(cpu)
|
||||
|
||||
# TODO(avidal): raise specific exception to point that there's data left in
|
||||
# the buffer
|
||||
if cur != len(raw):
|
||||
raise Exception
|
||||
# TODO(avidal): raise specific exception to point that the number of
|
||||
# entries is different than the expected
|
||||
if count != len(entries):
|
||||
raise Exception
|
||||
return entries
|
||||
|
||||
|
||||
def parse_inventory_category_entry(raw, fields):
|
||||
"""Parses one entry in an inventory category.
|
||||
|
||||
:param raw: the raw data to the entry. May contain more than one entry,
|
||||
only one entry will be read in that case.
|
||||
:param fields: an iterable of EntryField objects to be used for parsing the
|
||||
entry.
|
||||
|
||||
:returns: dict -- a tuple with the number of bytes read and a dictionary
|
||||
representing the entry.
|
||||
"""
|
||||
r = raw
|
||||
|
||||
obj = {}
|
||||
bytes_read = 0
|
||||
for field in fields:
|
||||
value = struct.unpack_from(field.fmt, r)[0]
|
||||
read = struct.calcsize(field.fmt)
|
||||
bytes_read += read
|
||||
r = r[read:]
|
||||
if not field.include:
|
||||
continue
|
||||
|
||||
if (field.fmt[-1] == "s"):
|
||||
value = value.rstrip("\x00")
|
||||
if (field.mapper and value in field.mapper):
|
||||
value = field.mapper[value]
|
||||
if (field.valuefunc):
|
||||
value = field.valuefunc(value)
|
||||
|
||||
if not field.multivaluefunc:
|
||||
obj[field.name] = value
|
||||
else:
|
||||
for key in value:
|
||||
obj[key] = value[key]
|
||||
|
||||
return bytes_read, obj
|
64
pyghmi/ipmi/oem/lenovo/pci.py
Executable file
64
pyghmi/ipmi/oem/lenovo/pci.py
Executable file
@ -0,0 +1,64 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyghmi.ipmi.oem.lenovo.inventory import EntryField, \
|
||||
parse_inventory_category_entry
|
||||
|
||||
pci_fields = (
|
||||
EntryField("index", "B"),
|
||||
EntryField("PCIType", "B", mapper={
|
||||
0x0: "On board slot",
|
||||
0x1: "Riser Type 1",
|
||||
0x2: "Riser Type 2",
|
||||
0x3: "Riser Type 3",
|
||||
0x4: "Riser Type 4",
|
||||
0x5: "Riser Type 5",
|
||||
0x6: "Riser Type 6a",
|
||||
0x7: "Riser Type 6b",
|
||||
0x8: "ROC",
|
||||
0x9: "Mezz"
|
||||
}),
|
||||
EntryField("BusNumber", "B"),
|
||||
EntryField("DeviceFunction", "B"),
|
||||
EntryField("VendorID", "<H"),
|
||||
EntryField("DeviceID", "<H"),
|
||||
EntryField("SubSystemVendorID", "<H"),
|
||||
EntryField("SubSystemID", "<H"),
|
||||
EntryField("InterfaceType", "B"),
|
||||
EntryField("SubClassCode", "B"),
|
||||
EntryField("BaseClassCode", "B"),
|
||||
EntryField("LinkSpeed", "B"),
|
||||
EntryField("LinkWidth", "B"),
|
||||
EntryField("Reserved", "h")
|
||||
)
|
||||
|
||||
|
||||
def parse_pci_info(raw):
|
||||
return parse_inventory_category_entry(raw, pci_fields)
|
||||
|
||||
|
||||
def get_categories():
|
||||
return {
|
||||
"pci": {
|
||||
"idstr": "PCI {0}",
|
||||
"parser": parse_pci_info,
|
||||
"command": {
|
||||
"netfn": 0x06,
|
||||
"command": 0x59,
|
||||
"data": (0x00, 0xc1, 0x03, 0x00)
|
||||
}
|
||||
}
|
||||
}
|
123
pyghmi/ipmi/oem/lenovo/psu.py
Executable file
123
pyghmi/ipmi/oem/lenovo/psu.py
Executable file
@ -0,0 +1,123 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyghmi.ipmi.oem.lenovo.inventory import EntryField, \
|
||||
parse_inventory_category_entry
|
||||
|
||||
psu_type = {
|
||||
0b0001: "Other",
|
||||
0b0010: "Unknown",
|
||||
0b0011: "Linear",
|
||||
0b0100: "Switching",
|
||||
0b0101: "Battery",
|
||||
0b0110: "UPS",
|
||||
0b0111: "Converter",
|
||||
0b1000: "Regulator",
|
||||
}
|
||||
psu_status = {
|
||||
0b001: "Other",
|
||||
0b010: "Unknown",
|
||||
0b011: "OK",
|
||||
0b100: "Non-critical",
|
||||
0b101: "Critical; power supply has failed and has been taken off-line"
|
||||
}
|
||||
psu_voltage_range_switch = {
|
||||
0b0001: "Other",
|
||||
0b0010: "Unknown",
|
||||
0b0011: "Manual",
|
||||
0b0100: "Auto-switch",
|
||||
0b0101: "Wide range",
|
||||
0b0110: "Not applicable"
|
||||
}
|
||||
|
||||
|
||||
def psu_status_word_slice(w, s, e):
|
||||
return int(w[-e-1:-s], 2)
|
||||
|
||||
|
||||
def psu_status_word_bit(w, b):
|
||||
return int(w[-b-1])
|
||||
|
||||
|
||||
def psu_status_word_parser(word):
|
||||
fields = {}
|
||||
word = "{0:016b}".format(word)
|
||||
|
||||
fields["DMTF Power Supply Type"] = \
|
||||
psu_type.get(psu_status_word_slice(word, 10, 13), "Invalid")
|
||||
|
||||
# fields["Status"] = \
|
||||
# psu_status.get(psu_status_word_slice(word, 7, 9), "Invalid")
|
||||
|
||||
fields["DMTF Input Voltage Range"] = \
|
||||
psu_voltage_range_switch.get(
|
||||
psu_status_word_slice(word, 3, 6),
|
||||
"Invalid"
|
||||
)
|
||||
|
||||
# Power supply is unplugged from the wall
|
||||
fields["Unplugged"] = \
|
||||
bool(psu_status_word_bit(word, 2))
|
||||
|
||||
# fields["Power supply is present"] = \
|
||||
# bool(psu_status_word_bit(word, 1))
|
||||
|
||||
# Power supply is hot-replaceable
|
||||
fields["Hot Replaceable"] = \
|
||||
bool(psu_status_word_bit(word, 0))
|
||||
|
||||
return fields
|
||||
|
||||
psu_fields = (
|
||||
EntryField("index", "B"),
|
||||
EntryField("Presence State", "B", include=False),
|
||||
EntryField("Capacity W", "<H"),
|
||||
EntryField("Board manufacturer", "18s"),
|
||||
EntryField("Board model", "18s"),
|
||||
EntryField("Board manufacture date", "10s"),
|
||||
EntryField("Board serial number", "34s"),
|
||||
EntryField("Board manufacturer revision", "5s"),
|
||||
EntryField("Board product name", "10s"),
|
||||
EntryField("PSU Asset Tag", "10s"),
|
||||
EntryField(
|
||||
"PSU Redundancy Status",
|
||||
"B",
|
||||
valuefunc=lambda v: "Not redundant" if v == 0x00 else "Redundant"
|
||||
),
|
||||
EntryField(
|
||||
"PSU Status Word",
|
||||
"<H",
|
||||
valuefunc=psu_status_word_parser, multivaluefunc=True
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def parse_psu_info(raw):
|
||||
return parse_inventory_category_entry(raw, psu_fields)
|
||||
|
||||
|
||||
def get_categories():
|
||||
return {
|
||||
"psu": {
|
||||
"idstr": "Power Supply {0}",
|
||||
"parser": parse_psu_info,
|
||||
"command": {
|
||||
"netfn": 0x06,
|
||||
"command": 0x59,
|
||||
"data": (0x00, 0xc6, 0x00, 0x00)
|
||||
}
|
||||
}
|
||||
}
|
2
pyghmi/ipmi/oem/lookup.py
Normal file → Executable file
2
pyghmi/ipmi/oem/lookup.py
Normal file → Executable file
@ -13,7 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import pyghmi.ipmi.oem.generic as generic
|
||||
import pyghmi.ipmi.oem.lenovo as lenovo
|
||||
import pyghmi.ipmi.oem.lenovo.handler as lenovo
|
||||
|
||||
# The mapping comes from
|
||||
# http://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
|
||||
|
Loading…
Reference in New Issue
Block a user