summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorWilliam Wolf <throughnothing@gmail.com>2011-08-10 01:18:44 -0400
committerWilliam Wolf <throughnothing@gmail.com>2011-08-10 01:18:44 -0400
commitc8a4a55437a332c49c7c1afc52a9c1c8bf93e593 (patch)
treee4e335cc60ec553296167dd24acaa51c80cf2688 /nova/api
parenta8a5b27a577f8e007e2cc79570f97ae075fda767 (diff)
parent4b3165429797d40da17f5c59aaeadb00673b71b2 (diff)
downloadnova-c8a4a55437a332c49c7c1afc52a9c1c8bf93e593.tar.gz
nova-c8a4a55437a332c49c7c1afc52a9c1c8bf93e593.tar.xz
nova-c8a4a55437a332c49c7c1afc52a9c1c8bf93e593.zip
merge trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/ec2/cloud.py42
-rw-r--r--nova/api/openstack/common.py33
-rw-r--r--nova/api/openstack/contrib/floating_ips.py12
-rw-r--r--nova/api/openstack/servers.py97
-rw-r--r--nova/api/openstack/views/servers.py16
5 files changed, 155 insertions, 45 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index f64a92d12..87bba58c3 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -213,8 +213,9 @@ class CloudController(object):
def _get_mpi_data(self, context, project_id):
result = {}
+ search_opts = {'project_id': project_id}
for instance in self.compute_api.get_all(context,
- project_id=project_id):
+ search_opts=search_opts):
if instance['fixed_ips']:
line = '%s slots=%d' % (instance['fixed_ips'][0]['address'],
instance['vcpus'])
@@ -264,8 +265,13 @@ class CloudController(object):
def get_metadata(self, address):
ctxt = context.get_admin_context()
- instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
- if instance_ref is None:
+ search_opts = {'fixed_ip': address}
+ try:
+ instance_ref = self.compute_api.get_all(ctxt,
+ search_opts=search_opts)
+ except exception.NotFound:
+ instance_ref = None
+ if not instance_ref:
return None
# This ensures that all attributes of the instance
@@ -1086,11 +1092,16 @@ class CloudController(object):
return result
def describe_instances(self, context, **kwargs):
- return self._format_describe_instances(context, **kwargs)
+ # Optional DescribeInstances argument
+ instance_id = kwargs.get('instance_id', None)
+ return self._format_describe_instances(context,
+ instance_id=instance_id)
def describe_instances_v6(self, context, **kwargs):
- kwargs['use_v6'] = True
- return self._format_describe_instances(context, **kwargs)
+ # Optional DescribeInstancesV6 argument
+ instance_id = kwargs.get('instance_id', None)
+ return self._format_describe_instances(context,
+ instance_id=instance_id, use_v6=True)
def _format_describe_instances(self, context, **kwargs):
return {'reservationSet': self._format_instances(context, **kwargs)}
@@ -1152,7 +1163,8 @@ class CloudController(object):
result['groupSet'] = CloudController._convert_to_set(
security_group_names, 'groupId')
- def _format_instances(self, context, instance_id=None, **kwargs):
+ def _format_instances(self, context, instance_id=None, use_v6=False,
+ **search_opts):
# TODO(termie): this method is poorly named as its name does not imply
# that it will be making a variety of database calls
# rather than simply formatting a bunch of instances that
@@ -1163,11 +1175,17 @@ class CloudController(object):
instances = []
for ec2_id in instance_id:
internal_id = ec2utils.ec2_id_to_id(ec2_id)
- instance = self.compute_api.get(context,
- instance_id=internal_id)
+ try:
+ instance = self.compute_api.get(context, internal_id)
+ except exception.NotFound:
+ continue
instances.append(instance)
else:
- instances = self.compute_api.get_all(context, **kwargs)
+ try:
+ instances = self.compute_api.get_all(context,
+ search_opts=search_opts)
+ except exception.NotFound:
+ instances = []
for instance in instances:
if not context.is_admin:
if instance['image_ref'] == str(FLAGS.vpn_image_id):
@@ -1189,7 +1207,7 @@ class CloudController(object):
fixed_addr = fixed['address']
if fixed['floating_ips']:
floating_addr = fixed['floating_ips'][0]['address']
- if fixed['network'] and 'use_v6' in kwargs:
+ if fixed['network'] and use_v6:
i['dnsNameV6'] = ipv6.to_global(
fixed['network']['cidr_v6'],
fixed['virtual_interface']['address'],
@@ -1326,7 +1344,7 @@ class CloudController(object):
'AvailabilityZone'),
block_device_mapping=kwargs.get('block_device_mapping', {}))
return self._format_run_instances(context,
- instances[0]['reservation_id'])
+ reservation_id=instances[0]['reservation_id'])
def _do_instance(self, action, context, ec2_id):
instance_id = ec2utils.ec2_id_to_id(ec2_id)
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index b916372d6..dfdd62201 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -27,6 +27,7 @@ from nova import flags
from nova import log as logging
from nova import quota
from nova.api.openstack import wsgi
+from nova.compute import power_state as compute_power_state
LOG = logging.getLogger('nova.api.openstack.common')
@@ -37,6 +38,38 @@ XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
+_STATUS_MAP = {
+ None: 'BUILD',
+ compute_power_state.NOSTATE: 'BUILD',
+ compute_power_state.RUNNING: 'ACTIVE',
+ compute_power_state.BLOCKED: 'ACTIVE',
+ compute_power_state.SUSPENDED: 'SUSPENDED',
+ compute_power_state.PAUSED: 'PAUSED',
+ compute_power_state.SHUTDOWN: 'SHUTDOWN',
+ compute_power_state.SHUTOFF: 'SHUTOFF',
+ compute_power_state.CRASHED: 'ERROR',
+ compute_power_state.FAILED: 'ERROR',
+ compute_power_state.BUILDING: 'BUILD',
+}
+
+
+def status_from_power_state(power_state):
+ """Map the power state to the server status string"""
+ return _STATUS_MAP[power_state]
+
+
+def power_states_from_status(status):
+ """Map the server status string to a list of power states"""
+ power_states = []
+ for power_state, status_map in _STATUS_MAP.iteritems():
+ # Skip the 'None' state
+ if power_state is None:
+ continue
+ if status.lower() == status_map.lower():
+ power_states.append(power_state)
+ return power_states
+
+
def get_pagination_params(request):
"""Return marker, limit tuple from request.
diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py
index 3d8049324..52c9c6cf9 100644
--- a/nova/api/openstack/contrib/floating_ips.py
+++ b/nova/api/openstack/contrib/floating_ips.py
@@ -18,12 +18,16 @@
from webob import exc
from nova import exception
+from nova import log as logging
from nova import network
from nova import rpc
from nova.api.openstack import faults
from nova.api.openstack import extensions
+LOG = logging.getLogger('nova.api.openstack.contrib.floating_ips')
+
+
def _translate_floating_ip_view(floating_ip):
result = {'id': floating_ip['id'],
'ip': floating_ip['address']}
@@ -97,8 +101,14 @@ class FloatingIPController(object):
def delete(self, req, id):
context = req.environ['nova.context']
-
ip = self.network_api.get_floating_ip(context, id)
+
+ if 'fixed_ip' in ip:
+ try:
+ self.disassociate(req, id, '')
+ except Exception as e:
+ LOG.exception(_("Error disassociating fixed_ip %s"), e)
+
self.network_api.release_floating_ip(context, address=ip)
return {'released': {
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 127962ce2..a516173d0 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -44,7 +44,7 @@ FLAGS = flags.FLAGS
class Controller(object):
- """ The Server API controller for the OpenStack API """
+ """ The Server API base controller class for the OpenStack API """
def __init__(self):
self.compute_api = compute.API()
@@ -53,17 +53,21 @@ class Controller(object):
def index(self, req):
""" Returns a list of server names and ids for a given user """
try:
- servers = self._items(req, is_detail=False)
+ servers = self._get_servers(req, is_detail=False)
except exception.Invalid as err:
return exc.HTTPBadRequest(explanation=str(err))
+ except exception.NotFound:
+ return exc.HTTPNotFound()
return servers
def detail(self, req):
""" Returns a list of server details for a given user """
try:
- servers = self._items(req, is_detail=True)
+ servers = self._get_servers(req, is_detail=True)
except exception.Invalid as err:
return exc.HTTPBadRequest(explanation=str(err))
+ except exception.NotFound as err:
+ return exc.HTTPNotFound()
return servers
def _build_view(self, req, instance, is_detail=False):
@@ -75,22 +79,55 @@ class Controller(object):
def _action_rebuild(self, info, request, instance_id):
raise NotImplementedError()
- def _items(self, req, is_detail):
- """Returns a list of servers for a given user.
-
- builder - the response model builder
+ def _get_servers(self, req, is_detail):
+ """Returns a list of servers, taking into account any search
+ options specified.
"""
- query_str = req.str_GET
- reservation_id = query_str.get('reservation_id')
- project_id = query_str.get('project_id')
- fixed_ip = query_str.get('fixed_ip')
- recurse_zones = utils.bool_from_str(query_str.get('recurse_zones'))
+
+ search_opts = {}
+ search_opts.update(req.str_GET)
+
+ context = req.environ['nova.context']
+ remove_invalid_options(context, search_opts,
+ self._get_server_search_options())
+
+ # Convert recurse_zones into a boolean
+ search_opts['recurse_zones'] = utils.bool_from_str(
+ search_opts.get('recurse_zones', False))
+
+ # If search by 'status', we need to convert it to 'state'
+ # If the status is unknown, bail.
+ # Leave 'state' in search_opts so compute can pass it on to
+ # child zones..
+ if 'status' in search_opts:
+ status = search_opts['status']
+ search_opts['state'] = common.power_states_from_status(status)
+ if len(search_opts['state']) == 0:
+ reason = _('Invalid server status: %(status)s') % locals()
+ LOG.error(reason)
+ raise exception.InvalidInput(reason=reason)
+
+ # By default, compute's get_all() will return deleted instances.
+ # If an admin hasn't specified a 'deleted' search option, we need
+ # to filter out deleted instances by setting the filter ourselves.
+ # ... Unless 'changes-since' is specified, because 'changes-since'
+ # should return recently deleted images according to the API spec.
+
+ if 'deleted' not in search_opts:
+ # Admin hasn't specified deleted filter
+ if 'changes-since' not in search_opts:
+ # No 'changes-since', so we need to find non-deleted servers
+ search_opts['deleted'] = False
+ else:
+ # This is the default, but just in case..
+ search_opts['deleted'] = True
+
instance_list = self.compute_api.get_all(
- req.environ['nova.context'],
- reservation_id=reservation_id,
- project_id=project_id,
- fixed_ip=fixed_ip,
- recurse_zones=recurse_zones)
+ context, search_opts=search_opts)
+
+ # FIXME(comstud): 'changes-since' is not fully implemented. Where
+ # should this be filtered?
+
limited_list = self._limit_items(instance_list, req)
servers = [self._build_view(req, inst, is_detail)['server']
for inst in limited_list]
@@ -506,6 +543,7 @@ class Controller(object):
class ControllerV10(Controller):
+ """v1.0 OpenStack API controller"""
@scheduler_api.redirect_handler
def delete(self, req, id):
@@ -568,8 +606,13 @@ class ControllerV10(Controller):
""" Determine the admin password for a server on creation """
return self.helper._get_server_admin_password_old_style(server)
+ def _get_server_search_options(self):
+ """Return server search options allowed by non-admin"""
+ return 'reservation_id', 'fixed_ip', 'name', 'recurse_zones'
+
class ControllerV11(Controller):
+ """v1.1 OpenStack API controller"""
@scheduler_api.redirect_handler
def delete(self, req, id):
@@ -744,6 +787,11 @@ class ControllerV11(Controller):
""" Determine the admin password for a server on creation """
return self.helper._get_server_admin_password_new_style(server)
+ def _get_server_search_options(self):
+ """Return server search options allowed by non-admin"""
+ return ('reservation_id', 'name', 'recurse_zones',
+ 'status', 'image', 'flavor', 'changes-since')
+
class HeadersSerializer(wsgi.ResponseHeadersSerializer):
@@ -922,3 +970,18 @@ def create_resource(version='1.0'):
deserializer = wsgi.RequestDeserializer(body_deserializers)
return wsgi.Resource(controller, deserializer, serializer)
+
+
+def remove_invalid_options(context, search_options, allowed_search_options):
+ """Remove search options that are not valid for non-admin API/context"""
+ if FLAGS.allow_admin_api and context.is_admin:
+ # Allow all options
+ return
+ # Otherwise, strip out all unknown options
+ unknown_options = [opt for opt in search_options
+ if opt not in allowed_search_options]
+ unk_opt_str = ", ".join(unknown_options)
+ log_msg = _("Removing options '%(unk_opt_str)s' from query") % locals()
+ LOG.debug(log_msg)
+ for opt in unknown_options:
+ search_options.pop(opt, None)
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 18c1a9057..bb5be90c5 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -20,7 +20,6 @@ import hashlib
import os
from nova import exception
-from nova.compute import power_state
import nova.compute
import nova.context
from nova.api.openstack import common
@@ -61,24 +60,11 @@ class ViewBuilder(object):
def _build_detail(self, inst):
"""Returns a detailed model of a server."""
- power_mapping = {
- None: 'BUILD',
- power_state.NOSTATE: 'BUILD',
- power_state.RUNNING: 'ACTIVE',
- power_state.BLOCKED: 'ACTIVE',
- power_state.SUSPENDED: 'SUSPENDED',
- power_state.PAUSED: 'PAUSED',
- power_state.SHUTDOWN: 'SHUTDOWN',
- power_state.SHUTOFF: 'SHUTOFF',
- power_state.CRASHED: 'ERROR',
- power_state.FAILED: 'ERROR',
- power_state.BUILDING: 'BUILD',
- }
inst_dict = {
'id': inst['id'],
'name': inst['display_name'],
- 'status': power_mapping[inst.get('state')]}
+ 'status': common.status_from_power_state(inst.get('state'))}
ctxt = nova.context.get_admin_context()
compute_api = nova.compute.API()