nova/nova/compute/cells_api.py

472 lines
19 KiB
Python

# Copyright (c) 2012 Rackspace Hosting
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Compute API that proxies via Cells Service"""
from nova import block_device
from nova.cells import rpcapi as cells_rpcapi
from nova.compute import api as compute_api
from nova.compute import task_states
from nova.compute import vm_states
from nova import exception
from nova.openstack.common import excutils
from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__)
check_instance_state = compute_api.check_instance_state
wrap_check_policy = compute_api.wrap_check_policy
check_policy = compute_api.check_policy
check_instance_lock = compute_api.check_instance_lock
def validate_cell(fn):
def _wrapped(self, context, instance, *args, **kwargs):
self._validate_cell(instance, fn.__name__)
return fn(self, context, instance, *args, **kwargs)
_wrapped.__name__ = fn.__name__
return _wrapped
class ComputeRPCAPINoOp(object):
def __getattr__(self, key):
def _noop_rpc_wrapper(*args, **kwargs):
return None
return _noop_rpc_wrapper
class SchedulerRPCAPIRedirect(object):
def __init__(self, cells_rpcapi_obj):
self.cells_rpcapi = cells_rpcapi_obj
def __getattr__(self, key):
def _noop_rpc_wrapper(*args, **kwargs):
return None
return _noop_rpc_wrapper
def run_instance(self, context, **kwargs):
self.cells_rpcapi.schedule_run_instance(context, **kwargs)
class ComputeCellsAPI(compute_api.API):
def __init__(self, *args, **kwargs):
super(ComputeCellsAPI, self).__init__(*args, **kwargs)
self.cells_rpcapi = cells_rpcapi.CellsAPI()
# Avoid casts/calls directly to compute
self.compute_rpcapi = ComputeRPCAPINoOp()
# Redirect scheduler run_instance to cells.
self.scheduler_rpcapi = SchedulerRPCAPIRedirect(self.cells_rpcapi)
def _cell_read_only(self, cell_name):
"""Is the target cell in a read-only mode?"""
# FIXME(comstud): Add support for this.
return False
def _validate_cell(self, instance, method):
cell_name = instance['cell_name']
if not cell_name:
raise exception.InstanceUnknownCell(
instance_uuid=instance['uuid'])
if self._cell_read_only(cell_name):
raise exception.InstanceInvalidState(
attr="vm_state",
instance_uuid=instance['uuid'],
state="temporary_readonly",
method=method)
def _cast_to_cells(self, context, instance, method, *args, **kwargs):
instance_uuid = instance['uuid']
cell_name = instance['cell_name']
if not cell_name:
raise exception.InstanceUnknownCell(instance_uuid=instance_uuid)
self.cells_rpcapi.cast_compute_api_method(context, cell_name,
method, instance_uuid, *args, **kwargs)
def _call_to_cells(self, context, instance, method, *args, **kwargs):
instance_uuid = instance['uuid']
cell_name = instance['cell_name']
if not cell_name:
raise exception.InstanceUnknownCell(instance_uuid=instance_uuid)
return self.cells_rpcapi.call_compute_api_method(context, cell_name,
method, instance_uuid, *args, **kwargs)
def _check_requested_networks(self, context, requested_networks):
"""Override compute API's checking of this. It'll happen in
child cell
"""
return
def _validate_image_href(self, context, image_href):
"""Override compute API's checking of this. It'll happen in
child cell
"""
return
def _create_image(self, context, instance, name, image_type,
backup_type=None, rotation=None, extra_properties=None):
if backup_type:
return self._call_to_cells(context, instance, 'backup',
name, backup_type, rotation,
extra_properties=extra_properties)
else:
return self._call_to_cells(context, instance, 'snapshot',
name, extra_properties=extra_properties)
def create(self, *args, **kwargs):
"""We can use the base functionality, but I left this here just
for completeness.
"""
return super(ComputeCellsAPI, self).create(*args, **kwargs)
@validate_cell
def update(self, context, instance, **kwargs):
"""Update an instance."""
rv = super(ComputeCellsAPI, self).update(context,
instance, **kwargs)
# We need to skip vm_state/task_state updates... those will
# happen when via a a _cast_to_cells for running a different
# compute api method
kwargs_copy = kwargs.copy()
kwargs_copy.pop('vm_state', None)
kwargs_copy.pop('task_state', None)
if kwargs_copy:
try:
self._cast_to_cells(context, instance, 'update',
**kwargs_copy)
except exception.InstanceUnknownCell:
pass
return rv
def _local_delete(self, context, instance, bdms):
# This will get called for every delete in the API cell
# because _delete() in compute/api.py will not find a
# service when checking if it's up.
# We need to only take action if there's no cell_name. Our
# overrides of delete() and soft_delete() will take care of
# the rest.
cell_name = instance['cell_name']
if not cell_name:
return super(ComputeCellsAPI, self)._local_delete(context,
instance, bdms)
def soft_delete(self, context, instance):
self._handle_cell_delete(context, instance,
super(ComputeCellsAPI, self).soft_delete, 'soft_delete')
def delete(self, context, instance):
self._handle_cell_delete(context, instance,
super(ComputeCellsAPI, self).delete, 'delete')
def _handle_cell_delete(self, context, instance, method, method_name):
"""Terminate an instance."""
# We can't use the decorator because we have special logic in the
# case we don't know the cell_name...
cell_name = instance['cell_name']
if cell_name and self._cell_read_only(cell_name):
raise exception.InstanceInvalidState(
attr="vm_state",
instance_uuid=instance['uuid'],
state="temporary_readonly",
method=method_name)
method(context, instance)
try:
self._cast_to_cells(context, instance, method_name)
except exception.InstanceUnknownCell:
# If there's no cell, there's also no host... which means
# the instance was destroyed from the DB here. Let's just
# broadcast a message down to all cells and hope this ends
# up resolving itself... Worse case.. the instance will
# show back up again here.
delete_type = method == 'soft_delete' and 'soft' or 'hard'
self.cells_rpcapi.instance_delete_everywhere(context,
instance['uuid'], delete_type)
@validate_cell
def restore(self, context, instance):
"""Restore a previously deleted (but not reclaimed) instance."""
super(ComputeCellsAPI, self).restore(context, instance)
self._cast_to_cells(context, instance, 'restore')
@validate_cell
def force_delete(self, context, instance):
"""Force delete a previously deleted (but not reclaimed) instance."""
super(ComputeCellsAPI, self).force_delete(context, instance)
self._cast_to_cells(context, instance, 'force_delete')
@validate_cell
def stop(self, context, instance, do_cast=True):
"""Stop an instance."""
super(ComputeCellsAPI, self).stop(context, instance)
if do_cast:
self._cast_to_cells(context, instance, 'stop', do_cast=True)
else:
return self._call_to_cells(context, instance, 'stop',
do_cast=False)
@validate_cell
def start(self, context, instance):
"""Start an instance."""
super(ComputeCellsAPI, self).start(context, instance)
self._cast_to_cells(context, instance, 'start')
@validate_cell
def reboot(self, context, instance, *args, **kwargs):
"""Reboot the given instance."""
super(ComputeCellsAPI, self).reboot(context, instance,
*args, **kwargs)
self._cast_to_cells(context, instance, 'reboot', *args,
**kwargs)
@validate_cell
def rebuild(self, context, instance, *args, **kwargs):
"""Rebuild the given instance with the provided attributes."""
super(ComputeCellsAPI, self).rebuild(context, instance, *args,
**kwargs)
self._cast_to_cells(context, instance, 'rebuild', *args, **kwargs)
@check_instance_state(vm_state=[vm_states.RESIZED])
@validate_cell
def revert_resize(self, context, instance):
"""Reverts a resize, deleting the 'new' instance in the process."""
# NOTE(markwash): regular api manipulates the migration here, but we
# don't have access to it. So to preserve the interface just update the
# vm and task state.
self.update(context, instance,
task_state=task_states.RESIZE_REVERTING)
self._cast_to_cells(context, instance, 'revert_resize')
@check_instance_state(vm_state=[vm_states.RESIZED])
@validate_cell
def confirm_resize(self, context, instance):
"""Confirms a migration/resize and deletes the 'old' instance."""
# NOTE(markwash): regular api manipulates migration here, but we don't
# have the migration in the api database. So to preserve the interface
# just update the vm and task state without calling super()
self.update(context, instance, task_state=None,
vm_state=vm_states.ACTIVE)
self._cast_to_cells(context, instance, 'confirm_resize')
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED],
task_state=[None])
@validate_cell
def resize(self, context, instance, *args, **kwargs):
"""Resize (ie, migrate) a running instance.
If flavor_id is None, the process is considered a migration, keeping
the original flavor_id. If flavor_id is not None, the instance should
be migrated to a new host and resized to the new flavor_id.
"""
super(ComputeCellsAPI, self).resize(context, instance, *args,
**kwargs)
# FIXME(comstud): pass new instance_type object down to a method
# that'll unfold it
self._cast_to_cells(context, instance, 'resize', *args, **kwargs)
@validate_cell
def add_fixed_ip(self, context, instance, *args, **kwargs):
"""Add fixed_ip from specified network to given instance."""
super(ComputeCellsAPI, self).add_fixed_ip(context, instance,
*args, **kwargs)
self._cast_to_cells(context, instance, 'add_fixed_ip',
*args, **kwargs)
@validate_cell
def remove_fixed_ip(self, context, instance, *args, **kwargs):
"""Remove fixed_ip from specified network to given instance."""
super(ComputeCellsAPI, self).remove_fixed_ip(context, instance,
*args, **kwargs)
self._cast_to_cells(context, instance, 'remove_fixed_ip',
*args, **kwargs)
@validate_cell
def pause(self, context, instance):
"""Pause the given instance."""
super(ComputeCellsAPI, self).pause(context, instance)
self._cast_to_cells(context, instance, 'pause')
@validate_cell
def unpause(self, context, instance):
"""Unpause the given instance."""
super(ComputeCellsAPI, self).unpause(context, instance)
self._cast_to_cells(context, instance, 'unpause')
def set_host_enabled(self, context, host, enabled):
"""Sets the specified host's ability to accept new instances."""
# FIXME(comstud): Since there's no instance here, we have no
# idea which cell should be the target.
pass
def host_power_action(self, context, host, action):
"""Reboots, shuts down or powers up the host."""
# FIXME(comstud): Since there's no instance here, we have no
# idea which cell should be the target.
pass
def get_diagnostics(self, context, instance):
"""Retrieve diagnostics for the given instance."""
# FIXME(comstud): Cache this?
# Also: only calling super() to get state/policy checking
super(ComputeCellsAPI, self).get_diagnostics(context, instance)
return self._call_to_cells(context, instance, 'get_diagnostics')
@validate_cell
def suspend(self, context, instance):
"""Suspend the given instance."""
super(ComputeCellsAPI, self).suspend(context, instance)
self._cast_to_cells(context, instance, 'suspend')
@validate_cell
def resume(self, context, instance):
"""Resume the given instance."""
super(ComputeCellsAPI, self).resume(context, instance)
self._cast_to_cells(context, instance, 'resume')
@validate_cell
def rescue(self, context, instance, rescue_password=None):
"""Rescue the given instance."""
super(ComputeCellsAPI, self).rescue(context, instance,
rescue_password=rescue_password)
self._cast_to_cells(context, instance, 'rescue',
rescue_password=rescue_password)
@validate_cell
def unrescue(self, context, instance):
"""Unrescue the given instance."""
super(ComputeCellsAPI, self).unrescue(context, instance)
self._cast_to_cells(context, instance, 'unrescue')
@validate_cell
def set_admin_password(self, context, instance, password=None):
"""Set the root/admin password for the given instance."""
super(ComputeCellsAPI, self).set_admin_password(context, instance,
password=password)
self._cast_to_cells(context, instance, 'set_admin_password',
password=password)
@validate_cell
def inject_file(self, context, instance, *args, **kwargs):
"""Write a file to the given instance."""
super(ComputeCellsAPI, self).inject_file(context, instance, *args,
**kwargs)
self._cast_to_cells(context, instance, 'inject_file', *args, **kwargs)
@wrap_check_policy
@validate_cell
def get_vnc_console(self, context, instance, console_type):
"""Get a url to a VNC Console."""
if not instance['host']:
raise exception.InstanceNotReady(instance_id=instance['uuid'])
connect_info = self._call_to_cells(context, instance,
'get_vnc_connect_info', console_type)
self.consoleauth_rpcapi.authorize_console(context,
connect_info['token'], console_type, connect_info['host'],
connect_info['port'], connect_info['internal_access_path'])
return {'url': connect_info['access_url']}
@validate_cell
def get_console_output(self, context, instance, *args, **kwargs):
"""Get console output for an an instance."""
# NOTE(comstud): Calling super() just to get policy check
super(ComputeCellsAPI, self).get_console_output(context, instance,
*args, **kwargs)
return self._call_to_cells(context, instance, 'get_console_output',
*args, **kwargs)
def lock(self, context, instance):
"""Lock the given instance."""
super(ComputeCellsAPI, self).lock(context, instance)
self._cast_to_cells(context, instance, 'lock')
def unlock(self, context, instance):
"""Unlock the given instance."""
super(ComputeCellsAPI, self).lock(context, instance)
self._cast_to_cells(context, instance, 'unlock')
@validate_cell
def reset_network(self, context, instance):
"""Reset networking on the instance."""
super(ComputeCellsAPI, self).reset_network(context, instance)
self._cast_to_cells(context, instance, 'reset_network')
@validate_cell
def inject_network_info(self, context, instance):
"""Inject network info for the instance."""
super(ComputeCellsAPI, self).inject_network_info(context, instance)
self._cast_to_cells(context, instance, 'inject_network_info')
@wrap_check_policy
@validate_cell
def attach_volume(self, context, instance, volume_id, device=None):
"""Attach an existing volume to an existing instance."""
if device and not block_device.match_device(device):
raise exception.InvalidDevicePath(path=device)
device = self.compute_rpcapi.reserve_block_device_name(
context, device=device, instance=instance, volume_id=volume_id)
try:
volume = self.volume_api.get(context, volume_id)
self.volume_api.check_attach(context, volume)
except Exception:
with excutils.save_and_reraise_exception():
self.db.block_device_mapping_destroy_by_instance_and_device(
context, instance['uuid'], device)
self._cast_to_cells(context, instance, 'attach_volume',
volume_id, device)
@check_instance_lock
@validate_cell
def _detach_volume(self, context, instance, volume_id):
"""Detach a volume from an instance."""
check_policy(context, 'detach_volume', instance)
volume = self.volume_api.get(context, volume_id)
self.volume_api.check_detach(context, volume)
self._cast_to_cells(context, instance, 'detach_volume',
volume_id)
@wrap_check_policy
@validate_cell
def associate_floating_ip(self, context, instance, address):
"""Makes calls to network_api to associate_floating_ip.
:param address: is a string floating ip address
"""
self._cast_to_cells(context, instance, 'associate_floating_ip',
address)
@validate_cell
def delete_instance_metadata(self, context, instance, key):
"""Delete the given metadata item from an instance."""
super(ComputeCellsAPI, self).delete_instance_metadata(context,
instance, key)
self._cast_to_cells(context, instance, 'delete_instance_metadata',
key)
@wrap_check_policy
@validate_cell
def update_instance_metadata(self, context, instance,
metadata, delete=False):
rv = super(ComputeCellsAPI, self).update_instance_metadata(context,
instance, metadata, delete=delete)
try:
self._cast_to_cells(context, instance,
'update_instance_metadata',
metadata, delete=delete)
except exception.InstanceUnknownCell:
pass
return rv