Pass prep_boot_part_uuid to install_bootloader for ppc64* partition images

This patch gets the PReP Boot partition UUID when performing a local boot
partition image deployment on ppc64* hardware.

Depends-On: I8f9748dd58146bfb2411c229b02969e0faf18222
Change-Id: I2bc9f13ec605de7b7b96d96a1a4edebee0af76dc
Story: #1749057
Task: #22995
This commit is contained in:
Michael Turek 2018-06-29 09:32:57 -04:00
parent 69520d2610
commit 8f89954f9a
13 changed files with 199 additions and 36 deletions

View File

@ -265,6 +265,7 @@ class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
task.process_event('resume')
node = task.node
iwdi = task.node.driver_internal_info.get('is_whole_disk_image')
cpu_arch = task.node.properties.get('cpu_arch')
error = self.check_deploy_success(node)
if error is not None:
# TODO(jimrollenhagen) power off if using neutron dhcp to
@ -285,6 +286,9 @@ class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
# In case of local boot using partition image, we need both
# 'root_uuid_or_disk_id' and 'efi_system_partition_uuid' to configure
# bootloader for local boot.
# NOTE(mjturek): In the case of local boot using a partition image on
# ppc64* hardware we need to provide the 'PReP_Boot_partition_uuid' to
# direct where the bootloader should be installed.
driver_internal_info = task.node.driver_internal_info
root_uuid = self._get_uuid_from_result(task, 'root_uuid')
if root_uuid:
@ -312,9 +316,14 @@ class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
efi_sys_uuid = None
if not iwdi:
if boot_mode_utils.get_boot_mode_for_deploy(node) == 'uefi':
efi_sys_uuid = (
self._get_uuid_from_result(task,
'efi_system_partition_uuid'))
efi_sys_uuid = (self._get_uuid_from_result(task,
'efi_system_partition_uuid'))
prep_boot_part_uuid = None
if cpu_arch is not None and cpu_arch.startswith('ppc64'):
prep_boot_part_uuid = (self._get_uuid_from_result(task,
'PReP_Boot_partition_uuid'))
LOG.info('Image successfully written to node %s', node.uuid)
if CONF.agent.manage_agent_boot:
@ -324,7 +333,8 @@ class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
# be done on node during deploy stage can be performed.
LOG.debug('Executing driver specific tasks before booting up the '
'instance for node %s', node.uuid)
self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid)
self.prepare_instance_to_boot(task, root_uuid,
efi_sys_uuid, prep_boot_part_uuid)
else:
manager_utils.node_set_boot_device(task, 'disk', persistent=True)

View File

