Local boot support for IPA

This patch adds support for local boot when using the IPA ramdisk, it
also make sure it clear the PXE configurations and set the node to boot
from disk permanently as part for the deployment if local boot is set.

Implement blueprint local-boot-support-with-partition-images
Depends-On: Ia588aafc240b55119c02f1254addc0cf796f88c5
Change-Id: I22c877100a19fe5147464bce589c8922f0bf3f1e
This commit is contained in:
Lucas Alvares Gomes 2015-02-19 15:31:35 +00:00
parent d87309e6f3
commit efed216157
4 changed files with 143 additions and 7 deletions

View File

@ -105,3 +105,11 @@ class AgentClient(object):
method='iscsi.start_iscsi_target',
params=params,
wait=True)
def install_bootloader(self, node, root_uuid):
"""Install a boot loader on the image."""
params = {'root_uuid': root_uuid}
return self._command(node=node,
method='image.install_bootloader',
params=params,
wait=True)

View File

@ -616,9 +616,32 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
node.driver_internal_info = driver_internal_info
node.save()
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
driver_utils.get_node_capability(node, 'boot_mode'))
if iscsi_deploy.get_boot_option(node) == "local":
# Install the boot loader
result = self._client.install_bootloader(node, root_uuid)
if result['command_status'] == 'FAILED':
msg = (_("Failed to install a bootloader when "
"deploying node %(node)s. Error: %(error)s") %
{'node': node.uuid,
'error': result['command_error']})
self._log_and_raise_deployment_error(task, msg)
try:
try_set_boot_device(task, boot_devices.DISK)
except Exception as e:
msg = (_("Failed to change the boot device to %(boot_dev)s "
"when deploying node %(node)s. Error: %(error)s") %
{'boot_dev': boot_devices.DISK, 'node': node.uuid,
'error': e})
self._log_and_raise_deployment_error(task, msg)
# If it's going to boot from the local disk, get rid of
# the PXE configuration files used for the deployment
pxe_utils.clean_up_pxe_config(task)
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
driver_utils.get_node_capability(node, 'boot_mode'))
try:
manager_utils.node_power_action(task, states.REBOOT)

View File

@ -134,3 +134,15 @@ class TestAgentClient(base.TestCase):
method='iscsi.start_iscsi_target',
params=params,
wait=True)
@mock.patch('uuid.uuid4', mock.MagicMock(return_value='uuid'))
def test_start_install_bootloader(self):
self.client._command = mock.Mock()
root_uuid = 'fake-root-uuid'
params = {'root_uuid': root_uuid}
self.client.install_bootloader(self.node, root_uuid)
self.client._command.assert_called_once_with(node=self.node,
method='image.install_bootloader',
params=params,
wait=True)

View File

@ -979,14 +979,14 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
def test_continue_deploy_command_failed(self, mock_image_info,
mock_pxe_opts, mock_start_iscsi,
mock_cont_deploy):
command_error = 'Gotham city is in danger!'
mock_start_iscsi.return_value = {'command_error': command_error,
'command_status': 'FAILED'}
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
'deployment_key': 'fake-deploy-key'}
mock_start_iscsi.return_value = {'command_error':
'Gotham city is in danger!',
'command_status': 'FAILED'}
result = self.assertRaises(exception.InstanceDeployFailure,
self.driver.vendor.continue_deploy, self.task)
self.assertIn("Gotham city is in danger!", result.format_message())
self.assertIn(command_error, result.format_message())
mock_image_info.assert_called_once_with(self.node, self.context)
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
self.task.context)
@ -1038,3 +1038,96 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
self.assertNotIn('root_uuid', self.node.driver_internal_info)
self.assertIsNotNone(self.node.last_error)
@mock.patch.object(pxe_utils, 'clean_up_pxe_config')
@mock.patch.object(manager_utils, 'node_set_boot_device')
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
@mock.patch.object(deploy_utils, 'switch_pxe_config')
@mock.patch.object(manager_utils, 'node_power_action')
def test_continue_deploy_localboot(self, mock_node_power, mock_pxe_config,
mock_install_bootloader, mock_boot_dev,
mock_clean_pxe, mock_image_info, mock_pxe_opts,
mock_start_iscsi, mock_cont_deploy):
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
'deployment_key': 'fake-deploy-key'}
mock_start_iscsi.return_value = {'command_error': None,
'command_status': 'SUCCEEDED'}
mock_cont_deploy.return_value = 'fake-root-uuid'
i_info = self.node.instance_info
i_info['capabilities'] = {'boot_option': 'local'}
mock_install_bootloader.return_value = {'command_status': 'SUCCEEDED'}
self.driver.vendor.continue_deploy(self.task)
mock_image_info.assert_called_once_with(self.node, self.context)
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
self.task.context)
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
self.assertIn('root_uuid', self.node.driver_internal_info)
mock_node_power.assert_called_once_with(self.task, states.REBOOT)
mock_install_bootloader.assert_called_once_with(self.node,
'fake-root-uuid')
self.assertIsNone(self.node.last_error)
# Assert we clean up the PXE configuration and make set the boot
# device to boot from disk
mock_clean_pxe.assert_called_once_with(self.task)
mock_boot_dev.assert_called_once_with(self.task, boot_devices.DISK,
persistent=True)
# Assert we dont try to switch the PXE configuration
self.assertFalse(mock_pxe_config.called)
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
def test_continue_deploy_localboot_command_failed(self,
mock_install_bootloader, mock_image_info,
mock_pxe_opts, mock_start_iscsi,
mock_cont_deploy):
command_error = 'Gotham city is in danger!'
mock_install_bootloader.return_value = {'command_error': command_error,
'command_status': 'FAILED'}
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
'deployment_key': 'fake-deploy-key'}
mock_start_iscsi.return_value = {'command_error': None,
'command_status': 'SUCCEEDED'}
mock_cont_deploy.return_value = 'fake-root-uuid'
i_info = self.node.instance_info
i_info['capabilities'] = {'boot_option': 'local'}
result = self.assertRaises(exception.InstanceDeployFailure,
self.driver.vendor.continue_deploy, self.task)
self.assertIn(command_error, result.format_message())
mock_image_info.assert_called_once_with(self.node, self.context)
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
self.task.context)
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
mock_install_bootloader.assert_called_once_with(self.node,
'fake-root-uuid')
self.assertIsNotNone(self.node.last_error)
@mock.patch.object(pxe, 'try_set_boot_device')
@mock.patch.object(agent_client.AgentClient, 'install_bootloader')
def test_continue_deploy_set_boot_device_failed(self,
mock_install_bootloader, mock_set_boot_dev,
mock_image_info, mock_pxe_opts, mock_start_iscsi,
mock_cont_deploy):
error_msg = "User error. Please replace the user."
mock_set_boot_dev.side_effect = exception.IPMIFailure(error_msg)
mock_install_bootloader.return_value = {'command_error': None,
'command_status': 'SUCCEEDED'}
mock_pxe_opts.return_value = {'iscsi_target_iqn': 'fake-iqn',
'deployment_key': 'fake-deploy-key'}
mock_start_iscsi.return_value = {'command_error': None,
'command_status': 'SUCCEEDED'}
mock_cont_deploy.return_value = 'fake-root-uuid'
i_info = self.node.instance_info
i_info['capabilities'] = {'boot_option': 'local'}
result = self.assertRaises(exception.InstanceDeployFailure,
self.driver.vendor.continue_deploy, self.task)
self.assertIn(error_msg, result.format_message())
mock_image_info.assert_called_once_with(self.node, self.context)
mock_pxe_opts.assert_called_once_with(self.task.node, mock.ANY,
self.task.context)
mock_start_iscsi.assert_called_once_with(self.node, 'fake-iqn')
mock_install_bootloader.assert_called_once_with(self.node,
'fake-root-uuid')
self.assertIsNotNone(self.node.last_error)