Add Dynamic Allocation feature for the OneView drivers

This change is about adding the ability to the OneView drivers of
dynamically allocate OneView resources to Ironic. The current
version of the drivers consider what we call "pre-allocation" of
nodes, meaning that when a node is registered in Ironic, even if
it is not in use, this resource is still reserved in OneView.
This change will prevent such situations by allocating OneView
resources only at boot time, allowing both systems to really
share the same pool of hardware.

Change-Id: I43d1db490b4834080562946b8a6ca584ea36864d
Co-Authored-By: Lilia Sampaio <liliars@lsd.ufcg.edu.br>
Co-Authored-By: Xavier <marcusrafael@lsd.ufcg.edu.br>
Co-Authored-By: Hugo Nicodemos <nicodemos@lsd.ufcg.edu.br>
Co-Authored-By: Thiago Paiva Brito <thiagop@lsd.ufcg.edu.br>
Co-Authored-By: Caio Oliveira <caiobo@lsd.ufcg.edu.br>
Partial-Bug: #1541096
This commit is contained in:
Sinval Vieira 2016-02-29 18:09:55 +00:00 committed by Thiago Paiva
parent 7f4c9a5924
commit 4483de30ba
16 changed files with 1359 additions and 59 deletions

View File

@ -83,7 +83,7 @@ IRONIC_HW_ARCH=${IRONIC_HW_ARCH:-x86_64}
# <BMC address> <MAC address> <BMC username> <BMC password> <UCS service profile>
#
# *_oneview:
# <Server Hardware URI> <Server Hardware Type URI> <Enclosure Group URI> <Server Profile Template URI> <MAC of primary connection>
# <Server Hardware URI> <Server Hardware Type URI> <Enclosure Group URI> <Server Profile Template URI> <MAC of primary connection> <Applied Server Profile URI>
#
# IRONIC_IPMIINFO_FILE is deprecated, please use IRONIC_HWINFO_FILE. IRONIC_IPMIINFO_FILE will be removed in Ocata.
IRONIC_IPMIINFO_FILE=${IRONIC_IPMIINFO_FILE:-""}
@ -1053,8 +1053,11 @@ function enroll_nodes {
local server_profile_template_uri
server_profile_template_uri=$(echo $hardware_info |awk '{print $4}')
mac_address=$(echo $hardware_info |awk '{print $5}')
local applied_server_profile_uri
applied_server_profile_uri=$(echo $hardware_info |awk '{print $6}')
node_options+=" -i server_hardware_uri=$server_hardware_uri"
node_options+=" -i applied_server_profile_uri=$applied_server_profile_uri"
node_options+=" -p capabilities="
node_options+="server_hardware_type_uri:$server_hardware_type_uri,"
node_options+="enclosure_group_uri:$enclosure_group_uri,"

View File

@ -1909,26 +1909,37 @@
# From ironic
#
# URL where OneView is available (string value)
# URL where OneView is available. (string value)
#manager_url = <None>
# OneView username to be used (string value)
# OneView username to be used. (string value)
#username = <None>
# OneView password to be used (string value)
# OneView password to be used. (string value)
#password = <None>
# Option to allow insecure connection with OneView (boolean
# Option to allow insecure connection with OneView. (boolean
# value)
#allow_insecure_connections = false
# Path to CA certificate (string value)
# Path to CA certificate. (string value)
#tls_cacert_file = <None>
# Max connection retries to check changes on OneView (integer
# Max connection retries to check changes on OneView. (integer
# value)
#max_polling_attempts = 12
# Period (in seconds) for periodic tasks to be executed.
# (integer value)
#periodic_check_interval = 300
# Whether to enable the periodic tasks for OneView driver be
# aware when OneView hardware resources are taken and released
# by Ironic or OneView users and proactively manage nodes in
# clean fail state according to Dynamic Allocation model of
# hardware resources allocation in OneView. (boolean value)
#enable_periodic_tasks = true
[oslo_concurrency]

View File

@ -595,6 +595,11 @@ class OneViewError(IronicException):
_msg_fmt = _("OneView exception occurred. Error: %(error)s")
class OneViewInvalidNodeParameter(OneViewError):
_msg_fmt = _("Error while obtaining OneView info from node %(node_uuid)s. "
"Error: %(error)s")
class NodeTagNotFound(IronicException):
_msg_fmt = _("Node %(node_id)s doesn't have a tag '%(tag)s'")

View File

@ -20,20 +20,32 @@ from ironic.common.i18n import _
opts = [
cfg.StrOpt('manager_url',
help=_('URL where OneView is available')),
help=_('URL where OneView is available.')),
cfg.StrOpt('username',
help=_('OneView username to be used')),
help=_('OneView username to be used.')),
cfg.StrOpt('password',
secret=True,
help=_('OneView password to be used')),
help=_('OneView password to be used.')),
cfg.BoolOpt('allow_insecure_connections',
default=False,
help=_('Option to allow insecure connection with OneView')),
help=_('Option to allow insecure connection with OneView.')),
cfg.StrOpt('tls_cacert_file',
help=_('Path to CA certificate')),
help=_('Path to CA certificate.')),
cfg.IntOpt('max_polling_attempts',
default=12,
help=_('Max connection retries to check changes on OneView')),
help=_('Max connection retries to check changes on OneView.')),
cfg.BoolOpt('enable_periodic_tasks',
default=True,
help=_('Whether to enable the periodic tasks for OneView '
'driver be aware when OneView hardware resources are '
'taken and released by Ironic or OneView users '
'and proactively manage nodes in clean fail state '
'according to Dynamic Allocation model of hardware '
'resources allocation in OneView.')),
cfg.IntOpt('periodic_check_interval',
default=300,
help=_('Period (in seconds) for periodic tasks to be '
'executed when enable_periodic_tasks=True.')),
]

View File

