Merge "Add next links to images requests"

This commit is contained in:
Jenkins
2011-09-26 15:55:49 +00:00
committed by Gerrit Code Review
4 changed files with 327 additions and 17 deletions

View File

@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import urlparse
import os.path
from lxml import etree
@@ -149,8 +148,7 @@ class ControllerV10(Controller):
filters = self._get_filters(req)
images = self._image_service.index(context, filters=filters)
images = common.limited(images, req)
builder = self.get_builder(req).build
return dict(images=[builder(image, detail=False) for image in images])
return self.get_builder(req).build_list(images)
def detail(self, req):
"""Return a detailed index listing of images available to the request.
@@ -183,11 +181,14 @@ class ControllerV11(Controller):
"""
context = req.environ['nova.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
images = self._image_service.index(context, filters=filters,
**page_params)
builder = self.get_builder(req).build
return dict(images=[builder(image, detail=False) for image in images])
return self.get_builder(req).build_list(images, **params)
def detail(self, req):
"""Return a detailed index listing of images available to the request.
@@ -197,11 +198,14 @@ class ControllerV11(Controller):
"""
context = req.environ['nova.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
images = self._image_service.detail(context, filters=filters,
**page_params)
builder = self.get_builder(req).build
return dict(images=[builder(image, detail=True) for image in images])
return self.get_builder(req).build_list(images, detail=True, **params)
def create(self, *args, **kwargs):
raise webob.exc.HTTPMethodNotAllowed()
@@ -253,20 +257,23 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer):
image_dict.get('metadata', {}))
image_elem.append(meta_elem)
for link in image_dict.get('links', []):
elem = etree.SubElement(image_elem,
'{%s}link' % xmlutil.XMLNS_ATOM)
self._populate_links(image_elem, image_dict.get('links', []))
def _populate_links(self, parent, links):
for link in links:
elem = etree.SubElement(parent, '{%s}link' % xmlutil.XMLNS_ATOM)
elem.set('rel', link['rel'])
if 'type' in link:
elem.set('type', link['type'])
elem.set('href', link['href'])
return image_elem
def index(self, images_dict):
images = etree.Element('images', nsmap=self.NSMAP)
for image_dict in images_dict['images']:
image = etree.SubElement(images, 'image')
self._populate_image(image, image_dict, False)
self._populate_links(images, images_dict.get('images_links', []))
return self._to_xml(images)
def detail(self, images_dict):

View File

@@ -9,4 +9,7 @@
</zeroOrMore>
</element>
</zeroOrMore>
<zeroOrMore>
<externalRef href="../atom-link.rng"/>
</zeroOrMore>
</element>

View File

@@ -59,6 +59,15 @@ class ViewBuilder(object):
"""Return an href string pointing to this object."""
return os.path.join(self.base_url, "images", str(image_id))
def build_list(self, image_objs, detail=False, **kwargs):
"""Return a standardized image list structure for display."""
images = []
for image_obj in image_objs:
image = self.build(image_obj, detail=detail)
images.append(image)
return dict(images=images)
def build(self, image_obj, detail=False):
"""Return a standardized image structure for display by the API."""
self._format_dates(image_obj)
@@ -135,6 +144,33 @@ class ViewBuilderV11(ViewBuilder):
return os.path.join(self.base_url, self.project_id,
"images", str(image_id))
def generate_next_link(self, image_id, params):
""" Return an href string with proper limit and marker params"""
params['marker'] = image_id
return "%s?%s" % (
os.path.join(self.base_url, self.project_id, "images"),
common.dict_to_query_str(params))
def build_list(self, image_objs, detail=False, **kwargs):
"""Return a standardized image list structure for display."""
limit = kwargs.get('limit', None)
images = []
images_links = []
for image_obj in image_objs:
image = self.build(image_obj, detail=detail)
images.append(image)
if (len(images) and limit) and (limit == len(images)):
next_link = self.generate_next_link(images[-1]["id"], kwargs)
images_links = [dict(rel="next", href=next_link)]
reval = dict(images=images)
if len(images_links) > 0:
reval['images_links'] = images_links
return reval
def build(self, image_obj, detail=False):
"""Return a standardized image structure for display by the API."""
image = ViewBuilder.build(self, image_obj, detail)

View File

