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
This commit is contained in:
Xavier 2016-09-08 00:35:37 -03:00
parent 7ea6b6ae11
commit b78605487d
14 changed files with 458 additions and 60 deletions

View File

@ -8,7 +8,7 @@ proliantutils>=2.1.11
pyghmi>=0.8.0 pyghmi>=0.8.0
pysnmp pysnmp
python-ironic-inspector-client>=1.5.0 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-scciclient>=0.4.0
python-seamicroclient>=0.4.0 python-seamicroclient>=0.4.0
UcsSdk==0.8.2.2 UcsSdk==0.8.2.2

View File

@ -354,3 +354,4 @@ class FakeOneViewDriver(base.BaseDriver):
self.management = oneview_management.OneViewManagement() self.management = oneview_management.OneViewManagement()
self.boot = fake.FakeBoot() self.boot = fake.FakeBoot()
self.deploy = fake.FakeDeploy() self.deploy = fake.FakeDeploy()
self.inspect = fake.FakeInspect()

View File

@ -155,13 +155,12 @@ def validate_oneview_resources_compatibility(task):
including server_hardware_uri, server_hardware_type_uri, including server_hardware_uri, server_hardware_type_uri,
server_profile_template_uri, enclosure_group_uri and node ports. Also server_profile_template_uri, enclosure_group_uri and node ports. Also
verifies if a Server Profile is applied to the Server Hardware the node verifies if a Server Profile is applied to the Server Hardware the node
represents. If any validation fails, python-oneviewclient will raise represents when in pre-allocation model. If any validation fails,
an appropriate OneViewException. python-oneviewclient will raise an appropriate OneViewException.
:param: task: a TaskManager instance containing the node to act on. :param: task: a TaskManager instance containing the node to act on.
""" """
node = task.node
node_ports = task.ports node_ports = task.ports
oneview_info = get_oneview_info(task.node) oneview_info = get_oneview_info(task.node)
@ -169,13 +168,15 @@ def validate_oneview_resources_compatibility(task):
try: try:
oneview_client = get_oneview_client() oneview_client = get_oneview_client()
oneview_client.validate_node_server_hardware( oneview_client.validate_node_server_profile_template(oneview_info)
oneview_info, node.properties.get('memory_mb'),
node.properties.get('cpus')
)
oneview_client.validate_node_server_hardware_type(oneview_info) oneview_client.validate_node_server_hardware_type(oneview_info)
oneview_client.validate_node_enclosure_group(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 # NOTE(thiagop): Support to pre-allocation will be dropped in the Pike
# release # release
@ -183,12 +184,12 @@ def validate_oneview_resources_compatibility(task):
oneview_client.is_node_port_mac_compatible_with_server_hardware( oneview_client.is_node_port_mac_compatible_with_server_hardware(
oneview_info, node_ports oneview_info, node_ports
) )
oneview_client.validate_node_server_profile_template(oneview_info)
else: else:
oneview_client.check_server_profile_is_applied(oneview_info) oneview_client.check_server_profile_is_applied(oneview_info)
oneview_client.is_node_port_mac_compatible_with_server_profile( oneview_client.is_node_port_mac_compatible_with_server_profile(
oneview_info, node_ports oneview_info, node_ports
) )
except oneview_exceptions.OneViewException as oneview_exc: except oneview_exceptions.OneViewException as oneview_exc:
msg = (_("Error validating node resources with OneView: %s") % msg = (_("Error validating node resources with OneView: %s") %
oneview_exc) oneview_exc)

View File

@ -211,6 +211,14 @@ class OneViewIscsiDeploy(iscsi_deploy.ISCSIDeploy, OneViewPeriodicTasks):
def get_properties(self): def get_properties(self):
deploy_utils.get_properties() 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): def prepare(self, task):
if common.is_dynamic_allocation_enabled(task.node): if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.prepare(task) deploy_utils.prepare(task)
@ -241,6 +249,14 @@ class OneViewAgentDeploy(agent.AgentDeploy, OneViewPeriodicTasks):
def get_properties(self): def get_properties(self):
deploy_utils.get_properties() 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): def prepare(self, task):
if common.is_dynamic_allocation_enabled(task.node): if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.prepare(task) deploy_utils.prepare(task)

View File

