summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorBrian Waldon <brian.waldon@rackspace.com>2011-03-25 10:55:07 -0400
committerBrian Waldon <brian.waldon@rackspace.com>2011-03-25 10:55:07 -0400
commit6c29d4a09574fd230a5fe3b0bbfa615fe18b328c (patch)
tree5bfb174908c77c7bf1a3effadf54779d085953b8 /nova/api
parent3b8f1f54136a67ba4c306e47b25b686328ec23b5 (diff)
parent162af7b79631b151f03bb46773a1448e6c051325 (diff)
merging trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/direct.py50
-rw-r--r--nova/api/ec2/__init__.py13
-rw-r--r--nova/api/ec2/admin.py2
-rw-r--r--nova/api/ec2/cloud.py26
-rw-r--r--nova/api/openstack/__init__.py18
-rw-r--r--nova/api/openstack/accounts.py7
-rw-r--r--nova/api/openstack/common.py35
-rw-r--r--nova/api/openstack/extensions.py369
-rw-r--r--nova/api/openstack/flavors.py63
-rw-r--r--nova/api/openstack/images.py152
-rw-r--r--nova/api/openstack/server_metadata.py78
-rw-r--r--nova/api/openstack/servers.py44
-rw-r--r--nova/api/openstack/views/flavors.py70
-rw-r--r--nova/api/openstack/zones.py34
14 files changed, 849 insertions, 112 deletions
diff --git a/nova/api/direct.py b/nova/api/direct.py
index dfca250e0..e5f33cee4 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -38,6 +38,7 @@ import routes
import webob
from nova import context
+from nova import exception
from nova import flags
from nova import utils
from nova import wsgi
@@ -205,10 +206,53 @@ class ServiceWrapper(wsgi.Controller):
# NOTE(vish): make sure we have no unicode keys for py2.6.
params = dict([(str(k), v) for (k, v) in params.iteritems()])
result = method(context, **params)
- if type(result) is dict or type(result) is list:
- return self._serialize(result, req.best_match_content_type())
- else:
+ if result is None or type(result) is str or type(result) is unicode:
return result
+ try:
+ return self._serialize(result, req.best_match_content_type())
+ except:
+ raise exception.Error("returned non-serializable type: %s"
+ % result)
+
+
+class Limited(object):
+ __notdoc = """Limit the available methods on a given object.
+
+ (Not a docstring so that the docstring can be conditionally overriden.)
+
+ Useful when defining a public API that only exposes a subset of an
+ internal API.
+
+ Expected usage of this class is to define a subclass that lists the allowed
+ methods in the 'allowed' variable.
+
+ Additionally where appropriate methods can be added or overwritten, for
+ example to provide backwards compatibility.
+
+ The wrapping approach has been chosen so that the wrapped API can maintain
+ its own internal consistency, for example if it calls "self.create" it
+ should get its own create method rather than anything we do here.
+
+ """
+
+ _allowed = None
+
+ def __init__(self, proxy):
+ self._proxy = proxy
+ if not self.__doc__:
+ self.__doc__ = proxy.__doc__
+ if not self._allowed:
+ self._allowed = []
+
+ def __getattr__(self, key):
+ """Only return methods that are named in self._allowed."""
+ if key not in self._allowed:
+ raise AttributeError()
+ return getattr(self._proxy, key)
+
+ def __dir__(self):
+ """Only return methods that are named in self._allowed."""
+ return [x for x in dir(self._proxy) if x in self._allowed]
class Proxy(object):
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 20701cfa8..a3c3b25a1 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -61,10 +61,13 @@ class RequestLogging(wsgi.Middleware):
return rv
def log_request_completion(self, response, request, start):
- controller = request.environ.get('ec2.controller', None)
- if controller:
- controller = controller.__class__.__name__
- action = request.environ.get('ec2.action', None)
+ apireq = request.environ.get('ec2.request', None)
+ if apireq:
+ controller = apireq.controller
+ action = apireq.action
+ else:
+ controller = None
+ action = None
ctxt = request.environ.get('ec2.context', None)
delta = utils.utcnow() - start
seconds = delta.seconds
@@ -75,7 +78,7 @@ class RequestLogging(wsgi.Middleware):
microseconds,
request.remote_addr,
request.method,
- request.path_info,
+ "%s%s" % (request.script_name, request.path_info),
controller,
action,
response.status_int,
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index d8d90ad83..6a5609d4a 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -304,7 +304,7 @@ class AdminController(object):
* Volume (up, down, None)
* Volume Count
"""
- services = db.service_get_all(context)
+ services = db.service_get_all(context, False)
now = datetime.datetime.utcnow()
hosts = []
rv = []
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index e257e44e7..0da642318 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -196,7 +196,7 @@ class CloudController(object):
def _describe_availability_zones(self, context, **kwargs):
ctxt = context.elevated()
- enabled_services = db.service_get_all(ctxt)
+ enabled_services = db.service_get_all(ctxt, False)
disabled_services = db.service_get_all(ctxt, True)
available_zones = []
for zone in [service.availability_zone for service
@@ -221,7 +221,7 @@ class CloudController(object):
rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
'zoneState': 'available'}]}
- services = db.service_get_all(context)
+ services = db.service_get_all(context, False)
now = datetime.datetime.utcnow()
hosts = []
for host in [service['host'] for service in services]:
@@ -541,7 +541,7 @@ class CloudController(object):
volumes = []
for ec2_id in volume_id:
internal_id = ec2utils.ec2_id_to_id(ec2_id)
- volume = self.volume_api.get(context, internal_id)
+ volume = self.volume_api.get(context, volume_id=internal_id)
volumes.append(volume)
else:
volumes = self.volume_api.get_all(context)
@@ -585,9 +585,11 @@ class CloudController(object):
def create_volume(self, context, size, **kwargs):
LOG.audit(_("Create volume of %s GB"), size, context=context)
- volume = self.volume_api.create(context, size,
- kwargs.get('display_name'),
- kwargs.get('display_description'))
+ volume = self.volume_api.create(
+ context,
+ size=size,
+ name=kwargs.get('display_name'),
+ description=kwargs.get('display_description'))
# TODO(vish): Instance should be None at db layer instead of
# trying to lazy load, but for now we turn it into
# a dict to avoid an error.
@@ -606,7 +608,9 @@ class CloudController(object):
if field in kwargs:
changes[field] = kwargs[field]
if changes:
- self.volume_api.update(context, volume_id, kwargs)
+ self.volume_api.update(context,
+ volume_id=volume_id,
+ fields=changes)
return True
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
@@ -619,7 +623,7 @@ class CloudController(object):
instance_id=instance_id,
volume_id=volume_id,
device=device)
- volume = self.volume_api.get(context, volume_id)
+ volume = self.volume_api.get(context, volume_id=volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
'instanceId': ec2utils.id_to_ec2_id(instance_id),
@@ -630,7 +634,7 @@ class CloudController(object):
def detach_volume(self, context, volume_id, **kwargs):
volume_id = ec2utils.ec2_id_to_id(volume_id)
LOG.audit(_("Detach volume %s"), volume_id, context=context)
- volume = self.volume_api.get(context, volume_id)
+ volume = self.volume_api.get(context, volume_id=volume_id)
instance = self.compute_api.detach_volume(context, volume_id=volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
@@ -768,7 +772,7 @@ class CloudController(object):
def release_address(self, context, public_ip, **kwargs):
LOG.audit(_("Release address %s"), public_ip, context=context)
- self.network_api.release_floating_ip(context, public_ip)
+ self.network_api.release_floating_ip(context, address=public_ip)
return {'releaseResponse': ["Address released."]}
def associate_address(self, context, instance_id, public_ip, **kwargs):
@@ -782,7 +786,7 @@ class CloudController(object):
def disassociate_address(self, context, public_ip, **kwargs):
LOG.audit(_("Disassociate address %s"), public_ip, context=context)
- self.network_api.disassociate_floating_ip(context, public_ip)
+ self.network_api.disassociate_floating_ip(context, address=public_ip)
return {'disassociateResponse': ["Address disassociated."]}
def run_instances(self, context, **kwargs):
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 143b1d2b2..727655a86 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -35,6 +35,7 @@ from nova.api.openstack import flavors
from nova.api.openstack import images
from nova.api.openstack import limits
from nova.api.openstack import servers
+from nova.api.openstack import server_metadata
from nova.api.openstack import shared_ip_groups
from nova.api.openstack import users
from nova.api.openstack import zones
@@ -71,7 +72,7 @@ class APIRouter(wsgi.Router):
"""Simple paste factory, :class:`nova.wsgi.Router` doesn't have one"""
return cls()
- def __init__(self):
+ def __init__(self, ext_mgr=None):
self.server_members = {}
mapper = routes.Mapper()
self._setup_routes(mapper)
@@ -117,9 +118,6 @@ class APIRouter(wsgi.Router):
mapper.resource("image", "images", controller=images.Controller(),
collection={'detail': 'GET'})
- mapper.resource("flavor", "flavors", controller=flavors.Controller(),
- collection={'detail': 'GET'})
-
mapper.resource("shared_ip_group", "shared_ip_groups",
collection={'detail': 'GET'},
controller=shared_ip_groups.Controller())
@@ -138,6 +136,10 @@ class APIRouterV10(APIRouter):
collection={'detail': 'GET'},
member=self.server_members)
+ mapper.resource("flavor", "flavors",
+ controller=flavors.ControllerV10(),
+ collection={'detail': 'GET'})
+
class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""
@@ -148,6 +150,14 @@ class APIRouterV11(APIRouter):
controller=servers.ControllerV11(),
collection={'detail': 'GET'},
member=self.server_members)
+ mapper.resource("server_meta", "meta",
+ controller=server_metadata.Controller(),
+ parent_resource=dict(member_name='server',
+ collection_name='servers'))
+
+ mapper.resource("flavor", "flavors",
+ controller=flavors.ControllerV11(),
+ collection={'detail': 'GET'})
class Versions(wsgi.Application):
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index 2510ffb61..86066fa20 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -14,6 +14,7 @@
# under the License.
import common
+import webob.exc
from nova import exception
from nova import flags
@@ -51,10 +52,10 @@ class Controller(wsgi.Controller):
raise exception.NotAuthorized(_("Not admin user."))
def index(self, req):
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise faults.Fault(webob.exc.HTTPNotImplemented())
def detail(self, req):
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise faults.Fault(webob.exc.HTTPNotImplemented())
def show(self, req, id):
"""Return data about the given account id"""
@@ -69,7 +70,7 @@ class Controller(wsgi.Controller):
def create(self, req):
"""We use update with create-or-update semantics
because the id comes from an external source"""
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise faults.Fault(webob.exc.HTTPNotImplemented())
def update(self, req, id):
"""This is really create or update."""
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index bff050347..8cad1273a 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -20,9 +20,12 @@ from urlparse import urlparse
import webob
from nova import exception
+from nova import flags
+FLAGS = flags.FLAGS
-def limited(items, request, max_limit=1000):
+
+def limited(items, request, max_limit=FLAGS.osapi_max_limit):
"""
Return a slice of items according to requested offset and limit.
@@ -56,6 +59,36 @@ def limited(items, request, max_limit=1000):
return items[offset:range_end]
+def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
+ """Return a slice of items according to the requested marker and limit."""
+
+ try:
+ marker = int(request.GET.get('marker', 0))
+ except ValueError:
+ raise webob.exc.HTTPBadRequest(_('marker param must be an integer'))
+
+ try:
+ limit = int(request.GET.get('limit', max_limit))
+ except ValueError:
+ raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
+
+ if limit < 0:
+ raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
+
+ limit = min(max_limit, limit)
+ start_index = 0
+ if marker:
+ start_index = -1
+ for i, item in enumerate(items):
+ if item['id'] == marker:
+ start_index = i + 1
+ break
+ if start_index < 0:
+ raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker))
+ range_end = start_index + limit
+ return items[start_index:range_end]
+
+
def get_image_id_from_image_hash(image_service, context, image_hash):
"""Given an Image ID Hash, return an objectstore Image ID.
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
new file mode 100644
index 000000000..9d98d849a
--- /dev/null
+++ b/nova/api/openstack/extensions.py
@@ -0,0 +1,369 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# 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.
+
+import imp
+import os
+import sys
+import routes
+import webob.dec
+import webob.exc
+
+from nova import flags
+from nova import log as logging
+from nova import wsgi
+from nova.api.openstack import faults
+
+
+LOG = logging.getLogger('extensions')
+
+
+FLAGS = flags.FLAGS
+
+
+class ActionExtensionController(wsgi.Controller):
+
+ def __init__(self, application):
+
+ self.application = application
+ self.action_handlers = {}
+
+ def add_action(self, action_name, handler):
+ self.action_handlers[action_name] = handler
+
+ def action(self, req, id):
+
+ input_dict = self._deserialize(req.body, req.get_content_type())
+ for action_name, handler in self.action_handlers.iteritems():
+ if action_name in input_dict:
+ return handler(input_dict, req, id)
+ # no action handler found (bump to downstream application)
+ res = self.application
+ return res
+
+
+class ResponseExtensionController(wsgi.Controller):
+
+ def __init__(self, application):
+ self.application = application
+ self.handlers = []
+
+ def add_handler(self, handler):
+ self.handlers.append(handler)
+
+ def process(self, req, *args, **kwargs):
+ res = req.get_response(self.application)
+ content_type = req.best_match_content_type()
+ # currently response handlers are un-ordered
+ for handler in self.handlers:
+ res = handler(res)
+ try:
+ body = res.body
+ headers = res.headers
+ except AttributeError:
+ body = self._serialize(res, content_type)
+ headers = {"Content-Type": content_type}
+ res = webob.Response()
+ res.body = body
+ res.headers = headers
+ return res
+
+
+class ExtensionController(wsgi.Controller):
+
+ def __init__(self, extension_manager):
+ self.extension_manager = extension_manager
+
+ def _translate(self, ext):
+ ext_data = {}
+ ext_data['name'] = ext.get_name()
+ ext_data['alias'] = ext.get_alias()
+ ext_data['description'] = ext.get_description()
+ ext_data['namespace'] = ext.get_namespace()
+ ext_data['updated'] = ext.get_updated()
+ ext_data['links'] = [] # TODO: implement extension links
+ return ext_data
+
+ def index(self, req):
+ extensions = []
+ for alias, ext in self.extension_manager.extensions.iteritems():
+ extensions.append(self._translate(ext))
+ return dict(extensions=extensions)
+
+ def show(self, req, id):
+ # NOTE: the extensions alias is used as the 'id' for show
+ ext = self.extension_manager.extensions[id]
+ return self._translate(ext)
+
+ def delete(self, req, id):
+ raise faults.Fault(exc.HTTPNotFound())
+
+ def create(self, req):
+ raise faults.Fault(exc.HTTPNotFound())
+
+ def delete(self, req, id):
+ raise faults.Fault(exc.HTTPNotFound())
+
+
+class ExtensionMiddleware(wsgi.Middleware):
+ """
+ Extensions middleware that intercepts configured routes for extensions.
+ """
+ @classmethod
+ def factory(cls, global_config, **local_config):
+ """ paste factory """
+ def _factory(app):
+ return cls(app, **local_config)
+ return _factory
+
+ def _action_ext_controllers(self, application, ext_mgr, mapper):
+ """
+ Return a dict of ActionExtensionController objects by collection
+ """
+ action_controllers = {}
+ for action in ext_mgr.get_actions():
+ if not action.collection in action_controllers.keys():
+ controller = ActionExtensionController(application)
+ mapper.connect("/%s/:(id)/action.:(format)" %
+ action.collection,
+ action='action',
+ controller=controller,
+ conditions=dict(method=['POST']))
+ mapper.connect("/%s/:(id)/action" % action.collection,
+ action='action',
+ controller=controller,
+ conditions=dict(method=['POST']))
+ action_controllers[action.collection] = controller
+
+ return action_controllers
+
+ def _response_ext_controllers(self, application, ext_mgr, mapper):
+ """
+ Return a dict of ResponseExtensionController objects by collection
+ """
+ response_ext_controllers = {}
+ for resp_ext in ext_mgr.get_response_extensions():
+ if not resp_ext.key in response_ext_controllers.keys():
+ controller = ResponseExtensionController(application)
+ mapper.connect(resp_ext.url_route + '.:(format)',
+ action='process',
+ controller=controller,
+ conditions=resp_ext.conditions)
+
+ mapper.connect(resp_ext.url_route,
+ action='process',
+ controller=controller,
+ conditions=resp_ext.conditions)
+ response_ext_controllers[resp_ext.key] = controller
+
+ return response_ext_controllers
+
+ def __init__(self, application, ext_mgr=None):
+
+ if ext_mgr is None:
+ ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path)
+ self.ext_mgr = ext_mgr
+
+ mapper = routes.Mapper()
+
+ # extended resources
+ for resource in ext_mgr.get_resources():
+ LOG.debug(_('Extended resource: %s'),
+ resource.collection)
+ mapper.resource(resource.collection, resource.collection,
+ controller=resource.controller,
+ collection=resource.collection_actions,
+ member=resource.member_actions,
+ parent_resource=resource.parent)
+
+ # extended actions
+ action_controllers = self._action_ext_controllers(application, ext_mgr,
+ mapper)
+ for action in ext_mgr.get_actions():
+ LOG.debug(_('Extended action: %s'), action.action_name)
+ controller = action_controllers[action.collection]
+ controller.add_action(action.action_name, action.handler)
+
+ # extended responses
+ resp_controllers = self._response_ext_controllers(application, ext_mgr,
+ mapper)
+ for response_ext in ext_mgr.get_response_extensions():
+ LOG.debug(_('Extended response: %s'), response_ext.key)
+ controller = resp_controllers[response_ext.key]
+ controller.add_handler(response_ext.handler)
+
+ self._router = routes.middleware.RoutesMiddleware(self._dispatch,
+ mapper)
+
+ super(ExtensionMiddleware, self).__init__(application)
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ """
+ Route the incoming request with router.
+ """
+ req.environ['extended.app'] = self.application
+ return self._router
+
+ @staticmethod
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def _dispatch(req):
+ """
+ Returns the routed WSGI app's response or defers to the extended
+ application.
+ """
+ match = req.environ['wsgiorg.routing_args'][1]
+ if not match:
+ return req.environ['extended.app']
+ app = match['controller']
+ return app
+
+
+class ExtensionManager(object):
+ """
+ Load extensions from the configured extension path.
+ See nova/tests/api/openstack/extensions/foxinsocks.py for an example
+ extension implementation.
+ """
+
+ def __init__(self, path):
+ LOG.audit(_('Initializing extension manager.'))
+
+ self.path = path
+ self.extensions = {}
+ self._load_extensions()
+
+ def get_resources(self):
+ """
+ returns a list of ResourceExtension objects
+ """
+ resources = []
+ resources.append(ResourceExtension('extensions',
+ ExtensionController(self)))
+ for alias, ext in self.extensions.iteritems():
+ try:
+ resources.extend(ext.get_resources())
+ except AttributeError:
+ # NOTE: Extension aren't required to have resource extensions
+ pass
+ return resources
+
+ def get_actions(self):
+ """
+ returns a list of ActionExtension objects
+ """
+ actions = []
+ for alias, ext in self.extensions.iteritems():
+ try:
+ actions.extend(ext.get_actions())
+ except AttributeError:
+ # NOTE: Extension aren't required to have action extensions
+ pass
+ return actions
+
+ def get_response_extensions(self):
+ """
+ returns a list of ResponseExtension objects
+ """
+ response_exts = []
+ for alias, ext in self.extensions.iteritems():
+ try:
+ response_exts.extend(ext.get_response_extensions())
+ except AttributeError:
+ # NOTE: Extension aren't required to have response extensions
+ pass
+ return response_exts
+
+ def _check_extension(self, extension):
+ """
+ Checks for required methods in extension objects.
+ """
+ try:
+ LOG.debug(_('Ext name: %s'), extension.get_name())
+ LOG.debug(_('Ext alias: %s'), extension.get_alias())
+ LOG.debug(_('Ext description: %s'), extension.get_description())
+ LOG.debug(_('Ext namespace: %s'), extension.get_namespace())
+ LOG.debug(_('Ext updated: %s'), extension.get_updated())
+ except AttributeError as ex:
+ LOG.exception(_("Exception loading extension: %s"), unicode(ex))
+
+ def _load_extensions(self):
+ """
+ Load extensions from the configured path. The extension name is
+ constructed from the module_name. If your extension module was named
+ widgets.py the extension class within that module should be
+ 'Widgets'.
+
+ See nova/tests/api/openstack/extensions/foxinsocks.py for an example
+ extension implementation.
+ """
+ if not os.path.exists(self.path):
+ return
+
+ for f in os.listdir(self.path):
+ LOG.audit(_('Loading extension file: %s'), f)
+ mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
+ ext_path = os.path.join(self.path, f)
+ if file_ext.lower() == '.py':
+ mod = imp.load_source(mod_name, ext_path)
+ ext_name = mod_name[0].upper() + mod_name[1:]
+ try:
+ new_ext = getattr(mod, ext_name)()
+ self._check_extension(new_ext)
+ self.extensions[new_ext.get_alias()] = new_ext
+ except AttributeError as ex:
+ LOG.exception(_("Exception loading extension: %s"),
+ unicode(ex))
+
+
+class ResponseExtension(object):
+ """
+ ResponseExtension objects can be used to add data to responses from
+ core nova OpenStack API controllers.
+ """
+
+ def __init__(self, method, url_route, handler):
+ self.url_route = url_route
+ self.handler = handler
+ self.conditions = dict(method=[method])
+ self.key = "%s-%s" % (method, url_route)
+
+
+class ActionExtension(object):
+ """
+ ActionExtension objects can be used to add custom actions to core nova
+ nova OpenStack API controllers.
+ """
+
+ def __init__(self, collection, action_name, handler):
+ self.collection = collection
+ self.action_name = action_name
+ self.handler = handler
+
+
+class ResourceExtension(object):
+ """
+ ResourceExtension objects can be used to add top level resources
+ to the OpenStack API in nova.
+ """
+
+ def __init__(self, collection, controller, parent=None,
+ collection_actions={}, member_actions={}):
+ self.collection = collection
+ self.controller = controller
+ self.parent = parent
+ self.collection_actions = collection_actions
+ self.member_actions = member_actions
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index c99b945fb..5b99b5a6f 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -15,16 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from webob import exc
+import webob
from nova import db
-from nova import context
-from nova.api.openstack import faults
-from nova.api.openstack import common
-from nova.compute import instance_types
-from nova.api.openstack.views import flavors as flavors_views
+from nova import exception
from nova import wsgi
-import nova.api.openstack
+from nova.api.openstack import views
class Controller(wsgi.Controller):
@@ -33,33 +29,50 @@ class Controller(wsgi.Controller):
_serialization_metadata = {
'application/xml': {
"attributes": {
- "flavor": ["id", "name", "ram", "disk"]}}}
+ "flavor": ["id", "name", "ram", "disk"],
+ "link": ["rel", "type", "href"],
+ }
+ }
+ }
def index(self, req):
"""Return all flavors in brief."""
- return dict(flavors=[dict(id=flavor['id'], name=flavor['name'])
- for flavor in self.detail(req)['flavors']])
+ items = self._get_flavors(req, is_detail=False)
+ return dict(flavors=items)
def detail(self, req):
"""Return all flavors in detail."""
- items = [self.show(req, id)['flavor'] for id in self._all_ids(req)]
+ items = self._get_flavors(req, is_detail=True)
return dict(flavors=items)
+ def _get_flavors(self, req, is_detail=True):
+ """Helper function that returns a list of flavor dicts."""
+ ctxt = req.environ['nova.context']
+ flavors = db.api.instance_type_get_all(ctxt)
+ builder = self._get_view_builder(req)
+ items = [builder.build(flavor, is_detail=is_detail)
+ for flavor in flavors.values()]
+ return items
+
def show(self, req, id):
"""Return data about the given flavor id."""
- ctxt = req.environ['nova.context']
- flavor = db.api.instance_type_get_by_flavor_id(ctxt, id)
- values = {
- "id": flavor["flavorid"],
- "name": flavor["name"],
- "ram": flavor["memory_mb"],
- "disk": flavor["local_gb"],
- }
+ try:
+ ctxt = req.environ['nova.context']
+ flavor = db.api.instance_type_get_by_flavor_id(ctxt, id)
+ except exception.NotFound:
+ return webob.exc.HTTPNotFound()
+
+ builder = self._get_view_builder(req)
+ values = builder.build(flavor, is_detail=True)
return dict(flavor=values)
- def _all_ids(self, req):
- """Return the list of all flavorids."""
- ctxt = req.environ['nova.context']
- inst_types = db.api.instance_type_get_all(ctxt)
- flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()]
- return sorted(flavor_ids)
+
+class ControllerV10(Controller):
+ def _get_view_builder(self, req):
+ return views.flavors.ViewBuilder()
+
+
+class ControllerV11(Controller):
+ def _get_view_builder(self, req):
+ base_url = req.application_url
+ return views.flavors.ViewBuilderV11(base_url)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 99c14275a..79852ecc6 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -15,10 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
+
from webob import exc
from nova import compute
+from nova import exception
from nova import flags
+from nova import log
from nova import utils
from nova import wsgi
import nova.api.openstack
@@ -27,6 +31,8 @@ from nova.api.openstack import faults
import nova.image.service
+LOG = log.getLogger('nova.api.openstack.images')
+
FLAGS = flags.FLAGS
@@ -84,8 +90,6 @@ def _translate_status(item):
# S3ImageService
pass
- return item
-
def _filter_keys(item, keys):
"""
@@ -104,6 +108,100 @@ def _convert_image_id_to_hash(image):
image['id'] = image_id
+def _translate_s3_like_images(image_metadata):
+ """Work-around for leaky S3ImageService abstraction"""
+ api_metadata = image_metadata.copy()
+ _convert_image_id_to_hash(api_metadata)
+ api_metadata = _translate_keys(api_metadata)
+ _translate_status(api_metadata)
+ return api_metadata
+
+
+def _translate_from_image_service_to_api(image_metadata):
+ """Translate from ImageService to OpenStack API style attribute names
+
+ This involves 4 steps:
+
+ 1. Filter out attributes that the OpenStack API doesn't need
+
+ 2. Translate from base image attributes from names used by
+ BaseImageService to names used by OpenStack API
+
+ 3. Add in any image properties
+
+ 4. Format values according to API spec (for example dates must
+ look like "2010-08-10T12:00:00Z")
+ """
+ service_metadata = image_metadata.copy()
+ properties = service_metadata.pop('properties', {})
+
+ # 1. Filter out unecessary attributes
+ api_keys = ['id', 'name', 'updated_at', 'created_at', 'status']
+ api_metadata = utils.subset_dict(service_metadata, api_keys)
+
+ # 2. Translate base image attributes
+ api_map = {'updated_at': 'updated', 'created_at': 'created'}
+ api_metadata = utils.map_dict_keys(api_metadata, api_map)
+
+ # 3. Add in any image properties
+ # 3a. serverId is used for backups and snapshots
+ try:
+ api_metadata['serverId'] = int(properties['instance_id'])
+ except KeyError:
+ pass # skip if it's not present
+ except ValueError:
+ pass # skip if it's not an integer
+
+ # 3b. Progress special case
+ # TODO(sirp): ImageService doesn't have a notion of progress yet, so for
+ # now just fake it
+ if service_metadata['status'] == 'saving':
+ api_metadata['progress'] = 0
+
+ # 4. Format values
+ # 4a. Format Image Status (API requires uppercase)
+ api_metadata['status'] = _format_status_for_api(api_metadata['status'])
+
+ # 4b. Format timestamps
+ for attr in ('created', 'updated'):
+ if attr in api_metadata:
+ api_metadata[attr] = _format_datetime_for_api(
+ api_metadata[attr])
+
+ return api_metadata
+
+
+def _format_status_for_api(status):
+ """Return status in a format compliant with OpenStack API"""
+ mapping = {'queued': 'QUEUED',
+ 'preparing': 'PREPARING',
+ 'saving': 'SAVING',
+ 'active': 'ACTIVE',
+ 'killed': 'FAILED'}
+ return mapping[status]
+
+
+def _format_datetime_for_api(datetime_):
+ """Stringify datetime objects in a format compliant with OpenStack API"""
+ API_DATETIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
+ return datetime_.strftime(API_DATETIME_FMT)
+
+
+def _safe_translate(image_metadata):
+ """Translate attributes for OpenStack API, temporary workaround for
+ S3ImageService attribute leakage.
+ """
+ # FIXME(sirp): The S3ImageService appears to be leaking implementation
+ # details, including its internal attribute names, and internal
+ # `status` values. Working around it for now.
+ s3_like_image = ('imageId' in image_metadata)
+ if s3_like_image:
+ translate = _translate_s3_like_images
+ else:
+ translate = _translate_from_image_service_to_api
+ return translate(image_metadata)
+
+
class Controller(wsgi.Controller):
_serialization_metadata = {
@@ -117,34 +215,32 @@ class Controller(wsgi.Controller):
def index(self, req):
"""Return all public images in brief"""
- items = self._service.index(req.environ['nova.context'])
- items = common.limited(items, req)
- items = [_filter_keys(item, ('id', 'name')) for item in items]
- return dict(images=items)
+ context = req.environ['nova.context']
+ image_metas = self._service.index(context)
+ image_metas = common.limited(image_metas, req)
+ return dict(images=image_metas)
def detail(self, req):
"""Return all public images in detail"""
- try:
- items = self._service.detail(req.environ['nova.context'])
- except NotImplementedError:
- items = self._service.index(req.environ['nova.context'])
- for image in items:
- _convert_image_id_to_hash(image)
-
- items = common.limited(items, req)
- items = [_translate_keys(item) for item in items]
- items = [_translate_status(item) for item in items]
- return dict(images=items)
+ context = req.environ['nova.context']
+ image_metas = self._service.detail(context)
+ image_metas = common.limited(image_metas, req)
+ api_image_metas = [_safe_translate(image_meta)
+ for image_meta in image_metas]
+ return dict(images=api_image_metas)
def show(self, req, id):
"""Return data about the given image id"""
- image_id = common.get_image_id_from_image_hash(self._service,
- req.environ['nova.context'], id)
+ context = req.environ['nova.context']
+ try:
+ image_id = common.get_image_id_from_image_hash(
+ self._service, context, id)
+ except exception.NotFound:
+ raise faults.Fault(exc.HTTPNotFound())
- image = self._service.show(req.environ['nova.context'], image_id)
- _convert_image_id_to_hash(image)
- self._format_image_dates(image)
- return dict(image=image)
+ image_meta = self._service.show(context, image_id)
+ api_image_meta = _safe_translate(image_meta)
+ return dict(image=api_image_meta)
def delete(self, req, id):
# Only public images are supported for now.
@@ -155,18 +251,12 @@ class Controller(wsgi.Controller):
env = self._deserialize(req.body, req.get_content_type())
instance_id = env["image"]["serverId"]
name = env["image"]["name"]
-
image_meta = compute.API().snapshot(
context, instance_id, name)
-
- return dict(image=image_meta)
+ api_image_meta = _safe_translate(image_meta)
+ return dict(image=api_image_meta)
def update(self, req, id):
# Users may not modify public images, and that's all that
# we support for now.
raise faults.Fault(exc.HTTPNotFound())
-
- def _format_image_dates(self, image):
- for attr in ['created_at', 'updated_at', 'deleted_at']:
- if image.get(attr) is not None:
- image[attr] = image[attr].strftime('%Y-%m-%dT%H:%M:%SZ')
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
new file mode 100644
index 000000000..45bbac99d
--- /dev/null
+++ b/nova/api/openstack/server_metadata.py
@@ -0,0 +1,78 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from webob import exc
+
+from nova import compute
+from nova import wsgi
+from nova.api.openstack import faults
+
+
+class Controller(wsgi.Controller):
+ """ The server metadata API controller for the Openstack API """
+
+ def __init__(self):
+ self.compute_api = compute.API()
+ super(Controller, self).__init__()
+
+ def _get_metadata(self, context, server_id):
+ metadata = self.compute_api.get_instance_metadata(context, server_id)
+ meta_dict = {}
+ for key, value in metadata.iteritems():
+ meta_dict[key] = value
+ return dict(metadata=meta_dict)
+
+ def index(self, req, server_id):
+ """ Returns the list of metadata for a given instance """
+ context = req.environ['nova.context']
+ return self._get_metadata(context, server_id)
+
+ def create(self, req, server_id):
+ context = req.environ['nova.context']
+ body = self._deserialize(req.body, req.get_content_type())
+ self.compute_api.update_or_create_instance_metadata(context,
+ server_id,
+ body['metadata'])
+ return req.body
+
+ def update(self, req, server_id, id):
+ context = req.environ['nova.context']
+ body = self._deserialize(req.body, req.get_content_type())
+ if not id in body:
+ expl = _('Request body and URI mismatch')
+ raise exc.HTTPBadRequest(explanation=expl)
+ if len(body) > 1:
+ expl = _('Request body contains too many items')
+ raise exc.HTTPBadRequest(explanation=expl)
+ self.compute_api.update_or_create_instance_metadata(context,
+ server_id,
+ body)
+ return req.body
+
+ def show(self, req, server_id, id):
+ """ Return a single metadata item """
+ context = req.environ['nova.context']
+ data = self._get_metadata(context, server_id)
+ if id in data['metadata']:
+ return {id: data['metadata'][id]}
+ else:
+ return faults.Fault(exc.HTTPNotFound())
+
+ def delete(self, req, server_id, id):
+ """ Deletes an existing metadata """
+ context = req.environ['nova.context']
+ self.compute_api.delete_instance_metadata(context, server_id, id)
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index ff9076003..05027bf1e 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -15,19 +15,19 @@
import base64
import hashlib
-import json
import traceback
-from xml.dom import minidom
from webob import exc
+from xml.dom import minidom
from nova import compute
from nova import context
from nova import exception
from nova import flags
from nova import log as logging
-from nova import wsgi
+from nova import quota
from nova import utils
+from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
import nova.api.openstack.views.addresses
@@ -36,8 +36,8 @@ import nova.api.openstack.views.servers
from nova.auth import manager as auth_manager
from nova.compute import instance_types
from nova.compute import power_state
-from nova.quota import QuotaError
import nova.api.openstack
+from nova.scheduler import api as scheduler_api
LOG = logging.getLogger('server')
@@ -86,21 +86,24 @@ class Controller(wsgi.Controller):
builder - the response model builder
"""
instance_list = self.compute_api.get_all(req.environ['nova.context'])
- limited_list = common.limited(instance_list, req)
+ limited_list = self._limit_items(instance_list, req)
builder = self._get_view_builder(req)
servers = [builder.build(inst, is_detail)['server']
for inst in limited_list]
return dict(servers=servers)
+ @scheduler_api.redirect_handler
def show(self, req, id):
""" Returns server details by server id """
try:
- instance = self.compute_api.get(req.environ['nova.context'], id)
+ instance = self.compute_api.routing_get(
+ req.environ['nova.context'], id)
builder = self._get_view_builder(req)
return builder.build(instance, is_detail=True)
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
+ @scheduler_api.redirect_handler
def delete(self, req, id):
""" Destroys a server """
try:
@@ -160,8 +163,8 @@ class Controller(wsgi.Controller):
key_data=key_data,
metadata=metadata,
injected_files=injected_files)
- except QuotaError as error:
- self._handle_quota_errors(error)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
inst['instance_type'] = flavor_id
inst['image_id'] = requested_image_id
@@ -215,7 +218,7 @@ class Controller(wsgi.Controller):
injected_files.append((path, contents))
return injected_files
- def _handle_quota_errors(self, error):
+ def _handle_quota_error(self, error):
"""
Reraise quota errors as api-specific http exceptions
"""
@@ -231,6 +234,7 @@ class Controller(wsgi.Controller):
# if the original error is okay, just reraise it
raise error
+ @scheduler_api.redirect_handler
def update(self, req, id):
""" Updates the server name or password """
if len(req.body) == 0:
@@ -246,7 +250,7 @@ class Controller(wsgi.Controller):
update_dict['admin_pass'] = inst_dict['server']['adminPass']
try:
self.compute_api.set_admin_password(ctxt, id)
- except exception.TimeoutException, e:
+ except exception.TimeoutException:
return exc.HTTPRequestTimeout()
if 'name' in inst_dict['server']:
update_dict['display_name'] = inst_dict['server']['name']
@@ -256,6 +260,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPNoContent()
+ @scheduler_api.redirect_handler
def action(self, req, id):
"""Multi-purpose method used to reboot, rebuild, or
resize a server"""
@@ -321,6 +326,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def lock(self, req, id):
"""
lock the instance with id
@@ -336,6 +342,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def unlock(self, req, id):
"""
unlock the instance with id
@@ -351,6 +358,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def get_lock(self, req, id):
"""
return the boolean state of (instance with id)'s lock
@@ -365,6 +373,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def reset_network(self, req, id):
"""
Reset networking on an instance (admin only).
@@ -379,6 +388,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def inject_network_info(self, req, id):
"""
Inject network info for an instance (admin only).
@@ -393,6 +403,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def pause(self, req, id):
""" Permit Admins to Pause the server. """
ctxt = req.environ['nova.context']
@@ -404,6 +415,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def unpause(self, req, id):
""" Permit Admins to Unpause the server. """
ctxt = req.environ['nova.context']
@@ -415,6 +427,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def suspend(self, req, id):
"""permit admins to suspend the server"""
context = req.environ['nova.context']
@@ -426,6 +439,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def resume(self, req, id):
"""permit admins to resume the server from suspend"""
context = req.environ['nova.context']
@@ -437,6 +451,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def rescue(self, req, id):
"""Permit users to rescue the server."""
context = req.environ["nova.context"]
@@ -448,6 +463,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def unrescue(self, req, id):
"""Permit users to unrescue the server."""
context = req.environ["nova.context"]
@@ -459,6 +475,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def get_ajax_console(self, req, id):
""" Returns a url to an instance's ajaxterm console. """
try:
@@ -468,6 +485,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted()
+ @scheduler_api.redirect_handler
def diagnostics(self, req, id):
"""Permit Admins to retrieve server diagnostics."""
ctxt = req.environ["nova.context"]
@@ -537,6 +555,9 @@ class ControllerV10(Controller):
def _get_addresses_view_builder(self, req):
return nova.api.openstack.views.addresses.ViewBuilderV10(req)
+ def _limit_items(self, items, req):
+ return common.limited(items, req)
+
class ControllerV11(Controller):
def _image_id_from_req_data(self, data):
@@ -560,6 +581,9 @@ class ControllerV11(Controller):
def _get_addresses_view_builder(self, req):
return nova.api.openstack.views.addresses.ViewBuilderV11(req)
+ def _limit_items(self, items, req):
+ return common.limited_by_marker(items, req)
+
class ServerCreateRequestXMLDeserializer(object):
"""
diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py
index 18bd779c0..462890ab2 100644
--- a/nova/api/openstack/views/flavors.py
+++ b/nova/api/openstack/views/flavors.py
@@ -19,16 +19,78 @@ from nova.api.openstack import common
class ViewBuilder(object):
- def __init__(self):
- pass
- def build(self, flavor_obj):
- raise NotImplementedError()
+ def build(self, flavor_obj, is_detail=False):
+ """Generic method used to generate a flavor entity."""
+ if is_detail:
+ flavor = self._build_detail(flavor_obj)
+ else:
+ flavor = self._build_simple(flavor_obj)
+
+ self._build_extra(flavor)
+
+ return flavor
+
+ def _build_simple(self, flavor_obj):
+ """Build a minimal representation of a flavor."""
+ return {
+ "id": flavor_obj["flavorid"],
+ "name": flavor_obj["name"],
+ }
+
+ def _build_detail(self, flavor_obj):
+ """Build a more complete representation of a flavor."""
+ simple = self._build_simple(flavor_obj)
+
+ detail = {
+ "ram": flavor_obj["memory_mb"],
+ "disk": flavor_obj["local_gb"],
+ }
+
+ detail.update(simple)
+
+ return detail
+
+ def _build_extra(self, flavor_obj):
+ """Hook for version-specific changes to newly created flavor object."""
+ pass
class ViewBuilderV11(ViewBuilder):
+ """Openstack API v1.1 flavors view builder."""
+
def __init__(self, base_url):
+ """
+ :param base_url: url of the root wsgi application
+ """
self.base_url = base_url
+ def _build_extra(self, flavor_obj):
+ flavor_obj["links"] = self._build_links(flavor_obj)
+
+ def _build_links(self, flavor_obj):
+ """Generate a container of links that refer to the provided flavor."""
+ href = self.generate_href(flavor_obj["id"])
+
+ links = [
+ {
+ "rel": "self",
+ "href": href,
+ },
+ {
+ "rel": "bookmark",
+ "type": "application/json",
+ "href": href,
+ },
+ {
+ "rel": "bookmark",
+ "type": "application/xml",
+ "href": href,
+ },
+ ]
+
+ return links
+
def generate_href(self, flavor_id):
+ """Create an url that refers to a specific flavor id."""
return "%s/flavors/%s" % (self.base_url, flavor_id)
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 8fe84275a..846cb48a1 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -15,9 +15,10 @@
import common
+from nova import db
from nova import flags
+from nova import log as logging
from nova import wsgi
-from nova import db
from nova.scheduler import api
@@ -38,7 +39,8 @@ def _exclude_keys(item, keys):
def _scrub_zone(zone):
- return _filter_keys(zone, ('id', 'api_url'))
+ return _exclude_keys(zone, ('username', 'password', 'created_at',
+ 'deleted', 'deleted_at', 'updated_at'))
class Controller(wsgi.Controller):
@@ -52,13 +54,9 @@ class Controller(wsgi.Controller):
"""Return all zones in brief"""
# Ask the ZoneManager in the Scheduler for most recent data,
# or fall-back to the database ...
- items = api.API().get_zone_list(req.environ['nova.context'])
- if not items:
- items = db.zone_get_all(req.environ['nova.context'])
-
+ items = api.get_zone_list(req.environ['nova.context'])
items = common.limited(items, req)
- items = [_exclude_keys(item, ['username', 'password'])
- for item in items]
+ items = [_scrub_zone(item) for item in items]
return dict(zones=items)
def detail(self, req):
@@ -67,29 +65,37 @@ class Controller(wsgi.Controller):
def info(self, req):
"""Return name and capabilities for this zone."""
- return dict(zone=dict(name=FLAGS.zone_name,
- capabilities=FLAGS.zone_capabilities))
+ items = api.get_zone_capabilities(req.environ['nova.context'])
+
+ zone = dict(name=FLAGS.zone_name)
+ caps = FLAGS.zone_capabilities
+ for cap in caps:
+ key, value = cap.split('=')
+ zone[key] = value
+ for item, (min_value, max_value) in items.iteritems():
+ zone[item] = "%s,%s" % (min_value, max_value)
+ return dict(zone=zone)
def show(self, req, id):
"""Return data about the given zone id"""
zone_id = int(id)
- zone = db.zone_get(req.environ['nova.context'], zone_id)
+ zone = api.zone_get(req.environ['nova.context'], zone_id)
return dict(zone=_scrub_zone(zone))
def delete(self, req, id):
zone_id = int(id)
- db.zone_delete(req.environ['nova.context'], zone_id)
+ api.zone_delete(req.environ['nova.context'], zone_id)
return {}
def create(self, req):
context = req.environ['nova.context']
env = self._deserialize(req.body, req.get_content_type())
- zone = db.zone_create(context, env["zone"])
+ zone = api.zone_create(context, env["zone"])
return dict(zone=_scrub_zone(zone))
def update(self, req, id):
context = req.environ['nova.context']
env = self._deserialize(req.body, req.get_content_type())
zone_id = int(id)
- zone = db.zone_update(context, zone_id, env["zone"])
+ zone = api.zone_update(context, zone_id, env["zone"])
return dict(zone=_scrub_zone(zone))