diff --git a/ironic/drivers/modules/ilo/boot.py b/ironic/drivers/modules/ilo/boot.py index 47bdc14ccb..b67c6e0bb6 100644 --- a/ironic/drivers/modules/ilo/boot.py +++ b/ironic/drivers/modules/ilo/boot.py @@ -37,6 +37,7 @@ from ironic.conductor import utils as manager_utils from ironic.drivers import base from ironic.drivers.modules import deploy_utils from ironic.drivers.modules.ilo import common as ilo_common +from ironic.drivers.modules import pxe LOG = logging.getLogger(__name__) @@ -564,3 +565,71 @@ class IloVirtualMediaBoot(base.BootInterface): i_info['ilo_boot_iso'] = boot_iso node.instance_info = i_info node.save() + + +class IloPXEBoot(pxe.PXEBoot): + + @METRICS.timer('IloPXEBoot.prepare_ramdisk') + def prepare_ramdisk(self, task, ramdisk_params): + """Prepares the boot of Ironic ramdisk using PXE. + + This method prepares the boot of the deploy ramdisk after + reading relevant information from the node's driver_info and + instance_info. + + :param task: a task from TaskManager. + :param ramdisk_params: the parameters to be passed to the ramdisk. + :returns: None + :raises: MissingParameterValue, if some information is missing in + node's driver_info or instance_info. + :raises: InvalidParameterValue, if some information provided is + invalid. + :raises: IronicException, if some power or set boot boot device + operation failed on the node. + :raises: IloOperationError, if some operation on iLO failed. + """ + + if task.node.provision_state == states.DEPLOYING: + prepare_node_for_deploy(task) + + super(IloPXEBoot, self).prepare_ramdisk(task, ramdisk_params) + + @METRICS.timer('IloPXEBoot.prepare_instance') + def prepare_instance(self, task): + """Prepares the boot of instance. + + This method prepares the boot of the instance after reading + relevant information from the node's instance_info. In case of netboot, + it updates the dhcp entries and switches the PXE config. In case of + localboot, it cleans up the PXE config. + + :param task: a task from TaskManager. + :returns: None + :raises: IloOperationError, if some operation on iLO failed. + """ + + # Set boot mode + ilo_common.update_boot_mode(task) + # Need to enable secure boot, if being requested + ilo_common.update_secure_boot_mode(task, True) + + super(IloPXEBoot, self).prepare_instance(task) + + @METRICS.timer('IloPXEBoot.clean_up_instance') + def clean_up_instance(self, task): + """Cleans up the boot of instance. + + This method cleans up the PXE environment that was setup for booting + the instance. It unlinks the instance kernel/ramdisk in the node's + directory in tftproot and removes it's PXE config. + + :param task: a task from TaskManager. + :returns: None + :raises: IloOperationError, if some operation on iLO failed. + """ + + LOG.debug("Cleaning up the instance.") + manager_utils.node_power_action(task, states.POWER_OFF) + disable_secure_boot_if_supported(task) + + super(IloPXEBoot, self).clean_up_instance(task) diff --git a/ironic/drivers/modules/ilo/deploy.py b/ironic/drivers/modules/ilo/deploy.py deleted file mode 100644 index 7cb900c595..0000000000 --- a/ironic/drivers/modules/ilo/deploy.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2014 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -iLO Deploy Driver(s) and supporting methods. -""" - -from ironic_lib import metrics_utils -from oslo_log import log as logging - -from ironic.common import boot_devices -from ironic.common import states -from ironic.conductor import task_manager -from ironic.conductor import utils as manager_utils -from ironic.drivers.modules.ilo import boot as ilo_boot -from ironic.drivers.modules.ilo import common as ilo_common -from ironic.drivers.modules import iscsi_deploy - -LOG = logging.getLogger(__name__) - -METRICS = metrics_utils.get_metrics_logger(__name__) - - -class IloIscsiDeployMixin(object): - - @METRICS.timer('IloIscsiDeployMixin.tear_down') - @task_manager.require_exclusive_lock - def tear_down(self, task): - """Tear down a previous deployment on the task's node. - - Power off the node. All actual clean-up is done in the clean_up() - method which should be called separately. - - :param task: a TaskManager instance containing the node to act on. - :returns: deploy state DELETED. - :raises: IloOperationError, if some operation on iLO failed. - """ - manager_utils.node_power_action(task, states.POWER_OFF) - ilo_boot.disable_secure_boot_if_supported(task) - return super(IloIscsiDeployMixin, self).tear_down(task) - - @METRICS.timer('IloIscsiDeployMixin.prepare_cleaning') - def prepare_cleaning(self, task): - """Boot into the agent to prepare for cleaning. - - :param task: a TaskManager object containing the node - :returns: states.CLEANWAIT to signify an asynchronous prepare. - :raises NodeCleaningFailure: if the previous cleaning ports cannot - be removed or if new cleaning ports cannot be created - :raises: IloOperationError, if some operation on iLO failed. - """ - # Powering off the Node before initiating boot for node cleaning. - # If node is in system POST, setting boot device fails. - manager_utils.node_power_action(task, states.POWER_OFF) - return super(IloIscsiDeployMixin, self).prepare_cleaning(task) - - @METRICS.timer('IloIscsiDeployMixin.continue_deploy') - @task_manager.require_exclusive_lock - def continue_deploy(self, task, **kwargs): - """Method invoked when deployed with the IPA ramdisk. - - This method is invoked during a heartbeat from an agent when - the node is in wait-call-back state. - This updates boot mode and secure boot settings, if required. - """ - ilo_common.update_boot_mode(task) - ilo_common.update_secure_boot_mode(task, True) - super(IloIscsiDeployMixin, self).continue_deploy(task, **kwargs) - - -class IloPXEDeploy(IloIscsiDeployMixin, iscsi_deploy.ISCSIDeploy): - - @METRICS.timer('IloPXEDeploy.prepare') - def prepare(self, task): - """Prepare the deployment environment for this task's node. - - If the node's 'capabilities' property includes a boot_mode, that - boot mode will be applied for the node. Otherwise, the existing - boot mode of the node is used in the node's 'capabilities' property. - - PXEDeploys' prepare method is then called, to prepare the deploy - environment for the node - - :param task: a TaskManager instance containing the node to act on. - :raises: IloOperationError, if some operation on iLO failed. - :raises: InvalidParameterValue, if some information is invalid. - """ - if task.node.provision_state == states.DEPLOYING: - ilo_boot.prepare_node_for_deploy(task) - - super(IloPXEDeploy, self).prepare(task) - - @METRICS.timer('IloPXEDeploy.deploy') - def deploy(self, task): - """Start deployment of the task's node. - - This method sets the boot device to 'NETWORK' and then calls - PXEDeploy's deploy method to deploy on the given node. - - :param task: a TaskManager instance containing the node to act on. - :returns: deploy state DEPLOYWAIT. - """ - manager_utils.node_set_boot_device(task, boot_devices.PXE) - return super(IloPXEDeploy, self).deploy(task) diff --git a/ironic/drivers/pxe.py b/ironic/drivers/pxe.py index 233e2474eb..64db66a481 100644 --- a/ironic/drivers/pxe.py +++ b/ironic/drivers/pxe.py @@ -26,8 +26,8 @@ from ironic.drivers import ipmi from ironic.drivers.modules import agent from ironic.drivers.modules.cimc import management as cimc_mgmt from ironic.drivers.modules.cimc import power as cimc_power +from ironic.drivers.modules.ilo import boot as ilo_boot from ironic.drivers.modules.ilo import console as ilo_console -from ironic.drivers.modules.ilo import deploy as ilo_deploy from ironic.drivers.modules.ilo import inspect as ilo_inspect from ironic.drivers.modules.ilo import management as ilo_management from ironic.drivers.modules.ilo import power as ilo_power @@ -91,8 +91,8 @@ class PXEAndIloDriver(base.BaseDriver): driver=self.__class__.__name__, reason=_("Unable to import proliantutils library")) self.power = ilo_power.IloPower() - self.boot = pxe.PXEBoot() - self.deploy = ilo_deploy.IloPXEDeploy() + self.boot = ilo_boot.IloPXEBoot() + self.deploy = iscsi_deploy.ISCSIDeploy() self.vendor = ilo_vendor.VendorPassthru() self.console = ilo_console.IloConsoleInterface() self.management = ilo_management.IloManagement() diff --git a/ironic/tests/unit/drivers/modules/ilo/test_boot.py b/ironic/tests/unit/drivers/modules/ilo/test_boot.py index 303cd8cf68..4cf54c538f 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_boot.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_boot.py @@ -34,6 +34,7 @@ from ironic.conductor import utils as manager_utils from ironic.drivers.modules import deploy_utils from ironic.drivers.modules.ilo import boot as ilo_boot from ironic.drivers.modules.ilo import common as ilo_common +from ironic.drivers.modules import pxe from ironic.drivers import utils as driver_utils from ironic.tests.unit.conductor import mgr_utils from ironic.tests.unit.db import base as db_base @@ -930,3 +931,77 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase): mock.ANY, task, "12312642-09d3-467f-8e09-12385826a123") update_boot_mode_mock.assert_called_once_with(task) update_secure_boot_mode_mock.assert_called_once_with(task, True) + + +class IloPXEBootTestCase(db_base.DbTestCase): + + def setUp(self): + super(IloPXEBootTestCase, self).setUp() + mgr_utils.mock_the_extension_manager(driver="pxe_ilo") + self.node = obj_utils.create_test_node( + self.context, driver='pxe_ilo', driver_info=INFO_DICT) + + @mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True, + autospec=True) + @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True, + autospec=True) + def test_prepare_ramdisk_not_deploying_not_cleaning( + self, pxe_boot_mock, prepare_node_mock): + self.node.provision_state = states.CLEANING + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertIsNone( + task.driver.boot.prepare_ramdisk(task, None)) + + self.assertFalse(prepare_node_mock.called) + pxe_boot_mock.assert_called_once_with(mock.ANY, task, None) + + @mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True, + autospec=True) + @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True, + autospec=True) + def test_prepare_ramdisk_in_deploying(self, pxe_boot_mock, + prepare_node_mock): + self.node.provision_state = states.DEPLOYING + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertIsNone( + task.driver.boot.prepare_ramdisk(task, None)) + + prepare_node_mock.assert_called_once_with(task) + pxe_boot_mock.assert_called_once_with(mock.ANY, task, None) + + @mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True, + autospec=True) + @mock.patch.object(manager_utils, 'node_power_action', spec_set=True, + autospec=True) + @mock.patch.object(pxe.PXEBoot, 'clean_up_instance', spec_set=True, + autospec=True) + def test_clean_up_instance(self, pxe_cleanup_mock, node_power_mock, + update_secure_boot_mode_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.boot.clean_up_instance(task) + + node_power_mock.assert_called_once_with(task, states.POWER_OFF) + update_secure_boot_mode_mock.assert_called_once_with(task, False) + pxe_cleanup_mock.assert_called_once_with(mock.ANY, task) + + @mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True, + autospec=True) + @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True, + autospec=True) + @mock.patch.object(pxe.PXEBoot, 'prepare_instance', spec_set=True, + autospec=True) + def test_prepare_instance(self, pxe_prepare_instance_mock, + update_boot_mode_mock, + update_secure_boot_mode_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.boot.prepare_instance(task) + + update_boot_mode_mock.assert_called_once_with(task) + update_secure_boot_mode_mock.assert_called_once_with(task, True) + pxe_prepare_instance_mock.assert_called_once_with(mock.ANY, task) diff --git a/ironic/tests/unit/drivers/modules/ilo/test_deploy.py b/ironic/tests/unit/drivers/modules/ilo/test_deploy.py deleted file mode 100644 index e33f6f1eff..0000000000 --- a/ironic/tests/unit/drivers/modules/ilo/test_deploy.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright 2014 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Test class for deploy methods used by iLO modules.""" - -import mock -import six - -from ironic.common import boot_devices -from ironic.common import states -from ironic.conductor import task_manager -from ironic.conductor import utils as manager_utils -from ironic.conf import CONF -from ironic.drivers.modules.ilo import boot as ilo_boot -from ironic.drivers.modules.ilo import common as ilo_common -from ironic.drivers.modules import iscsi_deploy -from ironic.tests.unit.conductor import mgr_utils -from ironic.tests.unit.db import base as db_base -from ironic.tests.unit.db import utils as db_utils -from ironic.tests.unit.objects import utils as obj_utils - - -if six.PY3: - import io - file = io.BytesIO - -INFO_DICT = db_utils.get_test_ilo_info() - - -class IloPXEDeployTestCase(db_base.DbTestCase): - - def setUp(self): - super(IloPXEDeployTestCase, self).setUp() - mgr_utils.mock_the_extension_manager(driver="pxe_ilo") - self.node = obj_utils.create_test_node( - self.context, driver='pxe_ilo', driver_info=INFO_DICT) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'validate', spec_set=True, - autospec=True) - def test_validate(self, pxe_validate_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.deploy.validate(task) - pxe_validate_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'prepare', spec_set=True, - autospec=True) - @mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True, - autospec=True) - def test_prepare(self, - prepare_node_for_deploy_mock, - pxe_prepare_mock): - self.node.provision_state = states.DEPLOYING - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.properties['capabilities'] = 'boot_mode:uefi' - task.driver.deploy.prepare(task) - prepare_node_for_deploy_mock.assert_called_once_with(task) - pxe_prepare_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'prepare', spec_set=True, - autospec=True) - @mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True, - autospec=True) - def test_prepare_active_node(self, - prepare_node_for_deploy_mock, - pxe_prepare_mock): - """Ensure nodes in running states are not inadvertently changed""" - test_states = list(states.STABLE_STATES) - test_states.extend([states.CLEANING, - states.CLEANWAIT, - states.INSPECTING]) - for state in test_states: - self.node.provision_state = state - self.node.save() - prepare_node_for_deploy_mock.reset_mock() - pxe_prepare_mock.reset_mock() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.properties['capabilities'] = 'boot_mode:uefi' - task.driver.deploy.prepare(task) - self.assertFalse(prepare_node_for_deploy_mock.called) - pxe_prepare_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'prepare', spec_set=True, - autospec=True) - @mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True, - autospec=True) - def test_prepare_whole_disk_image_uefi(self, prepare_node_for_deploy_mock, - pxe_prepare_mock): - CONF.set_override('default_boot_option', 'netboot', 'deploy') - self.node.provision_state = states.DEPLOYING - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.properties['capabilities'] = 'boot_mode:uefi' - task.node.driver_internal_info['is_whole_disk_image'] = True - task.driver.deploy.prepare(task) - prepare_node_for_deploy_mock.assert_called_once_with(task) - pxe_prepare_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'deploy', spec_set=True, - autospec=True) - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - def test_deploy_boot_mode_exists(self, set_persistent_mock, - pxe_deploy_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.deploy.deploy(task) - set_persistent_mock.assert_called_with(task, boot_devices.PXE) - pxe_deploy_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'tear_down', - spec_set=True, autospec=True) - @mock.patch.object(ilo_common, 'update_secure_boot_mode', autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', spec_set=True, - autospec=True) - def test_tear_down(self, node_power_action_mock, - update_secure_boot_mode_mock, pxe_tear_down_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - pxe_tear_down_mock.return_value = states.DELETED - 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) - pxe_tear_down_mock.assert_called_once_with(mock.ANY, task) - self.assertEqual(states.DELETED, returned_state) - - @mock.patch.object(ilo_boot.LOG, 'warning', - spec_set=True, autospec=True) - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'tear_down', - spec_set=True, autospec=True) - @mock.patch.object(ilo_boot, 'exception', spec_set=True, autospec=True) - @mock.patch.object(ilo_common, 'update_secure_boot_mode', - spec_set=True, autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', spec_set=True, - autospec=True) - def test_tear_down_handle_exception(self, node_power_action_mock, - update_secure_boot_mode_mock, - exception_mock, pxe_tear_down_mock, - mock_log): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - pxe_tear_down_mock.return_value = states.DELETED - exception_mock.IloOperationNotSupported = Exception - update_secure_boot_mode_mock.side_effect = Exception - returned_state = task.driver.deploy.tear_down(task) - update_secure_boot_mode_mock.assert_called_once_with(task, False) - pxe_tear_down_mock.assert_called_once_with(mock.ANY, task) - node_power_action_mock.assert_called_once_with(task, - states.POWER_OFF) - self.assertTrue(mock_log.called) - self.assertEqual(states.DELETED, returned_state) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'prepare_cleaning', - spec_set=True, autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', spec_set=True, - autospec=True) - def test_prepare_cleaning(self, node_power_action_mock, - iscsi_prep_clean_mock): - iscsi_prep_clean_mock.return_value = states.CLEANWAIT - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - ret = task.driver.deploy.prepare_cleaning(task) - self.assertEqual(states.CLEANWAIT, ret) - node_power_action_mock.assert_called_once_with(task, - states.POWER_OFF) - iscsi_prep_clean_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'continue_deploy', - spec_set=True, autospec=True) - @mock.patch.object(ilo_common, 'update_secure_boot_mode', autospec=True) - @mock.patch.object(ilo_common, 'update_boot_mode', autospec=True) - def test_continue_deploy(self, - func_update_boot_mode, - func_update_secure_boot_mode, - pxe_vendorpassthru_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.provision_state = states.DEPLOYWAIT - task.node.target_provision_state = states.ACTIVE - task.driver.deploy.continue_deploy(task) - func_update_boot_mode.assert_called_once_with(task) - func_update_secure_boot_mode.assert_called_once_with(task, True) - pxe_vendorpassthru_mock.assert_called_once_with( - mock.ANY, task)