@ -666,7 +666,8 @@ class AgentDeployMixin(HeartbeatMixin):
manager_utils.notify_conductor_resume_deploy(task)
@METRICS.timer('AgentDeployMixin.prepare_instance_to_boot')
def prepare_instance_to_boot(self, task, root_uuid, efi_sys_uuid):
def prepare_instance_to_boot(self, task, root_uuid, efi_sys_uuid,
prep_boot_part_uuid=None):
"""Prepares instance to boot.
:param task: a TaskManager object containing the node
@ -680,7 +681,8 @@ class AgentDeployMixin(HeartbeatMixin):
# Install the boot loader
self.configure_local_boot(
task, root_uuid=root_uuid,
efi_system_part_uuid=efi_sys_uuid)
efi_system_part_uuid=efi_sys_uuid,
prep_boot_part_uuid=prep_boot_part_uuid)
try:
task.driver.boot.prepare_instance(task)
except Exception as e:
@ -693,7 +695,8 @@ class AgentDeployMixin(HeartbeatMixin):
@METRICS.timer('AgentDeployMixin.configure_local_boot')
def configure_local_boot(self, task, root_uuid=None,
efi_system_part_uuid=None):
efi_system_part_uuid=None,
prep_boot_part_uuid=None):
"""Helper method to configure local boot on the node.
This method triggers bootloader installation on the node.
@ -707,6 +710,8 @@ class AgentDeployMixin(HeartbeatMixin):
have a bootloader installed.
:param efi_system_part_uuid: The UUID of the efi system partition.
This is used only in uefi boot mode.
:param prep_boot_part_uuid: The UUID of the PReP Boot partition.
This is used only for booting ppc64* hardware.
:raises: InstanceDeployFailure if bootloader installation failed or
on encountering error while setting the boot device on the node.
"""
@ -720,7 +725,8 @@ class AgentDeployMixin(HeartbeatMixin):
'efi': efi_system_part_uuid})
result = self._client.install_bootloader(
node, 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)
if result['command_status'] == 'FAILED':
msg = (_("Failed to install a bootloader when "
"deploying node %(node)s. Error: %(error)s") %

View File

@ -224,7 +224,8 @@ class AgentClient(object):
wait=True)
@METRICS.timer('AgentClient.install_bootloader')
def install_bootloader(self, node, root_uuid, efi_system_part_uuid=None):
def install_bootloader(self, node, root_uuid, efi_system_part_uuid=None,
prep_boot_part_uuid=None):
"""Install a boot loader on the image.
:param node: A node object.
@ -232,6 +233,9 @@ class AgentClient(object):
:param efi_system_part_uuid: The UUID of the efi system partition
where the bootloader will be installed to, only used for uefi
boot mode.
:param prep_boot_part_uuid: The UUID of the PReP Boot partition where
the bootloader will be installed to when local booting a
partition image on a ppc64* system.
:raises: IronicException when failed to issue the request or there was
a malformed response from the agent.
:raises: AgentAPIError when agent failed to execute specified command.
@ -239,7 +243,8 @@ class AgentClient(object):
See :func:`get_commands_status` for a command result sample.
"""
params = {'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}
return self._command(node=node,
method='image.install_bootloader',
params=params,

View File

@ -345,7 +345,8 @@ def deploy_partition_image(
address, port, iqn, lun, image_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,
preserve_ephemeral=False, configdrive=None,
boot_option=None, boot_mode="bios", disk_label=None):
boot_option=None, boot_mode="bios", disk_label=None,
cpu_arch=""):
"""All-in-one function to deploy a partition image to a node.
:param address: The iSCSI IP address.
@ -370,6 +371,7 @@ def deploy_partition_image(
:param disk_label: The disk label to be used when creating the
partition table. Valid values are: "msdos", "gpt" or None; If None
Ironic will figure it out according to the boot_mode parameter.
:param cpu_arch: Architecture of the node being deployed to.
:raises: InstanceDeployFailure if image virtual size is bigger than root
partition size.
:returns: a dictionary containing the following keys:
@ -392,7 +394,7 @@ def deploy_partition_image(
dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path,
node_uuid, preserve_ephemeral=preserve_ephemeral,
configdrive=configdrive, boot_option=boot_option,
boot_mode=boot_mode, disk_label=disk_label)
boot_mode=boot_mode, disk_label=disk_label, cpu_arch=cpu_arch)
return uuid_dict_returned

View File

@ -179,7 +179,8 @@ def get_deploy_info(node, address, iqn, port=None, lun='1'):
'ephemeral_mb': i_info['ephemeral_mb'],
'preserve_ephemeral': i_info['preserve_ephemeral'],
'boot_option': deploy_utils.get_boot_option(node),
'boot_mode': _get_boot_mode(node)})
'boot_mode': _get_boot_mode(node),
'cpu_arch': node.properties.get('cpu_arch')})
# Append disk label if specified
disk_label = deploy_utils.get_disk_label(node)
@ -408,7 +409,10 @@ class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
uuid_dict_returned = do_agent_iscsi_deploy(task, self._client)
root_uuid = uuid_dict_returned.get('root uuid')
efi_sys_uuid = uuid_dict_returned.get('efi system partition uuid')
self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid)
prep_boot_part_uuid = uuid_dict_returned.get(
'PrEP Boot partition uuid')
self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid,
prep_boot_part_uuid=prep_boot_part_uuid)
self.reboot_and_finish_deploy(task)

View File

