diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index 4cf3b910..4ca5f181 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -20,8 +20,10 @@ import pyghmi.constants as const 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.sdr as sdr +import struct boot_devices = { @@ -88,6 +90,7 @@ class Command(object): self.onlogon = onlogon self.bmc = bmc self._sdr = None + self._oem = None if onlogon is not None: self.ipmi_session = session.Session(bmc=bmc, userid=userid, @@ -123,6 +126,28 @@ class Command(object): """ return session.Session.wait_for_rsp(timeout=timeout) + def oem_init(self): + """Initialize the command object for OEM capabilities + + A number of capabilities are either totally OEM defined or + else augmented somehow by knowledge of the OEM. This + method does an interrogation to identify the OEM. + + """ + if self._oem: + return + response = self.raw_command(netfn=6, command=1) + if 'error' in response: + raise exc.IpmiException(response['error'], code=response['code']) + self._oem = get_oem_handler({ + 'device_id': response['data'][0], + 'device_revision': response['data'][1] & 0b1111, + 'manufacturer_id': struct.unpack( + ' frusize: @@ -204,7 +203,7 @@ class FRU(object): currtlv = self.databytes[offset] currlen = currtlv & 0b111111 currtype = (currtlv & 0b11000000) >> 6 - retinfo = self.databytes[offset + 1:offset + currlen] + retinfo = self.databytes[offset + 1:offset + currlen + 1] newoffset = offset + currlen + 1 if currlen == 0: return None, newoffset @@ -213,9 +212,15 @@ class FRU(object): return retinfo, newoffset elif currtype == 3: # text string if lang == 0: - retinfo = retinfo.decode('utf-8') + try: + retinfo = retinfo.decode('utf-8') + except UnicodeDecodeError: + pass else: - retinfo = retinfo.decode('utf-16le') + try: + retinfo = retinfo.decode('utf-16le') + except UnicodeDecodeError: + pass retinfo = retinfo.replace('\x00', '') return retinfo, newoffset elif currtype == 1: # BCD 'plus' @@ -237,9 +242,9 @@ class FRU(object): raise iexc.BmcErrorException("Invallid/Unsupported chassis area") inf = self.info # ignore length field, just process the data - inf['chassis_type'] = enclosure_types[self.databytes[offset + 2]] - inf['chassis_part_number'], offset = self._decode_tlv(offset + 3) - inf['chassis_serial'], offset = self._decode_tlv(offset) + inf['Chassis type'] = enclosure_types[self.databytes[offset + 2]] + inf['Chassis part number'], offset = self._decode_tlv(offset + 3) + inf['Chassis serial number'], offset = self._decode_tlv(offset) inf['chassis_extra'] = [] while self.databytes[offset] != 0xc1: fielddata, offset = self._decode_tlv(offset) @@ -253,12 +258,12 @@ class FRU(object): raise iexc.BmcErrorException("Invalid/Unsupported board info area") inf = self.info language = self.databytes[offset + 2] - inf['board_mfg_date'] = decode_fru_date( + inf['Board manufacture date'] = decode_fru_date( self.databytes[offset + 3:offset + 6]) - inf['board_manufacturer'], offset = self._decode_tlv(offset + 6) - inf['board_product'], offset = self._decode_tlv(offset, language) - inf['board_serial'], offset = self._decode_tlv(offset, language) - inf['board_model'], offset = self._decode_tlv(offset, language) + inf['Board manufacturer'], offset = self._decode_tlv(offset + 6) + inf['Board product name'], offset = self._decode_tlv(offset, language) + inf['Board serial number'], offset = self._decode_tlv(offset, language) + inf['Board model'], offset = self._decode_tlv(offset, language) _, offset = self._decode_tlv(offset, language) # decode but discard inf['board_extra'] = [] while self.databytes[offset] != 0xc1: @@ -271,13 +276,13 @@ class FRU(object): return inf = self.info language = self.databytes[offset + 2] - inf['product_manufacturer'], offset = self._decode_tlv(offset + 3, - language) - inf['product_name'], offset = self._decode_tlv(offset, language) - inf['product_model'], offset = self._decode_tlv(offset, language) - inf['product_version'], offset = self._decode_tlv(offset, language) - inf['product_serial'], offset = self._decode_tlv(offset, language) - inf['product_asset'], offset = self._decode_tlv(offset, language) + inf['Manufacturer'], offset = self._decode_tlv(offset + 3, + language) + inf['Product name'], offset = self._decode_tlv(offset, language) + inf['Model'], offset = self._decode_tlv(offset, language) + inf['Hardware Version'], offset = self._decode_tlv(offset, language) + inf['Serial Number'], offset = self._decode_tlv(offset, language) + inf['Asset Number'], offset = self._decode_tlv(offset, language) _, offset = self._decode_tlv(offset, language) inf['product_extra'] = [] while self.databytes[offset] != 0xc1: diff --git a/pyghmi/ipmi/oem/__init__.py b/pyghmi/ipmi/oem/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyghmi/ipmi/oem/generic.py b/pyghmi/ipmi/oem/generic.py new file mode 100644 index 00000000..43f4d834 --- /dev/null +++ b/pyghmi/ipmi/oem/generic.py @@ -0,0 +1,44 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2015 Lenovo Corporation +# +# 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. + + +class OEMHandler(object): + """Handler class for OEM capabilities. + + Any vendor wishing to implement OEM extensions should look at this + base class for an appropriate interface. If one does not exist, this + base class should be extended. At initialization an OEM is given + a dictionary with product_id, device_id, manufacturer_id, and + device_revision as keys in a dictionary, along with an ipmi Command object + """ + def __init__(self, oemid, ipmicmd): + pass + + def process_fru(self, fru): + """Modify a fru entry with OEM understanding. + + Given a fru, clarify 'extra' fields according to OEM rules and + return the transformed data structure. If OEM processes, it is + expected that it sets 'oem_parser' to the name of the module. For + clients passing through data, it is suggested to pass through + 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 + """ + # In the generic case, just pass through + fru['oem_parser'] = None + return fru diff --git a/pyghmi/ipmi/oem/lenovo.py b/pyghmi/ipmi/oem/lenovo.py new file mode 100644 index 00000000..f2389abf --- /dev/null +++ b/pyghmi/ipmi/oem/lenovo.py @@ -0,0 +1,46 @@ +# 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 pyghmi.ipmi.oem.generic as generic + + +class OEMHandler(generic.OEMHandler): + def __init__(self, oemid, ipmicmd): + # will need to retain data to differentiate + # variations. For example System X versus Thinkserver + self.oemid = oemid + + def process_fru(self, fru): + if (self.oemid['manufacturer_id'] == 19046 and + self.oemid['device_id'] == 32): + fru['oem_parser'] = 'lenovo' + # Thinkserver lays out specific interpretation of the + # board extra fields + _, _, wwn1, wwn2, mac1, mac2 = fru['board_extra'] + if wwn1 != '0000000000000000': + fru['WWN 1'] = wwn1 + if wwn2 != '0000000000000000': + fru['WWN 2'] = wwn2 + if mac1 != '00:00:00:00:00:00': + fru['MAC Address 1'] = mac1 + if mac2 != '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 + return fru + else: + fru['oem_parser'] = None + return fru diff --git a/pyghmi/ipmi/oem/lookup.py b/pyghmi/ipmi/oem/lookup.py new file mode 100644 index 00000000..58d4d70f --- /dev/null +++ b/pyghmi/ipmi/oem/lookup.py @@ -0,0 +1,31 @@ +# Copyright 2015 Lenovo Corporation +# +# 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 pyghmi.ipmi.oem.generic as generic +import pyghmi.ipmi.oem.lenovo as lenovo + +# The mapping comes from +# http://www.iana.org/assignments/enterprise-numbers/enterprise-numbers +# Only mapping the ones with known backends +oemmap = { + 20301: lenovo, # IBM x86 (and System X at Lenovo) + 19046: lenovo, # Lenovo x86 (e.g. Thinkserver) +} + + +def get_oem_handler(oemid, ipmicmd): + try: + return oemmap[oemid['manufacturer_id']].OEMHandler(oemid, ipmicmd) + except KeyError: + return generic.OEMHandler(oemid, ipmicmd)