From 934e8ab289cd906a94170f0d53cc49edf2db0d6f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 19:32:17 -0400 Subject: adding new query parameters --- nova/api/openstack/images.py | 13 ++- nova/tests/api/openstack/test_images.py | 142 +++++++++++++++++++++++--------- 2 files changed, 115 insertions(+), 40 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..b0364b403 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -35,7 +35,13 @@ from nova.api.openstack import wsgi LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -SUPPORTED_FILTERS = ['name', 'status'] +SUPPORTED_FILTERS = { + 'name': 'name', + 'status': 'status', + 'changes-since': 'changes-since', + 'server': 'property-instance_ref', + 'type': 'property-image_type', +} class Controller(object): @@ -62,8 +68,9 @@ class Controller(object): filters = {} for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): - filters[param] = req.str_params.get(param) - + # map filter name or carry through if property-* + _param = SUPPORTED_FILTERS.get(param, param) + filters[_param] = req.str_params.get(param) return filters def show(self, req, id): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index c1bdd6906..8d73f6684 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -801,11 +801,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'name': 'testname'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.index(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?name=testname') + request = webob.Request.blank('/v1.1/images?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -816,11 +814,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.index(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?status=ACTIVE') + request = webob.Request.blank('/v1.1/images?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -831,11 +827,51 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'property-test': '3'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.index(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?property-test=3') + request = webob.Request.blank('/v1.1/images?property-test=3') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_filter_server(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + # 'server' should be converted to 'property-instance_ref' + filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images?server=' + 'http://localhost:8774/servers/12') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_filter_changes_since(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'changes-since': '2011-01-24T17:08Z'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images?changes-since=' + '2011-01-24T17:08Z') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_filter_with_type(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'property-image_type': 'BASE'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -846,14 +882,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.index( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images?status=ACTIVE&UNSUPPORTEDFILTER=testname') + request = webob.Request.blank('/v1.1/images?status=ACTIVE&' + 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) - controller.index(request) + controller.detail(request) mocker.VerifyAll() def test_image_no_filters(self): @@ -876,11 +911,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'name': 'testname'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?name=testname') + request = webob.Request.blank('/v1.1/images/detail?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -891,11 +924,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?status=ACTIVE') + request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -906,26 +937,65 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {'property-test': '3'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?property-test=3') + request = webob.Request.blank('/v1.1/images/detail?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) mocker.VerifyAll() + def test_image_detail_filter_server(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + # 'server' should be converted to 'property-instance_ref' + filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images/detail?server=' + 'http://localhost:8774/servers/12') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_detail_filter_changes_since(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'changes-since': '2011-01-24T17:08Z'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images/detail?changes-since=' + '2011-01-24T17:08Z') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + + def test_image_detail_filter_with_type(self): + mocker = mox.Mox() + image_service = mocker.CreateMockAnything() + context = object() + filters = {'property-image_type': 'BASE'} + image_service.index(context, filters=filters).AndReturn([]) + mocker.ReplayAll() + request = webob.Request.blank('/v1.1/images/detail?type=BASE') + request.environ['nova.context'] = context + controller = images.ControllerV11(image_service=image_service) + controller.index(request) + mocker.VerifyAll() + def test_image_detail_filter_not_supported(self): mocker = mox.Mox() image_service = mocker.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail?status=ACTIVE&UNSUPPORTEDFILTER=testname') + request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE&' + 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -936,11 +1006,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service = mocker.CreateMockAnything() context = object() filters = {} - image_service.detail( - context, filters=filters).AndReturn([]) + image_service.detail(context, filters=filters).AndReturn([]) mocker.ReplayAll() - request = webob.Request.blank( - '/v1.1/images/detail') + request = webob.Request.blank('/v1.1/images/detail') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) -- cgit From 10ef948271130db0dcc53b0ea94c3a47313244f4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Jul 2011 09:29:27 -0400 Subject: removing mox object instantiation from each test; renaming _param to filter_name --- nova/api/openstack/images.py | 4 +- nova/tests/api/openstack/test_images.py | 112 ++++++++++++++------------------ 2 files changed, 50 insertions(+), 66 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index b0364b403..f82b98a38 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -69,8 +69,8 @@ class Controller(object): for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): # map filter name or carry through if property-* - _param = SUPPORTED_FILTERS.get(param, param) - filters[_param] = req.str_params.get(param) + filter_name = SUPPORTED_FILTERS.get(param, param) + filters[filter_name] = req.str_params.get(param) return filters def show(self, req, id): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 8d73f6684..2eb4896f0 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -797,222 +797,206 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertDictListMatch(expected, response_list) def test_image_filter_with_name(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'name': 'testname'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_with_status(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_with_property(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-test': '3'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_server(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?server=' 'http://localhost:8774/servers/12') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_changes_since(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?changes-since=' '2011-01-24T17:08Z') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_with_type(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_filter_not_supported(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images?status=ACTIVE&' 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_no_filters(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {} image_service.index( context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank( '/v1.1/images') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_name(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'name': 'testname'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_status(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_property(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-test': '3'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_server(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?server=' 'http://localhost:8774/servers/12') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_changes_since(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?changes-since=' '2011-01-24T17:08Z') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_with_type(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_filter_not_supported(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE&' 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_image_detail_no_filters(self): - mocker = mox.Mox() - image_service = mocker.CreateMockAnything() + image_service = self.mox.CreateMockAnything() context = object() filters = {} image_service.detail(context, filters=filters).AndReturn([]) - mocker.ReplayAll() + self.mox.ReplayAll() request = webob.Request.blank('/v1.1/images/detail') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) - mocker.VerifyAll() + self.mox.VerifyAll() def test_get_image_found(self): req = webob.Request.blank('/v1.0/images/123') -- cgit From 4c779a87651a37f9acf05f1101859a1ce4c288c1 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Thu, 14 Jul 2011 17:49:29 -0500 Subject: First pass --- nova/compute/api.py | 15 +++-- nova/compute/manager.py | 74 ++++++++++++---------- nova/db/sqlalchemy/api.py | 12 ++-- .../032_change_instance_id_in_migrations.py | 43 +++++++++++++ nova/db/sqlalchemy/models.py | 3 +- nova/tests/test_compute.py | 24 +++---- 6 files changed, 114 insertions(+), 57 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py diff --git a/nova/compute/api.py b/nova/compute/api.py index 432658bbb..7857f06a5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -822,6 +822,7 @@ class API(base.Base): instance_id, params=rebuild_params) + @scheduler_api.reroute_compute("revert_resize") def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process.""" context = context.elevated() @@ -832,11 +833,12 @@ class API(base.Base): status='finished') params = {'migration_id': migration_ref['id']} - self._cast_compute_message('revert_resize', context, instance_id, + self._cast_compute_message('revert_resize', context, migration_ref['dest_compute'], params=params) self.db.migration_update(context, migration_ref['id'], {'status': 'reverted'}) + @scheduler_api.reroute_compute("confirm_resize") def confirm_resize(self, context, instance_id): """Confirms a migration/resize and deletes the 'old' instance.""" context = context.elevated() @@ -845,9 +847,8 @@ class API(base.Base): if not migration_ref: raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') - instance_ref = self.db.instance_get(context, instance_id) params = {'migration_id': migration_ref['id']} - self._cast_compute_message('confirm_resize', context, instance_id, + self._cast_compute_message('confirm_resize', context, migration_ref['source_compute'], params=params) self.db.migration_update(context, migration_ref['id'], @@ -855,6 +856,7 @@ class API(base.Base): self.db.instance_update(context, instance_id, {'host': migration_ref['dest_compute'], }) + @scheduler_api.reroute_compute("resize") def resize(self, context, instance_id, flavor_id=None): """Resize (ie, migrate) a running instance. @@ -862,8 +864,8 @@ class API(base.Base): the original flavor_id. If flavor_id is not None, the instance should be migrated to a new host and resized to the new flavor_id. """ - instance = self.db.instance_get(context, instance_id) - current_instance_type = instance['instance_type'] + instance_ref = self._get_instance(context, instance_id, 'resize') + current_instance_type = instance_ref['instance_type'] # If flavor_id is not provided, only migrate the instance. if not flavor_id: @@ -891,10 +893,11 @@ class API(base.Base): raise exception.ApiError(_("Invalid flavor: cannot use" "the same flavor. ")) + instance_ref = self._get_instance(context, instance_id, 'resize') self._cast_scheduler_message(context, {"method": "prep_resize", "args": {"topic": FLAGS.compute_topic, - "instance_id": instance_id, + "instance_uuid": instance_ref['uuid'], "flavor_id": new_instance_type['id']}}) @scheduler_api.reroute_compute("add_fixed_ip") diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c627d2985..e00e3d0ab 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -664,10 +664,12 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def confirm_resize(self, context, instance_id, migration_id): + def confirm_resize(self, context, migration_id): """Destroys the source instance.""" - context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) + migration_ref = self.db.migration_get(context, migration_id) + instance_ref = self.db.instance_get(context, + migration_ref.instance_uuid) + self.driver.destroy(instance_ref) usage_info = utils.usage_from_instance(instance_ref) notifier_api.notify('compute.%s' % self.host, @@ -677,43 +679,44 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def revert_resize(self, context, instance_id, migration_id): + def revert_resize(self, context, migration_id): """Destroys the new instance on the destination machine. Reverts the model changes, and powers on the old instance on the source machine. """ - instance_ref = self.db.instance_get(context, instance_id) migration_ref = self.db.migration_get(context, migration_id) + instance_ref = self.db.instance_get(context, + migration_ref.instance_uuid) self.driver.destroy(instance_ref) topic = self.db.queue_get_for(context, FLAGS.compute_topic, instance_ref['host']) rpc.cast(context, topic, {'method': 'finish_revert_resize', - 'args': { - 'migration_id': migration_ref['id'], - 'instance_id': instance_id, }, + 'args': {'migration_id': migration_ref['id']}, }) @exception.wrap_exception @checks_instance_lock - def finish_revert_resize(self, context, instance_id, migration_id): + def finish_revert_resize(self, context, migration_id): """Finishes the second half of reverting a resize. Power back on the source instance and revert the resized attributes in the database. """ - instance_ref = self.db.instance_get(context, instance_id) migration_ref = self.db.migration_get(context, migration_id) + instance_ref = self.db.instance_get(context, + migration_ref.instance_uuid) + instance_type = self.db.instance_type_get_by_flavor_id(context, migration_ref['old_flavor_id']) # Just roll back the record. There's no need to resize down since # the 'old' VM already has the preferred attributes - self.db.instance_update(context, instance_id, + self.db.instance_update(context, instance_ref.instance_uuid, dict(memory_mb=instance_type['memory_mb'], vcpus=instance_type['vcpus'], local_gb=instance_type['local_gb'])) @@ -729,14 +732,14 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def prep_resize(self, context, instance_id, flavor_id): + def prep_resize(self, context, instance_uuid, flavor_id): """Initiates the process of moving a running instance to another host. Possibly changes the RAM and disk size in the process. """ context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) + instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) if instance_ref['host'] == FLAGS.host: raise exception.Error(_( 'Migration error: destination same as source!')) @@ -744,7 +747,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance_type = self.db.instance_type_get_by_flavor_id(context, flavor_id) migration_ref = self.db.migration_create(context, - {'instance_id': instance_id, + {'instance_uuid': instance_uuid, 'source_compute': instance_ref['host'], 'dest_compute': FLAGS.host, 'dest_host': self.driver.get_host_ip_addr(), @@ -752,15 +755,13 @@ class ComputeManager(manager.SchedulerDependentManager): 'new_flavor_id': flavor_id, 'status': 'pre-migrating'}) - LOG.audit(_('instance %s: migrating to '), instance_id, + LOG.audit(_('instance %s: migrating to '), instance_uuid, context=context) topic = self.db.queue_get_for(context, FLAGS.compute_topic, instance_ref['host']) rpc.cast(context, topic, {'method': 'resize_instance', - 'args': { - 'migration_id': migration_ref['id'], - 'instance_id': instance_id, }, + 'args': {'migration_id': migration_ref['id']}, }) usage_info = utils.usage_from_instance(instance_ref, new_instance_type=instance_type['name'], @@ -772,10 +773,12 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def resize_instance(self, context, instance_id, migration_id): + def resize_instance(self, context, migration_id): """Starts the migration of a running instance to another host.""" migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, instance_id) + instance_ref = self.db.instance_get_by_uuid(context, + migration_ref.instance_uuid) + self.db.migration_update(context, migration_id, {'status': 'migrating'}) @@ -791,14 +794,14 @@ class ComputeManager(manager.SchedulerDependentManager): topic = self.db.queue_get_for(context, FLAGS.compute_topic, migration_ref['dest_compute']) + params = {'migration_id': migration_id, + 'disk_info': disk_info} rpc.cast(context, topic, {'method': 'finish_resize', - 'args': {'migration_id': migration_id, - 'instance_id': instance_id, - 'disk_info': disk_info}}) + 'args': params}) @exception.wrap_exception @checks_instance_lock - def finish_resize(self, context, instance_id, migration_id, disk_info): + def finish_resize(self, context, migration_id, disk_info): """Completes the migration process. Sets up the newly transferred disk and turns on the instance at its @@ -806,24 +809,21 @@ class ComputeManager(manager.SchedulerDependentManager): """ migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, - migration_ref['instance_id']) - # TODO(mdietz): apply the rest of the instance_type attributes going - # after they're supported + instance_ref = self.db.instance_get_by_uuid(context, + migration_ref.instance_uuid) instance_type = self.db.instance_type_get_by_flavor_id(context, migration_ref['new_flavor_id']) - self.db.instance_update(context, instance_id, + self.db.instance_update(context, instance_ref.uuid, dict(instance_type_id=instance_type['id'], memory_mb=instance_type['memory_mb'], vcpus=instance_type['vcpus'], local_gb=instance_type['local_gb'])) - # reload the updated instance ref - # FIXME(mdietz): is there reload functionality? - instance = self.db.instance_get(context, instance_id) + instance_ref = self.db.instance_get_by_uuid(context, + instance_ref.uuid) network_info = self.network_api.get_instance_nw_info(context, - instance) - self.driver.finish_resize(instance, disk_info, network_info) + instance_ref) + self.driver.finish_resize(instance_ref, disk_info, network_info) self.db.migration_update(context, migration_id, {'status': 'finished', }) @@ -955,7 +955,11 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() LOG.debug(_('instance %s: getting locked state'), instance_id, context=context) - instance_ref = self.db.instance_get(context, instance_id) + if utils.is_uuid_like(instance_id): + uuid = instance_id + instance_ref = self.db.instance_get_by_uuid(context, uuid) + else: + instance_ref = self.db.instance_get(context, instance_id) return instance_ref['locked'] @checks_instance_lock diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ffd009513..c440e700e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1317,7 +1317,11 @@ def instance_update(context, instance_id, values): instance_metadata_update_or_create(context, instance_id, values.pop('metadata')) with session.begin(): - instance_ref = instance_get(context, instance_id, session=session) + if utils.is_uuid_like(instance_id): + instance_ref = instance_get_by_uuid(context, instance_id, + session=session) + else: + instance_ref = instance_get(context, instance_id, session=session) instance_ref.update(values) instance_ref.save(session=session) return instance_ref @@ -2765,13 +2769,13 @@ def migration_get(context, id, session=None): @require_admin_context -def migration_get_by_instance_and_status(context, instance_id, status): +def migration_get_by_instance_and_status(context, instance_uuid, status): session = get_session() result = session.query(models.Migration).\ - filter_by(instance_id=instance_id).\ + filter_by(instance_uuid=instance_uuid).\ filter_by(status=status).first() if not result: - raise exception.MigrationNotFoundByStatus(instance_id=instance_id, + raise exception.MigrationNotFoundByStatus(instance_id=instance_uuid, status=status) return result diff --git a/nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py new file mode 100644 index 000000000..b002ba064 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/032_change_instance_id_in_migrations.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License.from sqlalchemy import * + +from sqlalchemy import Column, Integer, String, MetaData, Table + +meta = MetaData() + + +# +# Tables to alter +# +# + +instance_id = Column('instance_id', Integer()) +instance_uuid = Column('instance_uuid', String(255)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + migrations = Table('migrations', meta, autoload=True) + migrations.create_column(instance_uuid) + migrations.c.instance_id.drop() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + migrations = Table('migrations', meta, autoload=True) + migrations.c.instance_uuid.drop() + migrations.create_column(instance_id) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index d29d3d6f1..1e8e2e758 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -526,7 +526,8 @@ class Migration(BASE, NovaBase): dest_host = Column(String(255)) old_flavor_id = Column(Integer()) new_flavor_id = Column(Integer()) - instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) + instance_uuid = Column(String(255), ForeignKey('instances.uuid'), + nullable=True) #TODO(_cerberus_): enum status = Column(String(255)) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 04bb194d5..0586cc40d 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -424,12 +424,12 @@ class ComputeTestCase(test.TestCase): self.stubs.Set(self.compute.network_api, 'get_instance_nw_info', fake) context = self.context.elevated() instance_id = self._create_instance() - self.compute.prep_resize(context, instance_id, 1) + instance_ref = db.instance_get(context, instance_id) + self.compute.prep_resize(context, instance_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') + instance_ref['uuid'], 'pre-migrating') try: - self.compute.finish_resize(context, instance_id, - int(migration_ref['id']), {}) + self.compute.finish_resize(context, int(migration_ref['id']), {}) except KeyError, e: # Only catch key errors. We want other reasons for the test to # fail to actually error out so we don't obscure anything @@ -441,14 +441,15 @@ class ComputeTestCase(test.TestCase): """Ensure notifications on instance migrate/resize""" instance_id = self._create_instance() context = self.context.elevated() + inst_ref = db.instance_get(context, instance_id) self.compute.run_instance(self.context, instance_id) test_notifier.NOTIFICATIONS = [] db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, instance_id, 1) + self.compute.prep_resize(context, inst_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') + inst_ref['uuid'], 'pre-migrating') self.assertEquals(len(test_notifier.NOTIFICATIONS), 1) msg = test_notifier.NOTIFICATIONS[0] @@ -471,14 +472,15 @@ class ComputeTestCase(test.TestCase): """Ensure instance can be migrated/resized""" instance_id = self._create_instance() context = self.context.elevated() + inst_ref = db.instance_get(context, instance_id) self.compute.run_instance(self.context, instance_id) - db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, instance_id, 1) + db.instance_update(self.context, inst_ref['uuid'], + {'host': 'foo'}) + self.compute.prep_resize(context, inst_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') - self.compute.resize_instance(context, instance_id, - migration_ref['id']) + inst_ref['uuid'], 'pre-migrating') + self.compute.resize_instance(context, migration_ref['id']) self.compute.terminate_instance(context, instance_id) def test_resize_invalid_flavor_fails(self): -- cgit From c8a35349be912bb862789059abc95ccf9f7b8ef5 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Fri, 15 Jul 2011 19:07:58 +0000 Subject: Updated with some changes from manual testing --- nova/compute/api.py | 14 +++++++++----- nova/compute/manager.py | 21 +++++++++++++++------ nova/exception.py | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 7857f06a5..2233b9225 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -826,15 +826,16 @@ class API(base.Base): def revert_resize(self, context, instance_id): """Reverts a resize, deleting the 'new' instance in the process.""" context = context.elevated() + instance_ref = self._get_instance(context, instance_id, 'confirm_resize') migration_ref = self.db.migration_get_by_instance_and_status(context, - instance_id, 'finished') + instance_ref['uuid'], 'finished') if not migration_ref: raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, - migration_ref['dest_compute'], params=params) + instance_ref['uuid'], params=params) self.db.migration_update(context, migration_ref['id'], {'status': 'reverted'}) @@ -842,14 +843,17 @@ class API(base.Base): def confirm_resize(self, context, instance_id): """Confirms a migration/resize and deletes the 'old' instance.""" context = context.elevated() + + instance_ref = self._get_instance(context, instance_id, 'confirm_resize') migration_ref = self.db.migration_get_by_instance_and_status(context, - instance_id, 'finished') + instance_ref['uuid'], 'finished') if not migration_ref: raise exception.MigrationNotFoundByStatus(instance_id=instance_id, status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, - migration_ref['source_compute'], params=params) + instance_ref['uuid'], + params=params) self.db.migration_update(context, migration_ref['id'], {'status': 'confirmed'}) @@ -897,7 +901,7 @@ class API(base.Base): self._cast_scheduler_message(context, {"method": "prep_resize", "args": {"topic": FLAGS.compute_topic, - "instance_uuid": instance_ref['uuid'], + "instance_id": instance_ref['uuid'], "flavor_id": new_instance_type['id']}}) @scheduler_api.reroute_compute("add_fixed_ip") diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e00e3d0ab..55bc1cdcd 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -89,6 +89,10 @@ def checks_instance_lock(function): """Decorator to prevent action against locked instances for non-admins.""" @functools.wraps(function) def decorated_function(self, context, instance_id, *args, **kwargs): + #TODO(anyone): this being called instance_id is forcing a slightly + # confusing convention of pushing instance_uuids + # through an "instance_id" key in the queue args dict when + # casting through the compute API LOG.info(_("check_instance_lock: decorating: |%s|"), function, context=context) LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|" @@ -664,10 +668,10 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def confirm_resize(self, context, migration_id): + def confirm_resize(self, context, instance_id, migration_id): """Destroys the source instance.""" migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, + instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) self.driver.destroy(instance_ref) @@ -679,7 +683,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def revert_resize(self, context, migration_id): + def revert_resize(self, context, instance_id, migration_id): """Destroys the new instance on the destination machine. Reverts the model changes, and powers on the old instance on the @@ -687,7 +691,7 @@ class ComputeManager(manager.SchedulerDependentManager): """ migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, + instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) self.driver.destroy(instance_ref) @@ -732,14 +736,19 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def prep_resize(self, context, instance_uuid, flavor_id): + def prep_resize(self, context, instance_id, flavor_id): """Initiates the process of moving a running instance to another host. Possibly changes the RAM and disk size in the process. """ context = context.elevated() - instance_ref = self.db.instance_get_by_uuid(context, instance_uuid) + + # Because of checks_instance_lock, this must currently be called + # instance_id. However, the compute API is always passing the UUID + # of the instance down + instance_ref = self.db.instance_get_by_uuid(context, instance_id) + if instance_ref['host'] == FLAGS.host: raise exception.Error(_( 'Migration error: destination same as source!')) diff --git a/nova/exception.py b/nova/exception.py index 988940d6a..a98b0a36b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -77,8 +77,8 @@ def wrap_db_error(f): except Exception, e: LOG.exception(_('DB exception wrapped.')) raise DBError(e) - return _wrap _wrap.func_name = f.func_name + return _wrap def wrap_exception(f): -- cgit From 435c205ddc04c5d5c33d23a326ade38dab8ed943 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Fri, 15 Jul 2011 14:20:44 -0500 Subject: One last nit --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 9788f6147..0e41e2a19 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -712,7 +712,7 @@ class ComputeManager(manager.SchedulerDependentManager): """ migration_ref = self.db.migration_get(context, migration_id) - instance_ref = self.db.instance_get(context, + instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) instance_type = self.db.instance_type_get_by_flavor_id(context, @@ -720,7 +720,7 @@ class ComputeManager(manager.SchedulerDependentManager): # Just roll back the record. There's no need to resize down since # the 'old' VM already has the preferred attributes - self.db.instance_update(context, instance_ref.instance_uuid, + self.db.instance_update(context, instance_ref['uuid'], dict(memory_mb=instance_type['memory_mb'], vcpus=instance_type['vcpus'], local_gb=instance_type['local_gb'])) -- cgit From 8bc0dcc54a7218bd9f567c5719718f540f6ac549 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Mon, 18 Jul 2011 22:16:38 +0000 Subject: Some missed instance_id casts --- nova/compute/manager.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0e41e2a19..1feb96faa 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -770,7 +770,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'resize_instance', - 'args': {'migration_id': migration_ref['id']}, + 'args': { 'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}, }) usage_info = utils.usage_from_instance(instance_ref, new_instance_type=instance_type['name'], @@ -782,7 +783,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock - def resize_instance(self, context, migration_id): + def resize_instance(self, context, instance_id, migration_id): """Starts the migration of a running instance to another host.""" migration_ref = self.db.migration_get(context, migration_id) instance_ref = self.db.instance_get_by_uuid(context, @@ -804,13 +805,14 @@ class ComputeManager(manager.SchedulerDependentManager): FLAGS.compute_topic, migration_ref['dest_compute']) params = {'migration_id': migration_id, - 'disk_info': disk_info} + 'disk_info': disk_info, + 'instance_id': instance_ref['uuid']} rpc.cast(context, topic, {'method': 'finish_resize', 'args': params}) @exception.wrap_exception @checks_instance_lock - def finish_resize(self, context, migration_id, disk_info): + def finish_resize(self, context, instance_id, migration_id, disk_info): """Completes the migration process. Sets up the newly transferred disk and turns on the instance at its -- cgit From 115727a7cd41e703b35b6b061b64d097b9bbbf1d Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Mon, 18 Jul 2011 17:58:33 -0500 Subject: Fixed the broken tests again --- nova/compute/manager.py | 5 ++--- nova/tests/test_compute.py | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 1feb96faa..595e2e334 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -770,9 +770,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref['host']) rpc.cast(context, topic, {'method': 'resize_instance', - 'args': { 'instance_id': instance_ref['uuid'], - 'migration_id': migration_ref['id']}, - }) + 'args': {'instance_id': instance_ref['uuid'], + 'migration_id': migration_ref['id']}}) usage_info = utils.usage_from_instance(instance_ref, new_instance_type=instance_type['name'], new_instance_type_id=instance_type['id']) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 0586cc40d..a4adaee17 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -429,7 +429,8 @@ class ComputeTestCase(test.TestCase): migration_ref = db.migration_get_by_instance_and_status(context, instance_ref['uuid'], 'pre-migrating') try: - self.compute.finish_resize(context, int(migration_ref['id']), {}) + self.compute.finish_resize(context, instance_ref['uuid'], + int(migration_ref['id']), {}) except KeyError, e: # Only catch key errors. We want other reasons for the test to # fail to actually error out so we don't obscure anything @@ -480,7 +481,8 @@ class ComputeTestCase(test.TestCase): self.compute.prep_resize(context, inst_ref['uuid'], 1) migration_ref = db.migration_get_by_instance_and_status(context, inst_ref['uuid'], 'pre-migrating') - self.compute.resize_instance(context, migration_ref['id']) + self.compute.resize_instance(context, inst_ref['uuid'], + migration_ref['id']) self.compute.terminate_instance(context, instance_id) def test_resize_invalid_flavor_fails(self): -- cgit From 45bdf5cc27c9389255d32ad189a561b967b8f89e Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Tue, 19 Jul 2011 16:25:16 -0500 Subject: Fixes lp813006 - inconsistent DB API naming --- nova/compute/instance_types.py | 2 +- nova/db/api.py | 4 ++-- nova/db/sqlalchemy/api.py | 2 +- nova/network/manager.py | 3 +-- nova/tests/db/fakes.py | 8 ++++---- nova/tests/test_instance_types_extra_specs.py | 8 ++++---- nova/tests/test_network.py | 4 ++-- nova/virt/libvirt/netutils.py | 2 +- 8 files changed, 16 insertions(+), 17 deletions(-) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 1d246e445..c13a629a9 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -112,7 +112,7 @@ def get_instance_type(id): return get_default_instance_type() try: ctxt = context.get_admin_context() - return db.instance_type_get_by_id(ctxt, id) + return db.instance_type_get(ctxt, id) except exception.DBError: raise exception.ApiError(_("Unknown instance type: %s") % id) diff --git a/nova/db/api.py b/nova/db/api.py index cb4da169c..ca7064333 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1305,9 +1305,9 @@ def instance_type_get_all(context, inactive=False): return IMPL.instance_type_get_all(context, inactive) -def instance_type_get_by_id(context, id): +def instance_type_get(context, id): """Get instance type by id.""" - return IMPL.instance_type_get_by_id(context, id) + return IMPL.instance_type_get(context, id) def instance_type_get_by_name(context, name): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 189be0714..04b16ba1a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2985,7 +2985,7 @@ def instance_type_get_all(context, inactive=False): @require_context -def instance_type_get_by_id(context, id): +def instance_type_get(context, id): """Returns a dict describing specific instance_type""" session = get_session() inst_type = session.query(models.InstanceTypes).\ diff --git a/nova/network/manager.py b/nova/network/manager.py index 24736f53d..963a3cbea 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -413,8 +413,7 @@ class NetworkManager(manager.SchedulerDependentManager): # TODO(tr3buchet) should handle floating IPs as well? fixed_ips = self.db.fixed_ip_get_by_instance(context, instance_id) vifs = self.db.virtual_interface_get_by_instance(context, instance_id) - flavor = self.db.instance_type_get_by_id(context, - instance_type_id) + flavor = self.db.instance_type_get(context, instance_type_id) network_info = [] # a vif has an address, instance_id, and network_id # it is also joined to the instance and network given by those IDs diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 7762df41c..19028a451 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -230,7 +230,7 @@ def stub_out_db_network_api(stubs): continue fixed_ip_fields['virtual_interface'] = FakeModel(vif[0]) - def fake_instance_type_get_by_id(context, id): + def fake_instance_type_get(context, id): if flavor_fields['id'] == id: return FakeModel(flavor_fields) @@ -323,7 +323,7 @@ def stub_out_db_network_api(stubs): fake_fixed_ip_get_by_address, fake_fixed_ip_get_network, fake_fixed_ip_update, - fake_instance_type_get_by_id, + fake_instance_type_get, fake_virtual_interface_create, fake_virtual_interface_delete_by_instance, fake_virtual_interface_get_by_instance, @@ -415,7 +415,7 @@ def stub_out_db_instance_api(stubs, injected=True): def fake_instance_type_get_by_name(context, name): return INSTANCE_TYPES[name] - def fake_instance_type_get_by_id(context, id): + def fake_instance_type_get(context, id): for name, inst_type in INSTANCE_TYPES.iteritems(): if str(inst_type['id']) == str(id): return inst_type @@ -448,7 +448,7 @@ def stub_out_db_instance_api(stubs, injected=True): fake_network_get_all_by_instance, fake_instance_type_get_all, fake_instance_type_get_by_name, - fake_instance_type_get_by_id, + fake_instance_type_get, fake_instance_get_fixed_addresses, fake_instance_get_fixed_addresses_v6, fake_network_get_all_by_instance, diff --git a/nova/tests/test_instance_types_extra_specs.py b/nova/tests/test_instance_types_extra_specs.py index c26cf82ff..393ed1e36 100644 --- a/nova/tests/test_instance_types_extra_specs.py +++ b/nova/tests/test_instance_types_extra_specs.py @@ -105,8 +105,8 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): self.instance_type_id) self.assertEquals(expected_specs, actual_specs) - def test_instance_type_get_by_id_with_extra_specs(self): - instance_type = db.api.instance_type_get_by_id( + def test_instance_type_get_with_extra_specs(self): + instance_type = db.api.instance_type_get( context.get_admin_context(), self.instance_type_id) self.assertEquals(instance_type['extra_specs'], @@ -115,7 +115,7 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): xpu_arch="fermi", xpus="2", xpu_model="Tesla 2050")) - instance_type = db.api.instance_type_get_by_id( + instance_type = db.api.instance_type_get( context.get_admin_context(), 5) self.assertEquals(instance_type['extra_specs'], {}) @@ -136,7 +136,7 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): "m1.small") self.assertEquals(instance_type['extra_specs'], {}) - def test_instance_type_get_by_id_with_extra_specs(self): + def test_instance_type_get_with_extra_specs(self): instance_type = db.api.instance_type_get_by_flavor_id( context.get_admin_context(), 105) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index b09021e13..dd12d15ca 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -139,13 +139,13 @@ class FlatNetworkTestCase(test.TestCase): def test_get_instance_nw_info(self): self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance') - self.mox.StubOutWithMock(db, 'instance_type_get_by_id') + self.mox.StubOutWithMock(db, 'instance_type_get') db.fixed_ip_get_by_instance(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(fixed_ips) db.virtual_interface_get_by_instance(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(vifs) - db.instance_type_get_by_id(mox.IgnoreArg(), + db.instance_type_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(flavor) self.mox.ReplayAll() diff --git a/nova/virt/libvirt/netutils.py b/nova/virt/libvirt/netutils.py index e5aaf7cec..8d36c0a05 100644 --- a/nova/virt/libvirt/netutils.py +++ b/nova/virt/libvirt/netutils.py @@ -59,7 +59,7 @@ def get_network_info(instance): vifs = db.virtual_interface_get_by_instance(admin_context, instance['id']) networks = db.network_get_all_by_instance(admin_context, instance['id']) - flavor = db.instance_type_get_by_id(admin_context, + flavor = db.instance_type_get(admin_context, instance['instance_type_id']) network_info = [] -- cgit From bc2747faf2164833b3e5412dd226f9ee431b1bbf Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 19 Jul 2011 17:51:43 -0400 Subject: updated links to use proper atom:link per spec --- nova/api/openstack/versions.py | 36 ++++++++++++++++++++++++++++++- nova/tests/api/openstack/test_versions.py | 8 ++----- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a634c3267..450b42765 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -17,6 +17,7 @@ import webob import webob.dec +from xml.dom import minidom import nova.api.openstack.views.versions from nova.api.openstack import wsgi @@ -32,7 +33,7 @@ class Versions(wsgi.Resource): } body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), + 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) @@ -54,3 +55,36 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + +class VersionsXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + super(VersionsXMLSerializer, self).__init__(metadata, xmlns) + + def _versions_to_xml(self, versions): + root = self.xml_doc.createElement('versions') + + for version in versions: + root.appendChild(self._create_version_node(version)) + + return root + + def _create_version_node(self, version): + version_node = self.xml_doc.createElement('version') + version_node.setAttribute('id', version['id']) + version_node.setAttribute('status', version['status']) + #TODO(wwolf) need 'updated' attribute too + + for link in version['links']: + link_node = self.xml_doc.createElement('atom:link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + version_node.appendChild(link_node) + + return version_node + + + def default(self, data): + self.xml_doc = minidom.Document() + node = self._versions_to_xml(data['versions']) + + return self.to_xml_string(node) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index fd8d50904..ff6298640 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -70,14 +70,10 @@ class VersionsTest(test.TestCase): expected = """ - - - + - - - + """.replace(" ", "").replace("\n", "") -- cgit From e22b4f1b80f60e7235ef6a1af7d6a7c2df88effe Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Jul 2011 15:12:24 -0700 Subject: tell glance to not filter out private images --- nova/image/glance.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nova/image/glance.py b/nova/image/glance.py index 55d948a32..4b7e7573f 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -89,6 +89,9 @@ class GlanceImageService(service.BaseImageService): # `get_images` here because we need `is_public` and `properties` # included so we can filter by user filtered = [] + filters = filters or {} + if 'is_public' not in filters: + filters['is_public'] = None image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) @@ -101,6 +104,9 @@ class GlanceImageService(service.BaseImageService): def detail(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of detailed image information.""" filtered = [] + filters = filters or {} + if 'is_public' not in filters: + filters['is_public'] = None image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) -- cgit From c5cb902d19c890dd20e5120017973ea0172dd428 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Jul 2011 15:29:12 -0700 Subject: have to use string 'none' and add a note --- nova/image/glance.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/image/glance.py b/nova/image/glance.py index 4b7e7573f..5c2dc957b 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -91,7 +91,8 @@ class GlanceImageService(service.BaseImageService): filtered = [] filters = filters or {} if 'is_public' not in filters: - filters['is_public'] = None + # NOTE(vish): don't filter out private images + filters['is_public'] = 'none' image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) @@ -106,7 +107,8 @@ class GlanceImageService(service.BaseImageService): filtered = [] filters = filters or {} if 'is_public' not in filters: - filters['is_public'] = None + # NOTE(vish): don't filter out private images + filters['is_public'] = 'none' image_metas = self.client.get_images_detailed(filters=filters, marker=marker, limit=limit) -- cgit From 27f326c712e485322003ccdc13acfd04a6fdb119 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 09:59:56 -0400 Subject: added 'update' field to versions --- nova/api/openstack/versions.py | 6 +++++- nova/api/openstack/views/versions.py | 1 + nova/tests/api/openstack/test_versions.py | 9 +++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 450b42765..1b1fc41ab 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -45,10 +45,14 @@ class Versions(wsgi.Resource): { "id": "v1.1", "status": "CURRENT", + #TODO(wwolf) get correct value for these + "updated": "2011-7-18T11:30:00Z", }, { "id": "v1.0", "status": "DEPRECATED", + #TODO(wwolf) get correct value for these + "updated": "2010-10-09T11:30:00Z", }, ] @@ -72,7 +76,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): version_node = self.xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) - #TODO(wwolf) need 'updated' attribute too + version_node.setAttribute('updated', version['updated']) for link in version['links']: link_node = self.xml_doc.createElement('atom:link') diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index d0145c94a..e815974f1 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -36,6 +36,7 @@ class ViewBuilder(object): version = { "id": version_data["id"], "status": version_data["status"], + "updated": version_data["updated"], "links": self._build_links(version_data), } diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index ff6298640..32c57057a 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -43,6 +43,7 @@ class VersionsTest(test.TestCase): { "id": "v1.1", "status": "CURRENT", + "updated": "2011-7-18T11:30:00Z", "links": [ { "rel": "self", @@ -52,6 +53,7 @@ class VersionsTest(test.TestCase): { "id": "v1.0", "status": "DEPRECATED", + "updated": "2010-10-09T11:30:00Z", "links": [ { "rel": "self", @@ -69,10 +71,11 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ - + - + """.replace(" ", "").replace("\n", "") @@ -87,11 +90,13 @@ class VersionsTest(test.TestCase): version_data = { "id": "3.2.1", "status": "CURRENT", + "updated": "2011-7-18T11:30:00Z" } expected = { "id": "3.2.1", "status": "CURRENT", + "updated": "2011-7-18T11:30:00Z", "links": [ { "rel": "self", -- cgit From a35a70fbaef0ef6634213308d5a68ee60bd714f2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 13:12:18 -0400 Subject: initial changes for application/atom+xml for versions --- nova/api/openstack/versions.py | 98 ++++++++++++++++++++++++++++--- nova/api/openstack/wsgi.py | 19 ++++-- nova/tests/api/openstack/test_versions.py | 42 +++++++++++++ 3 files changed, 145 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 1b1fc41ab..a83472e15 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -23,7 +23,11 @@ import nova.api.openstack.views.versions from nova.api.openstack import wsgi +ATOM_XMLNS = "http://www.w3.org/2005/Atom" + + class Versions(wsgi.Resource): + def __init__(self): metadata = { "attributes": { @@ -33,11 +37,19 @@ class Versions(wsgi.Resource): } body_serializers = { + 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) - wsgi.Resource.__init__(self, None, serializer=serializer) + supported_content_types = ('application/json', + 'application/xml', + 'application/atom+xml') + deserializer = wsgi.RequestDeserializer( + supported_content_types=supported_content_types) + + wsgi.Resource.__init__(self, None, serializer=serializer, + deserializer=deserializer) def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" @@ -61,11 +73,8 @@ class Versions(wsgi.Resource): return dict(versions=versions) class VersionsXMLSerializer(wsgi.XMLDictSerializer): - def __init__(self, metadata=None, xmlns=None): - super(VersionsXMLSerializer, self).__init__(metadata, xmlns) - def _versions_to_xml(self, versions): - root = self.xml_doc.createElement('versions') + root = self._xml_doc.createElement('versions') for version in versions: root.appendChild(self._create_version_node(version)) @@ -73,13 +82,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return root def _create_version_node(self, version): - version_node = self.xml_doc.createElement('version') + version_node = self._xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) version_node.setAttribute('updated', version['updated']) for link in version['links']: - link_node = self.xml_doc.createElement('atom:link') + link_node = self._xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) version_node.appendChild(link_node) @@ -88,7 +97,80 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def default(self, data): - self.xml_doc = minidom.Document() + self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + +class VersionsAtomSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + if not xmlns: + self.xmlns = ATOM_XMLNS + else: + self.xmlns = xmlns + + def _create_text_elem(self, name, text, type=None): + elem = self._xml_doc.createElement(name) + if type: + elem.setAttribute('type', type) + elem_text = self._xml_doc.createTextNode(text) + elem.appendChild(elem_text) + return elem + + def _create_meta(self, root): + title = self._create_text_elem('title', 'Available API Versions', + type='text') + #TODO(wwolf): what should updated be? + updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') + #TODO(wwolf): get URI + id = self._create_text_elem('id', '') + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + + author = self._xml_doc.createElement('author') + author_name = self._create_text_elem('name', 'Rackspace') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author.appendChild(author_name) + author.appendChild(author_uri) + + root.appendChild(title) + root.appendChild(updated) + root.appendChild(id) + root.appendChild(link) + root.appendChild(author) + + def _create_version_entries(self, root, versions): + for version in versions: + entry = self._xml_doc.createElement('entry') + #TODO(wwolf) GET URI + id = self._create_text_elem('id', 'URI') + title = self._create_text_elem('title', + 'Version %s' % version['id'], + type='text') + updated = self._create_text_elem('updated', version['updated']) + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], + version['status'], + version['updated'])) + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + entry.appendChild(link) + entry.appendChild(content) + root.appendChild(entry) + + def default(self, data): + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_meta(node) + self._create_version_entries(node, data['versions']) + + return self.to_xml_string(node) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..9df6fd058 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -20,21 +20,22 @@ LOG = logging.getLogger('nova.api.openstack.wsgi') class Request(webob.Request): """Add some Openstack API-specific logic to the base webob.Request.""" - def best_match_content_type(self): + def best_match_content_type(self, supported_content_types=None): """Determine the requested response content-type. Based on the query extension then the Accept header. """ - supported = ('application/json', 'application/xml') + supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') parts = self.path.rsplit('.', 1) if len(parts) > 1: ctype = 'application/{0}'.format(parts[1]) - if ctype in supported: + if ctype in supported_content_types: return ctype - bm = self.accept.best_match(supported) + bm = self.accept.best_match(supported_content_types) # default to application/json if we don't find a preference return bm or 'application/json' @@ -151,7 +152,12 @@ class RequestHeadersDeserializer(ActionDispatcher): class RequestDeserializer(object): """Break up a Request object into more useful pieces.""" - def __init__(self, body_deserializers=None, headers_deserializer=None): + def __init__(self, body_deserializers=None, headers_deserializer=None, + supported_content_types=None): + + self.supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') + self.body_deserializers = { 'application/xml': XMLDeserializer(), 'application/json': JSONDeserializer(), @@ -213,7 +219,7 @@ class RequestDeserializer(object): raise exception.InvalidContentType(content_type=content_type) def get_expected_content_type(self, request): - return request.best_match_content_type() + return request.best_match_content_type(self.supported_content_types) def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" @@ -412,6 +418,7 @@ class Resource(wsgi.Application): serialized by requested content type. """ + def __init__(self, controller, deserializer=None, serializer=None): """ :param controller: object that implement methods created by routes lib diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 32c57057a..275cfa9ef 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -84,6 +84,48 @@ class VersionsTest(test.TestCase): self.assertEqual(expected, actual) + def test_get_version_list_atom(self): + req = webob.Request.blank('/') + req.accept = "application/atom+xml" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/atom+xml") + + expected = """ + + Available API Versions + 2010-12-12T18:30:02.25Z + http://servers.api.openstack.org/ + + Rackspace + http://www.rackspace.com/ + + + + http://servers.api.openstack.org/v1.1/ + Version v1.1 + 2010-12-12T18:30:02.25Z + + + Version v1.1 CURRENT (2010-12-12T18:30:02.25Z) + + + + http://servers.api.openstack.org/v1.0/ + Version v1.0 + 2009-10-09T11:30:00Z + + + Version v1.0 DEPRECATED (2009-10-09T11:30:00Z) + + + + """.replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + + self.assertEqual(expected, actual) + def test_view_builder(self): base_url = "http://example.org/" -- cgit From a497d2e8f6beb667c73f66bef756fd955b9d6d69 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 12:26:50 -0500 Subject: Host fix --- nova/compute/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 207a3d27b..9cc517345 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -836,7 +836,10 @@ class API(base.Base): params = {'migration_id': migration_ref['id']} self._cast_compute_message('revert_resize', context, - instance_ref['uuid'], params=params) + instance_ref['uuid'], + migration_ref['source_compute'], + params=params) + self.db.migration_update(context, migration_ref['id'], {'status': 'reverted'}) @@ -853,6 +856,7 @@ class API(base.Base): status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, + migration_ref['dest_compute'], instance_ref['uuid'], params=params) -- cgit From 9fc4cfaafceb3b96c51bc49aec020f89068544a0 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 12:39:37 -0500 Subject: Stupid merge and fixed broken test --- nova/compute/manager.py | 2 +- nova/tests/test_compute.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 85a60e413..bfc7694e7 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -708,7 +708,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @checks_instance_lock - def finish_revert_resize(self, context, migration_id): + def finish_revert_resize(self, context, instance_id, migration_id): """Finishes the second half of reverting a resize. Power back on the source instance and revert the resized attributes diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index ce7ec46af..4bda38d85 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -545,14 +545,14 @@ class ComputeTestCase(test.TestCase): db.instance_update(self.context, instance_id, {'host': 'foo'}) - self.compute.prep_resize(context, instance_id, 3) + self.compute.prep_resize(context, inst_ref['uuid'], 3) migration_ref = db.migration_get_by_instance_and_status(context, - instance_id, 'pre-migrating') + inst_ref['uuid'], 'pre-migrating') - self.compute.resize_instance(context, instance_id, + self.compute.resize_instance(context, inst_ref['uuid'], migration_ref['id']) - self.compute.finish_resize(context, instance_id, + self.compute.finish_resize(context, inst_ref['uuid'], int(migration_ref['id']), {}) # Prove that the instance size is now the new size @@ -562,9 +562,9 @@ class ComputeTestCase(test.TestCase): self.assertEqual(instance_type_ref['flavorid'], 3) # Finally, revert and confirm the old flavor has been applied - self.compute.revert_resize(context, instance_id, + self.compute.revert_resize(context, inst_ref['uuid'], migration_ref['id']) - self.compute.finish_revert_resize(context, instance_id, + self.compute.finish_revert_resize(context, inst_ref['uuid'], migration_ref['id']) inst_ref = db.instance_get(context, instance_id) -- cgit From 775ea7f40d769645520d4ea7c7d9c0bfdb36c2dc Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Jul 2011 17:45:55 +0000 Subject: default image to private on register --- nova/image/s3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/image/s3.py b/nova/image/s3.py index 4a3df98ba..c313c7a13 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -168,7 +168,7 @@ class S3ImageService(service.BaseImageService): metadata.update({'disk_format': image_format, 'container_format': image_format, 'status': 'queued', - 'is_public': True, + 'is_public': False, 'properties': properties}) metadata['properties']['image_state'] = 'pending' image = self.service.create(context, metadata) -- cgit From 0f5184da2bfd9fb9007ea786aff5117a5b4461f3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Jul 2011 17:46:32 +0000 Subject: add smoketests to verify image listing --- smoketests/test_sysadmin.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/smoketests/test_sysadmin.py b/smoketests/test_sysadmin.py index 268d9865b..454f6f1d5 100644 --- a/smoketests/test_sysadmin.py +++ b/smoketests/test_sysadmin.py @@ -103,27 +103,48 @@ class ImageTests(base.UserSmokeTestCase): 'launchPermission') self.assert_(attrs.name, 'launch_permission') - def test_009_can_modify_image_launch_permission(self): + def test_009_can_add_image_launch_permission(self): + image = self.conn.get_image(self.data['image_id']) + self.assertEqual(image.id, self.data['image_id']) + self.assertEqual(image.is_public, False) self.conn.modify_image_attribute(image_id=self.data['image_id'], operation='add', attribute='launchPermission', groups='all') image = self.conn.get_image(self.data['image_id']) self.assertEqual(image.id, self.data['image_id']) + self.assertEqual(image.is_public, True) def test_010_can_see_launch_permission(self): attrs = self.conn.get_image_attribute(self.data['image_id'], 'launchPermission') - self.assert_(attrs.name, 'launch_permission') - self.assert_(attrs.attrs['groups'][0], 'all') + self.assertEqual(attrs.name, 'launch_permission') + self.assertEqual(attrs.attrs['groups'][0], 'all') + + def test_011_can_remove_image_launch_permission(self): + image = self.conn.get_image(self.data['image_id']) + self.assertEqual(image.id, self.data['image_id']) + self.assertEqual(image.is_public, True) + self.conn.modify_image_attribute(image_id=self.data['image_id'], + operation='remove', + attribute='launchPermission', + groups='all') + image = self.conn.get_image(self.data['image_id']) + self.assertEqual(image.id, self.data['image_id']) + self.assertEqual(image.is_public, False) + + def test_012_private_image_shows_in_list(self): + images = self.conn.get_all_images() + image_ids = [image.id for image in images] + self.assertTrue(self.data['image_id'] in image_ids) - def test_011_user_can_deregister_kernel(self): + def test_013_user_can_deregister_kernel(self): self.assertTrue(self.conn.deregister_image(self.data['kernel_id'])) - def test_012_can_deregister_image(self): + def test_014_can_deregister_image(self): self.assertTrue(self.conn.deregister_image(self.data['image_id'])) - def test_013_can_delete_bundle(self): + def test_015_can_delete_bundle(self): self.assertTrue(self.delete_bundle_bucket(TEST_BUCKET)) -- cgit From e2eff68dbe78d8639d4cb212ab48240ebf045f73 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 20 Jul 2011 14:25:23 -0500 Subject: Whoops --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e25a9eeb3..67aa3c20f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -902,8 +902,8 @@ class API(base.Base): status='finished') params = {'migration_id': migration_ref['id']} self._cast_compute_message('confirm_resize', context, - migration_ref['dest_compute'], instance_ref['uuid'], + migration_ref['dest_compute'], params=params) self.db.migration_update(context, migration_ref['id'], -- cgit From 148b4095a5f18f192ae243b02b4070af384c1152 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 16:47:17 -0400 Subject: added versions list atom test and it passes --- nova/api/openstack/versions.py | 68 ++++++++++++++++++++----------- nova/api/openstack/views/versions.py | 2 +- nova/tests/api/openstack/test_versions.py | 36 ++++++++-------- 3 files changed, 64 insertions(+), 42 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a83472e15..49633df88 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +from datetime import datetime import webob import webob.dec from xml.dom import minidom @@ -58,7 +59,7 @@ class Versions(wsgi.Resource): "id": "v1.1", "status": "CURRENT", #TODO(wwolf) get correct value for these - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", }, { "id": "v1.0", @@ -117,60 +118,81 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): elem.appendChild(elem_text) return elem - def _create_meta(self, root): + def _get_most_recent_update(self, versions): + recent = None + for version in versions: + updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + if not recent: + recent = updated + elif updated > recent: + recent = updated + + return recent.strftime('%Y-%m-%dT%H:%M:%SZ') + + def _get_base_url(self, link_href): + # Make sure no trailing / + link_href = link_href.rstrip('/') + return link_href.rsplit('/',1)[0] + '/' + + def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', type='text') - #TODO(wwolf): what should updated be? - updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') - #TODO(wwolf): get URI - id = self._create_text_elem('id', '') - #TODO(wwolf): get link info + # Set this updated to the most recently updated version + recent = self._get_most_recent_update(versions) + updated = self._create_text_elem('updated', recent) + + base_url = self._get_base_url(versions[0]['links'][0]['href']) + id = self._create_text_elem('id', base_url) link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + link.setAttribute('rel', 'self') + link.setAttribute('href', base_url) author = self._xml_doc.createElement('author') author_name = self._create_text_elem('name', 'Rackspace') - author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/') author.appendChild(author_name) author.appendChild(author_uri) root.appendChild(title) root.appendChild(updated) root.appendChild(id) - root.appendChild(link) root.appendChild(author) + root.appendChild(link) def _create_version_entries(self, root, versions): for version in versions: entry = self._xml_doc.createElement('entry') - #TODO(wwolf) GET URI - id = self._create_text_elem('id', 'URI') + + id = self._create_text_elem('id', version['links'][0]['href']) title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) - #TODO(wwolf): get link info - link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + + for link in version['links']: + link_node = self._xml_doc.createElement('link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + entry.appendChild(link_node) + content = self._create_text_elem('content', 'Version %s %s (%s)' % (version['id'], version['status'], - version['updated'])) + version['updated']), + type='text') - entry.appendChild(id) - entry.appendChild(title) - entry.appendChild(updated) - entry.appendChild(link) entry.appendChild(content) root.appendChild(entry) def default(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node) + self._create_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index e815974f1..9fa8f49dc 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -57,4 +57,4 @@ class ViewBuilder(object): def generate_href(self, version_number): """Create an url that refers to a specific version_number.""" - return os.path.join(self.base_url, version_number) + return os.path.join(self.base_url, version_number) + '/' diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 275cfa9ef..471afec94 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -43,7 +43,7 @@ class VersionsTest(test.TestCase): { "id": "v1.1", "status": "CURRENT", - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", "links": [ { "rel": "self", @@ -71,7 +71,7 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") expected = """ - + Available API Versions - 2010-12-12T18:30:02.25Z - http://servers.api.openstack.org/ + 2011-07-18T11:30:00Z + http://localhost/ Rackspace http://www.rackspace.com/ - + - http://servers.api.openstack.org/v1.1/ + http://localhost/v1.1/ Version v1.1 - 2010-12-12T18:30:02.25Z - + 2011-07-18T11:30:00Z + - Version v1.1 CURRENT (2010-12-12T18:30:02.25Z) + Version v1.1 CURRENT (2011-07-18T11:30:00Z) - http://servers.api.openstack.org/v1.0/ + http://localhost/v1.0/ Version v1.0 - 2009-10-09T11:30:00Z - + 2010-10-09T11:30:00Z + - Version v1.0 DEPRECATED (2009-10-09T11:30:00Z) + Version v1.0 DEPRECATED (2010-10-09T11:30:00Z) - - """.replace(" ", "").replace("\n", "") + + """.replace(" ", "").replace("\n", "").replace("\t", "") - actual = res.body.replace(" ", "").replace("\n", "") + actual = res.body.replace(" ", "").replace("\n", "").replace("\t","") self.assertEqual(expected, actual) @@ -132,13 +132,13 @@ class VersionsTest(test.TestCase): version_data = { "id": "3.2.1", "status": "CURRENT", - "updated": "2011-7-18T11:30:00Z" + "updated": "2011-07-18T11:30:00Z" } expected = { "id": "3.2.1", "status": "CURRENT", - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", "links": [ { "rel": "self", -- cgit From 38fb47ab2a12be0b169e3f51f4da413efc041f9c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 20 Jul 2011 17:06:36 -0400 Subject: adding flavors xml serialization --- nova/api/openstack/flavors.py | 55 ++++- nova/tests/api/openstack/test_flavors.py | 374 +++++++++++++++++++++++++------ 2 files changed, 351 insertions(+), 78 deletions(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 6fab13147..b4bda68d4 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -16,6 +16,7 @@ # under the License. import webob +import xml.dom.minidom as minidom from nova import db from nova import exception @@ -74,19 +75,65 @@ class ControllerV11(Controller): return views.flavors.ViewBuilderV11(base_url) +class FlavorXMLSerializer(wsgi.XMLDictSerializer): + + def __init__(self): + super(FlavorXMLSerializer, self).__init__(xmlns=wsgi.XMLNS_V11) + + def _flavor_to_xml(self, xml_doc, flavor, detailed): + flavor_node = xml_doc.createElement('flavor') + flavor_node.setAttribute('id', str(flavor['id'])) + flavor_node.setAttribute('name', flavor['name']) + + if detailed: + flavor_node.setAttribute('ram', str(flavor['ram'])) + flavor_node.setAttribute('disk', str(flavor['disk'])) + + link_nodes = self._create_link_nodes(xml_doc, flavor['links']) + for link_node in link_nodes: + flavor_node.appendChild(link_node) + return flavor_node + + def _flavors_list_to_xml(self, xml_doc, flavors, detailed): + container_node = xml_doc.createElement('flavors') + + for flavor in flavors: + item_node = self._flavor_to_xml(xml_doc, flavor, detailed) + container_node.appendChild(item_node) + return container_node + + def show(self, flavor_container): + xml_doc = minidom.Document() + flavor = flavor_container['flavor'] + node = self._flavor_to_xml(xml_doc, flavor, True) + return self.to_xml_string(node, True) + + def detail(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, True) + return self.to_xml_string(node, True) + + def index(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, False) + return self.to_xml_string(node, True) + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, }[version]() - xmlns = { - '1.0': wsgi.XMLNS_V10, - '1.1': wsgi.XMLNS_V11, + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10), + '1.1': FlavorXMLSerializer(), }[version] body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), + 'application/xml': xml_serializer, } serializer = wsgi.ResponseSerializer(body_serializers) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 689647cc6..4ac35b26b 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -18,12 +18,14 @@ import json import stubout import webob +import xml.dom.minidom as minidom +from nova.api.openstack import flavors import nova.db.api -from nova import context from nova import exception from nova import test from nova.tests.api.openstack import fakes +from nova import wsgi def stub_flavor(flavorid, name, memory_mb="256", local_gb="10"): @@ -64,7 +66,6 @@ class FlavorsTest(test.TestCase): return_instance_types) self.stubs.Set(nova.db.api, "instance_type_get_by_flavor_id", return_instance_type_by_flavor_id) - self.context = context.get_admin_context() def tearDown(self): self.stubs.UnsetAll() @@ -146,61 +147,65 @@ class FlavorsTest(test.TestCase): req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - flavor = json.loads(res.body)["flavor"] + flavor = json.loads(res.body) expected = { - "id": "12", - "name": "flavor 12", - "ram": "256", - "disk": "10", - "links": [ - { - "rel": "self", - "href": "http://localhost/v1.1/flavors/12", - }, - { - "rel": "bookmark", - "href": "http://localhost/flavors/12", - }, - ], - } - self.assertEqual(flavor, expected) - - def test_get_flavor_list_v1_1(self): - req = webob.Request.blank('/v1.1/flavors') - req.environ['api.version'] = '1.1' - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - flavor = json.loads(res.body)["flavors"] - expected = [ - { - "id": "1", - "name": "flavor 1", - "links": [ - { - "rel": "self", - "href": "http://localhost/v1.1/flavors/1", - }, - { - "rel": "bookmark", - "href": "http://localhost/flavors/1", - }, - ], - }, - { - "id": "2", - "name": "flavor 2", + "flavor": { + "id": "12", + "name": "flavor 12", + "ram": "256", + "disk": "10", "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/flavors/12", }, ], }, - ] + } + self.assertEqual(flavor, expected) + + def test_get_flavor_list_v1_1(self): + req = webob.Request.blank('/v1.1/flavors') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + flavor = json.loads(res.body) + expected = { + "flavors": [ + { + "id": "1", + "name": "flavor 1", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } self.assertEqual(flavor, expected) def test_get_flavor_list_detail_v1_1(self): @@ -208,52 +213,273 @@ class FlavorsTest(test.TestCase): req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - flavor = json.loads(res.body)["flavors"] - expected = [ - { - "id": "1", - "name": "flavor 1", + flavor = json.loads(res.body) + expected = { + "flavors": [ + { + "id": "1", + "name": "flavor 1", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } + self.assertEqual(flavor, expected) + + def test_get_empty_flavor_list_v1_1(self): + def _return_empty(self): + return {} + self.stubs.Set(nova.db.api, "instance_type_get_all", _return_empty) + + req = webob.Request.blank('/v1.1/flavors') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + flavors = json.loads(res.body)["flavors"] + expected = [] + self.assertEqual(flavors, expected) + + +class FlavorsXMLSerializationTest(test.TestCase): + + def test_show(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavor": { + "id": "12", + "name": "asdf", "ram": "256", "disk": "10", "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/1", + "href": "http://localhost/v1.1/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/1", + "href": "http://localhost/flavors/12", }, ], }, - { - "id": "2", - "name": "flavor 2", - "ram": "256", - "disk": "10", + } + + output = serializer.serialize(input, 'show') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_show_handles_integers(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavor": { + "id": 12, + "name": "asdf", + "ram": 256, + "disk": 10, "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/flavors/12", }, ], }, - ] - self.assertEqual(flavor, expected) + } - def test_get_empty_flavor_list_v1_1(self): - def _return_empty(self): - return {} - self.stubs.Set(nova.db.api, "instance_type_get_all", - _return_empty) + output = serializer.serialize(input, 'show') + actual = minidom.parseString(output.replace(" ", "")) - req = webob.Request.blank('/v1.1/flavors') - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - flavors = json.loads(res.body)["flavors"] - expected = [] - self.assertEqual(flavors, expected) + expected = minidom.parseString(""" + + + + + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_detail(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavors": [ + { + "id": "23", + "name": "flavor 23", + "ram": "512", + "disk": "20", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/23", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/23", + }, + ], + }, { + "id": "13", + "name": "flavor 13", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/13", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/13", + }, + ], + }, + ], + } + + output = serializer.serialize(input, 'detail') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + + + + + + + + + """.replace(" ", "") % locals()) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_index(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavors": [ + { + "id": "23", + "name": "flavor 23", + "ram": "512", + "disk": "20", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/23", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/23", + }, + ], + }, { + "id": "13", + "name": "flavor 13", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/13", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/13", + }, + ], + }, + ], + } + + output = serializer.serialize(input, 'index') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + + + + + + + + + + """.replace(" ", "") % locals()) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_index_empty(self): + serializer = flavors.FlavorXMLSerializer() + + input = { + "flavors": [], + } + + output = serializer.serialize(input, 'index') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + + """.replace(" ", "") % locals()) + + self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From 126714f297fc29c0a0c930e7e1f351bf114b3795 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 10:02:16 -0400 Subject: Perform fault wrapping in the openstack WSGI controller. This allows us to just raise webob Exceptions in OS API controllers with the appropriate explanations set. This resolves some inconsistencies with exception raising and returning that could cause HTML output to occur when an exception was raised. --- nova/api/openstack/accounts.py | 6 +- nova/api/openstack/backup_schedules.py | 9 ++- nova/api/openstack/common.py | 19 +++--- nova/api/openstack/consoles.py | 7 +-- nova/api/openstack/create_instance_helper.py | 7 +-- nova/api/openstack/image_metadata.py | 5 +- nova/api/openstack/images.py | 3 +- nova/api/openstack/ips.py | 11 ++-- nova/api/openstack/server_metadata.py | 1 - nova/api/openstack/servers.py | 92 +++++++++++++--------------- nova/api/openstack/shared_ip_groups.py | 13 ++-- nova/api/openstack/users.py | 3 +- nova/api/openstack/wsgi.py | 6 +- nova/api/openstack/zones.py | 8 +-- nova/tests/api/openstack/test_servers.py | 2 +- 15 files changed, 90 insertions(+), 102 deletions(-) diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index e3201b14f..a13a758ab 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -47,10 +47,10 @@ class Controller(object): raise exception.AdminRequired() def index(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def detail(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def show(self, req, id): """Return data about the given account id""" @@ -65,7 +65,7 @@ class Controller(object): def create(self, req, body): """We use update with create-or-update semantics because the id comes from an external source""" - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def update(self, req, id, body): """This is really create or update.""" diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 3e95aedf3..7ff0d999e 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,7 +19,6 @@ import time from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -36,20 +35,20 @@ class Controller(object): def index(self, req, server_id, **kwargs): """ Returns the list of backup schedules for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, server_id, id, **kwargs): """ Returns a single backup schedule for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, server_id, **kwargs): """ No actual update method required, since the existing API allows both create and update through a POST """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id, **kwargs): """ Deletes an existing backup schedule """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8e12ce0c0..f41d25537 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -53,10 +53,10 @@ def get_pagination_params(request): params[param] = int(request.GET[param]) except ValueError: msg = _('%s param must be an integer') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) if params[param] < 0: msg = _('%s param must be positive') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) return params @@ -77,18 +77,22 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - raise webob.exc.HTTPBadRequest(_('offset param must be an integer')) + msg = _('offset param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) try: limit = int(request.GET.get('limit', max_limit)) except ValueError: - raise webob.exc.HTTPBadRequest(_('limit param must be an integer')) + msg = _('limit param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) if limit < 0: - raise webob.exc.HTTPBadRequest(_('limit param must be positive')) + msg = _('limit param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) if offset < 0: - raise webob.exc.HTTPBadRequest(_('offset param must be positive')) + msg = _('offset param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) limit = min(max_limit, limit or max_limit) range_end = offset + limit @@ -111,7 +115,8 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker)) + msg = _('marker [%s] not found' % marker) + raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 9c7b37f0d..d2655acfa 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -20,7 +20,6 @@ import webob from nova import console from nova import exception -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -72,12 +71,12 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return _translate_detail_keys(console) def update(self, req, server_id, id): """You can't update a console""" - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): """Deletes a console""" @@ -86,7 +85,7 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2654e3c40..7784a3e81 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -28,7 +28,6 @@ from nova import quota from nova import utils from nova.compute import instance_types -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager as auth_manager @@ -70,7 +69,7 @@ class CreateInstanceHelper(object): return type from this method is left to the caller. """ if not body: - raise faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() context = req.environ['nova.context'] @@ -94,7 +93,7 @@ class CreateInstanceHelper(object): except Exception, e: msg = _("Cannot find requested image %(image_href)s: %(e)s" % locals()) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personality = body['server'].get('personality') @@ -153,7 +152,7 @@ class CreateInstanceHelper(object): self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 4f33844fa..c0fc8c09b 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -22,7 +22,6 @@ from nova import flags from nova import image from nova import quota from nova import utils -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -62,7 +61,7 @@ class Controller(object): if id in metadata: return {'meta': {id: metadata[id]}} else: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, image_id, body): context = req.environ['nova.context'] @@ -105,7 +104,7 @@ class Controller(object): img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id) if not id in metadata: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() metadata.pop(id) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..96c5cd3fa 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -25,7 +25,6 @@ from nova import flags import nova.image from nova import log from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import image_metadata from nova.api.openstack import servers from nova.api.openstack.views import images as images_view @@ -78,7 +77,7 @@ class Controller(object): image = self._image_service.show(context, id) except (exception.NotFound, exception.InvalidImageRef): explanation = _("Image not found.") - raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation)) + raise webob.exc.HTTPNotFound(explanation=explanation) return dict(image=self.get_builder(req).build(image, detail=True)) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 1ebfdb831..2996b032d 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -20,7 +20,6 @@ import time from webob import exc import nova -from nova.api.openstack import faults import nova.api.openstack.views.addresses from nova.api.openstack import wsgi from nova import db @@ -37,14 +36,14 @@ class Controller(object): instance = self.compute_api.get( req.environ['nova.context'], server_id) except nova.exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return instance def create(self, req, server_id, body): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() class ControllerV10(Controller): @@ -63,7 +62,7 @@ class ControllerV10(Controller): view = builder.build_public_parts(instance) else: msg = _("Only private and public networks available") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return {id: view} @@ -86,7 +85,7 @@ class ControllerV11(Controller): if network is None: msg = _("Instance is not a member of specified network") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return network diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 3b9169f81..d4f42bbf5 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,7 +18,6 @@ from webob import exc from nova import compute -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova import exception from nova import quota diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3b3d0685d..7bef1d9b2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -27,7 +27,6 @@ from nova import log as logging from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper -from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images @@ -102,17 +101,14 @@ class Controller(object): req.environ['nova.context'], id) return self._build_view(req, instance, is_detail=True) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, body): """ Creates a new server for a given user """ extra_values = None result = None - try: - extra_values, instances = self.helper.create_instance( - req, body, self.compute_api.create) - except faults.Fault, f: - return f + extra_values, instances = self.helper.create_instance( + req, body, self.compute_api.create) # We can only return 1 instance via the API, if we happen to # build more than one... instances is a list, so we'll just @@ -132,7 +128,7 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() if not body: - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() ctxt = req.environ['nova.context'] update_dict = {} @@ -147,7 +143,7 @@ class Controller(object): try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return exc.HTTPNoContent() @@ -171,7 +167,7 @@ class Controller(object): for key in actions.keys(): if key in body: return actions[key](body, req, id) - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -181,7 +177,7 @@ class Controller(object): self.compute_api.confirm_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in confirm-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return exc.HTTPNoContent() def _action_revert_resize(self, input_dict, req, id): @@ -189,7 +185,7 @@ class Controller(object): self.compute_api.revert_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in revert-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_resize(self, input_dict, req, id): @@ -200,14 +196,14 @@ class Controller(object): reboot_type = input_dict['reboot']['type'] else: LOG.exception(_("Missing argument 'type' for reboot")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver self.compute_api.reboot(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in reboot %s"), e) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_migrate(self, input_dict, req, id): @@ -215,7 +211,7 @@ class Controller(object): self.compute_api.resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in migrate %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -231,7 +227,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -247,7 +243,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -262,7 +258,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -277,7 +273,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -292,7 +288,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -304,7 +300,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -316,7 +312,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -328,7 +324,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -340,7 +336,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -352,7 +348,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -364,7 +360,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -374,7 +370,7 @@ class Controller(object): self.compute_api.get_ajax_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -384,7 +380,7 @@ class Controller(object): self.compute_api.get_vnc_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -416,7 +412,7 @@ class ControllerV10(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) def _image_ref_from_req_data(self, data): @@ -440,17 +436,13 @@ class ControllerV10(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ - try: - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing 'flavorId' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + if 'resize' in input_dict and 'flavorId' in input_dict['resize']: + flavor_id = input_dict['resize']['flavorId'] + self.compute_api.resize(req.environ['nova.context'], id, + flavor_id) + else: + LOG.exception(_("Missing 'flavorId' argument for resize")) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -462,14 +454,14 @@ class ControllerV10(Controller): except (KeyError, TypeError): msg = _("Could not parse imageId from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) @@ -486,7 +478,7 @@ class ControllerV11(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def _image_ref_from_req_data(self, data): return data['server']['imageRef'] @@ -530,7 +522,7 @@ class ControllerV11(Controller): except AttributeError as ex: msg = _("Unable to parse metadata key/value pairs.") LOG.debug(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _decode_personalities(self, personalities): """Decode the Base64-encoded personalities.""" @@ -541,14 +533,14 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Unable to parse personality path/contents.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: personality["contents"] = base64.b64decode(contents) except TypeError: msg = _("Personality content could not be Base64 decoded.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ @@ -560,10 +552,10 @@ class ControllerV11(Controller): flavor_id) else: LOG.exception(_("Missing 'flavorRef' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() except Exception, e: LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -575,7 +567,7 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Could not parse imageRef from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personalities = info["rebuild"].get("personality", []) metadata = info["rebuild"].get("metadata") @@ -591,7 +583,7 @@ class ControllerV11(Controller): except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index cf2ddbabb..54d0a8334 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,7 +17,6 @@ from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -26,27 +25,27 @@ class Controller(object): def index(self, req, **kwargs): """ Returns a list of Shared IP Groups for the user """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, id, **kwargs): """ Shows in-depth information on a specific Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def update(self, req, id, **kwargs): """ You can't update a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, id, **kwargs): """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def detail(self, req, **kwargs): """ Returns a complete list of Shared IP Groups """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, **kwargs): """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 6ae1eaf2a..8dd72d559 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -19,7 +19,6 @@ from nova import exception from nova import flags from nova import log as logging from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager @@ -69,7 +68,7 @@ class Controller(object): user = None if user is None: - raise faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return dict(user=_translate_keys(user)) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..05767da19 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -441,7 +441,11 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - action_result = self.dispatch(request, action, args) + try: + action_result = self.dispatch(request, action, args) + except webob.exc.HTTPException as ex: + LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) + action_result = faults.Fault(ex) #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict or action_result is None: diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 2e02ec380..f7fd87bcd 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -27,7 +27,6 @@ from nova.scheduler import api from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -127,11 +126,8 @@ class Controller(object): Returns a reservation ID (a UUID). """ result = None - try: - extra_values, result = self.helper.create_instance(req, body, - self.compute_api.create_all_at_once) - except faults.Fault, f: - return f + extra_values, result = self.helper.create_instance(req, body, + self.compute_api.create_all_at_once) reservation_id = result return {'reservation_id': reservation_id} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1577c922b..22e2a4b5b 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1707,7 +1707,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) + self.assertEqual(res.status_int, 500) def test_resized_server_has_correct_status(self): req = self.webreq('/1', 'GET') -- cgit From 822b5ecede8f18120ae37eb07ff046101f50d3aa Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 11:10:47 -0400 Subject: cleanup tests and fix pep8 issues --- nova/api/openstack/versions.py | 20 +++++++++++--------- nova/tests/api/openstack/test_versions.py | 23 +++++++++++------------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 49633df88..8e172ae02 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -43,8 +43,8 @@ class Versions(wsgi.Resource): } serializer = wsgi.ResponseSerializer(body_serializers) - supported_content_types = ('application/json', - 'application/xml', + supported_content_types = ('application/json', + 'application/xml', 'application/atom+xml') deserializer = wsgi.RequestDeserializer( supported_content_types=supported_content_types) @@ -73,6 +73,7 @@ class Versions(wsgi.Resource): versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') @@ -96,13 +97,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return version_node - def default(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): if not xmlns: @@ -121,7 +122,8 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_most_recent_update(self, versions): recent = None for version in versions: - updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + updated = datetime.strptime(version['updated'], + '%Y-%m-%dT%H:%M:%SZ') if not recent: recent = updated elif updated > recent: @@ -132,7 +134,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_base_url(self, link_href): # Make sure no trailing / link_href = link_href.rstrip('/') - return link_href.rsplit('/',1)[0] + '/' + return link_href.rsplit('/', 1)[0] + '/' def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', @@ -164,7 +166,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): entry = self._xml_doc.createElement('entry') id = self._create_text_elem('id', version['links'][0]['href']) - title = self._create_text_elem('title', + title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) @@ -179,9 +181,9 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_node.setAttribute('href', link['href']) entry.appendChild(link_node) - content = self._create_text_elem('content', - 'Version %s %s (%s)' % - (version['id'], + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], version['status'], version['updated']), type='text') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 471afec94..6139731b9 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -47,7 +47,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1", + "href": "http://localhost/v1.1/", }], }, { @@ -57,7 +57,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.0", + "href": "http://localhost/v1.0/", }], }, ] @@ -72,11 +72,11 @@ class VersionsTest(test.TestCase): expected = """ - + - - + + """.replace(" ", "").replace("\n", "") @@ -120,9 +120,9 @@ class VersionsTest(test.TestCase): - """.replace(" ", "").replace("\n", "").replace("\t", "") + """.replace(" ", "").replace("\n", "") - actual = res.body.replace(" ", "").replace("\n", "").replace("\t","") + actual = res.body.replace(" ", "").replace("\n", "") self.assertEqual(expected, actual) @@ -132,8 +132,7 @@ class VersionsTest(test.TestCase): version_data = { "id": "3.2.1", "status": "CURRENT", - "updated": "2011-07-18T11:30:00Z" - } + "updated": "2011-07-18T11:30:00Z"} expected = { "id": "3.2.1", @@ -142,7 +141,7 @@ class VersionsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://example.org/3.2.1", + "href": "http://example.org/3.2.1/", }, ], } @@ -156,7 +155,7 @@ class VersionsTest(test.TestCase): base_url = "http://example.org/app/" version_number = "v1.4.6" - expected = "http://example.org/app/v1.4.6" + expected = "http://example.org/app/v1.4.6/" builder = views.versions.ViewBuilder(base_url) actual = builder.generate_href(version_number) -- cgit From a57504ef9a8b3e2c5d78cfe4ecf2f21c03653556 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 12:19:09 -0400 Subject: added tests, updated pep8 fixes --- nova/api/openstack/versions.py | 1 - nova/tests/api/openstack/test_versions.py | 91 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 8e172ae02..df7a94b7e 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,7 +28,6 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" class Versions(wsgi.Resource): - def __init__(self): metadata = { "attributes": { diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 6139731b9..0eae18a62 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -21,6 +21,7 @@ import webob from nova import context from nova import test from nova.tests.api.openstack import fakes +from nova.api.openstack import versions from nova.api.openstack import views @@ -161,3 +162,93 @@ class VersionsTest(test.TestCase): actual = builder.generate_href(version_number) self.assertEqual(actual, expected) + + def test_xml_serializer(self): + versions_data = { + 'versions': [ + { + "id": "2.7.1", + "updated": "2011-07-18T11:30:00Z", + "status": "DEPRECATED", + "links": [ + { + "rel": "self", + "href": "http://test/2.7.1", + } + ], + }, + ] + } + + expected = """ + + + + + """.replace(" ", "").replace("\n", "") + + serializer = versions.VersionsXMLSerializer() + response = serializer.default(versions_data) + response = response.replace(" ", "").replace("\n", "") + self.assertEqual(expected, response) + + def test_atom_serializer(self): + versions_data = { + 'versions': [ + { + "id": "2.9.8", + "updated": "2011-07-20T11:40:00Z", + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": "http://test/2.9.8", + } + ], + }, + ] + } + + expected = """ + + + Available API Versions + + + 2011-07-20T11:40:00Z + + + http://test/ + + + + Rackspace + + + http://www.rackspace.com/ + + + + + + http://test/2.9.8 + + + Version 2.9.8 + + + 2011-07-20T11:40:00Z + + + + Version 2.9.8 CURRENT (2011-07-20T11:40:00Z) + + + """.replace(" ", "").replace("\n", "") + + serializer = versions.VersionsAtomSerializer() + response = serializer.default(versions_data) + print response + response = response.replace(" ", "").replace("\n", "") + self.assertEqual(expected, response) -- cgit From 1230d93f6154d73fcd8ce1c30c629d8a04c1c874 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Thu, 21 Jul 2011 11:52:58 -0500 Subject: Some broken tests from my other merge --- nova/compute/manager.py | 2 +- nova/tests/test_compute.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c9afa8072..893727f38 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -748,7 +748,7 @@ class ComputeManager(manager.SchedulerDependentManager): raise exception.Error(_( 'Migration error: destination same as source!')) - old_instance_type = self.db.instance_type_get_by_id(context, + old_instance_type = self.db.instance_type_get(context, instance_ref['instance_type_id']) migration_ref = self.db.migration_create(context, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index dc3f0596d..ce32ff1de 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -535,7 +535,7 @@ class ComputeTestCase(test.TestCase): # Confirm the instance size before the resize starts inst_ref = db.instance_get(context, instance_id) - instance_type_ref = db.instance_type_get_by_id(context, + instance_type_ref = db.instance_type_get(context, inst_ref['instance_type_id']) self.assertEqual(instance_type_ref['flavorid'], 1) @@ -553,7 +553,7 @@ class ComputeTestCase(test.TestCase): # Prove that the instance size is now the new size inst_ref = db.instance_get(context, instance_id) - instance_type_ref = db.instance_type_get_by_id(context, + instance_type_ref = db.instance_type_get(context, inst_ref['instance_type_id']) self.assertEqual(instance_type_ref['flavorid'], 3) @@ -564,7 +564,7 @@ class ComputeTestCase(test.TestCase): migration_ref['id']) inst_ref = db.instance_get(context, instance_id) - instance_type_ref = db.instance_type_get_by_id(context, + instance_type_ref = db.instance_type_get(context, inst_ref['instance_type_id']) self.assertEqual(instance_type_ref['flavorid'], 1) -- cgit From 5133f92e5c00395b9a9d690e86815aa9c97cda9e Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 14:51:55 -0400 Subject: pep8 and stuff. --- nova/api/openstack/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index f41d25537..57031ebf1 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -77,7 +77,7 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - msg = _('offset param must be an integer') + msg = _('offset param must be an integer') raise webob.exc.HTTPBadRequest(explanation=msg) try: @@ -115,7 +115,7 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - msg = _('marker [%s] not found' % marker) + msg = _('marker [%s] not found') % marker raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] -- cgit