@ -1,4 +1,3 @@
#
# Copyright 2015 Hewlett Packard Development Company, LP
# Copyright 2015 Universidade Federal de Campina Grande
#
@ -61,6 +60,15 @@ COMMON_PROPERTIES.update(REQUIRED_ON_DRIVER_INFO)
COMMON_PROPERTIES.update(REQUIRED_ON_PROPERTIES)
COMMON_PROPERTIES.update(OPTIONAL_ON_PROPERTIES)
ISCSI_PXE_ONEVIEW = 'iscsi_pxe_oneview'
AGENT_PXE_ONEVIEW = 'agent_pxe_oneview'
# NOTE(xavierr): We don't want to translate NODE_IN_USE_BY_ONEVIEW and
# SERVER_HARDWARE_ALLOCATION_ERROR to avoid inconsistency in the nodes
# caused by updates on translation in upgrades of ironic.
NODE_IN_USE_BY_ONEVIEW = 'node in use by OneView'
SERVER_HARDWARE_ALLOCATION_ERROR = 'server hardware allocation error'
def get_oneview_client():
"""Generates an instance of the OneView client.
@ -70,7 +78,6 @@ def get_oneview_client():
:returns: an instance of the OneView client
"""
oneview_client = client.Client(
manager_url=CONF.oneview.manager_url,
username=CONF.oneview.username,
@ -140,12 +147,16 @@ def get_oneview_info(node):
:enclosure_group_uri: the uri of the enclosure group in OneView
:server_profile_template_uri: the uri of the server profile template in
OneView
:raises InvalidParameterValue if node capabilities are malformed
:raises OneViewInvalidNodeParameter if node capabilities are malformed
"""
capabilities_dict = utils.capabilities_to_dict(
node.properties.get('capabilities', '')
)
try:
capabilities_dict = utils.capabilities_to_dict(
node.properties.get('capabilities', '')
)
except exception.InvalidParameterValue as e:
raise exception.OneViewInvalidNodeParameter(node_uuid=node.uuid,
error=e)
driver_info = node.driver_info
@ -159,6 +170,8 @@ def get_oneview_info(node):
'server_profile_template_uri':
capabilities_dict.get('server_profile_template_uri') or
driver_info.get('server_profile_template_uri'),
'applied_server_profile_uri':
driver_info.get('applied_server_profile_uri'),
}
return oneview_info
@ -180,25 +193,41 @@ def validate_oneview_resources_compatibility(task):
node = task.node
node_ports = task.ports
try:
oneview_info = get_oneview_info(task.node)
except exception.InvalidParameterValue as e:
msg = (_("Error while obtaining OneView info from node "
"%(node_uuid)s. Error: %(error)s") %
{'node_uuid': node.uuid, 'error': e})
raise exception.OneViewError(error=msg)
try:
oneview_client = get_oneview_client()
oneview_info = get_oneview_info(node)
oneview_client.validate_node_server_hardware(
oneview_info, node.properties.get('memory_mb'),
node.properties.get('cpus')
)
oneview_client.validate_node_server_hardware_type(oneview_info)
oneview_client.check_server_profile_is_applied(oneview_info)
oneview_client.is_node_port_mac_compatible_with_server_profile(
oneview_info, node_ports
)
oneview_client.validate_node_enclosure_group(oneview_info)
oneview_client.validate_node_server_profile_template(oneview_info)
# NOTE(thiagop): Support to pre-allocation will be dropped in 'P'
# release
if is_dynamic_allocation_enabled(task.node):
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)
LOG.error(msg)
msg = (_("Error validating node resources with OneView: %s") %
oneview_exc)
raise exception.OneViewError(error=msg)
@ -252,7 +281,13 @@ def node_has_server_profile(func):
"""
def inner(*args, **kwargs):
task = args[1]
oneview_info = get_oneview_info(task.node)
try:
oneview_info = get_oneview_info(task.node)
except exception.InvalidParameterValue as e:
msg = (_("Error while obtaining OneView info from node "
"%(node_uuid)s. Error: %(error)s") %
{'node_uuid': task.node.uuid, 'error': e})
raise exception.OneViewError(error=msg)
oneview_client = get_oneview_client()
try:
node_has_server_profile = (
@ -272,3 +307,17 @@ def node_has_server_profile(func):
)
return func(*args, **kwargs)
return inner
def is_dynamic_allocation_enabled(node):
flag = node.driver_info.get('dynamic_allocation')
if flag:
if isinstance(flag, bool):
return flag is True
else:
msg = (_LE("Invalid dynamic_allocation parameter value in "
"node's %(node_uuid)s driver_info. Valid values "
"are booleans true or false.") %
{"node_uuid": node.uuid})
raise exception.InvalidParameterValue(msg)
return False

View File

@ -0,0 +1,264 @@
# 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 abc
from futurist import periodics
from oslo_log import log as logging
import six
from ironic.common import exception
from ironic.common.i18n import _LE
from ironic.common.i18n import _LI
from ironic.common import states
from ironic.drivers.modules import agent
from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules.oneview import common
from ironic.drivers.modules.oneview import deploy_utils
from ironic import objects
LOG = logging.getLogger(__name__)
CONF = common.CONF
@six.add_metaclass(abc.ABCMeta)
class OneViewPeriodicTasks(object):
@abc.abstractproperty
def oneview_driver(self):
pass
@periodics.periodic(spacing=CONF.oneview.periodic_check_interval,
enabled=CONF.oneview.enable_periodic_tasks)
def _periodic_check_nodes_taken_by_oneview(self, manager, context):
"""Checks if nodes in Ironic were taken by OneView users.
This driver periodic task will check for nodes that were taken by
OneView users while the node is in available state, set the node to
maintenance mode with an appropriate maintenance reason message and
move the node to manageable state.
:param manager: a ConductorManager instance
:param context: request context
:returns: None.
"""
filters = {
'provision_state': states.AVAILABLE,
'maintenance': False,
'driver': self.oneview_driver
}
node_iter = manager.iter_nodes(filters=filters)
for node_uuid, driver in node_iter:
node = objects.Node.get(context, node_uuid)
try:
oneview_using = deploy_utils.is_node_in_use_by_oneview(node)
except exception.OneViewError as e:
LOG.error(_LE("Error while determining if node "
"%(node_uuid)s is in use by OneView. "
"Error: %(error)s"),
{'node_uuid': node.uuid, 'error': e})
if oneview_using:
purpose = (_LI('Updating node %(node_uuid)s in use '
'by OneView from %(provision_state)s state '
'to %(target_state)s state and maintenance '
'mode %(maintenance)s.'),
{'node_uuid': node_uuid,
'provision_state': states.AVAILABLE,
'target_state': states.MANAGEABLE,
'maintenance': True})
LOG.info(purpose)
node.maintenance = True
node.maintenance_reason = common.NODE_IN_USE_BY_ONEVIEW
manager.update_node(context, node)
manager.do_provisioning_action(context, node.uuid, 'manage')
@periodics.periodic(spacing=CONF.oneview.periodic_check_interval,
enabled=CONF.oneview.enable_periodic_tasks)
def _periodic_check_nodes_freed_by_oneview(self, manager, context):
"""Checks if nodes taken by OneView users were freed.
This driver periodic task will be responsible to poll the nodes that
are in maintenance mode and on manageable state to check if the Server
Profile was removed, indicating that the node was freed by the OneView
user. If so, it'll provide the node, that will pass through the
cleaning process and become available to be provisioned.
:param manager: a ConductorManager instance
:param context: request context
:returns: None.
"""
filters = {
'provision_state': states.MANAGEABLE,
'maintenance': True,
'driver': self.oneview_driver
}
node_iter = manager.iter_nodes(fields=['maintenance_reason'],
filters=filters)
for node_uuid, driver, maintenance_reason in node_iter:
if maintenance_reason == common.NODE_IN_USE_BY_ONEVIEW:
node = objects.Node.get(context, node_uuid)
try:
oneview_using = deploy_utils.is_node_in_use_by_oneview(
node
)
except exception.OneViewError as e:
LOG.error(_LE("Error while determining if node "
"%(node_uuid)s is in use by OneView. "
"Error: %(error)s"),
{'node_uuid': node.uuid, 'error': e})
if not oneview_using:
purpose = (_LI('Bringing node %(node_uuid)s back from '
'use by OneView from %(provision_state)s '
'state to %(target_state)s state and '
'maintenance mode %(maintenance)s.'),
{'node_uuid': node_uuid,
'provision_state': states.MANAGEABLE,
'target_state': states.AVAILABLE,
'maintenance': False})
LOG.info(purpose)
node.maintenance = False
node.maintenance_reason = None
manager.update_node(context, node)
manager.do_provisioning_action(
context, node.uuid, 'provide'
)
@periodics.periodic(spacing=CONF.oneview.periodic_check_interval,
enabled=CONF.oneview.enable_periodic_tasks)
def _periodic_check_nodes_taken_on_cleanfail(self, manager, context):
"""Checks failed deploys due to Oneview users taking Server Hardware.
This last driver periodic task will take care of nodes that would be
caught on a race condition between OneView and a deploy by Ironic. In
such cases, the validation will fail, throwing the node on deploy fail
and, afterwards on clean fail.
This task will set the node to maintenance mode with a proper reason
message and move it to manageable state, from where the second task
can rescue the node as soon as the Server Profile is removed.
:param manager: a ConductorManager instance
:param context: request context
:returns: None.
"""
filters = {
'provision_state': states.CLEANFAIL,
'driver': self.oneview_driver
}
node_iter = manager.iter_nodes(fields=['driver_internal_info'],
filters=filters)
for node_uuid, driver, driver_internal_info in node_iter:
node_oneview_error = driver_internal_info.get('oneview_error')
if node_oneview_error == common.SERVER_HARDWARE_ALLOCATION_ERROR:
node = objects.Node.get(context, node_uuid)
purpose = (_LI('Bringing node %(node_uuid)s back from use '
'by OneView from %(provision_state)s state '
'to %(target_state)s state and '
'maintenance mode %(maintenance)s.'),
{'node_uuid': node_uuid,
'provision_state': states.CLEANFAIL,
'target_state': states.MANAGEABLE,
'maintenance': False})
LOG.info(purpose)
node.maintenance = True
node.maintenance_reason = common.NODE_IN_USE_BY_ONEVIEW
driver_internal_info = node.driver_internal_info
driver_internal_info.pop('oneview_error', None)
node.driver_internal_info = driver_internal_info
manager.update_node(context, node)
manager.do_provisioning_action(context, node.uuid, 'manage')
class OneViewIscsiDeploy(iscsi_deploy.ISCSIDeploy, OneViewPeriodicTasks):
"""Class for OneView ISCSI deployment driver."""
oneview_driver = common.ISCSI_PXE_ONEVIEW
def get_properties(self):
deploy_utils.get_properties()
def prepare(self, task):
if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.prepare(task)
super(OneViewIscsiDeploy, self).prepare(task)
def tear_down(self, task):
if (common.is_dynamic_allocation_enabled(task.node) and
not CONF.conductor.automated_clean):
deploy_utils.tear_down(task)
super(OneViewIscsiDeploy, self).tear_down(task)
def prepare_cleaning(self, task):
if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.prepare_cleaning(task)
return super(OneViewIscsiDeploy, self).prepare_cleaning(task)
def tear_down_cleaning(self, task):
if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.tear_down_cleaning(task)
return super(OneViewIscsiDeploy, self).tear_down_cleaning(task)
class OneViewAgentDeploy(agent.AgentDeploy, OneViewPeriodicTasks):
"""Class for OneView Agent deployment driver."""
oneview_driver = common.AGENT_PXE_ONEVIEW
def get_properties(self):
deploy_utils.get_properties()
def prepare(self, task):
if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.prepare(task)
super(OneViewAgentDeploy, self).prepare(task)
def tear_down(self, task):
if (common.is_dynamic_allocation_enabled(task.node) and
not CONF.conductor.automated_clean):
deploy_utils.tear_down(task)
super(OneViewAgentDeploy, self).tear_down(task)
def prepare_cleaning(self, task):
if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.prepare_cleaning(task)
return super(OneViewAgentDeploy, self).prepare_cleaning(task)
def tear_down_cleaning(self, task):
if common.is_dynamic_allocation_enabled(task.node):
deploy_utils.tear_down_cleaning(task)
return super(OneViewAgentDeploy, self).tear_down_cleaning(task)

View File

@ -0,0 +1,335 @@
# 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 oslo_log import log as logging
from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common.i18n import _LI
from ironic.common.i18n import _LW
from ironic.common import states
from ironic.drivers.modules.oneview import common
LOG = logging.getLogger(__name__)
oneview_exception = importutils.try_import('oneview_client.exceptions')
oneview_utils = importutils.try_import('oneview_client.utils')
def get_properties():
return common.COMMON_PROPERTIES
def prepare(task):
"""Applies Server Profile and update the node when preparing.
This method is responsible for applying a Server Profile to the Server
Hardware and add the uri of the applied Server Profile in the node's
'applied_server_profile_uri' field on properties/capabilities.
:param task: A TaskManager object
:raises InstanceDeployFailure: If the node doesn't have the needed OneView
informations, if Server Hardware is in use by an OneView user, or
if the Server Profile can't be applied.
"""
if task.node.provision_state == states.DEPLOYING:
try:
instance_display_name = task.node.instance_info.get('display_name')
instance_uuid = task.node.instance_uuid
server_profile_name = (
"%(instance_name)s [%(instance_uuid)s]" %
{"instance_name": instance_display_name,
"instance_uuid": instance_uuid}
)
_allocate_server_hardware_to_ironic(task.node, server_profile_name)
except exception.OneViewError as e:
raise exception.InstanceDeployFailure(node=task.node.uuid,
reason=e)
def tear_down(task):
"""Remove Server profile and update the node when tear down.
This method is responsible for power a Server Hardware off, remove a Server
Profile from the Server Hardware and remove the uri of the applied Server
Profile from the node's 'applied_server_profile_uri' in
properties/capabilities.
:param task: A TaskManager object
:raises InstanceDeployFailure: If node has no uri of applied Server
Profile, or if some error occur while deleting Server Profile.
"""
try:
_deallocate_server_hardware_from_ironic(task.node)
except exception.OneViewError as e:
raise exception.InstanceDeployFailure(node=task.node.uuid, reason=e)
def prepare_cleaning(task):
"""Applies Server Profile and update the node when preparing cleaning.
This method is responsible for applying a Server Profile to the Server
Hardware and add the uri of the applied Server Profile in the node's
'applied_server_profile_uri' field on properties/capabilities.
:param task: A TaskManager object
:raises NodeCleaningFailure: If the node doesn't have the needed OneView
informations, if Server Hardware is in use by an OneView user, or
if the Server Profile can't be applied.
"""
try:
server_profile_name = "Ironic Cleaning [%s]" % task.node.uuid
_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
driver_internal_info['oneview_error'] = oneview_error
task.node.driver_internal_info = driver_internal_info
task.node.save()
raise exception.NodeCleaningFailure(node=task.node.uuid,
reason=e)
def tear_down_cleaning(task):
"""Remove Server profile and update the node when tear down cleaning.
This method is responsible for power a Server Hardware off, remove a Server
Profile from the Server Hardware and remove the uri of the applied Server
Profile from the node's 'applied_server_profile_uri' in
properties/capabilities.
:param task: A TaskManager object
:raises NodeCleaningFailure: If node has no uri of applied Server Profile,
or if some error occur while deleting Server Profile.
"""
try:
_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_by_oneview(node):
"""Check if node is in use by OneView user.
:param node: an ironic node object
: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.
"""
try:
oneview_info = common.get_oneview_info(node)
except exception.InvalidParameterValue as e:
msg = (_("Error while obtaining OneView info from node "
"%(node_uuid)s. Error: %(error)s") %
{'node_uuid': node.uuid, 'error': e})
raise exception.OneViewError(error=msg)
oneview_client = common.get_oneview_client()
sh_uuid = oneview_utils.get_uuid_from_uri(
oneview_info.get("server_hardware_uri")
)
try:
server_hardware = oneview_client.get_server_hardware_by_uuid(
sh_uuid
)
except oneview_exception.OneViewResourceNotFoundError as e:
msg = (_("Error while obtaining Server Hardware from node "
"%(node_uuid)s. Error: %(error)s") %
{'node_uuid': node.uuid, 'error': e})
raise exception.OneViewError(error=msg)
applied_sp_uri = (
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
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})
return False
def _add_applied_server_profile_uri_field(node, applied_profile):
"""Adds the applied Server Profile uri to a node.
:param node: an ironic node object
"""
driver_info = node.driver_info
driver_info['applied_server_profile_uri'] = applied_profile.uri
node.driver_info = driver_info
node.save()
def _del_applied_server_profile_uri_field(node):
"""Delete the applied Server Profile uri from a node if it exists.
:param node: an ironic node object
"""
driver_info = node.driver_info
driver_info.pop('applied_server_profile_uri', None)
node.driver_info = driver_info
node.save()
def _allocate_server_hardware_to_ironic(node, server_profile_name):
"""Allocate Server Hardware to ironic.
:param node: an ironic node object
:param server_profile_name: a formatted string with the Server Profile
name
:raises OneViewError: if an error occurs while allocating the Server
Hardware to ironic
"""
node_in_use_by_oneview = is_node_in_use_by_oneview(node)
if not node_in_use_by_oneview:
try:
oneview_info = common.get_oneview_info(node)
except exception.InvalidParameterValue as e:
msg = (_("Error while obtaining OneView info from node "
"%(node_uuid)s. Error: %(error)s") %
{'node_uuid': node.uuid, 'error': e})
raise exception.OneViewError(error=msg)
applied_sp_uri = node.driver_info.get('applied_server_profile_uri')
sh_uuid = oneview_utils.get_uuid_from_uri(
oneview_info.get("server_hardware_uri")
)
spt_uuid = oneview_utils.get_uuid_from_uri(
oneview_info.get("server_profile_template_uri")
)
oneview_client = common.get_oneview_client()
server_hardware = oneview_client.get_server_hardware_by_uuid(sh_uuid)
# Don't have Server Profile on OneView but has
# `applied_server_profile_uri` on driver_info
if (server_hardware.server_profile_uri in (None, '') and
applied_sp_uri is not (None, '')):
_del_applied_server_profile_uri_field(node)
LOG.info(_LI(
"Inconsistent 'applied_server_profile_uri' parameter "
"value in driver_info. There is no Server Profile "
"applied to node %(node_uuid)s. Value deleted."),
{"node_uuid": node.uuid}
)
# applied_server_profile_uri exists and is equal to Server profile
# applied on Hardware. Do not apply again.
if (applied_sp_uri and server_hardware.server_profile_uri and
server_hardware.server_profile_uri == applied_sp_uri):
LOG.info(_LI(
"The Server Profile %(applied_sp_uri)s was already applied "
"by ironic on node %(node_uuid)s. Reusing."),
{"node_uuid": node.uuid, "applied_sp_uri": applied_sp_uri}
)
return
try:
applied_profile = oneview_client.clone_template_and_apply(
server_profile_name, sh_uuid, spt_uuid
)
_add_applied_server_profile_uri_field(node, applied_profile)
LOG.info(
_LI("Server Profile %(server_profile_uuid)s was successfully"
" applied to node %(node_uuid)s."),
{"node_uuid": node.uuid,
"server_profile_uuid": applied_profile.uri}
)
except oneview_exception.OneViewServerProfileAssignmentError as e:
LOG.error(_LE("An error occurred during allocating server "
"hardware to ironic during prepare: %s"), e)
raise exception.OneViewError(error=e)
else:
msg = (_("Node %s is already in use by OneView.") %
node.uuid)
raise exception.OneViewError(error=msg)
def _deallocate_server_hardware_from_ironic(node):
"""Deallocate Server Hardware from ironic.
:param node: an ironic node object
:raises OneViewError: if an error occurs while deallocating the Server
Hardware to ironic
"""
try:
oneview_info = common.get_oneview_info(node)
except exception.InvalidParameterValue as e:
msg = (_("Error while obtaining OneView info from node "
"%(node_uuid)s. Error: %(error)s") %
{'node_uuid': node.uuid, 'error': e})
raise exception.OneViewError(error=msg)
oneview_client = common.get_oneview_client()
oneview_client.power_off(oneview_info)
applied_sp_uuid = oneview_utils.get_uuid_from_uri(
oneview_info.get('applied_server_profile_uri')
)
try:
oneview_client.delete_server_profile(applied_sp_uuid)
_del_applied_server_profile_uri_field(node)
LOG.info(
_LI("Server Profile %(server_profile_uuid)s was successfully"
" deleted from node %(node_uuid)s."
),
{"node_uuid": node.uuid, "server_profile_uuid": applied_sp_uuid}
)
except oneview_exception.OneViewException as e:
msg = (_("Error while deleting applied Server Profile from node "
"%(node_uuid)s. Error: %(error)s") %
{'node_uuid': node.uuid, 'error': e})
raise exception.OneViewError(
node=node.uuid, reason=msg
)

