merged trunk

This commit is contained in:
Trey Morris
2011-06-16 14:03:40 -05:00
8 changed files with 112 additions and 46 deletions

View File

@@ -605,3 +605,7 @@ class InstanceExists(Duplicate):
class MigrationError(NovaException): class MigrationError(NovaException):
message = _("Migration error") + ": %(reason)s" message = _("Migration error") + ": %(reason)s"
class MalformedRequestBody(NovaException):
message = _("Malformed message body: %(reason)s")

View File

@@ -106,12 +106,14 @@ def _wrap_method(function, self):
def _process(func, zone): def _process(func, zone):
"""Worker stub for green thread pool. Give the worker """Worker stub for green thread pool. Give the worker
an authenticated nova client and zone info.""" an authenticated nova client and zone info."""
nova = novaclient.OpenStack(zone.username, zone.password, zone.api_url) nova = novaclient.OpenStack(zone.username, zone.password, None,
zone.api_url)
nova.authenticate() nova.authenticate()
return func(nova, zone) return func(nova, zone)
def call_zone_method(context, method, errors_to_ignore=None, *args, **kwargs): def call_zone_method(context, method_name, errors_to_ignore=None,
novaclient_collection_name='zones', *args, **kwargs):
"""Returns a list of (zone, call_result) objects.""" """Returns a list of (zone, call_result) objects."""
if not isinstance(errors_to_ignore, (list, tuple)): if not isinstance(errors_to_ignore, (list, tuple)):
# This will also handle the default None # This will also handle the default None
@@ -121,7 +123,7 @@ def call_zone_method(context, method, errors_to_ignore=None, *args, **kwargs):
results = [] results = []
for zone in db.zone_get_all(context): for zone in db.zone_get_all(context):
try: try:
nova = novaclient.OpenStack(zone.username, zone.password, nova = novaclient.OpenStack(zone.username, zone.password, None,
zone.api_url) zone.api_url)
nova.authenticate() nova.authenticate()
except novaclient.exceptions.BadRequest, e: except novaclient.exceptions.BadRequest, e:
@@ -131,18 +133,16 @@ def call_zone_method(context, method, errors_to_ignore=None, *args, **kwargs):
#TODO (dabo) - add logic for failure counts per zone, #TODO (dabo) - add logic for failure counts per zone,
# with escalation after a given number of failures. # with escalation after a given number of failures.
continue continue
zone_method = getattr(nova.zones, method) novaclient_collection = getattr(nova, novaclient_collection_name)
collection_method = getattr(novaclient_collection, method_name)
def _error_trap(*args, **kwargs): def _error_trap(*args, **kwargs):
try: try:
return zone_method(*args, **kwargs) return collection_method(*args, **kwargs)
except Exception as e: except Exception as e:
if type(e) in errors_to_ignore: if type(e) in errors_to_ignore:
return None return None
# TODO (dabo) - want to be able to re-raise here. raise
# Returning a string now; raising was causing issues.
# raise e
return "ERROR", "%s" % e
res = pool.spawn(_error_trap, *args, **kwargs) res = pool.spawn(_error_trap, *args, **kwargs)
results.append((zone, res)) results.append((zone, res))

View File

@@ -89,8 +89,8 @@ class SchedulerManager(manager.Manager):
host = getattr(self.driver, driver_method)(elevated, *args, host = getattr(self.driver, driver_method)(elevated, *args,
**kwargs) **kwargs)
except AttributeError, e: except AttributeError, e:
LOG.exception(_("Driver Method %(driver_method)s missing: %(e)s") LOG.warning(_("Driver Method %(driver_method)s missing: %(e)s."
% locals()) "Reverting to schedule()") % locals())
host = self.driver.schedule(elevated, topic, *args, **kwargs) host = self.driver.schedule(elevated, topic, *args, **kwargs)
if not host: if not host:

View File