@ -983,7 +983,7 @@ class TestAgentDeploy(db_base.DbTestCase):
self.assertIn("Ironic Python Agent version 3.1.0 and beyond",
log_mock.call_args[0][0])
prepare_instance_mock.assert_called_once_with(mock.ANY, task,
None, None)
None, None, None)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
@ -1078,8 +1078,67 @@ class TestAgentDeploy(db_base.DbTestCase):
driver_int_info['root_uuid_or_disk_id']),
boot_mode_mock.assert_called_once_with(task.node)
self.assertFalse(log_mock.called)
prepare_instance_mock.assert_called_once_with(mock.ANY, task,
'root_uuid', None)
prepare_instance_mock.assert_called_once_with(mock.ANY,
task,
'root_uuid',
None, None)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.POWER_ON)
self.assertEqual(states.ACTIVE, task.node.provision_state)
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
autospec=True)
@mock.patch.object(agent.AgentDeployMixin, '_get_uuid_from_result',
autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
@mock.patch.object(fake.FakePower, 'get_power_state',
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
@mock.patch.object(agent.AgentDeployMixin, 'prepare_instance_to_boot',
autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentDeployMixin'
'.check_deploy_success', autospec=True)
def test_reboot_to_instance_partition_localboot_ppc64(
self, check_deploy_mock, prepare_instance_mock,
power_off_mock, get_power_state_mock,
node_power_action_mock, uuid_mock, boot_mode_mock, log_mock):
check_deploy_mock.return_value = None
uuid_mock.side_effect = ['root_uuid', 'prep_boot_part_uuid']
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE
self.node.save()
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
get_power_state_mock.return_value = states.POWER_OFF
driver_internal_info = task.node.driver_internal_info
driver_internal_info['is_whole_disk_image'] = False
task.node.driver_internal_info = driver_internal_info
boot_option = {'capabilities': '{"boot_option": "local"}'}
task.node.instance_info = boot_option
properties = task.node.properties
properties.update(cpu_arch='ppc64le')
task.node.properties = properties
boot_mode_mock.return_value = 'bios'
task.driver.deploy.reboot_to_instance(task)
check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
driver_int_info = task.node.driver_internal_info
self.assertEqual('root_uuid',
driver_int_info['root_uuid_or_disk_id']),
uuid_mock_calls = [
mock.call(mock.ANY, task, 'root_uuid'),
mock.call(mock.ANY, task, 'PReP_Boot_partition_uuid')]
uuid_mock.assert_has_calls(uuid_mock_calls)
boot_mode_mock.assert_called_once_with(task.node)
self.assertFalse(log_mock.called)
prepare_instance_mock.assert_called_once_with(
mock.ANY, task, 'root_uuid', None, 'prep_boot_part_uuid')
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
@ -1171,7 +1230,7 @@ class TestAgentDeploy(db_base.DbTestCase):
boot_mode_mock.assert_called_once_with(task.node)
self.assertFalse(log_mock.called)
prepare_instance_mock.assert_called_once_with(
mock.ANY, task, 'root_uuid', 'efi_uuid')
mock.ANY, task, 'root_uuid', 'efi_uuid', None)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(

View File

@ -763,7 +763,25 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
task, boot_devices.DISK)
install_bootloader_mock.assert_called_once_with(
mock.ANY, task.node, root_uuid='some-root-uuid',
efi_system_part_uuid=None)
efi_system_part_uuid=None, prep_boot_part_uuid=None)
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
autospec=True)
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
def test_configure_local_boot_with_prep(self, try_set_boot_device_mock,
install_bootloader_mock):
install_bootloader_mock.return_value = {
'command_status': 'SUCCESS', 'command_error': None}
with task_manager.acquire(self.context, self.node['uuid'],
shared=False) as task:
task.node.driver_internal_info['is_whole_disk_image'] = False
self.deploy.configure_local_boot(task, root_uuid='some-root-uuid',
prep_boot_part_uuid='fake-prep')
try_set_boot_device_mock.assert_called_once_with(
task, boot_devices.DISK)
install_bootloader_mock.assert_called_once_with(
mock.ANY, task.node, root_uuid='some-root-uuid',
efi_system_part_uuid=None, prep_boot_part_uuid='fake-prep')
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
autospec=True)
@ -782,7 +800,8 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
task, boot_devices.DISK)
install_bootloader_mock.assert_called_once_with(
mock.ANY, task.node, root_uuid='some-root-uuid',
efi_system_part_uuid='efi-system-part-uuid')
efi_system_part_uuid='efi-system-part-uuid',
prep_boot_part_uuid=None)
@mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
@mock.patch.object(agent_client.AgentClient, 'install_bootloader',
@ -828,7 +847,7 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
task, root_uuid='some-root-uuid')
install_bootloader_mock.assert_called_once_with(
mock.ANY, task.node, root_uuid='some-root-uuid',
efi_system_part_uuid=None)
efi_system_part_uuid=None, prep_boot_part_uuid=None)
collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
@ -852,10 +871,11 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
task.node.driver_internal_info['is_whole_disk_image'] = False
self.assertRaises(exception.InstanceDeployFailure,
self.deploy.configure_local_boot,
task, root_uuid='some-root-uuid')
task, root_uuid='some-root-uuid',
prep_boot_part_uuid=None)
install_bootloader_mock.assert_called_once_with(
mock.ANY, task.node, root_uuid='some-root-uuid',
efi_system_part_uuid=None)
efi_system_part_uuid=None, prep_boot_part_uuid=None)
try_set_boot_device_mock.assert_called_once_with(
task, boot_devices.DISK)
collect_logs_mock.assert_called_once_with(mock.ANY, task.node)
@ -911,7 +931,39 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
configure_mock.assert_called_once_with(
self.deploy, task,
root_uuid=root_uuid,
efi_system_part_uuid=efi_system_part_uuid)
efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=None)
boot_option_mock.assert_called_once_with(task.node)
prepare_instance_mock.assert_called_once_with(task.driver.boot,
task)
self.assertFalse(failed_state_mock.called)
@mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
@mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
@mock.patch.object(agent_base_vendor.AgentDeployMixin,
'configure_local_boot', autospec=True)
def test_prepare_instance_to_boot_localboot_prep_partition(
self, configure_mock, boot_option_mock,
prepare_instance_mock, failed_state_mock):
boot_option_mock.return_value = 'local'
prepare_instance_mock.return_value = None
self.node.provision_state = states.DEPLOYING
self.node.target_provision_state = states.ACTIVE
self.node.save()
root_uuid = 'root_uuid'
efi_system_part_uuid = 'efi_sys_uuid'
prep_boot_part_uuid = 'prep_boot_part_uuid'
with task_manager.acquire(self.context, self.node['uuid'],
shared=False) as task:
self.deploy.prepare_instance_to_boot(task, root_uuid,
efi_system_part_uuid,
prep_boot_part_uuid)
configure_mock.assert_called_once_with(
self.deploy, task,
root_uuid=root_uuid,
efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=prep_boot_part_uuid)
boot_option_mock.assert_called_once_with(task.node)
prepare_instance_mock.assert_called_once_with(task.driver.boot,
task)
@ -946,7 +998,8 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
configure_mock.assert_called_once_with(
self.deploy, task,
root_uuid=root_uuid,
efi_system_part_uuid=efi_system_part_uuid)
efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=None)
boot_option_mock.assert_called_once_with(task.node)
self.assertFalse(prepare_mock.called)
self.assertFalse(failed_state_mock.called)

