diff --git a/doc/v3/api_samples/servers/server-get-resp.json b/doc/v3/api_samples/servers/server-get-resp.json index 074ec35f7b98..408915b1cb98 100644 --- a/doc/v3/api_samples/servers/server-get-resp.json +++ b/doc/v3/api_samples/servers/server-get-resp.json @@ -12,7 +12,7 @@ } ] }, - "created": "2013-08-09T05:47:55Z", + "created": "2013-08-27T00:21:41Z", "flavor": { "id": "1", "links": [ @@ -22,24 +22,24 @@ } ] }, - "host_id": "9bc5b8424de645fd39f9edd242992c6be364eddefc936296fad0c582", - "id": "4fc3ceca-91ec-47e7-85ee-ce9834e8ddbb", + "host_id": "89429d40116cdb1f85f09de69dbc250f040a62db4a20e08b730b88c5", + "id": "5c2f3a9d-1858-454d-b2a2-ac2e93036d16", "image": { "id": "70a599e0-31e7-49b7-b260-868f441e862b", "links": [ { - "href": "http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b", + "href": "http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b", "rel": "bookmark" } ] }, "links": [ { - "href": "http://openstack.example.com/v3/servers/4fc3ceca-91ec-47e7-85ee-ce9834e8ddbb", + "href": "http://openstack.example.com/v3/servers/5c2f3a9d-1858-454d-b2a2-ac2e93036d16", "rel": "self" }, { - "href": "http://openstack.example.com/servers/4fc3ceca-91ec-47e7-85ee-ce9834e8ddbb", + "href": "http://openstack.example.com/servers/5c2f3a9d-1858-454d-b2a2-ac2e93036d16", "rel": "bookmark" } ], @@ -50,7 +50,7 @@ "progress": 0, "status": "ACTIVE", "tenant_id": "openstack", - "updated": "2013-08-09T05:47:55Z", + "updated": "2013-08-27T00:21:41Z", "user_id": "fake" } } \ No newline at end of file diff --git a/doc/v3/api_samples/servers/server-get-resp.xml b/doc/v3/api_samples/servers/server-get-resp.xml index d4b56ffc1352..c40b8f83253f 100644 --- a/doc/v3/api_samples/servers/server-get-resp.xml +++ b/doc/v3/api_samples/servers/server-get-resp.xml @@ -1,7 +1,7 @@ - + - + @@ -14,6 +14,6 @@ - - + + \ No newline at end of file diff --git a/doc/v3/api_samples/servers/servers-details-resp.json b/doc/v3/api_samples/servers/servers-details-resp.json index 436fa6ff8db1..1487ae729d1e 100644 --- a/doc/v3/api_samples/servers/servers-details-resp.json +++ b/doc/v3/api_samples/servers/servers-details-resp.json @@ -13,7 +13,7 @@ } ] }, - "created": "2013-08-09T05:47:55Z", + "created": "2013-08-27T00:34:14Z", "flavor": { "id": "1", "links": [ @@ -23,24 +23,24 @@ } ] }, - "host_id": "c977254e8fbf1c2dd83f283dce24dfdba6f1388d05c9972342472a6f", - "id": "b327644d-d61d-49f9-86fd-d56f166e98a8", + "host_id": "3da3b81edf53783e31c0bb9292c85b478943a79528028ed642c46c57", + "id": "aa830ba3-7562-44c4-b794-6e1678402854", "image": { "id": "70a599e0-31e7-49b7-b260-868f441e862b", "links": [ { - "href": "http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b", + "href": "http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b", "rel": "bookmark" } ] }, "links": [ { - "href": "http://openstack.example.com/v3/servers/b327644d-d61d-49f9-86fd-d56f166e98a8", + "href": "http://openstack.example.com/v3/servers/aa830ba3-7562-44c4-b794-6e1678402854", "rel": "self" }, { - "href": "http://openstack.example.com/servers/b327644d-d61d-49f9-86fd-d56f166e98a8", + "href": "http://openstack.example.com/servers/aa830ba3-7562-44c4-b794-6e1678402854", "rel": "bookmark" } ], @@ -51,7 +51,7 @@ "progress": 0, "status": "ACTIVE", "tenant_id": "openstack", - "updated": "2013-08-09T05:47:55Z", + "updated": "2013-08-27T00:34:14Z", "user_id": "fake" } ] diff --git a/doc/v3/api_samples/servers/servers-details-resp.xml b/doc/v3/api_samples/servers/servers-details-resp.xml index 9ae3d6e8107e..fab520299e01 100644 --- a/doc/v3/api_samples/servers/servers-details-resp.xml +++ b/doc/v3/api_samples/servers/servers-details-resp.xml @@ -1,8 +1,8 @@ - + - + @@ -15,7 +15,7 @@ - - + + \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index a05537a888ff..4c0851c20b73 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -124,8 +124,6 @@ "compute_extension:hypervisors": "rule:admin_api", "compute_extension:v3:os-hypervisors": "rule:admin_api", "compute_extension:image_size": "", - "compute_extension:v3:os-image-metadata": "", - "compute_extension:v3:os-images": "", "compute_extension:instance_actions": "", "compute_extension:v3:os-instance-actions": "", "compute_extension:instance_actions:events": "rule:admin_api", diff --git a/nova/api/openstack/compute/plugins/v3/image_metadata.py b/nova/api/openstack/compute/plugins/v3/image_metadata.py deleted file mode 100644 index aebd7ca2be0d..000000000000 --- a/nova/api/openstack/compute/plugins/v3/image_metadata.py +++ /dev/null @@ -1,166 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack Foundation -# 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 webob import exc - -from nova.api.openstack import common -from nova.api.openstack import extensions -from nova.api.openstack import wsgi -from nova import exception -from nova.image import glance -from nova.openstack.common.gettextutils import _ - -ALIAS = "os-image-metadata" -authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) - - -class ImageMetadataController(object): - """The image metadata API controller for the OpenStack API.""" - - def __init__(self): - self.image_service = glance.get_default_image_service() - - def _get_image(self, context, image_id): - try: - return self.image_service.show(context, image_id) - except exception.NotFound: - msg = _("Image not found.") - raise exc.HTTPNotFound(explanation=msg) - - @wsgi.serializers(xml=common.MetadataTemplate) - def index(self, req, image_id): - """Returns the list of metadata for a given instance.""" - context = req.environ['nova.context'] - authorize(context) - - metadata = self._get_image(context, image_id)['properties'] - return dict(metadata=metadata) - - @wsgi.serializers(xml=common.MetaItemTemplate) - def show(self, req, image_id, id): - context = req.environ['nova.context'] - authorize(context) - - metadata = self._get_image(context, image_id)['properties'] - if id in metadata: - return {'meta': {id: metadata[id]}} - else: - raise exc.HTTPNotFound() - - @wsgi.serializers(xml=common.MetadataTemplate) - @wsgi.deserializers(xml=common.MetadataDeserializer) - def create(self, req, image_id, body): - context = req.environ['nova.context'] - authorize(context) - - image = self._get_image(context, image_id) - if 'metadata' in body: - for key, value in body['metadata'].iteritems(): - image['properties'][key] = value - common.check_img_metadata_properties_quota(context, - image['properties']) - image = self.image_service.update(context, image_id, image, None) - return dict(metadata=image['properties']) - - @wsgi.serializers(xml=common.MetaItemTemplate) - @wsgi.deserializers(xml=common.MetaItemDeserializer) - def update(self, req, image_id, id, body): - context = req.environ['nova.context'] - authorize(context) - - try: - meta = body['meta'] - except KeyError: - expl = _('Incorrect request body format') - raise exc.HTTPBadRequest(explanation=expl) - - if id not in meta: - expl = _('Request body and URI mismatch') - raise exc.HTTPBadRequest(explanation=expl) - if len(meta) > 1: - expl = _('Request body contains too many items') - raise exc.HTTPBadRequest(explanation=expl) - - image = self._get_image(context, image_id) - image['properties'][id] = meta[id] - common.check_img_metadata_properties_quota(context, - image['properties']) - try: - self.image_service.update(context, image_id, image, None) - except exception.ImageNotAuthorized as e: - raise exc.HTTPForbidden(explanation=str(e)) - return dict(meta=meta) - - @wsgi.serializers(xml=common.MetadataTemplate) - @wsgi.deserializers(xml=common.MetadataDeserializer) - def update_all(self, req, image_id, body): - context = req.environ['nova.context'] - authorize(context) - - image = self._get_image(context, image_id) - metadata = body.get('metadata', {}) - common.check_img_metadata_properties_quota(context, metadata) - image['properties'] = metadata - self.image_service.update(context, image_id, image, None) - return dict(metadata=metadata) - - @wsgi.response(204) - def delete(self, req, image_id, id): - context = req.environ['nova.context'] - authorize(context) - - image = self._get_image(context, image_id) - if id not in image['properties']: - msg = _("Invalid metadata key") - raise exc.HTTPNotFound(explanation=msg) - image['properties'].pop(id) - self.image_service.update(context, image_id, image, None) - - -class ImageMetadata(extensions.V3APIExtensionBase): - """Image Metadata.""" - - name = "Image Metadata" - alias = ALIAS - namespace = "http://docs.openstack.org/compute/ext/image_metadata/v3" - version = 1 - - def __init__(self, extension_info): - super(ImageMetadata, self).__init__(extension_info) - self.image_metadata_controller = ImageMetadataController() - - def get_resources(self): - parent = {'member_name': 'os-images', - 'collection_name': 'os-images'} - resources = [ - extensions.ResourceExtension( - ALIAS, - self.image_metadata_controller, - parent=parent, - custom_routes_fn=self.image_metadata_map)] - - return resources - - def get_controller_extensions(self): - return [] - - def image_metadata_map(self, mapper, wsgi_resource): - mapper.connect("metadata", - "/os-images/{image_id}/os-image-metadata", - controller=self.image_metadata_controller, - action='update_all', - conditions={"method": ['PUT']}) diff --git a/nova/api/openstack/compute/plugins/v3/images.py b/nova/api/openstack/compute/plugins/v3/images.py deleted file mode 100644 index 82ec7dcd08a0..000000000000 --- a/nova/api/openstack/compute/plugins/v3/images.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# 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. - -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 -import nova.image.glance -from nova.openstack.common.gettextutils import _ -import nova.utils - - -ALIAS = "os-images" -authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) - -SUPPORTED_FILTERS = { - 'name': 'name', - 'status': 'status', - 'changes-since': 'changes-since', - 'server': 'property-instance_uuid', - 'type': 'property-image_type', - 'minRam': 'min_ram', - 'minDisk': 'min_disk', -} - - -def make_image(elem, detailed=False): - elem.set('name') - elem.set('id') - elem.set('size') - - if detailed: - elem.set('updated') - elem.set('created') - elem.set('status') - elem.set('progress') - elem.set('minRam') - elem.set('minDisk') - - server = xmlutil.SubTemplateElement(elem, 'server', selector='server') - server.set('id') - xmlutil.make_links(server, 'links') - - elem.append(common.MetadataTemplate()) - - xmlutil.make_links(elem, 'links') - - -image_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} - - -class ImageTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('image', selector='image') - make_image(root, detailed=True) - return xmlutil.MasterTemplate(root, 1, nsmap=image_nsmap) - - -class MinimalImagesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('images') - elem = xmlutil.SubTemplateElement(root, 'image', selector='images') - make_image(elem) - xmlutil.make_links(root, 'images_links') - return xmlutil.MasterTemplate(root, 1, nsmap=image_nsmap) - - -class ImagesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('images') - elem = xmlutil.SubTemplateElement(root, 'image', selector='images') - make_image(elem, detailed=True) - return xmlutil.MasterTemplate(root, 1, nsmap=image_nsmap) - - -class ImagesController(wsgi.Controller): - """Base controller for retrieving/displaying images.""" - - _view_builder_class = views_images.ViewBuilderV3 - - def __init__(self, image_service=None, **kwargs): - """Initialize new `ImageController`. - - :param image_service: `nova.image.glance:GlanceImageService` - - """ - super(ImagesController, self).__init__(**kwargs) - self._image_service = (image_service or - nova.image.glance.get_default_image_service()) - - def _get_filters(self, req): - """ - Return a dictionary of query param filters from the request - - :param req: the Request object coming from the wsgi layer - :retval a dict of key/value filters - """ - filters = {} - for param in req.params: - if param in SUPPORTED_FILTERS or param.startswith('property-'): - # map filter name or carry through if property-* - filter_name = SUPPORTED_FILTERS.get(param, param) - filters[filter_name] = req.params.get(param) - - # ensure server filter is the instance uuid - filter_name = 'property-instance_uuid' - try: - filters[filter_name] = filters[filter_name].rsplit('/', 1)[1] - except (AttributeError, IndexError, KeyError): - pass - - filter_name = 'status' - if filter_name in filters: - # The Image API expects us to use lowercase strings for status - filters[filter_name] = filters[filter_name].lower() - - return filters - - @wsgi.serializers(xml=ImageTemplate) - def show(self, req, id): - """Return detailed information about a specific image. - - :param req: `wsgi.Request` object - :param id: Image identifier - """ - context = req.environ['nova.context'] - authorize(context) - - try: - image = self._image_service.show(context, id) - except (exception.NotFound, exception.InvalidImageRef): - explanation = _("Image not found.") - raise webob.exc.HTTPNotFound(explanation=explanation) - - req.cache_db_items('images', [image], 'id') - return self._view_builder.show(req, image) - - def delete(self, req, id): - """Delete an image, if allowed. - - :param req: `wsgi.Request` object - :param id: Image identifier (integer) - """ - context = req.environ['nova.context'] - authorize(context) - - try: - self._image_service.delete(context, id) - except exception.ImageNotFound: - explanation = _("Image not found.") - raise webob.exc.HTTPNotFound(explanation=explanation) - except exception.ImageNotAuthorized: - # The image service raises this exception on delete if glanceclient - # raises HTTPForbidden. - explanation = _("You are not allowed to delete the image.") - raise webob.exc.HTTPForbidden(explanation=explanation) - return webob.exc.HTTPNoContent() - - @wsgi.serializers(xml=MinimalImagesTemplate) - def index(self, req): - """Return an index listing of images available to the request. - - :param req: `wsgi.Request` object - - """ - context = req.environ['nova.context'] - authorize(context) - - filters = self._get_filters(req) - params = req.GET.copy() - page_params = common.get_pagination_params(req) - for key, val in page_params.iteritems(): - params[key] = val - - try: - images = self._image_service.detail(context, filters=filters, - **page_params) - except exception.Invalid as e: - raise webob.exc.HTTPBadRequest(explanation=e.format_message()) - return self._view_builder.index(req, images) - - @wsgi.serializers(xml=ImagesTemplate) - def detail(self, req): - """Return a detailed index listing of images available to the request. - - :param req: `wsgi.Request` object. - - """ - context = req.environ['nova.context'] - authorize(context) - - filters = self._get_filters(req) - params = req.GET.copy() - page_params = common.get_pagination_params(req) - for key, val in page_params.iteritems(): - params[key] = val - try: - images = self._image_service.detail(context, filters=filters, - **page_params) - except exception.Invalid as e: - raise webob.exc.HTTPBadRequest(explanation=e.format_message()) - - req.cache_db_items('images', images, 'id') - return self._view_builder.detail(req, images) - - def create(self, *args, **kwargs): - raise webob.exc.HTTPMethodNotAllowed() - - -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/api/openstack/compute/views/images.py b/nova/api/openstack/compute/views/images.py index 0cc8f48268e9..306b99692758 100644 --- a/nova/api/openstack/compute/views/images.py +++ b/nova/api/openstack/compute/views/images.py @@ -152,46 +152,16 @@ class ViewBuilder(common.ViewBuilder): class ViewBuilderV3(ViewBuilder): - def show(self, request, image): - """Return a dictionary with image details.""" - image_dict = { - "id": image.get("id"), - "name": image.get("name"), - "size": int(image.get("size") or 0), - "minRam": int(image.get("min_ram") or 0), - "minDisk": int(image.get("min_disk") or 0), - "metadata": image.get("properties", {}), - "created": self._format_date(image.get("created_at")), - "updated": self._format_date(image.get("updated_at")), - "status": self._get_status(image), - "progress": self._get_progress(image), - "links": self._get_links(request, - image["id"], - self._collection_name), - } - - instance_uuid = image.get("properties", {}).get("instance_uuid") - - if instance_uuid is not None: - server_ref = self._get_href_link(request, instance_uuid, 'servers') - image_dict["server"] = { - "id": instance_uuid, - "links": [{ - "rel": "self", - "href": server_ref, - }, - { - "rel": "bookmark", - "href": self._get_bookmark_link(request, - instance_uuid, - 'servers'), - }], - } - - return dict(image=image_dict) - - def _get_alternate_link(self, request, identifier): - """Create an alternate link for a specific image id.""" - glance_url = glance.generate_glance_url() - glance_url = self._update_glance_link_prefix(glance_url) - return '/'.join([glance_url, self._collection_name, str(identifier)]) + def _get_bookmark_link(self, request, identifier, collection_name): + """Create a URL that refers to a specific resource.""" + if collection_name == "images": + glance_url = glance.generate_image_url(identifier) + return self._update_glance_link_prefix(glance_url) + else: + raise NotImplementedError + # NOTE(cyeoh) The V3 version of _get_bookmark_link should + # only ever be called with images as the + # collection_name. The images API has been removed in the + # V3 API and the V3 version of the view only exists for + # the servers view to be able to generate the appropriate + # bookmark link for the image of the instance. diff --git a/nova/api/openstack/compute/views/servers.py b/nova/api/openstack/compute/views/servers.py index e34c37074fee..47d506b321c5 100644 --- a/nova/api/openstack/compute/views/servers.py +++ b/nova/api/openstack/compute/views/servers.py @@ -228,6 +228,7 @@ class ViewBuilderV3(ViewBuilder): """Initialize view builder.""" super(ViewBuilderV3, self).__init__() self._address_builder = views_addresses.ViewBuilderV3() + self._image_builder = views_images.ViewBuilderV3() def show(self, request, instance): """Detailed view of a single instance.""" diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_image_metadata.py b/nova/tests/api/openstack/compute/plugins/v3/test_image_metadata.py deleted file mode 100644 index a83a1cb4daff..000000000000 --- a/nova/tests/api/openstack/compute/plugins/v3/test_image_metadata.py +++ /dev/null @@ -1,225 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack Foundation -# 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 oslo.config import cfg -import webob - -from nova.api.openstack.compute.plugins.v3 import image_metadata -from nova.openstack.common import jsonutils -from nova import test -from nova.tests.api.openstack import fakes - -CONF = cfg.CONF - - -class ImageMetaDataTest(test.TestCase): - - def setUp(self): - super(ImageMetaDataTest, self).setUp() - fakes.stub_out_glance(self.stubs) - self.controller = image_metadata.ImageMetadataController() - - def test_index(self): - req = fakes.HTTPRequestV3.blank('/v3/os-images/123/os-image-metadata') - res_dict = self.controller.index(req, '123') - expected = {'metadata': {'key1': 'value1'}} - self.assertEqual(res_dict, expected) - - def test_show(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/key1') - res_dict = self.controller.show(req, '123', 'key1') - self.assertTrue('meta' in res_dict) - self.assertEqual(len(res_dict['meta']), 1) - self.assertEqual('value1', res_dict['meta']['key1']) - - def test_show_not_found(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/key9') - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.show, req, '123', 'key9') - - def test_show_image_not_found(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/100/os-image-metadata/key1') - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.show, req, '100', 'key9') - - def test_create(self): - req = fakes.HTTPRequestV3.blank('/v3/os-images/123/os-image-metadata') - req.method = 'POST' - body = {"metadata": {"key7": "value7"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - res = self.controller.create(req, '123', body) - - expected_output = {'metadata': {'key1': 'value1', 'key7': 'value7'}} - self.assertEqual(expected_output, res) - - def test_create_image_not_found(self): - req = fakes.HTTPRequestV3.blank('/v3/os-images/100/os-image-metadata') - req.method = 'POST' - body = {"metadata": {"key7": "value7"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.create, req, '100', body) - - def test_update_all(self): - req = fakes.HTTPRequestV3.blank('/v3/os-images/123/os-image-metadata') - req.method = 'PUT' - body = {"metadata": {"key9": "value9"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - res = self.controller.update_all(req, '123', body) - - expected_output = {'metadata': {'key9': 'value9'}} - self.assertEqual(expected_output, res) - - def test_update_all_image_not_found(self): - req = fakes.HTTPRequestV3.blank('/v3/os-images/100/os-image-metadata') - req.method = 'PUT' - body = {"metadata": {"key9": "value9"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.update_all, req, '100', body) - - def test_update_item(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/key1') - req.method = 'PUT' - body = {"meta": {"key1": "zz"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - res = self.controller.update(req, '123', 'key1', body) - - expected_output = {'meta': {'key1': 'zz'}} - self.assertEqual(res, expected_output) - - def test_update_item_image_not_found(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/100/os-image-metadata/key1') - req.method = 'PUT' - body = {"meta": {"key1": "zz"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.update, req, '100', 'key1', body) - - def test_update_item_bad_body(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/key1') - req.method = 'PUT' - body = {"key1": "zz"} - req.body = '' - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPBadRequest, - self.controller.update, req, '123', 'key1', body) - - def test_update_item_too_many_keys(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/key1') - req.method = 'PUT' - overload = {} - for num in range(CONF.quota_metadata_items + 1): - overload['key%s' % num] = 'value%s' % num - body = {'meta': overload} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPBadRequest, - self.controller.update, req, '123', 'key1', body) - - def test_update_item_body_uri_mismatch(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/bad') - req.method = 'PUT' - body = {"meta": {"key1": "value1"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPBadRequest, - self.controller.update, req, '123', 'bad', body) - - def test_delete(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/key1') - req.method = 'DELETE' - res = self.controller.delete(req, '123', 'key1') - - self.assertEqual(None, res) - - def test_delete_not_found(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/blah') - req.method = 'DELETE' - - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.delete, req, '123', 'blah') - - def test_delete_image_not_found(self): - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/100/os-image-metadata/key1') - req.method = 'DELETE' - - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.delete, req, '100', 'key1') - - def test_too_many_metadata_items_on_create(self): - data = {"metadata": {}} - for num in range(CONF.quota_metadata_items + 1): - data['metadata']['key%i' % num] = "blah" - req = fakes.HTTPRequestV3.blank('/v3/os-images/123/os-image-metadata') - req.method = 'POST' - req.body = jsonutils.dumps(data) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, - self.controller.create, req, '123', data) - self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, - self.controller.create, req, '123', data) - - def test_too_many_metadata_items_on_put(self): - self.flags(quota_metadata_items=1) - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/123/os-image-metadata/blah') - req.method = 'PUT' - body = {"meta": {"blah": "blah"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, - self.controller.update, req, '123', 'blah', body) - - def test_image_not_authorized(self): - image_id = 131 - # see nova.tests.api.openstack.fakes:_make_image_fixtures - - req = fakes.HTTPRequestV3.blank( - '/v3/os-images/%s/os-image-metadata/key1' % image_id) - req.method = 'PUT' - body = {"meta": {"key1": "value1"}} - req.body = jsonutils.dumps(body) - req.headers["content-type"] = "application/json" - - self.assertRaises(webob.exc.HTTPForbidden, - self.controller.update, req, image_id, 'key1', body) diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_images.py b/nova/tests/api/openstack/compute/plugins/v3/test_images.py deleted file mode 100644 index abb5b7d2e861..000000000000 --- a/nova/tests/api/openstack/compute/plugins/v3/test_images.py +++ /dev/null @@ -1,956 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Tests of the new image services, both as a service layer, -and as a WSGI layer -""" - -import urlparse - -from lxml import etree -import webob - -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 -from nova.image import glance -from nova import test -from nova.tests.api.openstack import fakes -from nova.tests import matchers - -NS = "{http://docs.openstack.org/compute/api/v1.1}" -ATOMNS = "{http://www.w3.org/2005/Atom}" -NOW_API_FORMAT = "2010-10-11T10:30:22Z" - - -class ImagesControllerTest(test.TestCase): - """ - Test of the OpenStack API /images application controller w/Glance. - """ - - def setUp(self): - """Run before each test.""" - super(ImagesControllerTest, self).setUp() - fakes.stub_out_networking(self.stubs) - fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_key_pair_funcs(self.stubs) - fakes.stub_out_compute_api_snapshot(self.stubs) - fakes.stub_out_compute_api_backup(self.stubs) - fakes.stub_out_glance(self.stubs) - - self.controller = images.ImagesController() - - def test_get_image(self): - fake_req = fakes.HTTPRequestV3.blank('/os-images/123') - actual_image = self.controller.show(fake_req, '124') - - href = "http://localhost/v3/images/124" - bookmark = "http://localhost/images/124" - alternate = "%s/images/124" % glance.generate_glance_url() - server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74" - server_href = "http://localhost/v3/servers/" + server_uuid - server_bookmark = "http://localhost/servers/" + server_uuid - - expected_image = { - "image": { - "id": "124", - "name": "queued snapshot", - "updated": NOW_API_FORMAT, - "created": NOW_API_FORMAT, - "status": "SAVING", - "progress": 25, - "size": 25165824, - "minDisk": 0, - "minRam": 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "metadata": { - "instance_uuid": server_uuid, - "user_id": "fake", - }, - "links": [{ - "rel": "self", - "href": href, - }, - { - "rel": "bookmark", - "href": bookmark, - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": alternate - }], - }, - } - - self.assertThat(actual_image, matchers.DictMatches(expected_image)) - - 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.HTTPRequestV3.blank('/v3/os-images/124') - actual_image = self.controller.show(fake_req, '124') - href = "https://zoo.com:42/v3/images/124" - bookmark = "https://zoo.com:42/images/124" - alternate = "http://circus.com:34/images/124" - server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74" - server_href = "https://zoo.com:42/v3/servers/" + server_uuid - server_bookmark = "https://zoo.com:42/servers/" + server_uuid - - expected_image = { - "image": { - "id": "124", - "name": "queued snapshot", - "updated": NOW_API_FORMAT, - "created": NOW_API_FORMAT, - "status": "SAVING", - "progress": 25, - "size": 25165824, - "minDisk": 0, - "minRam": 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "metadata": { - "instance_uuid": server_uuid, - "user_id": "fake", - }, - "links": [{ - "rel": "self", - "href": href, - }, - { - "rel": "bookmark", - "href": bookmark, - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": alternate - }], - }, - } - self.assertThat(actual_image, matchers.DictMatches(expected_image)) - - def test_get_image_404(self): - 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.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/v3/servers/" + server_uuid - server_bookmark = "http://localhost/servers/" + server_uuid - alternate = "%s/images/%s" - - expected = [{ - 'id': '123', - 'name': 'public image', - 'metadata': {'key1': 'value1'}, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'ACTIVE', - 'progress': 100, - "size": 25165824, - 'minDisk': 10, - 'minRam': 128, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/123", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/123", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": alternate % (glance.generate_glance_url(), 123), - }], - }, - { - 'id': '124', - 'name': 'queued snapshot', - 'metadata': { - u'instance_uuid': server_uuid, - u'user_id': u'fake', - }, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'SAVING', - 'progress': 25, - "size": 25165824, - 'minDisk': 0, - 'minRam': 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/124", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/124", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": alternate % (glance.generate_glance_url(), 124), - }], - }, - { - 'id': '125', - 'name': 'saving snapshot', - 'metadata': { - u'instance_uuid': server_uuid, - u'user_id': u'fake', - }, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'SAVING', - 'progress': 50, - "size": 25165824, - 'minDisk': 0, - 'minRam': 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/125", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/125", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": "%s/images/125" % glance.generate_glance_url() - }], - }, - { - 'id': '126', - 'name': 'active snapshot', - 'metadata': { - u'instance_uuid': server_uuid, - u'user_id': u'fake', - }, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'ACTIVE', - 'progress': 100, - "size": 25165824, - 'minDisk': 0, - 'minRam': 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/126", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/126", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": "%s/images/126" % glance.generate_glance_url() - }], - }, - { - 'id': '127', - 'name': 'killed snapshot', - 'metadata': { - u'instance_uuid': server_uuid, - u'user_id': u'fake', - }, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'ERROR', - 'progress': 0, - "size": 25165824, - 'minDisk': 0, - 'minRam': 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/127", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/127", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": "%s/images/127" % glance.generate_glance_url() - }], - }, - { - 'id': '128', - 'name': 'deleted snapshot', - 'metadata': { - u'instance_uuid': server_uuid, - u'user_id': u'fake', - }, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'DELETED', - 'progress': 0, - "size": 25165824, - 'minDisk': 0, - 'minRam': 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/128", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/128", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": "%s/images/128" % glance.generate_glance_url() - }], - }, - { - 'id': '129', - 'name': 'pending_delete snapshot', - 'metadata': { - u'instance_uuid': server_uuid, - u'user_id': u'fake', - }, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'DELETED', - 'progress': 0, - "size": 25165824, - 'minDisk': 0, - 'minRam': 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/129", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/129", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": "%s/images/129" % glance.generate_glance_url() - }], - }, - { - 'id': '130', - 'name': None, - 'metadata': {}, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'ACTIVE', - 'progress': 100, - "size": 0, - 'minDisk': 0, - 'minRam': 0, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/130", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/130", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": "%s/images/130" % glance.generate_glance_url() - }], - }, - { - 'id': '131', - 'name': None, - 'metadata': {}, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'ACTIVE', - 'progress': 100, - "size": 0, - 'minDisk': 0, - 'minRam': 0, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/131", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/131", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": "%s/images/131" % glance.generate_glance_url() - }], - }, - ] - - self.assertThat(expected, matchers.DictListMatches(response_list)) - - def test_get_image_details_with_limit(self): - 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/v3/servers/" + server_uuid - server_bookmark = "http://localhost/servers/" + server_uuid - alternate = "%s/images/%s" - - expected = [{ - 'id': '123', - 'name': 'public image', - 'metadata': {'key1': 'value1'}, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'ACTIVE', - "size": 25165824, - 'minDisk': 10, - 'progress': 100, - 'minRam': 128, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/123", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/123", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": alternate % (glance.generate_glance_url(), 123), - }], - }, - { - 'id': '124', - 'name': 'queued snapshot', - 'metadata': { - u'instance_uuid': server_uuid, - u'user_id': u'fake', - }, - 'updated': NOW_API_FORMAT, - 'created': NOW_API_FORMAT, - 'status': 'SAVING', - "size": 25165824, - 'minDisk': 0, - 'progress': 25, - 'minRam': 0, - 'server': { - 'id': server_uuid, - "links": [{ - "rel": "self", - "href": server_href, - }, - { - "rel": "bookmark", - "href": server_bookmark, - }], - }, - "links": [{ - "rel": "self", - "href": "http://localhost/v3/images/124", - }, - { - "rel": "bookmark", - "href": "http://localhost/images/124", - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": alternate % (glance.generate_glance_url(), 124), - }], - }] - - self.assertThat(expected, matchers.DictListMatches(response_list)) - - href_parts = urlparse.urlparse(response_links[0]['href']) - self.assertEqual('/v3/images', href_parts.path) - params = urlparse.parse_qs(href_parts.query) - - self.assertThat({'limit': ['2'], 'marker': ['124']}, - matchers.DictMatches(params)) - - def test_image_detail_filter_with_name(self): - image_service = self.mox.CreateMockAnything() - filters = {'name': 'testname'} - 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.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.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.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.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.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 = '/v3/os-images/detail?server=' + ref - filters = {'property-instance_uuid': uuid} - request = fakes.HTTPRequestV3.blank(url) - context = request.environ['nova.context'] - image_service.detail(context, filters=filters).AndReturn([]) - self.mox.ReplayAll() - controller = images.ImagesController(image_service=image_service) - controller.detail(request) - - def test_image_detail_filter_server_uuid(self): - image_service = self.mox.CreateMockAnything() - uuid = 'fa95aaf5-ab3b-4cd8-88c0-2be7dd051aaf' - url = '/v3/os-images/detail?server=' + uuid - filters = {'property-instance_uuid': uuid} - request = fakes.HTTPRequestV3.blank(url) - context = request.environ['nova.context'] - image_service.detail(context, filters=filters).AndReturn([]) - self.mox.ReplayAll() - 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.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.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.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.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.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.ImagesController(image_service=image_service) - controller.detail(request) - - def test_image_detail_no_filters(self): - image_service = self.mox.CreateMockAnything() - filters = {} - 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.ImagesController(image_service=image_service) - controller.detail(request) - - def test_image_detail_invalid_marker(self): - class InvalidImageService(object): - - def detail(self, *args, **kwargs): - raise exception.Invalid('meow') - - 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.ViewBuilderV3() - request = fakes.HTTPRequestV3.blank('/v3/os-images/1') - generated_url = view._get_alternate_link(request, 1) - actual_url = "%s/images/1" % glance.generate_glance_url() - self.assertEqual(generated_url, actual_url) - - def test_delete_image(self): - request = fakes.HTTPRequestV3.blank('/v3/os-images/124') - request.method = 'DELETE' - response = self.controller.delete(request, '124') - self.assertEqual(response.status_int, 204) - - def test_delete_deleted_image(self): - """If you try to delete a deleted image, you get back 403 Forbidden.""" - - deleted_image_id = 128 - # see nova.tests.api.openstack.fakes:_make_image_fixtures - - 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.HTTPRequestV3.blank('/v3/os-images/300') - request.method = 'DELETE' - self.assertRaises(webob.exc.HTTPNotFound, - self.controller.delete, request, '300') - - -class ImageXMLSerializationTest(test.TestCase): - - TIMESTAMP = "2010-10-11T10:30:22Z" - SERVER_UUID = 'aa640691-d1a7-4a67-9d3c-d35ee6b3cc74' - 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 setUp(self): - super(ImageXMLSerializationTest, self).setUp() - self.fixture = { - 'image': { - 'id': 1, - 'name': 'Image1', - 'created': self.TIMESTAMP, - 'updated': self.TIMESTAMP, - 'status': 'ACTIVE', - 'progress': 80, - 'server': { - 'id': self.SERVER_UUID, - 'links': [ - { - 'href': self.SERVER_HREF, - 'rel': 'self', - }, - { - 'href': self.SERVER_BOOKMARK, - 'rel': 'bookmark', - }, - ], - }, - 'metadata': { - 'key1': 'value1', - }, - 'links': [ - { - 'href': self.IMAGE_HREF % 1, - 'rel': 'self', - }, - { - 'href': self.IMAGE_BOOKMARK % 1, - 'rel': 'bookmark', - }, - ], - }, - } - image = self.fixture['image'] - image['id'] = '2' - image['name'] = 'Image2' - image['status'] = 'SAVING' - image['links'][0]['href'] = self.IMAGE_HREF % 2 - image['links'][1]['href'] = self.IMAGE_BOOKMARK % 2 - self.fixture_dict = { - 'images': [ - self.fixture['image'], - image, - ], - 'images_links': [ - { - 'rel': 'next', - 'href': self.IMAGE_NEXT % (2, 2), - } - ], - } - self.serializer = images.ImageTemplate() - - def test_xml_declaration(self): - output = self.serializer.serialize(self.fixture) - has_dec = output.startswith("") - self.assertTrue(has_dec) - - def test_show(self): - self.fixture['image']['minRam'] = 10 - self.fixture['image']['minDisk'] = 100 - output = self.serializer.serialize(self.fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'image') - image_dict = self.fixture['image'] - - self._assertElementAndLinksEquals(root, image_dict, ['name', 'id', - 'updated', 'created', 'status', - 'progress']) - self._assertMetadataEquals(root, image_dict) - self._assertServerIdAndLinksEquals(root, image_dict) - - def test_show_zero_metadata(self): - self.fixture['image']['metadata'] = {} - output = self.serializer.serialize(self.fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'image') - image_dict = self.fixture['image'] - - meta_nodes = root.findall('{0}meta'.format(ATOMNS)) - self.assertEqual(len(meta_nodes), 0) - self._assertElementAndLinksEquals(root, image_dict, ['name', 'id', - 'updated', 'created', 'status']) - self._assertServerIdAndLinksEquals(root, image_dict) - - def test_show_image_no_metadata_key(self): - del self.fixture['image']['metadata'] - output = self.serializer.serialize(self.fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'image') - image_dict = self.fixture['image'] - - meta_nodes = root.findall('{0}meta'.format(ATOMNS)) - self.assertEqual(len(meta_nodes), 0) - self._assertElementAndLinksEquals(root, image_dict, ['name', 'id', - 'updated', 'created', 'status']) - self._assertServerIdAndLinksEquals(root, image_dict) - - def test_show_no_server(self): - del self.fixture['image']['server'] - output = self.serializer.serialize(self.fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'image') - image_dict = self.fixture['image'] - - server_root = root.find('{0}server'.format(NS)) - self.assertIsNone(server_root) - self._assertElementAndLinksEquals(root, image_dict, ['name', 'id', - 'updated', 'created', 'status']) - self._assertMetadataEquals(root, image_dict) - - def test_show_with_min_ram(self): - self.fixture['image']['minRam'] = 256 - output = self.serializer.serialize(self.fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'image') - image_dict = self.fixture['image'] - - self._assertElementAndLinksEquals(root, image_dict, ['name', 'id', - 'updated', 'created', 'status', - 'progress', 'minRam']) - self._assertMetadataEquals(root, image_dict) - self._assertServerIdAndLinksEquals(root, image_dict) - - def test_show_with_min_disk(self): - self.fixture['image']['minDisk'] = 5 - output = self.serializer.serialize(self.fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'image') - image_dict = self.fixture['image'] - - self._assertElementAndLinksEquals(root, image_dict, ['name', 'id', - 'updated', 'created', 'status', - 'progress', 'minDisk']) - self._assertMetadataEquals(root, image_dict) - self._assertServerIdAndLinksEquals(root, image_dict) - - def test_index(self): - serializer = images.MinimalImagesTemplate() - output = serializer.serialize(self.fixture_dict) - root = etree.XML(output) - xmlutil.validate_schema(root, 'images_index') - image_elems = root.findall('{0}image'.format(NS)) - self.assertEqual(len(image_elems), 2) - for i, image_elem in enumerate(image_elems): - image_dict = self.fixture_dict['images'][i] - - self._assertElementAndLinksEquals(image_elem, image_dict, ['name', - 'id']) - - def test_index_with_links(self): - serializer = images.MinimalImagesTemplate() - output = serializer.serialize(self.fixture_dict) - root = etree.XML(output) - xmlutil.validate_schema(root, 'images_index') - image_elems = root.findall('{0}image'.format(NS)) - self.assertEqual(len(image_elems), 2) - for i, image_elem in enumerate(image_elems): - image_dict = self.fixture_dict['images'][i] - - self._assertElementAndLinksEquals(image_elem, image_dict, ['name', - 'id']) - - images_links = root.findall('{0}link'.format(ATOMNS)) - for i, link in enumerate(self.fixture_dict['images_links']): - for key, value in link.items(): - self.assertEqual(images_links[i].get(key), value) - - def test_index_zero_images(self): - serializer = images.MinimalImagesTemplate() - del self.fixture_dict['images'] - output = serializer.serialize(self.fixture_dict) - root = etree.XML(output) - xmlutil.validate_schema(root, 'images_index') - image_elems = root.findall('{0}image'.format(NS)) - self.assertEqual(len(image_elems), 0) - - def test_detail(self): - serializer = images.ImagesTemplate() - output = serializer.serialize(self.fixture_dict) - root = etree.XML(output) - xmlutil.validate_schema(root, 'images') - image_elems = root.findall('{0}image'.format(NS)) - self.assertEqual(len(image_elems), 2) - for i, image_elem in enumerate(image_elems): - image_dict = self.fixture_dict['images'][i] - - self._assertElementAndLinksEquals(image_elem, image_dict, ['name', - 'id', 'updated', 'created', - 'status']) - - def _assertElementAndLinksEquals(self, elem, dict, keys): - for key in keys: - self.assertEquals(elem.get(key), str(dict[key])) - self._assertLinksEquals(elem, dict) - - def _assertLinksEquals(self, root, dict): - link_nodes = root.findall('{0}link'.format(ATOMNS)) - self.assertEqual(len(link_nodes), 2) - for i, link in enumerate(dict['links']): - for key, value in link.items(): - self.assertEqual(link_nodes[i].get(key), value) - - def _assertServerIdAndLinksEquals(self, root, dict): - server_root = root.find('{0}server'.format(NS)) - self.assertEqual(server_root.get('id'), dict['server']['id']) - self._assertLinksEquals(server_root, dict['server']) - - def _assertMetadataEquals(self, root, dict): - metadata_root = root.find('{0}metadata'.format(NS)) - metadata_elems = metadata_root.findall('{0}meta'.format(NS)) - self.assertEqual(len(metadata_elems), 1) - for i, metadata_elem in enumerate(metadata_elems): - (meta_key, meta_value) = dict['metadata'].items()[i] - self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) - self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_servers.py b/nova/tests/api/openstack/compute/plugins/v3/test_servers.py index 8af8e1ab3b39..033efbb82b9e 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_servers.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_servers.py @@ -188,13 +188,9 @@ class ControllerTest(test.TestCase): class ServersControllerTest(ControllerTest): - # def test_can_check_loaded_extensions(self): - #self.ext_mgr.extensions = {'os-fake': None} - #self.assertTrue(self.controller.ext_mgr.is_loaded('os-fake')) - #self.assertFalse(self.controller.ext_mgr.is_loaded('os-not-loaded')) - def setUp(self): super(ServersControllerTest, self).setUp() + CONF.set_override('glance_host', 'localhost') nova_utils.reset_is_neutron() def test_requested_networks_prefix(self): @@ -348,7 +344,7 @@ class ServersControllerTest(ControllerTest): def test_get_server_by_id(self): self.flags(use_ipv6=True) - image_bookmark = "http://localhost/images/10" + image_bookmark = "http://localhost:9292/images/10" flavor_bookmark = "http://localhost/flavors/1" uuid = FAKE_UUID @@ -364,7 +360,7 @@ class ServersControllerTest(ControllerTest): self.assertThat(res_dict, matchers.DictMatches(expected_server)) def test_get_server_with_active_status_by_id(self): - image_bookmark = "http://localhost/images/10" + image_bookmark = "http://localhost:9292/images/10" flavor_bookmark = "http://localhost/flavors/1" new_return_server = fakes.fake_instance_get( @@ -381,7 +377,7 @@ class ServersControllerTest(ControllerTest): def test_get_server_with_id_image_ref_by_id(self): image_ref = "10" - image_bookmark = "http://localhost/images/10" + image_bookmark = "http://localhost:9292/images/10" flavor_id = "1" flavor_bookmark = "http://localhost/flavors/1" @@ -1040,7 +1036,7 @@ class ServersControllerTest(ControllerTest): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/10', + "href": 'http://localhost:9292/images/10', }, ], } @@ -3297,6 +3293,7 @@ class ServersViewBuilderTest(test.TestCase): def setUp(self): super(ServersViewBuilderTest, self).setUp() + CONF.set_override('glance_host', 'localhost') self.flags(use_ipv6=True) self.instance = fakes.stub_instance( id=1, @@ -3381,7 +3378,7 @@ class ServersViewBuilderTest(test.TestCase): self.assertThat(output, matchers.DictMatches(expected_server)) def test_build_server_detail(self): - image_bookmark = "http://localhost/images/5" + image_bookmark = "http://localhost:9292/images/5" flavor_bookmark = "http://localhost/flavors/1" self_link = "http://localhost/v3/servers/%s" % self.uuid bookmark_link = "http://localhost/servers/%s" % self.uuid @@ -3456,7 +3453,7 @@ class ServersViewBuilderTest(test.TestCase): 'created_at': datetime.datetime(2010, 10, 10, 12, 0, 0), } - image_bookmark = "http://localhost/images/5" + image_bookmark = "http://localhost:9292/images/5" flavor_bookmark = "http://localhost/flavors/1" self_link = "http://localhost/v3/servers/%s" % self.uuid bookmark_link = "http://localhost/servers/%s" % self.uuid @@ -3591,7 +3588,7 @@ class ServersViewBuilderTest(test.TestCase): 'created_at': datetime.datetime(2010, 10, 10, 12, 0, 0), } - image_bookmark = "http://localhost/images/5" + image_bookmark = "http://localhost:9292/images/5" flavor_bookmark = "http://localhost/flavors/1" self_link = "http://localhost/v3/servers/%s" % self.uuid bookmark_link = "http://localhost/servers/%s" % self.uuid @@ -3603,7 +3600,7 @@ class ServersViewBuilderTest(test.TestCase): #set the power state of the instance to running self.instance['vm_state'] = vm_states.ACTIVE self.instance['progress'] = 100 - image_bookmark = "http://localhost/images/5" + image_bookmark = "http://localhost:9292/images/5" flavor_bookmark = "http://localhost/flavors/1" self_link = "http://localhost/v3/servers/%s" % self.uuid bookmark_link = "http://localhost/servers/%s" % self.uuid @@ -3667,7 +3664,7 @@ class ServersViewBuilderTest(test.TestCase): self.instance['access_ip_v4'] = '1.2.3.4' - image_bookmark = "http://localhost/images/5" + image_bookmark = "http://localhost:9292/images/5" flavor_bookmark = "http://localhost/flavors/1" self_link = "http://localhost/v3/servers/%s" % self.uuid bookmark_link = "http://localhost/servers/%s" % self.uuid @@ -3731,7 +3728,7 @@ class ServersViewBuilderTest(test.TestCase): self.instance['access_ip_v6'] = 'fead::1234' - image_bookmark = "http://localhost/images/5" + image_bookmark = "http://localhost:9292/images/5" flavor_bookmark = "http://localhost/flavors/1" self_link = "http://localhost/v3/servers/%s" % self.uuid bookmark_link = "http://localhost/servers/%s" % self.uuid @@ -3797,7 +3794,7 @@ class ServersViewBuilderTest(test.TestCase): metadata.append(models.InstanceMetadata(key="Open", value="Stack")) self.instance['metadata'] = metadata - image_bookmark = "http://localhost/images/5" + image_bookmark = "http://localhost:9292/images/5" flavor_bookmark = "http://localhost/flavors/1" self_link = "http://localhost/v3/servers/%s" % self.uuid bookmark_link = "http://localhost/servers/%s" % self.uuid @@ -3864,7 +3861,7 @@ class ServerXMLSerializationTest(test.TestCase): SERVER_HREF = 'http://localhost/v3/servers/%s' % FAKE_UUID SERVER_NEXT = 'http://localhost/v3/servers?limit=%s&marker=%s' SERVER_BOOKMARK = 'http://localhost/servers/%s' % FAKE_UUID - IMAGE_BOOKMARK = 'http://localhost/images/5' + IMAGE_BOOKMARK = 'http://localhost:9292/images/5' FLAVOR_BOOKMARK = 'http://localhost/flavors/1' def test_xml_declaration(self): diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index e0c14c4d5ab5..cdab3d36f38c 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -203,8 +203,6 @@ policy_data = """ "compute_extension:hypervisors": "", "compute_extension:v3:os-hypervisors": "rule:admin_api", "compute_extension:image_size": "", - "compute_extension:v3:os-image-metadata": "", - "compute_extension:v3:os-images": "", "compute_extension:instance_actions": "", "compute_extension:v3:os-instance-actions": "", "compute_extension:instance_actions:events": "is_admin:True", diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 6f8647768113..c534110f3df1 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -21,6 +21,7 @@ import urlparse from nova.openstack.common.gettextutils import _ from nova.openstack.common import jsonutils from nova.openstack.common import log as logging +from nova.tests.image import fake LOG = logging.getLogger(__name__) @@ -305,22 +306,26 @@ class TestOpenStackClientV3(TestOpenStackClient): """Simple OpenStack v3 API Client. This is a really basic OpenStack API client that is under our control, - so we can make changes / insert hooks for testing + so we can make changes / insert hooks for testing. + + Note that the V3 API does not have an image API and so it is + not possible to query the api for the image information. + So instead we just access the fake image service used by the unittests + directly. """ def get_image(self, image_id): - return self.api_get('/os-images/%s' % image_id)['image'] + return fake._fakeImageService.show(None, image_id) def get_images(self, detail=True): - rel_url = '/os-images/detail' if detail else '/os-images' - return self.api_get(rel_url)['images'] + return fake._fakeImageService.detail(None) def post_image(self, image): - return self.api_post('/os-images', image)['image'] + raise NotImplementedError def delete_image(self, image_id): - return self.api_delete('/os-images/%s' % image_id) + return fake._fakeImageService.delete(None, image_id) class TestOpenStackClientV3Mixin(object): diff --git a/nova/tests/integrated/v3/api_samples/servers/server-get-resp.json.tpl b/nova/tests/integrated/v3/api_samples/servers/server-get-resp.json.tpl index b8b65d750f86..6e31aae6f67c 100644 --- a/nova/tests/integrated/v3/api_samples/servers/server-get-resp.json.tpl +++ b/nova/tests/integrated/v3/api_samples/servers/server-get-resp.json.tpl @@ -28,7 +28,7 @@ "id": "%(uuid)s", "links": [ { - "href": "%(host)s/images/%(uuid)s", + "href": "%(glance_host)s/images/%(uuid)s", "rel": "bookmark" } ] diff --git a/nova/tests/integrated/v3/api_samples/servers/server-get-resp.xml.tpl b/nova/tests/integrated/v3/api_samples/servers/server-get-resp.xml.tpl index 557c5f610769..5cfb01fa5211 100644 --- a/nova/tests/integrated/v3/api_samples/servers/server-get-resp.xml.tpl +++ b/nova/tests/integrated/v3/api_samples/servers/server-get-resp.xml.tpl @@ -1,7 +1,7 @@ - + diff --git a/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.json.tpl b/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.json.tpl index 305b914da4d8..dce3ded800cb 100644 --- a/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.json.tpl +++ b/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.json.tpl @@ -29,7 +29,7 @@ "id": "%(uuid)s", "links": [ { - "href": "%(host)s/images/%(uuid)s", + "href": "%(glance_host)s/images/%(uuid)s", "rel": "bookmark" } ] diff --git a/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.xml.tpl b/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.xml.tpl index f1395d9cced4..d9e0234e789f 100644 --- a/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.xml.tpl +++ b/nova/tests/integrated/v3/api_samples/servers/servers-details-resp.xml.tpl @@ -2,7 +2,7 @@ - + diff --git a/setup.cfg b/setup.cfg index 8419bbe09213..df7d33995869 100644 --- a/setup.cfg +++ b/setup.cfg @@ -85,8 +85,6 @@ nova.api.v3.extensions = hide_server_addresses = nova.api.openstack.compute.plugins.v3.hide_server_addresses:HideServerAddresses hosts = nova.api.openstack.compute.plugins.v3.hosts:Hosts hypervisors = nova.api.openstack.compute.plugins.v3.hypervisors:Hypervisors - image_metadata = nova.api.openstack.compute.plugins.v3.image_metadata:ImageMetadata - images = nova.api.openstack.compute.plugins.v3.images:Images instance_actions = nova.api.openstack.compute.plugins.v3.instance_actions:InstanceActions ips = nova.api.openstack.compute.plugins.v3.ips:IPs instance_usage_audit_log = nova.api.openstack.compute.plugins.v3.instance_usage_audit_log:InstanceUsageAuditLog