View File

@ -97,7 +97,6 @@ class OneViewManagement(base.ManagementInterface):
if the server is already powered on.
:raises: OneViewError if the communication with OneView fails
"""
oneview_info = common.get_oneview_info(task.node)
if device not in self.get_supported_boot_devices(task):
@ -115,7 +114,6 @@ class OneViewManagement(base.ManagementInterface):
"Error setting boot device on OneView. Error: %s")
% oneview_exc
)
LOG.error(msg)
raise exception.OneViewError(error=msg)
@common.node_has_server_profile
@ -135,7 +133,6 @@ class OneViewManagement(base.ManagementInterface):
:raises: InvalidParameterValue if the boot device is unknown
:raises: OneViewError if the communication with OneView fails
"""
oneview_info = common.get_oneview_info(task.node)
try:
@ -146,7 +143,6 @@ class OneViewManagement(base.ManagementInterface):
"Error getting boot device from OneView. Error: %s")
% oneview_exc
)
LOG.error(msg)
raise exception.OneViewError(msg)
primary_device = boot_order[0]

View File

@ -69,8 +69,8 @@ class OneViewPower(base.PowerInterface):
:raises: OneViewError if fails to retrieve power state of OneView
resource
"""
oneview_info = common.get_oneview_info(task.node)
oneview_client = common.get_oneview_client()
try:
power_state = oneview_client.get_node_power_state(oneview_info)
@ -95,8 +95,8 @@ class OneViewPower(base.PowerInterface):
:raises: PowerStateFailure if the power couldn't be set to power_state.
:raises: OneViewError if OneView fails setting the power state.
"""
oneview_info = common.get_oneview_info(task.node)
oneview_client = common.get_oneview_client()
LOG.debug('Setting power state of node %(node_uuid)s to '

View File

@ -1,4 +1,3 @@
#
# Copyright 2015 Hewlett Packard Development Company, LP
# Copyright 2015 Universidade Federal de Campina Grande
#
@ -22,9 +21,9 @@ from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.drivers import base
from ironic.drivers.modules import agent
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 management
from ironic.drivers.modules.oneview import power
from ironic.drivers.modules.oneview import vendor
@ -32,14 +31,12 @@ from ironic.drivers.modules import pxe
class AgentPXEOneViewDriver(base.BaseDriver):
"""Agent + OneView driver.
"""OneViewDriver using OneViewClient interface.
This driver implements the `core` functionality, combining
:class:`ironic.drivers.ov.OVPower` for power on/off and reboot of virtual
machines, with :class:`ironic.driver.pxe.PXEBoot` for booting deploy kernel
and ramdisk and :class:`ironic.driver.iscsi_deploy.ISCSIDeploy` for image
deployment. Implementations are in those respective classes; this class is
merely the glue between them.
This driver implements the `core` functionality using
:class:ironic.drivers.modules.oneview.power.OneViewPower for power
management. And
:class:ironic.drivers.modules.oneview.deploy.OneViewAgentDeploy for deploy.
"""
def __init__(self):
@ -56,19 +53,17 @@ class AgentPXEOneViewDriver(base.BaseDriver):
self.power = power.OneViewPower()
self.management = management.OneViewManagement()
self.boot = pxe.PXEBoot()
self.deploy = agent.AgentDeploy()
self.deploy = deploy.OneViewAgentDeploy()
self.vendor = vendor.AgentVendorInterface()
class ISCSIPXEOneViewDriver(base.BaseDriver):
"""PXE + OneView driver.
"""OneViewDriver using OneViewClient interface.
This driver implements the `core` functionality, combining
:class:`ironic.drivers.ov.OVPower` for power on/off and reboot of virtual
machines, with :class:`ironic.driver.pxe.PXEBoot` for booting deploy kernel
and ramdisk and :class:`ironic.driver.iscsi_deploy.ISCSIDeploy` for image
deployment. Implementations are in those respective classes; this class is
merely the glue between them.
This driver implements the `core` functionality using
:class:ironic.drivers.modules.oneview.power.OneViewPower for power
management. And
:class:ironic.drivers.modules.oneview.deploy.OneViewIscsiDeploy for deploy.
"""
def __init__(self):
@ -85,5 +80,5 @@ class ISCSIPXEOneViewDriver(base.BaseDriver):
self.power = power.OneViewPower()
self.management = management.OneViewManagement()
self.boot = pxe.PXEBoot()
self.deploy = iscsi_deploy.ISCSIDeploy()
self.deploy = deploy.OneViewIscsiDeploy()
self.vendor = iscsi_deploy.VendorPassthru()

View File

@ -1,5 +1,3 @@
# -*- encoding: utf-8 -*-
#
# Copyright 2015 Hewlett Packard Development Company, LP
# Copyright 2015 Universidade Federal de Campina Grande
#
@ -115,6 +113,7 @@ class OneViewCommonTestCase(db_base.DbTestCase):
'server_hardware_type_uri': 'fake_sht_uri',
'enclosure_group_uri': 'fake_eg_uri',
'server_profile_template_uri': 'fake_spt_uri',
'applied_server_profile_uri': None,
}
self.assertEqual(
@ -124,7 +123,6 @@ class OneViewCommonTestCase(db_base.DbTestCase):
def test_get_oneview_info_missing_spt(self):
driver_info = db_utils.get_test_oneview_driver_info()
properties = db_utils.get_test_oneview_properties()
properties["capabilities"] = ("server_hardware_type_uri:fake_sht_uri,"
"enclosure_group_uri:fake_eg_uri")
@ -138,6 +136,7 @@ class OneViewCommonTestCase(db_base.DbTestCase):
'server_hardware_type_uri': 'fake_sht_uri',
'enclosure_group_uri': 'fake_eg_uri',
'server_profile_template_uri': None,
'applied_server_profile_uri': None,
}
self.assertEqual(
@ -165,6 +164,7 @@ class OneViewCommonTestCase(db_base.DbTestCase):
'server_hardware_type_uri': 'fake_sht_uri',
'enclosure_group_uri': 'fake_eg_uri',
'server_profile_template_uri': 'fake_spt_uri',
'applied_server_profile_uri': None,
}
self.assertEqual(
@ -172,6 +172,20 @@ class OneViewCommonTestCase(db_base.DbTestCase):
common.get_oneview_info(incomplete_node)
)
def test_get_oneview_info_malformed_capabilities(self):
driver_info = db_utils.get_test_oneview_driver_info()
del driver_info["server_hardware_uri"]
properties = db_utils.get_test_oneview_properties()
properties["capabilities"] = "anything,000"
self.node.driver_info = driver_info
self.node.properties = properties
self.assertRaises(exception.OneViewInvalidNodeParameter,
common.get_oneview_info,
self.node)
# TODO(gabriel-bezerra): Remove this after Mitaka
@mock.patch.object(common, 'LOG', autospec=True)
def test_deprecated_spt_in_driver_info(self, log_mock):
@ -194,6 +208,7 @@ class OneViewCommonTestCase(db_base.DbTestCase):
'server_hardware_type_uri': 'fake_sht_uri',
'enclosure_group_uri': 'fake_eg_uri',
'server_profile_template_uri': 'fake_spt_uri',
'applied_server_profile_uri': None,
}
self.assertEqual(
@ -226,6 +241,7 @@ class OneViewCommonTestCase(db_base.DbTestCase):
'server_hardware_type_uri': 'fake_sht_uri',
'enclosure_group_uri': 'fake_eg_uri',
'server_profile_template_uri': 'fake_spt_uri',
'applied_server_profile_uri': None,
}
self.assertEqual(
@ -281,8 +297,9 @@ class OneViewCommonTestCase(db_base.DbTestCase):
@mock.patch.object(common, 'get_oneview_client', spec_set=True,
autospec=True)
def test_validate_oneview_resources_compatibility(self,
mock_get_ov_client):
def test_validate_oneview_resources_compatibility(
self, mock_get_ov_client
):
oneview_client = mock_get_ov_client()
with task_manager.acquire(self.context, self.node.uuid) as task:
common.validate_oneview_resources_compatibility(task)
@ -290,12 +307,123 @@ class OneViewCommonTestCase(db_base.DbTestCase):
oneview_client.validate_node_server_hardware.called)
self.assertTrue(
oneview_client.validate_node_server_hardware_type.called)
self.assertTrue(
oneview_client.validate_node_enclosure_group.called)
self.assertTrue(
oneview_client.validate_node_server_profile_template.called)
self.assertTrue(
oneview_client.check_server_profile_is_applied.called)
self.assertTrue(
oneview_client.is_node_port_mac_compatible_with_server_profile.
called)
oneview_client.
is_node_port_mac_compatible_with_server_profile.called)
self.assertFalse(
oneview_client.
is_node_port_mac_compatible_with_server_hardware.called)
self.assertFalse(
oneview_client.validate_spt_primary_boot_connection.called)
@mock.patch.object(common, 'get_oneview_client', spec_set=True,
autospec=True)
def test_validate_oneview_resources_compatibility_dynamic_allocation(
self, mock_get_ov_client
):
"""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
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
not called
"""
oneview_client = mock_get_ov_client()
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['dynamic_allocation'] = True
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(
oneview_client.validate_node_enclosure_group.called)
self.assertTrue(
oneview_client.validate_node_server_profile_template.called)
self.assertTrue(
oneview_client.
is_node_port_mac_compatible_with_server_hardware.called)
self.assertTrue(
oneview_client.validate_node_server_profile_template.called)
self.assertFalse(
oneview_client.check_server_profile_is_applied.called)
self.assertFalse(
oneview_client.
is_node_port_mac_compatible_with_server_profile.called)
def test_is_dynamic_allocation_enabled(self):
"""Ensure Dynamic Allocation is enabled when flag is True.
1) Set 'dynamic_allocation' flag as True on node's driver_info
2) Check Dynamic Allocation is enabled for the given node
"""
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['dynamic_allocation'] = True
task.node.driver_info = driver_info
self.assertTrue(
common.is_dynamic_allocation_enabled(task.node)
)
def test_is_dynamic_allocation_enabled_false(self):
"""Ensure Dynamic Allocation is disabled when flag is False.
1) Set 'dynamic_allocation' flag as False on node's driver_info
2) Check Dynamic Allocation is disabled for the given node
"""
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['dynamic_allocation'] = False
task.node.driver_info = driver_info
self.assertFalse(
common.is_dynamic_allocation_enabled(task.node)
)
def test_is_dynamic_allocation_enabled_none(self):
"""Ensure Dynamic Allocation is disabled when flag is None.
1) Set 'dynamic_allocation' flag as None on node's driver_info
2) Check Dynamic Allocation is disabled for the given node
"""
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['dynamic_allocation'] = None
task.node.driver_info = driver_info
self.assertFalse(
common.is_dynamic_allocation_enabled(task.node)
)
def test_is_dynamic_allocation_enabled_without_flag(self):
"""Ensure Dynamic Allocation is disabled when node doesnt't have flag.
1) Create a node without 'dynamic_allocation' flag
2) Check Dynamic Allocation is disabled for the given node
"""
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertFalse(
common.is_dynamic_allocation_enabled(task.node)
)

