diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index 506d4db9..bee7f43a 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -22,6 +22,7 @@ import pyghmi.exceptions as exc import pyghmi.ipmi.fru as fru from pyghmi.ipmi.oem.lookup import get_oem_handler from pyghmi.ipmi.private import session +import pyghmi.ipmi.private.util as pygutil import pyghmi.ipmi.sdr as sdr import struct @@ -384,7 +385,7 @@ class Command(object): """ self.oem_init() if component == 'System': - return self._oem.process_fru(fru.FRU(ipmicmd=self).info) + return self._get_zero_fru() if self._sdr is None: self._sdr = sdr.SDR(self) for fruid in self._sdr.fru: @@ -392,6 +393,23 @@ class Command(object): return self._oem.process_fru(fru.FRU( ipmicmd=self, fruid=fruid, sdr=self._sdr.fru[fruid]).info) + def _get_zero_fru(self): + # It is expected that a manufacturer matches SMBIOS to IPMI + # get system uuid return data. If a manufacturer does not + # do so, they should handle either deletion or fixup in the + # OEM processing pass. Code optimistically assumes that if + # data is returned, than the vendor is properly using it. + zerofru = fru.FRU(ipmicmd=self).info + if zerofru is None: + zerofru = {} + guiddata = self.raw_command(netfn=6, command=0x37) + if 'error' not in guiddata: + zerofru['UUID'] = pygutil.decode_wireformat_uuid( + guiddata['data']) + if not zerofru: + zerofru = None + return self._oem.process_fru(zerofru) + def get_inventory(self): """Retrieve inventory of system @@ -403,10 +421,7 @@ class Command(object): or None for items not present. """ self.oem_init() - zerofru = fru.FRU(ipmicmd=self).info - if zerofru is not None: - zerofru = self._oem.process_fru(zerofru) - yield ("System", zerofru) + yield ("System", self._get_zero_fru()) if self._sdr is None: self._sdr = sdr.SDR(self) for fruid in self._sdr.fru: diff --git a/pyghmi/ipmi/fru.py b/pyghmi/ipmi/fru.py index 9823b837..6b6257ee 100644 --- a/pyghmi/ipmi/fru.py +++ b/pyghmi/ipmi/fru.py @@ -221,7 +221,11 @@ class FRU(object): retinfo = retinfo.decode('utf-16le') except UnicodeDecodeError: pass - retinfo = retinfo.replace('\x00', '').strip() + # Some things lie about being text. Do the best we can by + # removing trailing spaces and nulls like makes sense for text + # and rely on vendors to workaround deviations in their OEM + # module + retinfo = retinfo.rstrip('\x00 ') return retinfo, newoffset elif currtype == 1: # BCD 'plus' retdata = '' @@ -232,8 +236,7 @@ class FRU(object): retdata = retdata.strip() return retdata, newoffset elif currtype == 2: # 6-bit ascii - retinfo = unpack6bitascii(retinfo) - retinfo = retinfo.strip() + retinfo = unpack6bitascii(retinfo).strip() return retinfo, newoffset def _parse_chassis(self): diff --git a/pyghmi/ipmi/oem/generic.py b/pyghmi/ipmi/oem/generic.py index 9603517d..d0b6ffb6 100644 --- a/pyghmi/ipmi/oem/generic.py +++ b/pyghmi/ipmi/oem/generic.py @@ -37,7 +37,7 @@ class OEMHandler(object): board/product/chassis_extra_data arrays if 'oem_parser' is None, and mask those fields if not None. It is expected that OEMs leave the fields intact so that if client code hard codes around the - ordered lists that their expectations are not broken by an update + ordered lists that their expectations are not broken by an update. """ # In the generic case, just pass through if fru is None: diff --git a/pyghmi/ipmi/oem/lenovo.py b/pyghmi/ipmi/oem/lenovo.py index 290b662e..b396678e 100644 --- a/pyghmi/ipmi/oem/lenovo.py +++ b/pyghmi/ipmi/oem/lenovo.py @@ -15,9 +15,11 @@ # limitations under the License. import pyghmi.ipmi.oem.generic as generic +import pyghmi.ipmi.private.util as util 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 @@ -40,8 +42,16 @@ class OEMHandler(generic.OEMHandler): fru['MAC Address 1'] = mac1 if mac2 not in ('00:00:00:00:00:00', ''): fru['MAC Address 2'] = mac2 - # The product_extra is just UUID, we have that plenty of other ways - # So for now, leave that portion of the data alone + # 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 the string is not 16 long + byteguid.extend('\x00' * (16 - len(byteguid))) + fru['UUID'] = util.decode_wireformat_uuid(byteguid) return fru else: fru['oem_parser'] = None diff --git a/pyghmi/ipmi/private/util.py b/pyghmi/ipmi/private/util.py new file mode 100644 index 00000000..40afcda3 --- /dev/null +++ b/pyghmi/ipmi/private/util.py @@ -0,0 +1,31 @@ +# 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 + + +def decode_wireformat_uuid(rawguid): + """Decode a wire format UUID + + It handles the rather particular scheme where half is little endian + and half is big endian. It returns a string like dmidecode would output. + """ + if isinstance(rawguid, list): + rawguid = bytearray(rawguid) + lebytes = struct.unpack_from('HHI', buffer(rawguid[8:])) + return '{0:04X}-{1:02X}-{2:02X}-{3:02X}-{4:02X}{5:04X}'.format( + lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2])