View File

@ -247,19 +247,29 @@ class TestAgentClient(base.TestCase):
node=self.node, method='iscsi.start_iscsi_target',
params=params, wait=True)
def test_install_bootloader(self):
def _test_install_bootloader(self, root_uuid, efi_system_part_uuid=None,
prep_boot_part_uuid=None):
self.client._command = mock.MagicMock(spec_set=[])
root_uuid = 'fake-root-uuid'
efi_system_part_uuid = 'fake-efi-system-part-uuid'
params = {'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}
self.client.install_bootloader(
self.node, root_uuid, efi_system_part_uuid=efi_system_part_uuid)
self.node, root_uuid, efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=prep_boot_part_uuid)
self.client._command.assert_called_once_with(
node=self.node, method='image.install_bootloader', params=params,
wait=True)
def test_install_bootloader(self):
self._test_install_bootloader(root_uuid='fake-root-uuid',
efi_system_part_uuid='fake-efi-uuid')
def test_install_bootloaderi_with_prep(self):
self._test_install_bootloader(root_uuid='fake-root-uuid',
efi_system_part_uuid='fake-efi-uuid',
prep_boot_part_uuid='fake-prep-uuid')
def test_get_clean_steps(self):
self.client._command = mock.MagicMock(spec_set=[])
ports = []