@@ -88,9 +88,10 @@ class ZoneAwareScheduler(driver.Scheduler):
instance_properties = request_spec['instance_properties'] instance_properties = request_spec['instance_properties']
name = instance_properties['display_name'] name = instance_properties['display_name']
image_id = instance_properties['image_id'] image_ref = instance_properties['image_ref']
meta = instance_properties['metadata'] meta = instance_properties['metadata']
flavor_id = instance_type['flavorid'] flavor_id = instance_type['flavorid']
reservation_id = instance_properties['reservation_id']
files = kwargs['injected_files'] files = kwargs['injected_files']
ipgroup = None # Not supported in OS API ... yet ipgroup = None # Not supported in OS API ... yet
@@ -99,18 +100,20 @@ class ZoneAwareScheduler(driver.Scheduler):
child_blob = zone_info['child_blob'] child_blob = zone_info['child_blob']
zone = db.zone_get(context, child_zone) zone = db.zone_get(context, child_zone)
url = zone.api_url url = zone.api_url
LOG.debug(_("Forwarding instance create call to child zone %(url)s") LOG.debug(_("Forwarding instance create call to child zone %(url)s"
". ReservationID=%(reservation_id)s")
% locals()) % locals())
nova = None nova = None
try: try:
nova = novaclient.OpenStack(zone.username, zone.password, url) nova = novaclient.OpenStack(zone.username, zone.password, None,
url)
nova.authenticate() nova.authenticate()
except novaclient.exceptions.BadRequest, e: except novaclient.exceptions.BadRequest, e:
raise exception.NotAuthorized(_("Bad credentials attempting " raise exception.NotAuthorized(_("Bad credentials attempting "
"to talk to zone at %(url)s.") % locals()) "to talk to zone at %(url)s.") % locals())
nova.servers.create(name, image_id, flavor_id, ipgroup, meta, files, nova.servers.create(name, image_ref, flavor_id, ipgroup, meta, files,
child_blob) child_blob, reservation_id=reservation_id)
def _provision_resource_from_blob(self, context, item, instance_id, def _provision_resource_from_blob(self, context, item, instance_id,
request_spec, kwargs): request_spec, kwargs):
@@ -182,7 +185,11 @@ class ZoneAwareScheduler(driver.Scheduler):
if not build_plan: if not build_plan:
raise driver.NoValidHost(_('No hosts were available')) raise driver.NoValidHost(_('No hosts were available'))
for item in build_plan: for num in xrange(request_spec['num_instances']):
if not build_plan:
break
item = build_plan.pop(0)
self._provision_resource(context, item, instance_id, request_spec, self._provision_resource(context, item, instance_id, request_spec,
kwargs) kwargs)

View File

@@ -89,7 +89,8 @@ class ZoneState(object):
def _call_novaclient(zone): def _call_novaclient(zone):
"""Call novaclient. Broken out for testing purposes.""" """Call novaclient. Broken out for testing purposes."""
client = novaclient.OpenStack(zone.username, zone.password, zone.api_url) client = novaclient.OpenStack(zone.username, zone.password, None,
zone.api_url)
return client.zones.info()._info return client.zones.info()._info

View File

@@ -1109,10 +1109,4 @@ class CallZoneMethodTest(test.TestCase):
def test_call_zone_method_generates_exception(self): def test_call_zone_method_generates_exception(self):
context = {} context = {}
method = 'raises_exception' method = 'raises_exception'
results = api.call_zone_method(context, method) self.assertRaises(Exception, api.call_zone_method, context, method)
# FIXME(sirp): for now the _error_trap code is catching errors and
# converting them to a ("ERROR", "string") tuples. The code (and this
# test) should eventually handle real exceptions.
expected = [(1, ('ERROR', 'testing'))]
self.assertEqual(expected, results)

View File

