Add engine driver framework base driver.

Add BaseEngineDriver class, copy current ironic operations to
IronicDriver, modify mananger and base manger to separate driver
interface and manger.

Co-Authored-By: Zhenguo Niu <Niu.ZGlinux@gmail.com>

Change-Id: I1f503f925f24975a20ec5892b4aa107ae194fbca
Implements: blueprint engine-driver-framework
This commit is contained in:
WosunOoO 2017-02-10 16:11:05 +08:00 committed by Zhenguo Niu
parent bce8454175
commit 8a0046c784
16 changed files with 714 additions and 450 deletions

1
.gitignore vendored
View File

@ -34,6 +34,7 @@ nosetests.xml
*.mo
# Mr Developer
.idea
.mr.developer.cfg
.project
.pydevproject

View File

@ -36,7 +36,6 @@ from mogan.common.i18n import _
from mogan.common.i18n import _LW
from mogan.common import policy
from mogan.common import states
from mogan.engine.baremetal import ironic_states as ir_states
from mogan import network
from mogan import objects
@ -60,7 +59,7 @@ class InstanceStates(base.APIBase):
@classmethod
def sample(cls):
sample = cls(power_state=ir_states.POWER_ON,
sample = cls(power_state=states.POWER_ON,
status=states.ACTIVE, locked=False)
return sample
@ -172,13 +171,13 @@ class InstanceStatesController(InstanceControllerBase):
"""
# Currently we only support rebuild target
if target not in (ir_states.REBUILD,):
if target not in (states.REBUILD,):
raise exception.InvalidActionParameterValue(
value=target, action="provision",
instance=instance_uuid)
rpc_instance = self._resource or self._get_resource(instance_uuid)
if target == ir_states.REBUILD:
if target == states.REBUILD:
try:
pecan.request.engine_api.rebuild(pecan.request.context,
rpc_instance)

View File

@ -49,6 +49,17 @@ POWER_ACTION_MAP = {
}
#####################
# Provisioning states
#####################
REBUILD = 'rebuild'
""" Node is to be rebuilt.
This is not used as a state, but rather as a "verb" when changing the node's
provision_state via the REST API.
"""
#################
# Instance states
#################

View File

@ -87,3 +87,10 @@ def make_pretty_name(method):
except AttributeError:
pass
return ".".join(meth_pieces)
def check_isinstance(obj, cls):
"""Checks that obj is of type cls, and lets PyLint infer types."""
if isinstance(obj, cls):
return obj
raise Exception(_('Expected object of type: %s') % (str(cls)))

View File

@ -57,6 +57,10 @@ opts = [
default=600,
help=_("Interval to sync maintenance states between the "
"database and Ironic, in seconds.")),
cfg.StrOpt('engine_driver',
default='ironic.IronicDriver',
choices=['ironic.IronicDriver'],
help=_("Which driver to use, default to ironic driver."))
]

View File

@ -0,0 +1,198 @@
# Copyright 2017 Hengfeng Bank Co.,Ltd.
# 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.
"""Base engine driver functionality."""
import sys
from oslo_log import log as logging
from oslo_utils import importutils
from mogan.common.i18n import _LE
from mogan.common.i18n import _LI
from mogan.common import utils
LOG = logging.getLogger(__name__)
class BaseEngineDriver(object):
"""Base class for mogan baremetal drivers.
"""
def __init__(self):
"""Add init staff here.
"""
def init_host(self, host):
"""Initialize anything that is necessary for the engine driver to
function.
"""
def get_available_node_list(self):
"""Return all available nodes.
"""
raise NotImplementedError()
def get_maintenance_node_list(self):
"""Return maintenance nodes.
"""
raise NotImplementedError()
def get_nodes_power_state(self):
"""Return nodes power state.
"""
raise NotImplementedError()
def get_port_list(self):
"""Return all ports.
"""
raise NotImplementedError()
def get_portgroup_list(self):
"""Return all portgroups.
"""
raise NotImplementedError()
def get_node_by_instance(self, instance_uuid):
"""Return node info associated with certain instance.
:param instance_uuid: uuid of mogan instance to get node.
"""
raise NotImplementedError()
def get_power_state(self, instance_uuid):
"""Return a node's power state by passing instance uuid.
:param instance_uuid: mogan instance uuid to get power state.
"""
raise NotImplementedError()
def set_power_state(self, node_uuid, state):
"""Set a node's power state.
:param node_uuid: node id to change power state.
:param state: mogan states to change to.
"""
raise NotImplementedError()
def get_ports_from_node(self, node_uuid, detail=True):
"""Get a node's ports info.
:param node_uuid: node id to get ports info.
:param detail: whether to get detailed info of all the ports,
default to False.
"""
raise NotImplementedError()
def plug_vif(self, node_interface, neutron_port_id):
"""Plug a neutron port to a baremetal port.
:param node_interface: bare metal interface to plug neutron port.
:param neutron_port_id: neutron port id to plug.
"""
raise NotImplementedError()
def unplug_vif(self, node_interface):
"""Unplug a neutron port from a baremetal port.
:param node_interface: bare metal interface id to unplug port.
"""
raise NotImplementedError()
def set_instance_info(self, instance, node):
"""Associate the node with an instance.
:param instance: mogan instance object.
:param node: node object.
"""
raise NotImplementedError()
def unset_instance_info(self, instance):
"""Disassociate the node with an instance.
:param instance: mogan instance object.
"""
raise NotImplementedError()
def do_node_deploy(self, instance):
"""Trigger node deploy process.
:param instance: instance to deploy.
"""
raise NotImplementedError()
def get_node(self, node_uuid):
"""Get node info by node id.
:param node_uuid: node id to get info.
"""
raise NotImplementedError()
def destroy(self, instance):
"""Trigger node destroy process.
:param instance: the instance to destory.
"""
raise NotImplementedError()
def validate_node(self, node_uuid):
"""Validate whether the node's driver has enough information to
manage the Node.
:param node_uuid: node id to validate.
"""
raise NotImplementedError()
def is_node_unprovision(self, node):
"""Validate whether the node is in unprovision state.
:param node: node object to get state.
"""
raise NotImplementedError()
def do_node_rebuild(self, instance):
"""Trigger node deploy process.
:param instance: instance to rebuild.
"""
raise NotImplementedError()
def load_engine_driver(engine_driver):
"""Load a engine driver module.
Load the engine driver module specified by the engine_driver
configuration option or, if supplied, the driver name supplied as an
argument.
:param engine_driver: a engine driver name to override the config opt
:returns: a EngineDriver instance
"""
if not engine_driver:
LOG.error(_LE("Engine driver option required, but not specified"))
sys.exit(1)
LOG.info(_LI("Loading engine driver '%s'"), engine_driver)
try:
driver = importutils.import_object(
'mogan.engine.baremetal.%s' % engine_driver)
return utils.check_isinstance(driver, BaseEngineDriver)
except ImportError:
LOG.exception(_LE("Unable to load the baremetal driver"))
sys.exit(1)

View File

@ -1,197 +0,0 @@
# Copyright 2016 Huawei Technologies Co.,LTD.
# 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 ironicclient import exceptions as client_e
from oslo_log import log as logging
from mogan.common.i18n import _LE
from mogan.common.i18n import _LW
from mogan.common import states
from mogan.engine.baremetal import ironic_states
LOG = logging.getLogger(__name__)
_NODE_FIELDS = ('uuid', 'power_state', 'target_power_state', 'provision_state',
'target_provision_state', 'last_error', 'maintenance',
'properties', 'instance_uuid')
_POWER_STATE_MAP = {
ironic_states.POWER_ON: states.POWER_ON,
ironic_states.NOSTATE: states.NOSTATE,
ironic_states.POWER_OFF: states.POWER_OFF,
}
def map_power_state(state):
try:
return _POWER_STATE_MAP[state]
except KeyError:
LOG.warning(_LW("Power state %s not found."), state)
return states.NOSTATE
def get_power_state(ironicclient, instance_uuid):
try:
node = ironicclient.call('node.get_by_instance_uuid',
instance_uuid, fields=('power_state',))
return map_power_state(node.power_state)
except client_e.NotFound:
return map_power_state(ironic_states.NOSTATE)
def get_ports_from_node(ironicclient, node_uuid, detail=False):
"""List the MAC addresses and the port types from a node."""
ports = ironicclient.call("node.list_ports", node_uuid, detail=detail)
portgroups = ironicclient.call("portgroup.list", node=node_uuid,
detail=detail)
return ports + portgroups
def plug_vif(ironicclient, ironic_port_id, port_id):
patch = [{'op': 'add',
'path': '/extra/vif_port_id',
'value': port_id}]
ironicclient.call("port.update", ironic_port_id, patch)
def unplug_vif(ironicclient, ironic_port_id):
patch = [{'op': 'remove',
'path': '/extra/vif_port_id'}]
try:
ironicclient.call("port.update", ironic_port_id, patch)
except client_e.BadRequest:
pass
def set_instance_info(ironicclient, instance, node):
patch = []
# Associate the node with an instance
patch.append({'path': '/instance_uuid', 'op': 'add',
'value': instance.uuid})
# Add the required fields to deploy a node.
patch.append({'path': '/instance_info/image_source', 'op': 'add',
'value': instance.image_uuid})
# TODO(zhenguo) Add partition support
patch.append({'path': '/instance_info/root_gb', 'op': 'add',
'value': str(node.properties.get('local_gb', 0))})
ironicclient.call("node.update", instance.node_uuid, patch)
def unset_instance_info(ironicclient, instance):
patch = [{'path': '/instance_info', 'op': 'remove'},
{'path': '/instance_uuid', 'op': 'remove'}]
ironicclient.call("node.update", instance.node_uuid, patch)
def do_node_deploy(ironicclient, node_uuid):
# trigger the node deploy
ironicclient.call("node.set_provision_state", node_uuid,
ironic_states.ACTIVE)
def do_node_rebuild(ironicclient, node_uuid):
# trigger the node rebuild
ironicclient.call("node.set_provision_state", node_uuid,
ironic_states.REBUILD)
def get_node_by_instance(ironicclient, instance_uuid, fields=None):
if fields is None:
fields = _NODE_FIELDS
return ironicclient.call('node.get_by_instance_uuid',
instance_uuid, fields=fields)
def get_node(ironicclient, node_uuid, fields=None):
if fields is None:
fields = _NODE_FIELDS
"""Get a node by its UUID."""
return ironicclient.call('node.get', node_uuid, fields=fields)
def destroy_node(ironicclient, node_uuid):
# trigger the node destroy
ironicclient.call("node.set_provision_state", node_uuid,
ironic_states.DELETED)
def validate_node(ironicclient, node_uuid):
return ironicclient.call("node.validate", node_uuid)
def get_node_list(ironicclient, **kwargs):
"""Helper function to return the list of nodes.
If unable to connect ironic server, an empty list is returned.
:returns: a list of raw node from ironic
"""
try:
node_list = ironicclient.call("node.list", **kwargs)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get nodes from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
node_list = []
return node_list
def get_port_list(ironicclient, **kwargs):
"""Helper function to return the list of ports.
If unable to connect ironic server, an empty list is returned.
:returns: a list of raw port from ironic
"""
try:
port_list = ironicclient.call("port.list", **kwargs)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get ports from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
port_list = []
return port_list
def get_portgroup_list(ironicclient, **kwargs):
"""Helper function to return the list of portgroups.
If unable to connect ironic server, an empty list is returned.
:returns: a list of raw port from ironic
"""
try:
portgroup_list = ironicclient.call("portgroup.list", **kwargs)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get portgroups from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
portgroup_list = []
return portgroup_list
def set_power_state(ironicclient, node_uuid, state):
if state == "soft_off":
ironicclient.call("node.set_power_state",
node_uuid, "off", soft=True)
elif state == "soft_reboot":
ironicclient.call("node.set_power_state",
node_uuid, "reboot", soft=True)
else:
ironicclient.call("node.set_power_state", node_uuid, state)

View File

@ -0,0 +1,18 @@
# Copyright 2016 Huawei Technologies Co.,LTD.
# 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 mogan.engine.baremetal.ironic import driver
IronicDriver = driver.IronicDriver

View File

@ -0,0 +1,389 @@
# Copyright 2016 Huawei Technologies Co.,LTD.
# 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 ironicclient import exc as ironic_exc
from ironicclient import exceptions as client_e
from oslo_log import log as logging
from oslo_service import loopingcall
import six
from mogan.common import exception
from mogan.common.i18n import _
from mogan.common.i18n import _LE
from mogan.common.i18n import _LI
from mogan.common.i18n import _LW
from mogan.common import ironic
from mogan.common import states
from mogan.conf import CONF
from mogan.engine.baremetal import driver as base_driver
from mogan.engine.baremetal.ironic import ironic_states
LOG = logging.getLogger(__name__)
_POWER_STATE_MAP = {
ironic_states.POWER_ON: states.POWER_ON,
ironic_states.NOSTATE: states.NOSTATE,
ironic_states.POWER_OFF: states.POWER_OFF,
}
_UNPROVISION_STATES = (ironic_states.ACTIVE, ironic_states.DEPLOYFAIL,
ironic_states.ERROR, ironic_states.DEPLOYWAIT,
ironic_states.DEPLOYING)
_NODE_FIELDS = ('uuid', 'power_state', 'target_power_state', 'provision_state',
'target_provision_state', 'last_error', 'maintenance',
'properties', 'instance_uuid')
class IronicDriver(base_driver.BaseEngineDriver):
def __init__(self):
super(IronicDriver, self).__init__()
self.ironicclient = ironic.IronicClientWrapper()
def map_power_state(self, state):
try:
return _POWER_STATE_MAP[state]
except KeyError:
LOG.warning(_LW("Power state %s not found."), state)
return states.NOSTATE
def get_power_state(self, instance_uuid):
try:
node = self.ironicclient.call('node.get_by_instance_uuid',
instance_uuid,
fields=('power_state',))
return self.map_power_state(node.power_state)
except client_e.NotFound:
return self.map_power_state(ironic_states.NOSTATE)
def get_ports_from_node(self, node_uuid, detail=True):
"""List the MAC addresses and the port types from a node."""
ports = self.ironicclient.call("node.list_ports",
node_uuid, detail=detail)
portgroups = self.ironicclient.call("portgroup.list", node=node_uuid,
detail=detail)
return ports + portgroups
def plug_vif(self, ironic_port_id, port_id):
patch = [{'op': 'add',
'path': '/extra/vif_port_id',
'value': port_id}]
self.ironicclient.call("port.update", ironic_port_id, patch)
def unplug_vif(self, node_interface):
patch = [{'op': 'remove',
'path': '/extra/vif_port_id'}]
try:
if 'vif_port_id' in node_interface.extra:
self.ironicclient.call("port.update",
node_interface.uuid, patch)
except client_e.BadRequest:
pass
def set_instance_info(self, instance, node):
patch = list()
# Associate the node with an instance
patch.append({'path': '/instance_uuid', 'op': 'add',
'value': instance.uuid})
# Add the required fields to deploy a node.
patch.append({'path': '/instance_info/image_source', 'op': 'add',
'value': instance.image_uuid})
# TODO(zhenguo) Add partition support
patch.append({'path': '/instance_info/root_gb', 'op': 'add',
'value': str(node.properties.get('local_gb', 0))})
self.ironicclient.call("node.update", instance.node_uuid, patch)
def unset_instance_info(self, instance):
patch = [{'path': '/instance_info', 'op': 'remove'},
{'path': '/instance_uuid', 'op': 'remove'}]
try:
self.ironicclient.call("node.update", instance.node_uuid, patch)
except ironic_exc.BadRequest as e:
raise exception.Invalid(msg=six.text_type(e))
def do_node_deploy(self, instance):
# trigger the node deploy
self.ironicclient.call("node.set_provision_state", instance.node_uuid,
ironic_states.ACTIVE)
timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_active,
instance)
timer.start(interval=CONF.ironic.api_retry_interval).wait()
def _wait_for_active(self, instance):
"""Wait for the node to be marked as ACTIVE in Ironic."""
instance.refresh()
if instance.status in (states.DELETING, states.ERROR, states.DELETED):
raise exception.InstanceDeployFailure(
_("Instance %s provisioning was aborted") % instance.uuid)
node = self.get_node_by_instance(instance.uuid)
LOG.debug('Current ironic node state is %s', node.provision_state)
if node.provision_state == ironic_states.ACTIVE:
# job is done
LOG.debug("Ironic node %(node)s is now ACTIVE",
dict(node=node.uuid))
raise loopingcall.LoopingCallDone()
if node.target_provision_state in (ironic_states.DELETED,
ironic_states.AVAILABLE):
# ironic is trying to delete it now
raise exception.InstanceNotFound(instance_id=instance.uuid)
if node.provision_state in (ironic_states.NOSTATE,
ironic_states.AVAILABLE):
# ironic already deleted it
raise exception.InstanceNotFound(instance_id=instance.uuid)
if node.provision_state == ironic_states.DEPLOYFAIL:
# ironic failed to deploy
msg = (_("Failed to provision instance %(inst)s: %(reason)s")
% {'inst': instance.uuid, 'reason': node.last_error})
raise exception.InstanceDeployFailure(msg)
def get_node_by_instance(self, instance_uuid):
fields = _NODE_FIELDS
try:
return self.ironicclient.call('node.get_by_instance_uuid',
instance_uuid, fields=fields)
except ironic_exc.NotFound:
raise exception.NotFound
def get_node(self, node_uuid, fields=None):
if fields is None:
fields = _NODE_FIELDS
"""Get a node by its UUID."""
return self.ironicclient.call('node.get', node_uuid, fields=fields)
def destroy(self, instance):
node_uuid = instance.node_uuid
# trigger the node destroy
try:
self.ironicclient.call("node.set_provision_state", node_uuid,
ironic_states.DELETED)
except Exception as e:
# if the node is already in a deprovisioned state, continue
# This should be fixed in Ironic.
# TODO(deva): This exception should be added to
# python-ironicclient and matched directly,
# rather than via __name__.
if getattr(e, '__name__', None) != 'InstanceDeployFailure':
raise
# using a dict because this is modified in the local method
data = {'tries': 0}
def _wait_for_provision_state():
try:
node = self.get_node_by_instance(instance.uuid)
except exception.NotFound:
LOG.debug("Instance already removed from Ironic",
instance=instance)
raise loopingcall.LoopingCallDone()
LOG.debug('Current ironic node state is %s', node.provision_state)
if node.provision_state in (ironic_states.NOSTATE,
ironic_states.CLEANING,
ironic_states.CLEANWAIT,
ironic_states.CLEANFAIL,
ironic_states.AVAILABLE):
# From a user standpoint, the node is unprovisioned. If a node
# gets into CLEANFAIL state, it must be fixed in Ironic, but we
# can consider the instance unprovisioned.
LOG.debug("Ironic node %(node)s is in state %(state)s, "
"instance is now unprovisioned.",
dict(node=node.uuid, state=node.provision_state),
instance=instance)
raise loopingcall.LoopingCallDone()
if data['tries'] >= CONF.ironic.api_max_retries + 1:
msg = (_("Error destroying the instance on node %(node)s. "
"Provision state still '%(state)s'.")
% {'state': node.provision_state,
'node': node.uuid})
LOG.error(msg)
raise exception.MoganException(msg)
else:
data['tries'] += 1
# wait for the state transition to finish
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_provision_state)
timer.start(interval=CONF.ironic.api_retry_interval).wait()
LOG.info(_LI('Successfully destroyed Ironic node %s'), node_uuid)
def validate_node(self, node_uuid):
return self.ironicclient.call("node.validate", node_uuid)
def get_available_node_list(self):
"""Helper function to return the list of nodes.
If unable to connect ironic server, an empty list is returned.
:returns: a list of raw node from ironic
"""
params = {
'maintenance': False,
'detail': True,
'provision_state': ironic_states.AVAILABLE,
'associated': False,
'limit': 0
}
try:
node_list = self.ironicclient.call("node.list", **params)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get nodes from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
node_list = []
return node_list
def get_maintenance_node_list(self):
"""Helper function to return the list of maintenance nodes.
If unable to connect ironic server, an empty list is returned.
:returns: a list of maintenance node from ironic
"""
params = {
'associated': True,
'fields': ('instance_uuid', 'maintenance'),
'limit': 0
}
try:
node_list = self.ironicclient.call("node.list", **params)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get nodes from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
node_list = []
return node_list
def get_node_power_states(self):
"""Helper function to return the node power states.
If unable to connect ironic server, an empty list is returned.
:returns: a list of node power states from ironic
"""
params = {
'maintenance': False,
'associated': True,
'fields': ('instance_uuid', 'power_state', 'target_power_state'),
'limit': 0
}
try:
node_list = self.ironicclient.call("node.list", **params)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get nodes from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
node_list = []
return node_list
def get_port_list(self):
"""Helper function to return the list of ports.
If unable to connect ironic server, an empty list is returned.
:returns: a list of raw port from ironic
"""
params = {
'limit': 0,
'fields': ('uuid', 'node_uuid', 'extra', 'address')
}
try:
port_list = self.ironicclient.call("port.list", **params)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get ports from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
port_list = []
return port_list
def get_portgroup_list(self, **kwargs):
"""Helper function to return the list of portgroups.
If unable to connect ironic server, an empty list is returned.
:returns: a list of raw port from ironic
"""
params = {
'limit': 0,
'fields': ('uuid', 'node_uuid', 'extra', 'address')
}
try:
portgroup_list = self.ironicclient.call("portgroup.list", **params)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get portgroups from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
portgroup_list = []
return portgroup_list
def set_power_state(self, instance, state):
if state == "soft_off":
self.ironicclient.call("node.set_power_state",
instance.node_uuid, "off", soft=True)
elif state == "soft_reboot":
self.ironicclient.call("node.set_power_state",
instance.node_uuid, "reboot", soft=True)
else:
self.ironicclient.call("node.set_power_state",
instance.node_uuid, state)
timer = loopingcall.FixedIntervalLoopingCall(
self._wait_for_power_state, instance)
timer.start(interval=CONF.ironic.api_retry_interval).wait()
def is_node_unprovision(self, node):
return node.provision_state in _UNPROVISION_STATES
def _wait_for_power_state(self, instance):
"""Wait for the node to complete a power state change."""
try:
node = self.get_node_by_instance(self.ironicclient,
instance.uuid)
except exception.NotFound:
LOG.debug("While waiting for node to complete a power state "
"change, it dissociate with the instance.",
instance=instance)
raise exception.NodeNotFound()
if node.target_power_state == ironic_states.NOSTATE:
raise loopingcall.LoopingCallDone()
def do_node_rebuild(self, instance):
# trigger the node rebuild
try:
self.ironicclient.call("node.set_provision_state",
instance.node_uuid,
ironic_states.REBUILD)
except (ironic_exc.InternalServerError,
ironic_exc.BadRequest) as e:
msg = (_("Failed to request Ironic to rebuild instance "
"%(inst)s: %(reason)s") % {'inst': instance.uuid,
'reason': six.text_type(e)})
raise exception.InstanceDeployFailure(msg)
# Although the target provision state is REBUILD, it will actually go
# to ACTIVE once the redeploy is finished.
timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_active,
instance)
timer.start(interval=CONF.ironic.api_retry_interval).wait()

View File

@ -20,9 +20,9 @@ from oslo_service import periodic_task
from oslo_utils import importutils
from mogan.common.i18n import _
from mogan.common import ironic
from mogan.conf import CONF
from mogan.db import api as dbapi
from mogan.engine.baremetal import driver
from mogan.engine import rpcapi
from mogan import network
@ -39,7 +39,7 @@ class BaseEngineManager(periodic_task.PeriodicTasks):
self.network_api = network.API()
scheduler_driver = CONF.scheduler.scheduler_driver
self.scheduler = importutils.import_object(scheduler_driver)
self.ironicclient = ironic.IronicClientWrapper()
self.driver = driver.load_engine_driver(CONF.engine.engine_driver)
self.engine_rpcapi = rpcapi.EngineAPI()
self._sync_power_pool = greenpool.GreenPool(
size=CONF.engine.sync_power_state_pool_size)

View File

@ -27,9 +27,9 @@ from mogan.common.i18n import _
from mogan.common.i18n import _LE
from mogan.common.i18n import _LI
from mogan.common import utils
from mogan.engine.baremetal import ironic
from mogan import objects
LOG = logging.getLogger(__name__)
ACTION = 'instance:create'
@ -131,13 +131,13 @@ class OnFailureRescheduleTask(flow_utils.MoganTask):
class SetInstanceInfoTask(flow_utils.MoganTask):
"""Set instance info to ironic node and validate it."""
"""Set instance info to baremetal node and validate it."""
def __init__(self, ironicclient):
def __init__(self, driver):
requires = ['instance', 'context']
super(SetInstanceInfoTask, self).__init__(addons=[ACTION],
requires=requires)
self.ironicclient = ironicclient
self.driver = driver
# These exception types will trigger the instance info to be cleaned.
self.instance_info_cleaned_exc_types = [
exception.ValidationError,
@ -146,11 +146,11 @@ class SetInstanceInfoTask(flow_utils.MoganTask):
]
def execute(self, context, instance):
node = ironic.get_node(self.ironicclient, instance.node_uuid)
ironic.set_instance_info(self.ironicclient, instance, node)
node = self.driver.get_node(instance.node_uuid)
self.driver.set_instance_info(instance, node)
# validate we are ready to do the deploy
validate_chk = ironic.validate_node(self.ironicclient,
instance.node_uuid)
validate_chk = self.driver.validate_node(instance.node_uuid)
if (not validate_chk.deploy.get('result')
or not validate_chk.power.get('result')):
raise exception.ValidationError(_(
@ -161,13 +161,13 @@ class SetInstanceInfoTask(flow_utils.MoganTask):
'power': validate_chk.power})
def revert(self, context, result, flow_failures, instance, **kwargs):
# Check if we have a cause which need to clean up ironic node
# Check if we have a cause which need to clean up baremetal node
# instance info.
for failure in flow_failures.values():
if failure.check(*self.instance_info_cleaned_exc_types):
LOG.debug("Instance %s: cleaning up node instance info",
instance.uuid)
ironic.unset_instance_info(self.ironicclient, instance)
self.driver.unset_instance_info(instance)
return True
return False
@ -191,31 +191,31 @@ class BuildNetworkTask(flow_utils.MoganTask):
def _build_networks(self, context, instance, requested_networks):
node_uuid = instance.node_uuid
ironic_ports = ironic.get_ports_from_node(self.manager.ironicclient,
node_uuid,
detail=True)
bm_ports = self.manager.driver.get_ports_from_node(node_uuid,
detail=True)
LOG.debug(_('Find ports %(ports)s for node %(node)s') %
{'ports': ironic_ports, 'node': node_uuid})
if len(requested_networks) > len(ironic_ports):
{'ports': bm_ports, 'node': node_uuid})
if len(requested_networks) > len(bm_ports):
raise exception.InterfacePlugException(_(
"Ironic node: %(id)s virtual to physical interface count"
" mismatch"
" (Vif count: %(vif_count)d, Pif count: %(pif_count)d)")
% {'id': instance.node_uuid,
'vif_count': len(requested_networks),
'pif_count': len(ironic_ports)})
'pif_count': len(bm_ports)})
nics_obj = objects.InstanceNics(context)
for vif in requested_networks:
for pif in ironic_ports:
for pif in bm_ports:
# Match the specified port type with physical interface type
if vif.get('port_type') == pif.extra.get('port_type'):
try:
port = self.manager.network_api.create_port(
context, vif['net_id'], pif.address, instance.uuid)
port_dict = port['port']
ironic.plug_vif(self.manager.ironicclient, pif.uuid,
port_dict['id'])
self.manager.driver.plug_vif(pif.uuid, port_dict['id'])
nic_dict = {'port_id': port_dict['id'],
'network_id': port_dict['network_id'],
'mac_address': port_dict['mac_address'],
@ -224,6 +224,7 @@ class BuildNetworkTask(flow_utils.MoganTask):
'instance_uuid': instance.uuid}
nics_obj.objects.append(objects.InstanceNic(
context, **nic_dict))
except Exception:
# Set nics here, so we can clean up the
# created networks during reverting.
@ -273,11 +274,7 @@ class CreateInstanceTask(flow_utils.MoganTask):
]
def _build_instance(self, context, instance):
ironic.do_node_deploy(self.manager.ironicclient, instance.node_uuid)
timer = loopingcall.FixedIntervalLoopingCall(
self.manager.wait_for_active, instance)
timer.start(interval=CONF.ironic.api_retry_interval).wait()
self.manager.driver.do_node_deploy(instance)
LOG.info(_LI('Successfully provisioned Ironic node %s'),
instance.node_uuid)
@ -289,8 +286,7 @@ class CreateInstanceTask(flow_utils.MoganTask):
for failure in flow_failures.values():
if failure.check(*self.instance_cleaned_exc_types):
LOG.debug("Instance %s: destroy ironic node", instance.uuid)
ironic.destroy_node(self.manager.ironicclient,
instance.node_uuid)
self.manger.driver.destroy(instance)
return True
return False
@ -304,8 +300,8 @@ def get_flow(context, manager, instance, requested_networks, request_spec,
This flow will do the following:
1. Schedule a node to create instance
2. Set instance info to ironic node and validate it's ready to deploy
3. Build networks for the instance and set port id back to ironic port
2. Set instance info to baremetal node and validate it's ready to deploy
3. Build networks for the instance and set port id back to baremetal port
4. Do node deploy and handle errors.
"""
@ -325,7 +321,7 @@ def get_flow(context, manager, instance, requested_networks, request_spec,
instance_flow.add(ScheduleCreateInstanceTask(manager),
OnFailureRescheduleTask(manager.engine_rpcapi),
SetInstanceInfoTask(manager.ironicclient),
SetInstanceInfoTask(manager.driver),
BuildNetworkTask(manager),
CreateInstanceTask(manager))

View File

@ -15,13 +15,12 @@
import threading
from ironicclient import exc as ironic_exc
from oslo_log import log
import oslo_messaging as messaging
from oslo_service import loopingcall
from oslo_service import periodic_task
from oslo_utils import timeutils
import six
import sys
from mogan.common import exception
from mogan.common import flow_utils
@ -32,8 +31,6 @@ from mogan.common.i18n import _LW
from mogan.common import states
from mogan.common import utils
from mogan.conf import CONF
from mogan.engine.baremetal import ironic
from mogan.engine.baremetal import ironic_states
from mogan.engine import base_manager
from mogan.engine.flows import create_instance
from mogan.notifications import base as notifications
@ -42,10 +39,6 @@ from mogan.objects import fields
LOG = log.getLogger(__name__)
_UNPROVISION_STATES = (ironic_states.ACTIVE, ironic_states.DEPLOYFAIL,
ironic_states.ERROR, ironic_states.DEPLOYWAIT,
ironic_states.DEPLOYING)
class EngineManager(base_manager.BaseEngineManager):
"""Mogan Engine manager main class."""
@ -57,16 +50,9 @@ class EngineManager(base_manager.BaseEngineManager):
def _refresh_cache(self):
node_cache = {}
nodes = ironic.get_node_list(self.ironicclient, detail=True,
maintenance=False,
provision_state=ironic_states.AVAILABLE,
associated=False, limit=0)
ports = ironic.get_port_list(self.ironicclient, limit=0,
fields=('uuid', 'node_uuid', 'extra',
'address'))
portgroups = ironic.get_portgroup_list(self.ironicclient, limit=0,
fields=('uuid', 'node_uuid',
'extra', 'address'))
nodes = self.driver.get_available_node_list()
ports = self.driver.get_port_list()
portgroups = self.driver.get_portgroup_list()
ports += portgroups
for node in nodes:
# Add ports to the associated node
@ -90,14 +76,9 @@ class EngineManager(base_manager.BaseEngineManager):
# Only fetching the necessary fields, will skip synchronizing if
# target_power_state is not None.
node_fields = ('instance_uuid', 'power_state', 'target_power_state')
try:
nodes = ironic.get_node_list(self.ironicclient,
maintenance=False,
associated=True,
fields=node_fields,
limit=0)
nodes = self.driver.get_nodes_power_state()
except Exception as e:
LOG.warning(
_LW("Failed to retrieve node list when synchronizing power "
@ -202,14 +183,8 @@ class EngineManager(base_manager.BaseEngineManager):
def _sync_maintenance_states(self, context):
"""Align maintenance states between the database and the hypervisor."""
# Only fetching the necessary fields
node_fields = ('instance_uuid', 'maintenance')
try:
nodes = ironic.get_node_list(self.ironicclient,
associated=True,
fields=node_fields,
limit=0)
nodes = self.driver.get_maintenance_node_list()
except Exception as e:
LOG.warning(
_LW("Failed to retrieve node list when synchronizing "
@ -264,16 +239,16 @@ class EngineManager(base_manager.BaseEngineManager):
for port in ports:
self.network_api.delete_port(context, port, instance.uuid)
ironic_ports = ironic.get_ports_from_node(self.ironicclient,
instance.node_uuid,
detail=True)
for pif in ironic_ports:
if 'vif_port_id' in pif.extra:
ironic.unplug_vif(self.ironicclient, pif.uuid)
bm_interface = self.driver.get_ports_from_node(instance.node_uuid)
for pif in bm_interface:
self.driver.unplug_vif(pif)
def _destroy_instance(self, context, instance):
try:
ironic.destroy_node(self.ironicclient, instance.node_uuid)
self.driver.destroy(instance)
except exception.MoganException as e:
six.reraise(type(e), e, sys.exc_info()[2])
except Exception as e:
# if the node is already in a deprovisioned state, continue
# This should be fixed in Ironic.
@ -283,92 +258,19 @@ class EngineManager(base_manager.BaseEngineManager):
if getattr(e, '__name__', None) != 'InstanceDeployFailure':
raise
# using a dict because this is modified in the local method
data = {'tries': 0}
def _wait_for_provision_state():
try:
node = ironic.get_node_by_instance(self.ironicclient,
instance.uuid)
except ironic_exc.NotFound:
LOG.debug("Instance already removed from Ironic",
instance=instance)
raise loopingcall.LoopingCallDone()
LOG.debug('Current ironic node state is %s', node.provision_state)
if node.provision_state in (ironic_states.NOSTATE,
ironic_states.CLEANING,
ironic_states.CLEANWAIT,
ironic_states.CLEANFAIL,
ironic_states.AVAILABLE):
# From a user standpoint, the node is unprovisioned. If a node
# gets into CLEANFAIL state, it must be fixed in Ironic, but we
# can consider the instance unprovisioned.
LOG.debug("Ironic node %(node)s is in state %(state)s, "
"instance is now unprovisioned.",
dict(node=node.uuid, state=node.provision_state),
instance=instance)
raise loopingcall.LoopingCallDone()
if data['tries'] >= CONF.ironic.api_max_retries + 1:
msg = (_("Error destroying the instance on node %(node)s. "
"Provision state still '%(state)s'.")
% {'state': node.provision_state,
'node': node.uuid})
LOG.error(msg)
raise exception.MoganException(msg)
else:
data['tries'] += 1
# wait for the state transition to finish
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_provision_state)
timer.start(interval=CONF.ironic.api_retry_interval).wait()
LOG.info(_LI('Successfully destroyed Ironic node %s'),
instance.node_uuid)
def _remove_instance_info_from_node(self, instance):
try:
ironic.unset_instance_info(self.ironicclient, instance)
except ironic_exc.BadRequest as e:
self.driver.unset_instance_info(instance)
except exception.Invalid as e:
LOG.warning(_LW("Failed to remove deploy parameters from node "
"%(node)s when unprovisioning the instance "
"%(instance)s: %(reason)s"),
{'node': instance.node_uuid, 'instance': instance.uuid,
'reason': six.text_type(e)})
def wait_for_active(self, instance):
"""Wait for the node to be marked as ACTIVE in Ironic."""
instance.refresh()
if instance.status in (states.DELETING, states.ERROR, states.DELETED):
raise exception.InstanceDeployFailure(
_("Instance %s provisioning was aborted") % instance.uuid)
node = ironic.get_node_by_instance(self.ironicclient,
instance.uuid)
LOG.debug('Current ironic node state is %s', node.provision_state)
if node.provision_state == ironic_states.ACTIVE:
# job is done
LOG.debug("Ironic node %(node)s is now ACTIVE",
dict(node=node.uuid))
raise loopingcall.LoopingCallDone()
if node.target_provision_state in (ironic_states.DELETED,
ironic_states.AVAILABLE):
# ironic is trying to delete it now
raise exception.InstanceNotFound(instance_id=instance.uuid)
if node.provision_state in (ironic_states.NOSTATE,
ironic_states.AVAILABLE):
# ironic already deleted it
raise exception.InstanceNotFound(instance_id=instance.uuid)
if node.provision_state == ironic_states.DEPLOYFAIL:
# ironic failed to deploy
msg = (_("Failed to provision instance %(inst)s: %(reason)s")
% {'inst': instance.uuid, 'reason': node.last_error})
raise exception.InstanceDeployFailure(msg)
def create_instance(self, context, instance, requested_networks,
request_spec=None, filter_properties=None):
"""Perform a deployment."""
@ -423,8 +325,7 @@ class EngineManager(base_manager.BaseEngineManager):
# doesn't alter the instance in any way. This may raise
# InvalidState, if this event is not allowed in the current state.
fsm.process_event('done')
instance.power_state = ironic.get_power_state(self.ironicclient,
instance.uuid)
instance.power_state = self.driver.get_power_state(instance.uuid)
instance.status = fsm.current_state
instance.launched_at = timeutils.utcnow()
instance.save()
@ -442,14 +343,13 @@ class EngineManager(base_manager.BaseEngineManager):
target_state=states.DELETED)
try:
node = ironic.get_node_by_instance(self.ironicclient,
instance.uuid)
except ironic_exc.NotFound:
node = self.driver.get_node_by_instance(instance.uuid)
except exception.NotFound:
node = None
if node:
try:
if node.provision_state in _UNPROVISION_STATES:
if self.driver.is_node_unprovision(node):
self.destroy_networks(context, instance)
self._destroy_instance(context, instance)
else:
@ -471,20 +371,6 @@ class EngineManager(base_manager.BaseEngineManager):
instance.save()
instance.destroy()
def _wait_for_power_state(self, instance):
"""Wait for the node to complete a power state change."""
try:
node = ironic.get_node_by_instance(self.ironicclient,
instance.uuid)
except ironic_exc.NotFound:
LOG.debug("While waiting for node to complete a power state "
"change, it dissociate with the instance.",
instance=instance)
raise exception.NodeNotFound()
if node.target_power_state == ironic_states.NOSTATE:
raise loopingcall.LoopingCallDone()
def set_power_state(self, context, instance, state):
"""Set power state for the specified instance."""
@ -497,21 +383,13 @@ class EngineManager(base_manager.BaseEngineManager):
LOG.debug('Power %(state)s called for instance %(instance)s',
{'state': state,
'instance': instance})
ironic.set_power_state(self.ironicclient,
instance.node_uuid,
state)
timer = loopingcall.FixedIntervalLoopingCall(
self._wait_for_power_state, instance)
timer.start(interval=CONF.ironic.api_retry_interval).wait()
fsm.process_event('done')
instance.power_state = ironic.get_power_state(self.ironicclient,
instance.uuid)
instance.status = fsm.current_state
instance.save()
self.driver.set_power_state(instance, state)
do_set_power_state()
fsm.process_event('done')
instance.power_state = self.driver.get_power_state(instance.uuid)
instance.status = fsm.current_state
instance.save()
LOG.info(_LI('Successfully set node power state: %s'),
state, instance=instance)
@ -519,19 +397,9 @@ class EngineManager(base_manager.BaseEngineManager):
"""Perform rebuild action on the specified instance."""
try:
ironic.do_node_rebuild(self.ironicclient, instance.node_uuid)
except (ironic_exc.InternalServerError,
ironic_exc.BadRequest) as e:
msg = (_("Failed to request Ironic to rebuild instance "
"%(inst)s: %(reason)s") % {'inst': instance.uuid,
'reason': six.text_type(e)})
raise exception.InstanceDeployFailure(msg)
# Although the target provision state is REBUILD, it will actually go
# to ACTIVE once the redeploy is finished.
timer = loopingcall.FixedIntervalLoopingCall(self.wait_for_active,
instance)
timer.start(interval=CONF.ironic.api_retry_interval).wait()
self.driver.do_node_rebuild(instance)
except exception.InstanceDeployFailure as e:
six.reraise(type(e), e, sys.exc_info()[2])
def rebuild(self, context, instance):
"""Perform rebuild action on the specified instance."""

View File

@ -18,8 +18,9 @@ import mock
from oslo_context import context
from oslo_utils import uuidutils
from mogan.engine.baremetal import ironic
from mogan.engine.baremetal.ironic import IronicDriver
from mogan.engine.flows import create_instance
from mogan.engine import manager
from mogan.engine.scheduler import filter_scheduler as scheduler
from mogan import objects
from mogan.tests import base
@ -56,22 +57,22 @@ class CreateInstanceFlowTestCase(base.TestCase):
fake_filter_props)
self.assertEqual(fake_uuid, instance_obj.node_uuid)
@mock.patch.object(ironic, 'validate_node')
@mock.patch.object(ironic, 'set_instance_info')
def test_set_instance_info_task_execute(self, mock_set_inst,
@mock.patch.object(IronicDriver, 'validate_node')
@mock.patch.object(IronicDriver, 'set_instance_info')
@mock.patch.object(IronicDriver, 'get_node')
def test_set_instance_info_task_execute(self, mock_get_node, mock_set_inst,
mock_validate):
fake_ironicclient = mock.MagicMock()
task = create_instance.SetInstanceInfoTask(
fake_ironicclient)
flow_manager = manager.EngineManager('test-host', 'test-topic')
task = create_instance.SetInstanceInfoTask(flow_manager.driver)
instance_obj = obj_utils.get_test_instance(self.ctxt)
mock_get_node.side_effect = None
mock_set_inst.side_effect = None
mock_validate.side_effect = None
task.execute(self.ctxt, instance_obj)
mock_set_inst.assert_called_once_with(fake_ironicclient,
instance_obj, mock.ANY)
mock_validate.assert_called_once_with(fake_ironicclient,
instance_obj.node_uuid)
mock_get_node.assert_called_once_with(instance_obj.node_uuid)
mock_set_inst.assert_called_once_with(instance_obj, mock.ANY)
mock_validate.assert_called_once_with(instance_obj.node_uuid)
@mock.patch.object(objects.instance.Instance, 'save')
@mock.patch.object(create_instance.BuildNetworkTask, '_build_networks')

View File

@ -18,10 +18,9 @@
import mock
from oslo_config import cfg
from mogan.common import exception
from mogan.common import states
from mogan.engine.baremetal import ironic
from mogan.engine.baremetal import ironic_states
from mogan.engine.baremetal.ironic.driver import ironic_states
from mogan.engine.baremetal.ironic import IronicDriver
from mogan.engine import manager
from mogan.network import api as network_api
from mogan.tests.unit.db import base as tests_db_base
@ -35,8 +34,8 @@ CONF = cfg.CONF
class ManageInstanceTestCase(mgr_utils.ServiceSetUpMixin,
tests_db_base.DbTestCase):
@mock.patch.object(ironic, 'unplug_vif')
@mock.patch.object(ironic, 'get_ports_from_node')
@mock.patch.object(IronicDriver, 'unplug_vif')
@mock.patch.object(IronicDriver, 'get_ports_from_node')
@mock.patch.object(network_api.API, 'delete_port')
def test_destroy_networks(self, delete_port_mock,
get_ports_mock, unplug_vif_mock,
@ -57,18 +56,14 @@ class ManageInstanceTestCase(mgr_utils.ServiceSetUpMixin,
delete_port_mock.assert_called_once_with(
self.context, inst_port_id, instance.uuid)
get_ports_mock.assert_called_once_with(
mock.ANY, instance.node_uuid, detail=True)
unplug_vif_mock.assert_called_once_with(mock.ANY, 'fake-uuid')
get_ports_mock.assert_called_once_with(instance.node_uuid)
unplug_vif_mock.assert_called_once_with(port)
@mock.patch.object(ironic, 'get_node_by_instance')
@mock.patch.object(ironic, 'destroy_node')
@mock.patch.object(IronicDriver, 'destroy')
def _test__destroy_instance(self, destroy_node_mock,
get_node_mock, refresh_cache_mock,
state=None):
refresh_cache_mock, state=None):
fake_node = mock.MagicMock()
fake_node.provision_state = state
get_node_mock.return_value = fake_node
instance = obj_utils.create_test_instance(self.context)
destroy_node_mock.side_effect = None
refresh_cache_mock.side_effect = None
@ -77,8 +72,7 @@ class ManageInstanceTestCase(mgr_utils.ServiceSetUpMixin,
self.service._destroy_instance(self.context, instance)
self._stop_service()
get_node_mock.assert_called_once_with(mock.ANY, instance.uuid)
destroy_node_mock.assert_called_once_with(mock.ANY, instance.node_uuid)
destroy_node_mock.assert_called_once_with(instance)
def test__destroy_instance_cleaning(self, refresh_cache_mock):
self._test__destroy_instance(state=ironic_states.CLEANING,
@ -88,29 +82,7 @@ class ManageInstanceTestCase(mgr_utils.ServiceSetUpMixin,
self._test__destroy_instance(state=ironic_states.CLEANWAIT,
refresh_cache_mock=refresh_cache_mock)
@mock.patch.object(ironic, 'get_node_by_instance')
@mock.patch.object(ironic, 'destroy_node')
def test__destroy_instance_fail_max_retries(self, destroy_node_mock,
get_node_mock,
refresh_cache_mock):
CONF.set_default('api_max_retries', default=2, group='ironic')
fake_node = mock.MagicMock()
fake_node.provision_state = ironic_states.ACTIVE
get_node_mock.return_value = fake_node
instance = obj_utils.create_test_instance(self.context)
destroy_node_mock.side_effect = None
refresh_cache_mock.side_effect = None
self._start_service()
self.assertRaises(exception.MoganException,
self.service._destroy_instance,
self.context, instance)
self._stop_service()
self.assertTrue(get_node_mock.called)
destroy_node_mock.assert_called_once_with(mock.ANY, instance.node_uuid)
@mock.patch.object(ironic, 'get_node_by_instance')
@mock.patch.object(IronicDriver, 'get_node_by_instance')
@mock.patch.object(manager.EngineManager, '_destroy_instance')
@mock.patch.object(manager.EngineManager, 'destroy_networks')
def test_delete_instance(self, destroy_net_mock,
@ -131,9 +103,9 @@ class ManageInstanceTestCase(mgr_utils.ServiceSetUpMixin,
destroy_net_mock.assert_called_once_with(mock.ANY, instance)
destroy_inst_mock.assert_called_once_with(mock.ANY, instance)
get_node_mock.assert_called_once_with(mock.ANY, instance.uuid)
get_node_mock.assert_called_once_with(instance.uuid)
@mock.patch.object(ironic, 'get_node_by_instance')
@mock.patch.object(IronicDriver, 'get_node_by_instance')
@mock.patch.object(manager.EngineManager, '_destroy_instance')
def test_delete_instance_without_node_destroy(
self, destroy_inst_mock, get_node_mock, refresh_cache_mock):
@ -151,17 +123,15 @@ class ManageInstanceTestCase(mgr_utils.ServiceSetUpMixin,
self.assertFalse(destroy_inst_mock.called)
@mock.patch.object(ironic, 'get_power_state')
@mock.patch.object(ironic, 'get_node_by_instance')
@mock.patch.object(ironic, 'set_power_state')
@mock.patch.object(IronicDriver, 'get_power_state')
@mock.patch.object(IronicDriver, 'set_power_state')
def test_change_instance_power_state(
self, set_power_mock, get_node_mock, get_power_mock,
self, set_power_mock, get_power_mock,
refresh_cache_mock):
instance = obj_utils.create_test_instance(
self.context, status=states.POWERING_ON)
fake_node = mock.MagicMock()
fake_node.target_power_state = ironic_states.NOSTATE
get_node_mock.return_value = fake_node
get_power_mock.return_value = states.POWER_ON
refresh_cache_mock.side_effect = None
self._start_service()
@ -170,10 +140,9 @@ class ManageInstanceTestCase(mgr_utils.ServiceSetUpMixin,
ironic_states.POWER_ON)
self._stop_service()
set_power_mock.assert_called_once_with(mock.ANY, instance.node_uuid,
set_power_mock.assert_called_once_with(instance,
ironic_states.POWER_ON)
get_node_mock.assert_called_once_with(mock.ANY, instance.uuid)
get_power_mock.assert_called_once_with(mock.ANY, instance.uuid)
get_power_mock.assert_called_once_with(instance.uuid)
def test_list_availability_zone(self, refresh_cache_mock):
refresh_cache_mock.side_effect = None