Removes V3 API images and image_metadata extensions

Removes V3 API images and image_metadata extensions as the
same functionality can be accessed directly through glance. Also
removes the associated testcases. There is a discussion about
the decision here:

http://lists.openstack.org/pipermail/openstack-dev/2013-August/012958.html

Fixes the image bookmark links in server information requests
so they point to glance

Partially implements blueprint nova-v3-api

Change-Id: Id32be0c0794b0f4dd220a928345ddf0133e9ffca
This commit is contained in:
Chris Yeoh 2013-08-07 16:21:35 +09:30
parent b7c16d696c
commit 5303209b73
19 changed files with 65 additions and 1689 deletions

View File

@ -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"
}
}

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="2013-08-09T06:37:11Z" user_id="fake" name="new-server-test" created="2013-08-09T06:37:11Z" tenant_id="openstack" access_ip_v4="" progress="0" host_id="46dd159c89cba6409878c77977eed49bdf0a76be50885bf4a2010706" id="c20ab09b-1e56-47b4-9195-1ce8f9d40f2d" access_ip_v6="">
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="2013-08-27T00:27:57Z" user_id="fake" name="new-server-test" created="2013-08-27T00:27:57Z" tenant_id="openstack" access_ip_v4="" progress="0" host_id="043dfed1b17e67d9eacf15337bb912cea5d1bda2dc3af0edda9fd1a3" id="3a39932d-11e8-485d-8bb5-5e41b87a4e64" access_ip_v6="">
<image id="70a599e0-31e7-49b7-b260-868f441e862b">
<atom:link href="http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
<atom:link href="http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
</image>
<flavor id="1">
<atom:link href="http://openstack.example.com/flavors/1" rel="bookmark"/>
@ -14,6 +14,6 @@
<ip version="4" type="fixed" addr="192.168.0.3" mac_addr="aa:bb:cc:dd:ee:ff"/>
</network>
</addresses>
<atom:link href="http://openstack.example.com/v3/servers/c20ab09b-1e56-47b4-9195-1ce8f9d40f2d" rel="self"/>
<atom:link href="http://openstack.example.com/servers/c20ab09b-1e56-47b4-9195-1ce8f9d40f2d" rel="bookmark"/>
<atom:link href="http://openstack.example.com/v3/servers/3a39932d-11e8-485d-8bb5-5e41b87a4e64" rel="self"/>
<atom:link href="http://openstack.example.com/servers/3a39932d-11e8-485d-8bb5-5e41b87a4e64" rel="bookmark"/>
</server>

View File

@ -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"
}
]

View File

@ -1,8 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<servers xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
<server status="ACTIVE" updated="2013-08-09T06:37:11Z" user_id="fake" name="new-server-test" created="2013-08-09T06:37:11Z" tenant_id="openstack" access_ip_v4="" progress="0" host_id="5b894fe4cf74b3847753a3aaee4a543a813ef6a23558737c1812c81a" id="b83bb663-0744-4ec1-a982-d928472f0e5d" access_ip_v6="">
<server status="ACTIVE" updated="2013-08-27T00:34:16Z" user_id="fake" name="new-server-test" created="2013-08-27T00:34:16Z" tenant_id="openstack" access_ip_v4="" progress="0" host_id="cf19fcc5d5d7a9ff7fb80da2db424218bc28b5b360705b97b8afaef3" id="43980ac6-fa88-44fc-b65d-87da4fa6f800" access_ip_v6="">
<image id="70a599e0-31e7-49b7-b260-868f441e862b">
<atom:link href="http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
<atom:link href="http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
</image>
<flavor id="1">
<atom:link href="http://openstack.example.com/flavors/1" rel="bookmark"/>
@ -15,7 +15,7 @@
<ip version="4" type="fixed" addr="192.168.0.3" mac_addr="aa:bb:cc:dd:ee:ff"/>
</network>
</addresses>
<atom:link href="http://openstack.example.com/v3/servers/b83bb663-0744-4ec1-a982-d928472f0e5d" rel="self"/>
<atom:link href="http://openstack.example.com/servers/b83bb663-0744-4ec1-a982-d928472f0e5d" rel="bookmark"/>
<atom:link href="http://openstack.example.com/v3/servers/43980ac6-fa88-44fc-b65d-87da4fa6f800" rel="self"/>
<atom:link href="http://openstack.example.com/servers/43980ac6-fa88-44fc-b65d-87da4fa6f800" rel="bookmark"/>
</server>
</servers>

View File

@ -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",

View File

@ -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']})

View File

@ -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 []

View File

@ -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.

View File

@ -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."""

View File

@ -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)

View File

@ -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("<?xml version='1.0' encoding='UTF-8'?>")
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))

View File

@ -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):

View File

@ -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",

View File

@ -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):

View File

@ -28,7 +28,7 @@
"id": "%(uuid)s",
"links": [
{
"href": "%(host)s/images/%(uuid)s",
"href": "%(glance_host)s/images/%(uuid)s",
"rel": "bookmark"
}
]

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="%(timestamp)s" user_id="fake" name="new-server-test" created="%(timestamp)s" tenant_id="openstack" access_ip_v4="" progress="0" host_id="%(hostid)s" id="%(id)s" access_ip_v6="">
<image id="%(uuid)s">
<atom:link href="%(host)s/images/%(uuid)s" rel="bookmark"/>
<atom:link href="%(glance_host)s/images/%(uuid)s" rel="bookmark"/>
</image>
<flavor id="1">
<atom:link href="%(host)s/flavors/1" rel="bookmark"/>

View File

@ -29,7 +29,7 @@
"id": "%(uuid)s",
"links": [
{
"href": "%(host)s/images/%(uuid)s",
"href": "%(glance_host)s/images/%(uuid)s",
"rel": "bookmark"
}
]

View File

@ -2,7 +2,7 @@
<servers xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
<server status="ACTIVE" updated="%(timestamp)s" host_id="%(hostid)s" name="new-server-test" created="%(timestamp)s" user_id="fake" tenant_id="openstack" access_ip_v4="" access_ip_v6="" progress="0" id="%(id)s">
<image id="%(uuid)s">
<atom:link href="%(host)s/images/%(uuid)s" rel="bookmark"/>
<atom:link href="%(glance_host)s/images/%(uuid)s" rel="bookmark"/>
</image>
<flavor id="1">
<atom:link href="%(host)s/flavors/1" rel="bookmark"/>

View File

@ -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