@@ -56,9 +56,11 @@ To run a single test module:
""" """
import gettext import gettext
import heapq
import os import os
import unittest import unittest
import sys import sys
import time
gettext.install('nova', unicode=1) gettext.install('nova', unicode=1)
@@ -183,9 +185,21 @@ class _NullColorizer(object):
self.stream.write(text) self.stream.write(text)
def get_elapsed_time_color(elapsed_time):
if elapsed_time > 1.0:
return 'red'
elif elapsed_time > 0.25:
return 'yellow'
else:
return 'green'
class NovaTestResult(result.TextTestResult): class NovaTestResult(result.TextTestResult):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
self.show_elapsed = kw.pop('show_elapsed')
result.TextTestResult.__init__(self, *args, **kw) result.TextTestResult.__init__(self, *args, **kw)
self.num_slow_tests = 5
self.slow_tests = [] # this is a fixed-sized heap
self._last_case = None self._last_case = None
self.colorizer = None self.colorizer = None
# NOTE(vish): reset stdout for the terminal check # NOTE(vish): reset stdout for the terminal check
@@ -200,25 +214,40 @@ class NovaTestResult(result.TextTestResult):
def getDescription(self, test): def getDescription(self, test):
return str(test) return str(test)
def _handleElapsedTime(self, test):
self.elapsed_time = time.time() - self.start_time
item = (self.elapsed_time, test)
# Record only the n-slowest tests using heap
if len(self.slow_tests) >= self.num_slow_tests:
heapq.heappushpop(self.slow_tests, item)
else:
heapq.heappush(self.slow_tests, item)
def _writeElapsedTime(self, test):
color = get_elapsed_time_color(self.elapsed_time)
self.colorizer.write(" %.2f" % self.elapsed_time, color)
def _writeResult(self, test, long_result, color, short_result, success):
if self.showAll:
self.colorizer.write(long_result, color)
if self.show_elapsed and success:
self._writeElapsedTime(test)
self.stream.writeln()
elif self.dots:
self.stream.write(short_result)
self.stream.flush()
# NOTE(vish): copied from unittest with edit to add color # NOTE(vish): copied from unittest with edit to add color
def addSuccess(self, test): def addSuccess(self, test):
unittest.TestResult.addSuccess(self, test) unittest.TestResult.addSuccess(self, test)
if self.showAll: self._handleElapsedTime(test)
self.colorizer.write("OK", 'green') self._writeResult(test, 'OK', 'green', '.', True)
self.stream.writeln()
elif self.dots:
self.stream.write('.')
self.stream.flush()
# NOTE(vish): copied from unittest with edit to add color # NOTE(vish): copied from unittest with edit to add color
def addFailure(self, test, err): def addFailure(self, test, err):
unittest.TestResult.addFailure(self, test, err) unittest.TestResult.addFailure(self, test, err)
if self.showAll: self._handleElapsedTime(test)
self.colorizer.write("FAIL", 'red') self._writeResult(test, 'FAIL', 'red', 'F', False)
self.stream.writeln()
elif self.dots:
self.stream.write('F')
self.stream.flush()
# NOTE(vish): copied from nose with edit to add color # NOTE(vish): copied from nose with edit to add color
def addError(self, test, err): def addError(self, test, err):
@@ -226,6 +255,7 @@ class NovaTestResult(result.TextTestResult):
errorClasses. If the exception is a registered class, the errorClasses. If the exception is a registered class, the
error will be added to the list for that class, not errors. error will be added to the list for that class, not errors.
""" """
self._handleElapsedTime(test)
stream = getattr(self, 'stream', None) stream = getattr(self, 'stream', None)
ec, ev, tb = err ec, ev, tb = err
try: try:
@@ -252,14 +282,11 @@ class NovaTestResult(result.TextTestResult):
self.errors.append((test, exc_info)) self.errors.append((test, exc_info))
test.passed = False test.passed = False
if stream is not None: if stream is not None:
if self.showAll: self._writeResult(test, 'ERROR', 'red', 'E', False)
self.colorizer.write("ERROR", 'red')
self.stream.writeln()
elif self.dots:
stream.write('E')
def startTest(self, test): def startTest(self, test):
unittest.TestResult.startTest(self, test) unittest.TestResult.startTest(self, test)
self.start_time = time.time()
current_case = test.test.__class__.__name__ current_case = test.test.__class__.__name__
if self.showAll: if self.showAll:
@@ -273,21 +300,47 @@ class NovaTestResult(result.TextTestResult):
class NovaTestRunner(core.TextTestRunner): class NovaTestRunner(core.TextTestRunner):
def __init__(self, *args, **kwargs):
self.show_elapsed = kwargs.pop('show_elapsed')
core.TextTestRunner.__init__(self, *args, **kwargs)
def _makeResult(self): def _makeResult(self):
return NovaTestResult(self.stream, return NovaTestResult(self.stream,
self.descriptions, self.descriptions,
self.verbosity, self.verbosity,
self.config) self.config,
show_elapsed=self.show_elapsed)
def _writeSlowTests(self, result_):
# Pare out 'fast' tests
slow_tests = [item for item in result_.slow_tests
if get_elapsed_time_color(item[0]) != 'green']
if slow_tests:
slow_total_time = sum(item[0] for item in slow_tests)
self.stream.writeln("Slowest %i tests took %.2f secs:"
% (len(slow_tests), slow_total_time))
for elapsed_time, test in sorted(slow_tests, reverse=True):
time_str = "%.2f" % elapsed_time
self.stream.writeln(" %s %s" % (time_str.ljust(10), test))
def run(self, test):
result_ = core.TextTestRunner.run(self, test)
if self.show_elapsed:
self._writeSlowTests(result_)
return result_
if __name__ == '__main__': if __name__ == '__main__':
logging.setup() logging.setup()
# If any argument looks like a test name but doesn't have "nova.tests" in # If any argument looks like a test name but doesn't have "nova.tests" in
# front of it, automatically add that so we don't have to type as much # front of it, automatically add that so we don't have to type as much
show_elapsed = True
argv = [] argv = []
for x in sys.argv: for x in sys.argv:
if x.startswith('test_'): if x.startswith('test_'):
argv.append('nova.tests.%s' % x) argv.append('nova.tests.%s' % x)
elif x.startswith('--hide-elapsed'):
show_elapsed = False
else: else:
argv.append(x) argv.append(x)
@@ -300,5 +353,6 @@ if __name__ == '__main__':
runner = NovaTestRunner(stream=c.stream, runner = NovaTestRunner(stream=c.stream,
verbosity=c.verbosity, verbosity=c.verbosity,
config=c) config=c,
show_elapsed=show_elapsed)
sys.exit(not core.run(config=c, testRunner=runner, argv=argv)) sys.exit(not core.run(config=c, testRunner=runner, argv=argv))

