Merge "Install grub to PReP partition when prep_boot_part_uuid is provided"

This commit is contained in:
Zuul 2018-08-01 20:02:12 +00:00 committed by Gerrit Code Review
commit aac2776c9f
7 changed files with 124 additions and 15 deletions

View File

@ -76,7 +76,8 @@ def _get_partition(device, uuid):
raise errors.CommandExecutionError(error_msg)
def _install_grub2(device, root_uuid, efi_system_part_uuid=None):
def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
prep_boot_part_uuid=None):
"""Install GRUB2 bootloader on a given device."""
LOG.debug("Installing GRUB2 bootloader on device %s", device)
root_partition = _get_partition(device, uuid=root_uuid)
@ -92,6 +93,10 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None):
efi_partition = _get_partition(device, uuid=efi_system_part_uuid)
efi_partition_mount_point = os.path.join(path, "boot/efi")
# For power we want to install grub directly onto the PreP partition
if prep_boot_part_uuid:
device = _get_partition(device, uuid=prep_boot_part_uuid)
utils.execute('mount', root_partition, path)
for fs in BIND_MOUNTS:
utils.execute('mount', '-o', 'bind', fs, path + fs)
@ -196,13 +201,17 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None):
class ImageExtension(base.BaseAgentExtension):
@base.sync_command('install_bootloader')
def install_bootloader(self, root_uuid, efi_system_part_uuid=None):
def install_bootloader(self, root_uuid, efi_system_part_uuid=None,
prep_boot_part_uuid=None):
"""Install the GRUB2 bootloader on the image.
:param root_uuid: The UUID of the root partition.
:param efi_system_part_uuid: The UUID of the efi system partition.
To be used only for uefi boot mode. For uefi boot mode, the
boot loader will be installed here.
:param prep_boot_part_uuid: The UUID of the PReP Boot partition.
Used only for booting ppc64* partition images locally. In this
scenario the bootloader will be installed here.
:raises: CommandExecutionError if the installation of the
bootloader fails.
:raises: DeviceNotFound if the root partition is not found.
@ -212,4 +221,5 @@ class ImageExtension(base.BaseAgentExtension):
iscsi.clean_up(device)
_install_grub2(device,
root_uuid=root_uuid,
efi_system_part_uuid=efi_system_part_uuid)
efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=prep_boot_part_uuid)

View File

@ -73,6 +73,9 @@ def _write_partition_image(image, image_info, device):
disk_label = image_info.get('disk_label', 'msdos')
image_mb = disk_utils.get_image_mb(image)
root_mb = image_info['root_mb']
cpu_arch = hardware.dispatch_to_managers('get_cpus').architecture
if image_mb > int(root_mb):
msg = ('Root partition is too small for requested image. Image '
'virtual size: {} MB, Root size: {} MB').format(image_mb,
@ -88,7 +91,8 @@ def _write_partition_image(image, image_info, device):
configdrive=configdrive,
boot_option=boot_option,
boot_mode=boot_mode,
disk_label=disk_label)
disk_label=disk_label,
cpu_arch=cpu_arch)
except processutils.ProcessExecutionError as e:
raise errors.ImageWriteError(device, e.exit_code, e.stdout, e.stderr)

View File

@ -40,8 +40,10 @@ class TestImageExtension(base.IronicAgentTest):
self.fake_dev = '/dev/fake'
self.fake_efi_system_part = '/dev/fake1'
self.fake_root_part = '/dev/fake2'
self.fake_prep_boot_part = '/dev/fake3'
self.fake_root_uuid = '11111111-2222-3333-4444-555555555555'
self.fake_efi_system_part_uuid = '45AB-2312'
self.fake_prep_boot_part_uuid = '76937797-3253-8843-999999999999'
self.fake_dir = '/tmp/fake-dir'
@mock.patch.object(iscsi, 'clean_up', autospec=True)
@ -53,7 +55,7 @@ class TestImageExtension(base.IronicAgentTest):
mock_dispatch.assert_called_once_with('get_os_install_device')
mock_grub2.assert_called_once_with(
self.fake_dev, root_uuid=self.fake_root_uuid,
efi_system_part_uuid=None)
efi_system_part_uuid=None, prep_boot_part_uuid=None)
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
@mock.patch.object(iscsi, 'clean_up', autospec=True)
@ -68,7 +70,25 @@ class TestImageExtension(base.IronicAgentTest):
mock_grub2.assert_called_once_with(
self.fake_dev,
root_uuid=self.fake_root_uuid,
efi_system_part_uuid=self.fake_efi_system_part_uuid)
efi_system_part_uuid=self.fake_efi_system_part_uuid,
prep_boot_part_uuid=None)
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
@mock.patch.object(iscsi, 'clean_up', autospec=True)
@mock.patch.object(image, '_install_grub2', autospec=True)
def test_install_bootloader_prep(self, mock_grub2, mock_iscsi_clean,
mock_execute, mock_dispatch):
mock_dispatch.return_value = self.fake_dev
self.agent_extension.install_bootloader(
root_uuid=self.fake_root_uuid,
efi_system_part_uuid=None,
prep_boot_part_uuid=self.fake_prep_boot_part_uuid)
mock_dispatch.assert_called_once_with('get_os_install_device')
mock_grub2.assert_called_once_with(
self.fake_dev,
root_uuid=self.fake_root_uuid,
efi_system_part_uuid=None,
prep_boot_part_uuid=self.fake_prep_boot_part_uuid)
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
@mock.patch.object(os, 'environ', autospec=True)
@ -108,6 +128,48 @@ class TestImageExtension(base.IronicAgentTest):
uuid=self.fake_root_uuid)
self.assertFalse(mock_dispatch.called)
@mock.patch.object(os, 'environ', autospec=True)
@mock.patch.object(image, '_get_partition', autospec=True)
def test__install_grub2_prep(self, mock_get_part_uuid, environ_mock,
mock_execute, mock_dispatch):
mock_get_part_uuid.side_effect = [self.fake_root_part,
self.fake_prep_boot_part]
environ_mock.get.return_value = '/sbin'
image._install_grub2(self.fake_dev, self.fake_root_uuid,
prep_boot_part_uuid=self.fake_prep_boot_part_uuid)
expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
mock.call('mount', '-o', 'bind', '/dev',
self.fake_dir + '/dev'),
mock.call('mount', '-o', 'bind', '/proc',
self.fake_dir + '/proc'),
mock.call('mount', '-t', 'sysfs', 'none',
self.fake_dir + '/sys'),
mock.call(('chroot %s /bin/sh -c '
'"grub-install %s"' %
(self.fake_dir, self.fake_prep_boot_part)),
shell=True,
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
mock.call(('chroot %s /bin/sh -c '
'"grub-mkconfig -o '
'/boot/grub/grub.cfg"' % self.fake_dir),
shell=True,
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
mock.call('umount', self.fake_dir + '/dev',
attempts=3, delay_on_retry=True),
mock.call('umount', self.fake_dir + '/proc',
attempts=3, delay_on_retry=True),
mock.call('umount', self.fake_dir + '/sys',
attempts=3, delay_on_retry=True),
mock.call('umount', self.fake_dir, attempts=3,
delay_on_retry=True)]
mock_execute.assert_has_calls(expected)
mock_get_part_uuid.assert_any_call(self.fake_dev,
uuid=self.fake_root_uuid)
mock_get_part_uuid.assert_any_call(self.fake_dev,
uuid=self.fake_prep_boot_part_uuid)
self.assertFalse(mock_dispatch.called)
@mock.patch.object(os, 'environ', autospec=True)
@mock.patch.object(os, 'makedirs', autospec=True)
@mock.patch.object(image, '_get_partition', autospec=True)

View File

@ -19,6 +19,7 @@ from oslo_concurrency import processutils
from ironic_python_agent import errors
from ironic_python_agent.extensions import standby
from ironic_python_agent import hardware
from ironic_python_agent.tests.unit import base
@ -58,6 +59,11 @@ class TestStandbyExtension(base.IronicAgentTest):
def setUp(self):
super(TestStandbyExtension, self).setUp()
self.agent_extension = standby.StandbyExtension()
self.fake_cpu = hardware.CPU(model_name='fuzzypickles',
frequency=1024,
count=1,
architecture='generic',
flags='')
def test_validate_image_info_success(self):
standby._validate_image_info(None, _build_fake_image_info())
@ -137,13 +143,15 @@ class TestStandbyExtension(base.IronicAgentTest):
execute_mock.assert_called_once_with(*command, check_exit_code=[0])
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
@mock.patch('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
@mock.patch('ironic_lib.disk_utils.get_image_mb', autospec=True)
@mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True)
def test_write_partition_image_exception(self, work_on_disk_mock,
image_mb_mock,
execute_mock, open_mock):
execute_mock, open_mock,
dispatch_mock):
image_info = _build_fake_partition_image_info()
device = '/dev/sda'
root_mb = image_info['root_mb']
@ -156,10 +164,12 @@ class TestStandbyExtension(base.IronicAgentTest):
boot_mode = image_info['deploy_boot_mode']
boot_option = image_info['boot_option']
disk_label = image_info['disk_label']
cpu_arch = self.fake_cpu.architecture
image_path = standby._image_location(image_info)
image_mb_mock.return_value = 1
dispatch_mock.return_value = self.fake_cpu
exc = errors.ImageWriteError
Exception_returned = processutils.ProcessExecutionError
work_on_disk_mock.side_effect = Exception_returned
@ -176,15 +186,18 @@ class TestStandbyExtension(base.IronicAgentTest):
preserve_ephemeral=pr_ep,
boot_mode=boot_mode,
boot_option=boot_option,
disk_label=disk_label)
disk_label=disk_label,
cpu_arch=cpu_arch)
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
@mock.patch('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
@mock.patch('ironic_lib.disk_utils.get_image_mb', autospec=True)
@mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True)
def test_write_partition_image_no_node_uuid(self, work_on_disk_mock,
image_mb_mock,
execute_mock, open_mock):
execute_mock, open_mock,
dispatch_mock):
image_info = _build_fake_partition_image_info()
image_info['node_uuid'] = None
device = '/dev/sda'
@ -198,10 +211,12 @@ class TestStandbyExtension(base.IronicAgentTest):
boot_mode = image_info['deploy_boot_mode']
boot_option = image_info['boot_option']
disk_label = image_info['disk_label']
cpu_arch = self.fake_cpu.architecture
image_path = standby._image_location(image_info)
image_mb_mock.return_value = 1
dispatch_mock.return_value = self.fake_cpu
uuids = {'root uuid': 'root_uuid'}
expected_uuid = {'root uuid': 'root_uuid'}
image_mb_mock.return_value = 1
@ -218,11 +233,13 @@ class TestStandbyExtension(base.IronicAgentTest):
preserve_ephemeral=pr_ep,
boot_mode=boot_mode,
boot_option=boot_option,
disk_label=disk_label)
disk_label=disk_label,
cpu_arch=cpu_arch)
self.assertEqual(expected_uuid, work_on_disk_mock.return_value)
self.assertIsNone(node_uuid)
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
@mock.patch('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
@mock.patch('ironic_lib.disk_utils.get_image_mb', autospec=True)
@ -231,12 +248,15 @@ class TestStandbyExtension(base.IronicAgentTest):
work_on_disk_mock,
image_mb_mock,
execute_mock,
open_mock):
open_mock,
dispatch_mock):
dispatch_mock.return_value = self.fake_cpu
image_info = _build_fake_partition_image_info()
device = '/dev/sda'
image_path = standby._image_location(image_info)
image_mb_mock.return_value = 20
exc = errors.InvalidCommandParamsError
self.assertRaises(exc, standby._write_image, image_info,
@ -244,12 +264,13 @@ class TestStandbyExtension(base.IronicAgentTest):
image_mb_mock.assert_called_once_with(image_path)
self.assertFalse(work_on_disk_mock.called)
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
@mock.patch('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
@mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True)
@mock.patch('ironic_lib.disk_utils.get_image_mb', autospec=True)
def test_write_partition_image(self, image_mb_mock, work_on_disk_mock,
execute_mock, open_mock):
execute_mock, open_mock, dispatch_mock):
image_info = _build_fake_partition_image_info()
device = '/dev/sda'
root_mb = image_info['root_mb']
@ -262,11 +283,13 @@ class TestStandbyExtension(base.IronicAgentTest):
boot_mode = image_info['deploy_boot_mode']
boot_option = image_info['boot_option']
disk_label = image_info['disk_label']
cpu_arch = self.fake_cpu.architecture
image_path = standby._image_location(image_info)
uuids = {'root uuid': 'root_uuid'}
expected_uuid = {'root uuid': 'root_uuid'}
image_mb_mock.return_value = 1
dispatch_mock.return_value = self.fake_cpu
work_on_disk_mock.return_value = uuids
standby._write_image(image_info, device)
@ -280,7 +303,8 @@ class TestStandbyExtension(base.IronicAgentTest):
preserve_ephemeral=pr_ep,
boot_mode=boot_mode,
boot_option=boot_option,
disk_label=disk_label)
disk_label=disk_label,
cpu_arch=cpu_arch)
self.assertEqual(expected_uuid, work_on_disk_mock.return_value)

View File

@ -24,7 +24,7 @@ greenlet==0.4.13
hacking==1.0.0
idna==2.6
imagesize==1.0.0
ironic-lib==2.5.0
ironic-lib==2.14.0
iso8601==0.1.11
Jinja2==2.10
keystoneauth1==3.4.0

View File

@ -0,0 +1,9 @@
---
features:
- |
If a PReP boot partition is created, and the machine being deployed to is
of ppc64le architecture, the grub2 bootloader will be installed directly
there. This enables booting partition images locally on ppc64* hardware.
Using this feature requires ``ironic-lib`` version 2.14 as support to
create the PReP partition was introduced there.

View File

@ -21,4 +21,4 @@ rtslib-fb>=2.1.65 # Apache-2.0
six>=1.10.0 # MIT
stevedore>=1.20.0 # Apache-2.0
WSME>=0.8.0 # MIT
ironic-lib>=2.5.0 # Apache-2.0
ironic-lib>=2.14.0 # Apache-2.0