diff --git a/.mailmap b/.mailmap index 2af2d7cd..d13219ab 100644 --- a/.mailmap +++ b/.mailmap @@ -16,6 +16,8 @@ + + Masumoto diff --git a/Authors b/Authors index bcb2cd0f..82e07a6b 100644 --- a/Authors +++ b/Authors @@ -26,6 +26,7 @@ Josh Durgin Josh Kearney Joshua McKenty Justin Santa Barbara +Kei Masumoto Ken Pepple Koji Iida Lorin Hochstein @@ -34,6 +35,7 @@ Michael Gundlach Monsyne Dragon Monty Taylor MORITA Kazutaka +Muneyuki Noguchi Nachi Ueno Paul Voccio Rick Clark diff --git a/MANIFEST.in b/MANIFEST.in index 199ce30b..07e4dd51 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,6 +12,7 @@ include nova/cloudpipe/bootscript.sh include nova/cloudpipe/client.ovpn.template include nova/compute/fakevirtinstance.xml include nova/compute/interfaces.template +include nova/db/sqlalchemy/migrate_repo/migrate.cfg include nova/virt/interfaces.template include nova/virt/libvirt*.xml.template include nova/tests/CA/ diff --git a/bin/nova-api b/bin/nova-api index c7cbb3ab..7b4fbeab 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -34,22 +34,53 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): gettext.install('nova', unicode=1) -from nova import api from nova import flags -from nova import utils +from nova import log as logging from nova import wsgi +logging.basicConfig() +LOG = logging.getLogger('nova.api') +LOG.setLevel(logging.DEBUG) FLAGS = flags.FLAGS -flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host') -flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port') -flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') + +API_ENDPOINTS = ['ec2', 'osapi'] + + +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') diff --git a/bin/nova-api-paste b/bin/nova-api-paste deleted file mode 100755 index 419f0bbd..00000000 --- a/bin/nova-api-paste +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python -# pylint: disable-msg=C0103 -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Starter script for Nova API.""" - -import gettext -import os -import sys - -from paste import deploy - -# If ../nova/__init__.py exists, add ../ to Python search path, so that -# it will override what happens to be installed in /usr/(local/)lib/python... -possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), - os.pardir, - os.pardir)) -if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): - sys.path.insert(0, possible_topdir) - -gettext.install('nova', unicode=1) - -from nova import flags -from nova import log as logging -from nova import wsgi - -LOG = logging.getLogger('nova.api') -LOG.setLevel(logging.DEBUG) -LOG.addHandler(logging.StreamHandler()) - -FLAGS = flags.FLAGS - -API_ENDPOINTS = ['ec2', 'openstack'] - - -def load_configuration(paste_config): - """Load the paste configuration from the config file and return it.""" - config = None - # Try each known name to get the global DEFAULTS, which will give ports - for name in API_ENDPOINTS: - try: - config = deploy.appconfig("config:%s" % paste_config, name=name) - except LookupError: - pass - if config: - verbose = config.get('verbose', None) - if verbose: - FLAGS.verbose = int(verbose) == 1 - if FLAGS.verbose: - logging.getLogger().setLevel(logging.DEBUG) - return config - LOG.debug(_("Paste config at %s has no secion for known apis"), - paste_config) - print _("Paste config at %s has no secion for any known apis") % \ - paste_config - os.exit(1) - - -def launch_api(paste_config_file, section, server, port, host): - """Launch an api server from the specified port and IP.""" - LOG.debug(_("Launching %s api on %s:%s"), section, host, port) - app = deploy.loadapp('config:%s' % paste_config_file, name=section) - server.start(app, int(port), host) - - -def run_app(paste_config_file): - LOG.debug(_("Using paste.deploy config at: %s"), configfile) - config = load_configuration(paste_config_file) - LOG.debug(_("Configuration: %r"), config) - server = wsgi.Server() - ip = config.get('host', '0.0.0.0') - for api in API_ENDPOINTS: - port = config.get("%s_port" % api, None) - if not port: - continue - host = config.get("%s_host" % api, ip) - launch_api(configfile, api, server, port, host) - LOG.debug(_("All api servers launched, now waiting")) - server.wait() - - -if __name__ == '__main__': - FLAGS(sys.argv) - configfiles = ['/etc/nova/nova-api.conf'] - if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): - configfiles.insert(0, - os.path.join(possible_topdir, 'etc', 'nova-api.conf')) - for configfile in configfiles: - if os.path.exists(configfile): - run_app(configfile) - break - else: - LOG.debug(_("Skipping missing configuration: %s"), configfile) diff --git a/bin/nova-combined b/bin/nova-combined index f932fdfd..913c866b 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -36,22 +36,20 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): gettext.install('nova', unicode=1) -from nova import api from nova import flags +from nova import log as logging from nova import service from nova import utils from nova import wsgi FLAGS = flags.FLAGS -flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host') -flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port') -flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') if __name__ == '__main__': utils.default_flagfile() FLAGS(sys.argv) + logging.basicConfig() compute = service.Service.create(binary='nova-compute') network = service.Service.create(binary='nova-network') @@ -61,7 +59,22 @@ if __name__ == '__main__': service.serve(compute, network, volume, scheduler) - server = wsgi.Server() - server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) - server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host) - server.wait() + apps = [] + paste_config_file = wsgi.paste_config_file('nova-api.conf') + for api in ['osapi', 'ec2']: + config = wsgi.load_paste_configuration(paste_config_file, api) + if config is None: + continue + wsgi.paste_config_to_flags(config, { + "verbose": FLAGS.verbose, + "%s_host" % api: config.get('host', '0.0.0.0'), + "%s_port" % api: getattr(FLAGS, "%s_port" % api)}) + app = wsgi.load_paste_app(paste_config_file, api) + apps.append((app, getattr(FLAGS, "%s_port" % api), + getattr(FLAGS, "%s_host" % api))) + if len(apps) > 0: + logging.basicConfig() + server = wsgi.Server() + for app in apps: + server.start(*app) + server.wait() diff --git a/bin/nova-direct-api b/bin/nova-direct-api new file mode 100755 index 00000000..e7dd14fb --- /dev/null +++ b/bin/nova-direct-api @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# pylint: disable-msg=C0103 +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Starter script for Nova Direct API.""" + +import gettext +import os +import sys + +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + +gettext.install('nova', unicode=1) + +from nova import flags +from nova import utils +from nova import wsgi +from nova.api import direct +from nova.compute import api as compute_api + + +FLAGS = flags.FLAGS +flags.DEFINE_integer('direct_port', 8001, 'Direct API port') +flags.DEFINE_string('direct_host', '0.0.0.0', 'Direct API host') + +if __name__ == '__main__': + utils.default_flagfile() + FLAGS(sys.argv) + + direct.register_service('compute', compute_api.ComputeAPI()) + direct.register_service('reflect', direct.Reflection()) + router = direct.Router() + with_json = direct.JsonParamsMiddleware(router) + with_req = direct.PostParamsMiddleware(with_json) + with_auth = direct.DelegatedAuthMiddleware(with_req) + + server = wsgi.Server() + server.start(with_auth, FLAGS.direct_port, host=FLAGS.direct_host) + server.wait() diff --git a/bin/nova-manage b/bin/nova-manage index 7f241d7f..d0901ddf 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -527,12 +527,11 @@ class DbCommands(object): pass def sync(self, version=None): - """adds role to user - if project is specified, adds project specific role - arguments: user, role [project]""" + """Sync the database up to the most recent version.""" return migration.db_sync(version) def version(self): + """Print the current database version.""" print migration.db_version() diff --git a/bin/stack b/bin/stack new file mode 100755 index 00000000..7a6ce596 --- /dev/null +++ b/bin/stack @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""CLI for the Direct API.""" + +import eventlet +eventlet.monkey_patch() + +import os +import pprint +import sys +import textwrap +import urllib +import urllib2 + +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + +import gflags +from nova import utils + + +FLAGS = gflags.FLAGS +gflags.DEFINE_string('host', '127.0.0.1', 'Direct API host') +gflags.DEFINE_integer('port', 8001, 'Direct API host') +gflags.DEFINE_string('user', 'user1', 'Direct API username') +gflags.DEFINE_string('project', 'proj1', 'Direct API project') + + +USAGE = """usage: stack [options] [arg1=value arg2=value] + + `stack help` should output the list of available controllers + `stack ` should output the available methods for that controller + `stack help ` should do the same + `stack help ` should output info for a method +""" + + +def format_help(d): + """Format help text, keys are labels and values are descriptions.""" + indent = max([len(k) for k in d]) + out = [] + for k, v in d.iteritems(): + t = textwrap.TextWrapper(initial_indent=' %s ' % k.ljust(indent), + subsequent_indent=' ' * (indent + 6)) + out.extend(t.wrap(v)) + return out + + +def help_all(): + rv = do_request('reflect', 'get_controllers') + out = format_help(rv) + return (USAGE + str(FLAGS.MainModuleHelp()) + + '\n\nAvailable controllers:\n' + + '\n'.join(out) + '\n') + + +def help_controller(controller): + rv = do_request('reflect', 'get_methods') + methods = dict([(k.split('/')[2], v) for k, v in rv.iteritems() + if k.startswith('/%s' % controller)]) + return ('Available methods for %s:\n' % controller + + '\n'.join(format_help(methods))) + + +def help_method(controller, method): + rv = do_request('reflect', + 'get_method_info', + {'method': '/%s/%s' % (controller, method)}) + + sig = '%s(%s):' % (method, ', '.join(['='.join(x) for x in rv['args']])) + out = textwrap.wrap(sig, subsequent_indent=' ' * len('%s(' % method)) + out.append('\n' + rv['doc']) + return '\n'.join(out) + + +def do_request(controller, method, params=None): + if params: + data = urllib.urlencode(params) + else: + data = None + + url = 'http://%s:%s/%s/%s' % (FLAGS.host, FLAGS.port, controller, method) + headers = {'X-OpenStack-User': FLAGS.user, + 'X-OpenStack-Project': FLAGS.project} + + req = urllib2.Request(url, data, headers) + resp = urllib2.urlopen(req) + return utils.loads(resp.read()) + + +if __name__ == '__main__': + args = FLAGS(sys.argv) + + cmd = args.pop(0) + if not args: + print help_all() + sys.exit() + + first = args.pop(0) + if first == 'help': + action = help_all + params = [] + if args: + params.append(args.pop(0)) + action = help_controller + if args: + params.append(args.pop(0)) + action = help_method + print action(*params) + sys.exit(0) + + controller = first + if not args: + print help_controller(controller) + sys.exit() + + method = args.pop(0) + params = {} + for x in args: + key, value = x.split('=', 1) + params[key] = value + + pprint.pprint(do_request(controller, method, params)) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index de18a383..1652e24e 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -682,7 +682,7 @@ class AuthManager(object): region, _sep, region_host = item.partition("=") regions[region] = region_host else: - regions = {'nova': FLAGS.cc_host} + regions = {'nova': FLAGS.ec2_host} for region, host in regions.iteritems(): rc = self.__generate_rc(user, pid, @@ -727,28 +727,28 @@ class AuthManager(object): def __generate_rc(user, pid, use_dmz=True, host=None): """Generate rc file for user""" if use_dmz: - cc_host = FLAGS.cc_dmz + ec2_host = FLAGS.ec2_dmz_host else: - cc_host = FLAGS.cc_host + ec2_host = FLAGS.ec2_host # NOTE(vish): Always use the dmz since it is used from inside the # instance s3_host = FLAGS.s3_dmz if host: s3_host = host - cc_host = host + ec2_host = host rc = open(FLAGS.credentials_template).read() rc = rc % {'access': user.access, 'project': pid, 'secret': user.secret, - 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix, - cc_host, - FLAGS.cc_port, - FLAGS.ec2_suffix), + 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_scheme, + ec2_host, + FLAGS.ec2_port, + FLAGS.ec2_path), 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port), - 'os': '%s://%s:%s%s' % (FLAGS.os_prefix, - cc_host, + 'os': '%s://%s:%s%s' % (FLAGS.osapi_scheme, + ec2_host, FLAGS.osapi_port, - FLAGS.os_suffix), + FLAGS.osapi_path), 'user': user.name, 'nova': FLAGS.ca_file, 'cert': FLAGS.credential_cert_file, diff --git a/nova/flags.py b/nova/flags.py index ef66c3f3..81e2e36f 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -254,14 +254,15 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') -DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') -DEFINE_string('os_prefix', 'http', 'prefix for openstack') -DEFINE_string('cc_host', '$my_ip', 'ip of api server') -DEFINE_string('cc_dmz', '$my_ip', 'internal ip of api server') -DEFINE_integer('cc_port', 8773, 'cloud controller port') +DEFINE_string('ec2_host', '$my_ip', 'ip of api server') +DEFINE_string('ec2_dmz_host', '$my_ip', 'internal ip of api server') +DEFINE_integer('ec2_port', 8773, 'cloud controller port') +DEFINE_string('ec2_scheme', 'http', 'prefix for ec2') +DEFINE_string('ec2_path', '/services/Cloud', 'suffix for ec2') +DEFINE_string('osapi_host', '$my_ip', 'ip of api server') +DEFINE_string('osapi_scheme', 'http', 'prefix for openstack') DEFINE_integer('osapi_port', 8774, 'OpenStack API port') -DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') -DEFINE_string('os_suffix', '/v1.0/', 'suffix for openstack') +DEFINE_string('osapi_path', '/v1.0/', 'suffix for openstack') DEFINE_string('default_project', 'openstack', 'default project for openstack') DEFINE_string('default_image', 'ami-11111', diff --git a/nova/log.py b/nova/log.py index c1428c05..4997d3f2 100644 --- a/nova/log.py +++ b/nova/log.py @@ -116,6 +116,8 @@ def basicConfig(): handler.setFormatter(_formatter) if FLAGS.verbose: logging.root.setLevel(logging.DEBUG) + else: + logging.root.setLevel(logging.INFO) if FLAGS.use_syslog: syslog = SysLogHandler(address='/dev/log') syslog.setFormatter(_formatter) diff --git a/nova/tests/test_access.py b/nova/tests/test_access.py index 0929903c..e170ccee 100644 --- a/nova/tests/test_access.py +++ b/nova/tests/test_access.py @@ -20,21 +20,31 @@ import unittest import webob from nova import context -from nova import exception from nova import flags from nova import test from nova.api import ec2 from nova.auth import manager - FLAGS = flags.FLAGS -class Context(object): +class FakeControllerClass(object): pass +class FakeApiRequest(object): + def __init__(self, action): + self.controller = FakeControllerClass() + self.action = action + + class AccessTestCase(test.TestCase): + def _env_for(self, ctxt, action): + env = {} + env['ec2.context'] = ctxt + env['ec2.request'] = FakeApiRequest(action) + return env + def setUp(self): super(AccessTestCase, self).setUp() um = manager.AuthManager() @@ -64,7 +74,7 @@ class AccessTestCase(test.TestCase): return [''] self.mw = ec2.Authorizer(noopWSGIApp) - self.mw.action_roles = {'str': { + self.mw.action_roles = {'FakeControllerClass': { '_allow_all': ['all'], '_allow_none': [], '_allow_project_manager': ['projectmanager'], @@ -84,9 +94,7 @@ class AccessTestCase(test.TestCase): def response_status(self, user, methodName): ctxt = context.RequestContext(user, self.project) - environ = {'ec2.context': ctxt, - 'ec2.controller': 'some string', - 'ec2.action': methodName} + environ = self._env_for(ctxt, methodName) req = webob.Request.blank('/', environ) resp = req.get_response(self.mw) return resp.status_int diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 17789c25..66a16b0c 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -26,9 +26,8 @@ import StringIO import webob from nova import context -from nova import flags from nova import test -from nova import api +from nova.api import ec2 from nova.api.ec2 import cloud from nova.api.ec2 import apirequest from nova.auth import manager @@ -100,12 +99,10 @@ class ApiEc2TestCase(test.TestCase): """Unit test for the cloud controller on an EC2 API""" def setUp(self): super(ApiEc2TestCase, self).setUp() - self.manager = manager.AuthManager() - self.host = '127.0.0.1' - - self.app = api.API('ec2') + self.app = ec2.Authenticate(ec2.Requestify(ec2.Executor(), + 'nova.api.ec2.cloud.CloudController')) def expect_http(self, host=None, is_secure=False): """Returns a new EC2 connection""" diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 2e350cd5..771b1fcc 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -21,6 +21,7 @@ import json from M2Crypto import BIO from M2Crypto import RSA import os +import shutil import tempfile import time @@ -50,6 +51,8 @@ IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images') os.makedirs(IMAGES_PATH) +# TODO(termie): these tests are rather fragile, they should at the lest be +# wiping database state after each run class CloudTestCase(test.TestCase): def setUp(self): super(CloudTestCase, self).setUp() @@ -287,6 +290,7 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) def test_instance_update_state(self): + # TODO(termie): what is this code even testing? def instance(num): return { 'reservation_id': 'r-1', @@ -305,7 +309,8 @@ class CloudTestCase(test.TestCase): 'state': 0x01, 'user_data': ''} rv = self.cloud._format_describe_instances(self.context) - self.assert_(len(rv['reservationSet']) == 0) + logging.error(str(rv)) + self.assertEqual(len(rv['reservationSet']), 0) # simulate launch of 5 instances # self.cloud.instances['pending'] = {} @@ -368,6 +373,7 @@ class CloudTestCase(test.TestCase): self.assertEqual('Foo Img', img.metadata['description']) self._fake_set_image_description(self.context, 'ami-testing', '') self.assertEqual('', img.metadata['description']) + shutil.rmtree(pathdir) def test_update_of_instance_display_fields(self): inst = db.instance_create(self.context, {}) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index a7d47961..09f6ee94 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -32,8 +32,9 @@ from nova import utils from nova.auth import manager -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.compute') +FLAGS = flags.FLAGS +flags.DECLARE('stub_network', 'nova.compute.manager') class ComputeTestCase(test.TestCase): @@ -75,7 +76,7 @@ class ComputeTestCase(test.TestCase): ref = self.compute_api.create(self.context, FLAGS.default_instance_type, None, **instance) try: - self.assertNotEqual(ref[0].display_name, None) + self.assertNotEqual(ref[0]['display_name'], None) finally: db.instance_destroy(self.context, ref[0]['id']) @@ -86,10 +87,14 @@ class ComputeTestCase(test.TestCase): 'user_id': self.user.id, 'project_id': self.project.id} group = db.security_group_create(self.context, values) - ref = self.compute_api.create(self.context, - FLAGS.default_instance_type, None, security_group=['default']) + ref = self.compute_api.create( + self.context, + instance_type=FLAGS.default_instance_type, + image_id=None, + security_group=['default']) try: - self.assertEqual(len(ref[0]['security_groups']), 1) + self.assertEqual(len(db.security_group_get_by_instance( + self.context, ref[0]['id'])), 1) finally: db.security_group_destroy(self.context, group['id']) db.instance_destroy(self.context, ref[0]['id']) diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index 31b5ca79..85bf9445 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -111,12 +111,14 @@ class ConsoleTestCase(test.TestCase): console_instances = [con['instance_id'] for con in pool.consoles] self.assert_(instance_id in console_instances) + db.instance_destroy(self.context, instance_id) def test_add_console_does_not_duplicate(self): instance_id = self._create_instance() cons1 = self.console.add_console(self.context, instance_id) cons2 = self.console.add_console(self.context, instance_id) self.assertEqual(cons1, cons2) + db.instance_destroy(self.context, instance_id) def test_remove_console(self): instance_id = self._create_instance() @@ -127,3 +129,4 @@ class ConsoleTestCase(test.TestCase): db.console_get, self.context, console_id) + db.instance_destroy(self.context, instance_id) diff --git a/nova/tests/test_direct.py b/nova/tests/test_direct.py new file mode 100644 index 00000000..8a74b229 --- /dev/null +++ b/nova/tests/test_direct.py @@ -0,0 +1,103 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for Direct API.""" + +import json +import logging + +import webob + +from nova import compute +from nova import context +from nova import exception +from nova import test +from nova import utils +from nova.api import direct +from nova.tests import test_cloud + + +class FakeService(object): + def echo(self, context, data): + return {'data': data} + + def context(self, context): + return {'user': context.user_id, + 'project': context.project_id} + + +class DirectTestCase(test.TestCase): + def setUp(self): + super(DirectTestCase, self).setUp() + direct.register_service('fake', FakeService()) + self.router = direct.PostParamsMiddleware( + direct.JsonParamsMiddleware( + direct.Router())) + self.auth_router = direct.DelegatedAuthMiddleware(self.router) + self.context = context.RequestContext('user1', 'proj1') + + def tearDown(self): + direct.ROUTES = {} + + def test_delegated_auth(self): + req = webob.Request.blank('/fake/context') + req.headers['X-OpenStack-User'] = 'user1' + req.headers['X-OpenStack-Project'] = 'proj1' + resp = req.get_response(self.auth_router) + data = json.loads(resp.body) + self.assertEqual(data['user'], 'user1') + self.assertEqual(data['project'], 'proj1') + + def test_json_params(self): + req = webob.Request.blank('/fake/echo') + req.environ['openstack.context'] = self.context + req.method = 'POST' + req.body = 'json=%s' % json.dumps({'data': 'foo'}) + resp = req.get_response(self.router) + resp_parsed = json.loads(resp.body) + self.assertEqual(resp_parsed['data'], 'foo') + + def test_post_params(self): + req = webob.Request.blank('/fake/echo') + req.environ['openstack.context'] = self.context + req.method = 'POST' + req.body = 'data=foo' + resp = req.get_response(self.router) + resp_parsed = json.loads(resp.body) + self.assertEqual(resp_parsed['data'], 'foo') + + def test_proxy(self): + proxy = direct.Proxy(self.router) + rv = proxy.fake.echo(self.context, data='baz') + self.assertEqual(rv['data'], 'baz') + + +class DirectCloudTestCase(test_cloud.CloudTestCase): + def setUp(self): + super(DirectCloudTestCase, self).setUp() + compute_handle = compute.API(image_service=self.cloud.image_service, + network_api=self.cloud.network_api, + volume_api=self.cloud.volume_api) + direct.register_service('compute', compute_handle) + self.router = direct.JsonParamsMiddleware(direct.Router()) + proxy = direct.Proxy(self.router) + self.cloud.compute_api = proxy.compute + + def tearDown(self): + super(DirectCloudTestCase, self).tearDown() + direct.ROUTES = {} diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index afdc89ba..556fe561 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -122,10 +122,10 @@ class LibvirtConnTestCase(test.TestCase): if rescue: check = (lambda t: t.find('./os/kernel').text.split('/')[1], - 'rescue-kernel') + 'kernel.rescue') check_list.append(check) check = (lambda t: t.find('./os/initrd').text.split('/')[1], - 'rescue-ramdisk') + 'ramdisk.rescue') check_list.append(check) else: if expect_kernel: @@ -161,13 +161,16 @@ class LibvirtConnTestCase(test.TestCase): if rescue: common_checks += [ (lambda t: t.findall('./devices/disk/source')[0].get( - 'file').split('/')[1], 'rescue-disk'), + 'file').split('/')[1], 'disk.rescue'), (lambda t: t.findall('./devices/disk/source')[1].get( 'file').split('/')[1], 'disk')] else: common_checks += [(lambda t: t.findall( './devices/disk/source')[0].get('file').split('/')[1], 'disk')] + common_checks += [(lambda t: t.findall( + './devices/disk/source')[1].get('file').split('/')[1], + 'disk.local')] for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 261ee0fd..9f5b266f 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -34,6 +34,7 @@ from nova.virt.xenapi import volume_utils from nova.virt.xenapi.vmops import SimpleDH from nova.tests.db import fakes as db_fakes from nova.tests.xenapi import stubs +from nova.tests.glance import stubs as glance_stubs FLAGS = flags.FLAGS @@ -108,18 +109,16 @@ class XenAPIVolumeTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = db.instance_create(self.values) - xenapi_fake.create_vm(instance.name, 'Running') + vm = xenapi_fake.create_vm(instance.name, 'Running') result = conn.attach_volume(instance.name, volume['id'], '/dev/sdc') def check(): # check that the VM has a VBD attached to it - # Get XenAPI reference for the VM - vms = xenapi_fake.get_all('VM') # Get XenAPI record for VBD vbds = xenapi_fake.get_all('VBD') vbd = xenapi_fake.get_record('VBD', vbds[0]) vm_ref = vbd['VM'] - self.assertEqual(vm_ref, vms[0]) + self.assertEqual(vm_ref, vm) check() @@ -157,9 +156,14 @@ class XenAPIVMTestCase(test.TestCase): FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' xenapi_fake.reset() + xenapi_fake.create_local_srs() db_fakes.stub_out_db_instance_api(self.stubs) xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + stubs.stubout_get_this_vm_uuid(self.stubs) + stubs.stubout_stream_disk(self.stubs) + glance_stubs.stubout_glance_client(self.stubs, + glance_stubs.FakeGlance) self.conn = xenapi_conn.get_connection(False) def test_list_instances_0(self): @@ -207,40 +211,70 @@ class XenAPIVMTestCase(test.TestCase): check() - def test_spawn(self): - instance = self._create_instance() + def check_vm_record(self, conn): + instances = conn.list_instances() + self.assertEquals(instances, [1]) - def check(): - instances = self.conn.list_instances() - self.assertEquals(instances, [1]) + # Get Nova record for VM + vm_info = conn.get_info(1) - # Get Nova record for VM - vm_info = self.conn.get_info(1) + # Get XenAPI record for VM + vms = [rec for ref, rec + in xenapi_fake.get_all_records('VM').iteritems() + if not rec['is_control_domain']] + vm = vms[0] - # Get XenAPI record for VM - vms = xenapi_fake.get_all('VM') - vm = xenapi_fake.get_record('VM', vms[0]) + # Check that m1.large above turned into the right thing. + instance_type = instance_types.INSTANCE_TYPES['m1.large'] + mem_kib = long(instance_type['memory_mb']) << 10 + mem_bytes = str(mem_kib << 10) + vcpus = instance_type['vcpus'] + self.assertEquals(vm_info['max_mem'], mem_kib) + self.assertEquals(vm_info['mem'], mem_kib) + self.assertEquals(vm['memory_static_max'], mem_bytes) + self.assertEquals(vm['memory_dynamic_max'], mem_bytes) + self.assertEquals(vm['memory_dynamic_min'], mem_bytes) + self.assertEquals(vm['VCPUs_max'], str(vcpus)) + self.assertEquals(vm['VCPUs_at_startup'], str(vcpus)) - # Check that m1.large above turned into the right thing. - instance_type = instance_types.INSTANCE_TYPES['m1.large'] - mem_kib = long(instance_type['memory_mb']) << 10 - mem_bytes = str(mem_kib << 10) - vcpus = instance_type['vcpus'] - self.assertEquals(vm_info['max_mem'], mem_kib) - self.assertEquals(vm_info['mem'], mem_kib) - self.assertEquals(vm['memory_static_max'], mem_bytes) - self.assertEquals(vm['memory_dynamic_max'], mem_bytes) - self.assertEquals(vm['memory_dynamic_min'], mem_bytes) - self.assertEquals(vm['VCPUs_max'], str(vcpus)) - self.assertEquals(vm['VCPUs_at_startup'], str(vcpus)) + # Check that the VM is running according to Nova + self.assertEquals(vm_info['state'], power_state.RUNNING) - # Check that the VM is running according to Nova - self.assertEquals(vm_info['state'], power_state.RUNNING) + # Check that the VM is running according to XenAPI. + self.assertEquals(vm['power_state'], 'Running') - # Check that the VM is running according to XenAPI. - self.assertEquals(vm['power_state'], 'Running') + def _test_spawn(self, image_id, kernel_id, ramdisk_id): + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + values = {'name': 1, + 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': image_id, + 'kernel_id': kernel_id, + 'ramdisk_id': ramdisk_id, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff', + } + conn = xenapi_conn.get_connection(False) + instance = db.instance_create(values) + conn.spawn(instance) + self.check_vm_record(conn) - check() + def test_spawn_raw_objectstore(self): + FLAGS.xenapi_image_service = 'objectstore' + self._test_spawn(1, None, None) + + def test_spawn_objectstore(self): + FLAGS.xenapi_image_service = 'objectstore' + self._test_spawn(1, 2, 3) + + def test_spawn_raw_glance(self): + FLAGS.xenapi_image_service = 'glance' + self._test_spawn(1, None, None) + + def test_spawn_glance(self): + FLAGS.xenapi_image_service = 'glance' + self._test_spawn(1, 2, 3) def tearDown(self): super(XenAPIVMTestCase, self).tearDown() diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 292bd9ba..624995ad 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -115,6 +115,21 @@ def stub_out_get_target(stubs): stubs.Set(volume_utils, '_get_target', fake_get_target) +def stubout_get_this_vm_uuid(stubs): + def f(): + vms = [rec['uuid'] for ref, rec + in fake.get_all_records('VM').iteritems() + if rec['is_control_domain']] + return vms[0] + stubs.Set(vm_utils, 'get_this_vm_uuid', f) + + +def stubout_stream_disk(stubs): + def f(_1, _2, _3, _4): + pass + stubs.Set(vm_utils, '_stream_disk', f) + + class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ def __init__(self, uri): @@ -124,7 +139,10 @@ class FakeSessionForVMTests(fake.SessionBase): return self.xenapi.network.get_all_records() def host_call_plugin(self, _1, _2, _3, _4, _5): - return '' + sr_ref = fake.get_all('SR')[0] + vdi_ref = fake.create_vdi('', False, sr_ref, False) + vdi_rec = fake.get_record('VDI', vdi_ref) + return '%s' % vdi_rec['uuid'] def VM_start(self, _1, ref, _2, _3): vm = fake.get_record('VM', ref) @@ -159,10 +177,6 @@ class FakeSessionForVolumeTests(fake.SessionBase): def __init__(self, uri): super(FakeSessionForVolumeTests, self).__init__(uri) - def VBD_plug(self, _1, ref): - rec = fake.get_record('VBD', ref) - rec['currently-attached'] = True - def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11): valid_vdi = False