Install grub to PReP partition when prep_boot_part_uuid is provided

Installs the grub bootloader to the PreP Boot partition when the
prep_boot_partition_uuid is provided. This is required when
booting a partition image locally on ppc64* systems.

This change also passes the cpu_arch along to work_on_disk so
that the PReP partition is created when partitioning disks for
local boot on ppc64* systems,

Change-Id: I70667d43af962b357e6eeccba258f4fa5a91a09e
Depends-On: I2bc9f13ec605de7b7b96d96a1a4edebee0af76dc
Story: #1749057
Task: #22999
This commit is contained in:
Michael Turek 2018-07-11 13:35:54 +00:00
parent 91ccbbf75e
commit b32750f5c4
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