Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 52 additions & 44 deletions pyipmi/fru.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ def read_fru_data(self, offset=None, count=None, fru_id=0):
def read_fru_data_full(self, fru_id=0):
return self.read_fru_data(fru_id=fru_id)

def get_fru_inventory_header(self, fru_id=0):
def get_fru_inventory_header(self, fru_id=0, ignore_checksum=False):
data = self.read_fru_data(offset=0, count=8, fru_id=fru_id)
return InventoryCommonHeader(data)
return InventoryCommonHeader(data, ignore_checksum=ignore_checksum)

def _read_fru_area(self, offset, fru_id=0):
# read the area header
Expand All @@ -99,26 +99,30 @@ def _read_fru_area(self, offset, fru_id=0):
count = data[1] * 8
return self.read_fru_data(offset=offset, count=count, fru_id=fru_id)

def get_fru_chassis_area(self, fru_id=0):
header = self.get_fru_inventory_header(fru_id=fru_id)
def get_fru_chassis_area(self, fru_id=0, ignore_checksum=False):
header = self.get_fru_inventory_header(fru_id=fru_id,
ignore_checksum=ignore_checksum)
data = self._read_fru_area(offset=header.chassis_info_area_offset,
fru_id=fru_id)
return InventoryChassisInfoArea(data)
return InventoryChassisInfoArea(data, ignore_checksum=ignore_checksum)

def get_fru_board_area(self, fru_id=0):
header = self.get_fru_inventory_header(fru_id=fru_id)
def get_fru_board_area(self, fru_id=0, ignore_checksum=False):
header = self.get_fru_inventory_header(fru_id=fru_id,
ignore_checksum=ignore_checksum)
data = self._read_fru_area(offset=header.board_info_area_offset,
fru_id=fru_id)
return InventoryBoardInfoArea(data)
return InventoryBoardInfoArea(data, ignore_checksum=ignore_checksum)

def get_fru_product_area(self, fru_id=0):
header = self.get_fru_inventory_header(fru_id=fru_id)
def get_fru_product_area(self, fru_id=0, ignore_checksum=False):
header = self.get_fru_inventory_header(fru_id=fru_id,
ignore_checksum=ignore_checksum)
data = self._read_fru_area(offset=header.product_info_area_offset,
fru_id=fru_id)
return InventoryProductInfoArea(data)
return InventoryProductInfoArea(data, ignore_checksum=ignore_checksum)

def get_fru_multirecord_area(self, fru_id=0):
header = self.get_fru_inventory_header(fru_id=fru_id)
def get_fru_multirecord_area(self, fru_id=0, ignore_checksum=False):
header = self.get_fru_inventory_header(fru_id=fru_id,
ignore_checksum=ignore_checksum)

# we have to determine the length of the area first
offset = header.multirecord_area_offset
Expand All @@ -137,9 +141,9 @@ def get_fru_multirecord_area(self, fru_id=0):
# now read the full area
offset = header.multirecord_area_offset
data = self.read_fru_data(offset=offset, count=count)
return InventoryMultiRecordArea(data)
return InventoryMultiRecordArea(data, ignore_checksum=ignore_checksum)

def get_fru_inventory(self, fru_id=0):
def get_fru_inventory(self, fru_id=0, ignore_checksum=False):
"""
Get the full parsed FRU inventory data.
"""
Expand All @@ -161,7 +165,7 @@ def get_fru_inventory(self, fru_id=0):
return fru


def get_fru_inventory_from_file(filename):
def get_fru_inventory_from_file(filename, ignore_checksum=False):
try:
file = open(filename, "rb")
except IOError:
Expand All @@ -173,7 +177,7 @@ def get_fru_inventory_from_file(filename):
file_data = file.read(file_size)
data = array.array('B', file_data)
file.close()
return FruInventory(data)
return FruInventory(data, ignore_checksum=ignore_checksum)


CUSTOM_FIELD_END = 0xc1
Expand All @@ -190,37 +194,37 @@ def _decode_custom_fields(data):


class FruData(object):
def __init__(self, data=None):
def __init__(self, data=None, ignore_checksum=False):
if data:
if isinstance(data, str):
data = [ord(c) for c in data]
self.data = data
if hasattr(self, '_from_data'):
self._from_data(data)
self._from_data(data, ignore_checksum=ignore_checksum)


