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:
parent
b7c16d696c
commit
5303209b73
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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>
|
|
@ -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",
|
||||
|
|
|
@ -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']})
|
|
@ -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 []
|
|
@ -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.
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
|
@ -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):
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(host)s/images/%(uuid)s",
|
||||
"href": "%(glance_host)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(host)s/images/%(uuid)s",
|
||||
"href": "%(glance_host)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue