Secure boot support for iscsi_ilo driver

This patch implements secure boot support for iscsi_ilo driver.

Node power off step has been moved from deploy() to first step
in prepare() stage. The rational is:-
If iLO is in POST state, then it would not accept any RIBCL
commands related to boot mode changes. This could lead to deploy
failure.

Implements: blueprint uefi-secure-boot
Change-Id: Ie5813592496665d8fdb48270227be4b06cbf5570
This commit is contained in:
Shivanand Tendulker 2015-02-23 23:37:05 -08:00 committed by Chris Krelle
parent f1e6bce0b2
commit 2f04e74c9a
2 changed files with 134 additions and 33 deletions

View File

@ -27,6 +27,7 @@ from ironic.common.glance_service import service_utils
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common.i18n import _LI
from ironic.common.i18n import _LW
from ironic.common import image_service
from ironic.common import images
from ironic.common import keystone
@ -262,7 +263,12 @@ def _reboot_into(task, iso, ramdisk_options):
:raises: IloOperationError, if some operation on iLO failed.
"""
ilo_common.setup_vmedia_for_boot(task, iso, ramdisk_options)
manager_utils.node_set_boot_device(task, boot_devices.CDROM)
# In secure boot mode, node will reboot twice internally to
# enable/disable secure boot. Any one-time boot settings would
# be lost. Hence setting persistent=True.
manager_utils.node_set_boot_device(task, boot_devices.CDROM,
persistent=True)
manager_utils.node_power_action(task, states.REBOOT)
@ -379,18 +385,20 @@ class IloVirtualMediaIscsiDeploy(base.DeployInterface):
in instance_info for non-Glance image.
"""
iscsi_deploy.validate(task)
node = task.node
d_info = _parse_deploy_info(task.node)
d_info = _parse_deploy_info(node)
if task.node.driver_internal_info.get('is_whole_disk_image'):
if node.driver_internal_info.get('is_whole_disk_image'):
props = []
elif service_utils.is_glance_image(d_info['image_source']):
props = ['kernel_id', 'ramdisk_id']
else:
props = ['kernel', 'ramdisk']
iscsi_deploy.validate_image_properties(task.context, d_info, props)
driver_utils.validate_boot_mode_capability(task.node)
driver_utils.validate_boot_option_capability(task.node)
driver_utils.validate_boot_mode_capability(node)
driver_utils.validate_boot_option_capability(node)
driver_utils.validate_secure_boot_capability(node)
@task_manager.require_exclusive_lock
def deploy(self, task):
@ -409,7 +417,6 @@ class IloVirtualMediaIscsiDeploy(base.DeployInterface):
:raises: IloOperationError, if some operation on iLO fails.
"""
node = task.node
manager_utils.node_power_action(task, states.POWER_OFF)
iscsi_deploy.cache_instance_image(task.context, node)
iscsi_deploy.check_image_size(task)
@ -436,6 +443,16 @@ class IloVirtualMediaIscsiDeploy(base.DeployInterface):
:returns: deploy state DELETED.
"""
manager_utils.node_power_action(task, states.POWER_OFF)
try:
_update_secure_boot_mode(task, False)
# We need to handle IloOperationNotSupported exception so that if
# the user has incorrectly specified the Node capability
# 'secure_boot' to a node that does not have that capability and
# attempted deploy. Handling this exception here, will help the
# user to tear down such a Node.
except exception.IloOperationNotSupported:
LOG.warn(_LW('Secure boot mode is not supported for node %s'),
task.node.uuid)
return states.DELETED
def prepare(self, task):
@ -444,7 +461,7 @@ class IloVirtualMediaIscsiDeploy(base.DeployInterface):
:param task: a TaskManager instance containing the node to act on.
:raises: IloOperationError, if some operation on iLO failed.
"""
ilo_common.update_boot_mode(task)
_prepare_node_for_deploy(task)
def clean_up(self, task):
"""Clean up the deployment environment for the task's node.
@ -678,8 +695,13 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
LOG.error(_LE("Cannot get boot ISO for node %s"), node.uuid)
return
# In secure boot mode, node will reboot twice internally to
# enable/disable secure boot. Any one-time boot settings would
# be lost. Hence setting persistent=True.
ilo_common.setup_vmedia_for_boot(task, boot_iso)
manager_utils.node_set_boot_device(task, boot_devices.CDROM)
manager_utils.node_set_boot_device(task,
boot_devices.CDROM,
persistent=True)
i_info = node.instance_info
if not i_info.get('ilo_boot_iso'):
@ -729,6 +751,12 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
else:
self._configure_vmedia_boot(task, root_uuid_or_disk_id)
# Set boot mode
ilo_common.update_boot_mode(task)
# Need to enable secure boot, if being requested
_update_secure_boot_mode(task, True)
deploy_utils.notify_deploy_complete(kwargs.get('address'))
LOG.info(_LI('Deployment to node %s done'), node.uuid)
@ -776,4 +804,10 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
task.context.auth_token = keystone.get_admin_auth_token()
self._configure_vmedia_boot(task, root_uuid)
# Set boot mode
ilo_common.update_boot_mode(task)
# Need to enable secure boot, if being requested
_update_secure_boot_mode(task, True)
self.reboot_and_finish_deploy(task)

View File

@ -250,7 +250,8 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase):
ilo_deploy._reboot_into(task, 'iso', opts)
setup_vmedia_mock.assert_called_once_with(task, 'iso', opts)
set_boot_device_mock.assert_called_once_with(task,
boot_devices.CDROM)
boot_devices.CDROM,
persistent=True)
node_power_action_mock.assert_called_once_with(task, states.REBOOT)
@mock.patch.object(ilo_deploy, '_reboot_into')
@ -414,12 +415,16 @@ class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
self.node = obj_utils.create_test_node(self.context,
driver='iscsi_ilo', driver_info=INFO_DICT)
@mock.patch.object(driver_utils, 'validate_secure_boot_capability')
@mock.patch.object(driver_utils, 'validate_boot_mode_capability')
@mock.patch.object(iscsi_deploy, 'validate_image_properties')
@mock.patch.object(ilo_deploy, '_parse_deploy_info')
@mock.patch.object(iscsi_deploy, 'validate')
def _test_validate(self, validate_mock, deploy_info_mock,
validate_prop_mock, validate_boot_mode_mock,
def _test_validate(self, validate_mock,
deploy_info_mock,
validate_prop_mock,
validate_boot_mode_mock,
validate_secure_boot_mock,
props_expected):
d_info = {'image_source': 'uuid'}
deploy_info_mock.return_value = d_info
@ -431,6 +436,7 @@ class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
validate_prop_mock.assert_called_once_with(task.context,
d_info, props_expected)
validate_boot_mode_mock.assert_called_once_with(task.node)
validate_secure_boot_mock.assert_called_once_with(task.node)
@mock.patch.object(iscsi_deploy, 'validate_image_properties')
@mock.patch.object(ilo_deploy, '_parse_deploy_info')
@ -497,12 +503,15 @@ class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
@mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id')
@mock.patch.object(agent, 'build_agent_options')
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(iscsi_deploy, 'check_image_size')
@mock.patch.object(iscsi_deploy, 'cache_instance_image')
def test_deploy(self, cache_instance_image_mock, check_image_size_mock,
node_power_action_mock, build_opts_mock,
agent_options_mock, get_nic_mock, reboot_into_mock):
def test_deploy(self,
cache_instance_image_mock,
check_image_size_mock,
build_opts_mock,
agent_options_mock,
get_nic_mock,
reboot_into_mock):
deploy_opts = {'a': 'b'}
agent_options_mock.return_value = {
'ipa-api-url': 'http://1.2.3.4:6385'}
@ -514,7 +523,6 @@ class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
task.node.driver_info['ilo_deploy_iso'] = 'deploy-iso'
returned_state = task.driver.deploy.deploy(task)
node_power_action_mock.assert_any_call(task, states.POWER_OFF)
cache_instance_image_mock.assert_called_once_with(task.context,
task.node)
check_image_size_mock.assert_called_once_with(task)
@ -527,13 +535,37 @@ class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
self.assertEqual(states.DEPLOYWAIT, returned_state)
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(manager_utils, 'node_power_action')
def test_tear_down(self, node_power_action_mock):
def test_tear_down(self,
node_power_action_mock,
update_secure_boot_mode_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
returned_state = task.driver.deploy.tear_down(task)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
self.assertEqual(states.DELETED, returned_state)
@mock.patch.object(ilo_deploy.LOG, 'warn')
@mock.patch.object(ilo_deploy, 'exception')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(manager_utils, 'node_power_action')
def test_tear_down_handle_exception(self,
node_power_action_mock,
update_secure_boot_mode_mock,
exception_mock,
mock_log):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
exception_mock.IloOperationNotSupported = Exception
update_secure_boot_mode_mock.side_effect = Exception
returned_state = task.driver.deploy.tear_down(task)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
self.assertTrue(mock_log.called)
self.assertEqual(states.DELETED, returned_state)
@mock.patch.object(ilo_deploy, '_clean_up_boot_iso_for_instance')
@ -545,13 +577,12 @@ class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
destroy_images_mock.assert_called_once_with(task.node.uuid)
clean_up_boot_mock.assert_called_once_with(task.node)
@mock.patch.object(ilo_common, 'update_boot_mode')
def test_prepare(self,
update_boot_mode_mock):
@mock.patch.object(ilo_deploy, '_prepare_node_for_deploy')
def test_prepare(self, func_prepare_node_for_deploy):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.deploy.prepare(task)
update_boot_mode_mock.assert_called_once_with(task)
func_prepare_node_for_deploy.assert_called_once_with(task)
class IloVirtualMediaAgentDeployTestCase(db_base.DbTestCase):
@ -686,14 +717,19 @@ class VendorPassthruTestCase(db_base.DbTestCase):
self.assertFalse(get_deploy_info_mock.called)
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
@mock.patch.object(manager_utils, 'node_set_boot_device')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot')
@mock.patch.object(ilo_deploy, '_get_boot_iso')
@mock.patch.object(iscsi_deploy, 'continue_deploy')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
def test_pass_deploy_info_resume(self, cleanup_vmedia_boot_mock,
continue_deploy_mock, get_boot_iso_mock,
setup_vmedia_mock, set_boot_device_mock,
def test_pass_deploy_info_resume(self,
cleanup_vmedia_boot_mock,
continue_deploy_mock,
get_boot_iso_mock,
setup_vmedia_mock,
func_update_boot_mode,
func_update_secure_boot_mode,
notify_deploy_complete_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
continue_deploy_mock.return_value = {}
@ -711,13 +747,16 @@ class VendorPassthruTestCase(db_base.DbTestCase):
continue_deploy_mock.assert_called_once_with(task, **kwargs)
self.assertFalse(get_boot_iso_mock.called)
self.assertFalse(setup_vmedia_mock.called)
self.assertFalse(set_boot_device_mock.called)
self.assertFalse(func_update_boot_mode.called)
self.assertFalse(func_update_secure_boot_mode.called)
self.assertEqual(states.DEPLOYING, task.node.provision_state)
self.assertEqual(states.ACTIVE,
task.node.target_provision_state)
self.assertFalse(notify_deploy_complete_mock.called)
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(manager_utils, 'node_set_boot_device')
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot')
@mock.patch.object(ilo_deploy, '_get_boot_iso')
@ -726,6 +765,8 @@ class VendorPassthruTestCase(db_base.DbTestCase):
def test_pass_deploy_info_good(self, cleanup_vmedia_boot_mock,
continue_deploy_mock, get_boot_iso_mock,
setup_vmedia_mock, set_boot_device_mock,
func_update_boot_mode,
func_update_secure_boot_mode,
notify_deploy_complete_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
continue_deploy_mock.return_value = {'root uuid': 'root-uuid'}
@ -745,12 +786,14 @@ class VendorPassthruTestCase(db_base.DbTestCase):
self.assertEqual(states.ACTIVE, task.node.provision_state)
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
set_boot_device_mock.assert_called_once_with(task,
boot_devices.CDROM)
boot_devices.CDROM,
persistent=True)
func_update_boot_mode.assert_called_once_with(task)
func_update_secure_boot_mode.assert_called_once_with(task, True)
self.assertEqual('boot-iso',
task.node.instance_info['ilo_boot_iso'])
self.assertEqual(states.ACTIVE, task.node.provision_state)
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
notify_deploy_complete_mock.assert_called_once_with('123456')
notify_deploy_complete_mock.assert_called_once_with('123456')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
def test_pass_deploy_info_bad(self, cleanup_vmedia_boot_mock):
@ -816,10 +859,14 @@ class VendorPassthruTestCase(db_base.DbTestCase):
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
@mock.patch.object(manager_utils, 'node_set_boot_device')
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(iscsi_deploy, 'continue_deploy')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
def _test_pass_deploy_info_localboot(self, cleanup_vmedia_boot_mock,
continue_deploy_mock,
func_update_boot_mode,
func_update_secure_boot_mode,
set_boot_device_mock,
notify_deploy_complete_mock):
@ -839,6 +886,8 @@ class VendorPassthruTestCase(db_base.DbTestCase):
set_boot_device_mock.assert_called_once_with(task,
boot_devices.DISK,
persistent=True)
func_update_boot_mode.assert_called_once_with(task)
func_update_secure_boot_mode.assert_called_once_with(task, True)
notify_deploy_complete_mock.assert_called_once_with('123456')
self.assertEqual(states.ACTIVE, task.node.provision_state)
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
@ -853,6 +902,8 @@ class VendorPassthruTestCase(db_base.DbTestCase):
self.node.save()
self._test_pass_deploy_info_localboot()
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(keystone, 'get_admin_auth_token')
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
'reboot_and_finish_deploy')
@ -863,7 +914,9 @@ class VendorPassthruTestCase(db_base.DbTestCase):
do_agent_iscsi_deploy_mock,
configure_vmedia_boot_mock,
reboot_and_finish_deploy_mock,
keystone_mock):
keystone_mock,
boot_mode_cap_mock,
update_secure_boot_mock):
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.DEPLOYING
self.node.save()
@ -878,10 +931,14 @@ class VendorPassthruTestCase(db_base.DbTestCase):
mock.ANY)
configure_vmedia_boot_mock.assert_called_once_with(
task, 'some-root-uuid')
boot_mode_cap_mock.assert_called_once_with(task)
update_secure_boot_mock.assert_called_once_with(task, True)
reboot_and_finish_deploy_mock.assert_called_once_with(task)
# Ensure that admin token is populated in task
self.assertEqual('admin-token', task.context.auth_token)
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
'reboot_and_finish_deploy')
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
@ -891,7 +948,9 @@ class VendorPassthruTestCase(db_base.DbTestCase):
def test_continue_deploy_localboot(self, cleanup_vmedia_boot_mock,
do_agent_iscsi_deploy_mock,
configure_local_boot_mock,
reboot_and_finish_deploy_mock):
reboot_and_finish_deploy_mock,
boot_mode_cap_mock,
update_secure_boot_mock):
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.DEPLOYING
self.node.instance_info = {
@ -908,8 +967,12 @@ class VendorPassthruTestCase(db_base.DbTestCase):
configure_local_boot_mock.assert_called_once_with(
task, root_uuid='some-root-uuid',
efi_system_part_uuid=None)
boot_mode_cap_mock.assert_called_once_with(task)
update_secure_boot_mock.assert_called_once_with(task, True)
reboot_and_finish_deploy_mock.assert_called_once_with(task)
@mock.patch.object(ilo_deploy, '_update_secure_boot_mode')
@mock.patch.object(ilo_common, 'update_boot_mode')
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
'reboot_and_finish_deploy')
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
@ -919,7 +982,9 @@ class VendorPassthruTestCase(db_base.DbTestCase):
def test_continue_deploy_localboot_uefi(self, cleanup_vmedia_boot_mock,
do_agent_iscsi_deploy_mock,
configure_local_boot_mock,
reboot_and_finish_deploy_mock):
reboot_and_finish_deploy_mock,
boot_mode_cap_mock,
update_secure_boot_mock):
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.DEPLOYING
self.node.instance_info = {
@ -937,6 +1002,8 @@ class VendorPassthruTestCase(db_base.DbTestCase):
configure_local_boot_mock.assert_called_once_with(
task, root_uuid='some-root-uuid',
efi_system_part_uuid='efi-system-part-uuid')
boot_mode_cap_mock.assert_called_once_with(task)
update_secure_boot_mock.assert_called_once_with(task, True)
reboot_and_finish_deploy_mock.assert_called_once_with(task)