From 8f45317d4345872c050b2fa347f7be033556d2f5 Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 5 Jul 2017 11:13:41 -0300 Subject: [PATCH] Removes agent mixin from oneview drivers This patch removes the agent mixin interface from oneview drivers and adds a flag based solution instead. Once a set boot device operation needs to be performed, the operation is stored to driver_internal_info until the server power on. Before power the server on again, the operation stored is performed and then the server is powered on. Change-Id: I8dabf7ef1ff6e44c062310c5cb3509e545585c0a Closes-Bug: #1503855 --- ironic/drivers/modules/oneview/common.py | 50 ++- ironic/drivers/modules/oneview/deploy.py | 95 +----- ironic/drivers/modules/oneview/management.py | 103 ++++-- ironic/drivers/modules/oneview/power.py | 5 +- .../drivers/modules/oneview/test_deploy.py | 304 +----------------- .../modules/oneview/test_management.py | 212 ++++++++---- .../drivers/modules/oneview/test_power.py | 41 ++- ...-agent-mixin-removal-b7277e8f20df5ef2.yaml | 6 + 8 files changed, 312 insertions(+), 504 deletions(-) create mode 100644 releasenotes/notes/oneview-agent-mixin-removal-b7277e8f20df5ef2.yaml diff --git a/ironic/drivers/modules/oneview/common.py b/ironic/drivers/modules/oneview/common.py index 292a58556f..dfc62b9bc8 100644 --- a/ironic/drivers/modules/oneview/common.py +++ b/ironic/drivers/modules/oneview/common.py @@ -1,3 +1,4 @@ +# Copyright 2017 Hewlett Packard Enterprise Development Company LP. # Copyright 2015 Hewlett Packard Development Company, LP # Copyright 2015 Universidade Federal de Campina Grande # @@ -236,26 +237,41 @@ def _verify_node_info(node_namespace, node_info_dict, info_required): def node_has_server_profile(func): """Checks if the node's Server Hardware has a Server Profile associated. + Decorator to execute before the function execution if the Server Profile + is applied to the Server Hardware. + + :param func: a given decorated function. """ def inner(self, *args, **kwargs): oneview_client = self.oneview_client task = args[0] - oneview_info = get_oneview_info(task.node) - try: - node_has_server_profile = ( - oneview_client.get_server_profile_from_hardware(oneview_info) - ) - except oneview_exceptions.OneViewException as oneview_exc: - LOG.error( - "Failed to get server profile from OneView appliance for" - " node %(node)s. Error: %(message)s", - {"node": task.node.uuid, "message": oneview_exc} - ) - raise exception.OneViewError(error=oneview_exc) - if not node_has_server_profile: - raise exception.OperationNotPermitted( - _("A Server Profile is not associated with node %s.") % - task.node.uuid - ) + has_server_profile(task, oneview_client) return func(self, *args, **kwargs) return inner + + +def has_server_profile(task, oneview_client): + """Checks if the node's Server Hardware has a Server Profile associated. + + Function to check if the Server Profile is applied to the Server Hardware. + + :param oneview_client: an instance of the OneView client + :param task: a TaskManager instance containing the node to act on. + """ + oneview_info = get_oneview_info(task.node) + try: + node_has_server_profile = ( + oneview_client.get_server_profile_from_hardware(oneview_info) + ) + except oneview_exceptions.OneViewException as oneview_exc: + LOG.error( + "Failed to get server profile from OneView appliance for" + " node %(node)s. Error: %(message)s", + {"node": task.node.uuid, "message": oneview_exc} + ) + raise exception.OneViewError(error=oneview_exc) + if not node_has_server_profile: + raise exception.OperationNotPermitted( + _("A Server Profile is not associated with node %s.") % + task.node.uuid + ) diff --git a/ironic/drivers/modules/oneview/deploy.py b/ironic/drivers/modules/oneview/deploy.py index bbc0bccdfc..b1734be536 100644 --- a/ironic/drivers/modules/oneview/deploy.py +++ b/ironic/drivers/modules/oneview/deploy.py @@ -1,3 +1,4 @@ +# Copyright 2017 Hewlett Packard Enterprise Development Company LP. # Copyright 2016 Hewlett Packard Enterprise Development LP. # Copyright 2016 Universidade Federal de Campina Grande # All Rights Reserved. @@ -19,17 +20,12 @@ import abc from futurist import periodics from ironic_lib import metrics_utils from oslo_log import log as logging -import retrying import six from ironic.common import exception -from ironic.common.i18n import _ from ironic.common import states -from ironic.conductor import utils as manager_utils from ironic.conf import CONF from ironic.drivers.modules import agent -from ironic.drivers.modules import agent_base_vendor -from ironic.drivers.modules import deploy_utils as ironic_deploy_utils from ironic.drivers.modules import iscsi_deploy from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import deploy_utils @@ -266,94 +262,7 @@ class OneViewIscsiDeploy(iscsi_deploy.ISCSIDeploy, OneViewPeriodicTasks): super(OneViewIscsiDeploy, self).tear_down_cleaning(task) -# NOTE (thiagop): We overwrite this interface because we cannot change the boot -# device of OneView managed blades while they are still powered on. We moved -# the call of node_set_boot_device from reboot_to_instance to -# reboot_and_finish_deploy and changed the behavior to shutdown the node before -# doing it. -# TODO(thiagop): remove this interface once bug/1503855 is fixed -class OneViewAgentDeployMixin(object): - - @METRICS.timer('OneViewAgentDeployMixin.reboot_to_instance') - def reboot_to_instance(self, task, **kwargs): - task.process_event('resume') - node = task.node - error = self.check_deploy_success(node) - if error is not None: - # TODO(jimrollenhagen) power off if using neutron dhcp to - # align with pxe driver? - msg = (_('node %(node)s command status errored: %(error)s') % - {'node': node.uuid, 'error': error}) - LOG.error(msg) - ironic_deploy_utils.set_failed_state(task, msg) - return - - LOG.info('Image successfully written to node %s', node.uuid) - LOG.debug('Rebooting node %s to instance', node.uuid) - - self.reboot_and_finish_deploy(task) - - # NOTE(TheJulia): If we deployed a whole disk image, we - # should expect a whole disk image and clean-up the tftp files - # on-disk incase the node is disregarding the boot preference. - # TODO(rameshg87): Not all in-tree drivers using reboot_to_instance - # have a boot interface. So include a check for now. Remove this - # check once all in-tree drivers have a boot interface. - if task.driver.boot: - task.driver.boot.clean_up_ramdisk(task) - - @METRICS.timer('OneViewAgentDeployMixin.reboot_and_finish_deploy') - def reboot_and_finish_deploy(self, task): - """Helper method to trigger reboot on the node and finish deploy. - - This method initiates a reboot on the node. On success, it - marks the deploy as complete. On failure, it logs the error - and marks deploy as failure. - - :param task: a TaskManager object containing the node - :raises: InstanceDeployFailure, if node reboot failed. - """ - wait = CONF.agent.post_deploy_get_power_state_retry_interval * 1000 - attempts = CONF.agent.post_deploy_get_power_state_retries + 1 - - @retrying.retry( - stop_max_attempt_number=attempts, - retry_on_result=lambda state: state != states.POWER_OFF, - wait_fixed=wait - ) - def _wait_until_powered_off(task): - return task.driver.power.get_power_state(task) - - node = task.node - - try: - try: - self._client.power_off(node) - _wait_until_powered_off(task) - except Exception as e: - LOG.warning( - 'Failed to soft power off node %(node_uuid)s ' - 'in at least %(timeout)d seconds. Error: %(error)s', - {'node_uuid': node.uuid, - 'timeout': (wait * (attempts - 1)) / 1000, - 'error': e}) - manager_utils.node_power_action(task, states.POWER_OFF) - - manager_utils.node_set_boot_device(task, 'disk', - persistent=True) - manager_utils.node_power_action(task, states.POWER_ON) - except Exception as e: - msg = (_('Error rebooting node %(node)s after deploy. ' - 'Error: %(error)s') % - {'node': node.uuid, 'error': e}) - agent_base_vendor.log_and_raise_deployment_error(task, msg) - - task.process_event('done') - LOG.info('Deployment to node %s done', task.node.uuid) - - -class OneViewAgentDeploy(OneViewAgentDeployMixin, agent.AgentDeploy, - OneViewPeriodicTasks): +class OneViewAgentDeploy(agent.AgentDeploy, OneViewPeriodicTasks): """Class for OneView Agent deployment driver.""" oneview_driver = common.AGENT_PXE_ONEVIEW diff --git a/ironic/drivers/modules/oneview/management.py b/ironic/drivers/modules/oneview/management.py index 21aebadb49..04f87d593c 100644 --- a/ironic/drivers/modules/oneview/management.py +++ b/ironic/drivers/modules/oneview/management.py @@ -1,4 +1,4 @@ -# +# Copyright 2017 Hewlett Packard Enterprise Development Company LP. # Copyright 2015 Hewlett Packard Development Company, LP # Copyright 2015 Universidade Federal de Campina Grande # @@ -44,6 +44,59 @@ BOOT_DEVICE_OV_TO_GENERIC = { oneview_exceptions = importutils.try_import('oneview_client.exceptions') +def set_boot_device(task): + """Sets the boot device for a node. + + Sets the boot device to use on next reboot of the node. + + :param task: a task from TaskManager. + :raises: InvalidParameterValue if an invalid boot device is + specified. + :raises: OperationNotPermitted if the server has no server profile or + if the server is already powered on. + :raises: OneViewError if the communication with OneView fails + """ + oneview_client = common.get_oneview_client() + common.has_server_profile(task, oneview_client) + driver_internal_info = task.node.driver_internal_info + next_boot_device = driver_internal_info.get('next_boot_device') + + if next_boot_device: + boot_device = next_boot_device.get('boot_device') + persistent = next_boot_device.get('persistent') + + if boot_device not in sorted(BOOT_DEVICE_MAPPING_TO_OV): + raise exception.InvalidParameterValue( + _("Invalid boot device %s specified.") % boot_device) + + LOG.debug("Setting boot device to %(boot_device)s and " + "persistent to %(persistent)s for node %(node)s", + {"boot_device": boot_device, "persistent": persistent, + "node": task.node.uuid}) + + try: + oneview_info = common.get_oneview_info(task.node) + device_to_oneview = BOOT_DEVICE_MAPPING_TO_OV.get(boot_device) + oneview_client.set_boot_device(oneview_info, + device_to_oneview, + onetime=not persistent) + driver_internal_info.pop('next_boot_device', None) + task.node.driver_internal_info = driver_internal_info + task.node.save() + except oneview_exceptions.OneViewException as oneview_exc: + msg = (_( + "Error setting boot device on OneView. Error: %s") + % oneview_exc + ) + raise exception.OneViewError(error=msg) + + else: + LOG.debug("Not going to set boot device because there is no " + "'next_boot_device' on driver_internal_info " + "for the %(node)s", + {"node": task.node.uuid}) + + class OneViewManagement(base.ManagementInterface): def __init__(self): @@ -99,9 +152,11 @@ class OneViewManagement(base.ManagementInterface): @task_manager.require_exclusive_lock @common.node_has_server_profile def set_boot_device(self, task, device, persistent=False): - """Sets the boot device for a node. + """Set the next boot device to the node. - Sets the boot device to use on next reboot of the node. + Sets the boot device to the node next_boot_device on + driver_internal_info namespace. The operation will be + performed before the next node power on. :param task: a task from TaskManager. :param device: the boot device, one of the supported devices @@ -111,36 +166,32 @@ class OneViewManagement(base.ManagementInterface): Default: False. :raises: InvalidParameterValue if an invalid boot device is specified. - :raises: OperationNotPermitted if the server has no server profile or - if the server is already powered on. - :raises: OneViewError if the communication with OneView fails """ - oneview_info = common.get_oneview_info(task.node) - if device not in self.get_supported_boot_devices(task): raise exception.InvalidParameterValue( _("Invalid boot device %s specified.") % device) - LOG.debug("Setting boot device to %(device)s for node %(node)s", - {"device": device, "node": task.node.uuid}) - try: - device_to_oneview = BOOT_DEVICE_MAPPING_TO_OV.get(device) - self.oneview_client.set_boot_device(oneview_info, - device_to_oneview, - onetime=not persistent) - except oneview_exceptions.OneViewException as oneview_exc: - msg = (_( - "Error setting boot device on OneView. Error: %s") - % oneview_exc - ) - raise exception.OneViewError(error=msg) + driver_internal_info = task.node.driver_internal_info + next_boot_device = {'boot_device': device, + 'persistent': persistent} + driver_internal_info['next_boot_device'] = next_boot_device + task.node.driver_internal_info = driver_internal_info + task.node.save() + LOG.debug("The 'next_boot_device' flag was updated on " + "driver_internal_info with device=%(boot_device)s " + "and persistent=%(persistent)s for the node " + "%(node)s", + {"boot_device": device, "persistent": persistent, + "node": task.node.uuid}) @METRICS.timer('OneViewManagement.get_boot_device') @common.node_has_server_profile def get_boot_device(self, task): - """Get the current boot device for the task's node. + """Get the current boot device from the node. - Provides the current boot device of the node. + Gets the boot device from the node 'next_boot_device on + driver_internal_info namespace if exists. Gets through + a request to OneView otherwise. :param task: a task from TaskManager. :returns: a dictionary containing: @@ -153,6 +204,12 @@ class OneViewManagement(base.ManagementInterface): :raises: InvalidParameterValue if the boot device is unknown :raises: OneViewError if the communication with OneView fails """ + driver_internal_info = task.node.driver_internal_info + next_boot_device = driver_internal_info.get('next_boot_device') + + if next_boot_device: + return next_boot_device + oneview_info = common.get_oneview_info(task.node) try: diff --git a/ironic/drivers/modules/oneview/power.py b/ironic/drivers/modules/oneview/power.py index 49cf69bbc4..7637c38c54 100644 --- a/ironic/drivers/modules/oneview/power.py +++ b/ironic/drivers/modules/oneview/power.py @@ -1,4 +1,4 @@ -# +# Copyright 2017 Hewlett Packard Enterprise Development Company LP. # Copyright 2015 Hewlett Packard Development Company, LP # Copyright 2015 Universidade Federal de Campina Grande # @@ -25,6 +25,7 @@ from ironic.conductor import task_manager from ironic.drivers import base from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import deploy_utils +from ironic.drivers.modules.oneview import management LOG = logging.getLogger(__name__) @@ -130,11 +131,13 @@ class OneViewPower(base.PowerInterface): try: if power_state == states.POWER_ON: + management.set_boot_device(task) self.oneview_client.power_on(oneview_info) elif power_state == states.POWER_OFF: self.oneview_client.power_off(oneview_info) elif power_state == states.REBOOT: self.oneview_client.power_off(oneview_info) + management.set_boot_device(task) self.oneview_client.power_on(oneview_info) else: raise exception.InvalidParameterValue( diff --git a/ironic/tests/unit/drivers/modules/oneview/test_deploy.py b/ironic/tests/unit/drivers/modules/oneview/test_deploy.py index 062465fc6b..737f96e8b7 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_deploy.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_deploy.py @@ -1,3 +1,4 @@ +# Copyright 2017 Hewlett Packard Enterprise Development Company LP. # Copyright 2016 Hewlett Packard Enterprise Development LP. # Copyright 2016 Universidade Federal de Campina Grande # All Rights Reserved. @@ -14,9 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -import time -import types - import mock from oslo_utils import importutils @@ -24,17 +22,12 @@ from ironic.common import driver_factory from ironic.common import exception 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 import agent -from ironic.drivers.modules import agent_client from ironic.drivers.modules import iscsi_deploy from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import deploy from ironic.drivers.modules.oneview import deploy_utils -from ironic.drivers.modules.oneview import power -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 from ironic.tests.unit.db import utils as db_utils @@ -266,301 +259,6 @@ class OneViewPeriodicTasks(db_base.DbTestCase): self.assertFalse('oneview_error' in self.node.driver_internal_info) -@mock.patch.object(common, 'get_oneview_client', spec_set=True, autospec=True) -class TestOneViewAgentDeploy(db_base.DbTestCase): - - def setUp(self): - super(TestOneViewAgentDeploy, self).setUp() - self.config( - post_deploy_get_power_state_retries=GET_POWER_STATE_RETRIES, - group='agent') - mgr_utils.mock_the_extension_manager(driver="agent_pxe_oneview") - self.driver = driver_factory.get_driver("agent_pxe_oneview") - self.node = obj_utils.create_test_node( - self.context, driver='agent_pxe_oneview', - properties=db_utils.get_test_oneview_properties(), - driver_info=db_utils.get_test_oneview_driver_info(), - driver_internal_info={'agent_url': 'http://1.2.3.4:5678'}, - ) - - @mock.patch.object(time, 'sleep', lambda seconds: None) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(power.OneViewPower, 'get_power_state', - spec=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'power_off', - spec=types.FunctionType) - @mock.patch('ironic.conductor.utils.node_set_boot_device', autospec=True) - def test_reboot_and_finish_deploy(self, set_bootdev_mock, power_off_mock, - get_power_state_mock, - node_power_action_mock, - mock_get_ov_client): - self.node.provision_state = states.DEPLOYING - 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.side_effect = [states.POWER_ON, - states.POWER_OFF] - task.driver.deploy.reboot_and_finish_deploy(task) - power_off_mock.assert_called_once_with(task.node) - self.assertEqual(2, get_power_state_mock.call_count) - set_bootdev_mock.assert_called_once_with(task, 'disk', - persistent=True) - 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(time, 'sleep', lambda seconds: None) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(power.OneViewPower, 'get_power_state', - spec=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'power_off', - spec=types.FunctionType) - def test_reboot_and_finish_deploy_soft_poweroff_doesnt_complete( - self, power_off_mock, get_power_state_mock, - node_power_action_mock, mock_get_ov_client): - oneview_client = mock_get_ov_client.return_value - self.driver.management.oneview_client = oneview_client - fake_server_hardware = oneview_models.ServerHardware() - fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' - oneview_client.get_server_hardware_by_uuid.return_value = ( - fake_server_hardware - ) - mock_get_ov_client.return_value = oneview_client - - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - - driver_info = self.node.driver_info - driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/' - - self.node.driver_info = driver_info - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - get_power_state_mock.return_value = states.POWER_ON - task.driver.deploy.reboot_and_finish_deploy(task) - power_off_mock.assert_called_once_with(task.node) - self.assertEqual(GET_POWER_STATE_RETRIES + 1, - get_power_state_mock.call_count) - node_power_action_mock.assert_has_calls([ - mock.call(task, states.POWER_OFF), - mock.call(task, states.POWER_ON) - ]) - self.assertEqual(states.ACTIVE, task.node.provision_state) - self.assertEqual(states.NOSTATE, task.node.target_provision_state) - - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(agent_client.AgentClient, 'power_off', - spec=types.FunctionType) - def test_reboot_and_finish_deploy_soft_poweroff_fails( - self, power_off_mock, node_power_action_mock, - mock_get_ov_client): - oneview_client = mock_get_ov_client.return_value - self.driver.management.oneview_client = oneview_client - - fake_server_hardware = oneview_models.ServerHardware() - fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' - oneview_client.get_server_hardware_by_uuid.return_value = ( - fake_server_hardware - ) - mock_get_ov_client.return_value = oneview_client - - power_off_mock.side_effect = RuntimeError("boom") - - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - - driver_info = self.node.driver_info - driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/' - - self.node.driver_info = driver_info - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.deploy.reboot_and_finish_deploy(task) - power_off_mock.assert_called_once_with(task.node) - node_power_action_mock.assert_has_calls([ - mock.call(task, states.POWER_OFF), - mock.call(task, states.POWER_ON) - ]) - self.assertEqual(states.ACTIVE, task.node.provision_state) - self.assertEqual(states.NOSTATE, task.node.target_provision_state) - - @mock.patch.object(time, 'sleep', lambda seconds: None) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(power.OneViewPower, 'get_power_state', - spec=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'power_off', - spec=types.FunctionType) - def test_reboot_and_finish_deploy_get_power_state_fails( - self, power_off_mock, get_power_state_mock, - node_power_action_mock, mock_get_ov_client): - oneview_client = mock_get_ov_client.return_value - self.driver.management.oneview_client = oneview_client - fake_server_hardware = oneview_models.ServerHardware() - fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' - oneview_client.get_server_hardware_by_uuid.return_value = ( - fake_server_hardware - ) - mock_get_ov_client.return_value = oneview_client - - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - - driver_info = self.node.driver_info - driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/' - - self.node.driver_info = driver_info - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - get_power_state_mock.side_effect = RuntimeError("boom") - task.driver.deploy.reboot_and_finish_deploy(task) - power_off_mock.assert_called_once_with(task.node) - self.assertEqual(GET_POWER_STATE_RETRIES + 1, - get_power_state_mock.call_count) - node_power_action_mock.assert_has_calls([ - mock.call(task, states.POWER_OFF), - mock.call(task, states.POWER_ON) - ]) - self.assertEqual(states.ACTIVE, task.node.provision_state) - self.assertEqual(states.NOSTATE, task.node.target_provision_state) - - @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True) - @mock.patch.object(time, 'sleep', lambda seconds: None) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(power.OneViewPower, 'get_power_state', - spec=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'power_off', - spec=types.FunctionType) - def test_reboot_and_finish_deploy_power_action_fails( - self, power_off_mock, get_power_state_mock, - node_power_action_mock, collect_ramdisk_logs_mock, - mock_get_ov_client): - self.node.provision_state = states.DEPLOYING - 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_ON - node_power_action_mock.side_effect = RuntimeError("boom") - self.assertRaises(exception.InstanceDeployFailure, - task.driver.deploy.reboot_and_finish_deploy, - task) - power_off_mock.assert_called_once_with(task.node) - self.assertEqual(GET_POWER_STATE_RETRIES + 1, - get_power_state_mock.call_count) - node_power_action_mock.assert_has_calls([ - mock.call(task, states.POWER_OFF), - mock.call(task, states.POWER_OFF)]) - self.assertEqual(states.DEPLOYFAIL, task.node.provision_state) - self.assertEqual(states.ACTIVE, task.node.target_provision_state) - collect_ramdisk_logs_mock.assert_called_once_with(task.node) - - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(power.OneViewPower, 'get_power_state', - spec=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'power_off', - spec=types.FunctionType) - @mock.patch('ironic.drivers.modules.agent.AgentDeploy' - '.check_deploy_success', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True) - def test_reboot_to_instance(self, clean_pxe_mock, check_deploy_mock, - power_off_mock, get_power_state_mock, - node_power_action_mock, mock_get_ov_client): - check_deploy_mock.return_value = None - - oneview_client = mock_get_ov_client.return_value - self.driver.management.oneview_client = oneview_client - fake_server_hardware = oneview_models.ServerHardware() - fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' - oneview_client.get_server_hardware_by_uuid.return_value = ( - fake_server_hardware - ) - mock_get_ov_client.return_value = oneview_client - - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - - driver_info = self.node.driver_info - driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/' - - self.node.driver_info = driver_info - 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 - task.node.driver_internal_info['is_whole_disk_image'] = True - - task.driver.deploy.reboot_to_instance(task) - - clean_pxe_mock.assert_called_once_with(task.driver.boot, task) - check_deploy_mock.assert_called_once_with(mock.ANY, task.node) - 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(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(power.OneViewPower, 'get_power_state', - spec=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'power_off', - spec=types.FunctionType) - @mock.patch('ironic.drivers.modules.agent.AgentDeploy' - '.check_deploy_success', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True) - def test_reboot_to_instance_boot_none(self, clean_pxe_mock, - check_deploy_mock, - power_off_mock, - get_power_state_mock, - node_power_action_mock, - mock_get_ov_client): - oneview_client = mock_get_ov_client.return_value - self.driver.management.oneview_client = oneview_client - fake_server_hardware = oneview_models.ServerHardware() - fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' - oneview_client.get_server_hardware_by_uuid.return_value = ( - fake_server_hardware - ) - mock_get_ov_client.return_value = oneview_client - - check_deploy_mock.return_value = None - - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - - driver_info = self.node.driver_info - driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/' - - self.node.driver_info = driver_info - 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 - task.node.driver_internal_info['is_whole_disk_image'] = True - task.driver.boot = None - - task.driver.deploy.reboot_to_instance(task) - - self.assertFalse(clean_pxe_mock.called) - check_deploy_mock.assert_called_once_with(mock.ANY, task.node) - 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(common, 'get_oneview_client', spec_set=True, autospec=True) class OneViewIscsiDeployTestCase(db_base.DbTestCase): diff --git a/ironic/tests/unit/drivers/modules/oneview/test_management.py b/ironic/tests/unit/drivers/modules/oneview/test_management.py index 6d9f87e23e..151a9adfa2 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_management.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_management.py @@ -1,5 +1,4 @@ -# -*- encoding: utf-8 -*- -# +# Copyright 2017 Hewlett Packard Enterprise Development Company LP. # Copyright 2015 Hewlett Packard Development Company, LP # Copyright 2015 Universidade Federal de Campina Grande # @@ -35,6 +34,117 @@ oneview_exceptions = importutils.try_import('oneview_client.exceptions') oneview_models = importutils.try_import('oneview_client.models') +@mock.patch.object(common, 'get_oneview_client', spect_set=True, autospec=True) +class OneViewManagementDriverFunctionsTestCase(db_base.DbTestCase): + + def setUp(self): + super(OneViewManagementDriverFunctionsTestCase, self).setUp() + self.config(manager_url='https://1.2.3.4', group='oneview') + self.config(username='user', group='oneview') + self.config(password='password', group='oneview') + + mgr_utils.mock_the_extension_manager(driver="fake_oneview") + self.driver = driver_factory.get_driver("fake_oneview") + + self.node = obj_utils.create_test_node( + self.context, driver='fake_oneview', + properties=db_utils.get_test_oneview_properties(), + driver_info=db_utils.get_test_oneview_driver_info(), + ) + self.info = common.get_oneview_info(self.node) + + def test_set_boot_device(self, mock_get_ov_client): + oneview_client = mock_get_ov_client() + + with task_manager.acquire(self.context, self.node.uuid) as task: + driver_internal_info = task.node.driver_internal_info + next_boot_device = {'boot_device': boot_devices.PXE, + 'persistent': False} + driver_internal_info['next_boot_device'] = next_boot_device + task.node.driver_internal_info = driver_internal_info + management.set_boot_device(task) + oneview_client.set_boot_device.assert_called_once_with( + self.info, + management.BOOT_DEVICE_MAPPING_TO_OV[boot_devices.PXE], + onetime=True + ) + driver_internal_info = task.node.driver_internal_info + self.assertNotIn('next_boot_device', driver_internal_info) + + def test_set_boot_device_persistent(self, mock_get_ov_client): + oneview_client = mock_get_ov_client() + + with task_manager.acquire(self.context, self.node.uuid) as task: + driver_internal_info = task.node.driver_internal_info + next_boot_device = {'boot_device': boot_devices.PXE, + 'persistent': True} + driver_internal_info['next_boot_device'] = next_boot_device + task.node.driver_internal_info = driver_internal_info + management.set_boot_device(task) + oneview_client.set_boot_device.assert_called_once_with( + self.info, + management.BOOT_DEVICE_MAPPING_TO_OV[boot_devices.PXE], + onetime=False + ) + driver_internal_info = task.node.driver_internal_info + self.assertNotIn('next_boot_device', driver_internal_info) + + def test_set_boot_device_invalid_device(self, mock_get_ov_client): + oneview_client = mock_get_ov_client() + + with task_manager.acquire(self.context, self.node.uuid) as task: + driver_internal_info = task.node.driver_internal_info + next_boot_device = {'boot_device': 'pixie-boots', + 'persistent': True} + driver_internal_info['next_boot_device'] = next_boot_device + task.node.driver_internal_info = driver_internal_info + self.assertRaises(exception.InvalidParameterValue, + management.set_boot_device, + task) + self.assertFalse(oneview_client.set_boot_device.called) + self.assertIn('next_boot_device', driver_internal_info) + + def test_set_boot_device_fail_to_get_server_profile( + self, mock_get_ov_client): + oneview_client = mock_get_ov_client() + oneview_client.get_server_profile_from_hardware.side_effect = \ + oneview_exceptions.OneViewException() + + with task_manager.acquire(self.context, self.node.uuid) as task: + driver_internal_info = task.node.driver_internal_info + next_boot_device = {'boot_device': 'disk', + 'persistent': True} + driver_internal_info['next_boot_device'] = next_boot_device + task.node.driver_internal_info = driver_internal_info + self.assertRaises(exception.OneViewError, + management.set_boot_device, + task) + self.assertFalse(oneview_client.set_boot_device.called) + self.assertIn('next_boot_device', driver_internal_info) + + def test_set_boot_device_without_server_profile(self, mock_get_ov_client): + oneview_client = mock_get_ov_client() + oneview_client.get_server_profile_from_hardware.return_value = False + + with task_manager.acquire(self.context, self.node.uuid) as task: + driver_internal_info = task.node.driver_internal_info + next_boot_device = {'device': 'disk', + 'persistent': True} + driver_internal_info['next_boot_device'] = next_boot_device + task.node.driver_internal_info = driver_internal_info + expected_msg = ( + 'A Server Profile is not associated with node %s.' + % self.node.uuid + ) + self.assertRaisesRegex( + exception.OperationNotPermitted, + expected_msg, + management.set_boot_device, + task + ) + self.assertIn('next_boot_device', driver_internal_info) + + @mock.patch.object(common, 'get_oneview_client', spect_set=True, autospec=True) class OneViewManagementDriverTestCase(db_base.DbTestCase): @@ -126,70 +236,35 @@ class OneViewManagementDriverTestCase(db_base.DbTestCase): self.assertItemsEqual(expected, self.driver.management.get_properties()) - def test_set_boot_device(self, mock_get_ov_client): - oneview_client = mock_get_ov_client() - self.driver.management.oneview_client = oneview_client - + def test_set_boot_device_persistent_true(self, mock_get_ov_client): with task_manager.acquire(self.context, self.node.uuid) as task: - self.driver.management.set_boot_device(task, boot_devices.PXE) - oneview_client.set_boot_device.assert_called_once_with( - self.info, - management.BOOT_DEVICE_MAPPING_TO_OV[boot_devices.PXE], - onetime=True - ) - - def test_set_boot_device_persistent(self, mock_get_ov_client): - oneview_client = mock_get_ov_client() - self.driver.management.oneview_client = oneview_client + task.driver.management.set_boot_device( + task, boot_devices.PXE, True) + driver_internal_info = task.node.driver_internal_info + next_boot_device = driver_internal_info.get('next_boot_device') + self.assertIn('next_boot_device', driver_internal_info) + self.assertEqual( + next_boot_device.get('boot_device'), boot_devices.PXE) + self.assertTrue(next_boot_device.get('persistent')) + def test_set_boot_device_persistent_false(self, mock_get_ov_client): with task_manager.acquire(self.context, self.node.uuid) as task: - self.driver.management.set_boot_device(task, boot_devices.PXE, - persistent=True) - oneview_client.set_boot_device.assert_called_once_with( - self.info, - management.BOOT_DEVICE_MAPPING_TO_OV[boot_devices.PXE], - onetime=False - ) + task.driver.management.set_boot_device( + task, boot_devices.PXE, False) + driver_internal_info = task.node.driver_internal_info + next_boot_device = driver_internal_info.get('next_boot_device') + self.assertIn('next_boot_device', driver_internal_info) + self.assertEqual( + next_boot_device.get('boot_device'), boot_devices.PXE) + self.assertFalse(next_boot_device.get('persistent')) def test_set_boot_device_invalid_device(self, mock_get_ov_client): - oneview_client = mock_get_ov_client() - self.driver.management.oneview_client = oneview_client with task_manager.acquire(self.context, self.node.uuid) as task: self.assertRaises(exception.InvalidParameterValue, - self.driver.management.set_boot_device, - task, 'fake-device') - self.assertFalse(oneview_client.set_boot_device.called) - - def test_set_boot_device_fail_to_get_server_profile(self, - mock_get_ov_client): - oneview_client = mock_get_ov_client() - oneview_client.get_server_profile_from_hardware.side_effect = \ - oneview_exceptions.OneViewException() - self.driver.management.oneview_client = oneview_client - - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.OneViewError, - self.driver.management.set_boot_device, - task, 'disk') - self.assertFalse(oneview_client.set_boot_device.called) - - def test_set_boot_device_without_server_profile(self, mock_get_ov_client): - oneview_client = mock_get_ov_client() - oneview_client.get_server_profile_from_hardware.return_value = False - self.driver.management.oneview_client = oneview_client - - with task_manager.acquire(self.context, self.node.uuid) as task: - expected_msg = ( - 'A Server Profile is not associated with node %s.' - % self.node.uuid - ) - self.assertRaisesRegex( - exception.OperationNotPermitted, - expected_msg, - self.driver.management.set_boot_device, - task, - 'disk' - ) + task.driver.management.set_boot_device, + task, 'unknown-device', False) + driver_internal_info = task.node.driver_internal_info + self.assertNotIn('next_boot_device', driver_internal_info) def test_get_supported_boot_devices(self, mock_get_ov_client): with task_manager.acquire(self.context, self.node.uuid) as task: @@ -218,6 +293,25 @@ class OneViewManagementDriverTestCase(db_base.DbTestCase): self.assertEqual(expected_response, response) oneview_client.get_boot_order.assert_called_with(self.info) + def test_get_boot_device_from_next_boot_device( + self, mock_get_ov_client): + oneview_client = mock_get_ov_client() + self.driver.management.oneview_client = oneview_client + + with task_manager.acquire(self.context, self.node.uuid) as task: + driver_internal_info = task.node.driver_internal_info + next_boot_device = {'boot_device': boot_devices.DISK, + 'persistent': True} + driver_internal_info['next_boot_device'] = next_boot_device + task.node.driver_internal_info = driver_internal_info + expected_response = { + 'boot_device': boot_devices.DISK, + 'persistent': True + } + response = self.driver.management.get_boot_device(task) + self.assertEqual(expected_response, response) + self.assertFalse(oneview_client.get_boot_order.called) + def test_get_boot_device_fail(self, mock_get_ov_client): oneview_client = mock_get_ov_client() oneview_client.get_boot_order.side_effect = \ diff --git a/ironic/tests/unit/drivers/modules/oneview/test_power.py b/ironic/tests/unit/drivers/modules/oneview/test_power.py index 7e99dbe91d..5ed1aa4887 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_power.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_power.py @@ -25,6 +25,7 @@ from ironic.common import states from ironic.conductor import task_manager from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import deploy_utils +from ironic.drivers.modules.oneview import management 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 @@ -142,7 +143,9 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): task ) - def test_set_power_on(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_set_power_on( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -158,10 +161,13 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): driver_info['applied_server_profile_uri'] = sp_uri task.node.driver_info = driver_info self.driver.power.set_power_state(task, states.POWER_ON) + mock_set_boot_device.assert_called_once_with(task) self.info['applied_server_profile_uri'] = sp_uri oneview_client.power_on.assert_called_once_with(self.info) - def test_set_power_off(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_set_power_off( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -177,10 +183,13 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): driver_info['applied_server_profile_uri'] = sp_uri task.node.driver_info = driver_info self.driver.power.set_power_state(task, states.POWER_OFF) + self.assertFalse(mock_set_boot_device.called) self.info['applied_server_profile_uri'] = sp_uri oneview_client.power_off.assert_called_once_with(self.info) - def test_set_power_on_fail(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_set_power_on_fail( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -198,10 +207,13 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): self.assertRaises(exception.OneViewError, self.driver.power.set_power_state, task, states.POWER_ON) + mock_set_boot_device.assert_called_once_with(task) self.info['applied_server_profile_uri'] = sp_uri oneview_client.power_on.assert_called_once_with(self.info) - def test_set_power_off_fail(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_set_power_off_fail( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -219,10 +231,13 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): self.assertRaises(exception.OneViewError, self.driver.power.set_power_state, task, states.POWER_OFF) + self.assertFalse(mock_set_boot_device.called) self.info['applied_server_profile_uri'] = sp_uri oneview_client.power_off.assert_called_once_with(self.info) - def test_set_power_invalid_state(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_set_power_invalid_state( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -240,8 +255,11 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): self.assertRaises(exception.InvalidParameterValue, self.driver.power.set_power_state, task, 'fake state') + self.assertFalse(mock_set_boot_device.called) - def test_set_power_reboot(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_set_power_reboot( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -257,12 +275,15 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): driver_info['applied_server_profile_uri'] = sp_uri task.node.driver_info = driver_info self.driver.power.set_power_state(task, states.REBOOT) + mock_set_boot_device.assert_called_once_with(task) self.info['applied_server_profile_uri'] = sp_uri oneview_client.power_off.assert_called_once_with(self.info) oneview_client.power_off.assert_called_once_with(self.info) oneview_client.power_on.assert_called_once_with(self.info) - def test_reboot(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_reboot( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -278,11 +299,14 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): driver_info['applied_server_profile_uri'] = sp_uri task.node.driver_info = driver_info self.driver.power.reboot(task) + mock_set_boot_device.assert_called_once_with(task) self.info['applied_server_profile_uri'] = sp_uri oneview_client.power_off.assert_called_once_with(self.info) oneview_client.power_on.assert_called_once_with(self.info) - def test_reboot_fail(self, mock_get_ov_client): + @mock.patch.object(management, 'set_boot_device') + def test_reboot_fail( + self, mock_set_boot_device, mock_get_ov_client): sp_uri = '/any/server-profile' oneview_client = mock_get_ov_client() @@ -300,6 +324,7 @@ class OneViewPowerDriverTestCase(db_base.DbTestCase): task.node.driver_info = driver_info self.assertRaises(exception.OneViewError, self.driver.power.reboot, task) + self.assertFalse(mock_set_boot_device.called) self.info['applied_server_profile_uri'] = sp_uri oneview_client.power_off.assert_called_once_with(self.info) self.assertFalse(oneview_client.power_on.called) diff --git a/releasenotes/notes/oneview-agent-mixin-removal-b7277e8f20df5ef2.yaml b/releasenotes/notes/oneview-agent-mixin-removal-b7277e8f20df5ef2.yaml new file mode 100644 index 0000000000..78a6be2f53 --- /dev/null +++ b/releasenotes/notes/oneview-agent-mixin-removal-b7277e8f20df5ef2.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Oneview drivers caches the next boot device in node's internal + info when set_boot_device() is called. It is applied on the + bare metal when node is power cycled.