Refactor list_all_block_devices & add block_type param

Put the columns to retrieve from lsblk into a list so that future
modifications to columns will require fewer code changes.

Also add a 'block_type' parameter which defaults to 'disk'. To make the
function more flexible if callers wanted a different block type.

Update and add unit tests

Change-Id: If06460e13a5b56dc8d6efca9ff5b58ac6ba1f357
This commit is contained in:
John L. Villalovos
2015-08-28 11:14:52 -07:00
parent 7dba81f200
commit 3af9ab36bf
2 changed files with 59 additions and 11 deletions

View File

@@ -49,19 +49,22 @@ def _get_device_vendor(dev):
LOG.warning("Can't find the device vendor for device %s", dev) LOG.warning("Can't find the device vendor for device %s", dev)
def list_all_block_devices(): def list_all_block_devices(block_type='disk'):
"""List all physical block devices """List all physical block devices
The switches we use for lsblk: P for KEY="value" output, b for size output The switches we use for lsblk: P for KEY="value" output, b for size output
in bytes, d to exclude dependent devices (like md or dm devices), i to in bytes, d to exclude dependent devices (like md or dm devices), i to
ensure ascii characters only, and o to specify the fields we need. ensure ascii characters only, and o to specify the fields/columns we need.
Broken out as its own function to facilitate custom hardware managers that Broken out as its own function to facilitate custom hardware managers that
don't need to subclass GenericHardwareManager. don't need to subclass GenericHardwareManager.
:param block_type: Type of block device to find
:return: A list of BlockDevices :return: A list of BlockDevices
""" """
report = utils.execute('lsblk', '-PbdioKNAME,MODEL,SIZE,ROTA,TYPE',
columns = ['KNAME', 'MODEL', 'SIZE', 'ROTA', 'TYPE']
report = utils.execute('lsblk', '-Pbdi', '-o{}'.format(','.join(columns)),
check_exit_code=[0])[0] check_exit_code=[0])[0]
lines = report.split('\n') lines = report.split('\n')
context = pyudev.Context() context = pyudev.Context()
@@ -73,15 +76,15 @@ def list_all_block_devices():
vals = shlex.split(line) vals = shlex.split(line)
for key, val in (v.split('=', 1) for v in vals): for key, val in (v.split('=', 1) for v in vals):
device[key] = val.strip() device[key] = val.strip()
# Ignore non disk # Ignore block types not specified
if device.get('TYPE') != 'disk': if device.get('TYPE') != block_type:
continue continue
# Ensure all required keys are at least present, even if blank # Ensure all required columns are at least present, even if blank
diff = set(['KNAME', 'MODEL', 'SIZE', 'ROTA']) - set(device.keys()) missing = set(columns) - set(device)
if diff: if missing:
raise errors.BlockDeviceError( raise errors.BlockDeviceError(
'%s must be returned by lsblk.' % diff) '%s must be returned by lsblk.' % ', '.join(sorted(missing)))
name = '/dev/' + device['KNAME'] name = '/dev/' + device['KNAME']
try: try:

View File

