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.