@ -14,11 +14,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import operator
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import importutils from oslo_utils import importutils
from ironic.common import exception 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.common import states
from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import common
@ -54,7 +56,7 @@ def prepare(task):
{"instance_name": instance_display_name, {"instance_name": instance_display_name,
"instance_uuid": instance_uuid} "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: except exception.OneViewError as e:
raise exception.InstanceDeployFailure(node=task.node.uuid, raise exception.InstanceDeployFailure(node=task.node.uuid,
reason=e) reason=e)
@ -74,7 +76,7 @@ def tear_down(task):
""" """
try: try:
_deallocate_server_hardware_from_ironic(task.node) deallocate_server_hardware_from_ironic(task.node)
except exception.OneViewError as e: except exception.OneViewError as e:
raise exception.InstanceDeployFailure(node=task.node.uuid, reason=e) raise exception.InstanceDeployFailure(node=task.node.uuid, reason=e)
@ -94,7 +96,7 @@ def prepare_cleaning(task):
""" """
try: try:
server_profile_name = "Ironic Cleaning [%s]" % task.node.uuid 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: except exception.OneViewError as e:
oneview_error = common.SERVER_HARDWARE_ALLOCATION_ERROR oneview_error = common.SERVER_HARDWARE_ALLOCATION_ERROR
driver_internal_info = task.node.driver_internal_info driver_internal_info = task.node.driver_internal_info
@ -119,11 +121,29 @@ def tear_down_cleaning(task):
""" """
try: try:
_deallocate_server_hardware_from_ironic(task.node) deallocate_server_hardware_from_ironic(task.node)
except exception.OneViewError as e: except exception.OneViewError as e:
raise exception.NodeCleaningFailure(node=task.node.uuid, reason=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): def is_node_in_use_by_oneview(node):
"""Check if node is in use by OneView user. """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, :returns: Boolean value. True if node is in use by OneView,
False otherwise. False otherwise.
:raises OneViewError: if not possible to get OneView's informations :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 for the given node, if not possible to retrieve Server Hardware
from OneView. from OneView.
@ -157,24 +225,14 @@ def is_node_in_use_by_oneview(node):
node.driver_info.get('applied_server_profile_uri') node.driver_info.get('applied_server_profile_uri')
) )
# Check if Profile exists in Oneview and it is different of the one result = predicate(server_hardware, applied_sp_uri)
# 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
if result:
LOG.debug(positive)
else: else:
LOG.debug(_( LOG.debug(negative)
"Hardware %(hardware_uri)s is free for use by "
"ironic on node %(node_uuid)s."),
{"hardware_uri": server_hardware.uri,
"node_uuid": node.uuid})
return False return result
def _add_applied_server_profile_uri_field(node, applied_profile): def _add_applied_server_profile_uri_field(node, applied_profile):
@ -201,7 +259,7 @@ def _del_applied_server_profile_uri_field(node):
node.save() 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. """Allocate Server Hardware to ironic.
:param node: an ironic node object :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) raise exception.OneViewError(error=msg)
def _deallocate_server_hardware_from_ironic(node): def deallocate_server_hardware_from_ironic(node):
"""Deallocate Server Hardware from ironic. """Deallocate Server Hardware from ironic.
:param node: an ironic node object :param node: an ironic node object

View File

@ -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

View File

@ -23,6 +23,7 @@ from ironic.common.i18n import _
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.drivers import base from ironic.drivers import base
from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import common
from ironic.drivers.modules.oneview import deploy_utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -65,6 +66,10 @@ class OneViewManagement(base.ManagementInterface):
try: try:
common.validate_oneview_resources_compatibility(task) 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: except exception.OneViewError as oneview_exc:
raise exception.InvalidParameterValue(oneview_exc) raise exception.InvalidParameterValue(oneview_exc)

View File

@ -24,6 +24,7 @@ from ironic.drivers import base
from ironic.drivers.modules import iscsi_deploy from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules.oneview import common from ironic.drivers.modules.oneview import common
from ironic.drivers.modules.oneview import deploy 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 management
from ironic.drivers.modules.oneview import power from ironic.drivers.modules.oneview import power
from ironic.drivers.modules.oneview import vendor from ironic.drivers.modules.oneview import vendor
@ -55,6 +56,8 @@ class AgentPXEOneViewDriver(base.BaseDriver):
self.boot = pxe.PXEBoot() self.boot = pxe.PXEBoot()
self.deploy = deploy.OneViewAgentDeploy() self.deploy = deploy.OneViewAgentDeploy()
self.vendor = vendor.AgentVendorInterface() self.vendor = vendor.AgentVendorInterface()
self.inspect = inspect.OneViewInspect.create_if_enabled(
'AgentPXEOneViewDriver')
class ISCSIPXEOneViewDriver(base.BaseDriver): class ISCSIPXEOneViewDriver(base.BaseDriver):
@ -82,3 +85,5 @@ class ISCSIPXEOneViewDriver(base.BaseDriver):
self.boot = pxe.PXEBoot() self.boot = pxe.PXEBoot()
self.deploy = deploy.OneViewIscsiDeploy() self.deploy = deploy.OneViewIscsiDeploy()
self.vendor = iscsi_deploy.VendorPassthru() self.vendor = iscsi_deploy.VendorPassthru()
self.inspect = inspect.OneViewInspect.create_if_enabled(
'ISCSIPXEOneViewDriver')

View File

@ -237,8 +237,6 @@ class OneViewCommonTestCase(db_base.DbTestCase):
oneview_client = mock_get_ov_client() oneview_client = mock_get_ov_client()
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
common.validate_oneview_resources_compatibility(task) common.validate_oneview_resources_compatibility(task)
self.assertTrue(
oneview_client.validate_node_server_hardware.called)
self.assertTrue( self.assertTrue(
oneview_client.validate_node_server_hardware_type.called) oneview_client.validate_node_server_hardware_type.called)
self.assertTrue( self.assertTrue(
@ -264,15 +262,14 @@ class OneViewCommonTestCase(db_base.DbTestCase):
"""Validate compatibility of resources for Dynamic Allocation model. """Validate compatibility of resources for Dynamic Allocation model.
1) Set 'dynamic_allocation' flag as True on node's driver_info 1) Set 'dynamic_allocation' flag as True on node's driver_info
2) Check validate_node_server_hardware method is called 2) Check validate_node_server_hardware_type method is called
3) Check validate_node_server_hardware_type method is called 3) Check validate_node_enclosure_group method is called
4) Check validate_node_enclosure_group method is called 4) Check validate_node_server_profile_template method is called
5) Check validate_node_server_profile_template method is called 5) Check is_node_port_mac_compatible_with_server_hardware method
6) Check is_node_port_mac_compatible_with_server_hardware method
is called is called
7) Check validate_node_server_profile_template method is called 6) Check validate_node_server_profile_template method is called
8) Check check_server_profile_is_applied method is not called 7) Check check_server_profile_is_applied method is not called
9) Check is_node_port_mac_compatible_with_server_profile method is 8) Check is_node_port_mac_compatible_with_server_profile method is
not called not called
""" """
@ -283,8 +280,6 @@ class OneViewCommonTestCase(db_base.DbTestCase):
task.node.driver_info = driver_info task.node.driver_info = driver_info
common.validate_oneview_resources_compatibility(task) common.validate_oneview_resources_compatibility(task)
self.assertTrue(
oneview_client.validate_node_server_hardware.called)
self.assertTrue( self.assertTrue(
oneview_client.validate_node_server_hardware_type.called) oneview_client.validate_node_server_hardware_type.called)
self.assertTrue( self.assertTrue(

View File

@ -242,6 +242,43 @@ class OneViewDeployUtilsTestCase(db_base.DbTestCase):
deploy_utils.is_node_in_use_by_oneview(task.node) 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 # Tests for _add_applied_server_profile_uri_field
def test__add_applied_server_profile_uri_field(self, mock_get_ov_client): def test__add_applied_server_profile_uri_field(self, mock_get_ov_client):
"""Checks if applied_server_profile_uri was added to driver_info. """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', self.assertNotIn('applied_server_profile_uri',
task.node.driver_info) task.node.driver_info)
# Tests for _allocate_server_hardware_to_ironic # Tests for allocate_server_hardware_to_ironic
@mock.patch.object(objects.Node, 'save') @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 self, mock_node_save, mock_get_ov_client
): ):
"""Checks if a Server Profile was created and its uri is in driver_info. """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 mock_get_ov_client.return_value = ov_client
with task_manager.acquire(self.context, self.node.uuid) as task: 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' task.node, 'serverProfileName'
) )
self.assertTrue(ov_client.clone_template_and_apply.called) 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(objects.Node, 'save')
@mock.patch.object(deploy_utils, @mock.patch.object(deploy_utils,
'_del_applied_server_profile_uri_field') '_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 self, mock_delete_applied_sp, mock_node_save, mock_get_ov_client
): ):
"""Tests server profile allocation when applied_server_profile_uri exists. """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/' driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/'
task.node.driver_info = driver_info task.node.driver_info = driver_info
deploy_utils._allocate_server_hardware_to_ironic( deploy_utils.allocate_server_hardware_to_ironic(
task.node, 'serverProfileName' task.node, 'serverProfileName'
) )
self.assertTrue(mock_delete_applied_sp.called) 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') @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 self, mock_node_save, mock_get_ov_client
): ):
ov_client = mock_get_ov_client.return_value 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/' driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/'
task.node.driver_info = driver_info 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(ov_client.delete_server_profile.called)
self.assertTrue( self.assertTrue(
'applied_server_profile_uri' not in task.node.driver_info 'applied_server_profile_uri' not in task.node.driver_info

View File

@ -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)

View File

@ -32,6 +32,7 @@ from ironic.tests.unit.objects import utils as obj_utils
oneview_exceptions = importutils.try_import('oneview_client.exceptions') 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) @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', @mock.patch.object(common, 'validate_oneview_resources_compatibility',
spect_set=True, autospec=True) spect_set=True, autospec=True)
def test_validate(self, mock_validate, mock_get_ov_client): 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: with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.management.validate(task) task.driver.management.validate(task)
self.assertTrue(mock_validate.called) 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): def test_validate_fail(self, mock_get_ov_client):
node = obj_utils.create_test_node(self.context, node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(), uuid=uuidutils.generate_uuid(),

View File

@ -15,16 +15,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
import time import time
import types import types
import mock from oslo_utils import importutils
from ironic.common import exception from ironic.common import exception
from ironic.common import states from ironic.common import states
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import agent_client 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 power
from ironic.drivers.modules.oneview import vendor from ironic.drivers.modules.oneview import vendor
from ironic.drivers.modules import pxe 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.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils from ironic.tests.unit.objects import utils as obj_utils
oneview_models = importutils.try_import('oneview_client.models')
GET_POWER_STATE_RETRIES = 5 GET_POWER_STATE_RETRIES = 5
@mock.patch.object(common, 'get_oneview_client', spec_set=True, autospec=True)
class TestBaseAgentVendor(db_base.DbTestCase): class TestBaseAgentVendor(db_base.DbTestCase):
def setUp(self): def setUp(self):
@ -63,7 +68,8 @@ class TestBaseAgentVendor(db_base.DbTestCase):
@mock.patch('ironic.conductor.utils.node_set_boot_device', autospec=True) @mock.patch('ironic.conductor.utils.node_set_boot_device', autospec=True)
def test_reboot_and_finish_deploy(self, set_bootdev_mock, power_off_mock, def test_reboot_and_finish_deploy(self, set_bootdev_mock, power_off_mock,
get_power_state_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.provision_state = states.DEPLOYING
self.node.target_provision_state = states.ACTIVE self.node.target_provision_state = states.ACTIVE
self.node.save() self.node.save()
@ -89,9 +95,18 @@ class TestBaseAgentVendor(db_base.DbTestCase):
spec=types.FunctionType) spec=types.FunctionType)
def test_reboot_and_finish_deploy_soft_poweroff_doesnt_complete( def test_reboot_and_finish_deploy_soft_poweroff_doesnt_complete(
self, power_off_mock, get_power_state_mock, 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.provision_state = states.DEPLOYING
self.node.target_provision_state = states.ACTIVE 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() self.node.save()
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
@ -111,10 +126,20 @@ class TestBaseAgentVendor(db_base.DbTestCase):
@mock.patch.object(agent_client.AgentClient, 'power_off', @mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType) spec=types.FunctionType)
def test_reboot_and_finish_deploy_soft_poweroff_fails( 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") power_off_mock.side_effect = RuntimeError("boom")
self.node.provision_state = states.DEPLOYING self.node.provision_state = states.DEPLOYING
self.node.target_provision_state = states.ACTIVE 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() self.node.save()
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
@ -135,9 +160,18 @@ class TestBaseAgentVendor(db_base.DbTestCase):
spec=types.FunctionType) spec=types.FunctionType)
def test_reboot_and_finish_deploy_get_power_state_fails( def test_reboot_and_finish_deploy_get_power_state_fails(
self, power_off_mock, get_power_state_mock, 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.provision_state = states.DEPLOYING
self.node.target_provision_state = states.ACTIVE 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() self.node.save()
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
@ -162,7 +196,8 @@ class TestBaseAgentVendor(db_base.DbTestCase):
spec=types.FunctionType) spec=types.FunctionType)
def test_reboot_and_finish_deploy_power_action_fails( def test_reboot_and_finish_deploy_power_action_fails(
self, power_off_mock, get_power_state_mock, 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.provision_state = states.DEPLOYING
self.node.target_provision_state = states.ACTIVE self.node.target_provision_state = states.ACTIVE
self.node.save() self.node.save()
@ -193,11 +228,20 @@ class TestBaseAgentVendor(db_base.DbTestCase):
@mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True) @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
def test_reboot_to_instance(self, clean_pxe_mock, check_deploy_mock, def test_reboot_to_instance(self, clean_pxe_mock, check_deploy_mock,
power_off_mock, get_power_state_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 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.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE 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() self.node.save()
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
@ -227,11 +271,21 @@ class TestBaseAgentVendor(db_base.DbTestCase):
check_deploy_mock, check_deploy_mock,
power_off_mock, power_off_mock,
get_power_state_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 check_deploy_mock.return_value = None
self.node.provision_state = states.DEPLOYWAIT self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE 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() self.node.save()
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:

View File

@ -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.