Merged with trunk revno 572.

This commit is contained in:
Ewan Mellor 2011-01-17 19:32:34 +00:00
commit e8a8b14e6b
54 changed files with 1246 additions and 953 deletions

View File

@ -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']
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()
for app in apps:
server.start(*app)
server.wait()
if __name__ == '__main__':
utils.default_flagfile()
FLAGS(sys.argv)
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()
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')

View File

@ -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)

View File

@ -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()

61
bin/nova-direct-api Executable file
View File

@ -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()

145
bin/stack Executable file
View File

@ -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))

View File

@ -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:router]
paste.filter_factory = nova.api.ec2:router_factory
[filter:cloudrequest]
controller = nova.api.ec2.cloud.CloudController
paste.filter_factory = nova.api.ec2:Requestify.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:osapi]
paste.app_factory = nova.api.openstack:router_factory
[app:osapiapp]
paste.app_factory = nova.api.openstack:APIRouter.factory
[app:osversions]
paste.app_factory = nova.api.openstack:versions_factory
[pipeline:osversions]
pipeline = faultwrap osversionapp
[app:osversionapp]
paste.app_factory = nova.api.openstack:Versions.factory

View File

@ -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."""

232
nova/api/direct.py Normal file
View File

@ -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

View File

@ -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):
class Requestify(wsgi.Middleware):
"""Add ec2.'controller', .'action', and .'action_args' to WSGI environ."""
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()

View File

@ -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):

View File

@ -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):

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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).\

View File

@ -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

View File

@ -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',

View File

@ -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):
"""

View File

@ -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)

View File

@ -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():

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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 = []
##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)
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):

View File

@ -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.

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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"""

View File

@ -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, {})

View File

@ -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'])

View File

@ -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)

103
nova/tests/test_direct.py Normal file
View File

@ -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 = {}

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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"""

View File

@ -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)

View File

@ -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)