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) 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.""" """Install GRUB2 bootloader on a given device."""
LOG.debug("Installing GRUB2 bootloader on device %s", device) LOG.debug("Installing GRUB2 bootloader on device %s", device)
root_partition = _get_partition(device, uuid=root_uuid) 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 = _get_partition(device, uuid=efi_system_part_uuid)
efi_partition_mount_point = os.path.join(path, "boot/efi") 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) utils.execute('mount', root_partition, path)
for fs in BIND_MOUNTS: for fs in BIND_MOUNTS:
utils.execute('mount', '-o', 'bind', fs, path + fs) 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): class ImageExtension(base.BaseAgentExtension):
@base.sync_command('install_bootloader') @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. """Install the GRUB2 bootloader on the image.
:param root_uuid: The UUID of the root partition. :param root_uuid: The UUID of the root partition.
:param efi_system_part_uuid: The UUID of the efi system 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 To be used only for uefi boot mode. For uefi boot mode, the
boot loader will be installed here. 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 :raises: CommandExecutionError if the installation of the
bootloader fails. bootloader fails.
:raises: DeviceNotFound if the root partition is not found. :raises: DeviceNotFound if the root partition is not found.
@ -212,4 +221,5 @@ class ImageExtension(base.BaseAgentExtension):
iscsi.clean_up(device) iscsi.clean_up(device)
_install_grub2(device, _install_grub2(device,
root_uuid=root_uuid, 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') disk_label = image_info.get('disk_label', 'msdos')
image_mb = disk_utils.get_image_mb(image) image_mb = disk_utils.get_image_mb(image)
root_mb = image_info['root_mb'] root_mb = image_info['root_mb']
cpu_arch = hardware.dispatch_to_managers('get_cpus').architecture
if image_mb > int(root_mb): if image_mb > int(root_mb):
msg = ('Root partition is too small for requested image. Image ' msg = ('Root partition is too small for requested image. Image '
'virtual size: {} MB, Root size: {} MB').format(image_mb, 'virtual size: {} MB, Root size: {} MB').format(image_mb,
@ -88,7 +91,8 @@ def _write_partition_image(image, image_info, device):
configdrive=configdrive, configdrive=configdrive,
boot_option=boot_option, boot_option=boot_option,
boot_mode=boot_mode, boot_mode=boot_mode,
disk_label=disk_label) disk_label=disk_label,
cpu_arch=cpu_arch)
except processutils.ProcessExecutionError as e: except processutils.ProcessExecutionError as e:
raise errors.ImageWriteError(device, e.exit_code, e.stdout, e.stderr) 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_dev = '/dev/fake'
self.fake_efi_system_part = '/dev/fake1' self.fake_efi_system_part = '/dev/fake1'
self.fake_root_part = '/dev/fake2' self.fake_root_part = '/dev/fake2'
self.fake_prep_boot_part = '/dev/fake3'
self.fake_root_uuid = '11111111-2222-3333-4444-555555555555' self.fake_root_uuid = '11111111-2222-3333-4444-555555555555'
self.fake_efi_system_part_uuid = '45AB-2312' self.fake_efi_system_part_uuid = '45AB-2312'
self.fake_prep_boot_part_uuid = '76937797-3253-8843-999999999999'
self.fake_dir = '/tmp/fake-dir' self.fake_dir = '/tmp/fake-dir'
@mock.patch.object(iscsi, 'clean_up', autospec=True) @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_dispatch.assert_called_once_with('get_os_install_device')
mock_grub2.assert_called_once_with( mock_grub2.assert_called_once_with(
self.fake_dev, root_uuid=self.fake_root_uuid, 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_iscsi_clean.assert_called_once_with(self.fake_dev)
@mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(iscsi, 'clean_up', autospec=True)
@ -68,7 +70,25 @@ class TestImageExtension(base.IronicAgentTest):
mock_grub2.assert_called_once_with( mock_grub2.assert_called_once_with(
self.fake_dev, self.fake_dev,
root_uuid=self.fake_root_uuid, 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_iscsi_clean.assert_called_once_with(self.fake_dev)
@mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'environ', autospec=True)
@ -108,6 +128,48 @@ class TestImageExtension(base.IronicAgentTest):
uuid=self.fake_root_uuid) uuid=self.fake_root_uuid)
self.assertFalse(mock_dispatch.called) 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, 'environ', autospec=True)
@mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True)
@mock.patch.object(image, '_get_partition', 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 import errors
from ironic_python_agent.extensions import standby from ironic_python_agent.extensions import standby
from ironic_python_agent import hardware
from ironic_python_agent.tests.unit import base from ironic_python_agent.tests.unit import base
@ -58,6 +59,11 @@ class TestStandbyExtension(base.IronicAgentTest):
def setUp(self): def setUp(self):
super(TestStandbyExtension, self).setUp() super(TestStandbyExtension, self).setUp()
self.agent_extension = standby.StandbyExtension() 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): def test_validate_image_info_success(self):
standby._validate_image_info(None, _build_fake_image_info()) 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]) 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('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', 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.get_image_mb', autospec=True)
@mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True) @mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True)
def test_write_partition_image_exception(self, work_on_disk_mock, def test_write_partition_image_exception(self, work_on_disk_mock,
image_mb_mock, image_mb_mock,
execute_mock, open_mock): execute_mock, open_mock,
dispatch_mock):
image_info = _build_fake_partition_image_info() image_info = _build_fake_partition_image_info()
device = '/dev/sda' device = '/dev/sda'
root_mb = image_info['root_mb'] root_mb = image_info['root_mb']
@ -156,10 +164,12 @@ class TestStandbyExtension(base.IronicAgentTest):
boot_mode = image_info['deploy_boot_mode'] boot_mode = image_info['deploy_boot_mode']
boot_option = image_info['boot_option'] boot_option = image_info['boot_option']
disk_label = image_info['disk_label'] disk_label = image_info['disk_label']
cpu_arch = self.fake_cpu.architecture
image_path = standby._image_location(image_info) image_path = standby._image_location(image_info)
image_mb_mock.return_value = 1 image_mb_mock.return_value = 1
dispatch_mock.return_value = self.fake_cpu
exc = errors.ImageWriteError exc = errors.ImageWriteError
Exception_returned = processutils.ProcessExecutionError Exception_returned = processutils.ProcessExecutionError
work_on_disk_mock.side_effect = Exception_returned work_on_disk_mock.side_effect = Exception_returned
@ -176,15 +186,18 @@ class TestStandbyExtension(base.IronicAgentTest):
preserve_ephemeral=pr_ep, preserve_ephemeral=pr_ep,
boot_mode=boot_mode, boot_mode=boot_mode,
boot_option=boot_option, 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('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', 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.get_image_mb', autospec=True)
@mock.patch('ironic_lib.disk_utils.work_on_disk', 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, def test_write_partition_image_no_node_uuid(self, work_on_disk_mock,
image_mb_mock, image_mb_mock,
execute_mock, open_mock): execute_mock, open_mock,
dispatch_mock):
image_info = _build_fake_partition_image_info() image_info = _build_fake_partition_image_info()
image_info['node_uuid'] = None image_info['node_uuid'] = None
device = '/dev/sda' device = '/dev/sda'
@ -198,10 +211,12 @@ class TestStandbyExtension(base.IronicAgentTest):
boot_mode = image_info['deploy_boot_mode'] boot_mode = image_info['deploy_boot_mode']
boot_option = image_info['boot_option'] boot_option = image_info['boot_option']
disk_label = image_info['disk_label'] disk_label = image_info['disk_label']
cpu_arch = self.fake_cpu.architecture
image_path = standby._image_location(image_info) image_path = standby._image_location(image_info)
image_mb_mock.return_value = 1 image_mb_mock.return_value = 1
dispatch_mock.return_value = self.fake_cpu
uuids = {'root uuid': 'root_uuid'} uuids = {'root uuid': 'root_uuid'}
expected_uuid = {'root uuid': 'root_uuid'} expected_uuid = {'root uuid': 'root_uuid'}
image_mb_mock.return_value = 1 image_mb_mock.return_value = 1
@ -218,11 +233,13 @@ class TestStandbyExtension(base.IronicAgentTest):
preserve_ephemeral=pr_ep, preserve_ephemeral=pr_ep,
boot_mode=boot_mode, boot_mode=boot_mode,
boot_option=boot_option, 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.assertEqual(expected_uuid, work_on_disk_mock.return_value)
self.assertIsNone(node_uuid) self.assertIsNone(node_uuid)
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
@mock.patch('six.moves.builtins.open', autospec=True) @mock.patch('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', 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.get_image_mb', autospec=True)
@ -231,12 +248,15 @@ class TestStandbyExtension(base.IronicAgentTest):
work_on_disk_mock, work_on_disk_mock,
image_mb_mock, image_mb_mock,
execute_mock, execute_mock,
open_mock): open_mock,
dispatch_mock):
dispatch_mock.return_value = self.fake_cpu
image_info = _build_fake_partition_image_info() image_info = _build_fake_partition_image_info()
device = '/dev/sda' device = '/dev/sda'
image_path = standby._image_location(image_info) image_path = standby._image_location(image_info)
image_mb_mock.return_value = 20 image_mb_mock.return_value = 20
exc = errors.InvalidCommandParamsError exc = errors.InvalidCommandParamsError
self.assertRaises(exc, standby._write_image, image_info, 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) image_mb_mock.assert_called_once_with(image_path)
self.assertFalse(work_on_disk_mock.called) 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('six.moves.builtins.open', autospec=True)
@mock.patch('ironic_python_agent.utils.execute', 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.work_on_disk', autospec=True)
@mock.patch('ironic_lib.disk_utils.get_image_mb', 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, 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() image_info = _build_fake_partition_image_info()
device = '/dev/sda' device = '/dev/sda'
root_mb = image_info['root_mb'] root_mb = image_info['root_mb']
@ -262,11 +283,13 @@ class TestStandbyExtension(base.IronicAgentTest):
boot_mode = image_info['deploy_boot_mode'] boot_mode = image_info['deploy_boot_mode']
boot_option = image_info['boot_option'] boot_option = image_info['boot_option']
disk_label = image_info['disk_label'] disk_label = image_info['disk_label']
cpu_arch = self.fake_cpu.architecture
image_path = standby._image_location(image_info) image_path = standby._image_location(image_info)
uuids = {'root uuid': 'root_uuid'} uuids = {'root uuid': 'root_uuid'}
expected_uuid = {'root uuid': 'root_uuid'} expected_uuid = {'root uuid': 'root_uuid'}
image_mb_mock.return_value = 1 image_mb_mock.return_value = 1
dispatch_mock.return_value = self.fake_cpu
work_on_disk_mock.return_value = uuids work_on_disk_mock.return_value = uuids
standby._write_image(image_info, device) standby._write_image(image_info, device)
@ -280,7 +303,8 @@ class TestStandbyExtension(base.IronicAgentTest):
preserve_ephemeral=pr_ep, preserve_ephemeral=pr_ep,
boot_mode=boot_mode, boot_mode=boot_mode,
boot_option=boot_option, 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.assertEqual(expected_uuid, work_on_disk_mock.return_value)

View File

@ -24,7 +24,7 @@ greenlet==0.4.13
hacking==1.0.0 hacking==1.0.0
idna==2.6 idna==2.6
imagesize==1.0.0 imagesize==1.0.0
ironic-lib==2.5.0 ironic-lib==2.14.0
iso8601==0.1.11 iso8601==0.1.11
Jinja2==2.10 Jinja2==2.10
keystoneauth1==3.4.0 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 six>=1.10.0 # MIT
stevedore>=1.20.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0
WSME>=0.8.0 # MIT WSME>=0.8.0 # MIT
ironic-lib>=2.5.0 # Apache-2.0 ironic-lib>=2.14.0 # Apache-2.0