3013 lines
129 KiB
Python
3013 lines
129 KiB
Python
import base64
|
|
import contextlib
|
|
import datetime
|
|
import functools
|
|
import os
|
|
import sys
|
|
import time
|
|
import traceback
|
|
import uuid
|
|
|
|
import novaclient
|
|
import eventlet.event
|
|
import eventlet.timeout
|
|
from oslo.config import cfg
|
|
from oslo import messaging
|
|
|
|
from nova.compute import clients
|
|
from nova.compute import compute_context
|
|
from nova.openstack.common import timeutils
|
|
|
|
from nova import block_device
|
|
from nova.cells import rpcapi as cells_rpcapi
|
|
from nova.cloudpipe import pipelib
|
|
from nova import compute
|
|
from nova.compute import flavors
|
|
from nova.compute import power_state
|
|
from nova.compute import resource_tracker
|
|
from nova.compute import rpcapi as compute_rpcapi
|
|
from nova.compute import task_states
|
|
from nova.compute import utils as compute_utils
|
|
from nova.compute import vm_states
|
|
from nova import conductor
|
|
from nova import consoleauth
|
|
import nova.context
|
|
from nova import exception
|
|
from nova import hooks
|
|
from nova.image import glance
|
|
from nova.image import cascading
|
|
from nova import manager
|
|
from nova import network
|
|
from nova.network import model as network_model
|
|
from nova.network.security_group import openstack_driver
|
|
from nova.objects import aggregate as aggregate_obj
|
|
from nova.objects import base as obj_base
|
|
from nova.objects import block_device as block_device_obj
|
|
from nova.objects import external_event as external_event_obj
|
|
from nova.objects import flavor as flavor_obj
|
|
from nova.objects import instance as instance_obj
|
|
from nova.objects import instance_group as instance_group_obj
|
|
from nova.objects import migration as migration_obj
|
|
from nova.objects import quotas as quotas_obj
|
|
from nova.openstack.common import excutils
|
|
from nova.openstack.common.gettextutils import _
|
|
from nova.openstack.common import jsonutils
|
|
from nova.openstack.common import log as logging
|
|
from nova.openstack.common import periodic_task
|
|
from nova import paths
|
|
from nova import rpc
|
|
from nova import safe_utils
|
|
from nova.scheduler import rpcapi as scheduler_rpcapi
|
|
from nova import utils
|
|
from nova.virt import block_device as driver_block_device
|
|
from nova.virt import driver
|
|
from nova.virt import virtapi
|
|
from nova import volume
|
|
|
|
from nova.virt.libvirt import utils as libvirt_utils
|
|
from nova.network import neutronv2
|
|
from neutronclient.v2_0 import client as clientv20
|
|
|
|
|
|
compute_opts = [
|
|
cfg.StrOpt('instances_path',
|
|
default=paths.state_path_def('instances'),
|
|
help='Where instances are stored on disk'),
|
|
cfg.IntOpt('network_allocate_retries',
|
|
default=0,
|
|
help="Number of times to retry network allocation on failures"),
|
|
cfg.StrOpt('keystone_auth_url',
|
|
default='http://127.0.0.1:5000/v2.0/',
|
|
help='value of keystone url'),
|
|
cfg.StrOpt('nova_admin_username',
|
|
default='nova',
|
|
help='username for connecting to nova in admin context'),
|
|
cfg.StrOpt('nova_admin_password',
|
|
default='nova',
|
|
help='password for connecting to nova in admin context',
|
|
secret=True),
|
|
cfg.StrOpt('nova_admin_tenant_name',
|
|
default='admin',
|
|
help='tenant name for connecting to nova in admin context'),
|
|
cfg.StrOpt('proxy_region_name',
|
|
deprecated_name='proxy_region_name',
|
|
help='region name for connecting to neutron in admin context'),
|
|
cfg.IntOpt('novncproxy_port',
|
|
default=6080,
|
|
help='Port on which to listen for incoming requests'),
|
|
cfg.StrOpt('cascading_nova_url',
|
|
default='http://127.0.0.1:8774/v2',
|
|
help='value of cascading url'),
|
|
cfg.StrOpt('cascaded_nova_url',
|
|
default='http://127.0.0.1:8774/v2',
|
|
help='value of cascaded url'),
|
|
cfg.StrOpt('cascaded_neutron_url',
|
|
default='http://127.0.0.1:9696',
|
|
help='value of cascaded neutron url'),
|
|
cfg.BoolOpt('cascaded_glance_flag',
|
|
default=False,
|
|
help='Whether to use glance cescaded'),
|
|
cfg.StrOpt('cascaded_glance_url',
|
|
default='http://127.0.0.1:9292',
|
|
help='value of cascaded glance url')
|
|
]
|
|
|
|
interval_opts = [
|
|
cfg.IntOpt('volume_usage_poll_interval',
|
|
default=0,
|
|
help='Interval in seconds for gathering volume usages'),
|
|
cfg.IntOpt('sync_instance_state_interval',
|
|
default=5,
|
|
help='interval to sync instance states between '
|
|
'the nova and the nova-proxy')
|
|
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(compute_opts)
|
|
CONF.register_opts(interval_opts)
|
|
CONF.import_opt('allow_resize_to_same_host', 'nova.compute.api')
|
|
CONF.import_opt('host', 'nova.netconf')
|
|
CONF.import_opt('my_ip', 'nova.netconf')
|
|
CONF.import_opt('vnc_enabled', 'nova.vnc')
|
|
CONF.import_opt('enabled', 'nova.spice', group='spice')
|
|
CONF.import_opt('enable', 'nova.cells.opts', group='cells')
|
|
CONF.import_opt('enabled', 'nova.rdp', group='rdp')
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
get_notifier = functools.partial(rpc.get_notifier, service='compute')
|
|
wrap_exception = functools.partial(exception.wrap_exception,
|
|
get_notifier=get_notifier)
|
|
|
|
|
|
@utils.expects_func_args('migration')
|
|
def errors_out_migration(function):
|
|
"""Decorator to error out migration on failure."""
|
|
|
|
@functools.wraps(function)
|
|
def decorated_function(self, context, *args, **kwargs):
|
|
try:
|
|
return function(self, context, *args, **kwargs)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
# Find migration argument. The argument cannot be
|
|
# defined by position because the wrapped functions
|
|
# do not have the same signature.
|
|
for arg in args:
|
|
if not isinstance(arg, migration_obj.Migration):
|
|
continue
|
|
status = arg.status
|
|
if status not in ['migrating', 'post-migrating']:
|
|
continue
|
|
arg.status = 'error'
|
|
try:
|
|
arg.save(context.elevated())
|
|
except Exception:
|
|
LOG.debug(_('Error setting migration status '
|
|
'for instance %s.') %
|
|
arg.instance_uuid, exc_info=True)
|
|
break
|
|
|
|
return decorated_function
|
|
|
|
|
|
@utils.expects_func_args('instance')
|
|
def reverts_task_state(function):
|
|
"""Decorator to revert task_state on failure."""
|
|
|
|
@functools.wraps(function)
|
|
def decorated_function(self, context, *args, **kwargs):
|
|
try:
|
|
return function(self, context, *args, **kwargs)
|
|
except exception.UnexpectedTaskStateError as e:
|
|
# Note(maoy): unexpected task state means the current
|
|
# task is preempted. Do not clear task state in this
|
|
# case.
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.info(_("Task possibly preempted: %s") % e.format_message())
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
try:
|
|
self._instance_update(context,
|
|
kwargs['instance']['uuid'],
|
|
task_state=None)
|
|
except Exception:
|
|
pass
|
|
|
|
return decorated_function
|
|
|
|
|
|
@utils.expects_func_args('instance')
|
|
def wrap_instance_fault(function):
|
|
"""Wraps a method to catch exceptions related to instances.
|
|
|
|
This decorator wraps a method to catch any exceptions having to do with
|
|
an instance that may get thrown. It then logs an instance fault in the db.
|
|
"""
|
|
|
|
@functools.wraps(function)
|
|
def decorated_function(self, context, *args, **kwargs):
|
|
try:
|
|
return function(self, context, *args, **kwargs)
|
|
except exception.InstanceNotFound:
|
|
raise
|
|
except Exception as e:
|
|
# NOTE(gtt): If argument 'instance' is in args rather than kwargs,
|
|
# we will get a KeyError exception which will cover up the real
|
|
# exception. So, we update kwargs with the values from args first.
|
|
# then, we can get 'instance' from kwargs easily.
|
|
kwargs.update(dict(zip(function.func_code.co_varnames[2:], args)))
|
|
|
|
with excutils.save_and_reraise_exception():
|
|
compute_utils.add_instance_fault_from_exc(context,
|
|
self.conductor_api,
|
|
kwargs['instance'],
|
|
e, sys.exc_info())
|
|
|
|
return decorated_function
|
|
|
|
|
|
@utils.expects_func_args('instance')
|
|
def wrap_instance_event(function):
|
|
"""Wraps a method to log the event taken on the instance, and result.
|
|
|
|
This decorator wraps a method to log the start and result of an event, as
|
|
part of an action taken on an instance.
|
|
"""
|
|
|
|
@functools.wraps(function)
|
|
def decorated_function(self, context, *args, **kwargs):
|
|
wrapped_func = utils.get_wrapped_function(function)
|
|
keyed_args = safe_utils.getcallargs(wrapped_func, context, *args,
|
|
**kwargs)
|
|
instance_uuid = keyed_args['instance']['uuid']
|
|
|
|
event_name = 'compute_{0}'.format(function.func_name)
|
|
with compute_utils.EventReporter(context, self.conductor_api,
|
|
event_name, instance_uuid):
|
|
|
|
function(self, context, *args, **kwargs)
|
|
|
|
return decorated_function
|
|
|
|
|
|
@utils.expects_func_args('image_id', 'instance')
|
|
def delete_image_on_error(function):
|
|
"""Used for snapshot related method to ensure the image created in
|
|
compute.api is deleted when an error occurs.
|
|
"""
|
|
|
|
@functools.wraps(function)
|
|
def decorated_function(self, context, image_id, instance,
|
|
*args, **kwargs):
|
|
try:
|
|
return function(self, context, image_id, instance,
|
|
*args, **kwargs)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.debug(_("Cleaning up image %s") % image_id,
|
|
exc_info=True, instance=instance)
|
|
try:
|
|
image_service = glance.get_default_image_service()
|
|
image_service.delete(context, image_id)
|
|
except Exception:
|
|
LOG.exception(_("Error while trying to clean up image %s")
|
|
% image_id, instance=instance)
|
|
|
|
return decorated_function
|
|
|
|
|
|
# TODO(danms): Remove me after Icehouse
|
|
# NOTE(mikal): if the method being decorated has more than one decorator, then
|
|
# put this one first. Otherwise the various exception handling decorators do
|
|
# not function correctly.
|
|
def object_compat(function):
|
|
"""Wraps a method that expects a new-world instance
|
|
|
|
This provides compatibility for callers passing old-style dict
|
|
instances.
|
|
"""
|
|
|
|
@functools.wraps(function)
|
|
def decorated_function(self, context, *args, **kwargs):
|
|
def _load_instance(instance_or_dict):
|
|
if isinstance(instance_or_dict, dict):
|
|
instance = instance_obj.Instance._from_db_object(
|
|
context, instance_obj.Instance(), instance_or_dict,
|
|
expected_attrs=metas)
|
|
instance._context = context
|
|
return instance
|
|
return instance_or_dict
|
|
|
|
metas = ['metadata', 'system_metadata']
|
|
try:
|
|
kwargs['instance'] = _load_instance(kwargs['instance'])
|
|
except KeyError:
|
|
args = (_load_instance(args[0]),) + args[1:]
|
|
|
|
migration = kwargs.get('migration')
|
|
if isinstance(migration, dict):
|
|
migration = migration_obj.Migration._from_db_object(
|
|
context.elevated(),
|
|
migration_obj.Migration(),
|
|
migration)
|
|
kwargs['migration'] = migration
|
|
|
|
return function(self, context, *args, **kwargs)
|
|
|
|
return decorated_function
|
|
|
|
|
|
# TODO(danms): Remove me after Icehouse
|
|
def aggregate_object_compat(function):
|
|
"""Wraps a method that expects a new-world aggregate."""
|
|
|
|
@functools.wraps(function)
|
|
def decorated_function(self, context, *args, **kwargs):
|
|
aggregate = kwargs.get('aggregate')
|
|
if isinstance(aggregate, dict):
|
|
aggregate = aggregate_obj.Aggregate._from_db_object(
|
|
context.elevated(), aggregate_obj.Aggregate(),
|
|
aggregate)
|
|
kwargs['aggregate'] = aggregate
|
|
return function(self, context, *args, **kwargs)
|
|
return decorated_function
|
|
|
|
|
|
def _get_image_meta(context, image_ref):
|
|
image_service, image_id = glance.get_remote_image_service(context,
|
|
image_ref)
|
|
return image_service.show(context, image_id)
|
|
|
|
|
|
class InstanceEvents(object):
|
|
|
|
def __init__(self):
|
|
self._events = {}
|
|
|
|
@staticmethod
|
|
def _lock_name(instance):
|
|
return '%s-%s' % (instance.uuid, 'events')
|
|
|
|
def prepare_for_instance_event(self, instance, event_name):
|
|
"""Prepare to receive an event for an instance.
|
|
|
|
This will register an event for the given instance that we will
|
|
wait on later. This should be called before initiating whatever
|
|
action will trigger the event. The resulting eventlet.event.Event
|
|
object should be wait()'d on to ensure completion.
|
|
|
|
:param instance: the instance for which the event will be generated
|
|
:param event_name: the name of the event we're expecting
|
|
:returns: an event object that should be wait()'d on
|
|
"""
|
|
@utils.synchronized(self._lock_name)
|
|
def _create_or_get_event():
|
|
if instance.uuid not in self._events:
|
|
self._events.setdefault(instance.uuid, {})
|
|
return self._events[instance.uuid].setdefault(
|
|
event_name, eventlet.event.Event())
|
|
LOG.debug(_('Preparing to wait for external event %(event)s'),
|
|
{'event': event_name}, instance=instance)
|
|
return _create_or_get_event()
|
|
|
|
def pop_instance_event(self, instance, event):
|
|
"""Remove a pending event from the wait list.
|
|
|
|
This will remove a pending event from the wait list so that it
|
|
can be used to signal the waiters to wake up.
|
|
|
|
:param instance: the instance for which the event was generated
|
|
:param event: the nova.objects.external_event.InstanceExternalEvent
|
|
that describes the event
|
|
:returns: the eventlet.event.Event object on which the waiters
|
|
are blocked
|
|
"""
|
|
@utils.synchronized(self._lock_name)
|
|
def _pop_event():
|
|
events = self._events.get(instance.uuid)
|
|
if not events:
|
|
return None
|
|
_event = events.pop(event.key, None)
|
|
if not events:
|
|
del self._events[instance.uuid]
|
|
return _event
|
|
return _pop_event()
|
|
|
|
def clear_events_for_instance(self, instance):
|
|
"""Remove all pending events for an instance.
|
|
|
|
This will remove all events currently pending for an instance
|
|
and return them (indexed by event name).
|
|
|
|
:param instance: the instance for which events should be purged
|
|
:returns: a dictionary of {event_name: eventlet.event.Event}
|
|
"""
|
|
@utils.synchronized(self._lock_name)
|
|
def _clear_events():
|
|
# NOTE(danms): Use getitem syntax for the instance until
|
|
# all the callers are using objects
|
|
return self._events.pop(instance['uuid'], {})
|
|
return _clear_events()
|
|
|
|
|
|
class ComputeVirtAPI(virtapi.VirtAPI):
|
|
|
|
def __init__(self, compute):
|
|
super(ComputeVirtAPI, self).__init__()
|
|
self._compute = compute
|
|
|
|
def instance_update(self, context, instance_uuid, updates):
|
|
return self._compute._instance_update(context,
|
|
instance_uuid,
|
|
**updates)
|
|
|
|
def provider_fw_rule_get_all(self, context):
|
|
return self._compute.conductor_api.provider_fw_rule_get_all(context)
|
|
|
|
def agent_build_get_by_triple(self, context, hypervisor, os, architecture):
|
|
return self._compute.conductor_api.agent_build_get_by_triple(
|
|
context, hypervisor, os, architecture)
|
|
|
|
def _default_error_callback(self, event_name, instance):
|
|
raise exception.NovaException(_('Instance event failed'))
|
|
|
|
@contextlib.contextmanager
|
|
def wait_for_instance_event(self, instance, event_names, deadline=300,
|
|
error_callback=None):
|
|
"""Plan to wait for some events, run some code, then wait.
|
|
|
|
This context manager will first create plans to wait for the
|
|
provided event_names, yield, and then wait for all the scheduled
|
|
events to complete.
|
|
|
|
Note that this uses an eventlet.timeout.Timeout to bound the
|
|
operation, so callers should be prepared to catch that
|
|
failure and handle that situation appropriately.
|
|
|
|
If the event is not received by the specified timeout deadline,
|
|
eventlet.timeout.Timeout is raised.
|
|
|
|
If the event is received but did not have a 'completed'
|
|
status, a NovaException is raised. If an error_callback is
|
|
provided, instead of raising an exception as detailed above
|
|
for the failure case, the callback will be called with the
|
|
event_name and instance, and can return True to continue
|
|
waiting for the rest of the events, False to stop processing,
|
|
or raise an exception which will bubble up to the waiter.
|
|
|
|
:param:instance: The instance for which an event is expected
|
|
:param:event_names: A list of event names. Each element can be a
|
|
string event name or tuple of strings to
|
|
indicate (name, tag).
|
|
:param:deadline: Maximum number of seconds we should wait for all
|
|
of the specified events to arrive.
|
|
:param:error_callback: A function to be called if an event arrives
|
|
"""
|
|
|
|
if error_callback is None:
|
|
error_callback = self._default_error_callback
|
|
events = {}
|
|
for event_name in event_names:
|
|
if isinstance(event_name, tuple):
|
|
name, tag = event_name
|
|
event_name = external_event_obj.InstanceExternalEvent.make_key(
|
|
name, tag)
|
|
events[event_name] = (
|
|
self._compute.instance_events.prepare_for_instance_event(
|
|
instance, event_name))
|
|
yield
|
|
with eventlet.timeout.Timeout(deadline):
|
|
for event_name, event in events.items():
|
|
actual_event = event.wait()
|
|
if actual_event.status == 'completed':
|
|
continue
|
|
decision = error_callback(event_name, instance)
|
|
if decision is False:
|
|
break
|
|
|
|
|
|
class ComputeManager(manager.Manager):
|
|
|
|
"""Manages the running instances from creation to destruction."""
|
|
|
|
target = messaging.Target(version='3.23')
|
|
|
|
def __init__(self, compute_driver=None, *args, **kwargs):
|
|
"""Load configuration options and connect to the hypervisor."""
|
|
self.virtapi = ComputeVirtAPI(self)
|
|
self.network_api = network.API()
|
|
self.volume_api = volume.API()
|
|
self._last_host_check = 0
|
|
self._last_bw_usage_poll = 0
|
|
self._bw_usage_supported = True
|
|
self._last_bw_usage_cell_update = 0
|
|
self.compute_api = compute.API()
|
|
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
|
|
self.conductor_api = conductor.API()
|
|
self.compute_task_api = conductor.ComputeTaskAPI()
|
|
self.is_neutron_security_groups = (
|
|
openstack_driver.is_neutron_security_groups())
|
|
self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI()
|
|
self.cells_rpcapi = cells_rpcapi.CellsAPI()
|
|
self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
|
|
self._resource_tracker_dict = {}
|
|
self.instance_events = InstanceEvents()
|
|
|
|
super(ComputeManager, self).__init__(service_name="compute",
|
|
*args, **kwargs)
|
|
|
|
# NOTE(russellb) Load the driver last. It may call back into the
|
|
# compute manager via the virtapi, so we want it to be fully
|
|
# initialized before that happens.
|
|
self.driver = driver.load_compute_driver(self.virtapi, compute_driver)
|
|
self.use_legacy_block_device_info = \
|
|
self.driver.need_legacy_block_device_info
|
|
self._last_info_instance_state_heal = 0
|
|
self._change_since_time = None
|
|
|
|
def _get_resource_tracker(self, nodename):
|
|
rt = self._resource_tracker_dict.get(nodename)
|
|
if not rt:
|
|
if not self.driver.node_is_available(nodename):
|
|
raise exception.NovaException(
|
|
_("%s is not a valid node managed by this "
|
|
"compute host.") % nodename)
|
|
|
|
rt = resource_tracker.ResourceTracker(self.host,
|
|
self.driver,
|
|
nodename)
|
|
self._resource_tracker_dict[nodename] = rt
|
|
return rt
|
|
|
|
def _instance_update(self, context, instance_uuid, **kwargs):
|
|
"""Update an instance in the database using kwargs as value."""
|
|
|
|
instance_ref = self.conductor_api.instance_update(context,
|
|
instance_uuid,
|
|
**kwargs)
|
|
if (instance_ref['host'] == self.host and
|
|
self.driver.node_is_available(instance_ref['node'])):
|
|
rt = self._get_resource_tracker(instance_ref.get('node'))
|
|
rt.update_usage(context, instance_ref)
|
|
|
|
return instance_ref
|
|
|
|
@periodic_task.periodic_task(spacing=CONF.sync_instance_state_interval,
|
|
run_immediately=True)
|
|
def _heal_instance_state(self, context):
|
|
time_shift_tolerance = 3
|
|
heal_interval = CONF.sync_instance_state_interval
|
|
if not heal_interval:
|
|
return
|
|
curr_time = time.time()
|
|
if self._last_info_instance_state_heal != 0:
|
|
if self._last_info_instance_state_heal + heal_interval > curr_time:
|
|
return
|
|
self._last_info_instance_state_heal = curr_time
|
|
|
|
kwargs = {
|
|
'username': cfg.CONF.nova_admin_username,
|
|
'password': cfg.CONF.nova_admin_password,
|
|
'tenant': cfg.CONF.nova_admin_tenant_name,
|
|
'auth_url': cfg.CONF.keystone_auth_url,
|
|
'region_name': cfg.CONF.proxy_region_name
|
|
}
|
|
reqCon = compute_context.RequestContext(**kwargs)
|
|
openStackClients = clients.OpenStackClients(reqCon)
|
|
cascadedNovaCli = openStackClients.nova()
|
|
try:
|
|
if self._change_since_time is None:
|
|
search_opts_args = {'all_tenants': True}
|
|
servers = cascadedNovaCli.servers.list(
|
|
search_opts=search_opts_args)
|
|
else:
|
|
search_opts_args = {
|
|
'changes-since': self._change_since_time,
|
|
'all_tenants': True
|
|
}
|
|
servers = cascadedNovaCli.servers.list(
|
|
search_opts=search_opts_args)
|
|
LOG.debug(_('the current time is %s'), timeutils.utcnow())
|
|
_change_since_time = timeutils.utcnow() - \
|
|
datetime.timedelta(seconds=time_shift_tolerance)
|
|
self._change_since_time = timeutils.isotime(_change_since_time)
|
|
LOG.debug(_('the change since time is %s'),
|
|
self._change_since_time)
|
|
if len(servers) > 0:
|
|
LOG.debug(_('Updated the servers %s '), servers)
|
|
|
|
for server in servers:
|
|
self._instance_update(
|
|
context,
|
|
server._info['metadata']['mapping_uuid'],
|
|
vm_state=server._info['OS-EXT-STS:vm_state'],
|
|
task_state=server._info['OS-EXT-STS:task_state'],
|
|
power_state=server._info['OS-EXT-STS:power_state'],
|
|
launched_at=server._info['OS-SRV-USG:launched_at']
|
|
)
|
|
LOG.debug(_('Updated the server %s from nova-proxy'),
|
|
server.id)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to sys server status to db.'))
|
|
|
|
@periodic_task.periodic_task
|
|
def update_available_resource(self, context):
|
|
"""See driver.get_available_resource()
|
|
|
|
Periodic process that keeps that the compute host's understanding of
|
|
resource availability and usage in sync with the underlying hypervisor.
|
|
|
|
:param context: security context
|
|
"""
|
|
new_resource_tracker_dict = {}
|
|
nodenames = set(self.driver.get_available_nodes())
|
|
for nodename in nodenames:
|
|
rt = self._get_resource_tracker(nodename)
|
|
rt.update_available_resource(context)
|
|
new_resource_tracker_dict[nodename] = rt
|
|
|
|
self._resource_tracker_dict = new_resource_tracker_dict
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def run_instance(self, context, instance, request_spec,
|
|
filter_properties, requested_networks,
|
|
injected_files, admin_password,
|
|
is_first_time, node, legacy_bdm_in_spec):
|
|
|
|
if filter_properties is None:
|
|
filter_properties = {}
|
|
|
|
@utils.synchronized(instance['uuid'])
|
|
def do_run_instance():
|
|
self._run_instance(
|
|
context,
|
|
request_spec,
|
|
filter_properties,
|
|
requested_networks,
|
|
injected_files,
|
|
admin_password,
|
|
is_first_time,
|
|
node,
|
|
instance,
|
|
legacy_bdm_in_spec)
|
|
do_run_instance()
|
|
|
|
def _run_instance(self, context, request_spec,
|
|
filter_properties, requested_networks, injected_files,
|
|
admin_password, is_first_time, node, instance,
|
|
legacy_bdm_in_spec):
|
|
"""Launch a new instance with specified options."""
|
|
|
|
extra_usage_info = {}
|
|
|
|
def notify(status, msg="", fault=None, **kwargs):
|
|
"""Send a create.{start,error,end} notification."""
|
|
type_ = "create.%(status)s" % dict(status=status)
|
|
info = extra_usage_info.copy()
|
|
info['message'] = unicode(msg)
|
|
self._notify_about_instance_usage(
|
|
context,
|
|
instance,
|
|
type_,
|
|
extra_usage_info=info,
|
|
fault=fault,
|
|
**kwargs)
|
|
|
|
try:
|
|
self._prebuild_instance(context, instance)
|
|
|
|
if request_spec and request_spec.get('image'):
|
|
image_meta = request_spec['image']
|
|
else:
|
|
image_meta = {}
|
|
|
|
extra_usage_info = {"image_name": image_meta.get('name', '')}
|
|
|
|
notify("start") # notify that build is starting
|
|
|
|
instance, network_info = self._build_instance(context,
|
|
request_spec, filter_properties, requested_networks,
|
|
injected_files, admin_password, is_first_time, node,
|
|
instance, image_meta, legacy_bdm_in_spec)
|
|
notify("end", msg=_("Success"), network_info=network_info)
|
|
|
|
except exception.RescheduledException as e:
|
|
# Instance build encountered an error, and has been rescheduled.
|
|
notify("error", fault=e)
|
|
|
|
except exception.BuildAbortException as e:
|
|
# Instance build aborted due to a non-failure
|
|
LOG.info(e)
|
|
notify("end", msg=unicode(e)) # notify that build is done
|
|
|
|
except Exception as e:
|
|
# Instance build encountered a non-recoverable error:
|
|
with excutils.save_and_reraise_exception():
|
|
self._set_instance_error_state(context, instance['uuid'])
|
|
notify("error", fault=e) # notify that build failed
|
|
|
|
def _set_instance_error_state(self, context, instance_uuid):
|
|
try:
|
|
self._instance_update(context, instance_uuid,
|
|
vm_state=vm_states.ERROR)
|
|
except exception.InstanceNotFound:
|
|
LOG.debug(_('Instance has been destroyed from under us while '
|
|
'trying to set it to ERROR'),
|
|
instance_uuid=instance_uuid)
|
|
|
|
def _notify_about_instance_usage(self, context, instance, event_suffix,
|
|
network_info=None, system_metadata=None,
|
|
extra_usage_info=None, fault=None):
|
|
compute_utils.notify_about_instance_usage(
|
|
self.notifier, context, instance, event_suffix,
|
|
network_info=network_info,
|
|
system_metadata=system_metadata,
|
|
extra_usage_info=extra_usage_info, fault=fault)
|
|
|
|
def _prebuild_instance(self, context, instance):
|
|
try:
|
|
self._start_building(context, instance)
|
|
except (exception.InstanceNotFound,
|
|
exception.UnexpectedDeletingTaskStateError):
|
|
msg = _("Instance disappeared before we could start it")
|
|
# Quickly bail out of here
|
|
raise exception.BuildAbortException(instance_uuid=instance['uuid'],
|
|
reason=msg)
|
|
|
|
def _start_building(self, context, instance):
|
|
"""Save the host and launched_on fields and log appropriately."""
|
|
LOG.audit(_('Starting instance...'), context=context,
|
|
instance=instance)
|
|
self._instance_update(context, instance['uuid'],
|
|
vm_state=vm_states.BUILDING,
|
|
task_state=None,
|
|
expected_task_state=(task_states.SCHEDULING,
|
|
None))
|
|
|
|
def _build_instance(
|
|
self,
|
|
context,
|
|
request_spec,
|
|
filter_properties,
|
|
requested_networks,
|
|
injected_files,
|
|
admin_password,
|
|
is_first_time,
|
|
node,
|
|
instance,
|
|
image_meta,
|
|
legacy_bdm_in_spec):
|
|
context = context.elevated()
|
|
|
|
# If neutron security groups pass requested security
|
|
# groups to allocate_for_instance()
|
|
if request_spec and self.is_neutron_security_groups:
|
|
security_groups = request_spec.get('security_group')
|
|
else:
|
|
security_groups = []
|
|
|
|
if node is None:
|
|
node = self.driver.get_available_nodes(refresh=True)[0]
|
|
LOG.debug(_("No node specified, defaulting to %s"), node)
|
|
|
|
network_info = None
|
|
bdms = block_device_obj.BlockDeviceMappingList.get_by_instance_uuid(
|
|
context, instance['uuid'])
|
|
|
|
# b64 decode the files to inject:
|
|
injected_files_orig = injected_files
|
|
injected_files = self._decode_files(injected_files)
|
|
|
|
rt = self._get_resource_tracker(node)
|
|
try:
|
|
limits = filter_properties.get('limits', {})
|
|
with rt.instance_claim(context, instance, limits):
|
|
# NOTE(russellb) It's important that this validation be done
|
|
# *after* the resource tracker instance claim, as that is where
|
|
# the host is set on the instance.
|
|
self._validate_instance_group_policy(context, instance,
|
|
filter_properties)
|
|
macs = self.driver.macs_for_instance(instance)
|
|
dhcp_options = self.driver.dhcp_options_for_instance(instance)
|
|
|
|
network_info = self._allocate_network(
|
|
context,
|
|
instance,
|
|
requested_networks,
|
|
macs,
|
|
security_groups,
|
|
dhcp_options)
|
|
|
|
self._instance_update(
|
|
context, instance['uuid'],
|
|
vm_state=vm_states.BUILDING,
|
|
task_state=task_states.BLOCK_DEVICE_MAPPING)
|
|
|
|
cascaded_ports = []
|
|
self._heal_proxy_networks(context, instance, network_info)
|
|
cascaded_ports = self._heal_proxy_ports(context, instance,
|
|
network_info)
|
|
self._proxy_run_instance(context,
|
|
instance,
|
|
request_spec,
|
|
filter_properties,
|
|
requested_networks,
|
|
injected_files,
|
|
admin_password,
|
|
is_first_time,
|
|
node,
|
|
legacy_bdm_in_spec,
|
|
cascaded_ports)
|
|
except (exception.InstanceNotFound,
|
|
exception.UnexpectedDeletingTaskStateError):
|
|
# the instance got deleted during the spawn
|
|
# Make sure the async call finishes
|
|
msg = _("Instance disappeared during build")
|
|
if network_info is not None:
|
|
network_info.wait(do_raise=False)
|
|
try:
|
|
self._deallocate_network(context, instance)
|
|
except Exception:
|
|
msg = _('Failed to dealloc network '
|
|
'for deleted instance')
|
|
LOG.exception(msg, instance=instance)
|
|
raise exception.BuildAbortException(
|
|
instance_uuid=instance['uuid'],
|
|
reason=msg)
|
|
except (exception.UnexpectedTaskStateError,
|
|
exception.VirtualInterfaceCreateException) as e:
|
|
# Don't try to reschedule, just log and reraise.
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.debug(e.format_message(), instance=instance)
|
|
# Make sure the async call finishes
|
|
if network_info is not None:
|
|
network_info.wait(do_raise=False)
|
|
except exception.InvalidBDM:
|
|
with excutils.save_and_reraise_exception():
|
|
if network_info is not None:
|
|
network_info.wait(do_raise=False)
|
|
try:
|
|
self._deallocate_network(context, instance)
|
|
except Exception:
|
|
msg = _('Failed to dealloc network '
|
|
'for failed instance')
|
|
LOG.exception(msg, instance=instance)
|
|
except Exception:
|
|
exc_info = sys.exc_info()
|
|
# try to re-schedule instance:
|
|
# Make sure the async call finishes
|
|
if network_info is not None:
|
|
network_info.wait(do_raise=False)
|
|
self._reschedule_or_error(
|
|
context,
|
|
instance,
|
|
exc_info,
|
|
requested_networks,
|
|
admin_password,
|
|
injected_files_orig,
|
|
is_first_time,
|
|
request_spec,
|
|
filter_properties,
|
|
bdms,
|
|
legacy_bdm_in_spec)
|
|
raise exc_info[0], exc_info[1], exc_info[2]
|
|
|
|
# spawn success
|
|
return instance, network_info
|
|
|
|
def _heal_proxy_ports(self, context, instance, network_info):
|
|
physical_ports = []
|
|
for netObj in network_info:
|
|
net_id = netObj['network']['id']
|
|
physical_net_id = None
|
|
ovs_interface_mac = netObj['address']
|
|
fixed_ips = []
|
|
physicalNetIdExiFlag = False
|
|
if net_id in self.cascading_info_mapping['networks']:
|
|
physical_net_id = \
|
|
self.cascading_info_mapping['networks'][net_id]
|
|
physicalNetIdExiFlag = True
|
|
LOG.debug(_('Physical network has been created in physical'
|
|
' leval,logicalNetId:%s, physicalNetId: %s '),
|
|
net_id, physical_net_id)
|
|
if not physicalNetIdExiFlag:
|
|
raise exception.NetworkNotFound(network_id=net_id)
|
|
fixed_ips.append(
|
|
{'ip_address':
|
|
netObj['network']['subnets']
|
|
[0]['ips'][0]['address']}
|
|
)
|
|
reqbody = {'port':
|
|
{
|
|
'tenant_id': instance['project_id'],
|
|
'admin_state_up': True,
|
|
'network_id': physical_net_id,
|
|
'mac_address': ovs_interface_mac,
|
|
'fixed_ips': fixed_ips,
|
|
'binding:profile':
|
|
{"cascading_port_id": netObj['ovs_interfaceid']}
|
|
}
|
|
}
|
|
neutronClient = self._get_neutron_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_neutron_url)
|
|
try:
|
|
bodyReps = neutronClient.create_port(reqbody)
|
|
physical_ports.append(bodyReps)
|
|
LOG.debug(_('Finish to create Physical port, bodyReps %s'),
|
|
bodyReps)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Fail to create physical port reqbody %s .'),
|
|
reqbody)
|
|
|
|
return physical_ports
|
|
|
|
def _heal_proxy_networks(self, context, instance, network_info):
|
|
cascaded_network_list = []
|
|
self.cascading_info_mapping = {}
|
|
self.cascading_info_mapping['networks'] = {}
|
|
cascading_info_mapping_file = os.path.join(
|
|
CONF.instances_path,
|
|
'cascading_info_mapping.json')
|
|
if os.path.isfile(cascading_info_mapping_file):
|
|
cascading_info_mapping_file_context = libvirt_utils.load_file(
|
|
cascading_info_mapping_file)
|
|
mapping_networks = jsonutils.loads(
|
|
cascading_info_mapping_file_context)['networks']
|
|
self.cascading_info_mapping['networks'] = mapping_networks
|
|
for netObj in network_info:
|
|
net_id = netObj['network']['id']
|
|
physicalNetIdExiFlag = False
|
|
if net_id in self.cascading_info_mapping['networks']:
|
|
physicalNetIdExiFlag = True
|
|
physicalNetId = self.cascading_info_mapping['networks'][net_id]
|
|
cascaded_network_list.append(physicalNetId)
|
|
LOG.debug(_('Physical network has been exist, do not'
|
|
' need to create,logicalNetId:%s,'
|
|
'physicalNetId: %s '), net_id, physicalNetId)
|
|
if not physicalNetIdExiFlag:
|
|
try:
|
|
LOG.debug(_('Physical network do not be exist,'
|
|
'need to create,logicalNetId:%s'),
|
|
net_id)
|
|
kwargs = {
|
|
'username': cfg.CONF.neutron_admin_username,
|
|
'password': cfg.CONF.neutron_admin_password,
|
|
'tenant': cfg.CONF.neutron_admin_tenant_name,
|
|
'auth_url': cfg.CONF.neutron_admin_auth_url,
|
|
'region_name': cfg.CONF.os_region_name
|
|
}
|
|
reqCon = compute_context.RequestContext(**kwargs)
|
|
neutron = neutronv2.get_client(reqCon, True)
|
|
logicalnets = self.network_api._get_available_networks(
|
|
reqCon,
|
|
instance['project_id'],
|
|
[net_id],
|
|
neutron)
|
|
neutronClient = self._get_neutron_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_neutron_url)
|
|
|
|
if logicalnets[0]['provider:network_type'] == 'vxlan':
|
|
reqNetwork = {
|
|
'network': {
|
|
'provider:network_type': logicalnets[0]['provider:network_type'],
|
|
'provider:segmentation_id': logicalnets[0]['provider:segmentation_id'],
|
|
'tenant_id': instance['project_id'],
|
|
'admin_state_up': True}}
|
|
elif logicalnets[0]['provider:network_type'] == 'flat':
|
|
reqNetwork = {
|
|
'network': {
|
|
'provider:network_type': logicalnets[0]['provider:network_type'],
|
|
'provider:physical_network': logicalnets[0]['provider:physical_network'],
|
|
'tenant_id': instance['project_id'],
|
|
'admin_state_up': True}}
|
|
else:
|
|
reqNetwork = {
|
|
'network': {
|
|
'provider:network_type': logicalnets[0]['provider:network_type'],
|
|
'provider:physical_network': logicalnets[0]['provider:physical_network'],
|
|
'provider:segmentation_id': logicalnets[0]['provider:segmentation_id'],
|
|
'tenant_id': instance['project_id'],
|
|
'admin_state_up': True}}
|
|
repsNetwork = neutronClient.create_network(reqNetwork)
|
|
self.cascading_info_mapping['networks'][net_id] = \
|
|
repsNetwork['network']['id']
|
|
cascaded_network_list.append(repsNetwork['network']['id'])
|
|
LOG.debug(_('Finish to create Physical network,'
|
|
'repsNetwork %s'), reqNetwork)
|
|
reqSubnet = {
|
|
'subnet': {
|
|
'network_id': repsNetwork['network']['id'],
|
|
'cidr': netObj['network']['subnets'][0]['cidr'],
|
|
'ip_version': netObj['network']['subnets'][0]['version'],
|
|
'tenant_id': instance['project_id']}}
|
|
neutronClient.create_subnet(reqSubnet)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Fail to synchronizate physical network'))
|
|
|
|
cascading_info_mapping_path = os.path.join(
|
|
CONF.instances_path,
|
|
'cascading_info_mapping.json')
|
|
libvirt_utils.write_to_file(
|
|
cascading_info_mapping_path,
|
|
jsonutils.dumps(
|
|
self.cascading_info_mapping))
|
|
|
|
return cascaded_network_list
|
|
|
|
def _log_original_error(self, exc_info, instance_uuid):
|
|
LOG.error(_('Error: %s') % exc_info[1], instance_uuid=instance_uuid,
|
|
exc_info=exc_info)
|
|
|
|
def _cleanup_volumes(self, context, instance_uuid, bdms):
|
|
for bdm in bdms:
|
|
LOG.debug(_("terminating bdm %s") % bdm,
|
|
instance_uuid=instance_uuid)
|
|
if bdm.volume_id and bdm.delete_on_termination:
|
|
self.volume_api.delete(context, bdm.volume_id)
|
|
# NOTE(vish): bdms will be deleted on instance destroy
|
|
|
|
def _reschedule_or_error(
|
|
self,
|
|
context,
|
|
instance,
|
|
exc_info,
|
|
requested_networks,
|
|
admin_password,
|
|
injected_files,
|
|
is_first_time,
|
|
request_spec,
|
|
filter_properties,
|
|
bdms=None,
|
|
legacy_bdm_in_spec=True):
|
|
instance_uuid = instance['uuid']
|
|
rescheduled = False
|
|
|
|
compute_utils.add_instance_fault_from_exc(
|
|
context,
|
|
self.conductor_api,
|
|
instance,
|
|
exc_info[1],
|
|
exc_info=exc_info)
|
|
self._notify_about_instance_usage(
|
|
context,
|
|
instance,
|
|
'instance.create.error',
|
|
fault=exc_info[1])
|
|
|
|
try:
|
|
LOG.debug(_("Clean up resource before rescheduling."),
|
|
instance=instance)
|
|
if bdms is None:
|
|
bdms = (block_device_obj.BlockDeviceMappingList.
|
|
get_by_instance_uuid(context, instance.uuid))
|
|
|
|
self._shutdown_instance(context, instance,
|
|
bdms, requested_networks)
|
|
self._cleanup_volumes(context, instance['uuid'], bdms)
|
|
except Exception:
|
|
# do not attempt retry if clean up failed:
|
|
with excutils.save_and_reraise_exception():
|
|
self._log_original_error(exc_info, instance_uuid)
|
|
|
|
return rescheduled
|
|
|
|
def _quota_rollback(self, context, reservations, project_id=None,
|
|
user_id=None):
|
|
if reservations:
|
|
self.conductor_api.quota_rollback(context, reservations,
|
|
project_id=project_id,
|
|
user_id=user_id)
|
|
|
|
def _complete_deletion(self, context, instance, bdms,
|
|
quotas, system_meta):
|
|
if quotas:
|
|
quotas.commit()
|
|
|
|
# ensure block device mappings are not leaked
|
|
for bdm in bdms:
|
|
bdm.destroy()
|
|
|
|
self._notify_about_instance_usage(context, instance, "delete.end",
|
|
system_metadata=system_meta)
|
|
|
|
if CONF.vnc_enabled or CONF.spice.enabled:
|
|
if CONF.cells.enable:
|
|
self.cells_rpcapi.consoleauth_delete_tokens(context,
|
|
instance.uuid)
|
|
else:
|
|
self.consoleauth_rpcapi.delete_tokens_for_instance(
|
|
context,
|
|
instance.uuid)
|
|
|
|
@hooks.add_hook("delete_instance")
|
|
def _delete_instance(self, context, instance, bdms,
|
|
reservations=None):
|
|
"""Delete an instance on this host. Commit or rollback quotas
|
|
as necessary.
|
|
"""
|
|
instance_uuid = instance['uuid']
|
|
|
|
project_id, user_id = quotas_obj.ids_from_instance(context, instance)
|
|
|
|
was_soft_deleted = instance['vm_state'] == vm_states.SOFT_DELETED
|
|
if was_soft_deleted:
|
|
# Instances in SOFT_DELETED vm_state have already had quotas
|
|
# decremented.
|
|
try:
|
|
self._quota_rollback(context, reservations,
|
|
project_id=project_id,
|
|
user_id=user_id)
|
|
except Exception:
|
|
pass
|
|
reservations = None
|
|
|
|
try:
|
|
events = self.instance_events.clear_events_for_instance(instance)
|
|
if events:
|
|
LOG.debug(_('Events pending at deletion: %(events)s'),
|
|
{'events': ','.join(events.keys())},
|
|
instance=instance)
|
|
db_inst = obj_base.obj_to_primitive(instance)
|
|
instance.info_cache.delete()
|
|
self._notify_about_instance_usage(context, instance,
|
|
"delete.start")
|
|
self._shutdown_instance(context, db_inst, bdms)
|
|
# NOTE(vish): We have already deleted the instance, so we have
|
|
# to ignore problems cleaning up the volumes. It
|
|
# would be nice to let the user know somehow that
|
|
# the volume deletion failed, but it is not
|
|
# acceptable to have an instance that can not be
|
|
# deleted. Perhaps this could be reworked in the
|
|
# future to set an instance fault the first time
|
|
# and to only ignore the failure if the instance
|
|
# is already in ERROR.
|
|
try:
|
|
self._cleanup_volumes(context, instance_uuid, bdms)
|
|
except Exception as exc:
|
|
err_str = _("Ignoring volume cleanup failure due to %s")
|
|
LOG.warn(err_str % exc, instance=instance)
|
|
# if a delete task succeed, always update vm state and task
|
|
# state without expecting task state to be DELETING
|
|
instance.vm_state = vm_states.DELETED
|
|
instance.task_state = None
|
|
instance.terminated_at = timeutils.utcnow()
|
|
instance.save()
|
|
system_meta = utils.instance_sys_meta(instance)
|
|
db_inst = self.conductor_api.instance_destroy(
|
|
context, obj_base.obj_to_primitive(instance))
|
|
instance = instance_obj.Instance._from_db_object(context,
|
|
instance,
|
|
db_inst)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
self._quota_rollback(context, reservations,
|
|
project_id=project_id,
|
|
user_id=user_id)
|
|
|
|
quotas = quotas_obj.Quotas.from_reservations(context,
|
|
reservations,
|
|
instance=instance)
|
|
self._complete_deletion(context,
|
|
instance,
|
|
bdms,
|
|
quotas,
|
|
system_meta)
|
|
|
|
@wrap_exception()
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def terminate_instance(self, context, instance, bdms, reservations):
|
|
"""Terminate an instance on this host."""
|
|
# NOTE (ndipanov): If we get non-object BDMs, just get them from the
|
|
# db again, as this means they are sent in the old format and we want
|
|
# to avoid converting them back when we can just get them.
|
|
# Remove this when we bump the RPC major version to 4.0
|
|
if (bdms and
|
|
any(not isinstance(bdm, block_device_obj.BlockDeviceMapping)
|
|
for bdm in bdms)):
|
|
bdms = (block_device_obj.BlockDeviceMappingList.
|
|
get_by_instance_uuid(context, instance.uuid))
|
|
|
|
@utils.synchronized(instance['uuid'])
|
|
def do_terminate_instance(instance, bdms):
|
|
try:
|
|
self._delete_instance(context, instance, bdms,
|
|
reservations=reservations)
|
|
except exception.InstanceNotFound:
|
|
LOG.info(_("Instance disappeared during terminate"),
|
|
instance=instance)
|
|
except Exception:
|
|
# As we're trying to delete always go to Error if something
|
|
# goes wrong that _delete_instance can't handle.
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception(_('Setting instance vm_state to ERROR'),
|
|
instance=instance)
|
|
self._set_instance_error_state(context, instance['uuid'])
|
|
|
|
do_terminate_instance(instance, bdms)
|
|
|
|
def _heal_syn_server_metadata(self, context,
|
|
cascadingInsId, cascadedInsId):
|
|
"""
|
|
when only reboots the server scenario,
|
|
needs to synchronize server metadata between
|
|
logical and physical openstack.
|
|
"""
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
cascadedSerInf = cascadedNovaCli.servers.get(cascadedInsId)
|
|
cascadedSerMedInf = cascadedSerInf.metadata
|
|
|
|
cascadingNovCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.os_region_name,
|
|
cfg.CONF.cascading_nova_url)
|
|
cascadingSerInf = cascadingNovCli.servers.get(cascadingInsId)
|
|
cascadingSerMedInf = cascadingSerInf.metadata
|
|
|
|
tmpCascadedSerMedInf = dict(cascadedSerMedInf)
|
|
del tmpCascadedSerMedInf['mapping_uuid']
|
|
|
|
if tmpCascadedSerMedInf == cascadingSerMedInf:
|
|
LOG.debug(_("Don't need to synchronize server metadata between"
|
|
"logical and physical openstack."))
|
|
return
|
|
else:
|
|
LOG.debug(_('synchronize server metadata between logical and'
|
|
'physical openstack,cascadingSerMedInf %s,cascadedSerMedInf %s'),
|
|
cascadingSerMedInf,
|
|
cascadedSerMedInf)
|
|
delKeys = []
|
|
for key in cascadedSerMedInf:
|
|
if key != 'mapping_uuid' and key not in cascadingSerMedInf:
|
|
delKeys.append(key)
|
|
if len(delKeys) > 0:
|
|
cascadedNovaCli.servers.delete_meta(cascadedInsId, delKeys)
|
|
cascadingSerMedInf['mapping_uuid'] = \
|
|
cascadedSerMedInf['mapping_uuid']
|
|
cascadedNovaCli.servers.set_meta(cascadedInsId, cascadingSerMedInf)
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def pause_instance(self, context, instance):
|
|
"""Pause an instance on this host."""
|
|
context = context.elevated()
|
|
LOG.audit(_('Pausing'), context=context, instance=instance)
|
|
self._notify_about_instance_usage(context, instance, 'pause.start')
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('start vm failed,can not find server'
|
|
'in cascaded layer.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
cascadedNovaCli.servers.pause(cascaded_instance_id)
|
|
self._notify_about_instance_usage(context, instance, 'pause.end')
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_fault
|
|
@delete_image_on_error
|
|
def snapshot_instance(self, context, image_id, instance):
|
|
"""Snapshot an instance on this host.
|
|
|
|
:param context: security context
|
|
:param instance: a nova.objects.instance.Instance object
|
|
:param image_id: glance.db.sqlalchemy.models.Image.Id
|
|
"""
|
|
# NOTE(dave-mcnally) the task state will already be set by the api
|
|
# but if the compute manager has crashed/been restarted prior to the
|
|
# request getting here the task state may have been cleared so we set
|
|
# it again and things continue normally
|
|
glanceClient = glance.GlanceClientWrapper()
|
|
image = glanceClient.call(context, 2, 'get', image_id)
|
|
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('can not snapshot instance server %s.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
resp_image_id = cascadedNovaCli.servers.create_image(
|
|
cascaded_instance_id,
|
|
image['name'])
|
|
# update image's location
|
|
url = '%s/v2/images/%s' % (CONF.cascaded_glance_url, resp_image_id)
|
|
locations = [{
|
|
'url': url,
|
|
'metadata': {
|
|
'image_id': str(resp_image_id),
|
|
'image_from': 'snapshot'
|
|
}
|
|
}]
|
|
glanceClient.call(context, 2, 'update', image_id,
|
|
remove_props=None, locations=locations)
|
|
LOG.debug(_('Finish update image %s locations %s'),
|
|
image_id, locations)
|
|
|
|
def pre_start_hook(self):
|
|
"""After the service is initialized, but before we fully bring
|
|
the service up by listening on RPC queues, make sure to update
|
|
our available resources (and indirectly our available nodes).
|
|
"""
|
|
self.update_available_resource(nova.context.get_admin_context())
|
|
|
|
@contextlib.contextmanager
|
|
def _error_out_instance_on_exception(self, context, instance_uuid,
|
|
reservations=None,
|
|
instance_state=vm_states.ACTIVE):
|
|
try:
|
|
yield
|
|
except NotImplementedError as error:
|
|
with excutils.save_and_reraise_exception():
|
|
self._quota_rollback(context, reservations)
|
|
LOG.info(_("Setting instance back to %(state)s after: "
|
|
"%(error)s") %
|
|
{'state': instance_state, 'error': error},
|
|
instance_uuid=instance_uuid)
|
|
self._instance_update(context, instance_uuid,
|
|
vm_state=instance_state,
|
|
task_state=None)
|
|
except exception.InstanceFaultRollback as error:
|
|
self._quota_rollback(context, reservations)
|
|
LOG.info(_("Setting instance back to ACTIVE after: %s"),
|
|
error, instance_uuid=instance_uuid)
|
|
self._instance_update(context, instance_uuid,
|
|
vm_state=vm_states.ACTIVE,
|
|
task_state=None)
|
|
raise error.inner_exception
|
|
except Exception as error:
|
|
LOG.exception(_('Setting instance vm_state to ERROR'),
|
|
instance_uuid=instance_uuid)
|
|
with excutils.save_and_reraise_exception():
|
|
self._quota_rollback(context, reservations)
|
|
self._set_instance_error_state(context, instance_uuid)
|
|
|
|
def _get_volume_bdms(self, bdms, legacy=True):
|
|
"""Return only bdms that have a volume_id."""
|
|
if legacy:
|
|
return [bdm for bdm in bdms if bdm['volume_id']]
|
|
else:
|
|
return [bdm for bdm in bdms
|
|
if bdm['destination_type'] == 'volume']
|
|
|
|
@object_compat
|
|
@messaging.expected_exceptions(exception.PreserveEphemeralNotSupported)
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def rebuild_instance(self, context, instance, orig_image_ref, image_ref,
|
|
injected_files, new_pass, orig_sys_metadata,
|
|
bdms, recreate, on_shared_storage,
|
|
preserve_ephemeral=False):
|
|
"""Destroy and re-make this instance.
|
|
|
|
A 'rebuild' effectively purges all existing data from the system and
|
|
remakes the VM with given 'metadata' and 'personalities'.
|
|
|
|
:param context: `nova.RequestContext` object
|
|
:param instance: Instance object
|
|
:param orig_image_ref: Original image_ref before rebuild
|
|
:param image_ref: New image_ref for rebuild
|
|
:param injected_files: Files to inject
|
|
:param new_pass: password to set on rebuilt instance
|
|
:param orig_sys_metadata: instance system metadata from pre-rebuild
|
|
:param bdms: block-device-mappings to use for rebuild
|
|
:param recreate: True if the instance is being recreated (e.g. the
|
|
hypervisor it was on failed) - cleanup of old state will be
|
|
skipped.
|
|
:param on_shared_storage: True if instance files on shared storage
|
|
:param preserve_ephemeral: True if the default ephemeral storage
|
|
partition must be preserved on rebuild
|
|
"""
|
|
context = context.elevated()
|
|
with self._error_out_instance_on_exception(context, instance['uuid']):
|
|
LOG.audit(_("Rebuilding instance"), context=context,
|
|
instance=instance)
|
|
if bdms is None:
|
|
bdms = self.conductor_api.\
|
|
block_device_mapping_get_all_by_instance(
|
|
context, instance)
|
|
# NOTE(sirp): this detach is necessary b/c we will reattach the
|
|
# volumes in _prep_block_devices below.
|
|
for bdm in self._get_volume_bdms(bdms):
|
|
self.volume_api.detach(context, bdm['volume_id'])
|
|
|
|
kwargs = {}
|
|
disk_config = None
|
|
if len(injected_files) > 0:
|
|
kwargs['personality'] = injected_files
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('Rebuild failed,can not find server %s '),
|
|
instance['uuid'])
|
|
return
|
|
if cfg.CONF.cascaded_glance_flag:
|
|
image_uuid = self._get_cascaded_image_uuid(context,
|
|
image_ref)
|
|
else:
|
|
image_uuid = image_ref
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
cascadedNovaCli.servers.rebuild(cascaded_instance_id, image_uuid,
|
|
new_pass, disk_config, **kwargs)
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def suspend_instance(self, context, instance):
|
|
"""Suspend the given instance."""
|
|
context = context.elevated()
|
|
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('start vm failed,can not find server '
|
|
'in cascaded layer.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
cascadedNovaCli.servers.suspend(cascaded_instance_id)
|
|
self._notify_about_instance_usage(context, instance, 'suspend')
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def resume_instance(self, context, instance):
|
|
"""Resume the given suspended instance."""
|
|
context = context.elevated()
|
|
LOG.audit(_('Resuming'), context=context, instance=instance)
|
|
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('resume server,but can not find server'),
|
|
instance['uuid'])
|
|
return
|
|
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
cascadedNovaCli.servers.resume(cascaded_instance_id)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to resume server %s .'),
|
|
cascaded_instance_id)
|
|
self._notify_about_instance_usage(context, instance, 'resume')
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def unpause_instance(self, context, instance):
|
|
"""Unpause a paused instance on this host."""
|
|
context = context.elevated()
|
|
LOG.audit(_('Unpausing'), context=context, instance=instance)
|
|
self._notify_about_instance_usage(context, instance, 'unpause.start')
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('start vm failed,can not find server'
|
|
' in cascaded layer.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
cascadedNovaCli.servers.unpause(cascaded_instance_id)
|
|
self._notify_about_instance_usage(context, instance, 'unpause.end')
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def start_instance(self, context, instance):
|
|
"""Starting an instance on this host."""
|
|
self._notify_about_instance_usage(context, instance, "power_on.start")
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('start vm failed,can not find server'
|
|
' in cascaded layer.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
cascadedNovaCli.servers.start(cascaded_instance_id)
|
|
self._notify_about_instance_usage(context, instance, "power_on.end")
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def stop_instance(self, context, instance):
|
|
"""Stopping an instance on this host."""
|
|
self._notify_about_instance_usage(context,
|
|
instance, "power_off.start")
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('stop vm failed,can not find server'
|
|
' in cascaded layer.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
cascadedNovaCli.servers.stop(cascaded_instance_id)
|
|
self._notify_about_instance_usage(context, instance, "power_off.end")
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def reboot_instance(self, context, instance, block_device_info,
|
|
reboot_type):
|
|
"""Reboot an instance on this host."""
|
|
self._notify_about_instance_usage(context, instance, "reboot.start")
|
|
context = context.elevated()
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('Reboot can not find server %s.'), instance)
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
self._heal_syn_server_metadata(context, instance['uuid'],
|
|
cascaded_instance_id)
|
|
cascadedNovaCli.servers.reboot(cascaded_instance_id, reboot_type)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to reboot server %s .'),
|
|
cascaded_instance_id)
|
|
self._notify_about_instance_usage(context, instance, "reboot.end")
|
|
|
|
def _delete_proxy_instance(self, context, instance):
|
|
proxy_instance_id = instance['mapping_uuid']
|
|
if proxy_instance_id is None:
|
|
LOG.error(_('Delete server %s,but can not find this server'),
|
|
proxy_instance_id)
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
cascadedNovaCli.servers.delete(proxy_instance_id)
|
|
self._instance_update(
|
|
context,
|
|
instance['uuid'],
|
|
vm_state=vm_states.DELETED,
|
|
task_state=None)
|
|
LOG.debug(_('delete the server %s from nova-proxy'),
|
|
instance['uuid'])
|
|
except Exception:
|
|
if isinstance(sys.exc_info()[1], novaclient.exceptions.NotFound):
|
|
return
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to delete server %s'), proxy_instance_id)
|
|
|
|
def _get_instance_nw_info(self, context, instance, use_slave=False):
|
|
"""Get a list of dictionaries of network data of an instance."""
|
|
if (not hasattr(instance, 'system_metadata') or
|
|
len(instance['system_metadata']) == 0):
|
|
# NOTE(danms): Several places in the code look up instances without
|
|
# pulling system_metadata for performance, and call this function.
|
|
# If we get an instance without it, re-fetch so that the call
|
|
# to network_api (which requires it for instance_type) will
|
|
# succeed.
|
|
instance = instance_obj.Instance.get_by_uuid(context,
|
|
instance['uuid'],
|
|
use_slave=use_slave)
|
|
|
|
network_info = self.network_api.get_instance_nw_info(context,
|
|
instance)
|
|
return network_info
|
|
|
|
def _get_instance_volume_block_device_info(self, context, instance,
|
|
refresh_conn_info=False,
|
|
bdms=None):
|
|
"""Transform volumes to the driver block_device format."""
|
|
|
|
if not bdms:
|
|
bdms = (block_device_obj.BlockDeviceMappingList.
|
|
get_by_instance_uuid(context, instance['uuid']))
|
|
block_device_mapping = (
|
|
driver_block_device.convert_volumes(bdms) +
|
|
driver_block_device.convert_snapshots(bdms) +
|
|
driver_block_device.convert_images(bdms))
|
|
|
|
if not refresh_conn_info:
|
|
# if the block_device_mapping has no value in connection_info
|
|
# (returned as None), don't include in the mapping
|
|
block_device_mapping = [
|
|
bdm for bdm in block_device_mapping
|
|
if bdm.get('connection_info')]
|
|
else:
|
|
block_device_mapping = driver_block_device.refresh_conn_infos(
|
|
block_device_mapping, context, instance, self.volume_api,
|
|
self.driver)
|
|
|
|
if self.use_legacy_block_device_info:
|
|
block_device_mapping = driver_block_device.legacy_block_devices(
|
|
block_device_mapping)
|
|
return {'block_device_mapping': block_device_mapping}
|
|
|
|
def _try_deallocate_network(self, context, instance,
|
|
requested_networks=None):
|
|
try:
|
|
# tear down allocated network structure
|
|
self._deallocate_network(context, instance, requested_networks)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to deallocate network for instance.'),
|
|
instance=instance)
|
|
self._set_instance_error_state(context, instance['uuid'])
|
|
|
|
def _deallocate_network(self, context, instance,
|
|
requested_networks=None):
|
|
LOG.debug(_('Deallocating network for instance'), instance=instance)
|
|
self.network_api.deallocate_for_instance(
|
|
context, instance, requested_networks=requested_networks)
|
|
|
|
def _shutdown_instance(self, context, instance,
|
|
bdms, requested_networks=None, notify=True):
|
|
"""Shutdown an instance on this host."""
|
|
context = context.elevated()
|
|
LOG.audit(_('%(action_str)s instance') % {'action_str': 'Terminating'},
|
|
context=context, instance=instance)
|
|
|
|
if notify:
|
|
self._notify_about_instance_usage(context, instance,
|
|
"shutdown.start")
|
|
|
|
# get network info before tearing down
|
|
try:
|
|
self._get_instance_nw_info(context, instance)
|
|
except (exception.NetworkNotFound, exception.NoMoreFixedIps,
|
|
exception.InstanceInfoCacheNotFound):
|
|
network_model.NetworkInfo()
|
|
|
|
# NOTE(vish) get bdms before destroying the instance
|
|
vol_bdms = [bdm for bdm in bdms if bdm.is_volume]
|
|
# block_device_info = self._get_instance_volume_block_device_info(
|
|
# context, instance, bdms=bdms)
|
|
|
|
# NOTE(melwitt): attempt driver destroy before releasing ip, may
|
|
# want to keep ip allocated for certain failures
|
|
try:
|
|
self._delete_proxy_instance(context, instance)
|
|
except exception.InstancePowerOffFailure:
|
|
# if the instance can't power off, don't release the ip
|
|
with excutils.save_and_reraise_exception():
|
|
pass
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
# deallocate ip and fail without proceeding to
|
|
# volume api calls, preserving current behavior
|
|
self._try_deallocate_network(context, instance,
|
|
requested_networks)
|
|
|
|
self._try_deallocate_network(context, instance, requested_networks)
|
|
|
|
for bdm in vol_bdms:
|
|
try:
|
|
# NOTE(vish): actual driver detach done in driver.destroy, so
|
|
# just tell cinder that we are done with it.
|
|
# connector = self.driver.get_volume_connector(instance)
|
|
# self.volume_api.terminate_connection(context,
|
|
# bdm.volume_id,
|
|
# connector)
|
|
self.volume_api.detach(context, bdm.volume_id)
|
|
except exception.DiskNotFound as exc:
|
|
LOG.warn(_('Ignoring DiskNotFound: %s') % exc,
|
|
instance=instance)
|
|
except exception.VolumeNotFound as exc:
|
|
LOG.warn(_('Ignoring VolumeNotFound: %s') % exc,
|
|
instance=instance)
|
|
|
|
if notify:
|
|
self._notify_about_instance_usage(context, instance,
|
|
"shutdown.end")
|
|
|
|
def _get_nova_pythonClient(self, context, regNam, nova_url):
|
|
try:
|
|
# kwargs = {'auth_token':None,
|
|
# 'username':context.values['user_name'],
|
|
# 'password':cfg.CONF.nova_admin_password,
|
|
# 'aws_creds':None,'tenant':None,
|
|
# 'tenant_id':context.values['tenant'],
|
|
# 'auth_url':cfg.CONF.keystone_auth_url,
|
|
# 'roles':context.values['roles'],
|
|
# 'is_admin':context.values['is_admin'],
|
|
# 'region_name':regNam
|
|
# }
|
|
kwargs = {
|
|
'auth_token': context.auth_token,
|
|
'username': context.user_name,
|
|
'tenant_id': context.tenant,
|
|
'auth_url': cfg.CONF.keystone_auth_url,
|
|
'roles': context.roles,
|
|
'is_admin': context.is_admin,
|
|
'region_name': regNam,
|
|
'nova_url': nova_url
|
|
}
|
|
reqCon = compute_context.RequestContext(**kwargs)
|
|
openStackClients = clients.OpenStackClients(reqCon)
|
|
novaClient = openStackClients.nova()
|
|
return novaClient
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to get nova python client.'))
|
|
|
|
def _get_neutron_pythonClient(self, context, regNam, neutrol_url):
|
|
try:
|
|
kwargs = {
|
|
'endpoint_url': neutrol_url,
|
|
'timeout': CONF.neutron_url_timeout,
|
|
'insecure': CONF.neutron_api_insecure,
|
|
'ca_cert': CONF.neutron_ca_certificates_file,
|
|
'username': CONF.neutron_admin_username,
|
|
'password': CONF.neutron_admin_password,
|
|
'tenant_name': CONF.neutron_admin_tenant_name,
|
|
'auth_url': CONF.neutron_admin_auth_url,
|
|
'auth_strategy': CONF.neutron_auth_strategy
|
|
}
|
|
neutronClient = clientv20.Client(**kwargs)
|
|
return neutronClient
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to get neutron python client.'))
|
|
|
|
def _reschedule(self, context, request_spec, filter_properties,
|
|
instance_uuid, scheduler_method, method_args, task_state,
|
|
exc_info=None):
|
|
"""Attempt to re-schedule a compute operation."""
|
|
|
|
retry = filter_properties.get('retry', None)
|
|
if not retry:
|
|
# no retry information, do not reschedule.
|
|
LOG.debug(_("Retry info not present, will not reschedule"),
|
|
instance_uuid=instance_uuid)
|
|
return
|
|
|
|
if not request_spec:
|
|
LOG.debug(_("No request spec, will not reschedule"),
|
|
instance_uuid=instance_uuid)
|
|
return
|
|
|
|
request_spec['instance_uuids'] = [instance_uuid]
|
|
|
|
LOG.debug(_("Re-scheduling %(method)s: attempt %(num)d") %
|
|
{'method': scheduler_method.func_name,
|
|
'num': retry['num_attempts']}, instance_uuid=instance_uuid)
|
|
|
|
# reset the task state:
|
|
self._instance_update(context, instance_uuid, task_state=task_state)
|
|
|
|
if exc_info:
|
|
# stringify to avoid circular ref problem in json serialization:
|
|
retry['exc'] = traceback.format_exception(*exc_info)
|
|
|
|
scheduler_method(context, *method_args)
|
|
return True
|
|
|
|
def _reschedule_resize_or_reraise(
|
|
self,
|
|
context,
|
|
image,
|
|
instance,
|
|
exc_info,
|
|
instance_type,
|
|
reservations,
|
|
request_spec,
|
|
filter_properties):
|
|
"""Try to re-schedule the resize or re-raise the original error to
|
|
error out the instance.
|
|
"""
|
|
if not request_spec:
|
|
request_spec = {}
|
|
if not filter_properties:
|
|
filter_properties = {}
|
|
|
|
rescheduled = False
|
|
instance_uuid = instance['uuid']
|
|
|
|
try:
|
|
# NOTE(comstud): remove the scheduler RPCAPI method when
|
|
# this is adjusted to send to conductor... and then
|
|
# deprecate the scheduler manager method.
|
|
scheduler_method = self.scheduler_rpcapi.prep_resize
|
|
instance_p = obj_base.obj_to_primitive(instance)
|
|
method_args = (instance_p, instance_type, image, request_spec,
|
|
filter_properties, reservations)
|
|
task_state = task_states.RESIZE_PREP
|
|
|
|
rescheduled = self._reschedule(
|
|
context,
|
|
request_spec,
|
|
filter_properties,
|
|
instance_uuid,
|
|
scheduler_method,
|
|
method_args,
|
|
task_state,
|
|
exc_info)
|
|
except Exception as error:
|
|
rescheduled = False
|
|
LOG.exception(_("Error trying to reschedule"),
|
|
instance_uuid=instance_uuid)
|
|
compute_utils.add_instance_fault_from_exc(
|
|
context,
|
|
self.conductor_api,
|
|
instance,
|
|
error,
|
|
exc_info=sys.exc_info())
|
|
self._notify_about_instance_usage(context, instance,
|
|
'resize.error', fault=error)
|
|
|
|
if rescheduled:
|
|
self._log_original_error(exc_info, instance_uuid)
|
|
compute_utils.add_instance_fault_from_exc(
|
|
context,
|
|
self.conductor_api,
|
|
instance,
|
|
exc_info[1],
|
|
exc_info=exc_info)
|
|
self._notify_about_instance_usage(
|
|
context,
|
|
instance,
|
|
'resize.error',
|
|
fault=exc_info[1])
|
|
else:
|
|
# not re-scheduling
|
|
raise exc_info[0], exc_info[1], exc_info[2]
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def prep_resize(self, context, image, instance, instance_type,
|
|
reservations, request_spec, filter_properties, node):
|
|
"""Initiates the process of moving a running instance to another host.
|
|
|
|
Possibly changes the RAM and disk size in the process.
|
|
|
|
"""
|
|
if node is None:
|
|
node = self.driver.get_available_nodes(refresh=True)[0]
|
|
LOG.debug(_("No node specified, defaulting to %s"), node,
|
|
instance=instance)
|
|
|
|
with self._error_out_instance_on_exception(context, instance['uuid'],
|
|
reservations):
|
|
self.conductor_api.notify_usage_exists(
|
|
context, instance, current_period=True)
|
|
self._notify_about_instance_usage(
|
|
context, instance, "resize.prep.start")
|
|
try:
|
|
self._prep_resize(context, image, instance,
|
|
instance_type, reservations,
|
|
request_spec, filter_properties,
|
|
node)
|
|
except Exception:
|
|
# try to re-schedule the resize elsewhere:
|
|
exc_info = sys.exc_info()
|
|
self._reschedule_resize_or_reraise(
|
|
context,
|
|
image,
|
|
instance,
|
|
exc_info,
|
|
instance_type,
|
|
reservations,
|
|
request_spec,
|
|
filter_properties)
|
|
finally:
|
|
extra_usage_info = dict(
|
|
new_instance_type=instance_type['name'],
|
|
new_instance_type_id=instance_type['id'])
|
|
|
|
self._notify_about_instance_usage(
|
|
context, instance, "resize.prep.end",
|
|
extra_usage_info=extra_usage_info)
|
|
|
|
def _prep_resize(self, context, image, instance, instance_type,
|
|
reservations, request_spec, filter_properties, node):
|
|
|
|
if not filter_properties:
|
|
filter_properties = {}
|
|
|
|
if not instance['host']:
|
|
self._set_instance_error_state(context, instance['uuid'])
|
|
msg = _('Instance has no source host')
|
|
raise exception.MigrationError(msg)
|
|
|
|
same_host = instance['host'] == self.host
|
|
if same_host and not CONF.allow_resize_to_same_host:
|
|
self._set_instance_error_state(context, instance['uuid'])
|
|
msg = _('destination same as source!')
|
|
raise exception.MigrationError(msg)
|
|
|
|
# NOTE(danms): Stash the new instance_type to avoid having to
|
|
# look it up in the database later
|
|
sys_meta = instance.system_metadata
|
|
flavors.save_flavor_info(sys_meta, instance_type, prefix='new_')
|
|
# NOTE(mriedem): Stash the old vm_state so we can set the
|
|
# resized/reverted instance back to the same state later.
|
|
vm_state = instance['vm_state']
|
|
LOG.debug(_('Stashing vm_state: %s'), vm_state, instance=instance)
|
|
sys_meta['old_vm_state'] = vm_state
|
|
instance.save()
|
|
|
|
limits = filter_properties.get('limits', {})
|
|
rt = self._get_resource_tracker(node)
|
|
with rt.resize_claim(context, instance, instance_type,
|
|
limits=limits) as claim:
|
|
LOG.audit(_('Migrating'), context=context, instance=instance)
|
|
self.compute_rpcapi.resize_instance(
|
|
context,
|
|
instance,
|
|
claim.migration,
|
|
image,
|
|
instance_type,
|
|
reservations)
|
|
|
|
def _terminate_volume_connections(self, context, instance, bdms):
|
|
connector = self.driver.get_volume_connector(instance)
|
|
for bdm in bdms:
|
|
if bdm.is_volume:
|
|
self.volume_api.terminate_connection(context, bdm.volume_id,
|
|
connector)
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@errors_out_migration
|
|
@wrap_instance_fault
|
|
def resize_instance(self, context, instance, image,
|
|
reservations, migration, instance_type):
|
|
"""Starts the migration of a running instance to another host."""
|
|
with self._error_out_instance_on_exception(context, instance.uuid,
|
|
reservations):
|
|
if not instance_type:
|
|
instance_type = flavor_obj.Flavor.get_by_id(
|
|
context, migration['new_instance_type_id'])
|
|
|
|
network_info = self._get_instance_nw_info(context, instance)
|
|
|
|
migration.status = 'migrating'
|
|
migration.save(context.elevated())
|
|
|
|
instance.task_state = task_states.RESIZE_MIGRATING
|
|
instance.save(expected_task_state=task_states.RESIZE_PREP)
|
|
|
|
self._notify_about_instance_usage(
|
|
context, instance, "resize.start", network_info=network_info)
|
|
|
|
bdms = (block_device_obj.BlockDeviceMappingList.
|
|
get_by_instance_uuid(context, instance.uuid))
|
|
# block_device_info = self._get_instance_volume_block_device_info(
|
|
# context, instance, bdms=bdms)
|
|
|
|
# disk_info = self.driver.migrate_disk_and_power_off(
|
|
# context, instance, migration.dest_host,
|
|
# instance_type, network_info,
|
|
# block_device_info)
|
|
disk_info = None
|
|
|
|
self._terminate_volume_connections(context, instance, bdms)
|
|
|
|
migration_p = obj_base.obj_to_primitive(migration)
|
|
instance_p = obj_base.obj_to_primitive(instance)
|
|
self.conductor_api.network_migrate_instance_start(context,
|
|
instance_p,
|
|
migration_p)
|
|
|
|
migration.status = 'post-migrating'
|
|
migration.save(context.elevated())
|
|
|
|
instance.host = migration.dest_compute
|
|
instance.node = migration.dest_node
|
|
instance.task_state = task_states.RESIZE_MIGRATED
|
|
instance.save(expected_task_state=task_states.RESIZE_MIGRATING)
|
|
|
|
self.compute_rpcapi.finish_resize(
|
|
context,
|
|
instance,
|
|
migration,
|
|
image,
|
|
disk_info,
|
|
migration.dest_compute,
|
|
reservations=reservations)
|
|
|
|
self._notify_about_instance_usage(context, instance, "resize.end",
|
|
network_info=network_info)
|
|
self.instance_events.clear_events_for_instance(instance)
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@errors_out_migration
|
|
@wrap_instance_fault
|
|
def finish_resize(self, context, disk_info, image, instance,
|
|
reservations, migration):
|
|
"""Completes the migration process.
|
|
|
|
Sets up the newly transferred disk and turns on the instance at its
|
|
new host machine.
|
|
|
|
"""
|
|
try:
|
|
self._finish_resize(context, instance, migration,
|
|
disk_info, image)
|
|
self._quota_commit(context, reservations)
|
|
except Exception:
|
|
LOG.exception(_('Setting instance vm_state to ERROR'),
|
|
instance=instance)
|
|
with excutils.save_and_reraise_exception():
|
|
try:
|
|
self._quota_rollback(context, reservations)
|
|
except Exception as qr_error:
|
|
LOG.exception(_("Failed to rollback quota for failed "
|
|
"finish_resize: %s"),
|
|
qr_error, instance=instance)
|
|
self._set_instance_error_state(context, instance['uuid'])
|
|
|
|
@object_compat
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_fault
|
|
def reserve_block_device_name(self, context, instance, device,
|
|
volume_id, disk_bus=None, device_type=None):
|
|
# NOTE(ndipanov): disk_bus and device_type will be set to None if not
|
|
# passed (by older clients) and defaulted by the virt driver. Remove
|
|
# default values on the next major RPC version bump.
|
|
|
|
@utils.synchronized(instance['uuid'])
|
|
def do_reserve():
|
|
bdms = (
|
|
block_device_obj.BlockDeviceMappingList.get_by_instance_uuid(
|
|
context, instance.uuid))
|
|
|
|
device_name = compute_utils.get_device_name_for_instance(
|
|
context, instance, bdms, device)
|
|
|
|
# NOTE(vish): create bdm here to avoid race condition
|
|
bdm = block_device_obj.BlockDeviceMapping(
|
|
source_type='volume', destination_type='volume',
|
|
instance_uuid=instance.uuid,
|
|
volume_id=volume_id or 'reserved',
|
|
device_name=device_name,
|
|
disk_bus=disk_bus, device_type=device_type)
|
|
bdm.create(context)
|
|
|
|
return device_name
|
|
|
|
return do_reserve()
|
|
|
|
@object_compat
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_fault
|
|
def attach_volume(self, context, volume_id, mountpoint,
|
|
instance, bdm=None):
|
|
"""Attach a volume to an instance."""
|
|
if not bdm:
|
|
bdm = block_device_obj.BlockDeviceMapping.get_by_volume_id(
|
|
context, volume_id)
|
|
driver_bdm = driver_block_device.DriverVolumeBlockDevice(bdm)
|
|
try:
|
|
return self._attach_volume(context, instance, driver_bdm)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
bdm.destroy(context)
|
|
|
|
def _attach_volume(self, context, instance, bdm):
|
|
context = context.elevated()
|
|
LOG.audit(_('Attaching volume %(volume_id)s to %(mountpoint)s'),
|
|
{'volume_id': bdm.volume_id,
|
|
'mountpoint': bdm['mount_device']},
|
|
context=context, instance=instance)
|
|
try:
|
|
# bdm.attach(context, instance, self.volume_api, self.driver,
|
|
# do_check_attach=False, do_driver_attach=True)
|
|
self.volume_api.attach(context, bdm.volume_id,
|
|
instance['uuid'], bdm['mount_device'])
|
|
proxy_volume_id = None
|
|
try:
|
|
bodyReps = self.volume_api.get(context, bdm.volume_id)
|
|
proxy_volume_id = bodyReps['volume_metadata']['mapping_uuid']
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to get physical volume id ,logical'
|
|
' volume id %s,device %s'),
|
|
bdm.volume_id, bdm['mount_device'])
|
|
if proxy_volume_id is None:
|
|
LOG.error(_('attach_volume can not find physical volume id %s'
|
|
' in physical opensack lay,logical volume id %s'),
|
|
instance['uuid'], bdm.volume_id)
|
|
return
|
|
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
bodyReps = cascadedNovaCli.volumes.create_server_volume(
|
|
instance['mapping_uuid'],
|
|
proxy_volume_id, bdm['mount_device'])
|
|
except Exception: # pylint: disable=W0702
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception(_("Failed to attach %(volume_id)s "
|
|
"at %(mountpoint)s"),
|
|
{'volume_id': bdm.volume_id,
|
|
'mountpoint': bdm['mount_device']},
|
|
context=context, instance=instance)
|
|
self.volume_api.unreserve_volume(context, bdm.volume_id)
|
|
|
|
info = {'volume_id': bdm.volume_id}
|
|
self._notify_about_instance_usage(
|
|
context, instance, "volume.attach", extra_usage_info=info)
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_fault
|
|
def detach_volume(self, context, volume_id, instance):
|
|
"""Detach a volume from an instance."""
|
|
bdm = block_device_obj.BlockDeviceMapping.get_by_volume_id(
|
|
context, volume_id)
|
|
if CONF.volume_usage_poll_interval > 0:
|
|
vol_stats = []
|
|
mp = bdm.device_name
|
|
# Handle bootable volumes which will not contain /dev/
|
|
if '/dev/' in mp:
|
|
mp = mp[5:]
|
|
try:
|
|
vol_stats = self.driver.block_stats(instance['name'], mp)
|
|
except NotImplementedError:
|
|
pass
|
|
|
|
if vol_stats:
|
|
LOG.debug(_("Updating volume usage cache with totals"),
|
|
instance=instance)
|
|
rd_req, rd_bytes, wr_req, wr_bytes, flush_ops = vol_stats
|
|
self.conductor_api.vol_usage_update(context, volume_id,
|
|
rd_req, rd_bytes,
|
|
wr_req, wr_bytes,
|
|
instance,
|
|
update_totals=True)
|
|
|
|
self._detach_volume(context, instance, bdm)
|
|
self.volume_api.detach(context.elevated(), volume_id)
|
|
bdm.destroy()
|
|
info = dict(volume_id=volume_id)
|
|
self._notify_about_instance_usage(
|
|
context, instance, "volume.detach", extra_usage_info=info)
|
|
|
|
def _detach_volume(self, context, instance, bdm):
|
|
"""Do the actual driver detach using block device mapping."""
|
|
mp = bdm.device_name
|
|
volume_id = bdm.volume_id
|
|
|
|
LOG.audit(_('Detach volume %(volume_id)s from mountpoint %(mp)s'),
|
|
{'volume_id': volume_id, 'mp': mp},
|
|
context=context, instance=instance)
|
|
try:
|
|
proxy_volume_id = None
|
|
try:
|
|
bodyReps = self.volume_api.get(context, volume_id)
|
|
proxy_volume_id = bodyReps['volume_metadata']['mapping_uuid']
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to get physical volume id ,logical'
|
|
' volume id %s,device %s'),
|
|
volume_id, mp)
|
|
if proxy_volume_id is None:
|
|
LOG.error(_('detach_volume can not find physical volume id %s '
|
|
'in physical opensack lay,logical volume id %s'),
|
|
instance['uuid'], volume_id)
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
bodyReps = cascadedNovaCli.volumes.delete_server_volume(
|
|
instance['mapping_uuid'], proxy_volume_id)
|
|
except Exception: # pylint: disable=W0702
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.exception(_('Failed to detach volume %(volume_id)s '
|
|
'from %(mp)s'),
|
|
{'volume_id': volume_id, 'mp': mp},
|
|
context=context, instance=instance)
|
|
self.volume_api.roll_detaching(context, volume_id)
|
|
|
|
@wrap_exception()
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def confirm_resize(self, context, instance, reservations, migration):
|
|
|
|
@utils.synchronized(instance['uuid'])
|
|
def do_confirm_resize(context, instance, migration_id):
|
|
# NOTE(wangpan): Get the migration status from db, if it has been
|
|
# confirmed, we do nothing and return here
|
|
LOG.debug(_("Going to confirm migration %s") % migration_id,
|
|
context=context, instance=instance)
|
|
try:
|
|
# TODO(russellb) Why are we sending the migration object just
|
|
# to turn around and look it up from the db again?
|
|
migration = migration_obj.Migration.get_by_id(
|
|
context.elevated(), migration_id)
|
|
except exception.MigrationNotFound:
|
|
LOG.error(_("Migration %s is not found during confirmation") %
|
|
migration_id, context=context, instance=instance)
|
|
return
|
|
|
|
if migration.status == 'confirmed':
|
|
LOG.info(_("Migration %s is already confirmed") %
|
|
migration_id, context=context, instance=instance)
|
|
return
|
|
elif migration.status not in ('finished', 'confirming'):
|
|
LOG.warn(_("Unexpected confirmation status '%(status)s' of "
|
|
"migration %(id)s, exit confirmation process") %
|
|
{"status": migration.status, "id": migration_id},
|
|
context=context, instance=instance)
|
|
return
|
|
|
|
# NOTE(wangpan): Get the instance from db, if it has been
|
|
# deleted, we do nothing and return here
|
|
expected_attrs = ['metadata', 'system_metadata']
|
|
try:
|
|
instance = instance_obj.Instance.get_by_uuid(
|
|
context,
|
|
instance.uuid,
|
|
expected_attrs=expected_attrs)
|
|
except exception.InstanceNotFound:
|
|
LOG.info(_("Instance is not found during confirmation"),
|
|
context=context, instance=instance)
|
|
return
|
|
|
|
self._confirm_resize(context, instance, reservations=reservations,
|
|
migration=migration)
|
|
|
|
do_confirm_resize(context, instance, migration.id)
|
|
|
|
def _confirm_resize(self, context, instance, reservations=None,
|
|
migration=None):
|
|
"""Destroys the source instance."""
|
|
self._notify_about_instance_usage(context, instance,
|
|
"resize.confirm.start")
|
|
|
|
with self._error_out_instance_on_exception(context, instance['uuid'],
|
|
reservations):
|
|
# NOTE(danms): delete stashed migration information
|
|
# sys_meta, instance_type = self._cleanup_stored_instance_types(
|
|
# migration, instance)
|
|
# sys_meta.pop('old_vm_state', None)
|
|
#
|
|
# instance.system_metadata = sys_meta
|
|
# instance.save()
|
|
|
|
# NOTE(tr3buchet): tear down networks on source host
|
|
self.network_api.setup_networks_on_host(
|
|
context,
|
|
instance,
|
|
migration.source_compute,
|
|
teardown=True)
|
|
|
|
network_info = self._get_instance_nw_info(context, instance)
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.debug(_('Confirm resize can not find server %s.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
cascadedNovaCli.servers.confirm_resize(cascaded_instance_id)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to confirm resize server %s .'),
|
|
cascaded_instance_id)
|
|
|
|
migration.status = 'confirmed'
|
|
migration.save(context.elevated())
|
|
|
|
# rt = self._get_resource_tracker(migration.source_node)
|
|
# rt.drop_resize_claim(instance, prefix='old_')
|
|
|
|
# NOTE(mriedem): The old_vm_state could be STOPPED but the user
|
|
# might have manually powered up the instance to confirm the
|
|
# resize/migrate, so we need to check the current power state
|
|
# on the instance and set the vm_state appropriately. We default
|
|
# to ACTIVE because if the power state is not SHUTDOWN, we
|
|
# assume _sync_instance_power_state will clean it up.
|
|
p_state = instance.power_state
|
|
vm_state = None
|
|
if p_state == power_state.SHUTDOWN:
|
|
vm_state = vm_states.STOPPED
|
|
LOG.debug(_("Resized/migrated instance is powered off. "
|
|
"Setting vm_state to '%s'."), vm_state,
|
|
instance=instance)
|
|
else:
|
|
vm_state = vm_states.ACTIVE
|
|
|
|
instance.vm_state = vm_state
|
|
instance.task_state = None
|
|
instance.save(expected_task_state=[None, task_states.DELETING])
|
|
|
|
self._notify_about_instance_usage(
|
|
context, instance, "resize.confirm.end",
|
|
network_info=network_info)
|
|
|
|
self._quota_commit(context, reservations)
|
|
|
|
@messaging.expected_exceptions(NotImplementedError)
|
|
@wrap_exception()
|
|
@wrap_instance_fault
|
|
def get_console_output(self, context, instance, tail_length):
|
|
"""Send the console output for the given instance."""
|
|
instance = instance_obj.Instance._from_db_object(
|
|
context, instance_obj.Instance(), instance)
|
|
context = context.elevated()
|
|
LOG.audit(_("Get console output"), context=context,
|
|
instance=instance)
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.debug(_('get_vnc_console can not find server %s in'
|
|
' cascading_info_mapping %s .'),
|
|
instance['uuid'], self.cascading_info_mapping)
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
|
|
try:
|
|
output = cascadedNovaCli.servers.get_console_output(
|
|
cascaded_instance_id, tail_length)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to get_vnc_console server %s .'),
|
|
cascaded_instance_id)
|
|
return output
|
|
|
|
@object_compat
|
|
@wrap_exception()
|
|
@wrap_instance_fault
|
|
def get_vnc_console(self, context, console_type, instance):
|
|
"""Return connection information for a vnc console."""
|
|
context = context.elevated()
|
|
LOG.debug(_("Getting vnc console"), instance=instance)
|
|
token = str(uuid.uuid4())
|
|
|
|
if not CONF.vnc_enabled:
|
|
raise exception.ConsoleTypeInvalid(console_type=console_type)
|
|
|
|
try:
|
|
# access info token
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.debug(_('Get vnc_console can not find server %s .'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
bodyReps = cascadedNovaCli.servers.get_vnc_console(
|
|
cascaded_instance_id, console_type)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to get_vnc_console server %s .'),
|
|
cascaded_instance_id)
|
|
if console_type != 'novnc' and console_type != 'xvpvnc':
|
|
# For essex, novncproxy_base_url must include the full path
|
|
# including the html file (like http://myhost/vnc_auto.html)
|
|
raise exception.ConsoleTypeInvalid(console_type=console_type)
|
|
connect_info = {}
|
|
connect_info['token'] = token
|
|
connect_info['access_url'] = bodyReps['console']['url']
|
|
connect_info['host'] = CONF.vncserver_proxyclient_address
|
|
connect_info['port'] = CONF.novncproxy_port
|
|
connect_info['internal_access_path'] = None
|
|
except exception.InstanceNotFound:
|
|
if instance['vm_state'] != vm_states.BUILDING:
|
|
raise
|
|
raise exception.InstanceNotReady(instance_id=instance['uuid'])
|
|
|
|
return connect_info
|
|
|
|
def _cleanup_stored_instance_types(self, migration, instance,
|
|
restore_old=False):
|
|
"""Clean up "old" and "new" instance_type information stored in
|
|
instance's system_metadata. Optionally update the "current"
|
|
instance_type to the saved old one first.
|
|
|
|
Returns the updated system_metadata as a dict, as well as the
|
|
post-cleanup current instance type.
|
|
"""
|
|
sys_meta = instance.system_metadata
|
|
if restore_old:
|
|
instance_type = flavors.extract_flavor(instance, 'old_')
|
|
sys_meta = flavors.save_flavor_info(sys_meta, instance_type)
|
|
else:
|
|
instance_type = flavors.extract_flavor(instance)
|
|
|
|
flavors.delete_flavor_info(sys_meta, 'old_')
|
|
flavors.delete_flavor_info(sys_meta, 'new_')
|
|
|
|
return sys_meta, instance_type
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def finish_revert_resize(self, context, instance, reservations, migration):
|
|
"""Finishes the second half of reverting a resize.
|
|
|
|
Bring the original source instance state back (active/shutoff) and
|
|
revert the resized attributes in the database.
|
|
|
|
"""
|
|
with self._error_out_instance_on_exception(context, instance.uuid,
|
|
reservations):
|
|
self._get_instance_nw_info(context, instance)
|
|
|
|
self._notify_about_instance_usage(
|
|
context, instance, "resize.revert.start")
|
|
|
|
sys_meta, instance_type = self._cleanup_stored_instance_types(
|
|
migration, instance, True)
|
|
|
|
# NOTE(mriedem): delete stashed old_vm_state information; we
|
|
# default to ACTIVE for backwards compatibility if old_vm_state
|
|
# is not set
|
|
old_vm_state = sys_meta.pop('old_vm_state', vm_states.ACTIVE)
|
|
|
|
instance.system_metadata = sys_meta
|
|
instance.memory_mb = instance_type['memory_mb']
|
|
instance.vcpus = instance_type['vcpus']
|
|
instance.root_gb = instance_type['root_gb']
|
|
instance.ephemeral_gb = instance_type['ephemeral_gb']
|
|
instance.instance_type_id = instance_type['id']
|
|
instance.host = migration['source_compute']
|
|
instance.node = migration['source_node']
|
|
instance.save()
|
|
|
|
self.network_api.setup_networks_on_host(
|
|
context,
|
|
instance,
|
|
migration['source_compute'])
|
|
|
|
# block_device_info = self._get_instance_volume_block_device_info(
|
|
# context, instance, refresh_conn_info=True)
|
|
|
|
power_on = old_vm_state != vm_states.STOPPED
|
|
# self.driver.finish_revert_migration(context, instance,
|
|
# network_info,
|
|
# block_device_info, power_on)
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.debug(_('Revert resize can not find server %s.'),
|
|
instance['uuid'])
|
|
return
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
cascadedNovaCli.servers.revert_resize(cascaded_instance_id)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to resize server %s .'),
|
|
cascaded_instance_id)
|
|
|
|
instance.launched_at = timeutils.utcnow()
|
|
instance.save(expected_task_state=task_states.RESIZE_REVERTING)
|
|
|
|
instance_p = obj_base.obj_to_primitive(instance)
|
|
migration_p = obj_base.obj_to_primitive(migration)
|
|
self.conductor_api.network_migrate_instance_finish(context,
|
|
instance_p,
|
|
migration_p)
|
|
|
|
# if the original vm state was STOPPED, set it back to STOPPED
|
|
LOG.info(_("Updating instance to original state: '%s'") %
|
|
old_vm_state)
|
|
if power_on:
|
|
instance.vm_state = vm_states.ACTIVE
|
|
instance.task_state = None
|
|
instance.save()
|
|
else:
|
|
instance.task_state = task_states.POWERING_OFF
|
|
instance.save()
|
|
self.stop_instance(context, instance=instance)
|
|
|
|
self._notify_about_instance_usage(
|
|
context, instance, "resize.revert.end")
|
|
|
|
self._quota_commit(context, reservations)
|
|
|
|
@wrap_exception()
|
|
@reverts_task_state
|
|
@wrap_instance_event
|
|
@wrap_instance_fault
|
|
def revert_resize(self, context, instance, migration, reservations):
|
|
"""Destroys the new instance on the destination machine.
|
|
|
|
Reverts the model changes, and powers on the old instance on the
|
|
source machine.
|
|
|
|
"""
|
|
# NOTE(comstud): A revert_resize is essentially a resize back to
|
|
# the old size, so we need to send a usage event here.
|
|
self.conductor_api.notify_usage_exists(
|
|
context, instance, current_period=True)
|
|
|
|
with self._error_out_instance_on_exception(context, instance['uuid'],
|
|
reservations):
|
|
# NOTE(tr3buchet): tear down networks on destination host
|
|
self.network_api.setup_networks_on_host(context, instance,
|
|
teardown=True)
|
|
|
|
instance_p = obj_base.obj_to_primitive(instance)
|
|
migration_p = obj_base.obj_to_primitive(migration)
|
|
self.conductor_api.network_migrate_instance_start(context,
|
|
instance_p,
|
|
migration_p)
|
|
|
|
# network_info = self._get_instance_nw_info(context, instance)
|
|
bdms = (block_device_obj.BlockDeviceMappingList.
|
|
get_by_instance_uuid(context, instance.uuid))
|
|
# block_device_info = self._get_instance_volume_block_device_info(
|
|
# context, instance, bdms=bdms)
|
|
|
|
# self.driver.destroy(context, instance, network_info,
|
|
# block_device_info)
|
|
|
|
self._terminate_volume_connections(context, instance, bdms)
|
|
|
|
migration.status = 'reverted'
|
|
migration.save(context.elevated())
|
|
|
|
rt = self._get_resource_tracker(instance.node)
|
|
rt.drop_resize_claim(instance)
|
|
|
|
self.compute_rpcapi.finish_revert_resize(
|
|
context,
|
|
instance,
|
|
migration,
|
|
migration.source_compute,
|
|
reservations=reservations)
|
|
|
|
def _finish_resize(self, context, instance, migration, disk_info,
|
|
image):
|
|
old_instance_type_id = migration['old_instance_type_id']
|
|
new_instance_type_id = migration['new_instance_type_id']
|
|
old_instance_type = flavors.extract_flavor(instance)
|
|
sys_meta = instance.system_metadata
|
|
# NOTE(mriedem): Get the old_vm_state so we know if we should
|
|
# power on the instance. If old_vm_sate is not set we need to default
|
|
# to ACTIVE for backwards compatibility
|
|
sys_meta.get('old_vm_state', vm_states.ACTIVE)
|
|
flavors.save_flavor_info(sys_meta,
|
|
old_instance_type,
|
|
prefix='old_')
|
|
|
|
if old_instance_type_id != new_instance_type_id:
|
|
instance_type = flavors.extract_flavor(instance, prefix='new_')
|
|
flavors.save_flavor_info(sys_meta, instance_type)
|
|
instance.instance_type_id = instance_type['id']
|
|
instance.memory_mb = instance_type['memory_mb']
|
|
instance.vcpus = instance_type['vcpus']
|
|
instance.root_gb = instance_type['root_gb']
|
|
instance.ephemeral_gb = instance_type['ephemeral_gb']
|
|
instance.system_metadata = sys_meta
|
|
instance.save()
|
|
|
|
# NOTE(tr3buchet): setup networks on destination host
|
|
self.network_api.setup_networks_on_host(context, instance,
|
|
migration['dest_compute'])
|
|
|
|
instance_p = obj_base.obj_to_primitive(instance)
|
|
migration_p = obj_base.obj_to_primitive(migration)
|
|
self.conductor_api.network_migrate_instance_finish(context,
|
|
instance_p,
|
|
migration_p)
|
|
|
|
network_info = self._get_instance_nw_info(context, instance)
|
|
|
|
instance.task_state = task_states.RESIZE_FINISH
|
|
instance.system_metadata = sys_meta
|
|
# instance.save(expected_task_state=task_states.RESIZE_MIGRATED)
|
|
instance.save()
|
|
|
|
self._notify_about_instance_usage(
|
|
context, instance, "finish_resize.start",
|
|
network_info=network_info)
|
|
|
|
# block_device_info = self._get_instance_volume_block_device_info(
|
|
# context, instance, refresh_conn_info=True)
|
|
|
|
# NOTE(mriedem): If the original vm_state was STOPPED, we don't
|
|
# automatically power on the instance after it's migrated
|
|
# power_on = old_vm_state != vm_states.STOPPED
|
|
# self.driver.finish_migration(context, migration, instance,
|
|
# disk_info,
|
|
# network_info,
|
|
# image, resize_instance,
|
|
# block_device_info, power_on)
|
|
cascaded_instance_id = instance['mapping_uuid']
|
|
if cascaded_instance_id is None:
|
|
LOG.error(_('Finish resize can not find server %s %s .'),
|
|
instance['uuid'])
|
|
return
|
|
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
cascadedNovaCli.servers.resize(
|
|
cascaded_instance_id,
|
|
instance.system_metadata['new_instance_type_flavorid'])
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to resize server %s .'),
|
|
cascaded_instance_id)
|
|
|
|
migration.status = 'finished'
|
|
migration.save(context.elevated())
|
|
|
|
# instance.vm_state = vm_states.RESIZED
|
|
# instance.task_state = None
|
|
# instance.launched_at = timeutils.utcnow()
|
|
# instance.save(expected_task_state=task_states.RESIZE_FINISH)
|
|
|
|
self._notify_about_instance_usage(
|
|
context, instance, "finish_resize.end",
|
|
network_info=network_info)
|
|
|
|
def _quota_commit(self, context, reservations, project_id=None,
|
|
user_id=None):
|
|
if reservations:
|
|
self.conductor_api.quota_commit(context, reservations,
|
|
project_id=project_id,
|
|
user_id=user_id)
|
|
|
|
def _heal_syn_flavor_info(self, context, instance_type):
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
try:
|
|
flavors = cascadedNovaCli.flavors.get(instance_type['flavorid'])
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
flavors = cascadedNovaCli.flavors.create(
|
|
name=instance_type['name'],
|
|
ram=instance_type['memory_mb'],
|
|
vcpus=instance_type['vcpus'],
|
|
disk=instance_type['root_gb'],
|
|
flavorid=instance_type['flavorid'],
|
|
ephemeral=instance_type['ephemeral_gb'],
|
|
swap=instance_type['swap'],
|
|
rxtx_factor=instance_type['rxtx_factor']
|
|
)
|
|
LOG.debug(_('creat flavor %s .'), instance_type['flavorid'])
|
|
|
|
def _heal_syn_keypair_info(self, context, instance):
|
|
LOG.debug(_('Start to synchronize keypair %s to cascaded openstack'),
|
|
instance['key_name'])
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
keyPai = cascadedNovaCli.keypairs.list()
|
|
keyNam = instance['key_name']
|
|
keyDat = instance['key_data']
|
|
keyExiFlag = False
|
|
for key in keyPai:
|
|
if keyNam == key.name:
|
|
keyExiFlag = True
|
|
break
|
|
if keyExiFlag:
|
|
LOG.debug(_('Keypair is not updated ,no need to synchronize'),
|
|
keyNam)
|
|
return
|
|
else:
|
|
cascadedNovaCli.keypairs.create(keyNam, keyDat)
|
|
LOG.debug(_('Finish to synchronize keypair %s to cascaded openstack'),
|
|
instance['key_name'])
|
|
|
|
def _get_cascaded_image_uuid(self, context, image_uuid):
|
|
try:
|
|
glanceClient = glance.GlanceClientWrapper()
|
|
image = glanceClient.call(context, 2, 'get', image_uuid)
|
|
cascaded_image_uuid = None
|
|
for location in image['locations']:
|
|
if location['url'] and location['url'].startswith(
|
|
cfg.CONF.cascaded_glance_url):
|
|
cascaded_image_uuid = location['url'].split('/')[-1]
|
|
return cascaded_image_uuid
|
|
#lazy sync image
|
|
sync_service = cascading.GlanceCascadingService()
|
|
return sync_service.sync_image(context,
|
|
cfg.CONF.cascaded_glance_url,
|
|
image)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_("Error while trying to get cascaded"
|
|
" image and cascading uuid %s")
|
|
% image_uuid)
|
|
|
|
def _proxy_run_instance(
|
|
self,
|
|
context,
|
|
instance,
|
|
request_spec=None,
|
|
filter_properties=None,
|
|
requested_networks=None,
|
|
injected_files=None,
|
|
admin_password=None,
|
|
is_first_time=False,
|
|
node=None,
|
|
legacy_bdm_in_spec=True,
|
|
physical_ports=None):
|
|
cascadedNovaCli = self._get_nova_pythonClient(
|
|
context,
|
|
cfg.CONF.proxy_region_name,
|
|
cfg.CONF.cascaded_nova_url)
|
|
nicsList = []
|
|
for port in physical_ports:
|
|
nicsList.append({'port-id': port['port']['id']})
|
|
# for net in requested_networks:
|
|
# nicsList.append({'net-id':net[0]})
|
|
metadata = request_spec['instance_properties']['metadata']
|
|
metadata['mapping_uuid'] = instance['uuid']
|
|
|
|
try:
|
|
self._heal_syn_flavor_info(context, request_spec['instance_type'])
|
|
except Exception:
|
|
pass
|
|
|
|
if instance['key_name'] is not None:
|
|
self._heal_syn_keypair_info(context, instance)
|
|
|
|
availability_zone_info = \
|
|
request_spec['instance_properties']['availability_zone']
|
|
force_hosts = filter_properties.get('force_hosts')
|
|
if force_hosts and len(force_hosts) > 0:
|
|
availability_zone_info = availability_zone_info + \
|
|
":" + force_hosts[0]
|
|
|
|
files = {}
|
|
if injected_files is not None:
|
|
for injected_file in injected_files:
|
|
file_path = injected_file[0]
|
|
context = injected_file[1]
|
|
files[file_path] = context
|
|
|
|
image_uuid = None
|
|
if 'id' in request_spec['image']:
|
|
if cfg.CONF.cascaded_glance_flag:
|
|
image_uuid = self._get_cascaded_image_uuid(
|
|
context,
|
|
request_spec['image']['id'])
|
|
else:
|
|
image_uuid = request_spec['image']['id']
|
|
|
|
try:
|
|
block_device_mapping_v2_lst = None
|
|
block_device_mapping = request_spec['block_device_mapping']
|
|
for block_device_mapping_value in block_device_mapping:
|
|
if block_device_mapping_value['source_type'] == 'volume':
|
|
proxy_volume_id = None
|
|
bdm = block_device_obj.BlockDeviceMapping.get_by_volume_id(
|
|
context, block_device_mapping_value['volume_id'])
|
|
driver_bdm = \
|
|
driver_block_device.DriverVolumeBlockDevice(bdm)
|
|
try:
|
|
if driver_bdm['mount_device'] is None:
|
|
mount_point = '/dev/vda'
|
|
else:
|
|
mount_point = driver_bdm['mount_device']
|
|
self.volume_api.attach(context, bdm.volume_id,
|
|
instance['uuid'], mount_point)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
self.volume_api.detach(context.elevated(),
|
|
volume_id)
|
|
bdm.destroy(context)
|
|
try:
|
|
bodyReps = self.volume_api.get(
|
|
context,
|
|
block_device_mapping_value['volume_id'])
|
|
proxy_volume_id = \
|
|
bodyReps['volume_metadata']['mapping_uuid']
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to get physical volume id ,'
|
|
'logical volume id %s,device %s'),
|
|
block_device_mapping_value['volume_id'],
|
|
block_device_mapping_value['device_name'])
|
|
if proxy_volume_id is None:
|
|
LOG.error(_('Can not find physical volume'
|
|
' id %s in physical opensack lay,'
|
|
'logical volume id %s'),
|
|
instance['uuid'],
|
|
block_device_mapping_value['volume_id'])
|
|
return
|
|
|
|
block_device_mapping_v2_value = {}
|
|
block_device_mapping_v2_value['uuid'] = proxy_volume_id
|
|
block_device_mapping_v2_value['boot_index'] = \
|
|
block_device_mapping_value['boot_index']
|
|
block_device_mapping_v2_value['volume_size'] = \
|
|
block_device_mapping_value['volume_size']
|
|
block_device_mapping_v2_value['source_type'] = \
|
|
block_device_mapping_value['source_type']
|
|
block_device_mapping_v2_value['destination_type'] = \
|
|
block_device_mapping_value['destination_type']
|
|
block_device_mapping_v2_value['delete_on_termination'] = \
|
|
block_device_mapping_value['delete_on_termination']
|
|
block_device_mapping_v2_value['device_name'] = \
|
|
block_device_mapping_value['device_name']
|
|
block_device_mapping_v2_lst = \
|
|
[block_device_mapping_v2_value]
|
|
LOG.info(_("block_device_mapping_v2_value is:%s")
|
|
% block_device_mapping_v2_value)
|
|
break
|
|
|
|
bodyResponse = cascadedNovaCli.servers.create(
|
|
name=request_spec['instance_properties']['display_name'],
|
|
image=image_uuid,
|
|
flavor=request_spec['instance_type']['flavorid'],
|
|
meta=metadata,
|
|
key_name=request_spec['instance_properties']['key_name'],
|
|
security_groups=request_spec['security_group'],
|
|
userdata=request_spec['instance_properties']['user_data'],
|
|
block_device_mapping_v2=block_device_mapping_v2_lst,
|
|
scheduler_hints=filter_properties['scheduler_hints'],
|
|
nics=nicsList,
|
|
files=files,
|
|
availability_zone=availability_zone_info)
|
|
self._instance_update(context, instance['uuid'],
|
|
vm_state=vm_states.BUILDING,
|
|
mapping_uuid=bodyResponse.id,
|
|
task_state=None)
|
|
except Exception:
|
|
# Avoid a race condition where the thread could be cancelled
|
|
# before the ID is stored
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_('Failed to create server for instance.'),
|
|
instance=instance)
|
|
self._set_instance_error_state(context, instance['uuid'])
|
|
|
|
def _decode_files(self, injected_files):
|
|
"""Base64 decode the list of files to inject."""
|
|
if not injected_files:
|
|
return []
|
|
|
|
def _decode(f):
|
|
path, contents = f
|
|
try:
|
|
decoded = base64.b64decode(contents)
|
|
return path, decoded
|
|
except TypeError:
|
|
raise exception.Base64Exception(path=path)
|
|
|
|
return [_decode(f) for f in injected_files]
|
|
|
|
def _validate_instance_group_policy(self, context, instance,
|
|
filter_properties):
|
|
# NOTE(russellb) Instance group policy is enforced by the scheduler.
|
|
# However, there is a race condition with the enforcement of
|
|
# anti-affinity. Since more than one instance may be scheduled at the
|
|
# same time, it's possible that more than one instance with an
|
|
# anti-affinity policy may end up here. This is a validation step to
|
|
# make sure that starting the instance here doesn't violate the policy.
|
|
|
|
scheduler_hints = filter_properties.get('scheduler_hints') or {}
|
|
group_uuid = scheduler_hints.get('group')
|
|
if not group_uuid:
|
|
return
|
|
|
|
@utils.synchronized(group_uuid)
|
|
def _do_validation(context, instance, group_uuid):
|
|
group = instance_group_obj.InstanceGroup.get_by_uuid(context,
|
|
group_uuid)
|
|
if 'anti-affinity' not in group.policies:
|
|
return
|
|
|
|
group_hosts = group.get_hosts(context, exclude=[instance['uuid']])
|
|
if self.host in group_hosts:
|
|
msg = _("Anti-affinity instance group policy was violated.")
|
|
raise exception.RescheduledException(
|
|
instance_uuid=instance['uuid'],
|
|
reason=msg)
|
|
|
|
_do_validation(context, instance, group_uuid)
|
|
|
|
def _allocate_network_async(self, context, instance, requested_networks,
|
|
macs, security_groups, is_vpn, dhcp_options):
|
|
"""Method used to allocate networks in the background.
|
|
|
|
Broken out for testing.
|
|
"""
|
|
LOG.debug(_("Allocating IP information in the background."),
|
|
instance=instance)
|
|
retries = CONF.network_allocate_retries
|
|
if retries < 0:
|
|
LOG.warn(_("Treating negative config value (%(retries)s) for "
|
|
"'network_allocate_retries' as 0."),
|
|
{'retries': retries})
|
|
attempts = retries > 1 and retries + 1 or 1
|
|
retry_time = 1
|
|
for attempt in range(1, attempts + 1):
|
|
try:
|
|
nwinfo = self.network_api.allocate_for_instance(
|
|
context, instance, vpn=is_vpn,
|
|
requested_networks=requested_networks,
|
|
macs=macs,
|
|
security_groups=security_groups,
|
|
dhcp_options=dhcp_options)
|
|
LOG.debug(_('Instance network_info: |%s|'), nwinfo,
|
|
instance=instance)
|
|
# NOTE(alaski): This can be done more cleanly once we're sure
|
|
# we'll receive an object.
|
|
sys_meta = utils.metadata_to_dict(instance['system_metadata'])
|
|
sys_meta['network_allocated'] = 'True'
|
|
self._instance_update(context, instance['uuid'],
|
|
system_metadata=sys_meta)
|
|
return nwinfo
|
|
except Exception:
|
|
exc_info = sys.exc_info()
|
|
log_info = {'attempt': attempt,
|
|
'attempts': attempts}
|
|
if attempt == attempts:
|
|
LOG.exception(_('Instance failed network setup '
|
|
'after %(attempts)d attempt(s)'),
|
|
log_info)
|
|
raise exc_info[0], exc_info[1], exc_info[2]
|
|
LOG.warn(_('Instance failed network setup '
|
|
'(attempt %(attempt)d of %(attempts)d)'),
|
|
log_info, instance=instance)
|
|
time.sleep(retry_time)
|
|
retry_time *= 2
|
|
if retry_time > 30:
|
|
retry_time = 30
|
|
|
|
def _allocate_network(self, context, instance, requested_networks, macs,
|
|
security_groups, dhcp_options):
|
|
"""Start network allocation asynchronously. Return an instance
|
|
of NetworkInfoAsyncWrapper that can be used to retrieve the
|
|
allocated networks when the operation has finished.
|
|
"""
|
|
# NOTE(comstud): Since we're allocating networks asynchronously,
|
|
# this task state has little meaning, as we won't be in this
|
|
# state for very long.
|
|
instance = self._instance_update(context, instance['uuid'],
|
|
vm_state=vm_states.BUILDING,
|
|
task_state=task_states.NETWORKING,
|
|
expected_task_state=[None])
|
|
is_vpn = pipelib.is_vpn_image(instance['image_ref'])
|
|
return network_model.NetworkInfoAsyncWrapper(
|
|
self._allocate_network_async, context, instance,
|
|
requested_networks, macs, security_groups, is_vpn,
|
|
dhcp_options)
|