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:
parent
bce8454175
commit
8a0046c784
1
.gitignore
vendored
1
.gitignore
vendored
@ -34,6 +34,7 @@ nosetests.xml
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.idea
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
#################
|
||||
|
@ -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)))
|
||||
|
@ -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."))
|
||||
]
|
||||
|
||||
|
||||
|
0
mogan/engine/baremetal/cloudboot/__init__.py
Normal file
0
mogan/engine/baremetal/cloudboot/__init__.py
Normal file
198
mogan/engine/baremetal/driver.py
Normal file
198
mogan/engine/baremetal/driver.py
Normal 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)
|
@ -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)
|
18
mogan/engine/baremetal/ironic/__init__.py
Normal file
18
mogan/engine/baremetal/ironic/__init__.py
Normal 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
|
389
mogan/engine/baremetal/ironic/driver.py
Normal file
389
mogan/engine/baremetal/ironic/driver.py
Normal 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()
|
@ -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)
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user