View File

@ -0,0 +1,144 @@
# 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 oslo_utils import importutils
from ironic.common import driver_factory
from ironic.drivers.modules.oneview import common
from ironic.drivers.modules.oneview import deploy
from ironic.drivers.modules.oneview import deploy_utils
from ironic import objects
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
oneview_models = importutils.try_import('oneview_client.models')
@mock.patch.object(common, 'get_oneview_client', spec_set=True, autospec=True)
class OneViewPeriodicTasks(db_base.DbTestCase):
def setUp(self):
super(OneViewPeriodicTasks, self).setUp()
self.config(manager_url='https://1.2.3.4', group='oneview')
self.config(username='user', group='oneview')
self.config(password='password', group='oneview')
mgr_utils.mock_the_extension_manager(driver='fake_oneview')
self.driver = driver_factory.get_driver('fake_oneview')
self.node = obj_utils.create_test_node(
self.context, driver='fake_oneview',
properties=db_utils.get_test_oneview_properties(),
driver_info=db_utils.get_test_oneview_driver_info(),
)
self.info = common.get_oneview_info(self.node)
@mock.patch.object(objects.Node, 'get')
@mock.patch.object(deploy_utils, 'is_node_in_use_by_oneview')
def test__periodic_check_nodes_taken_by_oneview(
self, mock_is_node_in_use_by_oneview, mock_get_node,
mock_get_ov_client
):
manager = mock.MagicMock(
spec=['iter_nodes', 'update_node', 'do_provisioning_action']
)
manager.iter_nodes.return_value = [
(self.node.uuid, 'fake_oneview')
]
mock_get_node.return_value = self.node
mock_is_node_in_use_by_oneview.return_value = True
class OneViewDriverDeploy(deploy.OneViewPeriodicTasks):
oneview_driver = 'fake_oneview'
oneview_driver_deploy = OneViewDriverDeploy()
oneview_driver_deploy._periodic_check_nodes_taken_by_oneview(
manager, self.context
)
self.assertTrue(manager.update_node.called)
self.assertTrue(manager.do_provisioning_action.called)
self.assertTrue(self.node.maintenance)
self.assertEqual(common.NODE_IN_USE_BY_ONEVIEW,
self.node.maintenance_reason)
@mock.patch.object(deploy_utils, 'is_node_in_use_by_oneview')
def test__periodic_check_nodes_freed_by_oneview(
self, mock_is_node_in_use_by_oneview, mock_get_ov_client
):
manager = mock.MagicMock(
spec=['iter_nodes', 'update_node', 'do_provisioning_action']
)
manager.iter_nodes.return_value = [
(self.node.uuid, 'fake_oneview',
common.NODE_IN_USE_BY_ONEVIEW)
]
mock_is_node_in_use_by_oneview.return_value = False
class OneViewDriverDeploy(deploy.OneViewPeriodicTasks):
oneview_driver = 'fake_oneview'
oneview_driver_deploy = OneViewDriverDeploy()
oneview_driver_deploy._periodic_check_nodes_freed_by_oneview(
manager, self.context
)
self.assertTrue(manager.update_node.called)
self.assertTrue(manager.do_provisioning_action.called)
self.assertFalse(self.node.maintenance)
self.assertIsNone(self.node.maintenance_reason)
@mock.patch.object(objects.Node, 'get')
def test__periodic_check_nodes_taken_on_cleanfail(
self, mock_get_node, mock_get_ov_client
):
driver_internal_info = {
'oneview_error': common.SERVER_HARDWARE_ALLOCATION_ERROR
}
manager = mock.MagicMock(
spec=['iter_nodes', 'update_node', 'do_provisioning_action']
)
manager.iter_nodes.return_value = [
(self.node.uuid, 'fake_oneview', driver_internal_info)
]
self.node.driver_internal_info = driver_internal_info
mock_get_node.return_value = self.node
class OneViewDriverDeploy(deploy.OneViewPeriodicTasks):
oneview_driver = 'fake_oneview'
oneview_driver_deploy = OneViewDriverDeploy()
oneview_driver_deploy._periodic_check_nodes_taken_on_cleanfail(
manager, self.context
)
self.assertTrue(manager.update_node.called)
self.assertTrue(manager.do_provisioning_action.called)
self.assertTrue(self.node.maintenance)
self.assertEqual(common.NODE_IN_USE_BY_ONEVIEW,
self.node.maintenance_reason)
self.assertDictEqual({}, self.node.driver_internal_info)

