Add option to choose partition alignment mode

Under certain circumstances due to that alignment, the end of
particular partition could cross 1M boundary. And due to actual
partition' bounderies being rounded up, fuel-agent mistakenly
assumes that partition couldn't fit within provided boundaries
and raises errors.WrongPartitionSchemeError.

However, some h/w data storages are well known for reporting
relatively huge optimal IO sizes (16M or even bigger), so the 1M
room can't be enough. Thus, optimal aligning is not an option.
In such cases, it's better to proceed with minimal mode.

It's the minimum aligment needed to align the partition properly
to physical blocks which avoids performance degradation.

Partial-Bug: #1543233
DocImpact

Change-Id: I1dd94731f497f3deb47cec9a23957e3706c2fb3b
This commit is contained in:
Alexander Gordeev 2016-05-05 15:52:40 +03:00
parent ce4af55423
commit af247ea07a
6 changed files with 93 additions and 31 deletions

View File

@ -163,6 +163,12 @@ opts = [
'volumes. Devices in directories outside this hierarchy will be '
'ignored.'
),
cfg.StrOpt(
'partition_alignment',
default='optimal',
help='Set alignment for newly created partitions, valid alignment '
'types are: none, cylinder, minimal, optimal'
),
cfg.StrOpt(
'lvm_conf_path',
default='/etc/lvm/lvm.conf',
@ -217,7 +223,8 @@ class Manager(object):
for parted in parteds:
pu.make_label(parted.name, parted.label)
for prt in parted.partitions:
pu.make_partition(prt.device, prt.begin, prt.end, prt.type)
pu.make_partition(prt.device, prt.begin, prt.end, prt.type,
alignment=CONF.partition_alignment)
utils.udevadm_trigger_blocks()
for flag in prt.flags:
pu.set_partition_flag(prt.device, prt.count, flag)

View File

@ -89,8 +89,10 @@ class Parted(base.Serializable):
if not self.partitions:
return 1
if self.partitions[-1] == self.extended:
return self.partitions[-1].begin
return self.partitions[-1].end
# NOTE(agordeev): this 1M room could be enough for minimal
# partition alignment mode for the most of cases.
return self.partitions[-1].begin + 1
return self.partitions[-1].end + 1
def next_name(self):
if self.next_type() == 'extended':

View File

@ -447,19 +447,21 @@ class TestManager(unittest2.TestCase):
self.assertEqual(mock_pu_ml_expected_calls, mock_pu_ml.call_args_list)
mock_pu_mp_expected_calls = [
mock.call('/dev/sda', 1, 25, 'primary'),
mock.call('/dev/sda', 25, 225, 'primary'),
mock.call('/dev/sda', 225, 425, 'primary'),
mock.call('/dev/sda', 425, 625, 'primary'),
mock.call('/dev/sda', 625, 20063, 'primary'),
mock.call('/dev/sda', 20063, 65660, 'primary'),
mock.call('/dev/sda', 65660, 65680, 'primary'),
mock.call('/dev/sdb', 1, 25, 'primary'),
mock.call('/dev/sdb', 25, 225, 'primary'),
mock.call('/dev/sdb', 225, 65196, 'primary'),
mock.call('/dev/sdc', 1, 25, 'primary'),
mock.call('/dev/sdc', 25, 225, 'primary'),
mock.call('/dev/sdc', 225, 65196, 'primary')]
mock.call('/dev/sda', 1, 25, 'primary', alignment='optimal'),
mock.call('/dev/sda', 26, 226, 'primary', alignment='optimal'),
mock.call('/dev/sda', 227, 427, 'primary', alignment='optimal'),
mock.call('/dev/sda', 428, 628, 'primary', alignment='optimal'),
mock.call('/dev/sda', 629, 20067, 'primary', alignment='optimal'),
mock.call('/dev/sda', 20068, 65665, 'primary',
alignment='optimal'),
mock.call('/dev/sda', 65666, 65686, 'primary',
alignment='optimal'),
mock.call('/dev/sdb', 1, 25, 'primary', alignment='optimal'),
mock.call('/dev/sdb', 26, 226, 'primary', alignment='optimal'),
mock.call('/dev/sdb', 227, 65198, 'primary', alignment='optimal'),
mock.call('/dev/sdc', 1, 25, 'primary', alignment='optimal'),
mock.call('/dev/sdc', 26, 226, 'primary', alignment='optimal'),
mock.call('/dev/sdc', 227, 65198, 'primary', alignment='optimal')]
self.assertEqual(mock_pu_mp_expected_calls, mock_pu_mp.call_args_list)
mock_pu_spf_expected_calls = [mock.call('/dev/sda', 1, 'bios_grub'),
@ -1230,6 +1232,7 @@ class TestManagerMultipathPartition(unittest2.TestCase):
test_nailgun.MPATH_DISK_KS_SPACES
self.mgr = manager.Manager(data)
@mock.patch.object(mu, 'mdclean_all')
@mock.patch.object(manager.utils, 'refresh_multipath')
@mock.patch.object(hu, 'is_multipath_device')
@mock.patch.object(manager.os.path, 'exists')
@ -1240,7 +1243,7 @@ class TestManagerMultipathPartition(unittest2.TestCase):
@mock.patch.object(hu, 'list_block_devices')
def test_do_partitioning_mp(self, mock_hu_lbd, mock_fu_mf, mock_exec,
mock_unbl, mock_bl, mock_os_path, mock_mp,
mock_refresh_multipath):
mock_refresh_multipath, mock_mdclean_all):
mock_os_path.return_value = True
mock_hu_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_MPATH
self.mgr._make_partitions = mock.MagicMock()
@ -1300,14 +1303,19 @@ class TestManagerMultipathPartition(unittest2.TestCase):
mock.call('/dev/sdc', 'gpt')])
self.assertEqual(mock_make_partition.mock_calls, [
mock.call('/dev/mapper/12312', 1, 25, 'primary'),
mock.call('/dev/mapper/12312', 25, 225, 'primary'),
mock.call('/dev/mapper/12312', 225, 425, 'primary'),
mock.call('/dev/mapper/12312', 425, 625, 'primary'),
mock.call('/dev/mapper/12312', 625, 645, 'primary'),
mock.call('/dev/sdc', 1, 25, 'primary'),
mock.call('/dev/sdc', 25, 225, 'primary'),
mock.call('/dev/sdc', 225, 425, 'primary')])
mock.call('/dev/mapper/12312', 1, 25, 'primary',
alignment='optimal'),
mock.call('/dev/mapper/12312', 26, 226, 'primary',
alignment='optimal'),
mock.call('/dev/mapper/12312', 227, 427, 'primary',
alignment='optimal'),
mock.call('/dev/mapper/12312', 428, 628, 'primary',
alignment='optimal'),
mock.call('/dev/mapper/12312', 629, 649, 'primary',
alignment='optimal'),
mock.call('/dev/sdc', 1, 25, 'primary', alignment='optimal'),
mock.call('/dev/sdc', 26, 226, 'primary', alignment='optimal'),
mock.call('/dev/sdc', 227, 427, 'primary', alignment='optimal')])
self.assertEqual(mock_set_partition_flag.mock_calls, [
mock.call('/dev/mapper/12312', 1, 'bios_grub'),

View File

@ -286,15 +286,15 @@ class TestParted(unittest2.TestCase):
def test_next_begin_last_extended_partition(self):
self.prtd.partitions.append(
objects.Partition('name', 'count', 'device', 'begin', 'end',
objects.Partition('name', 'count', 'device', 1, 100,
'extended'))
self.assertEqual('begin', self.prtd.next_begin())
self.assertEqual(2, self.prtd.next_begin())
def test_next_begin_no_last_extended_partition(self):
self.prtd.partitions.append(
objects.Partition('name', 'count', 'device', 'begin', 'end',
objects.Partition('name', 'count', 'device', 1, 100,
'primary'))
self.assertEqual('end', self.prtd.next_begin())
self.assertEqual(101, self.prtd.next_begin())
def test_next_count_no_logical(self):
self.assertEqual(1, self.prtd.next_count('primary'))

View File

@ -134,6 +134,35 @@ class TestPartitionUtils(unittest2.TestCase):
self.assertEqual(mock_exec_expected_calls, mock_exec.call_args_list)
mock_rerd.assert_called_once_with('/dev/fake', out='out')
@mock.patch.object(utils, 'udevadm_settle')
@mock.patch.object(pu, 'reread_partitions')
@mock.patch.object(pu, 'info')
@mock.patch.object(utils, 'execute')
def test_make_partition_minimal(self, mock_exec, mock_info, mock_rerd,
mock_udev):
# should run parted OS command
# in order to create new partition
mock_exec.return_value = ('out', '')
mock_info.return_value = {
'parts': [
{'begin': 0, 'end': 1000, 'fstype': 'free'},
]
}
pu.make_partition('/dev/fake', 100, 200, 'primary',
alignment='minimal')
mock_exec_expected_calls = [
mock.call('parted', '-a', 'minimal', '-s', '/dev/fake', 'unit',
'MiB', 'mkpart', 'primary', '100', '200',
check_exit_code=[0, 1])]
mock_udev.assert_called_once_with()
self.assertEqual(mock_exec_expected_calls, mock_exec.call_args_list)
mock_rerd.assert_called_once_with('/dev/fake', out='out')
def test_make_partition_wrong_alignment(self):
self.assertRaises(errors.WrongPartitionSchemeError, pu.make_partition,
'/dev/fake', 1, 10, 'primary', 'invalid')
@mock.patch.object(utils, 'execute')
def test_make_partition_wrong_ptype(self, mock_exec):
# should check if partition type is one of

View File

@ -19,6 +19,7 @@ from fuel_agent.openstack.common import log as logging
from fuel_agent.utils import utils
LOG = logging.getLogger(__name__)
PARTITION_ALIGMENT = ('none', 'cylinder', 'minimal', 'optimal')
def parse_partition_info(output):
@ -135,12 +136,27 @@ def set_gpt_type(dev, num, type_guid):
dev, check_exit_code=[0])
def make_partition(dev, begin, end, ptype):
def make_partition(dev, begin, end, ptype, alignment='optimal'):
"""Creates a partition on the device.
:param dev: A device file, e.g. /dev/sda.
:param begin: Beginning of the partition.
:param end: Ending of the partition.
:param ptype: Partition type: primary or logical.
:param alignment: Set alignment mode for newly created partitions,
valid alignment types are: none, cylinder, minimal, optimal. For more
information about this you can find in GNU parted manual.
:returns: None
"""
LOG.debug('Trying to create a partition: dev=%s begin=%s end=%s' %
(dev, begin, end))
if ptype not in ('primary', 'logical'):
raise errors.WrongPartitionSchemeError(
'Wrong partition type: %s' % ptype)
if alignment not in PARTITION_ALIGMENT:
raise errors.WrongPartitionSchemeError(
'Wrong partition alignment requested: %s' % alignment)
# check begin >= end
if begin >= end:
@ -156,7 +172,7 @@ def make_partition(dev, begin, end, ptype):
utils.udevadm_settle()
out, err = utils.execute(
'parted', '-a', 'optimal', '-s', dev, 'unit', 'MiB',
'parted', '-a', alignment, '-s', dev, 'unit', 'MiB',
'mkpart', ptype, str(begin), str(end), check_exit_code=[0, 1])
LOG.debug('Parted output: \n%s' % out)
reread_partitions(dev, out=out)