summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/nova-api51
-rwxr-xr-xbin/nova-api-paste109
-rwxr-xr-xbin/nova-combined29
-rw-r--r--etc/nova-api.conf74
-rw-r--r--nova/api/__init__.py93
-rw-r--r--nova/api/ec2/__init__.py82
-rw-r--r--nova/api/ec2/apirequest.py7
-rw-r--r--nova/api/ec2/cloud.py6
-rw-r--r--nova/api/ec2/metadatarequesthandler.py7
-rw-r--r--nova/api/openstack/__init__.py35
-rw-r--r--nova/api/openstack/auth.py6
-rw-r--r--nova/api/openstack/images.py23
-rw-r--r--nova/api/openstack/ratelimiting/__init__.py6
-rw-r--r--nova/auth/manager.py22
-rw-r--r--nova/cloudpipe/pipelib.py4
-rw-r--r--nova/compute/api.py44
-rw-r--r--nova/compute/manager.py4
-rw-r--r--nova/flags.py15
-rw-r--r--nova/image/glance.py155
-rw-r--r--nova/log.py2
-rw-r--r--nova/network/linux_net.py2
-rw-r--r--nova/tests/api/fakes.py26
-rw-r--r--nova/tests/api/openstack/__init__.py21
-rw-r--r--nova/tests/api/openstack/fakes.py75
-rw-r--r--nova/tests/api/openstack/test_adminapi.py10
-rw-r--r--nova/tests/api/openstack/test_api.py27
-rw-r--r--nova/tests/api/openstack/test_auth.py18
-rw-r--r--nova/tests/api/openstack/test_flavors.py2
-rw-r--r--nova/tests/api/openstack/test_images.py4
-rw-r--r--nova/tests/api/openstack/test_servers.py40
-rw-r--r--nova/tests/api/test.py81
-rw-r--r--nova/tests/test_access.py22
-rw-r--r--nova/tests/test_api.py9
-rw-r--r--nova/tests/test_compute.py3
-rw-r--r--nova/utils.py21
-rw-r--r--nova/virt/libvirt_conn.py2
-rw-r--r--nova/virt/xenapi/vm_utils.py23
-rw-r--r--nova/virt/xenapi/vmops.py6
-rw-r--r--nova/virt/xenapi_conn.py8
-rw-r--r--nova/wsgi.py122
-rw-r--r--plugins/xenserver/xenapi/etc/xapi.d/plugins/glance11
41 files changed, 536 insertions, 771 deletions
diff --git a/bin/nova-api b/bin/nova-api
index c7cbb3ab4..7b4fbeab1 100755
--- a/bin/nova-api
+++ b/bin/nova-api
@@ -34,22 +34,53 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
gettext.install('nova', unicode=1)
-from nova import api
from nova import flags
-from nova import utils
+from nova import log as logging
from nova import wsgi
+logging.basicConfig()
+LOG = logging.getLogger('nova.api')
+LOG.setLevel(logging.DEBUG)
FLAGS = flags.FLAGS
-flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host')
-flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port')
-flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host')
+API_ENDPOINTS = ['ec2', 'osapi']
-if __name__ == '__main__':
- utils.default_flagfile()
- FLAGS(sys.argv)
+
+def run_app(paste_config_file):
+ LOG.debug(_("Using paste.deploy config at: %s"), paste_config_file)
+ apps = []
+ for api in API_ENDPOINTS:
+ config = wsgi.load_paste_configuration(paste_config_file, api)
+ if config is None:
+ LOG.debug(_("No paste configuration for app: %s"), api)
+ continue
+ LOG.debug(_("App Config: %s\n%r"), api, config)
+ wsgi.paste_config_to_flags(config, {
+ "verbose": FLAGS.verbose,
+ "%s_host" % api: config.get('host', '0.0.0.0'),
+ "%s_port" % api: getattr(FLAGS, "%s_port" % api)})
+ LOG.info(_("Running %s API"), api)
+ app = wsgi.load_paste_app(paste_config_file, api)
+ apps.append((app, getattr(FLAGS, "%s_port" % api),
+ getattr(FLAGS, "%s_host" % api)))
+ if len(apps) == 0:
+ LOG.error(_("No known API applications configured in %s."),
+ paste_config_file)
+ return
+
+ # NOTE(todd): redo logging config, verbose could be set in paste config
+ logging.basicConfig()
server = wsgi.Server()
- server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host)
- server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host)
+ for app in apps:
+ server.start(*app)
server.wait()
+
+
+if __name__ == '__main__':
+ FLAGS(sys.argv)
+ conf = wsgi.paste_config_file('nova-api.conf')
+ if conf:
+ run_app(conf)
+ else:
+ LOG.error(_("No paste configuration found for: %s"), 'nova-api.conf')
diff --git a/bin/nova-api-paste b/bin/nova-api-paste
deleted file mode 100755
index 419f0bbdc..000000000
--- a/bin/nova-api-paste
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python
-# pylint: disable-msg=C0103
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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.
-
-"""Starter script for Nova API."""
-
-import gettext
-import os
-import sys
-
-from paste import deploy
-
-# If ../nova/__init__.py exists, add ../ to Python search path, so that
-# it will override what happens to be installed in /usr/(local/)lib/python...
-possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
- os.pardir,
- os.pardir))
-if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
- sys.path.insert(0, possible_topdir)
-
-gettext.install('nova', unicode=1)
-
-from nova import flags
-from nova import log as logging
-from nova import wsgi
-
-LOG = logging.getLogger('nova.api')
-LOG.setLevel(logging.DEBUG)
-LOG.addHandler(logging.StreamHandler())
-
-FLAGS = flags.FLAGS
-
-API_ENDPOINTS = ['ec2', 'openstack']
-
-
-def load_configuration(paste_config):
- """Load the paste configuration from the config file and return it."""
- config = None
- # Try each known name to get the global DEFAULTS, which will give ports
- for name in API_ENDPOINTS:
- try:
- config = deploy.appconfig("config:%s" % paste_config, name=name)
- except LookupError:
- pass
- if config:
- verbose = config.get('verbose', None)
- if verbose:
- FLAGS.verbose = int(verbose) == 1
- if FLAGS.verbose:
- logging.getLogger().setLevel(logging.DEBUG)
- return config
- LOG.debug(_("Paste config at %s has no secion for known apis"),
- paste_config)
- print _("Paste config at %s has no secion for any known apis") % \
- paste_config
- os.exit(1)
-
-
-def launch_api(paste_config_file, section, server, port, host):
- """Launch an api server from the specified port and IP."""
- LOG.debug(_("Launching %s api on %s:%s"), section, host, port)
- app = deploy.loadapp('config:%s' % paste_config_file, name=section)
- server.start(app, int(port), host)
-
-
-def run_app(paste_config_file):
- LOG.debug(_("Using paste.deploy config at: %s"), configfile)
- config = load_configuration(paste_config_file)
- LOG.debug(_("Configuration: %r"), config)
- server = wsgi.Server()
- ip = config.get('host', '0.0.0.0')
- for api in API_ENDPOINTS:
- port = config.get("%s_port" % api, None)
- if not port:
- continue
- host = config.get("%s_host" % api, ip)
- launch_api(configfile, api, server, port, host)
- LOG.debug(_("All api servers launched, now waiting"))
- server.wait()
-
-
-if __name__ == '__main__':
- FLAGS(sys.argv)
- configfiles = ['/etc/nova/nova-api.conf']
- if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
- configfiles.insert(0,
- os.path.join(possible_topdir, 'etc', 'nova-api.conf'))
- for configfile in configfiles:
- if os.path.exists(configfile):
- run_app(configfile)
- break
- else:
- LOG.debug(_("Skipping missing configuration: %s"), configfile)
diff --git a/bin/nova-combined b/bin/nova-combined
index f932fdfd5..913c866bf 100755
--- a/bin/nova-combined
+++ b/bin/nova-combined
@@ -36,22 +36,20 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
gettext.install('nova', unicode=1)
-from nova import api
from nova import flags
+from nova import log as logging
from nova import service
from nova import utils
from nova import wsgi
FLAGS = flags.FLAGS
-flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host')
-flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port')
-flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host')
if __name__ == '__main__':
utils.default_flagfile()
FLAGS(sys.argv)
+ logging.basicConfig()
compute = service.Service.create(binary='nova-compute')
network = service.Service.create(binary='nova-network')
@@ -61,7 +59,22 @@ if __name__ == '__main__':
service.serve(compute, network, volume, scheduler)
- server = wsgi.Server()
- server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host)
- server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host)
- server.wait()
+ apps = []
+ paste_config_file = wsgi.paste_config_file('nova-api.conf')
+ for api in ['osapi', 'ec2']:
+ config = wsgi.load_paste_configuration(paste_config_file, api)
+ if config is None:
+ continue
+ wsgi.paste_config_to_flags(config, {
+ "verbose": FLAGS.verbose,
+ "%s_host" % api: config.get('host', '0.0.0.0'),
+ "%s_port" % api: getattr(FLAGS, "%s_port" % api)})
+ app = wsgi.load_paste_app(paste_config_file, api)
+ apps.append((app, getattr(FLAGS, "%s_port" % api),
+ getattr(FLAGS, "%s_host" % api)))
+ if len(apps) > 0:
+ logging.basicConfig()
+ server = wsgi.Server()
+ for app in apps:
+ server.start(*app)
+ server.wait()
diff --git a/etc/nova-api.conf b/etc/nova-api.conf
index c5dd0aaec..4873e465d 100644
--- a/etc/nova-api.conf
+++ b/etc/nova-api.conf
@@ -1,9 +1,5 @@
[DEFAULT]
verbose = 1
-ec2_port = 8773
-ec2_address = 0.0.0.0
-openstack_port = 8774
-openstack_address = 0.0.0.0
#######
# EC2 #
@@ -12,52 +8,80 @@ openstack_address = 0.0.0.0
[composite:ec2]
use = egg:Paste#urlmap
/: ec2versions
-/services: ec2api
+/services/Cloud: ec2cloud
+/services/Admin: ec2admin
/latest: ec2metadata
-/200: ec2metadata
+/20: ec2metadata
/1.0: ec2metadata
-[pipeline:ec2api]
-pipeline = authenticate router authorizer ec2executor
+[pipeline:ec2cloud]
+pipeline = logrequest authenticate cloudrequest authorizer ec2executor
+#pipeline = logrequest ec2lockout authenticate cloudrequest authorizer ec2executor
+
+[pipeline:ec2admin]
+pipeline = logrequest authenticate adminrequest authorizer ec2executor
+
+[pipeline:ec2metadata]
+pipeline = logrequest ec2md
+
+[pipeline:ec2versions]
+pipeline = logrequest ec2ver
+
+[filter:logrequest]
+paste.filter_factory = nova.api.ec2:RequestLogging.factory
+
+[filter:ec2lockout]
+paste.filter_factory = nova.api.ec2:Lockout.factory
[filter:authenticate]
-paste.filter_factory = nova.api.ec2:authenticate_factory
+paste.filter_factory = nova.api.ec2:Authenticate.factory
+
+[filter:cloudrequest]
+controller = nova.api.ec2.cloud.CloudController
+paste.filter_factory = nova.api.ec2:Requestify.factory
-[filter:router]
-paste.filter_factory = nova.api.ec2:router_factory
+[filter:adminrequest]
+controller = nova.api.ec2.admin.AdminController
+paste.filter_factory = nova.api.ec2:Requestify.factory
[filter:authorizer]
-paste.filter_factory = nova.api.ec2:authorizer_factory
+paste.filter_factory = nova.api.ec2:Authorizer.factory
[app:ec2executor]
-paste.app_factory = nova.api.ec2:executor_factory
+paste.app_factory = nova.api.ec2:Executor.factory
-[app:ec2versions]
-paste.app_factory = nova.api.ec2:versions_factory
+[app:ec2ver]
+paste.app_factory = nova.api.ec2:Versions.factory
-[app:ec2metadata]
-paste.app_factory = nova.api.ec2.metadatarequesthandler:metadata_factory
+[app:ec2md]
+paste.app_factory = nova.api.ec2.metadatarequesthandler:MetadataRequestHandler.factory
#############
# Openstack #
#############
-[composite:openstack]
+[composite:osapi]
use = egg:Paste#urlmap
/: osversions
/v1.0: openstackapi
[pipeline:openstackapi]
-pipeline = auth ratelimit osapi
+pipeline = faultwrap auth ratelimit osapiapp
+
+[filter:faultwrap]
+paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:auth]
-paste.filter_factory = nova.api.openstack.auth:auth_factory
+paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory
[filter:ratelimit]
-paste.filter_factory = nova.api.openstack.ratelimiting:ratelimit_factory
+paste.filter_factory = nova.api.openstack.ratelimiting:RateLimitingMiddleware.factory
+
+[app:osapiapp]
+paste.app_factory = nova.api.openstack:APIRouter.factory
-[app:osapi]
-paste.app_factory = nova.api.openstack:router_factory
+[pipeline:osversions]
+pipeline = faultwrap osversionapp
-[app:osversions]
-paste.app_factory = nova.api.openstack:versions_factory
+[app:osversionapp]
+paste.app_factory = nova.api.openstack:Versions.factory
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 803470570..0fedbbfad 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -15,96 +15,5 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Root WSGI middleware for all API controllers.
-**Related Flags**
-
-:osapi_subdomain: subdomain running the OpenStack API (default: api)
-:ec2api_subdomain: subdomain running the EC2 API (default: ec2)
-
-"""
-
-import routes
-import webob.dec
-
-from nova import flags
-from nova import wsgi
-from nova.api import ec2
-from nova.api import openstack
-from nova.api.ec2 import metadatarequesthandler
-
-
-flags.DEFINE_string('osapi_subdomain', 'api',
- 'subdomain running the OpenStack API')
-flags.DEFINE_string('ec2api_subdomain', 'ec2',
- 'subdomain running the EC2 API')
-
-FLAGS = flags.FLAGS
-
-
-class API(wsgi.Router):
- """Routes top-level requests to the appropriate controller."""
-
- def __init__(self, default_api):
- osapi_subdomain = {'sub_domain': [FLAGS.osapi_subdomain]}
- ec2api_subdomain = {'sub_domain': [FLAGS.ec2api_subdomain]}
- if default_api == 'os':
- osapi_subdomain = {}
- elif default_api == 'ec2':
- ec2api_subdomain = {}
- mapper = routes.Mapper()
- mapper.sub_domains = True
-
- mapper.connect("/", controller=self.osapi_versions,
- conditions=osapi_subdomain)
- mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(),
- conditions=osapi_subdomain)
-
- mapper.connect("/", controller=self.ec2api_versions,
- conditions=ec2api_subdomain)
- mapper.connect("/services/{path_info:.*}", controller=ec2.API(),
- conditions=ec2api_subdomain)
- mrh = metadatarequesthandler.MetadataRequestHandler()
- for s in ['/latest',
- '/2009-04-04',
- '/2008-09-01',
- '/2008-02-01',
- '/2007-12-15',
- '/2007-10-10',
- '/2007-08-29',
- '/2007-03-01',
- '/2007-01-19',
- '/1.0']:
- mapper.connect('%s/{path_info:.*}' % s, controller=mrh,
- conditions=ec2api_subdomain)
-
- super(API, self).__init__(mapper)
-
- @webob.dec.wsgify
- def osapi_versions(self, req):
- """Respond to a request for all OpenStack API versions."""
- response = {
- "versions": [
- dict(status="CURRENT", id="v1.0")]}
- metadata = {
- "application/xml": {
- "attributes": dict(version=["status", "id"])}}
- return wsgi.Serializer(req.environ, metadata).to_content_type(response)
-
- @webob.dec.wsgify
- def ec2api_versions(self, req):
- """Respond to a request for all EC2 versions."""
- # available api versions
- versions = [
- '1.0',
- '2007-01-19',
- '2007-03-01',
- '2007-08-29',
- '2007-10-10',
- '2007-12-15',
- '2008-02-01',
- '2008-09-01',
- '2009-04-04',
- ]
- return ''.join('%s\n' % v for v in versions)
+"""No-op __init__ for directory full of api goodies."""
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 2fa1f636c..238cb0f38 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -30,10 +30,9 @@ from nova import context
from nova import exception
from nova import flags
from nova import log as logging
+from nova import utils
from nova import wsgi
from nova.api.ec2 import apirequest
-from nova.api.ec2 import admin
-from nova.api.ec2 import cloud
from nova.auth import manager
@@ -42,8 +41,6 @@ LOG = logging.getLogger("nova.api")
flags.DEFINE_boolean('use_forwarded_for', False,
'Treat X-Forwarded-For as the canonical remote address. '
'Only enable this if you have a sanitizing proxy.')
-flags.DEFINE_boolean('use_lockout', False,
- 'Whether or not to use lockout middleware.')
flags.DEFINE_integer('lockout_attempts', 5,
'Number of failed auths before lockout.')
flags.DEFINE_integer('lockout_minutes', 15,
@@ -54,13 +51,8 @@ flags.DEFINE_list('lockout_memcached_servers', None,
'Memcached servers or None for in process cache.')
-class API(wsgi.Middleware):
- """Routing for all EC2 API requests."""
-
- def __init__(self):
- self.application = Authenticate(Router(Authorizer(Executor())))
- if FLAGS.use_lockout:
- self.application = Lockout(self.application)
+class RequestLogging(wsgi.Middleware):
+ """Access-Log akin logging for all EC2 API requests."""
@webob.dec.wsgify
def __call__(self, req):
@@ -192,26 +184,14 @@ class Authenticate(wsgi.Middleware):
return self.application
-class Router(wsgi.Middleware):
-
- """Add ec2.'controller', .'action', and .'action_args' to WSGI environ."""
+class Requestify(wsgi.Middleware):
- def __init__(self, application):
- super(Router, self).__init__(application)
- self.map = routes.Mapper()
- self.map.connect("/{controller_name}/")
- self.controllers = dict(Cloud=cloud.CloudController(),
- Admin=admin.AdminController())
+ def __init__(self, app, controller):
+ super(Requestify, self).__init__(app)
+ self.controller = utils.import_class(controller)()
@webob.dec.wsgify
def __call__(self, req):
- # Obtain the appropriate controller and action for this request.
- try:
- match = self.map.match(req.path_info)
- controller_name = match['controller_name']
- controller = self.controllers[controller_name]
- except:
- raise webob.exc.HTTPNotFound()
non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod',
'SignatureVersion', 'Version', 'Timestamp']
args = dict(req.params)
@@ -229,8 +209,8 @@ class Router(wsgi.Middleware):
LOG.debug(_('arg: %s\t\tval: %s'), key, value)
# Success!
- req.environ['ec2.controller'] = controller
- req.environ['ec2.action'] = action
+ api_request = apirequest.APIRequest(self.controller, action, args)
+ req.environ['ec2.request'] = api_request
req.environ['ec2.action_args'] = args
return self.application
@@ -291,16 +271,14 @@ class Authorizer(wsgi.Middleware):
@webob.dec.wsgify
def __call__(self, req):
context = req.environ['ec2.context']
- controller_name = req.environ['ec2.controller'].__class__.__name__
- action = req.environ['ec2.action']
- allowed_roles = self.action_roles[controller_name].get(action,
- ['none'])
+ controller = req.environ['ec2.request'].controller.__class__.__name__
+ action = req.environ['ec2.request'].action
+ allowed_roles = self.action_roles[controller].get(action, ['none'])
if self._matches_any_role(context, allowed_roles):
return self.application
else:
LOG.audit(_("Unauthorized request for controller=%s "
- "and action=%s"), controller_name, action,
- context=context)
+ "and action=%s"), controller, action, context=context)
raise webob.exc.HTTPUnauthorized()
def _matches_any_role(self, context, roles):
@@ -327,14 +305,10 @@ class Executor(wsgi.Application):
@webob.dec.wsgify
def __call__(self, req):
context = req.environ['ec2.context']
- controller = req.environ['ec2.controller']
- action = req.environ['ec2.action']
- args = req.environ['ec2.action_args']
-
- api_request = apirequest.APIRequest(controller, action)
+ api_request = req.environ['ec2.request']
result = None
try:
- result = api_request.send(context, **args)
+ result = api_request.invoke(context)
except exception.NotFound as ex:
LOG.info(_('NotFound raised: %s'), str(ex), context=context)
return self._error(req, context, type(ex).__name__, str(ex))
@@ -391,29 +365,3 @@ class Versions(wsgi.Application):
'2009-04-04',
]
return ''.join('%s\n' % v for v in versions)
-
-
-def authenticate_factory(global_args, **local_args):
- def authenticator(app):
- return Authenticate(app)
- return authenticator
-
-
-def router_factory(global_args, **local_args):
- def router(app):
- return Router(app)
- return router
-
-
-def authorizer_factory(global_args, **local_args):
- def authorizer(app):
- return Authorizer(app)
- return authorizer
-
-
-def executor_factory(global_args, **local_args):
- return Executor()
-
-
-def versions_factory(global_args, **local_args):
- return Versions()
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index d0b417db1..78576470a 100644
--- a/nova/api/ec2/apirequest.py
+++ b/nova/api/ec2/apirequest.py
@@ -83,11 +83,12 @@ def _try_convert(value):
class APIRequest(object):
- def __init__(self, controller, action):
+ def __init__(self, controller, action, args):
self.controller = controller
self.action = action
+ self.args = args
- def send(self, context, **kwargs):
+ def invoke(self, context):
try:
method = getattr(self.controller,
_camelcase_to_underscore(self.action))
@@ -100,7 +101,7 @@ class APIRequest(object):
raise Exception(_error)
args = {}
- for key, value in kwargs.items():
+ for key, value in self.args.items():
parts = key.split(".")
key = _camelcase_to_underscore(parts[0])
if isinstance(value, str) or isinstance(value, unicode):
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index c807bc13c..c94540793 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -254,15 +254,15 @@ class CloudController(object):
name, _sep, host = region.partition('=')
endpoint = '%s://%s:%s%s' % (FLAGS.ec2_prefix,
host,
- FLAGS.cc_port,
+ FLAGS.ec2_port,
FLAGS.ec2_suffix)
regions.append({'regionName': name,
'regionEndpoint': endpoint})
else:
regions = [{'regionName': 'nova',
'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
- FLAGS.cc_host,
- FLAGS.cc_port,
+ FLAGS.ec2_host,
+ FLAGS.ec2_port,
FLAGS.ec2_suffix)}]
return {'regionInfo': regions}
diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py
index 848f0b034..6fb441656 100644
--- a/nova/api/ec2/metadatarequesthandler.py
+++ b/nova/api/ec2/metadatarequesthandler.py
@@ -23,6 +23,7 @@ import webob.exc
from nova import log as logging
from nova import flags
+from nova import wsgi
from nova.api.ec2 import cloud
@@ -30,7 +31,7 @@ LOG = logging.getLogger('nova.api.ec2.metadata')
FLAGS = flags.FLAGS
-class MetadataRequestHandler(object):
+class MetadataRequestHandler(wsgi.Application):
"""Serve metadata from the EC2 API."""
def print_data(self, data):
@@ -78,7 +79,3 @@ class MetadataRequestHandler(object):
if data is None:
raise webob.exc.HTTPNotFound()
return self.print_data(data)
-
-
-def metadata_factory(global_args, **local_args):
- return MetadataRequestHandler()
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index f96e2af91..f2caac483 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -23,11 +23,9 @@ WSGI middleware for OpenStack API controllers.
import routes
import webob.dec
import webob.exc
-import webob
from nova import flags
from nova import log as logging
-from nova import utils
from nova import wsgi
from nova.api.openstack import faults
from nova.api.openstack import backup_schedules
@@ -40,32 +38,16 @@ from nova.api.openstack import shared_ip_groups
LOG = logging.getLogger('nova.api.openstack')
FLAGS = flags.FLAGS
-flags.DEFINE_string('os_api_auth',
- 'nova.api.openstack.auth.AuthMiddleware',
- 'The auth mechanism to use for the OpenStack API implemenation')
-
-flags.DEFINE_string('os_api_ratelimiting',
- 'nova.api.openstack.ratelimiting.RateLimitingMiddleware',
- 'Default ratelimiting implementation for the Openstack API')
-
flags.DEFINE_string('os_krm_mapping_file',
'krm_mapping.json',
'Location of OpenStack Flavor/OS:EC2 Kernel/Ramdisk/Machine JSON file.')
-
flags.DEFINE_bool('allow_admin_api',
False,
'When True, this API service will accept admin operations.')
-class API(wsgi.Middleware):
- """WSGI entry point for all OpenStack API requests."""
-
- def __init__(self):
- auth_middleware = utils.import_class(FLAGS.os_api_auth)
- ratelimiting_middleware = \
- utils.import_class(FLAGS.os_api_ratelimiting)
- app = auth_middleware(ratelimiting_middleware(APIRouter()))
- super(API, self).__init__(app)
+class FaultWrapper(wsgi.Middleware):
+ """Calls down the middleware stack, making exceptions into faults."""
@webob.dec.wsgify
def __call__(self, req):
@@ -83,6 +65,11 @@ class APIRouter(wsgi.Router):
and method.
"""
+ @classmethod
+ def factory(cls, global_config, **local_config):
+ """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one"""
+ return cls()
+
def __init__(self):
mapper = routes.Mapper()
@@ -132,11 +119,3 @@ class Versions(wsgi.Application):
"application/xml": {
"attributes": dict(version=["status", "id"])}}
return wsgi.Serializer(req.environ, metadata).to_content_type(response)
-
-
-def router_factory(global_cof, **local_conf):
- return APIRouter()
-
-
-def versions_factory(global_conf, **local_conf):
- return Versions()
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 00e817c8d..1dfdd5318 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -134,9 +134,3 @@ class AuthMiddleware(wsgi.Middleware):
token = self.db.auth_create_token(ctxt, token_dict)
return token, user
return None, None
-
-
-def auth_factory(global_conf, **local_conf):
- def auth(app):
- return AuthMiddleware(app)
- return auth
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index a5f55a489..9d56bc508 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -78,7 +78,14 @@ def _translate_status(item):
'decrypting': 'preparing',
'untarring': 'saving',
'available': 'active'}
- item['status'] = status_mapping[item['status']]
+ try:
+ item['status'] = status_mapping[item['status']]
+ except KeyError:
+ # TODO(sirp): Performing translation of status (if necessary) here for
+ # now. Perhaps this should really be done in EC2 API and
+ # S3ImageService
+ pass
+
return item
@@ -92,9 +99,11 @@ def _filter_keys(item, keys):
def _convert_image_id_to_hash(image):
- image_id = abs(hash(image['imageId']))
- image['imageId'] = image_id
- image['id'] = image_id
+ if 'imageId' in image:
+ # Convert EC2-style ID (i-blah) to Rackspace-style (int)
+ image_id = abs(hash(image['imageId']))
+ image['imageId'] = image_id
+ image['id'] = image_id
class Controller(wsgi.Controller):
@@ -147,7 +156,11 @@ class Controller(wsgi.Controller):
env = self._deserialize(req.body, req)
instance_id = env["image"]["serverId"]
name = env["image"]["name"]
- return compute.API().snapshot(context, instance_id, name)
+
+ image_meta = compute.API().snapshot(
+ context, instance_id, name)
+
+ return dict(image=image_meta)
def update(self, req, id):
# Users may not modify public images, and that's all that
diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py
index 81b83142f..cbb4b897e 100644
--- a/nova/api/openstack/ratelimiting/__init__.py
+++ b/nova/api/openstack/ratelimiting/__init__.py
@@ -219,9 +219,3 @@ class WSGIAppProxy(object):
# No delay
return None
return float(resp.getheader('X-Wait-Seconds'))
-
-
-def ratelimit_factory(global_conf, **local_conf):
- def rl(app):
- return RateLimitingMiddleware(app)
- return rl
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index de18a3838..1652e24e1 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -682,7 +682,7 @@ class AuthManager(object):
region, _sep, region_host = item.partition("=")
regions[region] = region_host
else:
- regions = {'nova': FLAGS.cc_host}
+ regions = {'nova': FLAGS.ec2_host}
for region, host in regions.iteritems():
rc = self.__generate_rc(user,
pid,
@@ -727,28 +727,28 @@ class AuthManager(object):
def __generate_rc(user, pid, use_dmz=True, host=None):
"""Generate rc file for user"""
if use_dmz:
- cc_host = FLAGS.cc_dmz
+ ec2_host = FLAGS.ec2_dmz_host
else:
- cc_host = FLAGS.cc_host
+ ec2_host = FLAGS.ec2_host
# NOTE(vish): Always use the dmz since it is used from inside the
# instance
s3_host = FLAGS.s3_dmz
if host:
s3_host = host
- cc_host = host
+ ec2_host = host
rc = open(FLAGS.credentials_template).read()
rc = rc % {'access': user.access,
'project': pid,
'secret': user.secret,
- 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
- cc_host,
- FLAGS.cc_port,
- FLAGS.ec2_suffix),
+ 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
+ ec2_host,
+ FLAGS.ec2_port,
+ FLAGS.ec2_path),
's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port),
- 'os': '%s://%s:%s%s' % (FLAGS.os_prefix,
- cc_host,
+ 'os': '%s://%s:%s%s' % (FLAGS.osapi_scheme,
+ ec2_host,
FLAGS.osapi_port,
- FLAGS.os_suffix),
+ FLAGS.osapi_path),
'user': user.name,
'nova': FLAGS.ca_file,
'cert': FLAGS.credential_cert_file,
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
index 8aefd341f..dc6f55af2 100644
--- a/nova/cloudpipe/pipelib.py
+++ b/nova/cloudpipe/pipelib.py
@@ -68,8 +68,8 @@ class CloudPipe(object):
shellfile = open(FLAGS.boot_script_template, "r")
s = string.Template(shellfile.read())
shellfile.close()
- boot_script = s.substitute(cc_dmz=FLAGS.cc_dmz,
- cc_port=FLAGS.cc_port,
+ boot_script = s.substitute(cc_dmz=FLAGS.ec2_dmz_host,
+ cc_port=FLAGS.ec2_port,
dmz_net=FLAGS.dmz_net,
dmz_mask=FLAGS.dmz_mask,
num_vpn=FLAGS.cnt_vpn_clients)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index cc85ec691..a6b99c1cb 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -335,27 +335,55 @@ class API(base.Base):
project_id)
return self.db.instance_get_all(context)
- def _cast_compute_message(self, method, context, instance_id, host=None):
- """Generic handler for RPC casts to compute."""
+ def _cast_compute_message(self, method, context, instance_id, host=None,
+ params=None):
+ """Generic handler for RPC casts to compute.
+
+ :param params: Optional dictionary of arguments to be passed to the
+ compute worker
+
+ :retval None
+ """
+ if not params:
+ params = {}
if not host:
instance = self.get(context, instance_id)
host = instance['host']
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
- kwargs = {'method': method, 'args': {'instance_id': instance_id}}
+ params['instance_id'] = instance_id
+ kwargs = {'method': method, 'args': params}
rpc.cast(context, queue, kwargs)
- def _call_compute_message(self, method, context, instance_id, host=None):
- """Generic handler for RPC calls to compute."""
+ def _call_compute_message(self, method, context, instance_id, host=None,
+ params=None):
+ """Generic handler for RPC calls to compute.
+
+ :param params: Optional dictionary of arguments to be passed to the
+ compute worker
+
+ :retval: Result returned by compute worker
+ """
+ if not params:
+ params = {}
if not host:
instance = self.get(context, instance_id)
host = instance["host"]
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
- kwargs = {"method": method, "args": {"instance_id": instance_id}}
+ params['instance_id'] = instance_id
+ kwargs = {'method': method, 'args': params}
return rpc.call(context, queue, kwargs)
def snapshot(self, context, instance_id, name):
- """Snapshot the given instance."""
- self._cast_compute_message('snapshot_instance', context, instance_id)
+ """Snapshot the given instance.
+
+ :retval: A dict containing image metadata
+ """
+ data = {'name': name, 'is_public': False}
+ image_meta = self.image_service.create(context, data)
+ params = {'image_id': image_meta['id']}
+ self._cast_compute_message('snapshot_instance', context, instance_id,
+ params=params)
+ return image_meta
def reboot(self, context, instance_id):
"""Reboot the given instance."""
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index ccbdc7b4f..644f601af 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -326,7 +326,7 @@ class ComputeManager(manager.Manager):
self._update_state(context, instance_id)
@exception.wrap_exception
- def snapshot_instance(self, context, instance_id, name):
+ def snapshot_instance(self, context, instance_id, image_id):
"""Snapshot an instance on this server."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
@@ -343,7 +343,7 @@ class ComputeManager(manager.Manager):
'instance: %s (state: %s excepted: %s)'),
instance_id, instance_ref['state'], power_state.RUNNING)
- self.driver.snapshot(instance_ref, name)
+ self.driver.snapshot(instance_ref, image_id)
@exception.wrap_exception
@checks_instance_lock
diff --git a/nova/flags.py b/nova/flags.py
index ef66c3f3a..81e2e36f9 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -254,14 +254,15 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval')
DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts')
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
-DEFINE_string('ec2_prefix', 'http', 'prefix for ec2')
-DEFINE_string('os_prefix', 'http', 'prefix for openstack')
-DEFINE_string('cc_host', '$my_ip', 'ip of api server')
-DEFINE_string('cc_dmz', '$my_ip', 'internal ip of api server')
-DEFINE_integer('cc_port', 8773, 'cloud controller port')
+DEFINE_string('ec2_host', '$my_ip', 'ip of api server')
+DEFINE_string('ec2_dmz_host', '$my_ip', 'internal ip of api server')
+DEFINE_integer('ec2_port', 8773, 'cloud controller port')
+DEFINE_string('ec2_scheme', 'http', 'prefix for ec2')
+DEFINE_string('ec2_path', '/services/Cloud', 'suffix for ec2')
+DEFINE_string('osapi_host', '$my_ip', 'ip of api server')
+DEFINE_string('osapi_scheme', 'http', 'prefix for openstack')
DEFINE_integer('osapi_port', 8774, 'OpenStack API port')
-DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2')
-DEFINE_string('os_suffix', '/v1.0/', 'suffix for openstack')
+DEFINE_string('osapi_path', '/v1.0/', 'suffix for openstack')
DEFINE_string('default_project', 'openstack', 'default project for openstack')
DEFINE_string('default_image', 'ami-11111',
diff --git a/nova/image/glance.py b/nova/image/glance.py
index a3a2f4308..593c4bce6 100644
--- a/nova/image/glance.py
+++ b/nova/image/glance.py
@@ -14,9 +14,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-
"""Implementation of an image service that uses Glance as the backend"""
+from __future__ import absolute_import
import httplib
import json
import urlparse
@@ -24,171 +24,40 @@ import urlparse
from nova import exception
from nova import flags
from nova import log as logging
+from nova import utils
from nova.image import service
LOG = logging.getLogger('nova.image.glance')
FLAGS = flags.FLAGS
-flags.DEFINE_string('glance_teller_address', 'http://127.0.0.1',
- 'IP address or URL where Glance\'s Teller service resides')
-flags.DEFINE_string('glance_teller_port', '9191',
- 'Port for Glance\'s Teller service')
-flags.DEFINE_string('glance_parallax_address', 'http://127.0.0.1',
- 'IP address or URL where Glance\'s Parallax service '
- 'resides')
-flags.DEFINE_string('glance_parallax_port', '9292',
- 'Port for Glance\'s Parallax service')
-
-
-class TellerClient(object):
-
- def __init__(self):
- self.address = FLAGS.glance_teller_address
- self.port = FLAGS.glance_teller_port
- url = urlparse.urlparse(self.address)
- self.netloc = url.netloc
- self.connection_type = {'http': httplib.HTTPConnection,
- 'https': httplib.HTTPSConnection}[url.scheme]
-
-class ParallaxClient(object):
-
- def __init__(self):
- self.address = FLAGS.glance_parallax_address
- self.port = FLAGS.glance_parallax_port
- url = urlparse.urlparse(self.address)
- self.netloc = url.netloc
- self.connection_type = {'http': httplib.HTTPConnection,
- 'https': httplib.HTTPSConnection}[url.scheme]
-
- def get_image_index(self):
- """
- Returns a list of image id/name mappings from Parallax
- """
- try:
- c = self.connection_type(self.netloc, self.port)
- c.request("GET", "images")
- res = c.getresponse()
- if res.status == 200:
- # Parallax returns a JSONified dict(images=image_list)
- data = json.loads(res.read())['images']
- return data
- else:
- LOG.warn(_("Parallax returned HTTP error %d from "
- "request for /images"), res.status_int)
- return []
- finally:
- c.close()
-
- def get_image_details(self):
- """
- Returns a list of detailed image data mappings from Parallax
- """
- try:
- c = self.connection_type(self.netloc, self.port)
- c.request("GET", "images/detail")
- res = c.getresponse()
- if res.status == 200:
- # Parallax returns a JSONified dict(images=image_list)
- data = json.loads(res.read())['images']
- return data
- else:
- LOG.warn(_("Parallax returned HTTP error %d from "
- "request for /images/detail"), res.status_int)
- return []
- finally:
- c.close()
-
- def get_image_metadata(self, image_id):
- """
- Returns a mapping of image metadata from Parallax
- """
- try:
- c = self.connection_type(self.netloc, self.port)
- c.request("GET", "images/%s" % image_id)
- res = c.getresponse()
- if res.status == 200:
- # Parallax returns a JSONified dict(image=image_info)
- data = json.loads(res.read())['image']
- return data
- else:
- # TODO(jaypipes): log the error?
- return None
- finally:
- c.close()
-
- def add_image_metadata(self, image_metadata):
- """
- Tells parallax about an image's metadata
- """
- try:
- c = self.connection_type(self.netloc, self.port)
- body = json.dumps(image_metadata)
- c.request("POST", "images", body)
- res = c.getresponse()
- if res.status == 200:
- # Parallax returns a JSONified dict(image=image_info)
- data = json.loads(res.read())['image']
- return data['id']
- else:
- # TODO(jaypipes): log the error?
- return None
- finally:
- c.close()
-
- def update_image_metadata(self, image_id, image_metadata):
- """
- Updates Parallax's information about an image
- """
- try:
- c = self.connection_type(self.netloc, self.port)
- body = json.dumps(image_metadata)
- c.request("PUT", "images/%s" % image_id, body)
- res = c.getresponse()
- return res.status == 200
- finally:
- c.close()
-
- def delete_image_metadata(self, image_id):
- """
- Deletes Parallax's information about an image
- """
- try:
- c = self.connection_type(self.netloc, self.port)
- c.request("DELETE", "images/%s" % image_id)
- res = c.getresponse()
- return res.status == 200
- finally:
- c.close()
+GlanceClient = utils.import_class('glance.client.Client')
class GlanceImageService(service.BaseImageService):
"""Provides storage and retrieval of disk image objects within Glance."""
def __init__(self):
- self.teller = TellerClient()
- self.parallax = ParallaxClient()
+ self.client = GlanceClient(FLAGS.glance_host, FLAGS.glance_port)
def index(self, context):
"""
- Calls out to Parallax for a list of images available
+ Calls out to Glance for a list of images available
"""
- images = self.parallax.get_image_index()
- return images
+ return self.client.get_images()
def detail(self, context):
"""
- Calls out to Parallax for a list of detailed image information
+ Calls out to Glance for a list of detailed image information
"""
- images = self.parallax.get_image_details()
- return images
+ return self.client.get_images_detailed()
def show(self, context, id):
"""
Returns a dict containing image data for the given opaque image id.
"""
- image = self.parallax.get_image_metadata(id)
+ image = self.client.get_image_meta(id)
if image:
return image
raise exception.NotFound
@@ -200,7 +69,7 @@ class GlanceImageService(service.BaseImageService):
:raises AlreadyExists if the image already exist.
"""
- return self.parallax.add_image_metadata(data)
+ return self.client.add_image(image_meta=data)
def update(self, context, image_id, data):
"""Replace the contents of the given image with the new data.
@@ -208,7 +77,7 @@ class GlanceImageService(service.BaseImageService):
:raises NotFound if the image does not exist.
"""
- self.parallax.update_image_metadata(image_id, data)
+ return self.client.update_image(image_id, data)
def delete(self, context, image_id):
"""
@@ -217,7 +86,7 @@ class GlanceImageService(service.BaseImageService):
:raises NotFound if the image does not exist.
"""
- self.parallax.delete_image_metadata(image_id)
+ return self.client.delete_image(image_id)
def delete_all(self):
"""
diff --git a/nova/log.py b/nova/log.py
index c1428c051..4997d3f28 100644
--- a/nova/log.py
+++ b/nova/log.py
@@ -116,6 +116,8 @@ def basicConfig():
handler.setFormatter(_formatter)
if FLAGS.verbose:
logging.root.setLevel(logging.DEBUG)
+ else:
+ logging.root.setLevel(logging.INFO)
if FLAGS.use_syslog:
syslog = SysLogHandler(address='/dev/log')
syslog.setFormatter(_formatter)
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 891e9bc1d..d29e17603 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -61,7 +61,7 @@ def metadata_forward():
"""Create forwarding rule for metadata"""
_confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 "
"-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT "
- "--to-destination %s:%s" % (FLAGS.cc_dmz, FLAGS.cc_port))
+ "--to-destination %s:%s" % (FLAGS.ec2_dmz_host, FLAGS.ec2_port))
def init_host():
diff --git a/nova/tests/api/fakes.py b/nova/tests/api/fakes.py
deleted file mode 100644
index 0aedcaff0..000000000
--- a/nova/tests/api/fakes.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 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 webob.dec
-from nova import wsgi
-
-
-class APIStub(object):
- """Class to verify request and mark it was called."""
- @webob.dec.wsgify
- def __call__(self, req):
- return req.path_info
diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py
index 9e183bd0d..14eaaa62c 100644
--- a/nova/tests/api/openstack/__init__.py
+++ b/nova/tests/api/openstack/__init__.py
@@ -15,23 +15,28 @@
# License for the specific language governing permissions and limitations
# under the License.
+import webob.dec
import unittest
from nova import context
from nova import flags
from nova.api.openstack.ratelimiting import RateLimitingMiddleware
from nova.api.openstack.common import limited
-from nova.tests.api.fakes import APIStub
-from nova import utils
+from nova.tests.api.openstack import fakes
from webob import Request
FLAGS = flags.FLAGS
+@webob.dec.wsgify
+def simple_wsgi(req):
+ return ""
+
+
class RateLimitingMiddlewareTest(unittest.TestCase):
def test_get_action_name(self):
- middleware = RateLimitingMiddleware(APIStub())
+ middleware = RateLimitingMiddleware(simple_wsgi)
def verify(method, url, action_name):
req = Request.blank(url)
@@ -61,19 +66,19 @@ class RateLimitingMiddlewareTest(unittest.TestCase):
self.assertTrue('Retry-After' in resp.headers)
def test_single_action(self):
- middleware = RateLimitingMiddleware(APIStub())
+ middleware = RateLimitingMiddleware(simple_wsgi)
self.exhaust(middleware, 'DELETE', '/servers/4', 'usr1', 100)
self.exhaust(middleware, 'DELETE', '/servers/4', 'usr2', 100)
def test_POST_servers_action_implies_POST_action(self):
- middleware = RateLimitingMiddleware(APIStub())
+ middleware = RateLimitingMiddleware(simple_wsgi)
self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10)
self.exhaust(middleware, 'POST', '/images/4', 'usr2', 10)
self.assertTrue(set(middleware.limiter._levels) == \
set(['usr1:POST', 'usr1:POST servers', 'usr2:POST']))
def test_POST_servers_action_correctly_ratelimited(self):
- middleware = RateLimitingMiddleware(APIStub())
+ middleware = RateLimitingMiddleware(simple_wsgi)
# Use up all of our "POST" allowance for the minute, 5 times
for i in range(5):
self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10)
@@ -83,9 +88,9 @@ class RateLimitingMiddlewareTest(unittest.TestCase):
self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 0)
def test_proxy_ctor_works(self):
- middleware = RateLimitingMiddleware(APIStub())
+ middleware = RateLimitingMiddleware(simple_wsgi)
self.assertEqual(middleware.limiter.__class__.__name__, "Limiter")
- middleware = RateLimitingMiddleware(APIStub(), service_host='foobar')
+ middleware = RateLimitingMiddleware(simple_wsgi, service_host='foobar')
self.assertEqual(middleware.limiter.__class__.__name__, "WSGIAppProxy")
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 194304e79..fb282f1c9 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -22,6 +22,9 @@ import string
import webob
import webob.dec
+from paste import urlmap
+
+from glance import client as glance_client
from nova import auth
from nova import context
@@ -29,6 +32,7 @@ from nova import exception as exc
from nova import flags
from nova import utils
import nova.api.openstack.auth
+from nova.api import openstack
from nova.api.openstack import auth
from nova.api.openstack import ratelimiting
from nova.image import glance
@@ -69,6 +73,17 @@ def fake_wsgi(self, req):
return self.application
+def wsgi_app(inner_application=None):
+ if not inner_application:
+ inner_application = openstack.APIRouter()
+ mapper = urlmap.URLMap()
+ api = openstack.FaultWrapper(auth.AuthMiddleware(
+ ratelimiting.RateLimitingMiddleware(inner_application)))
+ mapper['/v1.0'] = api
+ mapper['/'] = openstack.FaultWrapper(openstack.Versions())
+ return mapper
+
+
def stub_out_key_pair_funcs(stubs):
def key_pair(context, user_id):
return [dict(name='key', public_key='public_key')]
@@ -116,64 +131,60 @@ def stub_out_compute_api_snapshot(stubs):
stubs.Set(nova.compute.API, 'snapshot', snapshot)
-def stub_out_glance(stubs, initial_fixtures=[]):
+def stub_out_glance(stubs, initial_fixtures=None):
- class FakeParallaxClient:
+ class FakeGlanceClient:
def __init__(self, initial_fixtures):
- self.fixtures = initial_fixtures
+ self.fixtures = initial_fixtures or []
- def fake_get_image_index(self):
+ def fake_get_images(self):
return [dict(id=f['id'], name=f['name'])
for f in self.fixtures]
- def fake_get_image_details(self):
+ def fake_get_images_detailed(self):
return self.fixtures
- def fake_get_image_metadata(self, image_id):
+ def fake_get_image_meta(self, image_id):
for f in self.fixtures:
if f['id'] == image_id:
return f
return None
- def fake_add_image_metadata(self, image_data):
+ def fake_add_image(self, image_meta):
id = ''.join(random.choice(string.letters) for _ in range(20))
- image_data['id'] = id
- self.fixtures.append(image_data)
+ image_meta['id'] = id
+ self.fixtures.append(image_meta)
return id
- def fake_update_image_metadata(self, image_id, image_data):
- f = self.fake_get_image_metadata(image_id)
+ def fake_update_image(self, image_id, image_meta):
+ f = self.fake_get_image_meta(image_id)
if not f:
raise exc.NotFound
- f.update(image_data)
+ f.update(image_meta)
- def fake_delete_image_metadata(self, image_id):
- f = self.fake_get_image_metadata(image_id)
+ def fake_delete_image(self, image_id):
+ f = self.fake_get_image_meta(image_id)
if not f:
raise exc.NotFound
self.fixtures.remove(f)
- def fake_delete_all(self):
- self.fixtures = []
-
- fake_parallax_client = FakeParallaxClient(initial_fixtures)
- stubs.Set(nova.image.glance.ParallaxClient, 'get_image_index',
- fake_parallax_client.fake_get_image_index)
- stubs.Set(nova.image.glance.ParallaxClient, 'get_image_details',
- fake_parallax_client.fake_get_image_details)
- stubs.Set(nova.image.glance.ParallaxClient, 'get_image_metadata',
- fake_parallax_client.fake_get_image_metadata)
- stubs.Set(nova.image.glance.ParallaxClient, 'add_image_metadata',
- fake_parallax_client.fake_add_image_metadata)
- stubs.Set(nova.image.glance.ParallaxClient, 'update_image_metadata',
- fake_parallax_client.fake_update_image_metadata)
- stubs.Set(nova.image.glance.ParallaxClient, 'delete_image_metadata',
- fake_parallax_client.fake_delete_image_metadata)
- stubs.Set(nova.image.glance.GlanceImageService, 'delete_all',
- fake_parallax_client.fake_delete_all)
+ ##def fake_delete_all(self):
+ ## self.fixtures = []
+
+ GlanceClient = glance_client.Client
+ fake = FakeGlanceClient(initial_fixtures)
+
+ stubs.Set(GlanceClient, 'get_images', fake.fake_get_images)
+ stubs.Set(GlanceClient, 'get_images_detailed',
+ fake.fake_get_images_detailed)
+ stubs.Set(GlanceClient, 'get_image_meta', fake.fake_get_image_meta)
+ stubs.Set(GlanceClient, 'add_image', fake.fake_add_image)
+ stubs.Set(GlanceClient, 'update_image', fake.fake_update_image)
+ stubs.Set(GlanceClient, 'delete_image', fake.fake_delete_image)
+ #stubs.Set(GlanceClient, 'delete_all', fake.fake_delete_all)
class FakeToken(object):
diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py
index 1b2e1654d..73120c31d 100644
--- a/nova/tests/api/openstack/test_adminapi.py
+++ b/nova/tests/api/openstack/test_adminapi.py
@@ -19,15 +19,19 @@ import unittest
import stubout
import webob
+from paste import urlmap
-import nova.api
from nova import flags
+from nova.api import openstack
+from nova.api.openstack import ratelimiting
+from nova.api.openstack import auth
from nova.tests.api.openstack import fakes
FLAGS = flags.FLAGS
class AdminAPITest(unittest.TestCase):
+
def setUp(self):
self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {}
@@ -45,7 +49,7 @@ class AdminAPITest(unittest.TestCase):
FLAGS.allow_admin_api = True
# We should still be able to access public operations.
req = webob.Request.blank('/v1.0/flavors')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
# TODO: Confirm admin operations are available.
@@ -53,7 +57,7 @@ class AdminAPITest(unittest.TestCase):
FLAGS.allow_admin_api = False
# We should still be able to access public operations.
req = webob.Request.blank('/v1.0/flavors')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
# TODO: Confirm admin operations are unavailable.
diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py
index d8b202e21..db0fe1060 100644
--- a/nova/tests/api/openstack/test_api.py
+++ b/nova/tests/api/openstack/test_api.py
@@ -19,14 +19,18 @@ import unittest
import webob.exc
import webob.dec
-import nova.api.openstack
-from nova.api.openstack import API
-from nova.api.openstack import faults
from webob import Request
+from nova.api import openstack
+from nova.api.openstack import faults
+
class APITest(unittest.TestCase):
+ def _wsgi_app(self, inner_app):
+ # simpler version of the app than fakes.wsgi_app
+ return openstack.FaultWrapper(inner_app)
+
def test_exceptions_are_converted_to_faults(self):
@webob.dec.wsgify
@@ -46,29 +50,32 @@ class APITest(unittest.TestCase):
exc = webob.exc.HTTPNotFound(explanation='Raised a webob.exc')
return faults.Fault(exc)
- api = API()
-
- api.application = succeed
+ #api.application = succeed
+ api = self._wsgi_app(succeed)
resp = Request.blank('/').get_response(api)
self.assertFalse('computeFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 200, resp.body)
- api.application = raise_webob_exc
+ #api.application = raise_webob_exc
+ api = self._wsgi_app(raise_webob_exc)
resp = Request.blank('/').get_response(api)
self.assertFalse('computeFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 404, resp.body)
- api.application = raise_api_fault
+ #api.application = raise_api_fault
+ api = self._wsgi_app(raise_api_fault)
resp = Request.blank('/').get_response(api)
self.assertTrue('itemNotFound' in resp.body, resp.body)
self.assertEqual(resp.status_int, 404, resp.body)
- api.application = fail
+ #api.application = fail
+ api = self._wsgi_app(fail)
resp = Request.blank('/').get_response(api)
self.assertTrue('{"computeFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 500, resp.body)
- api.application = fail
+ #api.application = fail
+ api = self._wsgi_app(fail)
resp = Request.blank('/.xml').get_response(api)
self.assertTrue('<computeFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 500, resp.body)
diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py
index 489a1dfbf..0dd65d321 100644
--- a/nova/tests/api/openstack/test_auth.py
+++ b/nova/tests/api/openstack/test_auth.py
@@ -53,7 +53,7 @@ class Test(unittest.TestCase):
req = webob.Request.blank('/v1.0/')
req.headers['X-Auth-User'] = 'herp'
req.headers['X-Auth-Key'] = 'derp'
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '204 No Content')
self.assertEqual(len(result.headers['X-Auth-Token']), 40)
self.assertEqual(result.headers['X-CDN-Management-Url'],
@@ -67,7 +67,7 @@ class Test(unittest.TestCase):
req = webob.Request.blank('/v1.0/', {'HTTP_HOST': 'foo'})
req.headers['X-Auth-User'] = 'herp'
req.headers['X-Auth-Key'] = 'derp'
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '204 No Content')
self.assertEqual(len(result.headers['X-Auth-Token']), 40)
self.assertEqual(result.headers['X-Server-Management-Url'],
@@ -81,7 +81,7 @@ class Test(unittest.TestCase):
fakes.FakeRouter)
req = webob.Request.blank('/v1.0/fake')
req.headers['X-Auth-Token'] = token
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '200 OK')
self.assertEqual(result.headers['X-Test-Success'], 'True')
@@ -105,7 +105,7 @@ class Test(unittest.TestCase):
req = webob.Request.blank('/v1.0/')
req.headers['X-Auth-Token'] = 'bacon'
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '401 Unauthorized')
self.assertEqual(self.destroy_called, True)
@@ -113,18 +113,18 @@ class Test(unittest.TestCase):
req = webob.Request.blank('/v1.0/')
req.headers['X-Auth-User'] = 'herp'
req.headers['X-Auth-Key'] = 'derp'
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '401 Unauthorized')
def test_no_user(self):
req = webob.Request.blank('/v1.0/')
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '401 Unauthorized')
def test_bad_token(self):
req = webob.Request.blank('/v1.0/')
req.headers['X-Auth-Token'] = 'baconbaconbacon'
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '401 Unauthorized')
@@ -149,7 +149,7 @@ class TestLimiter(unittest.TestCase):
req = webob.Request.blank('/v1.0/')
req.headers['X-Auth-User'] = 'herp'
req.headers['X-Auth-Key'] = 'derp'
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(len(result.headers['X-Auth-Token']), 40)
token = result.headers['X-Auth-Token']
@@ -158,7 +158,7 @@ class TestLimiter(unittest.TestCase):
req = webob.Request.blank('/v1.0/fake')
req.method = 'POST'
req.headers['X-Auth-Token'] = token
- result = req.get_response(nova.api.API('os'))
+ result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '200 OK')
self.assertEqual(result.headers['X-Test-Success'], 'True')
diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py
index 41018afdf..1bdaea161 100644
--- a/nova/tests/api/openstack/test_flavors.py
+++ b/nova/tests/api/openstack/test_flavors.py
@@ -39,7 +39,7 @@ class FlavorsTest(unittest.TestCase):
def test_get_flavor_list(self):
req = webob.Request.blank('/v1.0/flavors')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
def test_get_flavor_by_id(self):
pass
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 00ca739a5..5d9ddefbe 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -210,7 +210,7 @@ class ImageControllerWithGlanceServiceTest(unittest.TestCase):
def test_get_image_index(self):
req = webob.Request.blank('/v1.0/images')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
fixture_index = [dict(id=f['id'], name=f['name']) for f
@@ -222,7 +222,7 @@ class ImageControllerWithGlanceServiceTest(unittest.TestCase):
def test_get_image_details(self):
req = webob.Request.blank('/v1.0/images/detail')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
def _is_equivalent_subset(x, y):
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 0396daf98..29883e7c8 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -100,14 +100,14 @@ class ServersTest(unittest.TestCase):
def test_get_server_by_id(self):
req = webob.Request.blank('/v1.0/servers/1')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res_dict['server']['id'], '1')
self.assertEqual(res_dict['server']['name'], 'server1')
def test_get_server_list(self):
req = webob.Request.blank('/v1.0/servers')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
i = 0
@@ -160,14 +160,14 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
def test_update_no_body(self):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 422)
def test_update_bad_params(self):
@@ -186,7 +186,7 @@ class ServersTest(unittest.TestCase):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
req.body = self.body
- req.get_response(nova.api.API('os'))
+ req.get_response(fakes.wsgi_app())
def test_update_server(self):
inst_dict = dict(name='server_test', adminPass='bacon')
@@ -202,28 +202,28 @@ class ServersTest(unittest.TestCase):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
req.body = self.body
- req.get_response(nova.api.API('os'))
+ req.get_response(fakes.wsgi_app())
def test_create_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
req.method = 'POST'
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status, '404 Not Found')
def test_delete_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
req.method = 'DELETE'
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status, '404 Not Found')
def test_get_server_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status, '404 Not Found')
def test_get_all_server_details(self):
req = webob.Request.blank('/v1.0/servers/detail')
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
i = 0
@@ -242,7 +242,7 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_unpause(self):
@@ -254,7 +254,7 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_suspend(self):
@@ -266,7 +266,7 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_resume(self):
@@ -278,19 +278,19 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_diagnostics(self):
req = webob.Request.blank("/v1.0/servers/1/diagnostics")
req.method = "GET"
- res = req.get_response(nova.api.API("os"))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
def test_server_actions(self):
req = webob.Request.blank("/v1.0/servers/1/actions")
req.method = "GET"
- res = req.get_response(nova.api.API("os"))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
def test_server_reboot(self):
@@ -301,7 +301,7 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
def test_server_rebuild(self):
body = dict(server=dict(
@@ -311,7 +311,7 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
def test_server_resize(self):
body = dict(server=dict(
@@ -321,7 +321,7 @@ class ServersTest(unittest.TestCase):
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
def test_delete_server_instance(self):
req = webob.Request.blank('/v1.0/servers/1')
@@ -335,7 +335,7 @@ class ServersTest(unittest.TestCase):
self.stubs.Set(nova.db.api, 'instance_destroy',
instance_destroy_mock)
- res = req.get_response(nova.api.API('os'))
+ res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status, '202 Accepted')
self.assertEqual(self.server_delete_called, True)
diff --git a/nova/tests/api/test.py b/nova/tests/api/test.py
deleted file mode 100644
index 9caa8c9d0..000000000
--- a/nova/tests/api/test.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 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.
-
-"""
-Test for the root WSGI middleware for all API controllers.
-"""
-
-import unittest
-
-import stubout
-import webob
-import webob.dec
-
-import nova.exception
-from nova import api
-from nova.tests.api.fakes import APIStub
-
-
-class Test(unittest.TestCase):
-
- def setUp(self):
- self.stubs = stubout.StubOutForTesting()
-
- def tearDown(self):
- self.stubs.UnsetAll()
-
- def _request(self, url, subdomain, **kwargs):
- environ_keys = {'HTTP_HOST': '%s.example.com' % subdomain}
- environ_keys.update(kwargs)
- req = webob.Request.blank(url, environ_keys)
- return req.get_response(api.API('ec2'))
-
- def test_openstack(self):
- self.stubs.Set(api.openstack, 'API', APIStub)
- result = self._request('/v1.0/cloud', 'api')
- self.assertEqual(result.body, "/cloud")
-
- def test_ec2(self):
- self.stubs.Set(api.ec2, 'API', APIStub)
- result = self._request('/services/cloud', 'ec2')
- self.assertEqual(result.body, "/cloud")
-
- def test_not_found(self):
- self.stubs.Set(api.ec2, 'API', APIStub)
- self.stubs.Set(api.openstack, 'API', APIStub)
- result = self._request('/test/cloud', 'ec2')
- self.assertNotEqual(result.body, "/cloud")
-
- def test_query_api_versions(self):
- result = self._request('/', 'api')
- self.assertTrue('CURRENT' in result.body)
-
- def test_metadata(self):
- def go(url):
- result = self._request(url, 'ec2', REMOTE_ADDR='128.192.151.2')
- # Each should get to the ORM layer and fail to find the IP
- self.assertRaises(nova.exception.NotFound, go, '/latest/')
- self.assertRaises(nova.exception.NotFound, go, '/2009-04-04/')
- self.assertRaises(nova.exception.NotFound, go, '/1.0/')
-
- def test_ec2_root(self):
- result = self._request('/', 'ec2')
- self.assertTrue('2007-12-15\n' in result.body)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/nova/tests/test_access.py b/nova/tests/test_access.py
index 0929903cf..e170ccee6 100644
--- a/nova/tests/test_access.py
+++ b/nova/tests/test_access.py
@@ -20,21 +20,31 @@ import unittest
import webob
from nova import context
-from nova import exception
from nova import flags
from nova import test
from nova.api import ec2
from nova.auth import manager
-
FLAGS = flags.FLAGS
-class Context(object):
+class FakeControllerClass(object):
pass
+class FakeApiRequest(object):
+ def __init__(self, action):
+ self.controller = FakeControllerClass()
+ self.action = action
+
+
class AccessTestCase(test.TestCase):
+ def _env_for(self, ctxt, action):
+ env = {}
+ env['ec2.context'] = ctxt
+ env['ec2.request'] = FakeApiRequest(action)
+ return env
+
def setUp(self):
super(AccessTestCase, self).setUp()
um = manager.AuthManager()
@@ -64,7 +74,7 @@ class AccessTestCase(test.TestCase):
return ['']
self.mw = ec2.Authorizer(noopWSGIApp)
- self.mw.action_roles = {'str': {
+ self.mw.action_roles = {'FakeControllerClass': {
'_allow_all': ['all'],
'_allow_none': [],
'_allow_project_manager': ['projectmanager'],
@@ -84,9 +94,7 @@ class AccessTestCase(test.TestCase):
def response_status(self, user, methodName):
ctxt = context.RequestContext(user, self.project)
- environ = {'ec2.context': ctxt,
- 'ec2.controller': 'some string',
- 'ec2.action': methodName}
+ environ = self._env_for(ctxt, methodName)
req = webob.Request.blank('/', environ)
resp = req.get_response(self.mw)
return resp.status_int
diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py
index 17789c25c..66a16b0cb 100644
--- a/nova/tests/test_api.py
+++ b/nova/tests/test_api.py
@@ -26,9 +26,8 @@ import StringIO
import webob
from nova import context
-from nova import flags
from nova import test
-from nova import api
+from nova.api import ec2
from nova.api.ec2 import cloud
from nova.api.ec2 import apirequest
from nova.auth import manager
@@ -100,12 +99,10 @@ class ApiEc2TestCase(test.TestCase):
"""Unit test for the cloud controller on an EC2 API"""
def setUp(self):
super(ApiEc2TestCase, self).setUp()
-
self.manager = manager.AuthManager()
-
self.host = '127.0.0.1'
-
- self.app = api.API('ec2')
+ self.app = ec2.Authenticate(ec2.Requestify(ec2.Executor(),
+ 'nova.api.ec2.cloud.CloudController'))
def expect_http(self, host=None, is_secure=False):
"""Returns a new EC2 connection"""
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index f50f5755f..09f6ee94a 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -32,8 +32,9 @@ from nova import utils
from nova.auth import manager
-FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.tests.compute')
+FLAGS = flags.FLAGS
+flags.DECLARE('stub_network', 'nova.compute.manager')
class ComputeTestCase(test.TestCase):
diff --git a/nova/utils.py b/nova/utils.py
index fdbe81c0c..6d3ddd092 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -334,6 +334,20 @@ class LazyPluggable(object):
return getattr(backend, key)
+class LoopingCallDone(Exception):
+ """The poll-function passed to LoopingCall can raise this exception to
+ break out of the loop normally. This is somewhat analogous to
+ StopIteration.
+
+ An optional return-value can be included as the argument to the exception;
+ this return-value will be returned by LoopingCall.wait()
+ """
+
+ def __init__(self, retvalue=True):
+ """:param retvalue: Value that LoopingCall.wait() should return"""
+ self.retvalue = retvalue
+
+
class LoopingCall(object):
def __init__(self, f=None, *args, **kw):
self.args = args
@@ -352,12 +366,15 @@ class LoopingCall(object):
while self._running:
self.f(*self.args, **self.kw)
greenthread.sleep(interval)
+ except LoopingCallDone, e:
+ self.stop()
+ done.send(e.retvalue)
except Exception:
logging.exception('in looping call')
done.send_exception(*sys.exc_info())
return
-
- done.send(True)
+ else:
+ done.send(True)
self.done = done
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 534227339..7d1f76b32 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -312,7 +312,7 @@ class LibvirtConnection(object):
virt_dom.detachDevice(xml)
@exception.wrap_exception
- def snapshot(self, instance, name):
+ def snapshot(self, instance, image_id):
""" Create snapshot from a running VM instance """
raise NotImplementedError(
_("Instance snapshotting is not supported for libvirt"
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index a91c8ea27..eb0393d2a 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -236,14 +236,15 @@ class VMHelper(HelperBase):
return template_vm_ref, [template_vdi_uuid, parent_uuid]
@classmethod
- def upload_image(cls, session, instance_id, vdi_uuids, image_name):
+ def upload_image(cls, session, instance_id, vdi_uuids, image_id):
""" Requests that the Glance plugin bundle the specified VDIs and
push them into Glance using the specified human-friendly name.
"""
- LOG.debug(_("Asking xapi to upload %s as '%s'"), vdi_uuids, image_name)
+ logging.debug(_("Asking xapi to upload %s as ID %s"),
+ vdi_uuids, image_id)
params = {'vdi_uuids': vdi_uuids,
- 'image_name': image_name,
+ 'image_id': image_id,
'glance_host': FLAGS.glance_host,
'glance_port': FLAGS.glance_port}
@@ -424,9 +425,16 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref,
* parent_vhd
snapshot
"""
- #TODO(sirp): we need to timeout this req after a while
+ max_attempts = FLAGS.xenapi_vhd_coalesce_max_attempts
+ attempts = {'counter': 0}
def _poll_vhds():
+ attempts['counter'] += 1
+ if attempts['counter'] > max_attempts:
+ msg = (_("VHD coalesce attempts exceeded (%d > %d), giving up...")
+ % (attempts['counter'], max_attempts))
+ raise exception.Error(msg)
+
scan_sr(session, instance_id, sr_ref)
parent_uuid = get_vhd_parent_uuid(session, vdi_ref)
if original_parent_uuid and (parent_uuid != original_parent_uuid):
@@ -434,13 +442,12 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref,
"waiting for coalesce..."), parent_uuid,
original_parent_uuid)
else:
- done.send(parent_uuid)
+ # Breakout of the loop (normally) and return the parent_uuid
+ raise utils.LoopingCallDone(parent_uuid)
- done = event.Event()
loop = utils.LoopingCall(_poll_vhds)
loop.start(FLAGS.xenapi_vhd_coalesce_poll_interval, now=True)
- parent_uuid = done.wait()
- loop.stop()
+ parent_uuid = loop.wait()
return parent_uuid
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 6e359ef82..5e414bab4 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -161,11 +161,11 @@ class VMOps(object):
raise Exception(_('Instance not present %s') % instance_name)
return vm
- def snapshot(self, instance, name):
+ def snapshot(self, instance, image_id):
""" Create snapshot from a running VM instance
:param instance: instance to be snapshotted
- :param name: name/label to be given to the snapshot
+ :param image_id: id of image to upload to
Steps involved in a XenServer snapshot:
@@ -201,7 +201,7 @@ class VMOps(object):
try:
# call plugin to ship snapshot off to glance
VMHelper.upload_image(
- self._session, instance.id, template_vdi_uuids, name)
+ self._session, instance.id, template_vdi_uuids, image_id)
finally:
self._destroy(instance, template_vm_ref, shutdown=False)
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index a2eb2d8d7..c10f73fe7 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -93,6 +93,10 @@ flags.DEFINE_float('xenapi_vhd_coalesce_poll_interval',
5.0,
'The interval used for polling of coalescing vhds.'
' Used only if connection_type=xenapi.')
+flags.DEFINE_integer('xenapi_vhd_coalesce_max_attempts',
+ 5,
+ 'Max number of times to poll for VHD to coalesce.'
+ ' Used only if connection_type=xenapi.')
flags.DEFINE_string('target_host',
None,
'iSCSI Target Host')
@@ -141,9 +145,9 @@ class XenAPIConnection(object):
"""Create VM instance"""
self._vmops.spawn(instance)
- def snapshot(self, instance, name):
+ def snapshot(self, instance, image_id):
""" Create snapshot from a running VM instance """
- self._vmops.snapshot(instance, name)
+ self._vmops.snapshot(instance, image_id)
def reboot(self, instance):
"""Reboot VM instance"""
diff --git a/nova/wsgi.py b/nova/wsgi.py
index 00b351253..4f5307d80 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -21,6 +21,7 @@
Utility methods for working with WSGI servers
"""
+import os
import sys
from xml.dom import minidom
@@ -33,10 +34,16 @@ import webob
import webob.dec
import webob.exc
+from paste import deploy
+
+from nova import flags
from nova import log as logging
from nova import utils
+FLAGS = flags.FLAGS
+
+
class WritableLogger(object):
"""A thin wrapper that responds to `write` and logs."""
@@ -76,10 +83,33 @@ class Server(object):
class Application(object):
-# TODO(gundlach): I think we should toss this class, now that it has no
-# purpose.
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
+ @classmethod
+ def factory(cls, global_config, **local_config):
+ """Used for paste app factories in paste.deploy config fles.
+
+ Any local configuration (that is, values under the [app:APPNAME]
+ section of the paste config) will be passed into the `__init__` method
+ as kwargs.
+
+ A hypothetical configuration would look like:
+
+ [app:wadl]
+ latest_version = 1.3
+ paste.app_factory = nova.api.fancy_api:Wadl.factory
+
+ which would result in a call to the `Wadl` class as
+
+ import nova.api.fancy_api
+ fancy_api.Wadl(latest_version='1.3')
+
+ You could of course re-implement the `factory` method in subclasses,
+ but using the kwarg passing it shouldn't be necessary.
+
+ """
+ return cls(**local_config)
+
def __call__(self, environ, start_response):
r"""Subclasses will probably want to implement __call__ like this:
@@ -125,6 +155,33 @@ class Middleware(Application):
behavior.
"""
+ @classmethod
+ def factory(cls, global_config, **local_config):
+ """Used for paste app factories in paste.deploy config fles.
+
+ Any local configuration (that is, values under the [filter:APPNAME]
+ section of the paste config) will be passed into the `__init__` method
+ as kwargs.
+
+ A hypothetical configuration would look like:
+
+ [filter:analytics]
+ redis_host = 127.0.0.1
+ paste.filter_factory = nova.api.analytics:Analytics.factory
+
+ which would result in a call to the `Analytics` class as
+
+ import nova.api.analytics
+ analytics.Analytics(app_from_paste, redis_host='127.0.0.1')
+
+ You could of course re-implement the `factory` method in subclasses,
+ but using the kwarg passing it shouldn't be necessary.
+
+ """
+ def _factory(app):
+ return cls(app, **local_config)
+ return _factory
+
def __init__(self, application):
self.application = application
@@ -403,3 +460,64 @@ class Serializer(object):
node = doc.createTextNode(str(data))
result.appendChild(node)
return result
+
+
+def paste_config_file(basename):
+ """Find the best location in the system for a paste config file.
+
+ Search Order
+ ------------
+
+ The search for a paste config file honors `FLAGS.state_path`, which in a
+ version checked out from bzr will be the `nova` directory in the top level
+ of the checkout, and in an installation for a package for your distribution
+ will likely point to someplace like /etc/nova.
+
+ This method tries to load places likely to be used in development or
+ experimentation before falling back to the system-wide configuration
+ in `/etc/nova/`.
+
+ * Current working directory
+ * the `etc` directory under state_path, because when working on a checkout
+ from bzr this will point to the default
+ * top level of FLAGS.state_path, for distributions
+ * /etc/nova, which may not be diffrerent from state_path on your distro
+
+ """
+
+ configfiles = [basename,
+ os.path.join(FLAGS.state_path, 'etc', basename),
+ os.path.join(FLAGS.state_path, basename),
+ '/etc/nova/%s' % basename]
+ for configfile in configfiles:
+ if os.path.exists(configfile):
+ return configfile
+
+
+def load_paste_configuration(filename, appname):
+ """Returns a paste configuration dict, or None."""
+ filename = os.path.abspath(filename)
+ config = None
+ try:
+ config = deploy.appconfig("config:%s" % filename, name=appname)
+ except LookupError:
+ pass
+ return config
+
+
+def load_paste_app(filename, appname):
+ """Builds a wsgi app from a paste config, None if app not configured."""
+ filename = os.path.abspath(filename)
+ app = None
+ try:
+ app = deploy.loadapp("config:%s" % filename, name=appname)
+ except LookupError:
+ pass
+ return app
+
+
+def paste_config_to_flags(config, mixins):
+ for k, v in mixins.iteritems():
+ value = config.get(k, v)
+ converted_value = FLAGS[k].parser.Parse(value)
+ setattr(FLAGS, k, converted_value)
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
index 5e648b970..cc34a1ec9 100644
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
@@ -45,24 +45,24 @@ FILE_SR_PATH = '/var/run/sr-mount'
def put_vdis(session, args):
params = pickle.loads(exists(args, 'params'))
vdi_uuids = params["vdi_uuids"]
- image_name = params["image_name"]
+ image_id = params["image_id"]
glance_host = params["glance_host"]
glance_port = params["glance_port"]
sr_path = get_sr_path(session)
#FIXME(sirp): writing to a temp file until Glance supports chunked-PUTs
- tmp_file = "%s.tar.gz" % os.path.join('/tmp', image_name)
+ tmp_file = "%s.tar.gz" % os.path.join('/tmp', str(image_id))
tar_cmd = ['tar', '-zcf', tmp_file, '--directory=%s' % sr_path]
paths = [ "%s.vhd" % vdi_uuid for vdi_uuid in vdi_uuids ]
tar_cmd.extend(paths)
logging.debug("Bundling image with cmd: %s", tar_cmd)
subprocess.call(tar_cmd)
logging.debug("Writing to test file %s", tmp_file)
- put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port)
+ put_bundle_in_glance(tmp_file, image_id, glance_host, glance_port)
return "" # FIXME(sirp): return anything useful here?
-def put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port):
+def put_bundle_in_glance(tmp_file, image_id, glance_host, glance_port):
size = os.path.getsize(tmp_file)
basename = os.path.basename(tmp_file)
@@ -72,7 +72,6 @@ def put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port):
'x-image-meta-store': 'file',
'x-image-meta-is_public': 'True',
'x-image-meta-type': 'raw',
- 'x-image-meta-name': image_name,
'x-image-meta-size': size,
'content-length': size,
'content-type': 'application/octet-stream',
@@ -80,7 +79,7 @@ def put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port):
conn = httplib.HTTPConnection(glance_host, glance_port)
#NOTE(sirp): httplib under python2.4 won't accept a file-like object
# to request
- conn.putrequest('POST', '/images')
+ conn.putrequest('PUT', '/images/%s' % image_id)
for header, value in headers.iteritems():
conn.putheader(header, value)