Add some real-world testing on DiskPartitioner
This patch adds some integration tests for DiskPartitioner by using it on a temporary file. As a side effect, this patch adds disk_partitioner.list_partitions() helper, which is used by the test, but can by used for anything else later. The new tests are currently skipped on gate-ironic-python26, as there is no `parted` utility there. Also these tests do not cover destroy_disk_metadata, as it can't be executed on a file. Change-Id: I53bf2d207b7d15a0673be6a6786c47500b9eb499 Closes-Bug: #1306153
This commit is contained in:
parent
2a9c6acd57
commit
fc7dfcc543
|
@ -13,9 +13,14 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
from ironic.common import utils
|
||||
from ironic.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DiskPartitioner(object):
|
||||
|
@ -96,3 +101,35 @@ class DiskPartitioner(object):
|
|||
# the "device is busy" problem. lsof, fuser...
|
||||
# avoid "device is busy"
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
_PARTED_PRINT_RE = re.compile(r"^\d+:([\d\.]+)MiB:"
|
||||
"([\d\.]+)MiB:([\d\.]+)MiB:(\w*)::(\w*)")
|
||||
|
||||
|
||||
def list_partitions(device):
|
||||
"""Get partitions information from given device.
|
||||
|
||||
:param device: The device path.
|
||||
:returns: list of dictionaries (one per partition) with keys:
|
||||
start, end, size (in MiB), filesystem, flags
|
||||
"""
|
||||
output = utils.execute(
|
||||
'parted', '-s', '-m', device, 'unit', 'MiB', 'print')[0]
|
||||
lines = [line for line in output.split('\n') if line.strip()][2:]
|
||||
# Example of line: 1:1.00MiB:501MiB:500MiB:ext4::boot
|
||||
fields = ('start', 'end', 'size', 'filesystem', 'flags')
|
||||
result = []
|
||||
for line in lines:
|
||||
match = _PARTED_PRINT_RE.match(line)
|
||||
if match is None:
|
||||
LOG.warn(_("Partition information from parted for device "
|
||||
"%(device)s does not match "
|
||||
"expected format: %(line)s"),
|
||||
dict(device=device, line=line))
|
||||
continue
|
||||
# Cast int fields to ints (some are floats and we round them down)
|
||||
groups = [int(float(x)) if i < 3 else x
|
||||
for i, x in enumerate(match.groups())]
|
||||
result.append(dict(zip(fields, groups)))
|
||||
return result
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import itertools
|
||||
import time
|
||||
|
||||
import fixtures
|
||||
|
@ -585,3 +586,80 @@ class GetDeviceBlockSizeTestCase(tests_base.TestCase):
|
|||
run_as_root=True, check_exit_code=[0])]
|
||||
utils.get_dev_block_size(self.dev)
|
||||
mock_exec.assert_has_calls(expected_call)
|
||||
|
||||
|
||||
@mock.patch.object(utils, 'is_block_device', lambda d: True)
|
||||
@mock.patch.object(utils, 'block_uuid', lambda p: 'uuid')
|
||||
@mock.patch.object(utils, 'dd', lambda *_: None)
|
||||
@mock.patch.object(common_utils, 'mkfs', lambda *_: None)
|
||||
# TODO(dtantsur): remove once https://review.openstack.org/90126 lands
|
||||
@mock.patch.object(time, 'sleep', lambda *_: None)
|
||||
# NOTE(dtantsur): destroy_disk_metadata resets file size, disabling it
|
||||
@mock.patch.object(utils, 'destroy_disk_metadata', lambda *_: None)
|
||||
class RealFilePartitioningTestCase(tests_base.TestCase):
|
||||
"""This test applies some real-world partitioning scenario to a file.
|
||||
|
||||
This test covers the whole partitioning, mocking everything not possible
|
||||
on a file. That helps us assure, that we do all partitioning math properly
|
||||
and also conducts integration testing of DiskPartitioner.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(RealFilePartitioningTestCase, self).setUp()
|
||||
# NOTE(dtantsur): no parted utility on gate-ironic-python26
|
||||
try:
|
||||
common_utils.execute('parted', '--version')
|
||||
except OSError as exc:
|
||||
self.skipTest('parted utility was not found: %s' % exc)
|
||||
self.file = tempfile.NamedTemporaryFile()
|
||||
self.addCleanup(lambda: self.file.close())
|
||||
# NOTE(dtantsur): 20 MiB file with zeros
|
||||
common_utils.execute('dd', 'if=/dev/zero', 'of=%s' % self.file.name,
|
||||
'bs=1', 'count=0', 'seek=20MiB')
|
||||
|
||||
@staticmethod
|
||||
def _run_without_root(func, *args, **kwargs):
|
||||
"""Make sure root is not required when using utils.execute."""
|
||||
real_execute = common_utils.execute
|
||||
|
||||
def fake_execute(*cmd, **kwargs):
|
||||
kwargs['run_as_root'] = False
|
||||
return real_execute(*cmd, **kwargs)
|
||||
|
||||
with mock.patch.object(common_utils, 'execute', fake_execute):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
def test_different_sizes(self):
|
||||
# NOTE(dtantsur): Keep this list in order with expected partitioning
|
||||
fields = ['ephemeral_mb', 'swap_mb', 'root_mb']
|
||||
variants = itertools.product([0, 1, 5], repeat=3)
|
||||
for variant in variants:
|
||||
if variant == (0, 0, 0):
|
||||
continue
|
||||
kwargs = dict(zip(fields, variant))
|
||||
self._run_without_root(utils.work_on_disk, self.file.name,
|
||||
ephemeral_format='ext4', node_uuid='',
|
||||
image_path='path', **kwargs)
|
||||
part_table = self._run_without_root(
|
||||
disk_partitioner.list_partitions, self.file.name)
|
||||
for part, expected_size in zip(part_table, filter(None, variant)):
|
||||
self.assertEqual(expected_size, part['size'],
|
||||
"comparison failed for %s" % list(variant))
|
||||
|
||||
def test_whole_disk(self):
|
||||
# 6 MiB ephemeral + 3 MiB swap + 9 MiB root + 1 MiB for MBR
|
||||
# + 1 MiB MAGIC == 20 MiB whole disk
|
||||
# TODO(dtantsur): figure out why we need 'magic' 1 more MiB
|
||||
# and why the is different on Ubuntu and Fedora (see below)
|
||||
self._run_without_root(utils.work_on_disk, self.file.name,
|
||||
root_mb=9, ephemeral_mb=6, swap_mb=3,
|
||||
ephemeral_format='ext4', node_uuid='',
|
||||
image_path='path')
|
||||
part_table = self._run_without_root(
|
||||
disk_partitioner.list_partitions, self.file.name)
|
||||
sizes = [part['size'] for part in part_table]
|
||||
# NOTE(dtantsur): parted in Ubuntu 12.04 will occupy the last MiB,
|
||||
# parted in Fedora 20 won't - thus two possible variants for last part
|
||||
self.assertEqual([6, 3], sizes[:2],
|
||||
"unexpected partitioning %s" % part_table)
|
||||
self.assertIn(sizes[2], (9, 10))
|
||||
|
|
|
@ -66,3 +66,37 @@ class DiskPartitionerTestCase(base.TestCase):
|
|||
'mkpart', 'fake-type', 'fake-fs-type', '1', '2',
|
||||
'mkpart', 'fake-type', 'fake-fs-type', '2', '3',
|
||||
'set', '2', 'boot', 'on')
|
||||
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
class ListPartitionsTestCase(base.TestCase):
|
||||
|
||||
def test_correct(self, execute_mock):
|
||||
output = """
|
||||
BYT;
|
||||
/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:;
|
||||
1:1.00MiB:501MiB:500MiB:ext4::boot;
|
||||
2:501MiB:476940MiB:476439MiB:::;
|
||||
"""
|
||||
expected = [
|
||||
{'start': 1, 'end': 501, 'size': 500,
|
||||
'filesystem': 'ext4', 'flags': 'boot'},
|
||||
{'start': 501, 'end': 476940, 'size': 476439,
|
||||
'filesystem': '', 'flags': ''},
|
||||
]
|
||||
execute_mock.return_value = (output, '')
|
||||
result = disk_partitioner.list_partitions('/dev/fake')
|
||||
self.assertEqual(expected, result)
|
||||
execute_mock.assert_called_once_with(
|
||||
'parted', '-s', '-m', '/dev/fake', 'unit', 'MiB', 'print')
|
||||
|
||||
@mock.patch.object(disk_partitioner.LOG, 'warn')
|
||||
def test_incorrect(self, log_mock, execute_mock):
|
||||
output = """
|
||||
BYT;
|
||||
/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:;
|
||||
1:XX1076MiB:---:524MiB:ext4::boot;
|
||||
"""
|
||||
execute_mock.return_value = (output, '')
|
||||
self.assertEqual([], disk_partitioner.list_partitions('/dev/fake'))
|
||||
self.assertEqual(1, log_mock.call_count)
|
||||
|
|
Loading…
Reference in New Issue