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

@ -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)

@ -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)

@ -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)

@ -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)

@ -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

@ -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.

@ -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