From b78605487dc082dcf8a181aeb70e8bacdacd083b Mon Sep 17 00:00:00 2001 From: Xavier Date: Thu, 8 Sep 2016 00:35:37 -0300 Subject: [PATCH] Add inspection feature for the OneView drivers This change is about adding the ability to the OneView drivers of doing hardware inspection. In this context, for enable hardware under management by OneView been inspected, is essencial a Server Profile applied to that hardware. A Server Profile, in OneView context, means a configuration that makes booting and connectivity operations settings possible. Considering in-band inspection as the inspection process that involves booting a ramdisk on the target node and fetching information directly from it, in this patch we extend the ironic.drivers.modules.inspector.Inspector class to inherit the ironic implementation for in-band inspection behaviours and to address our needs, we override the 'inspect_hardware' method to apply the needed Server Profile and we also override the '_periodic_check_result' periodic task to remove the Server Profile after the inspection process is over. Change-Id: I77ebf4b9175c2c1df1baa6d58714ff54c97c17ff Closes-Bug: 1621530 --- driver-requirements.txt | 2 +- ironic/drivers/fake.py | 1 + ironic/drivers/modules/oneview/common.py | 19 ++-- ironic/drivers/modules/oneview/deploy.py | 16 +++ .../drivers/modules/oneview/deploy_utils.py | 102 ++++++++++++++---- ironic/drivers/modules/oneview/inspect.py | 98 +++++++++++++++++ ironic/drivers/modules/oneview/management.py | 5 + ironic/drivers/oneview.py | 5 + .../drivers/modules/oneview/test_common.py | 19 ++-- .../modules/oneview/test_deploy_utils.py | 53 +++++++-- .../drivers/modules/oneview/test_inspect.py | 95 ++++++++++++++++ .../modules/oneview/test_management.py | 28 +++++ .../drivers/modules/oneview/test_vendor.py | 70 ++++++++++-- ...inspection-interface-c2d6902bbeca0501.yaml | 5 + 14 files changed, 458 insertions(+), 60 deletions(-) create mode 100644 ironic/drivers/modules/oneview/inspect.py create mode 100644 ironic/tests/unit/drivers/modules/oneview/test_inspect.py create mode 100644 releasenotes/notes/oneview-inspection-interface-c2d6902bbeca0501.yaml diff --git a/driver-requirements.txt b/driver-requirements.txt index 711c3379e2..b5a418fdc7 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -8,7 +8,7 @@ proliantutils>=2.1.11 pyghmi>=0.8.0 pysnmp python-ironic-inspector-client>=1.5.0 -python-oneviewclient<3.0.0,>=2.0.2 +python-oneviewclient<3.0.0,>=2.5.1 python-scciclient>=0.4.0 python-seamicroclient>=0.4.0 UcsSdk==0.8.2.2 diff --git a/ironic/drivers/fake.py b/ironic/drivers/fake.py index 996c152a57..124e383545 100644 --- a/ironic/drivers/fake.py +++ b/ironic/drivers/fake.py @@ -354,3 +354,4 @@ class FakeOneViewDriver(base.BaseDriver): self.management = oneview_management.OneViewManagement() self.boot = fake.FakeBoot() self.deploy = fake.FakeDeploy() + self.inspect = fake.FakeInspect() diff --git a/ironic/drivers/modules/oneview/common.py b/ironic/drivers/modules/oneview/common.py index 1c6754696f..1090c4d1b0 100644 --- a/ironic/drivers/modules/oneview/common.py +++ b/ironic/drivers/modules/oneview/common.py @@ -155,13 +155,12 @@ def validate_oneview_resources_compatibility(task): including server_hardware_uri, server_hardware_type_uri, server_profile_template_uri, enclosure_group_uri and node ports. Also verifies if a Server Profile is applied to the Server Hardware the node - represents. If any validation fails, python-oneviewclient will raise - an appropriate OneViewException. + represents when in pre-allocation model. If any validation fails, + python-oneviewclient will raise an appropriate OneViewException. :param: task: a TaskManager instance containing the node to act on. """ - node = task.node node_ports = task.ports oneview_info = get_oneview_info(task.node) @@ -169,13 +168,15 @@ def validate_oneview_resources_compatibility(task): try: oneview_client = get_oneview_client() - oneview_client.validate_node_server_hardware( - oneview_info, node.properties.get('memory_mb'), - node.properties.get('cpus') - ) + oneview_client.validate_node_server_profile_template(oneview_info) oneview_client.validate_node_server_hardware_type(oneview_info) oneview_client.validate_node_enclosure_group(oneview_info) - oneview_client.validate_node_server_profile_template(oneview_info) + + oneview_client.validate_node_server_hardware( + oneview_info, + task.node.properties.get('memory_mb'), + task.node.properties.get('cpus') + ) # NOTE(thiagop): Support to pre-allocation will be dropped in the Pike # release @@ -183,12 +184,12 @@ def validate_oneview_resources_compatibility(task): oneview_client.is_node_port_mac_compatible_with_server_hardware( oneview_info, node_ports ) - oneview_client.validate_node_server_profile_template(oneview_info) else: oneview_client.check_server_profile_is_applied(oneview_info) oneview_client.is_node_port_mac_compatible_with_server_profile( oneview_info, node_ports ) + except oneview_exceptions.OneViewException as oneview_exc: msg = (_("Error validating node resources with OneView: %s") % oneview_exc) diff --git a/ironic/drivers/modules/oneview/deploy.py b/ironic/drivers/modules/oneview/deploy.py index 19262ca4c9..72c23f2e19 100644 --- a/ironic/drivers/modules/oneview/deploy.py +++ b/ironic/drivers/modules/oneview/deploy.py @@ -211,6 +211,14 @@ class OneViewIscsiDeploy(iscsi_deploy.ISCSIDeploy, OneViewPeriodicTasks): def get_properties(self): deploy_utils.get_properties() + def validate(self, task): + common.verify_node_info(task.node) + try: + common.validate_oneview_resources_compatibility(task) + except exception.OneViewError as oneview_exc: + raise exception.InvalidParameterValue(oneview_exc) + super(OneViewIscsiDeploy, self).validate(task) + def prepare(self, task): if common.is_dynamic_allocation_enabled(task.node): deploy_utils.prepare(task) @@ -241,6 +249,14 @@ class OneViewAgentDeploy(agent.AgentDeploy, OneViewPeriodicTasks): def get_properties(self): deploy_utils.get_properties() + def validate(self, task): + common.verify_node_info(task.node) + try: + common.validate_oneview_resources_compatibility(task) + except exception.OneViewError as oneview_exc: + raise exception.InvalidParameterValue(oneview_exc) + super(OneViewAgentDeploy, self).validate(task) + def prepare(self, task): if common.is_dynamic_allocation_enabled(task.node): deploy_utils.prepare(task) diff --git a/ironic/drivers/modules/oneview/deploy_utils.py b/ironic/drivers/modules/oneview/deploy_utils.py index b96f9885be..d10db36aff 100644 --- a/ironic/drivers/modules/oneview/deploy_utils.py +++ b/ironic/drivers/modules/oneview/deploy_utils.py @@ -14,11 +14,13 @@ # License for the specific language governing permissions and limitations # under the License. +import operator + from oslo_log import log as logging from oslo_utils import importutils from ironic.common import exception -from ironic.common.i18n import _, _LE, _LI, _LW +from ironic.common.i18n import _, _LE, _LI from ironic.common import states from ironic.drivers.modules.oneview import common @@ -54,7 +56,7 @@ def prepare(task): {"instance_name": instance_display_name, "instance_uuid": instance_uuid} ) - _allocate_server_hardware_to_ironic(task.node, server_profile_name) + allocate_server_hardware_to_ironic(task.node, server_profile_name) except exception.OneViewError as e: raise exception.InstanceDeployFailure(node=task.node.uuid, reason=e) @@ -74,7 +76,7 @@ def tear_down(task): """ try: - _deallocate_server_hardware_from_ironic(task.node) + deallocate_server_hardware_from_ironic(task.node) except exception.OneViewError as e: raise exception.InstanceDeployFailure(node=task.node.uuid, reason=e) @@ -94,7 +96,7 @@ def prepare_cleaning(task): """ try: server_profile_name = "Ironic Cleaning [%s]" % task.node.uuid - _allocate_server_hardware_to_ironic(task.node, server_profile_name) + allocate_server_hardware_to_ironic(task.node, server_profile_name) except exception.OneViewError as e: oneview_error = common.SERVER_HARDWARE_ALLOCATION_ERROR driver_internal_info = task.node.driver_internal_info @@ -119,11 +121,29 @@ def tear_down_cleaning(task): """ try: - _deallocate_server_hardware_from_ironic(task.node) + deallocate_server_hardware_from_ironic(task.node) except exception.OneViewError as e: raise exception.NodeCleaningFailure(node=task.node.uuid, reason=e) +def _is_node_in_use(server_hardware, applied_sp_uri, by_oneview=False): + """Check if node is in use by ironic or by OneView. + + :param by_oneview: Boolean value. True when want to verify if node is in + use by OneView. False to verify if node is in use by + ironic. + :param node: an ironic node object + :returns: Boolean value. True if by_oneview param is also True and node is + in use by OneView, False otherwise. True if by_oneview param is + False and node is in use by ironic, False otherwise. + + """ + + operation = operator.ne if by_oneview else operator.eq + return (server_hardware.server_profile_uri not in (None, '') and + operation(applied_sp_uri, server_hardware.server_profile_uri)) + + def is_node_in_use_by_oneview(node): """Check if node is in use by OneView user. @@ -131,6 +151,54 @@ def is_node_in_use_by_oneview(node): :returns: Boolean value. True if node is in use by OneView, False otherwise. :raises OneViewError: if not possible to get OneView's informations + for the given node, if not possible to retrieve Server Hardware + from OneView. + + """ + + positive = _("Node '%s' is in use by OneView.") % node.uuid + negative = _("Node '%s' is not in use by OneView.") % node.uuid + + def predicate(server_hardware, applied_sp_uri): + # Check if Profile exists in Oneview and it is different of the one + # applied by ironic + return _is_node_in_use(server_hardware, applied_sp_uri, + by_oneview=True) + + return _check_applied_server_profile(node, predicate, positive, negative) + + +def is_node_in_use_by_ironic(node): + """Check if node is in use by ironic in OneView. + + :param node: an ironic node object + :returns: Boolean value. True if node is in use by ironic, + False otherwise. + :raises OneViewError: if not possible to get OneView's information + for the given node, if not possible to retrieve Server Hardware + from OneView. + + """ + + positive = _("Node '%s' is in use by Ironic.") % node.uuid + negative = _("Node '%s' is not in use by Ironic.") % node.uuid + + def predicate(server_hardware, applied_sp_uri): + # Check if Profile exists in Oneview and it is equals of the one + # applied by ironic + return _is_node_in_use(server_hardware, applied_sp_uri, + by_oneview=False) + + return _check_applied_server_profile(node, predicate, positive, negative) + + +def _check_applied_server_profile(node, predicate, positive, negative): + """Check if node is in use by ironic in OneView. + + :param node: an ironic node object + :returns: Boolean value. True if node is in use by ironic, + False otherwise. + :raises OneViewError: if not possible to get OneView's information for the given node, if not possible to retrieve Server Hardware from OneView. @@ -157,24 +225,14 @@ def is_node_in_use_by_oneview(node): node.driver_info.get('applied_server_profile_uri') ) - # Check if Profile exists in Oneview and it is different of the one - # applied by ironic - if (server_hardware.server_profile_uri not in (None, '') and - applied_sp_uri != server_hardware.server_profile_uri): - - LOG.warning(_LW("Node %s is already in use by OneView."), - node.uuid) - - return True + result = predicate(server_hardware, applied_sp_uri) + if result: + LOG.debug(positive) else: - LOG.debug(_( - "Hardware %(hardware_uri)s is free for use by " - "ironic on node %(node_uuid)s."), - {"hardware_uri": server_hardware.uri, - "node_uuid": node.uuid}) + LOG.debug(negative) - return False + return result def _add_applied_server_profile_uri_field(node, applied_profile): @@ -201,7 +259,7 @@ def _del_applied_server_profile_uri_field(node): node.save() -def _allocate_server_hardware_to_ironic(node, server_profile_name): +def allocate_server_hardware_to_ironic(node, server_profile_name): """Allocate Server Hardware to ironic. :param node: an ironic node object @@ -276,7 +334,7 @@ def _allocate_server_hardware_to_ironic(node, server_profile_name): raise exception.OneViewError(error=msg) -def _deallocate_server_hardware_from_ironic(node): +def deallocate_server_hardware_from_ironic(node): """Deallocate Server Hardware from ironic. :param node: an ironic node object diff --git a/ironic/drivers/modules/oneview/inspect.py b/ironic/drivers/modules/oneview/inspect.py new file mode 100644 index 0000000000..dde2e65834 --- /dev/null +++ b/ironic/drivers/modules/oneview/inspect.py @@ -0,0 +1,98 @@ +# Copyright 2016 Hewlett Packard Enterprise Development LP. +# Copyright 2016 Universidade Federal de Campina Grande +# 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. + +from futurist import periodics +from oslo_log import log as logging +from oslo_utils import importutils + +from ironic.common import exception +from ironic.common import states +from ironic.conductor import task_manager +from ironic.drivers.modules import inspector +from ironic.drivers.modules.oneview import common +from ironic.drivers.modules.oneview import deploy_utils + +from ironic.conf import CONF + +LOG = logging.getLogger(__name__) + +oneview_exception = importutils.try_import('oneview_client.exceptions') +oneview_utils = importutils.try_import('oneview_client.utils') + + +class OneViewInspect(inspector.Inspector): + """Interface for in band inspection.""" + + def get_properties(self): + return common.COMMON_PROPERTIES + + def validate(self, task): + """Checks required info on 'driver_info' and validates node with OneView + + Validates whether the 'driver_info' property of the supplied + task's node contains the required info such as server_hardware_uri, + server_hardware_type, server_profile_template_uri and + enclosure_group_uri. Also, checks if the server profile of the node is + applied, if NICs are valid for the server profile of the node. + + :param task: a task from TaskManager. + :raises: InvalidParameterValue if parameters set are inconsistent with + resources in OneView + """ + + common.verify_node_info(task.node) + + try: + common.validate_oneview_resources_compatibility(task) + except exception.OneViewError as oneview_exc: + raise exception.InvalidParameterValue(oneview_exc) + + def inspect_hardware(self, task): + profile_name = 'Ironic Inspecting [%s]' % task.node.uuid + deploy_utils.allocate_server_hardware_to_ironic( + task.node, profile_name + ) + return super(OneViewInspect, self).inspect_hardware(task) + + @periodics.periodic(spacing=CONF.inspector.status_check_period, + enabled=CONF.inspector.enabled) + def _periodic_check_result(self, manager, context): + filters = {'provision_state': states.INSPECTING} + node_iter = manager.iter_nodes(filters=filters) + + for node_uuid, driver in node_iter: + if driver in [common.AGENT_PXE_ONEVIEW, + common.ISCSI_PXE_ONEVIEW]: + try: + lock_purpose = 'checking hardware inspection status' + with task_manager.acquire(context, node_uuid, + shared=True, + purpose=lock_purpose) as task: + self._check_status(task) + except (exception.NodeLocked, exception.NodeNotFound): + continue + + def _check_status(self, task): + state_before = task.node.provision_state + result = inspector._check_status(task) + state_after = task.node.provision_state + + # inspection finished + if (state_before == states.INSPECTING and + state_after in [states.MANAGEABLE, states.INSPECTFAIL]): + deploy_utils.deallocate_server_hardware_from_ironic(task.node) + + return result diff --git a/ironic/drivers/modules/oneview/management.py b/ironic/drivers/modules/oneview/management.py index 2f48323403..b0bf2941e0 100644 --- a/ironic/drivers/modules/oneview/management.py +++ b/ironic/drivers/modules/oneview/management.py @@ -23,6 +23,7 @@ from ironic.common.i18n import _ 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 LOG = logging.getLogger(__name__) @@ -65,6 +66,10 @@ class OneViewManagement(base.ManagementInterface): try: common.validate_oneview_resources_compatibility(task) + + if not deploy_utils.is_node_in_use_by_ironic(task.node): + raise exception.InvalidParameterValue( + _("Node %s is not in use by ironic.") % task.node.uuid) except exception.OneViewError as oneview_exc: raise exception.InvalidParameterValue(oneview_exc) diff --git a/ironic/drivers/oneview.py b/ironic/drivers/oneview.py index 4d5992d2b1..941e62d9d4 100644 --- a/ironic/drivers/oneview.py +++ b/ironic/drivers/oneview.py @@ -24,6 +24,7 @@ from ironic.drivers import base 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 inspect from ironic.drivers.modules.oneview import management from ironic.drivers.modules.oneview import power from ironic.drivers.modules.oneview import vendor @@ -55,6 +56,8 @@ class AgentPXEOneViewDriver(base.BaseDriver): self.boot = pxe.PXEBoot() self.deploy = deploy.OneViewAgentDeploy() self.vendor = vendor.AgentVendorInterface() + self.inspect = inspect.OneViewInspect.create_if_enabled( + 'AgentPXEOneViewDriver') class ISCSIPXEOneViewDriver(base.BaseDriver): @@ -82,3 +85,5 @@ class ISCSIPXEOneViewDriver(base.BaseDriver): self.boot = pxe.PXEBoot() self.deploy = deploy.OneViewIscsiDeploy() self.vendor = iscsi_deploy.VendorPassthru() + self.inspect = inspect.OneViewInspect.create_if_enabled( + 'ISCSIPXEOneViewDriver') diff --git a/ironic/tests/unit/drivers/modules/oneview/test_common.py b/ironic/tests/unit/drivers/modules/oneview/test_common.py index 6eab2690af..7a3daf3f17 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_common.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_common.py @@ -237,8 +237,6 @@ class OneViewCommonTestCase(db_base.DbTestCase): oneview_client = mock_get_ov_client() with task_manager.acquire(self.context, self.node.uuid) as task: common.validate_oneview_resources_compatibility(task) - self.assertTrue( - oneview_client.validate_node_server_hardware.called) self.assertTrue( oneview_client.validate_node_server_hardware_type.called) self.assertTrue( @@ -264,15 +262,14 @@ class OneViewCommonTestCase(db_base.DbTestCase): """Validate compatibility of resources for Dynamic Allocation model. 1) Set 'dynamic_allocation' flag as True on node's driver_info - 2) Check validate_node_server_hardware method is called - 3) Check validate_node_server_hardware_type method is called - 4) Check validate_node_enclosure_group method is called - 5) Check validate_node_server_profile_template method is called - 6) Check is_node_port_mac_compatible_with_server_hardware method + 2) Check validate_node_server_hardware_type method is called + 3) Check validate_node_enclosure_group method is called + 4) Check validate_node_server_profile_template method is called + 5) Check is_node_port_mac_compatible_with_server_hardware method is called - 7) Check validate_node_server_profile_template method is called - 8) Check check_server_profile_is_applied method is not called - 9) Check is_node_port_mac_compatible_with_server_profile method is + 6) Check validate_node_server_profile_template method is called + 7) Check check_server_profile_is_applied method is not called + 8) Check is_node_port_mac_compatible_with_server_profile method is not called """ @@ -283,8 +280,6 @@ class OneViewCommonTestCase(db_base.DbTestCase): task.node.driver_info = driver_info common.validate_oneview_resources_compatibility(task) - self.assertTrue( - oneview_client.validate_node_server_hardware.called) self.assertTrue( oneview_client.validate_node_server_hardware_type.called) self.assertTrue( diff --git a/ironic/tests/unit/drivers/modules/oneview/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/oneview/test_deploy_utils.py index 695ed7fb15..a291765b71 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_deploy_utils.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_deploy_utils.py @@ -242,6 +242,43 @@ class OneViewDeployUtilsTestCase(db_base.DbTestCase): deploy_utils.is_node_in_use_by_oneview(task.node) ) + # Tests for is_node_in_use_by_oneview + def test_is_node_in_use_by_ironic(self, mock_get_ov_client): + """Node has a Server Profile applied by ironic. + + """ + fake_sh = oneview_models.ServerHardware() + fake_sh.server_profile_uri = "same/applied_sp_uri/" + + ov_client = mock_get_ov_client.return_value + ov_client.get_server_hardware_by_uuid.return_value = fake_sh + + with task_manager.acquire(self.context, self.node.uuid) as task: + driver_info = task.node.driver_info + driver_info['dynamic_allocation'] = True + driver_info['applied_server_profile_uri'] = 'same/applied_sp_uri/' + task.node.driver_info = driver_info + self.assertTrue( + deploy_utils.is_node_in_use_by_ironic(task.node) + ) + + def test_is_node_in_use_by_ironic_no_server_profile( + self, mock_get_ov_client + ): + """Node has no Server Profile. + + """ + fake_sh = oneview_models.ServerHardware() + fake_sh.server_profile_uri = None + + ov_client = mock_get_ov_client.return_value + ov_client.get_server_hardware_by_uuid.return_value = fake_sh + + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertFalse( + deploy_utils.is_node_in_use_by_ironic(task.node) + ) + # Tests for _add_applied_server_profile_uri_field def test__add_applied_server_profile_uri_field(self, mock_get_ov_client): """Checks if applied_server_profile_uri was added to driver_info. @@ -276,9 +313,9 @@ class OneViewDeployUtilsTestCase(db_base.DbTestCase): self.assertNotIn('applied_server_profile_uri', task.node.driver_info) - # Tests for _allocate_server_hardware_to_ironic + # Tests for allocate_server_hardware_to_ironic @mock.patch.object(objects.Node, 'save') - def test__allocate_server_hardware_to_ironic( + def test_allocate_server_hardware_to_ironic( self, mock_node_save, mock_get_ov_client ): """Checks if a Server Profile was created and its uri is in driver_info. @@ -291,7 +328,7 @@ class OneViewDeployUtilsTestCase(db_base.DbTestCase): mock_get_ov_client.return_value = ov_client with task_manager.acquire(self.context, self.node.uuid) as task: - deploy_utils._allocate_server_hardware_to_ironic( + deploy_utils.allocate_server_hardware_to_ironic( task.node, 'serverProfileName' ) self.assertTrue(ov_client.clone_template_and_apply.called) @@ -300,7 +337,7 @@ class OneViewDeployUtilsTestCase(db_base.DbTestCase): @mock.patch.object(objects.Node, 'save') @mock.patch.object(deploy_utils, '_del_applied_server_profile_uri_field') - def test__allocate_server_hardware_to_ironic_node_has_server_profile( + def test_allocate_server_hardware_to_ironic_node_has_server_profile( self, mock_delete_applied_sp, mock_node_save, mock_get_ov_client ): """Tests server profile allocation when applied_server_profile_uri exists. @@ -321,14 +358,14 @@ class OneViewDeployUtilsTestCase(db_base.DbTestCase): driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/' task.node.driver_info = driver_info - deploy_utils._allocate_server_hardware_to_ironic( + deploy_utils.allocate_server_hardware_to_ironic( task.node, 'serverProfileName' ) self.assertTrue(mock_delete_applied_sp.called) - # Tests for _deallocate_server_hardware_from_ironic + # Tests for deallocate_server_hardware_from_ironic @mock.patch.object(objects.Node, 'save') - def test__deallocate_server_hardware_from_ironic( + def test_deallocate_server_hardware_from_ironic( self, mock_node_save, mock_get_ov_client ): ov_client = mock_get_ov_client.return_value @@ -342,7 +379,7 @@ class OneViewDeployUtilsTestCase(db_base.DbTestCase): driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/' task.node.driver_info = driver_info - deploy_utils._deallocate_server_hardware_from_ironic(task.node) + deploy_utils.deallocate_server_hardware_from_ironic(task.node) self.assertTrue(ov_client.delete_server_profile.called) self.assertTrue( 'applied_server_profile_uri' not in task.node.driver_info diff --git a/ironic/tests/unit/drivers/modules/oneview/test_inspect.py b/ironic/tests/unit/drivers/modules/oneview/test_inspect.py new file mode 100644 index 0000000000..4ffb5de13f --- /dev/null +++ b/ironic/tests/unit/drivers/modules/oneview/test_inspect.py @@ -0,0 +1,95 @@ +# Copyright 2016 Hewlett Packard Enterprise Development LP. +# Copyright 2016 Universidade Federal de Campina Grande +# 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. + +import mock + +from ironic.conductor import task_manager +from ironic.drivers.modules.oneview import common as oneview_common +from ironic.drivers.modules.oneview import deploy_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 +from ironic.tests.unit.objects import utils as obj_utils + + +class AgentPXEOneViewInspectTestCase(db_base.DbTestCase): + + def setUp(self): + super(AgentPXEOneViewInspectTestCase, self).setUp() + self.config(enabled=True, group='inspector') + mgr_utils.mock_the_extension_manager(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(), + ) + + def test_get_properties(self): + expected = oneview_common.COMMON_PROPERTIES + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertEqual(expected, task.driver.inspect.get_properties()) + + @mock.patch.object(oneview_common, 'verify_node_info', spec_set=True, + autospec=True) + def test_validate(self, mock_verify_node_info): + self.config(enabled=False, group='inspector') + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.inspect.validate(task) + mock_verify_node_info.assert_called_once_with(task.node) + + @mock.patch.object(deploy_utils, 'allocate_server_hardware_to_ironic') + def test_inspect_hardware(self, mock_allocate_server_hardware_to_ironic): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.inspect.inspect_hardware(task) + self.assertTrue(mock_allocate_server_hardware_to_ironic.called) + + +class ISCSIPXEOneViewInspectTestCase(db_base.DbTestCase): + + def setUp(self): + super(ISCSIPXEOneViewInspectTestCase, self).setUp() + self.config(enabled=True, group='inspector') + mgr_utils.mock_the_extension_manager(driver="iscsi_pxe_oneview") + self.node = obj_utils.create_test_node( + self.context, driver='iscsi_pxe_oneview', + properties=db_utils.get_test_oneview_properties(), + driver_info=db_utils.get_test_oneview_driver_info(), + ) + + def test_get_properties(self): + expected = oneview_common.COMMON_PROPERTIES + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertEqual(expected, task.driver.inspect.get_properties()) + + @mock.patch.object(oneview_common, 'verify_node_info', spec_set=True, + autospec=True) + def test_validate(self, mock_verify_node_info): + self.config(enabled=False, group='inspector') + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.inspect.validate(task) + mock_verify_node_info.assert_called_once_with(task.node) + + @mock.patch.object(deploy_utils, 'allocate_server_hardware_to_ironic') + def test_inspect_hardware(self, mock_allocate_server_hardware_to_ironic): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.inspect.inspect_hardware(task) + self.assertTrue(mock_allocate_server_hardware_to_ironic.called) diff --git a/ironic/tests/unit/drivers/modules/oneview/test_management.py b/ironic/tests/unit/drivers/modules/oneview/test_management.py index 0199c6fdb8..b5778e5076 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_management.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_management.py @@ -32,6 +32,7 @@ from ironic.tests.unit.objects import utils as obj_utils 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) @@ -56,10 +57,37 @@ class OneViewManagementDriverTestCase(db_base.DbTestCase): @mock.patch.object(common, 'validate_oneview_resources_compatibility', spect_set=True, autospec=True) def test_validate(self, mock_validate, mock_get_ov_client): + client = mock_get_ov_client.return_value + fake_server_hardware = oneview_models.ServerHardware() + fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' + client.get_server_hardware_by_uuid.return_value = fake_server_hardware + mock_get_ov_client.return_value = client + 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) as task: task.driver.management.validate(task) self.assertTrue(mock_validate.called) + @mock.patch.object(common, 'validate_oneview_resources_compatibility', + spect_set=True, autospec=True) + def test_validate_for_node_not_in_use_by_ironic(self, + mock_validate, + mock_get_ov_client): + client = mock_get_ov_client.return_value + fake_server_hardware = oneview_models.ServerHardware() + fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' + client.get_server_hardware_by_uuid.return_value = fake_server_hardware + mock_get_ov_client.return_value = client + driver_info = self.node.driver_info + driver_info['applied_server_profile_uri'] = 'other/applied_sp_uri/' + self.node.driver_info = driver_info + self.node.save() + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaises(exception.InvalidParameterValue, + task.driver.management.validate, task) + def test_validate_fail(self, mock_get_ov_client): node = obj_utils.create_test_node(self.context, uuid=uuidutils.generate_uuid(), diff --git a/ironic/tests/unit/drivers/modules/oneview/test_vendor.py b/ironic/tests/unit/drivers/modules/oneview/test_vendor.py index ace029e21c..aae708fa5a 100644 --- a/ironic/tests/unit/drivers/modules/oneview/test_vendor.py +++ b/ironic/tests/unit/drivers/modules/oneview/test_vendor.py @@ -15,16 +15,18 @@ # License for the specific language governing permissions and limitations # under the License. +import mock import time import types -import mock +from oslo_utils import importutils 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.drivers.modules import agent_client +from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import power from ironic.drivers.modules.oneview import vendor from ironic.drivers.modules import pxe @@ -34,10 +36,13 @@ 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 +oneview_models = importutils.try_import('oneview_client.models') + GET_POWER_STATE_RETRIES = 5 +@mock.patch.object(common, 'get_oneview_client', spec_set=True, autospec=True) class TestBaseAgentVendor(db_base.DbTestCase): def setUp(self): @@ -63,7 +68,8 @@ class TestBaseAgentVendor(db_base.DbTestCase): @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): + node_power_action_mock, + mock_get_ov_client): self.node.provision_state = states.DEPLOYING self.node.target_provision_state = states.ACTIVE self.node.save() @@ -89,9 +95,18 @@ class TestBaseAgentVendor(db_base.DbTestCase): 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): + node_power_action_mock, mock_get_ov_client): + client = mock_get_ov_client.return_value + fake_server_hardware = oneview_models.ServerHardware() + fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' + client.get_server_hardware_by_uuid.return_value = fake_server_hardware + mock_get_ov_client.return_value = 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: @@ -111,10 +126,20 @@ class TestBaseAgentVendor(db_base.DbTestCase): @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): + self, power_off_mock, node_power_action_mock, + mock_get_ov_client): + client = mock_get_ov_client.return_value + fake_server_hardware = oneview_models.ServerHardware() + fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' + client.get_server_hardware_by_uuid.return_value = fake_server_hardware + mock_get_ov_client.return_value = 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: @@ -135,9 +160,18 @@ class TestBaseAgentVendor(db_base.DbTestCase): 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): + node_power_action_mock, mock_get_ov_client): + client = mock_get_ov_client.return_value + fake_server_hardware = oneview_models.ServerHardware() + fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' + client.get_server_hardware_by_uuid.return_value = fake_server_hardware + mock_get_ov_client.return_value = 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: @@ -162,7 +196,8 @@ class TestBaseAgentVendor(db_base.DbTestCase): 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): + 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() @@ -193,11 +228,20 @@ class TestBaseAgentVendor(db_base.DbTestCase): @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): + node_power_action_mock, mock_get_ov_client): check_deploy_mock.return_value = None + client = mock_get_ov_client.return_value + fake_server_hardware = oneview_models.ServerHardware() + fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' + client.get_server_hardware_by_uuid.return_value = fake_server_hardware + mock_get_ov_client.return_value = 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: @@ -227,11 +271,21 @@ class TestBaseAgentVendor(db_base.DbTestCase): check_deploy_mock, power_off_mock, get_power_state_mock, - node_power_action_mock): + node_power_action_mock, + mock_get_ov_client): + client = mock_get_ov_client.return_value + fake_server_hardware = oneview_models.ServerHardware() + fake_server_hardware.server_profile_uri = 'any/applied_sp_uri/' + client.get_server_hardware_by_uuid.return_value = fake_server_hardware + mock_get_ov_client.return_value = 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: diff --git a/releasenotes/notes/oneview-inspection-interface-c2d6902bbeca0501.yaml b/releasenotes/notes/oneview-inspection-interface-c2d6902bbeca0501.yaml new file mode 100644 index 0000000000..092633b6c6 --- /dev/null +++ b/releasenotes/notes/oneview-inspection-interface-c2d6902bbeca0501.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - Minimum required version of python-oneviewclient bumped to 2.5.1 +features: + - Adds in-band inspection interface usable by OneView drivers.