@@ -140,6 +140,14 @@ BLK_DEVICE_TEMPLATE_SMALL = (
'KNAME="sdb" MODEL="AlmostBigEnough Drive" SIZE="4294967295" ' 'KNAME="sdb" MODEL="AlmostBigEnough Drive" SIZE="4294967295" '
'ROTA="0" TYPE="disk"' 'ROTA="0" TYPE="disk"'
) )
BLK_DEVICE_TEMPLATE_SMALL_DEVICES = [
hardware.BlockDevice(name='/dev/sda', model='TinyUSB Drive',
size=3116853504, rotational=False,
vendor="FooTastic"),
hardware.BlockDevice(name='/dev/sdb', model='AlmostBigEnough Drive',
size=4294967295, rotational=False,
vendor="FooTastic"),
]
SHRED_OUTPUT = ( SHRED_OUTPUT = (
'shred: /dev/sda: pass 1/2 (random)...\n' 'shred: /dev/sda: pass 1/2 (random)...\n'
@@ -277,7 +285,7 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
mocked_execute.return_value = (BLK_DEVICE_TEMPLATE, '') mocked_execute.return_value = (BLK_DEVICE_TEMPLATE, '')
self.assertEqual(self.hardware.get_os_install_device(), '/dev/sdb') self.assertEqual(self.hardware.get_os_install_device(), '/dev/sdb')
mocked_execute.assert_called_once_with( mocked_execute.assert_called_once_with(
'lsblk', '-PbdioKNAME,MODEL,SIZE,ROTA,TYPE', 'lsblk', '-Pbdi', '-oKNAME,MODEL,SIZE,ROTA,TYPE',
check_exit_code=[0]) check_exit_code=[0])
@mock.patch.object(utils, 'execute') @mock.patch.object(utils, 'execute')
@@ -287,7 +295,8 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
ex = self.assertRaises(errors.DeviceNotFound, ex = self.assertRaises(errors.DeviceNotFound,
self.hardware.get_os_install_device) self.hardware.get_os_install_device)
mocked_execute.assert_called_once_with( mocked_execute.assert_called_once_with(
'lsblk', '-PbdioKNAME,MODEL,SIZE,ROTA,TYPE', check_exit_code=[0]) 'lsblk', '-Pbdi', '-oKNAME,MODEL,SIZE,ROTA,TYPE',
check_exit_code=[0])
self.assertIn(str(4 * units.Gi), ex.details) self.assertIn(str(4 * units.Gi), ex.details)
@mock.patch.object(hardware, 'list_all_block_devices') @mock.patch.object(hardware, 'list_all_block_devices')
@@ -792,3 +801,39 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
def test_get_bmc_address_virt(self, mocked_execute): def test_get_bmc_address_virt(self, mocked_execute):
mocked_execute.side_effect = processutils.ProcessExecutionError() mocked_execute.side_effect = processutils.ProcessExecutionError()
self.assertIsNone(self.hardware.get_bmc_address()) self.assertIsNone(self.hardware.get_bmc_address())
class TestModuleFunctions(test_base.BaseTestCase):
@mock.patch.object(hardware, '_get_device_vendor', lambda x: "FooTastic")
@mock.patch.object(hardware.pyudev.Device, "from_device_file")
@mock.patch.object(utils, 'execute', autospec=True)
def test_list_all_block_devices_success(self, mocked_execute,
mocked_fromdevfile):
mocked_fromdevfile.return_value = {}
mocked_execute.return_value = (BLK_DEVICE_TEMPLATE_SMALL, '')
result = hardware.list_all_block_devices()
mocked_execute.assert_called_once_with(
'lsblk', '-Pbdi', '-oKNAME,MODEL,SIZE,ROTA,TYPE',
check_exit_code=[0])
self.assertEqual(BLK_DEVICE_TEMPLATE_SMALL_DEVICES, result)
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(hardware, '_get_device_vendor', lambda x: "FooTastic")
def test_list_all_block_devices_wrong_block_type(self, mocked_execute):
mocked_execute.return_value = ('TYPE="foo" MODEL="model"', '')
result = hardware.list_all_block_devices()
mocked_execute.assert_called_once_with(
'lsblk', '-Pbdi', '-oKNAME,MODEL,SIZE,ROTA,TYPE',
check_exit_code=[0])
self.assertEqual([], result)
@mock.patch.object(utils, 'execute', autospec=True)
def test_list_all_block_devices_missing(self, mocked_execute):
"""Test for missing values returned from lsblk"""
mocked_execute.return_value = ('TYPE="disk" MODEL="model"', '')
self.assertRaisesRegexp(
errors.BlockDeviceError,
r'^Block device caused unknown error: KNAME, ROTA, SIZE must be '
r'returned by lsblk.$',
hardware.list_all_block_devices)