@@ -21,12 +21,12 @@ and as a WSGI layer
"""
import copy
from lxml import etree
import json
import xml.dom.minidom as minidom
from lxml import etree
import mox
import stubout
import urlparse
import webob
from nova import context
@@ -284,10 +284,12 @@ class ImagesTest(test.TestCase):
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
response = request.get_response(app)
print response.body
response_dict = json.loads(response.body)
response_list = response_dict["images"]
self.assertTrue('images_links' not in response_dict)
expected = [
expected_images = [
{
"id": "123",
"name": "public image",
@@ -450,7 +452,118 @@ class ImagesTest(test.TestCase):
},
]
self.assertDictListMatch(response_list, expected)
self.assertDictListMatch(response_list, expected_images)
def test_get_image_index_v1_1_with_limit(self):
request = webob.Request.blank('/v1.1/fake/images?limit=3')
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
response = request.get_response(app)
print response.body
response_dict = json.loads(response.body)
response_list = response_dict["images"]
response_links = response_dict["images_links"]
alternate = "%s/fake/images/%s"
expected_images = [
{
"id": "123",
"name": "public image",
"links": [
{
"rel": "self",
"href": "http://localhost/v1.1/fake/images/123",
},
{
"rel": "bookmark",
"href": "http://localhost/fake/images/123",
},
{
"rel": "alternate",
"type": "application/vnd.openstack.image",
"href": alternate % (utils.generate_glance_url(), 123),
},
],
},
{
"id": "124",
"name": "queued snapshot",
"links": [
{
"rel": "self",
"href": "http://localhost/v1.1/fake/images/124",
},
{
"rel": "bookmark",
"href": "http://localhost/fake/images/124",
},
{
"rel": "alternate",
"type": "application/vnd.openstack.image",
"href": alternate % (utils.generate_glance_url(), 124),
},
],
},
{
"id": "125",
"name": "saving snapshot",
"links": [
{
"rel": "self",
"href": "http://localhost/v1.1/fake/images/125",
},
{
"rel": "bookmark",
"href": "http://localhost/fake/images/125",
},
{
"rel": "alternate",
"type": "application/vnd.openstack.image",
"href": alternate % (utils.generate_glance_url(), 125),
},
],
},
]
self.assertDictListMatch(response_list, expected_images)
self.assertEqual(response_links[0]['rel'], 'next')
href_parts = urlparse.urlparse(response_links[0]['href'])
self.assertEqual('/v1.1/fake/images', href_parts.path)
params = urlparse.parse_qs(href_parts.query)
self.assertDictMatch({'limit': ['3'], 'marker': ['125']}, params)
def test_get_image_index_v1_1_with_limit_and_extra_params(self):
request = webob.Request.blank('/v1.1/fake/images?limit=3&extra=boo')
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
response = request.get_response(app)
response_dict = json.loads(response.body)
response_links = response_dict["images_links"]
self.assertEqual(response_links[0]['rel'], 'next')
href_parts = urlparse.urlparse(response_links[0]['href'])
self.assertEqual('/v1.1/fake/images', href_parts.path)
params = urlparse.parse_qs(href_parts.query)
self.assertDictMatch(
{'limit': ['3'], 'marker': ['125'], 'extra': ['boo']},
params)
def test_get_image_index_v1_1_with_big_limit(self):
"""
Make sure we don't get images_links if limit is set
and the number of images returned is < limit
"""
request = webob.Request.blank('/v1.1/fake/images?limit=30')
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
response = request.get_response(app)
response_dict = json.loads(response.body)
response_list = response_dict["images"]
self.assertTrue('images_links' not in response_dict)
self.assertEqual(len(response_list), 8)
def test_get_image_details(self):
request = webob.Request.blank('/v1.0/images/detail')
@@ -536,6 +649,7 @@ class ImagesTest(test.TestCase):
response_list = response_dict["images"]
server_href = "http://localhost/v1.1/servers/42"
server_bookmark = "http://localhost/servers/42"
alternate = "%s/fake/images/%s"
expected = [{
'id': '123',
@@ -558,7 +672,7 @@ class ImagesTest(test.TestCase):
{
"rel": "alternate",
"type": "application/vnd.openstack.image",
"href": "%s/fake/images/123" % utils.generate_glance_url()
"href": alternate % (utils.generate_glance_url(), 123),
}],
},
{
@@ -596,7 +710,7 @@ class ImagesTest(test.TestCase):
{
"rel": "alternate",
"type": "application/vnd.openstack.image",
"href": "%s/fake/images/124" % utils.generate_glance_url()
"href": alternate % (utils.generate_glance_url(), 124),
}],
},
{
@@ -817,6 +931,89 @@ class ImagesTest(test.TestCase):
self.assertDictListMatch(expected, response_list)
def test_get_image_details_with_limit_v1_1(self):
request = webob.Request.blank('/v1.1/fake/images/detail?limit=2')
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
response = request.get_response(app)
response_dict = json.loads(response.body)
response_list = response_dict["images"]
response_links = response_dict["images_links"]
server_href = "http://localhost/v1.1/servers/42"
server_bookmark = "http://localhost/servers/42"
alternate = "%s/fake/images/%s"
expected = [{
'id': '123',
'name': 'public image',
'metadata': {'key1': 'value1'},
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
'status': 'ACTIVE',
'minDisk': 0,
'progress': 100,
'minRam': 0,
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/fake/images/123",
},
{
"rel": "bookmark",
"href": "http://localhost/fake/images/123",
},
{
"rel": "alternate",
"type": "application/vnd.openstack.image",
"href": alternate % (utils.generate_glance_url(), 123),
}],
},
{
'id': '124',
'name': 'queued snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'fake',
},
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
'status': 'SAVING',
'minDisk': 0,
'progress': 0,
'minRam': 0,
'server': {
'id': '42',
"links": [{
"rel": "self",
"href": server_href,
},
{
"rel": "bookmark",
"href": server_bookmark,
}],
},
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/fake/images/124",
},
{
"rel": "bookmark",
"href": "http://localhost/fake/images/124",
},
{
"rel": "alternate",
"type": "application/vnd.openstack.image",
"href": alternate % (utils.generate_glance_url(), 124),
}],
}]
self.assertDictListMatch(expected, response_list)
href_parts = urlparse.urlparse(response_links[0]['href'])
self.assertEqual('/v1.1/fake/images', href_parts.path)
params = urlparse.parse_qs(href_parts.query)
self.assertDictMatch({'limit': ['2'], 'marker': ['124']}, params)
def test_image_filter_with_name(self):
image_service = self.mox.CreateMockAnything()
context = self._get_fake_context()
@@ -1115,6 +1312,7 @@ class ImageXMLSerializationTest(test.TestCase):
SERVER_HREF = 'http://localhost/v1.1/servers/123'
SERVER_BOOKMARK = 'http://localhost/servers/123'
IMAGE_HREF = 'http://localhost/v1.1/fake/images/%s'
IMAGE_NEXT = 'http://localhost/v1.1/fake/images?limit=%s&marker=%s'
IMAGE_BOOKMARK = 'http://localhost/fake/images/%s'
def test_xml_declaration(self):
@@ -1612,6 +1810,72 @@ class ImageXMLSerializationTest(test.TestCase):
for key, value in link.items():
self.assertEqual(link_nodes[i].get(key), value)
def test_index_with_links(self):
serializer = images.ImageXMLSerializer()
fixture = {
'images': [
{
'id': 1,
'name': 'Image1',
'links': [
{
'href': self.IMAGE_HREF % 1,
'rel': 'self',
},
{
'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
},
],
},
{
'id': 2,
'name': 'Image2',
'links': [
{
'href': self.IMAGE_HREF % 2,
'rel': 'self',
},
{
'href': self.IMAGE_BOOKMARK % 2,
'rel': 'bookmark',
},
],
},
],
'images_links': [
{
'rel': 'next',
'href': self.IMAGE_NEXT % (2, 2),
}
],
}
output = serializer.serialize(fixture, 'index')
print output
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 = fixture['images'][i]
for key in ['name', 'id']:
self.assertEqual(image_elem.get(key), str(image_dict[key]))
link_nodes = image_elem.findall('{0}link'.format(ATOMNS))
self.assertEqual(len(link_nodes), 2)
for i, link in enumerate(image_dict['links']):
for key, value in link.items():
self.assertEqual(link_nodes[i].get(key), value)
# Check images_links
images_links = root.findall('{0}link'.format(ATOMNS))
for i, link in enumerate(fixture['images_links']):
for key, value in link.items():
self.assertEqual(images_links[i].get(key), value)
def test_index_zero_images(self):
serializer = images.ImageXMLSerializer()