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:
parent
91ccbbf75e
commit
b32750f5c4
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user