View File

@ -383,6 +383,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
'boot_mode': None,
'boot_option': None,
'configdrive': None,
'cpu_arch': None,
'disk_label': None,
'ephemeral_format': None,
'ephemeral_mb': None,
@ -422,6 +423,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
'boot_option': deploy_args['boot_option'],
'configdrive': deploy_args['configdrive'],
'disk_label': deploy_args['disk_label'],
'cpu_arch': deploy_args['cpu_arch'] or '',
'preserve_ephemeral': deploy_args['preserve_ephemeral']
}
utils.deploy_partition_image(
@ -444,7 +446,8 @@ class PhysicalWorkTestCase(tests_base.TestCase):
# not set
'boot_option': deploy_args['boot_option'] or 'netboot',
'boot_mode': deploy_args['boot_mode'],
'disk_label': deploy_args['disk_label']
'disk_label': deploy_args['disk_label'],
'cpu_arch': deploy_args['cpu_arch'] or ''
}
mock_work_on_disk.assert_called_once_with(
dev, deploy_args['root_mb'], deploy_args['swap_mb'],
@ -502,6 +505,9 @@ class PhysicalWorkTestCase(tests_base.TestCase):
def test_deploy_partition_image_with_configdrive(self):
self._test_deploy_partition_image(configdrive='http://1.2.3.4/cd')
def test_deploy_partition_image_with_cpu_arch(self):
self._test_deploy_partition_image(cpu_arch='generic')
@mock.patch.object(disk_utils, 'create_config_drive_partition',
autospec=True)
@mock.patch.object(disk_utils, 'get_disk_identifier', autospec=True)
@ -775,7 +781,8 @@ class PhysicalWorkTestCase(tests_base.TestCase):
preserve_ephemeral=False,
boot_option="netboot",
boot_mode="bios",
disk_label=None)]
disk_label=None,
cpu_arch="")]
self.assertRaises(TestException, utils.deploy_partition_image,
address, port, iqn, lun, image_path,

View File

@ -896,7 +896,7 @@ class ISCSIDeployTestCase(db_base.DbTestCase):
task, task.driver.deploy._client)
configure_local_boot_mock.assert_called_once_with(
task.driver.deploy, task, root_uuid='some-root-uuid',
efi_system_part_uuid=None)
efi_system_part_uuid=None, prep_boot_part_uuid=None)
reboot_and_finish_deploy_mock.assert_called_once_with(
task.driver.deploy, task)
set_boot_device_mock.assert_called_once_with(
@ -928,7 +928,7 @@ class ISCSIDeployTestCase(db_base.DbTestCase):
task, task.driver.deploy._client)
configure_local_boot_mock.assert_called_once_with(
task.driver.deploy, task, root_uuid='some-root-uuid',
efi_system_part_uuid='efi-part-uuid')
efi_system_part_uuid='efi-part-uuid', prep_boot_part_uuid=None)
reboot_and_finish_deploy_mock.assert_called_once_with(
task.driver.deploy, task)
set_boot_device_mock.assert_called_once_with(

View File

@ -38,7 +38,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
jmespath==0.9.3

View File

@ -0,0 +1,7 @@
---
features:
- Added support for local booting a partition image for ppc64*
hardware. If a PReP partition is detected when deploying to a
ppc64* machine, the partition will be specified to IPA causing
the bootloader to be installed there directly. This feature
requires a ironic-python-agent ramdisk with ironic-lib >=2.14.

View File

@ -11,7 +11,7 @@ python-cinderclient>=3.3.0 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0
python-glanceclient>=2.8.0 # Apache-2.0
keystoneauth1>=3.4.0 # Apache-2.0
ironic-lib>=2.5.0 # Apache-2.0
ironic-lib>=2.14.0 # Apache-2.0
python-swiftclient>=3.2.0 # Apache-2.0
pytz>=2013.6 # MIT
stevedore>=1.20.0 # Apache-2.0