2
0
mirror of https://opendev.org/x/pyghmi synced 2026-03-29 06:13:30 +00:00

Add system UUID to inventory

While not strictly in the FRU area, it is often desirable
to have the system UUID available.  The intent is for the
UUID to match what dmidecode would return.  If a manufacturer
does it right, that UUID will be unique.  For ThinkServers,
override with the UUID from the OEM FRU fields rather than
using the get system UUID result.

Change-Id: Ie9a1b7e8fee2cb40ab679cbf2df04db61fd4e42f
This commit is contained in:
Jarrod Johnson
2015-04-29 10:49:06 -04:00
parent d022c58e61
commit 54b90439e7
5 changed files with 70 additions and 11 deletions

View File

@@ -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:

View File

@@ -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):

View File

@@ -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:

View File

@@ -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

View File

@@ -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('<IHH', buffer(rawguid[:8]))
bebytes = 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])