class InventoryCommonHeader(FruData):
def _from_data(self, data):
if len(data) != 8:
def _from_data(self, data, ignore_checksum=False):
if len(data) < 8:
raise DecodingError('InventoryCommonHeader length != 8')
self.format_version = data[0] & 0x0f
self.internal_use_area_offset = data[1] * 8 or None
self.chassis_info_area_offset = data[2] * 8 or None
self.board_info_area_offset = data[3] * 8 or None
self.product_info_area_offset = data[4] * 8 or None
self.multirecord_area_offset = data[5] * 8 or None
if sum(data) % 256 != 0:
raise DecodingError('InventoryCommonHeader checksum failed')
if sum(data[:8]) % 256 != 0 and ignore_checksum is False:
raise DecodingError(f'InventoryCommonHeader checksum failed {sum(data) % 0x10}')


class CommonInfoArea(FruData):
def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
self.format_version = data[0] & 0x0f
if self.format_version != 1:
raise DecodingError('unsupported format version (%d)' %
self.format_version)
self.length = data[1] * 8
if sum(data[:self.length]) % 256 != 0:
if sum(data[:self.length]) % 256 != 0 and ignore_checksum is False:
raise DecodingError('checksum failed')


Expand Down Expand Up @@ -249,7 +253,7 @@ class InventoryChassisInfoArea(CommonInfoArea):
TYPE_RAID_CHASSIS = 22
TYPE_RACK_MOUNT_CHASSIS = 23

def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
CommonInfoArea._from_data(self, data)
self.type = data[2]
offset = 3
Expand All @@ -261,8 +265,8 @@ def _from_data(self, data):


class InventoryBoardInfoArea(CommonInfoArea):
def _from_data(self, data):
CommonInfoArea._from_data(self, data)
def _from_data(self, data, ignore_checksum=False):
CommonInfoArea._from_data(self, data, ignore_checksum=ignore_checksum)
self.language_code = data[2]
minutes = data[5] << 16 | data[4] << 8 | data[3]
self.mfg_date = (datetime.datetime(1996, 1, 1)
Expand All @@ -282,7 +286,7 @@ def _from_data(self, data):


class InventoryProductInfoArea(CommonInfoArea):
def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
CommonInfoArea._from_data(self, data)
self.language_code = data[2]
offset = 3
Expand Down Expand Up @@ -318,17 +322,17 @@ def __str__(self):
return '%02x: %s' % (self.record_type_id,
' '.join('%02x' % b for b in self.raw))

def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
if len(data) < 5:
raise DecodingError('data too short')
self.record_type_id = data[0]
self.format_version = data[1] & 0x0f
self.end_of_list = bool(data[1] & 0x80)
self.length = data[2]
if sum(data[:5]) % 256 != 0:
if sum(data[:5]) % 256 != 0 and ignore_checksum is False:
raise DecodingError('FruDataMultiRecord header checksum failed')
self.raw = data[5:5+self.length]
if (sum(self.raw) + data[3]) % 256 != 0:
if (sum(self.raw) + data[3]) % 256 != 0 and ignore_checksum is False:
raise DecodingError('FruDataMultiRecord record checksum failed')

@staticmethod
Expand Down Expand Up @@ -386,19 +390,19 @@ def create_from_record_id(data):

return FruPicmgRecord(data)

def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
if len(data) < 10:
raise DecodingError('data too short')
data = array.array('B', data)
FruDataMultiRecord._from_data(self, data)
FruDataMultiRecord._from_data(self, data, ignore_checksum=ignore_checksum)
self.manufacturer_id = \
data[5] | data[6] << 8 | data[7] << 16
self.picmg_record_type_id = data[8]
self.format_version = data[9]


class FruPicmgPowerModuleCapabilityRecord(FruPicmgRecord):
def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
if len(data) < 12:
raise DecodingError('data too short')
FruPicmgRecord._from_data(self, data)
Expand All @@ -407,11 +411,11 @@ def _from_data(self, data):


class InventoryMultiRecordArea(object):
def __init__(self, data):
def __init__(self, data, ignore_checksum=False):
if data:
self._from_data(data)

def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
self.records = list()
offset = 0
while True:
Expand All @@ -423,31 +427,35 @@ def _from_data(self, data):


class FruInventory(object):
def __init__(self, data=None):
def __init__(self, data=None, ignore_checksum=False):
self.chassis_info_area = None
self.board_info_area = None
self.product_info_area = None
self.multirecord_area = None

if data:
self._from_data(data)
self._from_data(data, ignore_checksum=ignore_checksum)

def _from_data(self, data):
def _from_data(self, data, ignore_checksum=False):
self.raw = data
self.common_header = InventoryCommonHeader(data[:8])

if self.common_header.chassis_info_area_offset:
self.chassis_info_area = InventoryChassisInfoArea(
data[self.common_header.chassis_info_area_offset:])
data[self.common_header.chassis_info_area_offset:],
ignore_checksum=ignore_checksum)

