diff options
author | Chris Yeoh <cyeoh@au1.ibm.com> | 2013-06-27 20:30:45 +0930 |
---|---|---|
committer | Chris Yeoh <cyeoh@au1.ibm.com> | 2013-06-29 17:15:05 +0930 |
commit | 61dce13b5e4e8c4a99ce864e98d5ddfc4459a32c (patch) | |
tree | 02f31e3f7365a43d564389189d7910059afa4ee7 | |
parent | 815f0faffe085895febcd80dde8d2eacdffceb60 (diff) | |
download | nova-61dce13b5e4e8c4a99ce864e98d5ddfc4459a32c.tar.gz nova-61dce13b5e4e8c4a99ce864e98d5ddfc4459a32c.tar.xz nova-61dce13b5e4e8c4a99ce864e98d5ddfc4459a32c.zip |
Port images functionality to v3 API Part 2
The images functionality is moved for the V3 API from core to an
extension and is now accessed via /os-images instead of /images.
Consensus appears to be that we continue to need this
functionality for v3 because some deployers disable the public
image endpoint due security concerns and whether this will be
fixed in glance for Havana. Deployers can disable
this extension if they do not want it.
A further changeset will follow which changes the view builder
to correctly handle the lack of project id in the path with the
v3 API.
Partially implements blueprint nova-v3-api
Change-Id: I1237ad1ab5c58bb2d7cd9460b14666b4b97ef7fc
-rw-r--r-- | etc/nova/policy.json | 1 | ||||
-rw-r--r-- | nova/api/openstack/compute/plugins/v3/images.py | 36 | ||||
-rw-r--r-- | nova/tests/api/openstack/compute/plugins/v3/test_images.py | 142 | ||||
-rw-r--r-- | nova/tests/fake_policy.py | 1 | ||||
-rw-r--r-- | setup.cfg | 1 |
5 files changed, 106 insertions, 75 deletions
diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 7c563b936..3088185a5 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -79,6 +79,7 @@ "compute_extension:hosts": "rule:admin_api", "compute_extension:hypervisors": "rule:admin_api", "compute_extension:image_size": "", + "compute_extension:v3:os-images": "", "compute_extension:instance_actions": "", "compute_extension:instance_actions:events": "rule:admin_api", "compute_extension:instance_usage_audit_log": "rule:admin_api", diff --git a/nova/api/openstack/compute/plugins/v3/images.py b/nova/api/openstack/compute/plugins/v3/images.py index e0c4f7465..dde22488d 100644 --- a/nova/api/openstack/compute/plugins/v3/images.py +++ b/nova/api/openstack/compute/plugins/v3/images.py @@ -17,6 +17,7 @@ import webob.exc from nova.api.openstack import common from nova.api.openstack.compute.views import images as views_images +from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova import exception @@ -24,6 +25,9 @@ import nova.image.glance import nova.utils +ALIAS = "os-images" +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) + SUPPORTED_FILTERS = { 'name': 'name', 'status': 'status', @@ -83,7 +87,7 @@ class ImagesTemplate(xmlutil.TemplateBuilder): return xmlutil.MasterTemplate(root, 1, nsmap=image_nsmap) -class Controller(wsgi.Controller): +class ImagesController(wsgi.Controller): """Base controller for retrieving/displaying images.""" _view_builder_class = views_images.ViewBuilder @@ -94,7 +98,7 @@ class Controller(wsgi.Controller): :param image_service: `nova.image.glance:GlanceImageService` """ - super(Controller, self).__init__(**kwargs) + super(ImagesController, self).__init__(**kwargs) self._image_service = (image_service or nova.image.glance.get_default_image_service()) @@ -134,6 +138,7 @@ class Controller(wsgi.Controller): :param id: Image identifier """ context = req.environ['nova.context'] + authorize(context) try: image = self._image_service.show(context, id) @@ -151,6 +156,8 @@ class Controller(wsgi.Controller): :param id: Image identifier (integer) """ context = req.environ['nova.context'] + authorize(context) + try: self._image_service.delete(context, id) except exception.ImageNotFound: @@ -171,6 +178,8 @@ class Controller(wsgi.Controller): """ context = req.environ['nova.context'] + authorize(context) + filters = self._get_filters(req) params = req.GET.copy() page_params = common.get_pagination_params(req) @@ -192,6 +201,8 @@ class Controller(wsgi.Controller): """ context = req.environ['nova.context'] + authorize(context) + filters = self._get_filters(req) params = req.GET.copy() page_params = common.get_pagination_params(req) @@ -210,5 +221,22 @@ class Controller(wsgi.Controller): raise webob.exc.HTTPMethodNotAllowed() -def create_resource(): - return wsgi.Resource(Controller()) +class Images(extensions.V3APIExtensionBase): + """Server addresses.""" + + name = "Images" + alias = ALIAS + namespace = "http://docs.openstack.org/compute/ext/images/v3" + version = 1 + + def get_resources(self): + collection_actions = {'detail': 'GET'} + resources = [ + extensions.ResourceExtension( + ALIAS, ImagesController(), + collection_actions=collection_actions)] + + return resources + + def get_controller_extensions(self): + return [] diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_images.py b/nova/tests/api/openstack/compute/plugins/v3/test_images.py index a35dc6e51..712e3c8a5 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_images.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_images.py @@ -25,7 +25,7 @@ import urlparse from lxml import etree import webob -from nova.api.openstack.compute import images +from nova.api.openstack.compute.plugins.v3 import images from nova.api.openstack.compute.views import images as images_view from nova.api.openstack import xmlutil from nova import exception @@ -54,18 +54,18 @@ class ImagesControllerTest(test.TestCase): fakes.stub_out_compute_api_backup(self.stubs) fakes.stub_out_glance(self.stubs) - self.controller = images.Controller() + self.controller = images.ImagesController() def test_get_image(self): - fake_req = fakes.HTTPRequest.blank('/v2/fake/images/123') + fake_req = fakes.HTTPRequestV3.blank('/os-images/123') actual_image = self.controller.show(fake_req, '124') - href = "http://localhost/v2/fake/images/124" - bookmark = "http://localhost/fake/images/124" + href = "http://localhost/v3/images/124" + bookmark = "http://localhost/images/124" alternate = "%s/fake/images/124" % glance.generate_glance_url() server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74" - server_href = "http://localhost/v2/fake/servers/" + server_uuid - server_bookmark = "http://localhost/fake/servers/" + server_uuid + server_href = "http://localhost/v3/servers/" + server_uuid + server_bookmark = "http://localhost/servers/" + server_uuid expected_image = { "image": { @@ -113,14 +113,14 @@ class ImagesControllerTest(test.TestCase): def test_get_image_with_custom_prefix(self): self.flags(osapi_compute_link_prefix='https://zoo.com:42', osapi_glance_link_prefix='http://circus.com:34') - fake_req = fakes.HTTPRequest.blank('/v2/fake/images/123') + fake_req = fakes.HTTPRequestV3.blank('/v3/os-images/124') actual_image = self.controller.show(fake_req, '124') - href = "https://zoo.com:42/v2/fake/images/124" - bookmark = "https://zoo.com:42/fake/images/124" + href = "https://zoo.com:42/v3/images/124" + bookmark = "https://zoo.com:42/images/124" alternate = "http://circus.com:34/fake/images/124" server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74" - server_href = "https://zoo.com:42/v2/fake/servers/" + server_uuid - server_bookmark = "https://zoo.com:42/fake/servers/" + server_uuid + server_href = "https://zoo.com:42/v3/servers/" + server_uuid + server_bookmark = "https://zoo.com:42/servers/" + server_uuid expected_image = { "image": { @@ -165,18 +165,18 @@ class ImagesControllerTest(test.TestCase): self.assertThat(actual_image, matchers.DictMatches(expected_image)) def test_get_image_404(self): - fake_req = fakes.HTTPRequest.blank('/v2/fake/images/unknown') + fake_req = fakes.HTTPRequestV3.blank('/os-images/unknown') self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, fake_req, 'unknown') def test_get_image_details(self): - request = fakes.HTTPRequest.blank('/v2/fake/images/detail') + request = fakes.HTTPRequestV3.blank('/os-images/detail') response = self.controller.detail(request) response_list = response["images"] server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74" - server_href = "http://localhost/v2/fake/servers/" + server_uuid - server_bookmark = "http://localhost/fake/servers/" + server_uuid + server_href = "http://localhost/v3/servers/" + server_uuid + server_bookmark = "http://localhost/servers/" + server_uuid alternate = "%s/fake/images/%s" expected = [{ @@ -191,11 +191,11 @@ class ImagesControllerTest(test.TestCase): 'minRam': 128, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/123", + "href": "http://localhost/v3/images/123", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/123", + "href": "http://localhost/images/123", }, { "rel": "alternate", @@ -229,11 +229,11 @@ class ImagesControllerTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/124", + "href": "http://localhost/v3/images/124", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/124", + "href": "http://localhost/images/124", }, { "rel": "alternate", @@ -267,11 +267,11 @@ class ImagesControllerTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/125", + "href": "http://localhost/v3/images/125", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/125", + "href": "http://localhost/images/125", }, { "rel": "alternate", @@ -305,11 +305,11 @@ class ImagesControllerTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/126", + "href": "http://localhost/v3/images/126", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/126", + "href": "http://localhost/images/126", }, { "rel": "alternate", @@ -343,11 +343,11 @@ class ImagesControllerTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/127", + "href": "http://localhost/v3/images/127", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/127", + "href": "http://localhost/images/127", }, { "rel": "alternate", @@ -381,11 +381,11 @@ class ImagesControllerTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/128", + "href": "http://localhost/v3/images/128", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/128", + "href": "http://localhost/images/128", }, { "rel": "alternate", @@ -419,11 +419,11 @@ class ImagesControllerTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/129", + "href": "http://localhost/v3/images/129", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/129", + "href": "http://localhost/images/129", }, { "rel": "alternate", @@ -443,11 +443,11 @@ class ImagesControllerTest(test.TestCase): 'minRam': 0, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/130", + "href": "http://localhost/v3/images/130", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/130", + "href": "http://localhost/images/130", }, { "rel": "alternate", @@ -460,14 +460,14 @@ class ImagesControllerTest(test.TestCase): self.assertThat(expected, matchers.DictListMatches(response_list)) def test_get_image_details_with_limit(self): - request = fakes.HTTPRequest.blank('/v2/fake/images/detail?limit=2') + request = fakes.HTTPRequestV3.blank('/os-images/detail?limit=2') response = self.controller.detail(request) response_list = response["images"] response_links = response["images_links"] server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74" - server_href = "http://localhost/v2/fake/servers/" + server_uuid - server_bookmark = "http://localhost/fake/servers/" + server_uuid + server_href = "http://localhost/v3/servers/" + server_uuid + server_bookmark = "http://localhost/servers/" + server_uuid alternate = "%s/fake/images/%s" expected = [{ @@ -482,11 +482,11 @@ class ImagesControllerTest(test.TestCase): 'minRam': 128, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/123", + "href": "http://localhost/v3/images/123", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/123", + "href": "http://localhost/images/123", }, { "rel": "alternate", @@ -520,11 +520,11 @@ class ImagesControllerTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v2/fake/images/124", + "href": "http://localhost/v3/images/124", }, { "rel": "bookmark", - "href": "http://localhost/fake/images/124", + "href": "http://localhost/images/124", }, { "rel": "alternate", @@ -536,7 +536,7 @@ class ImagesControllerTest(test.TestCase): self.assertThat(expected, matchers.DictListMatches(response_list)) href_parts = urlparse.urlparse(response_links[0]['href']) - self.assertEqual('/v2/fake/images', href_parts.path) + self.assertEqual('/v3/images', href_parts.path) params = urlparse.parse_qs(href_parts.query) self.assertThat({'limit': ['2'], 'marker': ['124']}, @@ -545,47 +545,47 @@ class ImagesControllerTest(test.TestCase): def test_image_detail_filter_with_name(self): image_service = self.mox.CreateMockAnything() filters = {'name': 'testname'} - request = fakes.HTTPRequest.blank('/v2/fake/images/detail' + request = fakes.HTTPRequestV3.blank('/v3/os-images/detail' '?name=testname') context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_filter_with_status(self): image_service = self.mox.CreateMockAnything() filters = {'status': 'active'} - request = fakes.HTTPRequest.blank('/v2/fake/images/detail' + request = fakes.HTTPRequestV3.blank('/v3/os-images/detail' '?status=ACTIVE') context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_filter_with_property(self): image_service = self.mox.CreateMockAnything() filters = {'property-test': '3'} - request = fakes.HTTPRequest.blank('/v2/fake/images/detail' + request = fakes.HTTPRequestV3.blank('/v3/os-images/detail' '?property-test=3') context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_filter_server_href(self): image_service = self.mox.CreateMockAnything() uuid = 'fa95aaf5-ab3b-4cd8-88c0-2be7dd051aaf' ref = 'http://localhost:8774/servers/' + uuid - url = '/v2/fake/images/detail?server=' + ref + url = '/v3/os-images/detail?server=' + ref filters = {'property-instance_uuid': uuid} - request = fakes.HTTPRequest.blank(url) + request = fakes.HTTPRequestV3.blank(url) context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_filter_server_uuid(self): @@ -593,53 +593,53 @@ class ImagesControllerTest(test.TestCase): uuid = 'fa95aaf5-ab3b-4cd8-88c0-2be7dd051aaf' url = '/v2/fake/images/detail?server=' + uuid filters = {'property-instance_uuid': uuid} - request = fakes.HTTPRequest.blank(url) + request = fakes.HTTPRequestV3.blank(url) context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_filter_changes_since(self): image_service = self.mox.CreateMockAnything() filters = {'changes-since': '2011-01-24T17:08Z'} - request = fakes.HTTPRequest.blank('/v2/fake/images/detail' + request = fakes.HTTPRequestV3.blank('/v3/os-images/detail' '?changes-since=2011-01-24T17:08Z') context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_filter_with_type(self): image_service = self.mox.CreateMockAnything() filters = {'property-image_type': 'BASE'} - request = fakes.HTTPRequest.blank('/v2/fake/images/detail?type=BASE') + request = fakes.HTTPRequestV3.blank('/v3/os-images/detail?type=BASE') context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_filter_not_supported(self): image_service = self.mox.CreateMockAnything() filters = {'status': 'active'} - request = fakes.HTTPRequest.blank('/v2/fake/images/detail?status=' + request = fakes.HTTPRequestV3.blank('/v3/os-images/detail?status=' 'ACTIVE&UNSUPPORTEDFILTER=testname') context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_no_filters(self): image_service = self.mox.CreateMockAnything() filters = {} - request = fakes.HTTPRequest.blank('/v2/fake/images/detail') + request = fakes.HTTPRequestV3.blank('/v3/os-images/detail') context = request.environ['nova.context'] image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - controller = images.Controller(image_service=image_service) + controller = images.ImagesController(image_service=image_service) controller.detail(request) def test_image_detail_invalid_marker(self): @@ -648,19 +648,19 @@ class ImagesControllerTest(test.TestCase): def detail(self, *args, **kwargs): raise exception.Invalid('meow') - request = fakes.HTTPRequest.blank('/v2/images?marker=invalid') - controller = images.Controller(image_service=InvalidImageService()) + request = fakes.HTTPRequestV3.blank('/v3/os-images?marker=invalid') + controller = images.ImagesController(image_service=InvalidImageService()) self.assertRaises(webob.exc.HTTPBadRequest, controller.detail, request) def test_generate_alternate_link(self): view = images_view.ViewBuilder() - request = fakes.HTTPRequest.blank('/v2/fake/images/1') + request = fakes.HTTPRequestV3.blank('/v3/os-images/1') generated_url = view._get_alternate_link(request, 1) actual_url = "%s/fake/images/1" % glance.generate_glance_url() self.assertEqual(generated_url, actual_url) def test_delete_image(self): - request = fakes.HTTPRequest.blank('/v2/fake/images/124') + request = fakes.HTTPRequestV3.blank('/v3/os-images/124') request.method = 'DELETE' response = self.controller.delete(request, '124') self.assertEqual(response.status_int, 204) @@ -671,14 +671,14 @@ class ImagesControllerTest(test.TestCase): deleted_image_id = 128 # see nova.tests.api.openstack.fakes:_make_image_fixtures - request = fakes.HTTPRequest.blank( - '/v2/fake/images/%s' % deleted_image_id) + request = fakes.HTTPRequestV3.blank( + '/v3/os-images/%s' % deleted_image_id) request.method = 'DELETE' self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete, request, '%s' % deleted_image_id) def test_delete_image_not_found(self): - request = fakes.HTTPRequest.blank('/v2/fake/images/300') + request = fakes.HTTPRequestV3.blank('/v3/os-images/300') request.method = 'DELETE' self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, request, '300') @@ -688,11 +688,11 @@ class ImageXMLSerializationTest(test.TestCase): TIMESTAMP = "2010-10-11T10:30:22Z" SERVER_UUID = 'aa640691-d1a7-4a67-9d3c-d35ee6b3cc74' - SERVER_HREF = 'http://localhost/v2/fake/servers/' + SERVER_UUID - SERVER_BOOKMARK = 'http://localhost/fake/servers/' + SERVER_UUID - IMAGE_HREF = 'http://localhost/v2/fake/images/%s' - IMAGE_NEXT = 'http://localhost/v2/fake/images?limit=%s&marker=%s' - IMAGE_BOOKMARK = 'http://localhost/fake/images/%s' + SERVER_HREF = 'http://localhost/v3/servers/' + SERVER_UUID + SERVER_BOOKMARK = 'http://localhost/servers/' + SERVER_UUID + IMAGE_HREF = 'http://localhost/v3/os-images/%s' + IMAGE_NEXT = 'http://localhost/v3/os-images?limit=%s&marker=%s' + IMAGE_BOOKMARK = 'http://localhost/os-images/%s' def test_xml_declaration(self): serializer = images.ImageTemplate() diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index da43115d0..66c14ec64 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -155,6 +155,7 @@ policy_data = """ "compute_extension:hosts": "", "compute_extension:hypervisors": "", "compute_extension:image_size": "", + "compute_extension:v3:os-images": "", "compute_extension:instance_actions": "", "compute_extension:instance_actions:events": "is_admin:True", "compute_extension:instance_usage_audit_log": "", @@ -62,6 +62,7 @@ nova.api.v3.extensions = flavors = nova.api.openstack.compute.plugins.v3.flavors:Flavors flavor_access = nova.api.openstack.compute.plugins.v3.flavor_access:FlavorAccess flavor_disabled = nova.api.openstack.compute.plugins.v3.flavor_disabled:FlavorDisabled + images = nova.api.openstack.compute.plugins.v3.images:Images ips = nova.api.openstack.compute.plugins.v3.ips:IPs keypairs = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs quota_sets = nova.api.openstack.compute.plugins.v3.quota_sets:QuotaSets |