summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/nova-api51
-rwxr-xr-xbin/nova-api-paste109
-rwxr-xr-xbin/nova-combined29
-rwxr-xr-xbin/nova-direct-api61
-rwxr-xr-xbin/stack145
-rw-r--r--etc/nova-api.conf74
-rw-r--r--nova/api/__init__.py93
-rw-r--r--nova/api/direct.py232
-rw-r--r--nova/api/ec2/__init__.py82
-rw-r--r--nova/api/ec2/apirequest.py7
-rw-r--r--nova/api/ec2/cloud.py69
-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.py95
-rw-r--r--nova/compute/manager.py4
-rw-r--r--nova/db/sqlalchemy/api.py7
-rw-r--r--nova/db/sqlalchemy/models.py10
-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/network/manager.py10
-rw-r--r--nova/test.py100
-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.py13
-rw-r--r--nova/tests/test_cloud.py8
-rw-r--r--nova/tests/test_compute.py15
-rw-r--r--nova/tests/test_console.py3
-rw-r--r--nova/tests/test_direct.py103
-rw-r--r--nova/tests/test_log.py8
-rw-r--r--nova/tests/test_middleware.py2
-rw-r--r--nova/tests/test_twistd.py2
-rw-r--r--nova/utils.py55
-rw-r--r--nova/virt/libvirt_conn.py5
-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.py158
-rw-r--r--plugins/xenserver/xenapi/etc/xapi.d/plugins/glance11
54 files changed, 1247 insertions, 954 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/bin/nova-direct-api b/bin/nova-direct-api
new file mode 100755
index 000000000..e7dd14fb2
--- /dev/null
+++ b/bin/nova-direct-api
@@ -0,0 +1,61 @@
+#!/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 Direct API."""
+
+import gettext
+import os
+import sys
+
+# 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 utils
+from nova import wsgi
+from nova.api import direct
+from nova.compute import api as compute_api
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_integer('direct_port', 8001, 'Direct API port')
+flags.DEFINE_string('direct_host', '0.0.0.0', 'Direct API host')
+
+if __name__ == '__main__':
+ utils.default_flagfile()
+ FLAGS(sys.argv)
+
+ direct.register_service('compute', compute_api.ComputeAPI())
+ direct.register_service('reflect', direct.Reflection())
+ router = direct.Router()
+ with_json = direct.JsonParamsMiddleware(router)
+ with_req = direct.PostParamsMiddleware(with_json)
+ with_auth = direct.DelegatedAuthMiddleware(with_req)
+
+ server = wsgi.Server()
+ server.start(with_auth, FLAGS.direct_port, host=FLAGS.direct_host)
+ server.wait()
diff --git a/bin/stack b/bin/stack
new file mode 100755
index 000000000..7a6ce5960
--- /dev/null
+++ b/bin/stack
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+# 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.
+
+"""CLI for the Direct API."""
+
+import eventlet
+eventlet.monkey_patch()
+
+import os
+import pprint
+import sys
+import textwrap
+import urllib
+import urllib2
+
+# 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)
+
+import gflags
+from nova import utils
+
+
+FLAGS = gflags.FLAGS
+gflags.DEFINE_string('host', '127.0.0.1', 'Direct API host')
+gflags.DEFINE_integer('port', 8001, 'Direct API host')
+gflags.DEFINE_string('user', 'user1', 'Direct API username')
+gflags.DEFINE_string('project', 'proj1', 'Direct API project')
+
+
+USAGE = """usage: stack [options] <controller> <method> [arg1=value arg2=value]
+
+ `stack help` should output the list of available controllers
+ `stack <controller>` should output the available methods for that controller
+ `stack help <controller>` should do the same
+ `stack help <controller> <method>` should output info for a method
+"""
+
+
+def format_help(d):
+ """Format help text, keys are labels and values are descriptions."""
+ indent = max([len(k) for k in d])
+ out = []
+ for k, v in d.iteritems():
+ t = textwrap.TextWrapper(initial_indent=' %s ' % k.ljust(indent),
+ subsequent_indent=' ' * (indent + 6))
+ out.extend(t.wrap(v))
+ return out
+
+
+def help_all():
+ rv = do_request('reflect', 'get_controllers')
+ out = format_help(rv)
+ return (USAGE + str(FLAGS.MainModuleHelp()) +
+ '\n\nAvailable controllers:\n' +
+ '\n'.join(out) + '\n')
+
+
+def help_controller(controller):
+ rv = do_request('reflect', 'get_methods')
+ methods = dict([(k.split('/')[2], v) for k, v in rv.iteritems()
+ if k.startswith('/%s' % controller)])
+ return ('Available methods for %s:\n' % controller +
+ '\n'.join(format_help(methods)))
+
+
+def help_method(controller, method):
+ rv = do_request('reflect',
+ 'get_method_info',
+ {'method': '/%s/%s' % (controller, method)})
+
+ sig = '%s(%s):' % (method, ', '.join(['='.join(x) for x in rv['args']]))
+ out = textwrap.wrap(sig, subsequent_indent=' ' * len('%s(' % method))
+ out.append('\n' + rv['doc'])
+ return '\n'.join(out)
+
+
+def do_request(controller, method, params=None):
+ if params:
+ data = urllib.urlencode(params)
+ else:
+ data = None
+
+ url = 'http://%s:%s/%s/%s' % (FLAGS.host, FLAGS.port, controller, method)
+ headers = {'X-OpenStack-User': FLAGS.user,
+ 'X-OpenStack-Project': FLAGS.project}
+
+ req = urllib2.Request(url, data, headers)
+ resp = urllib2.urlopen(req)
+ return utils.loads(resp.read())
+
+
+if __name__ == '__main__':
+ args = FLAGS(sys.argv)
+
+ cmd = args.pop(0)
+ if not args:
+ print help_all()
+ sys.exit()
+
+ first = args.pop(0)
+ if first == 'help':
+ action = help_all
+ params = []
+ if args:
+ params.append(args.pop(0))
+ action = help_controller
+ if args:
+ params.append(args.pop(0))
+ action = help_method
+ print action(*params)
+ sys.exit(0)
+
+ controller = first
+ if not args:
+ print help_controller(controller)
+ sys.exit()
+
+ method = args.pop(0)
+ params = {}
+ for x in args:
+ key, value = x.split('=', 1)
+ params[key] = value
+
+ pprint.pprint(do_request(controller, method, params))
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/direct.py b/nova/api/direct.py
new file mode 100644
index 000000000..81b3ae202
--- /dev/null
+++ b/nova/api/direct.py
@@ -0,0 +1,232 @@
+# 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.
+
+"""Public HTTP interface that allows services to self-register.
+
+The general flow of a request is:
+ - Request is parsed into WSGI bits.
+ - Some middleware checks authentication.
+ - Routing takes place based on the URL to find a controller.
+ (/controller/method)
+ - Parameters are parsed from the request and passed to a method on the
+ controller as keyword arguments.
+ - Optionally 'json' is decoded to provide all the parameters.
+ - Actual work is done and a result is returned.
+ - That result is turned into json and returned.
+
+"""
+
+import inspect
+import urllib
+
+import routes
+import webob
+
+from nova import context
+from nova import flags
+from nova import utils
+from nova import wsgi
+
+
+ROUTES = {}
+
+
+def register_service(path, handle):
+ ROUTES[path] = handle
+
+
+class Router(wsgi.Router):
+ def __init__(self, mapper=None):
+ if mapper is None:
+ mapper = routes.Mapper()
+
+ self._load_registered_routes(mapper)
+ super(Router, self).__init__(mapper=mapper)
+
+ def _load_registered_routes(self, mapper):
+ for route in ROUTES:
+ mapper.connect('/%s/{action}' % route,
+ controller=ServiceWrapper(ROUTES[route]))
+
+
+class DelegatedAuthMiddleware(wsgi.Middleware):
+ def process_request(self, request):
+ os_user = request.headers['X-OpenStack-User']
+ os_project = request.headers['X-OpenStack-Project']
+ context_ref = context.RequestContext(user=os_user, project=os_project)
+ request.environ['openstack.context'] = context_ref
+
+
+class JsonParamsMiddleware(wsgi.Middleware):
+ def process_request(self, request):
+ if 'json' not in request.params:
+ return
+
+ params_json = request.params['json']
+ params_parsed = utils.loads(params_json)
+ params = {}
+ for k, v in params_parsed.iteritems():
+ if k in ('self', 'context'):
+ continue
+ if k.startswith('_'):
+ continue
+ params[k] = v
+
+ request.environ['openstack.params'] = params
+
+
+class PostParamsMiddleware(wsgi.Middleware):
+ def process_request(self, request):
+ params_parsed = request.params
+ params = {}
+ for k, v in params_parsed.iteritems():
+ if k in ('self', 'context'):
+ continue
+ if k.startswith('_'):
+ continue
+ params[k] = v
+
+ request.environ['openstack.params'] = params
+
+
+class Reflection(object):
+ """Reflection methods to list available methods."""
+ def __init__(self):
+ self._methods = {}
+ self._controllers = {}
+
+ def _gather_methods(self):
+ methods = {}
+ controllers = {}
+ for route, handler in ROUTES.iteritems():
+ controllers[route] = handler.__doc__.split('\n')[0]
+ for k in dir(handler):
+ if k.startswith('_'):
+ continue
+ f = getattr(handler, k)
+ if not callable(f):
+ continue
+
+ # bunch of ugly formatting stuff
+ argspec = inspect.getargspec(f)
+ args = [x for x in argspec[0]
+ if x != 'self' and x != 'context']
+ defaults = argspec[3] and argspec[3] or []
+ args_r = list(reversed(args))
+ defaults_r = list(reversed(defaults))
+
+ args_out = []
+ while args_r:
+ if defaults_r:
+ args_out.append((args_r.pop(0),
+ repr(defaults_r.pop(0))))
+ else:
+ args_out.append((str(args_r.pop(0)),))
+
+ # if the method accepts keywords
+ if argspec[2]:
+ args_out.insert(0, ('**%s' % argspec[2],))
+
+ methods['/%s/%s' % (route, k)] = {
+ 'short_doc': f.__doc__.split('\n')[0],
+ 'doc': f.__doc__,
+ 'name': k,
+ 'args': list(reversed(args_out))}
+
+ self._methods = methods
+ self._controllers = controllers
+
+ def get_controllers(self, context):
+ """List available controllers."""
+ if not self._controllers:
+ self._gather_methods()
+
+ return self._controllers
+
+ def get_methods(self, context):
+ """List available methods."""
+ if not self._methods:
+ self._gather_methods()
+
+ method_list = self._methods.keys()
+ method_list.sort()
+ methods = {}
+ for k in method_list:
+ methods[k] = self._methods[k]['short_doc']
+ return methods
+
+ def get_method_info(self, context, method):
+ """Get detailed information about a method."""
+ if not self._methods:
+ self._gather_methods()
+ return self._methods[method]
+
+
+class ServiceWrapper(wsgi.Controller):
+ def __init__(self, service_handle):
+ self.service_handle = service_handle
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ arg_dict = req.environ['wsgiorg.routing_args'][1]
+ action = arg_dict['action']
+ del arg_dict['action']
+
+ context = req.environ['openstack.context']
+ # allow middleware up the stack to override the params
+ params = {}
+ if 'openstack.params' in req.environ:
+ params = req.environ['openstack.params']
+
+ # TODO(termie): do some basic normalization on methods
+ method = getattr(self.service_handle, action)
+
+ result = method(context, **params)
+ if type(result) is dict or type(result) is list:
+ return self._serialize(result, req)
+ else:
+ return result
+
+
+class Proxy(object):
+ """Pretend a Direct API endpoint is an object."""
+ def __init__(self, app, prefix=None):
+ self.app = app
+ self.prefix = prefix
+
+ def __do_request(self, path, context, **kwargs):
+ req = webob.Request.blank(path)
+ req.method = 'POST'
+ req.body = urllib.urlencode({'json': utils.dumps(kwargs)})
+ req.environ['openstack.context'] = context
+ resp = req.get_response(self.app)
+ try:
+ return utils.loads(resp.body)
+ except Exception:
+ return resp.body
+
+ def __getattr__(self, key):
+ if self.prefix is None:
+ return self.__class__(self.app, prefix=key)
+
+ def _wrapper(context, **kwargs):
+ return self.__do_request('/%s/%s' % (self.prefix, key),
+ context,
+ **kwargs)
+ _wrapper.func_name = key
+ return _wrapper
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 630aaeaf2..57d41ed67 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -37,7 +37,6 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import network
-from nova import rpc
from nova import utils
from nova import volume
from nova.compute import instance_types
@@ -93,8 +92,11 @@ class CloudController(object):
self.image_service = utils.import_object(FLAGS.image_service)
self.network_api = network.API()
self.volume_api = volume.API()
- self.compute_api = compute.API(self.image_service, self.network_api,
- self.volume_api)
+ self.compute_api = compute.API(
+ network_api=self.network_api,
+ image_service=self.image_service,
+ volume_api=self.volume_api,
+ hostname_factory=id_to_ec2_id)
self.setup()
def __str__(self):
@@ -130,15 +132,6 @@ class CloudController(object):
result[key] = [line]
return result
- def _trigger_refresh_security_group(self, context, security_group):
- nodes = set([instance['host'] for instance in security_group.instances
- if instance['host'] is not None])
- for node in nodes:
- rpc.cast(context,
- '%s.%s' % (FLAGS.compute_topic, node),
- {"method": "refresh_security_group",
- "args": {"security_group_id": security_group.id}})
-
def _get_availability_zone_by_host(self, context, host):
services = db.service_get_all_by_host(context, host)
if len(services) > 0:
@@ -261,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}
@@ -522,13 +515,8 @@ class CloudController(object):
# instance_id is passed in as a list of instances
ec2_id = instance_id[0]
instance_id = ec2_id_to_id(ec2_id)
- instance_ref = self.compute_api.get(context, instance_id)
- output = rpc.call(context,
- '%s.%s' % (FLAGS.compute_topic,
- instance_ref['host']),
- {"method": "get_console_output",
- "args": {"instance_id": instance_ref['id']}})
-
+ output = self.compute_api.get_console_output(
+ context, instance_id=instance_id)
now = datetime.datetime.utcnow()
return {"InstanceId": ec2_id,
"Timestamp": now,
@@ -596,7 +584,7 @@ class CloudController(object):
def delete_volume(self, context, volume_id, **kwargs):
volume_id = ec2_id_to_id(volume_id)
- self.volume_api.delete(context, volume_id)
+ self.volume_api.delete(context, volume_id=volume_id)
return True
def update_volume(self, context, volume_id, **kwargs):
@@ -613,9 +601,12 @@ class CloudController(object):
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
volume_id = ec2_id_to_id(volume_id)
instance_id = ec2_id_to_id(instance_id)
- LOG.audit(_("Attach volume %s to instacne %s at %s"), volume_id,
+ LOG.audit(_("Attach volume %s to instance %s at %s"), volume_id,
instance_id, device, context=context)
- self.compute_api.attach_volume(context, instance_id, volume_id, device)
+ self.compute_api.attach_volume(context,
+ instance_id=instance_id,
+ volume_id=volume_id,
+ device=device)
volume = self.volume_api.get(context, volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
@@ -628,7 +619,7 @@ class CloudController(object):
volume_id = ec2_id_to_id(volume_id)
LOG.audit(_("Detach volume %s"), volume_id, context=context)
volume = self.volume_api.get(context, volume_id)
- instance = self.compute_api.detach_volume(context, volume_id)
+ instance = self.compute_api.detach_volume(context, volume_id=volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
'instanceId': id_to_ec2_id(instance['id']),
@@ -659,6 +650,10 @@ class CloudController(object):
return i[0]
def _format_instances(self, context, instance_id=None, **kwargs):
+ # TODO(termie): this method is poorly named as its name does not imply
+ # that it will be making a variety of database calls
+ # rather than simply formatting a bunch of instances that
+ # were handed to it
reservations = {}
# NOTE(vish): instance_id is an optional list of ids to filter by
if instance_id:
@@ -759,7 +754,9 @@ class CloudController(object):
LOG.audit(_("Associate address %s to instance %s"), public_ip,
instance_id, context=context)
instance_id = ec2_id_to_id(instance_id)
- self.compute_api.associate_floating_ip(context, instance_id, public_ip)
+ self.compute_api.associate_floating_ip(context,
+ instance_id=instance_id,
+ address=public_ip)
return {'associateResponse': ["Address associated."]}
def disassociate_address(self, context, public_ip, **kwargs):
@@ -770,8 +767,9 @@ class CloudController(object):
def run_instances(self, context, **kwargs):
max_count = int(kwargs.get('max_count', 1))
instances = self.compute_api.create(context,
- instance_types.get_by_type(kwargs.get('instance_type', None)),
- kwargs['image_id'],
+ instance_type=instance_types.get_by_type(
+ kwargs.get('instance_type', None)),
+ image_id=kwargs['image_id'],
min_count=int(kwargs.get('min_count', max_count)),
max_count=max_count,
kernel_id=kwargs.get('kernel_id', None),
@@ -782,8 +780,7 @@ class CloudController(object):
user_data=kwargs.get('user_data'),
security_group=kwargs.get('security_group'),
availability_zone=kwargs.get('placement', {}).get(
- 'AvailabilityZone'),
- generate_hostname=id_to_ec2_id)
+ 'AvailabilityZone'))
return self._format_run_instances(context,
instances[0]['reservation_id'])
@@ -793,7 +790,7 @@ class CloudController(object):
LOG.debug(_("Going to start terminating instances"))
for ec2_id in instance_id:
instance_id = ec2_id_to_id(ec2_id)
- self.compute_api.delete(context, instance_id)
+ self.compute_api.delete(context, instance_id=instance_id)
return True
def reboot_instances(self, context, instance_id, **kwargs):
@@ -801,19 +798,19 @@ class CloudController(object):
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
for ec2_id in instance_id:
instance_id = ec2_id_to_id(ec2_id)
- self.compute_api.reboot(context, instance_id)
+ self.compute_api.reboot(context, instance_id=instance_id)
return True
def rescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
instance_id = ec2_id_to_id(instance_id)
- self.compute_api.rescue(context, instance_id)
+ self.compute_api.rescue(context, instance_id=instance_id)
return True
def unrescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
instance_id = ec2_id_to_id(instance_id)
- self.compute_api.unrescue(context, instance_id)
+ self.compute_api.unrescue(context, instance_id=instance_id)
return True
def update_instance(self, context, ec2_id, **kwargs):
@@ -824,7 +821,7 @@ class CloudController(object):
changes[field] = kwargs[field]
if changes:
instance_id = ec2_id_to_id(ec2_id)
- self.compute_api.update(context, instance_id, **kwargs)
+ self.compute_api.update(context, instance_id=instance_id, **kwargs)
return True
def describe_images(self, context, image_id=None, **kwargs):
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 90273da36..a6b99c1cb 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -21,6 +21,7 @@ Handles all requests relating to instances (guest vms).
"""
import datetime
+import re
import time
from nova import db
@@ -47,7 +48,8 @@ def generate_default_hostname(instance_id):
class API(base.Base):
"""API for interacting with the compute manager."""
- def __init__(self, image_service=None, network_api=None, volume_api=None,
+ def __init__(self, image_service=None, network_api=None,
+ volume_api=None, hostname_factory=generate_default_hostname,
**kwargs):
if not image_service:
image_service = utils.import_object(FLAGS.image_service)
@@ -58,9 +60,11 @@ class API(base.Base):
if not volume_api:
volume_api = volume.API()
self.volume_api = volume_api
+ self.hostname_factory = hostname_factory
super(API, self).__init__(**kwargs)
def get_network_topic(self, context, instance_id):
+ """Get the network topic for an instance."""
try:
instance = self.get(context, instance_id)
except exception.NotFound as e:
@@ -81,8 +85,7 @@ class API(base.Base):
min_count=1, max_count=1,
display_name='', display_description='',
key_name=None, key_data=None, security_group='default',
- availability_zone=None, user_data=None,
- generate_hostname=generate_default_hostname):
+ availability_zone=None, user_data=None):
"""Create the number of instances requested if quota and
other arguments check out ok."""
@@ -172,9 +175,9 @@ class API(base.Base):
security_group_id)
# Set sane defaults if not specified
- updates = dict(hostname=generate_hostname(instance_id))
- if (not hasattr(instance, 'display_name')) or \
- instance.display_name == None:
+ updates = dict(hostname=self.hostname_factory(instance_id))
+ if (not hasattr(instance, 'display_name') or
+ instance.display_name == None):
updates['display_name'] = "Server %s" % instance_id
instance = self.update(context, instance_id, **updates)
@@ -192,7 +195,7 @@ class API(base.Base):
for group_id in security_groups:
self.trigger_security_group_members_refresh(elevated, group_id)
- return instances
+ return [dict(x.iteritems()) for x in instances]
def ensure_default_security_group(self, context):
""" Create security group for the security context if it
@@ -277,7 +280,8 @@ class API(base.Base):
:retval None
"""
- return self.db.instance_update(context, instance_id, kwargs)
+ rv = self.db.instance_update(context, instance_id, kwargs)
+ return dict(rv.iteritems())
def delete(self, context, instance_id):
LOG.debug(_("Going to try to terminate %s"), instance_id)
@@ -308,7 +312,8 @@ class API(base.Base):
def get(self, context, instance_id):
"""Get a single instance with the given ID."""
- return self.db.instance_get_by_id(context, instance_id)
+ rv = self.db.instance_get_by_id(context, instance_id)
+ return dict(rv.iteritems())
def get_all(self, context, project_id=None, reservation_id=None,
fixed_ip=None):
@@ -317,7 +322,7 @@ class API(base.Base):
an admin, it will retreive all instances in the system."""
if reservation_id is not None:
return self.db.instance_get_all_by_reservation(context,
- reservation_id)
+ reservation_id)
if fixed_ip is not None:
return self.db.fixed_ip_get_instance(context, fixed_ip)
if project_id or not context.is_admin:
@@ -330,18 +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 calls 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,
+ 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)
+ 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."""
@@ -357,7 +399,10 @@ class API(base.Base):
def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for the given instance."""
- self._cast_compute_message('get_diagnostics', context, instance_id)
+ return self._call_compute_message(
+ "get_diagnostics",
+ context,
+ instance_id)
def get_actions(self, context, instance_id):
"""Retrieve actions for the given instance."""
@@ -385,23 +430,23 @@ class API(base.Base):
def get_ajax_console(self, context, instance_id):
"""Get a url to an AJAX Console"""
-
instance = self.get(context, instance_id)
-
- output = rpc.call(context,
- '%s.%s' % (FLAGS.compute_topic,
- instance['host']),
- {'method': 'get_ajax_console',
- 'args': {'instance_id': instance['id']}})
-
+ output = self._call_compute_message('get_ajax_console',
+ context,
+ instance_id)
rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic,
{'method': 'authorize_ajax_console',
'args': {'token': output['token'], 'host': output['host'],
'port': output['port']}})
-
return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url,
output['token'])}
+ def get_console_output(self, context, instance_id):
+ """Get console output for an an instance"""
+ return self._call_compute_message('get_console_output',
+ context,
+ instance_id)
+
def lock(self, context, instance_id):
"""lock the instance with instance_id"""
self._cast_compute_message('lock_instance', context, instance_id)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 613ee45f6..6f09ce674 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -294,7 +294,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)
@@ -311,7 +311,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/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 6c9989b7b..b63b84bed 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -611,9 +611,9 @@ def fixed_ip_get_instance_v6(context, address):
session = get_session()
mac = utils.to_mac(address)
- result = session.query(models.Instance
- ).filter_by(mac_address=mac
- ).first()
+ result = session.query(models.Instance).\
+ filter_by(mac_address=mac).\
+ first()
return result
@@ -775,6 +775,7 @@ def instance_get_by_id(context, instance_id):
if is_admin_context(context):
result = session.query(models.Instance).\
+ options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.floating_ips')).\
filter_by(id=instance_id).\
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index af9584f81..bf5e48b04 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -90,8 +90,14 @@ class NovaBase(object):
setattr(self, k, v)
def iteritems(self):
- """Make the model object behave like a dict"""
- return iter(self)
+ """Make the model object behave like a dict.
+
+ Includes attributes from joins."""
+ local = dict(self)
+ joined = dict([(k, v) for k, v in self.__dict__.iteritems()
+ if not k[0] == '_'])
+ local.update(joined)
+ return local.iteritems()
# TODO(vish): Store images in the database instead of file system
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 bbe98d3da..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("HEAD", "/images/%s" % image_id)
- res = c.getresponse()
- if res.status == 200:
- # TODO(ewanmellor): Temporary hack! We should be parsing
- # the response from Glance properly.
- return {'url': '/images/%s' % image_id}
- 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/network/manager.py b/nova/network/manager.py
index 4d553f074..2a043cc6b 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -239,8 +239,8 @@ class NetworkManager(manager.Manager):
"""Get the network host for the current context."""
raise NotImplementedError()
- def create_networks(self, context, num_networks, network_size, cidr_v6,
- *args, **kwargs):
+ def create_networks(self, context, cidr, num_networks, network_size,
+ cidr_v6, *args, **kwargs):
"""Create networks based on parameters."""
raise NotImplementedError()
@@ -517,10 +517,8 @@ class VlanManager(NetworkManager):
net['vlan'] = vlan
net['bridge'] = 'br%s' % vlan
if(FLAGS.use_ipv6):
- cidr_v6 = "%s/%s" % (
- fixed_net_v6[start_v6],
- significant_bits_v6
- )
+ cidr_v6 = "%s/%s" % (fixed_net_v6[start_v6],
+ significant_bits_v6)
net['cidr_v6'] = cidr_v6
# NOTE(vish): This makes ports unique accross the cloud, a more
diff --git a/nova/test.py b/nova/test.py
index 5922e4b1c..881baccd5 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -23,14 +23,10 @@ and some black magic for inline callbacks.
"""
import datetime
-import sys
-import time
import unittest
import mox
import stubout
-from twisted.internet import defer
-from twisted.trial import unittest as trial_unittest
from nova import context
from nova import db
@@ -74,7 +70,8 @@ class TestCase(unittest.TestCase):
FLAGS.fixed_range,
5, 16,
FLAGS.vlan_start,
- FLAGS.vpn_start)
+ FLAGS.vpn_start,
+ FLAGS.fixed_range_v6)
# emulate some of the mox stuff, we can't use the metaclass
# because it screws with our generators
@@ -139,96 +136,3 @@ class TestCase(unittest.TestCase):
_wrapped.func_name = self.originalAttach.func_name
rpc.Consumer.attach_to_eventlet = _wrapped
-
-
-class TrialTestCase(trial_unittest.TestCase):
- """Test case base class for all unit tests"""
- def setUp(self):
- """Run before each test method to initialize test environment"""
- super(TrialTestCase, self).setUp()
- # NOTE(vish): We need a better method for creating fixtures for tests
- # now that we have some required db setup for the system
- # to work properly.
- self.start = datetime.datetime.utcnow()
- ctxt = context.get_admin_context()
- if db.network_count(ctxt) != 5:
- network_manager.VlanManager().create_networks(ctxt,
- FLAGS.fixed_range,
- 5, 16,
- FLAGS.vlan_start,
- FLAGS.vpn_start,
- FLAGS.fixed_range_v6)
-
- # emulate some of the mox stuff, we can't use the metaclass
- # because it screws with our generators
- self.mox = mox.Mox()
- self.stubs = stubout.StubOutForTesting()
- self.flag_overrides = {}
- self.injected = []
- self._original_flags = FLAGS.FlagValuesDict()
-
- def tearDown(self):
- """Runs after each test method to finalize/tear down test
- environment."""
- try:
- self.mox.UnsetStubs()
- self.stubs.UnsetAll()
- self.stubs.SmartUnsetAll()
- self.mox.VerifyAll()
- # NOTE(vish): Clean up any ips associated during the test.
- ctxt = context.get_admin_context()
- db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host,
- self.start)
- db.network_disassociate_all(ctxt)
- for x in self.injected:
- try:
- x.stop()
- except AssertionError:
- pass
-
- if FLAGS.fake_rabbit:
- fakerabbit.reset_all()
-
- db.security_group_destroy_all(ctxt)
- super(TrialTestCase, self).tearDown()
- finally:
- self.reset_flags()
-
- def flags(self, **kw):
- """Override flag variables for a test"""
- for k, v in kw.iteritems():
- if k in self.flag_overrides:
- self.reset_flags()
- raise Exception(
- 'trying to override already overriden flag: %s' % k)
- self.flag_overrides[k] = getattr(FLAGS, k)
- setattr(FLAGS, k, v)
-
- def reset_flags(self):
- """Resets all flag variables for the test. Runs after each test"""
- FLAGS.Reset()
- for k, v in self._original_flags.iteritems():
- setattr(FLAGS, k, v)
-
- def run(self, result=None):
- test_method = getattr(self, self._testMethodName)
- setattr(self,
- self._testMethodName,
- self._maybeInlineCallbacks(test_method, result))
- rv = super(TrialTestCase, self).run(result)
- setattr(self, self._testMethodName, test_method)
- return rv
-
- def _maybeInlineCallbacks(self, func, result):
- def _wrapped():
- g = func()
- if isinstance(g, defer.Deferred):
- return g
- if not hasattr(g, 'send'):
- return defer.succeed(g)
-
- inlined = defer.inlineCallbacks(func)
- d = inlined()
- return d
- _wrapped.func_name = func.func_name
- return _wrapped
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 d22d7beb1..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
@@ -79,7 +78,7 @@ class FakeHttplibConnection(object):
pass
-class XmlConversionTestCase(test.TrialTestCase):
+class XmlConversionTestCase(test.TestCase):
"""Unit test api xml conversion"""
def test_number_conversion(self):
conv = apirequest._try_convert
@@ -96,16 +95,14 @@ class XmlConversionTestCase(test.TrialTestCase):
self.assertEqual(conv('-0'), 0)
-class ApiEc2TestCase(test.TrialTestCase):
+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_cloud.py b/nova/tests/test_cloud.py
index 2e350cd5a..771b1fcc0 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -21,6 +21,7 @@ import json
from M2Crypto import BIO
from M2Crypto import RSA
import os
+import shutil
import tempfile
import time
@@ -50,6 +51,8 @@ IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images')
os.makedirs(IMAGES_PATH)
+# TODO(termie): these tests are rather fragile, they should at the lest be
+# wiping database state after each run
class CloudTestCase(test.TestCase):
def setUp(self):
super(CloudTestCase, self).setUp()
@@ -287,6 +290,7 @@ class CloudTestCase(test.TestCase):
db.service_destroy(self.context, comp1['id'])
def test_instance_update_state(self):
+ # TODO(termie): what is this code even testing?
def instance(num):
return {
'reservation_id': 'r-1',
@@ -305,7 +309,8 @@ class CloudTestCase(test.TestCase):
'state': 0x01,
'user_data': ''}
rv = self.cloud._format_describe_instances(self.context)
- self.assert_(len(rv['reservationSet']) == 0)
+ logging.error(str(rv))
+ self.assertEqual(len(rv['reservationSet']), 0)
# simulate launch of 5 instances
# self.cloud.instances['pending'] = {}
@@ -368,6 +373,7 @@ class CloudTestCase(test.TestCase):
self.assertEqual('Foo Img', img.metadata['description'])
self._fake_set_image_description(self.context, 'ami-testing', '')
self.assertEqual('', img.metadata['description'])
+ shutil.rmtree(pathdir)
def test_update_of_instance_display_fields(self):
inst = db.instance_create(self.context, {})
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index a7d47961c..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):
@@ -75,7 +76,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(self.context,
FLAGS.default_instance_type, None, **instance)
try:
- self.assertNotEqual(ref[0].display_name, None)
+ self.assertNotEqual(ref[0]['display_name'], None)
finally:
db.instance_destroy(self.context, ref[0]['id'])
@@ -86,10 +87,14 @@ class ComputeTestCase(test.TestCase):
'user_id': self.user.id,
'project_id': self.project.id}
group = db.security_group_create(self.context, values)
- ref = self.compute_api.create(self.context,
- FLAGS.default_instance_type, None, security_group=['default'])
+ ref = self.compute_api.create(
+ self.context,
+ instance_type=FLAGS.default_instance_type,
+ image_id=None,
+ security_group=['default'])
try:
- self.assertEqual(len(ref[0]['security_groups']), 1)
+ self.assertEqual(len(db.security_group_get_by_instance(
+ self.context, ref[0]['id'])), 1)
finally:
db.security_group_destroy(self.context, group['id'])
db.instance_destroy(self.context, ref[0]['id'])
diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py
index 31b5ca79c..85bf94458 100644
--- a/nova/tests/test_console.py
+++ b/nova/tests/test_console.py
@@ -111,12 +111,14 @@ class ConsoleTestCase(test.TestCase):
console_instances = [con['instance_id'] for con in pool.consoles]
self.assert_(instance_id in console_instances)
+ db.instance_destroy(self.context, instance_id)
def test_add_console_does_not_duplicate(self):
instance_id = self._create_instance()
cons1 = self.console.add_console(self.context, instance_id)
cons2 = self.console.add_console(self.context, instance_id)
self.assertEqual(cons1, cons2)
+ db.instance_destroy(self.context, instance_id)
def test_remove_console(self):
instance_id = self._create_instance()
@@ -127,3 +129,4 @@ class ConsoleTestCase(test.TestCase):
db.console_get,
self.context,
console_id)
+ db.instance_destroy(self.context, instance_id)
diff --git a/nova/tests/test_direct.py b/nova/tests/test_direct.py
new file mode 100644
index 000000000..8a74b2296
--- /dev/null
+++ b/nova/tests/test_direct.py
@@ -0,0 +1,103 @@
+# 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.
+
+"""Tests for Direct API."""
+
+import json
+import logging
+
+import webob
+
+from nova import compute
+from nova import context
+from nova import exception
+from nova import test
+from nova import utils
+from nova.api import direct
+from nova.tests import test_cloud
+
+
+class FakeService(object):
+ def echo(self, context, data):
+ return {'data': data}
+
+ def context(self, context):
+ return {'user': context.user_id,
+ 'project': context.project_id}
+
+
+class DirectTestCase(test.TestCase):
+ def setUp(self):
+ super(DirectTestCase, self).setUp()
+ direct.register_service('fake', FakeService())
+ self.router = direct.PostParamsMiddleware(
+ direct.JsonParamsMiddleware(
+ direct.Router()))
+ self.auth_router = direct.DelegatedAuthMiddleware(self.router)
+ self.context = context.RequestContext('user1', 'proj1')
+
+ def tearDown(self):
+ direct.ROUTES = {}
+
+ def test_delegated_auth(self):
+ req = webob.Request.blank('/fake/context')
+ req.headers['X-OpenStack-User'] = 'user1'
+ req.headers['X-OpenStack-Project'] = 'proj1'
+ resp = req.get_response(self.auth_router)
+ data = json.loads(resp.body)
+ self.assertEqual(data['user'], 'user1')
+ self.assertEqual(data['project'], 'proj1')
+
+ def test_json_params(self):
+ req = webob.Request.blank('/fake/echo')
+ req.environ['openstack.context'] = self.context
+ req.method = 'POST'
+ req.body = 'json=%s' % json.dumps({'data': 'foo'})
+ resp = req.get_response(self.router)
+ resp_parsed = json.loads(resp.body)
+ self.assertEqual(resp_parsed['data'], 'foo')
+
+ def test_post_params(self):
+ req = webob.Request.blank('/fake/echo')
+ req.environ['openstack.context'] = self.context
+ req.method = 'POST'
+ req.body = 'data=foo'
+ resp = req.get_response(self.router)
+ resp_parsed = json.loads(resp.body)
+ self.assertEqual(resp_parsed['data'], 'foo')
+
+ def test_proxy(self):
+ proxy = direct.Proxy(self.router)
+ rv = proxy.fake.echo(self.context, data='baz')
+ self.assertEqual(rv['data'], 'baz')
+
+
+class DirectCloudTestCase(test_cloud.CloudTestCase):
+ def setUp(self):
+ super(DirectCloudTestCase, self).setUp()
+ compute_handle = compute.API(image_service=self.cloud.image_service,
+ network_api=self.cloud.network_api,
+ volume_api=self.cloud.volume_api)
+ direct.register_service('compute', compute_handle)
+ self.router = direct.JsonParamsMiddleware(direct.Router())
+ proxy = direct.Proxy(self.router)
+ self.cloud.compute_api = proxy.compute
+
+ def tearDown(self):
+ super(DirectCloudTestCase, self).tearDown()
+ direct.ROUTES = {}
diff --git a/nova/tests/test_log.py b/nova/tests/test_log.py
index beb1d97cf..868a5ead3 100644
--- a/nova/tests/test_log.py
+++ b/nova/tests/test_log.py
@@ -9,7 +9,7 @@ def _fake_context():
return context.RequestContext(1, 1)
-class RootLoggerTestCase(test.TrialTestCase):
+class RootLoggerTestCase(test.TestCase):
def setUp(self):
super(RootLoggerTestCase, self).setUp()
self.log = log.logging.root
@@ -46,7 +46,7 @@ class RootLoggerTestCase(test.TrialTestCase):
self.assert_(True) # didn't raise exception
-class NovaFormatterTestCase(test.TrialTestCase):
+class NovaFormatterTestCase(test.TestCase):
def setUp(self):
super(NovaFormatterTestCase, self).setUp()
self.flags(logging_context_format_string="HAS CONTEXT "\
@@ -78,7 +78,7 @@ class NovaFormatterTestCase(test.TrialTestCase):
self.assertEqual("NOCTXT: baz --DBG\n", self.stream.getvalue())
-class NovaLoggerTestCase(test.TrialTestCase):
+class NovaLoggerTestCase(test.TestCase):
def setUp(self):
super(NovaLoggerTestCase, self).setUp()
self.flags(default_log_levels=["nova-test=AUDIT"], verbose=False)
@@ -96,7 +96,7 @@ class NovaLoggerTestCase(test.TrialTestCase):
self.assertEqual(log.AUDIT, l.level)
-class VerboseLoggerTestCase(test.TrialTestCase):
+class VerboseLoggerTestCase(test.TestCase):
def setUp(self):
super(VerboseLoggerTestCase, self).setUp()
self.flags(default_log_levels=["nova.test=AUDIT"], verbose=True)
diff --git a/nova/tests/test_middleware.py b/nova/tests/test_middleware.py
index 0febf52d6..9d49167ba 100644
--- a/nova/tests/test_middleware.py
+++ b/nova/tests/test_middleware.py
@@ -38,7 +38,7 @@ def conditional_forbid(req):
return 'OK'
-class LockoutTestCase(test.TrialTestCase):
+class LockoutTestCase(test.TestCase):
"""Test case for the Lockout middleware."""
def setUp(self): # pylint: disable-msg=C0103
super(LockoutTestCase, self).setUp()
diff --git a/nova/tests/test_twistd.py b/nova/tests/test_twistd.py
index 75007b9c8..ff8627c3b 100644
--- a/nova/tests/test_twistd.py
+++ b/nova/tests/test_twistd.py
@@ -28,7 +28,7 @@ from nova import test
FLAGS = flags.FLAGS
-class TwistdTestCase(test.TrialTestCase):
+class TwistdTestCase(test.TestCase):
def setUp(self):
super(TwistdTestCase, self).setUp()
self.Options = twistd.WrapTwistedOptions(twistd.TwistdServerOptions)
diff --git a/nova/utils.py b/nova/utils.py
index 27589c30c..6d3ddd092 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -22,6 +22,7 @@ System-level utilities and helper functions.
import datetime
import inspect
+import json
import os
import random
import subprocess
@@ -333,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
@@ -351,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
@@ -391,3 +409,36 @@ def utf8(value):
return value.encode("utf-8")
assert isinstance(value, str)
return value
+
+
+def to_primitive(value):
+ if type(value) is type([]) or type(value) is type((None,)):
+ o = []
+ for v in value:
+ o.append(to_primitive(v))
+ return o
+ elif type(value) is type({}):
+ o = {}
+ for k, v in value.iteritems():
+ o[k] = to_primitive(v)
+ return o
+ elif isinstance(value, datetime.datetime):
+ return str(value)
+ elif hasattr(value, 'iteritems'):
+ return to_primitive(dict(value.iteritems()))
+ elif hasattr(value, '__iter__'):
+ return to_primitive(list(value))
+ else:
+ return value
+
+
+def dumps(value):
+ try:
+ return json.dumps(value)
+ except TypeError:
+ pass
+ return json.dumps(to_primitive(value))
+
+
+def loads(s):
+ return json.loads(s)
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 073a8e5bb..f38af5ed8 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -295,7 +295,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"
@@ -995,8 +995,7 @@ class NWFilterFirewall(FirewallDriver):
['no-mac-spoofing',
'no-ip-spoofing',
'no-arp-spoofing',
- 'allow-dhcp-server'
- ]))
+ 'allow-dhcp-server']))
self._define_filter(self.nova_base_ipv4_filter)
self._define_filter(self.nova_base_ipv6_filter)
self._define_filter(self.nova_dhcp_filter)
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 2c9d53858..b80ff4dba 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -264,14 +264,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}
@@ -532,9 +533,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):
@@ -542,13 +550,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 37e623ed3..6c2fd6a68 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -162,11 +162,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:
@@ -202,7 +202,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 492e2314b..c57c883c9 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -96,6 +96,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')
@@ -144,9 +148,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 e999f76a3..4f5307d80 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -21,7 +21,7 @@
Utility methods for working with WSGI servers
"""
-import json
+import os
import sys
from xml.dom import minidom
@@ -34,7 +34,14 @@ 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):
@@ -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:
@@ -117,20 +147,65 @@ class Application(object):
class Middleware(Application):
- """
- Base WSGI middleware wrapper. These classes require an application to be
+ """Base WSGI middleware.
+
+ These classes require an application to be
initialized that will be called next. By default the middleware will
simply call its wrapped app, or you can override __call__ to customize its
behavior.
"""
- def __init__(self, application): # pylint: disable-msg=W0231
+ @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
+ def process_request(self, req):
+ """Called on each request.
+
+ If this returns None, the next application down the stack will be
+ executed. If it returns a response then that response will be returned
+ and execution will stop here.
+
+ """
+ return None
+
+ def process_response(self, response):
+ """Do whatever you'd like to the response."""
+ return response
+
@webob.dec.wsgify
- def __call__(self, req): # pylint: disable-msg=W0221
- """Override to implement middleware behavior."""
- return self.application
+ def __call__(self, req):
+ response = self.process_request(req)
+ if response:
+ return response
+ response = req.get_response(self.application)
+ return self.process_response(response)
class Debug(Middleware):
@@ -316,7 +391,7 @@ class Serializer(object):
try:
is_xml = (datastring[0] == '<')
if not is_xml:
- return json.loads(datastring)
+ return utils.loads(datastring)
return self._from_xml(datastring)
except:
return None
@@ -349,7 +424,7 @@ class Serializer(object):
return result
def _to_json(self, data):
- return json.dumps(data)
+ return utils.dumps(data)
def _to_xml(self, data):
metadata = self.metadata.get('application/xml', {})
@@ -385,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 97cf32dcf..aadacce57 100644
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
@@ -73,24 +73,24 @@ def _copy_kernel_vdi(dest,copy_args):
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)
@@ -100,7 +100,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',
@@ -108,7 +107,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)