if self.common_header.board_info_area_offset:
self.board_info_area = InventoryBoardInfoArea(
data[self.common_header.board_info_area_offset:])
data[self.common_header.board_info_area_offset:],
ignore_checksum=ignore_checksum)

if self.common_header.product_info_area_offset:
self.product_info_area = InventoryProductInfoArea(
data[self.common_header.product_info_area_offset:])
data[self.common_header.product_info_area_offset:],
ignore_checksum=ignore_checksum)

if self.common_header.multirecord_area_offset:
self.multirecord_area = InventoryMultiRecordArea(
data[self.common_header.multirecord_area_offset:])
data[self.common_header.multirecord_area_offset:],
ignore_checksum=ignore_checksum)
40 changes: 31 additions & 9 deletions tests/test_fru.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import pytest

from pyipmi.fru import (FruData, FruPicmgPowerModuleCapabilityRecord,
from pyipmi.errors import (DecodingError)

from pyipmi.fru import (FruData, FruInventory,
FruPicmgPowerModuleCapabilityRecord,
InventoryCommonHeader, InventoryBoardInfoArea,
get_fru_inventory_from_file)


this_file_path = os.path.dirname(os.path.abspath(__file__))


def test_frudata_object():
fru_field = FruData((0, 1, 2, 3))
assert fru_field.data[0] == 0
Expand All @@ -25,16 +32,21 @@ def test_commonheader_object():
InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 235))


def test_commonheader_object_invalid_checksum():
with pytest.raises(DecodingError):
InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 0))

InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 0), ignore_checksum=True)


def test_fru_inventory_from_file():
path = os.path.dirname(os.path.abspath(__file__))
fru_file = os.path.join(path, 'fru_bin/kontron_am4010.bin')
fru_file = os.path.join(this_file_path, 'fru_bin/kontron_am4010.bin')
fru = get_fru_inventory_from_file(fru_file)
assert fru.chassis_info_area is None


def test_board_area():
path = os.path.dirname(os.path.abspath(__file__))
fru_file = os.path.join(path, 'fru_bin/kontron_am4010.bin')
fru_file = os.path.join(this_file_path, 'fru_bin/kontron_am4010.bin')
fru = get_fru_inventory_from_file(fru_file)

board_area = fru.board_info_area
Expand All @@ -45,8 +57,7 @@ def test_board_area():


def test_product_area():
path = os.path.dirname(os.path.abspath(__file__))
fru_file = os.path.join(path, 'fru_bin/kontron_am4010.bin')
fru_file = os.path.join(this_file_path, 'fru_bin/kontron_am4010.bin')
fru = get_fru_inventory_from_file(fru_file)

product_area = fru.product_info_area
Expand All @@ -57,8 +68,7 @@ def test_product_area():


def test_multirecord_with_power_module_capability_record():
path = os.path.dirname(os.path.abspath(__file__))
fru_file = os.path.join(path, 'fru_bin/vadatech_utc017.bin')
fru_file = os.path.join(this_file_path, 'fru_bin/vadatech_utc017.bin')
fru = get_fru_inventory_from_file(fru_file)
assert len(fru.multirecord_area.records) == 1
record = fru.multirecord_area.records[0]
Expand All @@ -72,3 +82,15 @@ def test_BoardInfoArea():
assert area.product_name.string == 'PowerEdge R515 '
assert area.serial_number.string == 'CN717033AI0058'
assert area.part_number.string == '0RMRF7A05'


def test_FruInventory_ignore_checksum_error():
data = b'\x01\x00\x00\x01\x04\x00\x00\xfa\x01\x03\x00vq\xb4\xcaASRockRack\xc0\xc0\xc0\xc0\xc1\x00\x1b\x01\x03\x00\xcaASRockRack\xc0\xc0\xc0\xc0\xc0\xc0\xc1\x00\x00M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

with pytest.raises(DecodingError):
FruInventory(data, ignore_checksum=False)

inv = FruInventory(data, ignore_checksum=True)

assert inv.board_info_area.manufacturer.string == 'ASRockRack'
assert inv.product_info_area.manufacturer.string == 'ASRockRack'
Loading