View File

@ -0,0 +1,349 @@
# 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 oslo_utils import importutils
from ironic.common import driver_factory
from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers.modules.oneview import common
from ironic.drivers.modules.oneview import deploy_utils
from ironic import objects
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
oneview_models = importutils.try_import('oneview_client.models')
@mock.patch.object(common, 'get_oneview_client', spec_set=True, autospec=True)
class OneViewDeployUtilsTestCase(db_base.DbTestCase):
def setUp(self):
super(OneViewDeployUtilsTestCase, self).setUp()
self.config(manager_url='https://1.2.3.4', group='oneview')
self.config(username='user', group='oneview')
self.config(password='password', group='oneview')
mgr_utils.mock_the_extension_manager(driver='fake_oneview')
self.driver = driver_factory.get_driver('fake_oneview')
self.node = obj_utils.create_test_node(
self.context, driver='fake_oneview',
properties=db_utils.get_test_oneview_properties(),
driver_info=db_utils.get_test_oneview_driver_info(),
)
self.info = common.get_oneview_info(self.node)
# Tests for prepare
def test_prepare_node_is_in_use_by_oneview(self, mock_get_ov_client):
"""`prepare` behavior when the node already has a Profile on OneView.
"""
oneview_client = mock_get_ov_client()
fake_server_hardware = oneview_models.ServerHardware()
fake_server_hardware.server_profile_uri = "/any/sp_uri"
oneview_client.get_server_hardware.return_value = fake_server_hardware
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['dynamic_allocation'] = True
task.node.driver_info = driver_info
task.node.provision_state = states.DEPLOYING
self.assertRaises(
exception.InstanceDeployFailure,
deploy_utils.prepare,
task
)
@mock.patch.object(objects.Node, 'save')
def test_prepare_node_is_successfuly_allocated_to_ironic(
self, mock_node_save, mock_get_ov_client
):
"""`prepare` behavior when the node is free from OneView standpoint.
"""
ov_client = mock_get_ov_client()
fake_sh = oneview_models.ServerHardware()
fake_sh.server_profile_uri = None
ov_client.get_server_hardware_by_uuid.return_value = fake_sh
with task_manager.acquire(self.context, self.node.uuid) as task:
task.node.provision_state = states.DEPLOYING
deploy_utils.prepare(task)
self.assertTrue(ov_client.clone_template_and_apply.called)
self.assertTrue(ov_client.get_server_profile_from_hardware)
# Tests for tear_down
def test_tear_down(self, mock_get_ov_client):
"""`tear_down` behavior when node already has Profile applied
"""
ov_client = mock_get_ov_client()
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['applied_server_profile_uri'] = \
'/rest/server-profiles/1234556789'
task.node.driver_info = driver_info
self.assertTrue(
'applied_server_profile_uri' in task.node.driver_info
)
deploy_utils.tear_down(task)
self.assertFalse(
'applied_server_profile_uri' in task.node.driver_info
)
self.assertTrue(
ov_client.delete_server_profile.called
)
# Tests for prepare_cleaning
@mock.patch.object(objects.Node, 'save')
def test_prepare_cleaning_when_node_does_not_have_sp_applied(
self, mock_node_save, mock_get_ov_client
):
"""`prepare_cleaning` behavior when node is free
"""
ov_client = mock_get_ov_client()
fake_sh = oneview_models.ServerHardware()
fake_sh.server_profile_uri = None
ov_client.get_server_hardware_by_uuid.return_value = fake_sh
with task_manager.acquire(self.context, self.node.uuid) as task:
deploy_utils.prepare_cleaning(task)
self.assertTrue(ov_client.clone_template_and_apply.called)
@mock.patch.object(objects.Node, 'save')
def test_prepare_cleaning_when_node_has_sp_applied(
self, mock_node_save, mock_get_ov_client
):
"""`prepare_cleaning` behavior when node already has Profile applied
"""
ov_client = mock_get_ov_client()
fake_sh = oneview_models.ServerHardware()
fake_sh.server_profile_uri = 'same/sp_applied'
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['applied_server_profile_uri'] = 'same/sp_applied'
task.node.driver_info = driver_info
deploy_utils.prepare_cleaning(task)
self.assertFalse(ov_client.clone_template_and_apply.called)
def test_prepare_cleaning_node_is_in_use_by_oneview(
self, mock_get_ov_client
):
"""`prepare_cleaning` behavior when node has Server Profile on OneView
"""
oneview_client = mock_get_ov_client()
fake_server_hardware = oneview_models.ServerHardware()
fake_server_hardware.server_profile_uri = "/any/sp_uri"
oneview_client.get_server_hardware.return_value = fake_server_hardware
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['dynamic_allocation'] = True
task.node.driver_info = driver_info
task.node.provision_state = states.DEPLOYING
self.assertRaises(
exception.NodeCleaningFailure,
deploy_utils.prepare_cleaning,
task
)
# Tests for tear_down_cleaning
def test_tear_down_cleaning(self, mock_get_ov_client):
"""Checks if Server Profile was deleted and its uri removed
"""
ov_client = mock_get_ov_client()
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['applied_server_profile_uri'] = \
'/rest/server-profiles/1234556789'
task.node.driver_info = driver_info
self.assertIn('applied_server_profile_uri', task.node.driver_info)
deploy_utils.tear_down_cleaning(task)
self.assertNotIn('applied_server_profile_uri',
task.node.driver_info)
self.assertTrue(ov_client.delete_server_profile.called)
# Tests for is_node_in_use_by_oneview
def test_is_node_in_use_by_oneview(self, mock_get_ov_client):
"""Node has a Server Profile applied by a third party user.
"""
fake_server_hardware = oneview_models.ServerHardware()
fake_server_hardware.server_profile_uri = "/any/sp_uri"
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['dynamic_allocation'] = True
task.node.driver_info = driver_info
self.assertTrue(
deploy_utils.is_node_in_use_by_oneview(task.node)
)
def test_is_node_in_use_by_oneview_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_oneview(task.node)
)
def test_is_node_in_use_by_oneview_same_server_profile_applied(
self, mock_get_ov_client
):
"""Node's Server Profile uri is the same 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['applied_server_profile_uri'] = 'same/applied_sp_uri/'
task.node.driver_info = driver_info
self.assertFalse(
deploy_utils.is_node_in_use_by_oneview(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.
"""
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
task.node.driver_info = driver_info
fake_server_profile = oneview_models.ServerProfile()
fake_server_profile.uri = 'any/applied_sp_uri/'
self.assertNotIn('applied_server_profile_uri',
task.node.driver_info)
deploy_utils._add_applied_server_profile_uri_field(
task.node,
fake_server_profile
)
self.assertIn('applied_server_profile_uri', task.node.driver_info)
# Tests for _del_applied_server_profile_uri_field
def test__del_applied_server_profile_uri_field(self, mock_get_ov_client):
"""Checks if applied_server_profile_uri was removed from driver_info.
"""
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/'
task.node.driver_info = driver_info
self.assertIn('applied_server_profile_uri', task.node.driver_info)
deploy_utils._del_applied_server_profile_uri_field(task.node)
self.assertNotIn('applied_server_profile_uri',
task.node.driver_info)
# Tests for _allocate_server_hardware_to_ironic
@mock.patch.object(objects.Node, 'save')
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.
"""
ov_client = mock_get_ov_client.return_value
fake_sh = oneview_models.ServerHardware()
fake_sh.server_profile_uri = None
ov_client.get_server_hardware_by_uuid.return_value = fake_sh
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(
task.node, 'serverProfileName'
)
self.assertTrue(ov_client.clone_template_and_apply.called)
self.assertIn('applied_server_profile_uri', task.node.driver_info)
@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(
self, mock_delete_applied_sp, mock_node_save, mock_get_ov_client
):
"""Tests server profile allocation when applied_server_profile_uri exists.
This test consider that no Server Profile is applied on the Server
Hardware but the applied_server_profile_uri remained on the node. Thus,
the conductor should remove the value and apply a new server profile to
use the node.
"""
ov_client = mock_get_ov_client.return_value
fake_sh = oneview_models.ServerHardware()
fake_sh.server_profile_uri = None
ov_client.get_server_hardware_by_uuid.return_value = fake_sh
mock_get_ov_client.return_value = ov_client
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
driver_info['applied_server_profile_uri'] = 'any/applied_sp_uri/'
task.node.driver_info = driver_info
deploy_utils._allocate_server_hardware_to_ironic(
task.node, 'serverProfileName'
)
self.assertTrue(mock_delete_applied_sp.called)
# Tests for _deallocate_server_hardware_from_ironic
@mock.patch.object(objects.Node, 'save')
def test__deallocate_server_hardware_from_ironic(
self, mock_node_save, mock_get_ov_client
):
ov_client = mock_get_ov_client.return_value
fake_sh = oneview_models.ServerHardware()
fake_sh.server_profile_uri = 'any/applied_sp_uri/'
ov_client.get_server_hardware_by_uuid.return_value = fake_sh
mock_get_ov_client.return_value = ov_client
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_info = task.node.driver_info
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)
self.assertTrue(ov_client.delete_server_profile.called)
self.assertTrue(
'applied_server_profile_uri' not in task.node.driver_info
)

View File

@ -126,6 +126,8 @@ ONEVIEWCLIENT_SPEC = (
'client',
'states',
'exceptions',
'models',
'utils',
)
ONEVIEWCLIENT_CLIENT_CLS_SPEC = (

View File

@ -126,8 +126,10 @@ if not oneview_client:
ONEVIEW_ERROR='error')
sys.modules['oneview_client.states'] = states
sys.modules['oneview_client.exceptions'] = oneview_client.exceptions
sys.modules['oneview_client.utils'] = oneview_client.utils
oneview_client.exceptions.OneViewException = type('OneViewException',
(Exception,), {})
sys.modules['oneview_client.models'] = oneview_client.models
if 'ironic.drivers.oneview' in sys.modules:
six.moves.reload_module(sys.modules['ironic.drivers.modules.oneview'])

View File

@ -0,0 +1,5 @@
---
features:
- Add Dynamic Allocation feature for the OneView drivers.
deprecations:
- Deprecates pre-allocation feature for the OneView drivers.