From 198af0ef9e65bc4c2efe74b9d93cf40210eb77bc Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Thu, 21 Oct 2010 14:29:34 -0400 Subject: Moves db writes into compute manager class. Cleans up sqlalchemy model/api to remove redundant calls for updating what is really a dict. --- nova/api/ec2/cloud.py | 43 ++++++++++++--------- nova/api/openstack/servers.py | 25 ++++++------ nova/compute/manager.py | 35 +++++++++++++++++ nova/db/sqlalchemy/api.py | 66 +++++++++++--------------------- nova/db/sqlalchemy/models.py | 10 +++++ nova/tests/api/openstack/fakes.py | 1 + nova/tests/api/openstack/test_servers.py | 4 +- 7 files changed, 104 insertions(+), 80 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6d4f58499..096ddf668 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -97,6 +97,7 @@ class CloudController(object): """ def __init__(self): self.network_manager = utils.import_object(FLAGS.network_manager) + self.compute_manager = utils.import_object(FLAGS.compute_manager) self.setup() def __str__(self): @@ -846,27 +847,29 @@ class CloudController(object): elevated = context.elevated() for num in range(num_instances): - instance_ref = db.instance_create(context, base_options) - inst_id = instance_ref['id'] + + instance_data = base_options + instance_data['mac_address'] = utils.generate_mac() + instance_data['launch_index'] = num - for security_group_id in security_groups: - db.instance_add_security_group(elevated, - inst_id, - security_group_id) + instance_ref = self.compute_manager.create_instance(context, + instance_data, + security_groups) - inst = {} - inst['mac_address'] = utils.generate_mac() - inst['launch_index'] = num internal_id = instance_ref['internal_id'] ec2_id = internal_id_to_ec2_id(internal_id) - inst['hostname'] = ec2_id - db.instance_update(context, inst_id, inst) + instance_ref['hostname'] = ec2_id + + self.compute_manager.update_instance(context, + instance_ref['id'], + instance_ref) + # TODO(vish): This probably should be done in the scheduler # or in compute as a call. The network should be # allocated after the host is assigned and setup # can happen at the same time. address = self.network_manager.allocate_fixed_ip(context, - inst_id, + instance_ref['id'], vpn) network_topic = self._get_network_topic(context) rpc.cast(elevated, @@ -878,9 +881,9 @@ class CloudController(object): FLAGS.scheduler_topic, {"method": "run_instance", "args": {"topic": FLAGS.compute_topic, - "instance_id": inst_id}}) + "instance_id": instance_ref['id']}}) logging.debug("Casting to scheduler for %s/%s's instance %s" % - (context.project.name, context.user.name, inst_id)) + (context.project.name, context.user.name, instance_ref['id'])) return self._format_run_instances(context, reservation_id) @@ -907,11 +910,13 @@ class CloudController(object): id_str) continue now = datetime.datetime.utcnow() - db.instance_update(context, - instance_ref['id'], - {'state_description': 'terminating', - 'state': 0, - 'terminated_at': now}) + updated_data = {'state_description': 'terminating', + 'state': 0, + 'terminated_at': now} + self.compute_manager.update_instance(context, + instance_ref['id'], + updated_data) + # FIXME(ja): where should network deallocate occur? address = db.instance_get_floating_address(context, instance_ref['id']) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a73591ccc..6ce364eb7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -94,6 +94,7 @@ class Controller(wsgi.Controller): db_driver = FLAGS.db_driver self.db_driver = utils.import_object(db_driver) self.network_manager = utils.import_object(FLAGS.network_manager) + self.compute_manager = utils.import_object(FLAGS.compute_manager) super(Controller, self).__init__() def index(self, req): @@ -241,34 +242,30 @@ class Controller(wsgi.Controller): inst['memory_mb'] = flavor['memory_mb'] inst['vcpus'] = flavor['vcpus'] inst['local_gb'] = flavor['local_gb'] - - ref = self.db_driver.instance_create(ctxt, inst) - inst['id'] = ref.internal_id - inst['mac_address'] = utils.generate_mac() - - #TODO(dietz) is this necessary? inst['launch_index'] = 0 - inst['hostname'] = str(ref.internal_id) - self.db_driver.instance_update(ctxt, inst['id'], inst) + ref = self.compute_manager.create_instance(ctxt, inst) + inst['id'] = ref['internal_id'] + + inst['hostname'] = str(ref['internal_id']) + self.compute_manager.update_instance(ctxt, inst['id'], inst) - network_manager = utils.import_object(FLAGS.network_manager) - address = network_manager.allocate_fixed_ip(ctxt, - inst['id']) + address = self.network_manager.allocate_fixed_ip(ctxt, + inst['id']) # TODO(vish): This probably should be done in the scheduler # network is setup when host is assigned - network_topic = self._get_network_topic(ctxt, network_manager) + network_topic = self._get_network_topic(ctxt) rpc.call(ctxt, network_topic, {"method": "setup_fixed_ip", "args": {"address": address}}) return inst - def _get_network_topic(self, context, network_manager): + def _get_network_topic(self, context): """Retrieves the network host for a project""" - network_ref = network_manager.get_network(context) + network_ref = self.network_manager.get_network(context) host = network_ref['host'] if not host: host = rpc.call(context, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 523bb8893..c752d954b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -67,6 +67,41 @@ class ComputeManager(manager.Manager): def refresh_security_group(self, context, security_group_id, **_kwargs): yield self.driver.refresh_security_group(security_group_id) + + def create_instance(self, context, instance_data, security_groups=[]): + """Creates the instance in the datastore and returns the + new instance as a mapping + + :param context: The security context + :param instance_data: mapping of instance options + :param security_groups: list of security group ids to + attach to the instance + + :retval Returns a mapping of the instance information + that has just been created + + """ + instance_ref = self.db.instance_create(context, instance_data) + inst_id = instance_ref['id'] + + elevated = context.elevated() + for security_group_id in security_groups: + self.db.instance_add_security_group(elevated, + inst_id, + security_group_id) + return instance_ref + + def update_instance(self, context, instance_id, instance_data): + """Updates the instance in the datastore + + :param context: The security context + :param instance_data: mapping of instance options + + :retval None + + """ + self.db.instance_update(context, instance_id, instance_data) + @defer.inlineCallbacks @exception.wrap_exception def run_instance(self, context, instance_id, **_kwargs): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 209d6e51f..74fd0fdc8 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -235,8 +235,7 @@ def service_get_by_args(context, host, binary): @require_admin_context def service_create(context, values): service_ref = models.Service() - for (key, value) in values.iteritems(): - service_ref[key] = value + service_ref.update(values) service_ref.save() return service_ref @@ -246,8 +245,7 @@ def service_update(context, service_id, values): session = get_session() with session.begin(): service_ref = service_get(context, service_id, session=session) - for (key, value) in values.iteritems(): - service_ref[key] = value + service_ref.update(values) service_ref.save(session=session) @@ -278,8 +276,7 @@ def floating_ip_allocate_address(context, host, project_id): @require_context def floating_ip_create(context, values): floating_ip_ref = models.FloatingIp() - for (key, value) in values.iteritems(): - floating_ip_ref[key] = value + floating_ip_ref.update(values) floating_ip_ref.save() return floating_ip_ref['address'] @@ -450,8 +447,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id): @require_context def fixed_ip_create(_context, values): fixed_ip_ref = models.FixedIp() - for (key, value) in values.iteritems(): - fixed_ip_ref[key] = value + fixed_ip_ref.update(values) fixed_ip_ref.save() return fixed_ip_ref['address'] @@ -520,8 +516,7 @@ def fixed_ip_update(context, address, values): fixed_ip_ref = fixed_ip_get_by_address(context, address, session=session) - for (key, value) in values.iteritems(): - fixed_ip_ref[key] = value + fixed_ip_ref.update(values) fixed_ip_ref.save(session=session) @@ -534,8 +529,7 @@ def fixed_ip_update(context, address, values): @require_context def instance_create(context, values): instance_ref = models.Instance() - for (key, value) in values.iteritems(): - instance_ref[key] = value + instance_ref.update(values) session = get_session() with session.begin(): @@ -727,8 +721,7 @@ def instance_update(context, instance_id, values): session = get_session() with session.begin(): instance_ref = instance_get(context, instance_id, session=session) - for (key, value) in values.iteritems(): - instance_ref[key] = value + instance_ref.update(values) instance_ref.save(session=session) @@ -750,8 +743,7 @@ def instance_add_security_group(context, instance_id, security_group_id): @require_context def key_pair_create(context, values): key_pair_ref = models.KeyPair() - for (key, value) in values.iteritems(): - key_pair_ref[key] = value + key_pair_ref.update(values) key_pair_ref.save() return key_pair_ref @@ -866,8 +858,7 @@ def network_count_reserved_ips(context, network_id): @require_admin_context def network_create_safe(context, values): network_ref = models.Network() - for (key, value) in values.iteritems(): - network_ref[key] = value + network_ref.update(values) try: network_ref.save() return network_ref @@ -976,8 +967,7 @@ def network_update(context, network_id, values): session = get_session() with session.begin(): network_ref = network_get(context, network_id, session=session) - for (key, value) in values.iteritems(): - network_ref[key] = value + network_ref.update(values) network_ref.save(session=session) @@ -1027,8 +1017,7 @@ def export_device_count(context): @require_admin_context def export_device_create_safe(context, values): export_device_ref = models.ExportDevice() - for (key, value) in values.iteritems(): - export_device_ref[key] = value + export_device_ref.update(values) try: export_device_ref.save() return export_device_ref @@ -1054,8 +1043,7 @@ def auth_get_token(_context, token_hash): def auth_create_token(_context, token): tk = models.AuthToken() - for k,v in token.iteritems(): - tk[k] = v + tk.update(token) tk.save() return tk @@ -1081,8 +1069,7 @@ def quota_get(context, project_id, session=None): @require_admin_context def quota_create(context, values): quota_ref = models.Quota() - for (key, value) in values.iteritems(): - quota_ref[key] = value + quota_ref.update(values) quota_ref.save() return quota_ref @@ -1092,8 +1079,7 @@ def quota_update(context, project_id, values): session = get_session() with session.begin(): quota_ref = quota_get(context, project_id, session=session) - for (key, value) in values.iteritems(): - quota_ref[key] = value + quota_ref.update(values) quota_ref.save(session=session) @@ -1141,8 +1127,7 @@ def volume_attached(context, volume_id, instance_id, mountpoint): @require_context def volume_create(context, values): volume_ref = models.Volume() - for (key, value) in values.iteritems(): - volume_ref[key] = value + volume_ref.update(values) session = get_session() with session.begin(): @@ -1298,8 +1283,7 @@ def volume_update(context, volume_id, values): session = get_session() with session.begin(): volume_ref = volume_get(context, volume_id, session=session) - for (key, value) in values.iteritems(): - volume_ref[key] = value + volume_ref.update(values) volume_ref.save(session=session) @@ -1392,8 +1376,7 @@ def security_group_create(context, values): # FIXME(devcamcar): Unless I do this, rules fails with lazy load exception # once save() is called. This will get cleaned up in next orm pass. security_group_ref.rules - for (key, value) in values.iteritems(): - security_group_ref[key] = value + security_group_ref.update(values) security_group_ref.save() return security_group_ref @@ -1446,8 +1429,7 @@ def security_group_rule_get(context, security_group_rule_id, session=None): @require_context def security_group_rule_create(context, values): security_group_rule_ref = models.SecurityGroupIngressRule() - for (key, value) in values.iteritems(): - security_group_rule_ref[key] = value + security_group_rule_ref.update(values) security_group_rule_ref.save() return security_group_rule_ref @@ -1498,8 +1480,7 @@ def user_get_by_access_key(context, access_key, session=None): @require_admin_context def user_create(_context, values): user_ref = models.User() - for (key, value) in values.iteritems(): - user_ref[key] = value + user_ref.update(values) user_ref.save() return user_ref @@ -1527,8 +1508,7 @@ def user_get_all(context): def project_create(_context, values): project_ref = models.Project() - for (key, value) in values.iteritems(): - project_ref[key] = value + project_ref.update(values) project_ref.save() return project_ref @@ -1590,8 +1570,7 @@ def user_update(context, user_id, values): session = get_session() with session.begin(): user_ref = user_get(context, user_id, session=session) - for (key, value) in values.iteritems(): - user_ref[key] = value + user_ref.update(values) user_ref.save(session=session) @@ -1599,8 +1578,7 @@ def project_update(context, project_id, values): session = get_session() with session.begin(): project_ref = project_get(context, project_id, session=session) - for (key, value) in values.iteritems(): - project_ref[key] = value + project_ref.update(values) project_ref.save(session=session) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index a63bca2b0..853c320e4 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -90,6 +90,16 @@ class NovaBase(object): n = self._i.next().name return n, getattr(self, n) + def update(self, values): + """Make the model object behave like a dict""" + for k, v in values.iteritems(): + setattr(self, k, v) + + def iteritems(self): + """Make the model object behave like a dict""" + return iter(self) + + # TODO(vish): Store images in the database instead of file system #class Image(BASE, NovaBase): # """Represents an image in the datastore""" diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 14170fbb2..f12c7b610 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -30,6 +30,7 @@ from nova import exception as exc import nova.api.openstack.auth from nova.image import service from nova.image.services import glance +from nova.tests import fake_flags from nova.wsgi import Router diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d1ee533b6..f4a09fd97 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -92,9 +92,7 @@ class ServersTest(unittest.TestCase): pass def instance_create(context, inst): - class Foo(object): - internal_id = 1 - return Foo() + return {'id': 1, 'internal_id': 1} def fake_method(*args, **kwargs): pass -- cgit From 627a968e79ed21d970225e5ece332d9100abe022 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 25 Oct 2010 23:04:49 -0700 Subject: fix completely broken ServiceTestCase --- nova/tests/service_unittest.py | 125 +++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 55 deletions(-) diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index e74e0f726..a268bc4fe 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -23,8 +23,8 @@ Unit Tests for remote procedure calls using queue import mox from twisted.application.app import startApplication +from twisted.internet import defer -from nova import context from nova import exception from nova import flags from nova import rpc @@ -48,7 +48,7 @@ class ExtendedService(service.Service): return 'service' -class ServiceManagerTestCase(test.BaseTestCase): +class ServiceManagerTestCase(test.TrialTestCase): """Test cases for Services""" def test_attribute_error_for_no_manager(self): @@ -75,13 +75,12 @@ class ServiceManagerTestCase(test.BaseTestCase): self.assertEqual(serv.test_method(), 'service') -class ServiceTestCase(test.BaseTestCase): +class ServiceTestCase(test.TrialTestCase): """Test cases for Services""" def setUp(self): super(ServiceTestCase, self).setUp() self.mox.StubOutWithMock(service, 'db') - self.context = context.get_admin_context() def test_create(self): host = 'foo' @@ -144,87 +143,103 @@ class ServiceTestCase(test.BaseTestCase): # whether it is disconnected, it looks for a variable on itself called # 'model_disconnected' and report_state doesn't really do much so this # these are mostly just for coverage - def test_report_state(self): - host = 'foo' - binary = 'bar' - service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, - host, - binary).AndReturn(service_ref) - service.db.service_update(self.context, service_ref['id'], - mox.ContainsKeyValue('report_count', 1)) - - self.mox.ReplayAll() - s = service.Service() - rv = yield s.report_state(host, binary) - + @defer.inlineCallbacks def test_report_state_no_service(self): host = 'foo' binary = 'bar' + topic = 'test' service_create = {'host': host, 'binary': binary, + 'topic': topic, 'report_count': 0} service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} + 'binary': binary, + 'topic': topic, + 'report_count': 0, + 'id': 1} - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, + service.db.service_get_by_args(mox.IgnoreArg(), host, binary).AndRaise(exception.NotFound()) - service.db.service_create(self.context, + service.db.service_create(mox.IgnoreArg(), service_create).AndReturn(service_ref) - service.db.service_get(self.context, + service.db.service_get(mox.IgnoreArg(), service_ref['id']).AndReturn(service_ref) - service.db.service_update(self.context, service_ref['id'], + service.db.service_update(mox.IgnoreArg(), service_ref['id'], mox.ContainsKeyValue('report_count', 1)) self.mox.ReplayAll() - s = service.Service() - rv = yield s.report_state(host, binary) + serv = service.Service(host, + binary, + topic, + 'nova.tests.service_unittest.FakeManager') + serv.startService() + yield serv.report_state() + @defer.inlineCallbacks def test_report_state_newly_disconnected(self): host = 'foo' binary = 'bar' + topic = 'test' + service_create = {'host': host, + 'binary': binary, + 'topic': topic, + 'report_count': 0} service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} + 'binary': binary, + 'topic': topic, + 'report_count': 0, + 'id': 1} - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, - host, - binary).AndRaise(Exception()) + service.db.service_get_by_args(mox.IgnoreArg(), + host, + binary).AndRaise(exception.NotFound()) + service.db.service_create(mox.IgnoreArg(), + service_create).AndReturn(service_ref) + service.db.service_get(mox.IgnoreArg(), + mox.IgnoreArg()).AndRaise(Exception()) self.mox.ReplayAll() - s = service.Service() - rv = yield s.report_state(host, binary) - - self.assert_(s.model_disconnected) + serv = service.Service(host, + binary, + topic, + 'nova.tests.service_unittest.FakeManager') + serv.startService() + yield serv.report_state() + self.assert_(serv.model_disconnected) + @defer.inlineCallbacks def test_report_state_newly_connected(self): host = 'foo' binary = 'bar' + topic = 'test' + service_create = {'host': host, + 'binary': binary, + 'topic': topic, + 'report_count': 0} service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} + 'binary': binary, + 'topic': topic, + 'report_count': 0, + 'id': 1} - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, - host, - binary).AndReturn(service_ref) - service.db.service_update(self.context, service_ref['id'], + service.db.service_get_by_args(mox.IgnoreArg(), + host, + binary).AndRaise(exception.NotFound()) + service.db.service_create(mox.IgnoreArg(), + service_create).AndReturn(service_ref) + service.db.service_get(mox.IgnoreArg(), + service_ref['id']).AndReturn(service_ref) + service.db.service_update(mox.IgnoreArg(), service_ref['id'], mox.ContainsKeyValue('report_count', 1)) self.mox.ReplayAll() - s = service.Service() - s.model_disconnected = True - rv = yield s.report_state(host, binary) + serv = service.Service(host, + binary, + topic, + 'nova.tests.service_unittest.FakeManager') + serv.startService() + serv.model_disconnected = True + yield serv.report_state() - self.assert_(not s.model_disconnected) + self.assert_(not serv.model_disconnected) -- cgit From ba6d9293204284a7c74b5b0cffe63767941fd25c Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 26 Oct 2010 11:48:20 -0400 Subject: Delete BaseTestCase and with it the last reference to tornado. Requires commenting out some service_unittest tests which were silently failing under BaseTestCase and which now fail under TrialTestCase. vishy says he wrote the code and thinks he knows what is going wrong. --- nova/test.py | 156 ---------------------------------- nova/tests/api_unittest.py | 4 +- nova/tests/service_unittest.py | 184 +++++++++++++++++++++-------------------- run_tests.py | 16 ---- 4 files changed, 96 insertions(+), 264 deletions(-) diff --git a/nova/test.py b/nova/test.py index 8ef7eca1a..5c2a72819 100644 --- a/nova/test.py +++ b/nova/test.py @@ -28,7 +28,6 @@ import time import mox import stubout -from tornado import ioloop from twisted.internet import defer from twisted.trial import unittest @@ -159,158 +158,3 @@ class TrialTestCase(unittest.TestCase): _wrapped.func_name = self.originalAttach.func_name rpc.Consumer.attach_to_twisted = _wrapped - - -class BaseTestCase(TrialTestCase): - # TODO(jaypipes): Can this be moved into the TrialTestCase class? - """Base test case class for all unit tests. - - DEPRECATED: This is being removed once Tornado is gone, use TrialTestCase. - """ - def setUp(self): - """Run before each test method to initialize test environment""" - super(BaseTestCase, self).setUp() - # TODO(termie): we could possibly keep a more global registry of - # the injected listeners... this is fine for now though - self.ioloop = ioloop.IOLoop.instance() - - self._waiting = None - self._done_waiting = False - self._timed_out = False - - def _wait_for_test(self, timeout=60): - """ Push the ioloop along to wait for our test to complete. """ - self._waiting = self.ioloop.add_timeout(time.time() + timeout, - self._timeout) - - def _wait(): - - """Wrapped wait function. Called on timeout.""" - if self._timed_out: - self.fail('test timed out') - self._done() - if self._done_waiting: - self.ioloop.stop() - return - # we can use add_callback here but this uses less cpu when testing - self.ioloop.add_timeout(time.time() + 0.01, _wait) - - self.ioloop.add_callback(_wait) - self.ioloop.start() - - def _done(self): - """Callback used for cleaning up deferred test methods.""" - if self._waiting: - try: - self.ioloop.remove_timeout(self._waiting) - except Exception: # pylint: disable-msg=W0703 - # TODO(jaypipes): This produces a pylint warning. Should - # we really be catching Exception and then passing here? - pass - self._waiting = None - self._done_waiting = True - - def _maybe_inline_callbacks(self, func): - """ If we're doing async calls in our tests, wait on them. - - This is probably the most complicated hunk of code we have so far. - - First up, if the function is normal (not async) we just act normal - and return. - - Async tests will use the "Inline Callbacks" pattern, which means - you yield Deferreds at every "waiting" step of your code instead - of making epic callback chains. - - Example (callback chain, ugly): - - # A deferred instance - d = self.compute.terminate_instance(instance_id) - def _describe(_): - # Another deferred instance - d_desc = self.compute.describe_instances() - return d_desc - def _checkDescribe(rv): - self.assertEqual(rv, []) - d.addCallback(_describe) - d.addCallback(_checkDescribe) - d.addCallback(lambda x: self._done()) - self._wait_for_test() - - Example (inline callbacks! yay!): - - yield self.compute.terminate_instance(instance_id) - rv = yield self.compute.describe_instances() - self.assertEqual(rv, []) - - If the test fits the Inline Callbacks pattern we will automatically - handle calling wait and done. - """ - # TODO(termie): this can be a wrapper function instead and - # and we can make a metaclass so that we don't - # have to copy all that "run" code below. - g = func() - if not hasattr(g, 'send'): - self._done() - return defer.succeed(g) - - inlined = defer.inlineCallbacks(func) - d = inlined() - return d - - def _catch_exceptions(self, result, failure): - """Catches all exceptions and handles keyboard interrupts.""" - exc = (failure.type, failure.value, failure.getTracebackObject()) - if isinstance(failure.value, self.failureException): - result.addFailure(self, exc) - elif isinstance(failure.value, KeyboardInterrupt): - raise - else: - result.addError(self, exc) - - self._done() - - def _timeout(self): - """Helper method which trips the timeouts""" - self._waiting = False - self._timed_out = True - - def run(self, result=None): - """Runs the test case""" - - result.startTest(self) - test_method = getattr(self, self._testMethodName) - try: - try: - self.setUp() - except KeyboardInterrupt: - raise - except: - result.addError(self, sys.exc_info()) - return - - ok = False - try: - d = self._maybe_inline_callbacks(test_method) - d.addErrback(lambda x: self._catch_exceptions(result, x)) - d.addBoth(lambda x: self._done() and x) - self._wait_for_test() - ok = True - except self.failureException: - result.addFailure(self, sys.exc_info()) - except KeyboardInterrupt: - raise - except: - result.addError(self, sys.exc_info()) - - try: - self.tearDown() - except KeyboardInterrupt: - raise - except: - result.addError(self, sys.exc_info()) - ok = False - if ok: - result.addSuccess(self) - finally: - result.stopTest(self) diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py index 0b1c3e353..0a81c575b 100644 --- a/nova/tests/api_unittest.py +++ b/nova/tests/api_unittest.py @@ -83,7 +83,7 @@ class FakeHttplibConnection(object): pass -class XmlConversionTestCase(test.BaseTestCase): +class XmlConversionTestCase(test.TrialTestCase): """Unit test api xml conversion""" def test_number_conversion(self): conv = apirequest._try_convert @@ -100,7 +100,7 @@ class XmlConversionTestCase(test.BaseTestCase): self.assertEqual(conv('-0'), 0) -class ApiEc2TestCase(test.BaseTestCase): +class ApiEc2TestCase(test.TrialTestCase): """Unit test for the cloud controller on an EC2 API""" def setUp(self): super(ApiEc2TestCase, self).setUp() diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index e74e0f726..142c2ebea 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -48,7 +48,7 @@ class ExtendedService(service.Service): return 'service' -class ServiceManagerTestCase(test.BaseTestCase): +class ServiceManagerTestCase(test.TrialTestCase): """Test cases for Services""" def test_attribute_error_for_no_manager(self): @@ -75,7 +75,7 @@ class ServiceManagerTestCase(test.BaseTestCase): self.assertEqual(serv.test_method(), 'service') -class ServiceTestCase(test.BaseTestCase): +class ServiceTestCase(test.TrialTestCase): """Test cases for Services""" def setUp(self): @@ -140,91 +140,95 @@ class ServiceTestCase(test.BaseTestCase): startApplication(app, False) self.assert_(app) - # We're testing sort of weird behavior in how report_state decides - # whether it is disconnected, it looks for a variable on itself called - # 'model_disconnected' and report_state doesn't really do much so this - # these are mostly just for coverage - def test_report_state(self): - host = 'foo' - binary = 'bar' - service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, - host, - binary).AndReturn(service_ref) - service.db.service_update(self.context, service_ref['id'], - mox.ContainsKeyValue('report_count', 1)) - - self.mox.ReplayAll() - s = service.Service() - rv = yield s.report_state(host, binary) - - def test_report_state_no_service(self): - host = 'foo' - binary = 'bar' - service_create = {'host': host, - 'binary': binary, - 'report_count': 0} - service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} - - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, - host, - binary).AndRaise(exception.NotFound()) - service.db.service_create(self.context, - service_create).AndReturn(service_ref) - service.db.service_get(self.context, - service_ref['id']).AndReturn(service_ref) - service.db.service_update(self.context, service_ref['id'], - mox.ContainsKeyValue('report_count', 1)) - - self.mox.ReplayAll() - s = service.Service() - rv = yield s.report_state(host, binary) - - def test_report_state_newly_disconnected(self): - host = 'foo' - binary = 'bar' - service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} - - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, - host, - binary).AndRaise(Exception()) - - self.mox.ReplayAll() - s = service.Service() - rv = yield s.report_state(host, binary) - - self.assert_(s.model_disconnected) - - def test_report_state_newly_connected(self): - host = 'foo' - binary = 'bar' - service_ref = {'host': host, - 'binary': binary, - 'report_count': 0, - 'id': 1} - - service.db.__getattr__('report_state') - service.db.service_get_by_args(self.context, - host, - binary).AndReturn(service_ref) - service.db.service_update(self.context, service_ref['id'], - mox.ContainsKeyValue('report_count', 1)) - - self.mox.ReplayAll() - s = service.Service() - s.model_disconnected = True - rv = yield s.report_state(host, binary) - - self.assert_(not s.model_disconnected) +# TODO(gundlach): These tests were "passing" when this class inherited from +# BaseTestCase. In reality, they were failing, but BaseTestCase was +# swallowing the error. Now that we inherit from TrialTestCase, these tests +# are failing, and need to get fixed. +# # We're testing sort of weird behavior in how report_state decides +# # whether it is disconnected, it looks for a variable on itself called +# # 'model_disconnected' and report_state doesn't really do much so this +# # these are mostly just for coverage +# def test_report_state(self): +# host = 'foo' +# binary = 'bar' +# service_ref = {'host': host, +# 'binary': binary, +# 'report_count': 0, +# 'id': 1} +# service.db.__getattr__('report_state') +# service.db.service_get_by_args(self.context, +# host, +# binary).AndReturn(service_ref) +# service.db.service_update(self.context, service_ref['id'], +# mox.ContainsKeyValue('report_count', 1)) +# +# self.mox.ReplayAll() +# s = service.Service() +# rv = yield s.report_state(host, binary) +# +# def test_report_state_no_service(self): +# host = 'foo' +# binary = 'bar' +# service_create = {'host': host, +# 'binary': binary, +# 'report_count': 0} +# service_ref = {'host': host, +# 'binary': binary, +# 'report_count': 0, +# 'id': 1} +# +# service.db.__getattr__('report_state') +# service.db.service_get_by_args(self.context, +# host, +# binary).AndRaise(exception.NotFound()) +# service.db.service_create(self.context, +# service_create).AndReturn(service_ref) +# service.db.service_get(self.context, +# service_ref['id']).AndReturn(service_ref) +# service.db.service_update(self.context, service_ref['id'], +# mox.ContainsKeyValue('report_count', 1)) +# +# self.mox.ReplayAll() +# s = service.Service() +# rv = yield s.report_state(host, binary) +# +# def test_report_state_newly_disconnected(self): +# host = 'foo' +# binary = 'bar' +# service_ref = {'host': host, +# 'binary': binary, +# 'report_count': 0, +# 'id': 1} +# +# service.db.__getattr__('report_state') +# service.db.service_get_by_args(self.context, +# host, +# binary).AndRaise(Exception()) +# +# self.mox.ReplayAll() +# s = service.Service() +# rv = yield s.report_state(host, binary) +# +# self.assert_(s.model_disconnected) +# +# def test_report_state_newly_connected(self): +# host = 'foo' +# binary = 'bar' +# service_ref = {'host': host, +# 'binary': binary, +# 'report_count': 0, +# 'id': 1} +# +# service.db.__getattr__('report_state') +# service.db.service_get_by_args(self.context, +# host, +# binary).AndReturn(service_ref) +# service.db.service_update(self.context, service_ref['id'], +# mox.ContainsKeyValue('report_count', 1)) +# +# self.mox.ReplayAll() +# s = service.Service() +# s.model_disconnected = True +# rv = yield s.report_state(host, binary) +# +# self.assert_(not s.model_disconnected) diff --git a/run_tests.py b/run_tests.py index 9a2f40dc9..c16c63249 100644 --- a/run_tests.py +++ b/run_tests.py @@ -48,24 +48,8 @@ from twisted.scripts import trial as trial_script from nova import flags from nova import twistd -from nova.tests.access_unittest import * from nova.tests.auth_unittest import * -from nova.tests.api_unittest import * -from nova.tests.cloud_unittest import * -from nova.tests.compute_unittest import * -from nova.tests.flags_unittest import * -from nova.tests.network_unittest import * -from nova.tests.objectstore_unittest import * -from nova.tests.process_unittest import * -from nova.tests.quota_unittest import * -from nova.tests.rpc_unittest import * -from nova.tests.scheduler_unittest import * from nova.tests.service_unittest import * -from nova.tests.twistd_unittest import * -from nova.tests.validator_unittest import * -from nova.tests.virt_unittest import * -from nova.tests.volume_unittest import * -from nova.tests.virt_unittest import * FLAGS = flags.FLAGS -- cgit From f0d79d7d602a31fff03d8d934203128a2cd8940d Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 26 Oct 2010 11:58:46 -0400 Subject: Oops, didn't mean to check this one in. Ninja-patch --- run_tests.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/run_tests.py b/run_tests.py index c16c63249..9a2f40dc9 100644 --- a/run_tests.py +++ b/run_tests.py @@ -48,8 +48,24 @@ from twisted.scripts import trial as trial_script from nova import flags from nova import twistd +from nova.tests.access_unittest import * from nova.tests.auth_unittest import * +from nova.tests.api_unittest import * +from nova.tests.cloud_unittest import * +from nova.tests.compute_unittest import * +from nova.tests.flags_unittest import * +from nova.tests.network_unittest import * +from nova.tests.objectstore_unittest import * +from nova.tests.process_unittest import * +from nova.tests.quota_unittest import * +from nova.tests.rpc_unittest import * +from nova.tests.scheduler_unittest import * from nova.tests.service_unittest import * +from nova.tests.twistd_unittest import * +from nova.tests.validator_unittest import * +from nova.tests.virt_unittest import * +from nova.tests.volume_unittest import * +from nova.tests.virt_unittest import * FLAGS = flags.FLAGS -- cgit From cce61a2d29fac66cdbef74517bf1ab025df33d1f Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Tue, 26 Oct 2010 09:06:37 -0500 Subject: Added Google Analytics code --- doc/source/_templates/layout.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 doc/source/_templates/layout.html diff --git a/doc/source/_templates/layout.html b/doc/source/_templates/layout.html new file mode 100644 index 000000000..0b72a77ac --- /dev/null +++ b/doc/source/_templates/layout.html @@ -0,0 +1,17 @@ +{% extends "!layout.html" %} + +{% block footer %} +{{ super() }} + + +{% endblock %} + -- cgit From d8d12549a5e47c7c44f449f12d6b556e2c56483d Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 26 Oct 2010 15:37:32 -0700 Subject: More PEP8 fixes that were introduced in the last couple commits. --- nova/tests/api/openstack/test_api.py | 6 ++++++ nova/utils.py | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py index a8c0ff9f8..dd83991b9 100644 --- a/nova/tests/api/openstack/test_api.py +++ b/nova/tests/api/openstack/test_api.py @@ -24,22 +24,28 @@ from nova.api.openstack import API from nova.api.openstack import faults from webob import Request + class APITest(unittest.TestCase): def test_exceptions_are_converted_to_faults(self): + @webob.dec.wsgify def succeed(req): return 'Succeeded' + @webob.dec.wsgify def raise_webob_exc(req): raise webob.exc.HTTPNotFound(explanation='Raised a webob.exc') + @webob.dec.wsgify def fail(req): raise Exception("Threw an exception") + @webob.dec.wsgify def raise_api_fault(req): exc = webob.exc.HTTPNotFound(explanation='Raised a webob.exc') return faults.Fault(exc) + api = API() api.application = succeed diff --git a/nova/utils.py b/nova/utils.py index 2c53b027e..bc495a691 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -213,10 +213,10 @@ def deferredToThread(f): def xhtml_escape(value): """Escapes a string so it is valid within XML or XHTML. - + Code is directly from the utf8 function in http://github.com/facebook/tornado/blob/master/tornado/escape.py - + """ return saxutils.escape(value, {'"': """}) @@ -232,4 +232,3 @@ def utf8(value): return value.encode("utf-8") assert isinstance(value, str) return value - -- cgit From 79acdcca7d37e81d626be7a3369394ef9dface1b Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Wed, 27 Oct 2010 11:10:50 -0400 Subject: Style cleanups and review from Eric. --- nova/api/ec2/cloud.py | 23 ++++++++++++----------- nova/compute/manager.py | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 51e972aa7..9084958a1 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -836,29 +836,30 @@ class CloudController(object): elevated = context.elevated() for num in range(num_instances): - + instance_data = base_options - instance_data['mac_address'] = utils.generate_mac() - instance_data['launch_index'] = num instance_ref = self.compute_manager.create_instance(context, - instance_data, - security_groups) + instance_data, + security_groups, + mac_address=utils.generate_mac(), + launch_index=num) + inst_id = instance_ref['id'] internal_id = instance_ref['internal_id'] ec2_id = internal_id_to_ec2_id(internal_id) - instance_ref['hostname'] = ec2_id self.compute_manager.update_instance(context, - instance_ref['id'], - instance_ref) + inst_id, + instance_ref, + hostname=ec2_id) # TODO(vish): This probably should be done in the scheduler # or in compute as a call. The network should be # allocated after the host is assigned and setup # can happen at the same time. address = self.network_manager.allocate_fixed_ip(context, - instance_ref['id'], + inst_id, vpn) network_topic = self._get_network_topic(context) rpc.cast(elevated, @@ -870,9 +871,9 @@ class CloudController(object): FLAGS.scheduler_topic, {"method": "run_instance", "args": {"topic": FLAGS.compute_topic, - "instance_id": instance_ref['id']}}) + "instance_id": inst_id}}) logging.debug("Casting to scheduler for %s/%s's instance %s" % - (context.project.name, context.user.name, instance_ref['id'])) + (context.project.name, context.user.name, inst_id)) return self._format_run_instances(context, reservation_id) def terminate_instances(self, context, instance_id, **kwargs): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d99d938af..c04dd213a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -69,8 +69,8 @@ class ComputeManager(manager.Manager): def refresh_security_group(self, context, security_group_id, **_kwargs): yield self.driver.refresh_security_group(security_group_id) - - def create_instance(self, context, instance_data, security_groups=[]): + def create_instance(self, context, instance_data, security_groups=[], + **kwargs): """Creates the instance in the datastore and returns the new instance as a mapping @@ -78,11 +78,15 @@ class ComputeManager(manager.Manager): :param instance_data: mapping of instance options :param security_groups: list of security group ids to attach to the instance + :param **kwargs: All additional keyword args are treated + as data fields of the instance to be + created :retval Returns a mapping of the instance information that has just been created """ + instance_data.update(kwargs) instance_ref = self.db.instance_create(context, instance_data) inst_id = instance_ref['id'] @@ -93,15 +97,20 @@ class ComputeManager(manager.Manager): security_group_id) return instance_ref - def update_instance(self, context, instance_id, instance_data): + def update_instance(self, context, instance_id, instance_data, + **kwargs): """Updates the instance in the datastore :param context: The security context :param instance_data: mapping of instance options + :param **kwargs: All additional keyword args are treated + as data fields of the instance to be + updated :retval None """ + instance_data.update(kwargs) self.db.instance_update(context, instance_id, instance_data) @defer.inlineCallbacks -- cgit From 213b9987365c4b336b63e08e1ca187a43d00fa3d Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Wed, 27 Oct 2010 14:55:01 -0400 Subject: OK, let's try this one more time. --- nova/api/ec2/cloud.py | 13 ++++++------- nova/api/openstack/servers.py | 4 ++-- nova/compute/manager.py | 16 ++++++---------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9084958a1..7b6144ba5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -837,13 +837,11 @@ class CloudController(object): for num in range(num_instances): - instance_data = base_options - instance_ref = self.compute_manager.create_instance(context, - instance_data, security_groups, mac_address=utils.generate_mac(), - launch_index=num) + launch_index=num, + **base_options) inst_id = instance_ref['id'] internal_id = instance_ref['internal_id'] @@ -851,7 +849,6 @@ class CloudController(object): self.compute_manager.update_instance(context, inst_id, - instance_ref, hostname=ec2_id) # TODO(vish): This probably should be done in the scheduler @@ -903,8 +900,10 @@ class CloudController(object): 'state': 0, 'terminated_at': now} self.compute_manager.update_instance(context, - instance_ref['id'], - updated_data) + instance_ref['id'], + state_description='terminating', + state=0, + terminated_at=now) # FIXME(ja): where should network deallocate occur? address = db.instance_get_floating_address(context, diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e1a254d4e..1d8aa2fa4 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -246,11 +246,11 @@ class Controller(wsgi.Controller): inst['mac_address'] = utils.generate_mac() inst['launch_index'] = 0 - ref = self.compute_manager.create_instance(ctxt, inst) + ref = self.compute_manager.create_instance(ctxt, **inst) inst['id'] = ref['internal_id'] inst['hostname'] = str(ref['internal_id']) - self.compute_manager.update_instance(ctxt, inst['id'], inst) + self.compute_manager.update_instance(ctxt, inst['id'], **inst) address = self.network_manager.allocate_fixed_ip(ctxt, inst['id']) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c04dd213a..7cdd6b110 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -69,13 +69,11 @@ class ComputeManager(manager.Manager): def refresh_security_group(self, context, security_group_id, **_kwargs): yield self.driver.refresh_security_group(security_group_id) - def create_instance(self, context, instance_data, security_groups=[], - **kwargs): + def create_instance(self, context, security_groups=[], **kwargs): """Creates the instance in the datastore and returns the new instance as a mapping :param context: The security context - :param instance_data: mapping of instance options :param security_groups: list of security group ids to attach to the instance :param **kwargs: All additional keyword args are treated @@ -86,23 +84,22 @@ class ComputeManager(manager.Manager): that has just been created """ - instance_data.update(kwargs) - instance_ref = self.db.instance_create(context, instance_data) + instance_ref = self.db.instance_create(context, kwargs) inst_id = instance_ref['id'] elevated = context.elevated() + security_groups = kwargs.get('security_groups', []) for security_group_id in security_groups: self.db.instance_add_security_group(elevated, inst_id, security_group_id) return instance_ref - def update_instance(self, context, instance_id, instance_data, - **kwargs): + def update_instance(self, context, instance_id, **kwargs): """Updates the instance in the datastore :param context: The security context - :param instance_data: mapping of instance options + :param instance_id: ID of the instance to update :param **kwargs: All additional keyword args are treated as data fields of the instance to be updated @@ -110,8 +107,7 @@ class ComputeManager(manager.Manager): :retval None """ - instance_data.update(kwargs) - self.db.instance_update(context, instance_id, instance_data) + self.db.instance_update(context, instance_id, kwargs) @defer.inlineCallbacks @exception.wrap_exception -- cgit From 4012860b57593632d1f0061099e0d211dba58a59 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Thu, 28 Oct 2010 11:43:08 -0400 Subject: Remove unused updated_data variable --- nova/api/ec2/cloud.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7b6144ba5..f2a6dc3b0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -896,9 +896,6 @@ class CloudController(object): id_str) continue now = datetime.datetime.utcnow() - updated_data = {'state_description': 'terminating', - 'state': 0, - 'terminated_at': now} self.compute_manager.update_instance(context, instance_ref['id'], state_description='terminating', -- cgit From f42967bdc7029e5c892811d84c7dfeb39a9b9f47 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Fri, 29 Oct 2010 11:53:09 -0500 Subject: Updated location of layout.html and change conf.py to use a build variable. --- doc/source/_ga/layout.html | 17 +++++++++++++++++ doc/source/_templates/.DS_Store | Bin 0 -> 6148 bytes doc/source/_templates/layout.html | 17 ----------------- 3 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 doc/source/_ga/layout.html create mode 100644 doc/source/_templates/.DS_Store delete mode 100644 doc/source/_templates/layout.html diff --git a/doc/source/_ga/layout.html b/doc/source/_ga/layout.html new file mode 100644 index 000000000..0b72a77ac --- /dev/null +++ b/doc/source/_ga/layout.html @@ -0,0 +1,17 @@ +{% extends "!layout.html" %} + +{% block footer %} +{{ super() }} + + +{% endblock %} + diff --git a/doc/source/_templates/.DS_Store b/doc/source/_templates/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/doc/source/_templates/.DS_Store differ diff --git a/doc/source/_templates/layout.html b/doc/source/_templates/layout.html deleted file mode 100644 index 0b72a77ac..000000000 --- a/doc/source/_templates/layout.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "!layout.html" %} - -{% block footer %} -{{ super() }} - - -{% endblock %} - -- cgit From 75a0182e9a9a3cb6732e68eb3c9965b8033e1ce1 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Fri, 29 Oct 2010 12:27:30 -0500 Subject: Changes to conf.py --- doc/source/conf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 505771ff8..e137e728a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -27,7 +27,13 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', todo_include_todos = True # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +# Changing the path so that the Hudson build output contains GA code and the source +# docs do not contain the code so local, offline sphinx builds are "clean." +templates_path = [] +if os.getenv('HUDSON_PUBLISH_DOCS'): + templates_path = ['_ga', '_templates'] +else: + templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' -- cgit From 2e2dce7ebf478258f67a9122c6b158ba5e89c1ed Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 2 Nov 2010 11:28:14 -0700 Subject: Added support for OpenStack and EC2 APIs to run on different ports. --- bin/nova-api | 9 +++++++-- nova/api/__init__.py | 34 +++++++++++++++------------------- nova/wsgi.py | 25 +++++++++++++++++++++---- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 20f1bd74f..a9002ae2d 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -37,13 +37,18 @@ from nova import utils from nova import server FLAGS = flags.FLAGS -flags.DEFINE_integer('api_port', 8773, 'API port') +flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port') +flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port') def main(_args): from nova import api from nova import wsgi - wsgi.run_server(api.API(), FLAGS.api_port) + server = wsgi.Server() + server.start(api.API('os'), FLAGS.osapi_port) + server.start(api.API('ec2'), FLAGS.ec2api_port) + server.wait() + if __name__ == '__main__': utils.default_flagfile() diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 8a1d9fe32..707c1623e 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -35,37 +35,31 @@ flags.DEFINE_string('osapi_subdomain', 'api', 'subdomain running the OpenStack API') flags.DEFINE_string('ec2api_subdomain', 'ec2', 'subdomain running the EC2 API') -flags.DEFINE_string('FAKE_subdomain', None, - 'set to api or ec2 to fake the subdomain of the host ' - 'for testing') FLAGS = flags.FLAGS class API(wsgi.Router): """Routes top-level requests to the appropriate controller.""" - def __init__(self): - osapidomain = {'sub_domain': [FLAGS.osapi_subdomain]} - ec2domain = {'sub_domain': [FLAGS.ec2api_subdomain]} - # If someone wants to pretend they're hitting the OSAPI subdomain - # on their local box, they can set FAKE_subdomain to 'api', which - # removes subdomain restrictions from the OpenStack API routes below. - if FLAGS.FAKE_subdomain == 'api': - osapidomain = {} - elif FLAGS.FAKE_subdomain == 'ec2': - ec2domain = {} + 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=osapidomain) + conditions=osapi_subdomain) mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), - conditions=osapidomain) + conditions=osapi_subdomain) mapper.connect("/", controller=self.ec2api_versions, - conditions=ec2domain) + conditions=ec2api_subdomain) mapper.connect("/services/{path_info:.*}", controller=ec2.API(), - conditions=ec2domain) - mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API()) + conditions=ec2api_subdomain) mrh = metadatarequesthandler.MetadataRequestHandler() for s in ['/latest', '/2009-04-04', @@ -78,7 +72,9 @@ class API(wsgi.Router): '/2007-01-19', '/1.0']: mapper.connect('%s/{path_info:.*}' % s, controller=mrh, - conditions=ec2domain) + conditions=ec2api_subdomain) + + mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API()) super(API, self).__init__(mapper) @webob.dec.wsgify diff --git a/nova/wsgi.py b/nova/wsgi.py index eb305a3d3..b04b487ea 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -39,10 +39,27 @@ import webob.exc logging.getLogger("routes.middleware").addHandler(logging.StreamHandler()) -def run_server(application, port): - """Run a WSGI server with the given application.""" - sock = eventlet.listen(('0.0.0.0', port)) - eventlet.wsgi.server(sock, application) +class Server(object): + """Server class to manage multiple WSGI sockets and applications.""" + + def __init__(self, threads=1000): + self.pool = eventlet.GreenPool(threads) + + def start(self, application, port, host='0.0.0.0', backlog=128): + """Run a WSGI server with the given application.""" + socket = eventlet.listen((host, port), backlog=backlog) + self.pool.spawn_n(self._run, application, socket) + + def wait(self): + """Wait until all servers have completed running.""" + try: + self.pool.waitall() + except KeyboardInterrupt: + pass + + def _run(self, application, socket): + """Start a WSGI server in a new green thread.""" + eventlet.wsgi.server(socket, application, custom_pool=self.pool) class Application(object): -- cgit From 785d60c9492a8d4583eb27b214abefda6c1fbcfc Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 2 Nov 2010 12:02:42 -0700 Subject: Fixed tests to work with new default API argument. --- nova/tests/api/__init__.py | 2 +- nova/tests/api/openstack/fakes.py | 4 ---- nova/tests/api/openstack/test_auth.py | 18 +++++++++--------- nova/tests/api/openstack/test_flavors.py | 2 +- nova/tests/api/openstack/test_images.py | 4 ++-- nova/tests/api/openstack/test_servers.py | 28 ++++++++++++++-------------- nova/tests/api_unittest.py | 6 +----- 7 files changed, 28 insertions(+), 36 deletions(-) diff --git a/nova/tests/api/__init__.py b/nova/tests/api/__init__.py index 46f09e906..9caa8c9d0 100644 --- a/nova/tests/api/__init__.py +++ b/nova/tests/api/__init__.py @@ -42,7 +42,7 @@ class Test(unittest.TestCase): 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()) + return req.get_response(api.API('ec2')) def test_openstack(self): self.stubs.Set(api.openstack, 'API', APIStub) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 1b8c18974..52b392601 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -34,9 +34,6 @@ from nova.tests import fake_flags from nova.wsgi import Router -FLAGS = flags.FLAGS - - class Context(object): pass @@ -108,7 +105,6 @@ def stub_out_networking(stubs): def get_my_ip(): return '127.0.0.1' stubs.Set(nova.utils, 'get_my_ip', get_my_ip) - FLAGS.FAKE_subdomain = 'api' def stub_out_glance(stubs, initial_fixtures=[]): diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index b63da187f..29f4b8874 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -51,7 +51,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()) + result = req.get_response(nova.api.API('os')) self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) self.assertEqual(result.headers['X-CDN-Management-Url'], @@ -65,7 +65,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()) + result = req.get_response(nova.api.API('os')) self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) self.assertEqual(result.headers['X-Server-Management-Url'], @@ -79,7 +79,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()) + result = req.get_response(nova.api.API('os')) self.assertEqual(result.status, '200 OK') self.assertEqual(result.headers['X-Test-Success'], 'True') @@ -103,7 +103,7 @@ class Test(unittest.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-Token'] = 'bacon' - result = req.get_response(nova.api.API()) + result = req.get_response(nova.api.API('os')) self.assertEqual(result.status, '401 Unauthorized') self.assertEqual(self.destroy_called, True) @@ -111,18 +111,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()) + result = req.get_response(nova.api.API('os')) self.assertEqual(result.status, '401 Unauthorized') def test_no_user(self): req = webob.Request.blank('/v1.0/') - result = req.get_response(nova.api.API()) + result = req.get_response(nova.api.API('os')) 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()) + result = req.get_response(nova.api.API('os')) self.assertEqual(result.status, '401 Unauthorized') @@ -146,7 +146,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()) + result = req.get_response(nova.api.API('os')) self.assertEqual(len(result.headers['X-Auth-Token']), 40) token = result.headers['X-Auth-Token'] @@ -155,7 +155,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()) + result = req.get_response(nova.api.API('os')) self.assertEqual(result.status, '200 OK') self.assertEqual(result.headers['X-Test-Success'], 'True') diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 8dd4d1f29..41018afdf 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -39,7 +39,7 @@ class FlavorsTest(unittest.TestCase): def test_get_flavor_list(self): req = webob.Request.blank('/v1.0/flavors') - res = req.get_response(nova.api.API()) + res = req.get_response(nova.api.API('os')) def test_get_flavor_by_id(self): pass diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index d61c3a99b..0f3941c29 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -203,7 +203,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()) + res = req.get_response(nova.api.API('os')) res_dict = json.loads(res.body) fixture_index = [dict(id=f['id'], name=f['name']) for f @@ -215,7 +215,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()) + res = req.get_response(nova.api.API('os')) res_dict = json.loads(res.body) for image in res_dict['images']: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 785fb6f3a..8cfc6c45a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -69,14 +69,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()) + res = req.get_response(nova.api.API('os')) 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()) + res = req.get_response(nova.api.API('os')) res_dict = json.loads(res.body) i = 0 @@ -119,14 +119,14 @@ class ServersTest(unittest.TestCase): req.method = 'POST' req.body = json.dumps(body) - res = req.get_response(nova.api.API()) + res = req.get_response(nova.api.API('os')) 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()) + res = req.get_response(nova.api.API('os')) self.assertEqual(res.status_int, 422) def test_update_bad_params(self): @@ -145,7 +145,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()) + req.get_response(nova.api.API('os')) def test_update_server(self): inst_dict = dict(name='server_test', adminPass='bacon') @@ -161,28 +161,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()) + req.get_response(nova.api.API('os')) 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()) + res = req.get_response(nova.api.API('os')) 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()) + res = req.get_response(nova.api.API('os')) 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()) + res = req.get_response(nova.api.API('os')) 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()) + res = req.get_response(nova.api.API('os')) res_dict = json.loads(res.body) i = 0 @@ -200,7 +200,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()) + res = req.get_response(nova.api.API('os')) def test_server_rebuild(self): body = dict(server=dict( @@ -210,7 +210,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()) + res = req.get_response(nova.api.API('os')) def test_server_resize(self): body = dict(server=dict( @@ -220,7 +220,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()) + res = req.get_response(nova.api.API('os')) def test_delete_server_instance(self): req = webob.Request.blank('/v1.0/servers/1') @@ -234,7 +234,7 @@ class ServersTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_destroy', instance_destroy_mock) - res = req.get_response(nova.api.API()) + res = req.get_response(nova.api.API('os')) self.assertEqual(res.status, '202 Accepted') self.assertEqual(self.server_delete_called, True) diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py index 0a81c575b..33d4cb294 100644 --- a/nova/tests/api_unittest.py +++ b/nova/tests/api_unittest.py @@ -34,10 +34,6 @@ from nova.api.ec2 import apirequest from nova.auth import manager -FLAGS = flags.FLAGS -FLAGS.FAKE_subdomain = 'ec2' - - class FakeHttplibSocket(object): """a fake socket implementation for httplib.HTTPResponse, trivial""" def __init__(self, response_string): @@ -109,7 +105,7 @@ class ApiEc2TestCase(test.TrialTestCase): self.host = '127.0.0.1' - self.app = api.API() + self.app = api.API('ec2') def expect_http(self, host=None, is_secure=False): """Returns a new EC2 connection""" -- cgit