fuel-agent/fuel_agent/tests/test_hardware_utils.py
Alexander Gordeev 1e8f38bbb8 Don't filter removable block devices
list_block_devices should report all devices that it can find.
If an error occurs during block device analysing,
then it should be silently ignored and the device should be skipped.

This change will let provisioning to proceed if this skipped device
is not required for provisioning purposes.

Change-Id: I9399b1e0855493289e82f40ffd8c892f89412f1c
Related-Bug: #1486601
2015-08-27 17:07:40 +03:00

474 lines
18 KiB
Python

# Copyright 2014 Mirantis, Inc.
#
# 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 mock
import unittest2
from fuel_agent import errors
from fuel_agent.utils import hardware as hu
from fuel_agent.utils import utils
class TestHardwareUtils(unittest2.TestCase):
@mock.patch.object(utils, 'execute')
def test_parse_dmidecode(self, exec_mock):
exec_mock.return_value = ["""
System Slot Information
Designation: PCIEX16_1
ID: 1
Bus Address: 0000:00:01.0
Characteristics:
3.3 V is provided
PME signal is supported
System Slot Information
Type: 32-bit PCI Express
ID: 3
Characteristics:
Opening is shared
Bus Address: 0000:00:1c.4
"""]
expected = [{"designation": "PCIEX16_1",
"id": "1",
"characteristics": ["3.3 V is provided",
"PME signal is supported"],
"bus address": "0000:00:01.0"},
{"type": "32-bit PCI Express",
"id": "3",
"characteristics": ["Opening is shared"],
"bus address": "0000:00:1c.4"}]
self.assertEqual(expected, hu.parse_dmidecode("fake_type"))
exec_mock.assert_called_once_with("dmidecode", "-q", "--type",
"fake_type")
@mock.patch.object(utils, 'execute')
def test_parse_lspci(self, exec_mock):
exec_mock.return_value = ["""Slot: 07:00.0
Class: PCI bridge
Vendor: ASMedia Technology Inc.
Device: ASM1083/1085 PCIe to PCI Bridge
Rev: 01
ProgIf: 01
Slot: 09:00.0
Class: IDE interface
Vendor: Marvell Technology Group Ltd.
Device: 88SE6121 SATA II / PATA Controller
SVendor: ASUSTeK Computer Inc.
SDevice: Device 82a2
Rev: b2
ProgIf: 8f
"""]
expected = [{'class': 'PCI bridge',
'device': 'ASM1083/1085 PCIe to PCI Bridge',
'progif': '01',
'rev': '01',
'slot': '07:00.0',
'vendor': 'ASMedia Technology Inc.'},
{'class': 'IDE interface',
'device': '88SE6121 SATA II / PATA Controller',
'progif': '8f',
'rev': 'b2',
'sdevice': 'Device 82a2',
'slot': '09:00.0',
'svendor': 'ASUSTeK Computer Inc.',
'vendor': 'Marvell Technology Group Ltd.'}]
self.assertEqual(expected, hu.parse_lspci())
exec_mock.assert_called_once_with('lspci', '-vmm', '-D')
@mock.patch.object(utils, 'execute')
def test_parse_simple_kv(self, exec_mock):
exec_mock.return_value = ["""driver: r8169
version: 2.3LK-NAPI
firmware-version: rtl_nic/rtl8168e-2.fw
bus-info: 0000:06:00.0
supports-statistics: yes
supports-test: no
supports-eeprom-access: no
supports-register-dump: yes
"""]
expected = {'driver': 'r8169',
'version': '2.3LK-NAPI',
'firmware-version': 'rtl_nic/rtl8168e-2.fw',
'bus-info': '0000:06:00.0',
'supports-statistics': 'yes',
'supports-test': 'no',
'supports-eeprom-access': 'no',
'supports-register-dump': 'yes'}
self.assertEqual(expected, hu.parse_simple_kv('fake', 'cmd'))
exec_mock.assert_called_once_with('fake', 'cmd')
@mock.patch.object(utils, 'execute')
def test_udevreport(self, mock_exec):
# should run udevadm info OS command
# in order to get udev properties for a device
mock_exec.return_value = (
'DEVLINKS=\'/dev/disk/by-id/fakeid1 /dev/disk/by-id/fakeid2\'\n'
'DEVNAME=\'/dev/fake\'\n'
'DEVPATH=\'/devices/fakepath\'\n'
'DEVTYPE=\'disk\'\n'
'MAJOR=\'11\'\n'
'MINOR=\'0\'\n'
'ID_BUS=\'fakebus\'\n'
'ID_MODEL=\'fakemodel\'\n'
'ID_SERIAL_SHORT=\'fakeserial\'\n'
'ID_WWN=\'fakewwn\'\n'
'ID_CDROM=\'1\'\n'
'ANOTHER=\'another\'\n',
''
)
expected = {
'DEVLINKS': ['/dev/disk/by-id/fakeid1', '/dev/disk/by-id/fakeid2'],
'DEVNAME': '/dev/fake',
'DEVPATH': '/devices/fakepath',
'DEVTYPE': 'disk',
'MAJOR': '11',
'MINOR': '0',
'ID_BUS': 'fakebus',
'ID_MODEL': 'fakemodel',
'ID_SERIAL_SHORT': 'fakeserial',
'ID_WWN': 'fakewwn',
'ID_CDROM': '1'
}
self.assertEqual(expected, hu.udevreport('/dev/fake'))
mock_exec.assert_called_once_with('udevadm',
'info',
'--query=property',
'--export',
'--name=/dev/fake',
check_exit_code=[0])
@mock.patch.object(utils, 'execute')
def test_blockdevreport(self, mock_exec):
# should run blockdev OS command
# in order to get block device properties
cmd = ['blockdev', '--getsz', '--getro', '--getss', '--getpbsz',
'--getsize64', '--getiomin', '--getioopt', '--getra',
'--getalignoff', '--getmaxsect', '/dev/fake']
mock_exec.return_value = (
'625142448\n0\n512\n4096\n320072933376\n4096\n0\n256\n0\n1024',
''
)
expected = {
'sz': '625142448',
'ro': '0',
'ss': '512',
'pbsz': '4096',
'size64': '320072933376',
'iomin': '4096',
'ioopt': '0',
'ra': '256',
'alignoff': '0',
'maxsect': '1024'
}
self.assertEqual(expected, hu.blockdevreport('/dev/fake'))
mock_exec.assert_called_once_with(*cmd, check_exit_code=[0])
@mock.patch('six.moves.builtins.open')
def test_extrareport(self, mock_open):
# should read some files from sysfs e.g. /sys/block/fake/removable
# in order to get some device properties
def with_side_effect(arg):
mock_with = mock.MagicMock()
mock_with.__exit__.return_value = None
mock_file = mock.Mock()
if arg == '/sys/block/fake/removable':
mock_file.read.return_value = '0\n'
elif arg == '/sys/block/fake/device/state':
mock_file.read.return_value = 'running\n'
elif arg == '/sys/block/fake/device/timeout':
mock_file.read.return_value = '30\n'
elif arg == '/sys/block/fake/device/vendor':
mock_file.read.return_value = 'VENDORNAME \n'
mock_with.__enter__.return_value = mock_file
return mock_with
mock_open.side_effect = with_side_effect
expected = {'removable': '0', 'state': 'running', 'timeout': '30',
'vendor': 'VENDORNAME'}
self.assertEqual(expected, hu.extrareport('/dev/fake'))
@mock.patch('six.moves.builtins.open')
def test_extrareport_exceptions(self, mock_open):
mock_open.side_effect = Exception('foo')
expected = {}
self.assertEqual(expected, hu.extrareport('/dev/fake'))
@mock.patch.object(hu, 'blockdevreport')
@mock.patch.object(hu, 'udevreport')
def test_is_disk_uspec_bspec_none(self, mock_ureport, mock_breport):
# should call udevreport if uspec is None
# should call blockdevreport if bspec is None
# should return True if uspec and bspec are empty
mock_ureport.return_value = {}
mock_breport.return_value = {}
self.assertTrue(hu.is_disk('/dev/fake'))
mock_ureport.assert_called_once_with('/dev/fake')
mock_breport.assert_called_once_with('/dev/fake')
@mock.patch.object(hu, 'udevreport')
def test_is_disk_uspec_none(self, mock_ureport):
# should call udevreport if uspec is None but bspec is not None
bspec = {'key': 'value'}
mock_ureport.return_value = {}
hu.is_disk('/dev/fake', bspec=bspec)
mock_ureport.assert_called_once_with('/dev/fake')
@mock.patch.object(hu, 'blockdevreport')
def test_is_disk_bspec_none(self, mock_breport):
# should call blockdevreport if bspec is None but uspec is not None
uspec = {'key': 'value'}
mock_breport.return_value = {}
hu.is_disk('/dev/fake', uspec=uspec)
mock_breport.assert_called_once_with('/dev/fake')
@mock.patch.object(hu, 'blockdevreport')
def test_is_disk_cdrom(self, mock_breport):
# should return False if udev ID_CDROM is set to 1
mock_breport.return_value = {}
uspec = {
'ID_CDROM': '1'
}
self.assertFalse(hu.is_disk('/dev/fake', uspec=uspec))
@mock.patch.object(hu, 'blockdevreport')
def test_is_disk_partition(self, mock_breport):
# should return False if udev DEVTYPE is partition
mock_breport.return_value = {}
uspec = {
'DEVTYPE': 'partition'
}
self.assertFalse(hu.is_disk('/dev/fake', uspec=uspec))
@mock.patch.object(hu, 'blockdevreport')
def test_is_disk_major(self, mock_breport):
# should return False if udev MAJOR is not in a list of
# major numbers which are used for disks
# look at kernel/Documentation/devices.txt
mock_breport.return_value = {}
valid_majors = [3, 8, 65, 66, 67, 68, 69, 70, 71, 104, 105,
106, 107, 108, 109, 110, 111, 202, 252, 253, 259]
for major in (set(range(1, 261)) - set(valid_majors)):
uspec = {
'MAJOR': str(major)
}
self.assertFalse(hu.is_disk('/dev/fake', uspec=uspec))
@mock.patch.object(hu, 'udevreport')
def test_is_disk_readonly(self, mock_ureport):
# should return False if device is read only
mock_ureport.return_value = {}
bspec = {
'ro': '1'
}
self.assertFalse(hu.is_disk('/dev/fake', bspec=bspec))
@mock.patch('fuel_agent.utils.hardware.utils.execute')
def test_get_block_devices_from_udev_db(self, mock_exec):
mock_exec.return_value = ("""P: /devices/virtual/block/loop0
N: loop0
E: DEVNAME=/dev/loop0
E: DEVPATH=/devices/virtual/block/loop0
E: DEVTYPE=disk
E: MAJOR=7
E: SUBSYSTEM=block
P: /devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
N: sda
S: disk/by-id/wwn-0x5000c5004008ac0f
S: disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0
E: DEVNAME=/dev/sda
E: DEVTYPE=disk
E: ID_ATA=1
E: MAJOR=8
E: SUBSYSTEM=block
E: UDEV_LOG=3
P: /devices/pci:00/:00:04.0/misc/nvme0
N: nvme0
E: DEVNAME=/dev/nvme0
E: DEVPATH=/devices/pci:00/:00:04.0/misc/nvme0
E: MAJOR=10
E: MINOR=57
E: SUBSYSTEM=misc
P: /devices/pci:00/:00:04.0/block/nvme0n1
N: nvme0n1
E: DEVNAME=/dev/nvme0n1
E: DEVPATH=/devices/pci:00/:00:04.0/block/nvme0n1
E: DEVTYPE=disk
E: MAJOR=259
E: MINOR=0
E: SUBSYSTEM=block
E: USEC_INITIALIZED=87744
P: /devices/pci0000:00/0000:00:1c.1/target16:0:0/16:0:0:0/block/sr0
E: DEVTYPE=disk
E: DEVNAME=/dev/sr0
E: MAJOR=11
E: MINOR=0
E: SEQNUM=4400
E: SUBSYSTEM=block
P: /devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
N: sda
S: disk/by-id/wwn-0x5000c5004008ac0f
S: disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0
E: DEVNAME=/dev/sda1
E: DEVTYPE=partition
E: ID_ATA=1
E: SUBSYSTEM=block
E: MAJOR=8
E: UDEV_LOG=3""", '')
self.assertEqual(['/dev/sda', '/dev/nvme0n1', '/dev/sda1'],
hu.get_block_devices_from_udev_db())
@mock.patch.object(hu, 'get_block_devices_from_udev_db')
@mock.patch.object(hu, 'is_disk')
@mock.patch.object(hu, 'extrareport')
@mock.patch.object(hu, 'blockdevreport')
@mock.patch.object(hu, 'udevreport')
def test_list_block_devices(self, mock_ureport, mock_breport, mock_ereport,
mock_isdisk, mock_get_devs):
# should run blockdev --report command
# in order to get a list of block devices
# should call report methods to get device info
# should call is_disk method to filter out
# those block devices which are not disks
mock_get_devs.return_value = ['/dev/fake', '/dev/fake1', '/dev/sr0']
def isdisk_side_effect(arg, uspec=None, bspec=None):
if arg == '/dev/fake':
return True
elif arg in ('/dev/fake1', '/dev/sr0'):
return False
mock_isdisk.side_effect = isdisk_side_effect
mock_ureport.return_value = {'key0': 'value0'}
mock_breport.return_value = {'key1': 'value1'}
mock_ereport.return_value = {'key2': 'value2'}
expected = [{
'device': '/dev/fake',
'uspec': {'key0': 'value0'},
'bspec': {'key1': 'value1'},
'espec': {'key2': 'value2'}
}]
self.assertEqual(hu.list_block_devices(), expected)
self.assertEqual(mock_ureport.call_args_list, [mock.call('/dev/fake'),
mock.call('/dev/fake1'), mock.call('/dev/sr0')])
self.assertEqual(mock_breport.call_args_list, [mock.call('/dev/fake'),
mock.call('/dev/fake1'), mock.call('/dev/sr0')])
self.assertEqual(mock_ereport.call_args_list, [mock.call('/dev/fake'),
mock.call('/dev/fake1'), mock.call('/dev/sr0')])
@mock.patch.object(hu, 'get_block_devices_from_udev_db')
@mock.patch.object(hu, 'is_disk')
@mock.patch.object(hu, 'extrareport')
@mock.patch.object(hu, 'blockdevreport')
@mock.patch.object(hu, 'udevreport')
def test_list_block_devices_skip_block_device(self, mock_ureport,
mock_breport, mock_ereport,
mock_isdisk, mock_get_devs):
mock_get_devs.return_value = ['/dev/disk',
'/dev/wrong_disk_skipped',
'/dev/wrong_disk_skipped_too']
mock_isdisk.return_value = True
mock_ureport.return_value = {}
mock_ereport.side_effect = [
{'removable': '1'},
errors.ProcessExecutionError,
KeyError,
]
mock_breport.return_value = {'key1': 'value1'}
expected = [{
'device': '/dev/disk',
'uspec': {},
'bspec': {'key1': 'value1'},
'espec': {'removable': '1'},
}]
self.assertEqual(hu.list_block_devices(), expected)
expected_calls = [mock.call('/dev/disk'),
mock.call('/dev/wrong_disk_skipped'),
mock.call('/dev/wrong_disk_skipped_too')]
self.assertEqual(expected_calls, mock_ureport.call_args_list)
mock_breport.assert_called_once_with('/dev/disk')
self.assertEqual(expected_calls, mock_ereport.call_args_list)
def test_match_device_devlinks(self):
# should return true if at least one by-id link from first uspec
# matches by-id link from another uspec
uspec1 = {'DEVLINKS': ['/dev/disk/by-path/fakepath',
'/dev/disk/by-id/fakeid1',
'/dev/disk/by-id/fakeid2']}
uspec2 = {'DEVLINKS': ['/dev/disk/by-id/fakeid2',
'/dev/disk/by-id/fakeid3']}
self.assertTrue(hu.match_device(uspec1, uspec2))
def test_match_device_wwn(self):
# should return true if ID_WWN is given
# and if it is the same in both uspecs
# and if DEVTYPE is given and if DEVTYPE is disk
# or if DEVTYPE is partition and MINOR is the same for both uspecs
uspec1 = uspec2 = {'ID_WWN': 'fakewwn',
'DEVTYPE': 'disk'}
self.assertTrue(hu.match_device(uspec1, uspec2))
uspec1 = uspec2 = {'ID_WWN': 'fakewwn',
'DEVTYPE': 'partition',
'MINOR': '1'}
self.assertTrue(hu.match_device(uspec1, uspec2))
def test_match_device_wwn_false(self):
# should return false if ID_WWN is given
# and does not match each other
uspec1 = {'ID_WWN': 'fakewwn1'}
uspec2 = {'ID_WWN': 'fakewwn2'}
self.assertFalse(hu.match_device(uspec1, uspec2))
def test_match_device_devpath(self):
# should return true if DEVPATH is given
# and if it is the same for both uspecs
uspec1 = uspec2 = {'DEVPATH': '/devices/fake'}
self.assertTrue(hu.match_device(uspec1, uspec2))
def test_match_device_serial(self):
# should return true if ID_SERIAL_SHORT is given
# and if it is the same for both uspecs
# and if DEVTYPE is given and if it is 'disk'
uspec1 = uspec2 = {'ID_SERIAL_SHORT': 'fakeserial',
'DEVTYPE': 'disk'}
self.assertTrue(hu.match_device(uspec1, uspec2))
def test_match_device_serial_false(self):
# should return false if ID_SERIAL_SHORT is given
# and if it does not match each other
uspec1 = {'ID_SERIAL_SHORT': 'fakeserial1'}
uspec2 = {'ID_SERIAL_SHORT': 'fakeserial2'}
self.assertFalse(hu.match_device(uspec1, uspec2))
def test_match_device_false(self):
uspec1 = {'ID_WWN': 'fakewwn1', 'DEVTYPE': 'disk'}
uspec2 = {'ID_WWN': 'fakewwn1', 'DEVTYPE': 'partition'}
self.assertFalse(hu.match_device(uspec1, uspec2))