View File

@@ -10,6 +10,7 @@ function usage {
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -p, --pep8 Just run pep8" echo " -p, --pep8 Just run pep8"
echo " -h, --help Print this usage message" echo " -h, --help Print this usage message"
echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list"
echo "" echo ""
echo "Note: with no options specified, the script will try to run the tests in a virtual environment," echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
echo " If no virtualenv is found, the script will ask if you would like to create one. If you " echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
@@ -24,6 +25,7 @@ function process_option {
-N|--no-virtual-env) let always_venv=0; let never_venv=1;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;;
-f|--force) let force=1;; -f|--force) let force=1;;
-p|--pep8) let just_pep8=1;; -p|--pep8) let just_pep8=1;;
-*) noseopts="$noseopts $1";;
*) noseargs="$noseargs $1" *) noseargs="$noseargs $1"
esac esac
} }
@@ -34,6 +36,7 @@ always_venv=0
never_venv=0 never_venv=0
force=0 force=0
noseargs= noseargs=
noseopts=
wrapper="" wrapper=""
just_pep8=0 just_pep8=0
@@ -72,7 +75,7 @@ function run_pep8 {
--exclude=vcsversion.py ${srcfiles} --exclude=vcsversion.py ${srcfiles}
} }
NOSETESTS="python run_tests.py $noseargs" NOSETESTS="python run_tests.py $noseopts $noseargs"
if [ $never_venv -eq 0 ] if [ $never_venv -eq 0 ]
then then
@@ -107,7 +110,10 @@ fi
run_tests || exit run_tests || exit
# Also run pep8 if no options were provided. # NOTE(sirp): we only want to run pep8 when we're running the full-test suite,
# not when we're running tests individually. To handle this, we need to
# distinguish between options (noseopts), which begin with a '-', and
# arguments (noseargs).
if [ -z "$noseargs" ]; then if [ -z "$noseargs" ]; then
run_pep8 run_pep8
fi fi