From 85f89a344dad750b5ea88fbd3c139b220449d7e6 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Fri, 21 Feb 2020 15:37:55 -0600 Subject: [PATCH 01/11] storage: add dasd prober Probe for dasd devices. Use dasdview to extract the information needed about dasd devices and include them in the probe data. LP: #1862849 --- probert/dasd.py | 133 ++++++++++++++++++++++++++++++++++++ probert/storage.py | 3 +- probert/tests/test_utils.py | 26 +++---- probert/utils.py | 17 +++-- 4 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 probert/dasd.py diff --git a/probert/dasd.py b/probert/dasd.py new file mode 100644 index 0000000..67afb27 --- /dev/null +++ b/probert/dasd.py @@ -0,0 +1,133 @@ +# Copyright 2020 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +import os +import pyudev +import re +import subprocess + +log = logging.getLogger('probert.dasd') + +DASD_FORMAT = r"^format\s+:.+\s+(?P\w+\s\w+)$" +DASD_BLKSIZE = r"^blocksize\s+:\shex\s\w+\s+dec\s(?P\d+)$" + + +def _search(regex, content, groupkey, flags=None): + if flags is None: + flags = re.MULTILINE + m = re.search(regex, content, flags) + if m: + return m.group(groupkey) + + +def blocksize(dasdview_output): + """ Read and return device_id's 'blocksize' value. + + :param: device_id: string of device ccw bus_id. + :returns: int: the device's current blocksize. + """ + if not dasdview_output: + return + + blksize = _search(DASD_BLKSIZE, dasdview_output, 'blksize') + if blksize: + return int(blksize) + + +def disk_format(dasdview_output): + """ Read and return specified device "disk_layout" value. + + :returns: string: One of ['cdl', 'ldl', 'not-formatted']. + :raises: ValueError if dasdview result missing 'format' section. + + """ + if not dasdview_output: + return + + mapping = { + 'cdl formatted': 'cdl', + 'ldl formatted': 'ldl', + 'not formatted': 'not-formatted', + } + diskfmt = _search(DASD_FORMAT, dasdview_output, 'format') + if diskfmt: + return mapping.get(diskfmt.lower()) + + +def dasdview(devname): + ''' Run dasdview on devname and return dictionary of data. + + dasdview --extended has 3 sections + general (2:6), geometry (8:12), extended (14:) + + ''' + if not os.path.exists(devname): + raise ValueError("Invalid dasd device name: '%s'" % devname) + + cmd = ['dasdview', '--extended', devname] + try: + result = subprocess.run(cmd, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except (subprocess.CalledProcessError, FileNotFoundError): + log.error('Failed to run cmd: %s', cmd) + return None + + return result.stdout.decode('utf-8') + + +def get_dasd_info(device): + """ from a udev blockdev device entry, return all required dasd info + + """ + name = device.get('DEVNAME') + device_id = device.get('ID_PATH', '').replace('ccw-', '') + dasdview_output = dasdview(name) + diskfmt = disk_format(dasdview_output) + blksize = blocksize(dasdview_output) + if not all([name, device_id, diskfmt, blksize]): + vals = ("name=%s device_id=%s format=%s blksize=%s" % ( + name, device_id, diskfmt, blksize)) + log.debug('Failed to probe some DASD values: %s', vals) + return None + + return {'name': name, 'device_id': device_id, + 'disk_layout': diskfmt, 'blocksize': blksize} + + +def probe(context=None): + """Examine all dasd devices present and extract configuration attributes + + This data is useful for determining if the dasd device has been + formatted, if so what the block size, the partition layout used + and the s390x device_id used to uniquely identify the device. + """ + log.debug('Probing DASD devies') + dasds = {} + if not context: + context = pyudev.Context() + + for device in context.list_devices(subsystem='block'): + # dasd devices have MAJOR 95 + if device['MAJOR'] != "94": + continue + # ignore dasd partitions + if 'PARTN' in device: + continue + dasd_info = get_dasd_info(device) + if dasd_info: + dasds[device['DEVNAME']] = dasd_info + + return dasds diff --git a/probert/storage.py b/probert/storage.py index a27ebe1..1b1eb05 100644 --- a/probert/storage.py +++ b/probert/storage.py @@ -19,7 +19,7 @@ import subprocess from probert.utils import udev_get_attributes, read_sys_block_size_bytes -from probert import (bcache, dmcrypt, filesystem, lvm, mount, multipath, +from probert import (bcache, dasd, dmcrypt, filesystem, lvm, mount, multipath, raid, zfs) log = logging.getLogger('probert.storage') @@ -145,6 +145,7 @@ class Storage(): probe_map = { 'bcache': bcache.probe, 'blockdev': blockdev_probe, + 'dasd': dasd.probe, 'dmcrypt': dmcrypt.probe, 'filesystem': filesystem.probe, 'lvm': lvm.probe, diff --git a/probert/tests/test_utils.py b/probert/tests/test_utils.py index 4ebb730..9c83744 100644 --- a/probert/tests/test_utils.py +++ b/probert/tests/test_utils.py @@ -1,8 +1,8 @@ import testtools -from mock import call +from mock import call, patch from probert import utils -from probert.tests.helpers import random_string, simple_mocked_open +from probert.tests.helpers import random_string class ProbertTestUtils(testtools.TestCase): @@ -38,22 +38,24 @@ def test_utils_dict_merge_dicts(self): test_result = utils.dict_merge(r1, r2) self.assertEqual(sorted(combined), sorted(test_result)) - def test_utils_read_sys_block_size_bytes(self): + @patch('probert.utils.load_file') + def test_utils_read_sys_block_size_bytes(self, m_load_file): devname = random_string() expected_fname = '/sys/class/block/%s/size' % devname expected_bytes = 10737418240 content = '20971520' - with simple_mocked_open(content=content) as m_open: - result = utils.read_sys_block_size_bytes(devname) - self.assertEqual(expected_bytes, result) - self.assertEqual([call(expected_fname)], m_open.call_args_list) + m_load_file.return_value = content.encode('utf-8') + result = utils.read_sys_block_size_bytes(devname) + self.assertEqual(expected_bytes, result) + self.assertEqual([call(expected_fname)], m_load_file.call_args_list) - def test_utils_read_sys_block_size_bytes_strips_value(self): + @patch('probert.utils.load_file') + def test_utils_read_sys_block_size_bytes_strips_value(self, m_load_file): devname = random_string() expected_fname = '/sys/class/block/%s/size' % devname expected_bytes = 10737418240 content = ' 20971520 \n ' - with simple_mocked_open(content=content) as m_open: - result = utils.read_sys_block_size_bytes(devname) - self.assertEqual(expected_bytes, result) - self.assertEqual([call(expected_fname)], m_open.call_args_list) + m_load_file.return_value = content.encode('utf-8') + result = utils.read_sys_block_size_bytes(devname) + self.assertEqual(expected_bytes, result) + self.assertEqual([call(expected_fname)], m_load_file.call_args_list) diff --git a/probert/utils.py b/probert/utils.py index e9a2113..dbd518f 100644 --- a/probert/utils.py +++ b/probert/utils.py @@ -229,14 +229,23 @@ def parse_etc_network_interfaces(ifaces, contents, path): ifaces[iface]['auto'] = False +def load_file(path, read_len=None, offset=0, decode=True): + with open(path, "rb") as fp: + if offset: + fp.seek(offset) + contents = fp.read(read_len) if read_len else fp.read() + + if decode: + return contents.decode('utf-8') + else: + return contents + + def read_sys_block_size_bytes(device): """ /sys/class/block//size and return integer value in bytes""" device_dir = os.path.join('/sys/class/block', os.path.basename(device)) blockdev_size = os.path.join(device_dir, 'size') - with open(blockdev_size) as d: - size = int(d.read().strip()) * SECTOR_SIZE_BYTES - - return size + return int(load_file(blockdev_size).strip()) * SECTOR_SIZE_BYTES def read_sys_block_slaves(device): From 8e7ebd994dce9e436e5983f4cb1744652f1e4222 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 16:49:06 -0600 Subject: [PATCH 02/11] Drop util load_file change, not needed --- probert/tests/test_utils.py | 26 ++++++++++++-------------- probert/utils.py | 17 ++++------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/probert/tests/test_utils.py b/probert/tests/test_utils.py index 9c83744..4ebb730 100644 --- a/probert/tests/test_utils.py +++ b/probert/tests/test_utils.py @@ -1,8 +1,8 @@ import testtools -from mock import call, patch +from mock import call from probert import utils -from probert.tests.helpers import random_string +from probert.tests.helpers import random_string, simple_mocked_open class ProbertTestUtils(testtools.TestCase): @@ -38,24 +38,22 @@ def test_utils_dict_merge_dicts(self): test_result = utils.dict_merge(r1, r2) self.assertEqual(sorted(combined), sorted(test_result)) - @patch('probert.utils.load_file') - def test_utils_read_sys_block_size_bytes(self, m_load_file): + def test_utils_read_sys_block_size_bytes(self): devname = random_string() expected_fname = '/sys/class/block/%s/size' % devname expected_bytes = 10737418240 content = '20971520' - m_load_file.return_value = content.encode('utf-8') - result = utils.read_sys_block_size_bytes(devname) - self.assertEqual(expected_bytes, result) - self.assertEqual([call(expected_fname)], m_load_file.call_args_list) + with simple_mocked_open(content=content) as m_open: + result = utils.read_sys_block_size_bytes(devname) + self.assertEqual(expected_bytes, result) + self.assertEqual([call(expected_fname)], m_open.call_args_list) - @patch('probert.utils.load_file') - def test_utils_read_sys_block_size_bytes_strips_value(self, m_load_file): + def test_utils_read_sys_block_size_bytes_strips_value(self): devname = random_string() expected_fname = '/sys/class/block/%s/size' % devname expected_bytes = 10737418240 content = ' 20971520 \n ' - m_load_file.return_value = content.encode('utf-8') - result = utils.read_sys_block_size_bytes(devname) - self.assertEqual(expected_bytes, result) - self.assertEqual([call(expected_fname)], m_load_file.call_args_list) + with simple_mocked_open(content=content) as m_open: + result = utils.read_sys_block_size_bytes(devname) + self.assertEqual(expected_bytes, result) + self.assertEqual([call(expected_fname)], m_open.call_args_list) diff --git a/probert/utils.py b/probert/utils.py index dbd518f..e9a2113 100644 --- a/probert/utils.py +++ b/probert/utils.py @@ -229,23 +229,14 @@ def parse_etc_network_interfaces(ifaces, contents, path): ifaces[iface]['auto'] = False -def load_file(path, read_len=None, offset=0, decode=True): - with open(path, "rb") as fp: - if offset: - fp.seek(offset) - contents = fp.read(read_len) if read_len else fp.read() - - if decode: - return contents.decode('utf-8') - else: - return contents - - def read_sys_block_size_bytes(device): """ /sys/class/block//size and return integer value in bytes""" device_dir = os.path.join('/sys/class/block', os.path.basename(device)) blockdev_size = os.path.join(device_dir, 'size') - return int(load_file(blockdev_size).strip()) * SECTOR_SIZE_BYTES + with open(blockdev_size) as d: + size = int(d.read().strip()) * SECTOR_SIZE_BYTES + + return size def read_sys_block_slaves(device): From 5d63170082a850c1ce26f4014fdaea6923e13fa2 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 16:49:43 -0600 Subject: [PATCH 03/11] Hard-code re.MULTILINE for now --- probert/dasd.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/probert/dasd.py b/probert/dasd.py index 67afb27..8b6a4b5 100644 --- a/probert/dasd.py +++ b/probert/dasd.py @@ -25,10 +25,8 @@ DASD_BLKSIZE = r"^blocksize\s+:\shex\s\w+\s+dec\s(?P\d+)$" -def _search(regex, content, groupkey, flags=None): - if flags is None: - flags = re.MULTILINE - m = re.search(regex, content, flags) +def _search(regex, content, groupkey): + m = re.search(regex, content, re.MULTILINE) if m: return m.group(groupkey) From abafbdfb7208bca70b071cfd321d559c15350883 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 16:50:25 -0600 Subject: [PATCH 04/11] dasd: fix docstring on dasdview method --- probert/dasd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/probert/dasd.py b/probert/dasd.py index 8b6a4b5..bd56258 100644 --- a/probert/dasd.py +++ b/probert/dasd.py @@ -66,7 +66,7 @@ def disk_format(dasdview_output): def dasdview(devname): - ''' Run dasdview on devname and return dictionary of data. + ''' Run dasdview on devname and return the output. dasdview --extended has 3 sections general (2:6), geometry (8:12), extended (14:) From 6bc551b87efada6536d30e1eac0c579db9a8316f Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 16:50:51 -0600 Subject: [PATCH 05/11] Fix comment on MAJOR number --- probert/dasd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/probert/dasd.py b/probert/dasd.py index bd56258..7852675 100644 --- a/probert/dasd.py +++ b/probert/dasd.py @@ -118,7 +118,7 @@ def probe(context=None): context = pyudev.Context() for device in context.list_devices(subsystem='block'): - # dasd devices have MAJOR 95 + # dasd devices have MAJOR 94 if device['MAJOR'] != "94": continue # ignore dasd partitions From d652426a88b44cb8d7d5813cd0de933ad6156989 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 16:54:57 -0600 Subject: [PATCH 06/11] Skip probing dasds unless on s390x --- probert/dasd.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/probert/dasd.py b/probert/dasd.py index 7852675..2f8a57a 100644 --- a/probert/dasd.py +++ b/probert/dasd.py @@ -15,6 +15,7 @@ import logging import os +import platform import pyudev import re import subprocess @@ -113,6 +114,11 @@ def probe(context=None): and the s390x device_id used to uniquely identify the device. """ log.debug('Probing DASD devies') + machine = platform.machine() + if machine != "s390x": + log.debug('DASD devices only present on s390x, arch=%s', machine) + return {} + dasds = {} if not context: context = pyudev.Context() From 9607089dad2b859c68782a1a2bba2219be41588e Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 16:59:04 -0600 Subject: [PATCH 07/11] Handle ValueError when fetching dasd info --- probert/dasd.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/probert/dasd.py b/probert/dasd.py index 2f8a57a..2a40578 100644 --- a/probert/dasd.py +++ b/probert/dasd.py @@ -130,7 +130,13 @@ def probe(context=None): # ignore dasd partitions if 'PARTN' in device: continue - dasd_info = get_dasd_info(device) + + try: + dasd_info = get_dasd_info(device) + except ValueError as e: + log.error('Error probing dasd device %s: %s', device['DEVNAME']) + dasd_info = None + if dasd_info: dasds[device['DEVNAME']] = dasd_info From 5216a5ababc5f1978a425bab3438a3f9709e70a2 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 17:48:17 -0600 Subject: [PATCH 08/11] Add exception to log string --- probert/dasd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/probert/dasd.py b/probert/dasd.py index 2a40578..ab7d461 100644 --- a/probert/dasd.py +++ b/probert/dasd.py @@ -134,7 +134,7 @@ def probe(context=None): try: dasd_info = get_dasd_info(device) except ValueError as e: - log.error('Error probing dasd device %s: %s', device['DEVNAME']) + log.error('Error probing dasd device %s: %s', device['DEVNAME'], e) dasd_info = None if dasd_info: From efd48e917e42b1b5550481c9b8b0393a82b1c142 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 24 Feb 2020 17:49:23 -0600 Subject: [PATCH 09/11] tests: add dasd unittests --- probert/tests/data/dasdd.view | 52 ++++++++++++++++ probert/tests/data/dasde.view | 52 ++++++++++++++++ probert/tests/test_dasd.py | 108 ++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 probert/tests/data/dasdd.view create mode 100644 probert/tests/data/dasde.view create mode 100644 probert/tests/test_dasd.py diff --git a/probert/tests/data/dasdd.view b/probert/tests/data/dasdd.view new file mode 100644 index 0000000..23ab64e --- /dev/null +++ b/probert/tests/data/dasdd.view @@ -0,0 +1,52 @@ + +--- general DASD information -------------------------------------------------- +device node : /dev/dasdd +busid : 0.0.1544 +type : ECKD +device type : hex 3390 dec 13200 + +--- DASD geometry ------------------------------------------------------------- +number of cylinders : hex 7563 dec 30051 +tracks per cylinder : hex f dec 15 +blocks per track : hex c dec 12 +blocksize : hex 1000 dec 4096 + +--- extended DASD information ------------------------------------------------- +real device number : hex 0 dec 0 +subchannel identifier : hex 1a4 dec 420 +CU type (SenseID) : hex 3990 dec 14736 +CU model (SenseID) : hex e9 dec 233 +device type (SenseID) : hex 3390 dec 13200 +device model (SenseID) : hex c dec 12 +open count : hex 2 dec 2 +req_queue_len : hex 0 dec 0 +chanq_len : hex 0 dec 0 +status : hex 5 dec 5 +label_block : hex 2 dec 2 +FBA_layout : hex 0 dec 0 +characteristics_size : hex 40 dec 64 +confdata_size : hex 100 dec 256 +format : hex 2 dec 2 CDL formatted +features : hex 0 dec 0 default + +characteristics : 3990e933 900c5e0c 39f72032 7563000f + e000e5a2 05940222 13090674 00000000 + 00000000 00000000 32321502 dfee0001 + 0677080f 007f4800 1f3c0000 00007563 + +configuration_data : dc010100 f0f0f2f1 f0f7f9f0 f0c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f10844 + d4020000 f0f0f2f1 f0f7f9f6 f1c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f10800 + d0000000 f0f0f2f1 f0f7f9f6 f1c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f00800 + f0000001 f0f0f2f1 f0f7f9f0 f0c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f10800 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 81000003 2d001e00 15000247 000c0016 + 000cc044 0a0f61ca 00030000 0000a000 diff --git a/probert/tests/data/dasde.view b/probert/tests/data/dasde.view new file mode 100644 index 0000000..c7090f1 --- /dev/null +++ b/probert/tests/data/dasde.view @@ -0,0 +1,52 @@ + +--- general DASD information -------------------------------------------------- +device node : /dev/dasde +busid : 0.0.2520 +type : ECKD +device type : hex 3390 dec 13200 + +--- DASD geometry ------------------------------------------------------------- +number of cylinders : hex 2721 dec 10017 +tracks per cylinder : hex f dec 15 +blocks per track : hex 0 dec 0 +blocksize : hex 200 dec 512 + +--- extended DASD information ------------------------------------------------- +real device number : hex 0 dec 0 +subchannel identifier : hex 5e0 dec 1504 +CU type (SenseID) : hex 3990 dec 14736 +CU model (SenseID) : hex e9 dec 233 +device type (SenseID) : hex 3390 dec 13200 +device model (SenseID) : hex c dec 12 +open count : hex 1 dec 1 +req_queue_len : hex 0 dec 0 +chanq_len : hex 0 dec 0 +status : hex 3 dec 3 +label_block : hex 2 dec 2 +FBA_layout : hex 1 dec 1 +characteristics_size : hex 40 dec 64 +confdata_size : hex 100 dec 256 +format : hex 0 dec 0 NOT formatted +features : hex 0 dec 0 default + +characteristics : 3990e933 900c5e0c 39f72032 2721000f + e000e5a2 05940222 13090674 00000000 + 00000000 00000000 32321502 dfee0001 + 0677080f 007f4800 1f3c0000 00002721 + +configuration_data : dc010100 f0f0f2f1 f0f7f9f0 f0c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f10920 + d4020000 f0f0f2f1 f0f7f9f6 f1c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f10900 + d0000000 f0f0f2f1 f0f7f9f6 f1c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f00900 + f0000001 f0f0f2f1 f0f7f9f0 f0c9c2d4 + f7f5f0f0 f0f0f0f0 f0c4e7d7 f7f10900 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 81000003 2d001e00 25000246 000c0016 + 000cc020 84228958 00030000 0000a000 diff --git a/probert/tests/test_dasd.py b/probert/tests/test_dasd.py new file mode 100644 index 0000000..777612b --- /dev/null +++ b/probert/tests/test_dasd.py @@ -0,0 +1,108 @@ +import mock +import subprocess +import testtools + +from probert import dasd +from probert.tests import fakes +from probert.tests.helpers import random_string + + +class TestDasd(testtools.TestCase): + + def _load_test_data(self, data_fname): + testfile = fakes.TEST_DATA + '/' + data_fname + with open(testfile, 'r') as fh: + return fh.read() + + @mock.patch('probert.dasd.os.path.exists') + @mock.patch('probert.dasd.subprocess.run') + def test_dasdview_returns_stdout(self, m_run, m_exists): + devname = random_string() + dasdview_out = random_string() + cp = subprocess.CompletedProcess(args=['foo'], returncode=0, + stdout=dasdview_out.encode('utf-8'), + stderr="") + m_run.return_value = cp + m_exists.return_value = True + result = dasd.dasdview(devname) + self.assertEqual(dasdview_out, result) + m_run.assert_called_with(['dasdview', '--extended', devname], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + + @mock.patch('probert.dasd.os.path.exists') + @mock.patch('probert.dasd.subprocess.run') + def test_dasdview_raises_valueerror(self, m_run, m_exists): + devname = random_string() + m_exists.return_value = False + self.assertRaises(ValueError, dasd.dasdview, devname) + self.assertEqual(0, m_run.call_count) + + @mock.patch('probert.dasd.os.path.exists') + @mock.patch('probert.dasd.subprocess.run') + def test_dasdview_returns_none_on_subprocess_error(self, m_run, m_exists): + devname = random_string() + m_exists.return_value = True + m_run.side_effect = subprocess.CalledProcessError( + cmd=[random_string()], returncode=1) + self.assertEqual(None, dasd.dasdview(devname)) + + def test_dasd_parses_blocksize(self): + self.assertEqual(4096, + dasd.blocksize(self._load_test_data('dasdd.view'))) + + def test_dasd_blocksize_returns_none_on_invalid_output(self): + self.assertIsNone(dasd.blocksize(random_string())) + + def test_dasd_parses_disk_format(self): + self.assertEqual('cdl', + dasd.disk_format(self._load_test_data('dasdd.view'))) + self.assertEqual('not-formatted', + dasd.disk_format(self._load_test_data('dasde.view'))) + + def test_dasd_parses_disk_format_ldl(self): + output = "format : hex 1 dec 1 LDL formatted" + self.assertEqual('ldl', dasd.disk_format(output)) + + def test_dasd_disk_format_returns_none_on_invalid_output(self): + self.assertIsNone(dasd.disk_format(random_string())) + + @mock.patch('probert.dasd.dasdview') + def test_get_dasd_info(self, m_dview): + devname = random_string() + id_path = random_string() + device = {'DEVNAME': devname, 'ID_PATH': 'ccw-' + id_path} + m_dview.return_value = self._load_test_data('dasdd.view') + self.assertEqual({'name': devname, 'device_id': id_path, + 'disk_layout': 'cdl', 'blocksize': 4096}, + dasd.get_dasd_info(device)) + + @mock.patch('probert.dasd.dasdview') + def test_get_dasd_info_returns_none_if_not_all(self, m_dview): + devname = random_string() + id_path = random_string() + device = {'DEVNAME': devname, 'ID_PATH': 'ccw-' + id_path} + m_dview.return_value = random_string() + self.assertIsNone(dasd.get_dasd_info(device)) + + @mock.patch('probert.dasd.blocksize') + @mock.patch('probert.dasd.dasdview') + def test_get_dasd_info_returns_none_if_bad_blocksize(self, m_dview, + m_block): + devname = random_string() + id_path = random_string() + device = {'DEVNAME': devname, 'ID_PATH': 'ccw-' + id_path} + m_dview.return_value = self._load_test_data('dasdd.view') + m_block.return_value = None + self.assertIsNone(dasd.get_dasd_info(device)) + + @mock.patch('probert.dasd.blocksize') + @mock.patch('probert.dasd.dasdview') + def test_get_dasd_info_returns_none_if_bad_disk_format(self, m_dview, + m_disk): + devname = random_string() + id_path = random_string() + device = {'DEVNAME': devname, 'ID_PATH': 'ccw-' + id_path} + m_dview.return_value = self._load_test_data('dasdd.view') + m_disk.return_value = None + self.assertIsNone(dasd.get_dasd_info(device)) From a0acebbdf8fe16afc534e1cee5c01e4a6f3a2811 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Tue, 25 Feb 2020 10:01:24 -0600 Subject: [PATCH 10/11] debian/control: add s390x dep on s390-tools --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index cdbddb3..76fd21c 100644 --- a/debian/control +++ b/debian/control @@ -53,6 +53,7 @@ Depends: probert-common (= ${source:Version}), lvm2, mdadm, multipath-tools, + s390-tools [s390x], zfsutils-linux, ${misc:Depends}, ${python3:Depends}, From c85be86cf202eaad4162e11bd8ecf494400c45bb Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Tue, 25 Feb 2020 11:22:32 -0600 Subject: [PATCH 11/11] dasd: add test of probe function --- probert/tests/test_dasd.py | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/probert/tests/test_dasd.py b/probert/tests/test_dasd.py index 777612b..f3b4377 100644 --- a/probert/tests/test_dasd.py +++ b/probert/tests/test_dasd.py @@ -106,3 +106,66 @@ def test_get_dasd_info_returns_none_if_bad_disk_format(self, m_dview, m_dview.return_value = self._load_test_data('dasdd.view') m_disk.return_value = None self.assertIsNone(dasd.get_dasd_info(device)) + + @mock.patch('probert.dasd.platform.machine') + def test_dasd_probe_returns_empty_dict_non_s390x_arch(self, m_machine): + machine = random_string() + self.assertNotEqual("s390x", machine) + m_machine.return_value = machine + self.assertEqual({}, dasd.probe()) + + @mock.patch('probert.dasd.platform.machine') + @mock.patch('probert.dasd.dasdview') + def test_dasd_probe_dasdd(self, m_dasdview, m_machine): + m_machine.return_value = 's390x' + m_dasdview.side_effect = iter([self._load_test_data('dasdd.view')]) + + context = mock.MagicMock() + context.list_devices.side_effect = iter([ + [{"MAJOR": "94", "DEVNAME": "/dev/dasdd", "ID_SERIAL": "0X1544", + "ID_PATH": "ccw-0.0.1544"}], + ]) + expected_results = { + '/dev/dasdd': { + 'name': '/dev/dasdd', 'device_id': '0.0.1544', + 'disk_layout': 'cdl', 'blocksize': 4096}, + } + self.assertEqual(expected_results, dasd.probe(context=context)) + + @mock.patch('probert.dasd.platform.machine') + @mock.patch('probert.dasd.dasdview') + def test_dasd_probe_dasde(self, m_dasdview, m_machine): + m_machine.return_value = 's390x' + m_dasdview.side_effect = iter([self._load_test_data('dasde.view')]) + + context = mock.MagicMock() + context.list_devices.side_effect = iter([ + [{"MAJOR": "94", "DEVNAME": "/dev/dasde", + "ID_PATH": "ccw-0.0.2250"}], + ]) + expected_results = { + '/dev/dasde': { + 'name': '/dev/dasde', 'device_id': '0.0.2250', + 'disk_layout': 'not-formatted', 'blocksize': 512}, + } + self.assertEqual(expected_results, dasd.probe(context=context)) + + @mock.patch('probert.dasd.platform.machine') + @mock.patch('probert.dasd.dasdview') + def test_dasd_probe_dasdd_skips_partitions(self, m_dasdview, m_machine): + m_machine.return_value = 's390x' + m_dasdview.side_effect = iter([self._load_test_data('dasdd.view')]) + + context = mock.MagicMock() + context.list_devices.side_effect = iter([ + [{"MAJOR": "94", "DEVNAME": "/dev/dasdd", "ID_SERIAL": "0X1544", + "ID_PATH": "ccw-0.0.1544"}], + [{"MAJOR": "94", "DEVNAME": "/dev/dasdd1", "ID_SERIAL": "0X1544", + "ID_PATH": "ccw-0.0.1544", "PARTN": "1"}], + ]) + expected_results = { + '/dev/dasdd': { + 'name': '/dev/dasdd', 'device_id': '0.0.1544', + 'disk_layout': 'cdl', 'blocksize': 4096}, + } + self.assertEqual(expected_results, dasd.probe(context=context))