Collection named based on resource type

To be more consistent with other existing OpenStack APIs, the collection
name is now based on the requested resource type. Also, the link to the
next subset of the collection is now an attribute on the root document
of the request body.

Change-Id: Ie0f99d975b691aad7cd39fddd7d141f95c7912f8
Closes-Bug: #1227928
This commit is contained in:
Lucas Alvares Gomes 2013-09-23 11:32:09 +01:00
parent 92413d7246
commit 59c2862d65
7 changed files with 77 additions and 85 deletions

View File

@ -91,15 +91,17 @@ class Chassis(base.APIBase):
class ChassisCollection(collection.Collection):
"""API representation of a collection of chassis."""
items = [Chassis]
chassis = [Chassis]
"A list containing chassis objects"
def __init__(self, **kwargs):
self._type = 'chassis'
@classmethod
def convert_with_links(cls, chassis, limit, **kwargs):
collection = ChassisCollection()
collection.type = 'chassis'
collection.items = [Chassis.convert_with_links(ch) for ch in chassis]
collection.links = collection.make_links(limit, 'chassis', **kwargs)
collection.chassis = [Chassis.convert_with_links(ch) for ch in chassis]
collection.next = collection.get_next(limit, **kwargs)
return collection
@ -200,10 +202,9 @@ class ChassisController(rest.RestController):
sort_key=sort_key,
sort_dir=sort_dir)
collection = node.NodeCollection()
collection.type = 'node'
collection.items = [node.Node.convert_with_links(n) for n in nodes]
collection.nodes = [node.Node.convert_with_links(n) for n in nodes]
resource_url = '/'.join(['chassis', chassis_uuid, 'nodes'])
collection.links = collection.make_links(limit, resource_url,
sort_key=sort_key,
sort_dir=sort_dir)
collection.next = collection.get_next(limit, url=resource_url,
sort_key=sort_key,
sort_dir=sort_dir)
return collection

View File

@ -25,29 +25,27 @@ from ironic.api.controllers.v1 import link
class Collection(base.APIBase):
links = [link.Link]
"A list containing a link to retrieve the next subset of the collection"
next = wtypes.text
"A link to retrieve the next subset of the collection"
type = wtypes.text
"The type of the collection"
def _check_items(self):
if not hasattr(self, 'items') or self.items == wtypes.Unset:
raise AttributeError(_("Collection items are uninitialized"))
@property
def collection(self):
return getattr(self, self._type)
def has_next(self, limit):
self._check_items()
return len(self.items) and len(self.items) == limit
"""Return whether collection has more items."""
return len(self.collection) and len(self.collection) == limit
def make_links(self, limit, res_name, **kwargs):
self._check_items()
links = []
if self.has_next(limit):
q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs])
next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % {
'args': q_args, 'limit': limit,
'marker': self.items[-1].uuid}
links = [link.Link.make_link('next', pecan.request.host_url,
res_name, next_args)
]
return links
def get_next(self, limit, url=None, **kwargs):
"""Return a link to the next subset of the collection."""
if not self.has_next(limit):
return wtypes.Unset
resource_url = url or self._type
q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs])
next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % {
'args': q_args, 'limit': limit,
'marker': self.collection[-1].uuid}
return link.Link.make_link('next', pecan.request.host_url,
resource_url, next_args).href

View File

@ -245,15 +245,17 @@ class Node(base.APIBase):
class NodeCollection(collection.Collection):
"""API representation of a collection of nodes."""
items = [Node]
nodes = [Node]
"A list containing nodes objects"
def __init__(self, **kwargs):
self._type = 'nodes'
@classmethod
def convert_with_links(cls, nodes, limit, **kwargs):
collection = NodeCollection()
collection.type = 'node'
collection.items = [Node.convert_with_links(n) for n in nodes]
collection.links = collection.make_links(limit, 'nodes', **kwargs)
collection.nodes = [Node.convert_with_links(n) for n in nodes]
collection.next = collection.get_next(limit, **kwargs)
return collection
@ -425,10 +427,9 @@ class NodesController(rest.RestController):
sort_key=sort_key,
sort_dir=sort_dir)
collection = port.PortCollection()
collection.type = 'port'
collection.items = [port.Port.convert_with_links(n) for n in ports]
collection.ports = [port.Port.convert_with_links(n) for n in ports]
resource_url = '/'.join(['nodes', node_uuid, 'ports'])
collection.links = collection.make_links(limit, resource_url,
sort_key=sort_key,
sort_dir=sort_dir)
collection.next = collection.get_next(limit, url=resource_url,
sort_key=sort_key,
sort_dir=sort_dir)
return collection

View File

@ -75,15 +75,17 @@ class Port(base.APIBase):
class PortCollection(collection.Collection):
"""API representation of a collection of ports."""
items = [Port]
ports = [Port]
"A list containing ports objects"
def __init__(self, **kwargs):
self._type = 'ports'
@classmethod
def convert_with_links(cls, ports, limit, **kwargs):
collection = PortCollection()
collection.type = 'port'
collection.items = [Port.convert_with_links(p) for p in ports]
collection.links = collection.make_links(limit, 'ports', **kwargs)
collection.ports = [Port.convert_with_links(p) for p in ports]
collection.next = collection.get_next(limit, **kwargs)
return collection

View File

@ -25,13 +25,13 @@ class TestListChassis(base.FunctionalTest):
def test_empty(self):
data = self.get_json('/chassis')
self.assertEqual([], data['items'])
self.assertEqual([], data['chassis'])
def test_one(self):
ndict = dbutils.get_test_chassis()
chassis = self.dbapi.create_chassis(ndict)
data = self.get_json('/chassis')
self.assertEqual(chassis['uuid'], data['items'][0]["uuid"])
self.assertEqual(chassis['uuid'], data['chassis'][0]["uuid"])
def test_many(self):
ch_list = []
@ -41,9 +41,9 @@ class TestListChassis(base.FunctionalTest):
chassis = self.dbapi.create_chassis(ndict)
ch_list.append(chassis['uuid'])
data = self.get_json('/chassis')
self.assertEqual(len(ch_list), len(data['items']))
self.assertEqual(len(ch_list), len(data['chassis']))
uuids = [n['uuid'] for n in data['items']]
uuids = [n['uuid'] for n in data['chassis']]
self.assertEqual(ch_list.sort(), uuids.sort())
def test_links(self):
@ -63,12 +63,10 @@ class TestListChassis(base.FunctionalTest):
ch = self.dbapi.create_chassis(ndict)
chassis.append(ch['uuid'])
data = self.get_json('/chassis/?limit=3')
self.assertEqual(data['type'], 'chassis')
self.assertEqual(len(data['items']), 3)
self.assertEqual(len(data['chassis']), 3)
next_marker = data['items'][-1]['uuid']
next_link = [l['href'] for l in data['links'] if l['rel'] == 'next'][0]
self.assertIn(next_marker, next_link)
next_marker = data['chassis'][-1]['uuid']
self.assertIn(next_marker, data['next'])
def test_nodes_subresource_link(self):
ndict = dbutils.get_test_chassis()
@ -87,15 +85,13 @@ class TestListChassis(base.FunctionalTest):
self.dbapi.create_node(ndict)
data = self.get_json('/chassis/%s/nodes' % cdict['uuid'])
self.assertEqual(data['type'], 'node')
self.assertEqual(len(data['items']), 2)
self.assertEqual(len(data['links']), 0)
self.assertEqual(len(data['nodes']), 2)
self.assertNotIn('next', data.keys())
# Test collection pagination
data = self.get_json('/chassis/%s/nodes?limit=1' % cdict['uuid'])
self.assertEqual(data['type'], 'node')
self.assertEqual(len(data['items']), 1)
self.assertEqual(len(data['links']), 1)
self.assertEqual(len(data['nodes']), 1)
self.assertIn('next', data.keys())
class TestPatch(base.FunctionalTest):
@ -222,8 +218,8 @@ class TestPost(base.FunctionalTest):
self.post_json('/chassis', cdict)
result = self.get_json('/chassis')
self.assertEqual(cdict['description'],
result['items'][0]['description'])
self.assertTrue(uuidutils.is_uuid_like(result['items'][0]['uuid']))
result['chassis'][0]['description'])
self.assertTrue(uuidutils.is_uuid_like(result['chassis'][0]['uuid']))
class TestDelete(base.FunctionalTest):

View File

@ -31,13 +31,13 @@ class TestListNodes(base.FunctionalTest):
def test_empty(self):
data = self.get_json('/nodes')
self.assertEqual([], data['items'])
self.assertEqual([], data['nodes'])
def test_one(self):
ndict = dbutils.get_test_node()
node = self.dbapi.create_node(ndict)
data = self.get_json('/nodes')
self.assertEqual(node['uuid'], data['items'][0]["uuid"])
self.assertEqual(node['uuid'], data['nodes'][0]["uuid"])
def test_many(self):
nodes = []
@ -47,9 +47,9 @@ class TestListNodes(base.FunctionalTest):
node = self.dbapi.create_node(ndict)
nodes.append(node['uuid'])
data = self.get_json('/nodes')
self.assertEqual(len(nodes), len(data['items']))
self.assertEqual(len(nodes), len(data['nodes']))
uuids = [n['uuid'] for n in data['items']]
uuids = [n['uuid'] for n in data['nodes']]
self.assertEqual(nodes.sort(), uuids.sort())
def test_links(self):
@ -69,12 +69,10 @@ class TestListNodes(base.FunctionalTest):
node = self.dbapi.create_node(ndict)
nodes.append(node['uuid'])
data = self.get_json('/nodes/?limit=3')
self.assertEqual(data['type'], 'node')
self.assertEqual(len(data['items']), 3)
self.assertEqual(len(data['nodes']), 3)
next_marker = data['items'][-1]['uuid']
next_link = [l['href'] for l in data['links'] if l['rel'] == 'next'][0]
self.assertIn(next_marker, next_link)
next_marker = data['nodes'][-1]['uuid']
self.assertIn(next_marker, data['next'])
def test_ports_subresource_link(self):
ndict = dbutils.get_test_node()
@ -93,15 +91,13 @@ class TestListNodes(base.FunctionalTest):
self.dbapi.create_port(pdict)
data = self.get_json('/nodes/%s/ports' % ndict['uuid'])
self.assertEqual(data['type'], 'port')
self.assertEqual(len(data['items']), 2)
self.assertEqual(len(data['links']), 0)
self.assertEqual(len(data['ports']), 2)
self.assertNotIn('next', data.keys())
# Test collection pagination
data = self.get_json('/nodes/%s/ports?limit=1' % ndict['uuid'])
self.assertEqual(data['type'], 'port')
self.assertEqual(len(data['items']), 1)
self.assertEqual(len(data['links']), 1)
self.assertEqual(len(data['ports']), 1)
self.assertIn('next', data.keys())
def test_state(self):
ndict = dbutils.get_test_node()

View File

@ -25,13 +25,13 @@ class TestListPorts(base.FunctionalTest):
def test_empty(self):
data = self.get_json('/ports')
self.assertEqual([], data['items'])
self.assertEqual([], data['ports'])
def test_one(self):
ndict = dbutils.get_test_port()
port = self.dbapi.create_port(ndict)
data = self.get_json('/ports')
self.assertEqual(port['uuid'], data['items'][0]["uuid"])
self.assertEqual(port['uuid'], data['ports'][0]["uuid"])
def test_many(self):
ports = []
@ -41,9 +41,9 @@ class TestListPorts(base.FunctionalTest):
port = self.dbapi.create_port(ndict)
ports.append(port['uuid'])
data = self.get_json('/ports')
self.assertEqual(len(ports), len(data['items']))
self.assertEqual(len(ports), len(data['ports']))
uuids = [n['uuid'] for n in data['items']]
uuids = [n['uuid'] for n in data['ports']]
self.assertEqual(ports.sort(), uuids.sort())
def test_links(self):
@ -63,12 +63,10 @@ class TestListPorts(base.FunctionalTest):
port = self.dbapi.create_port(ndict)
ports.append(port['uuid'])
data = self.get_json('/ports/?limit=3')
self.assertEqual(data['type'], 'port')
self.assertEqual(len(data['items']), 3)
self.assertEqual(len(data['ports']), 3)
next_marker = data['items'][-1]['uuid']
next_link = [l['href'] for l in data['links'] if l['rel'] == 'next'][0]
self.assertIn(next_marker, next_link)
next_marker = data['ports'][-1]['uuid']
self.assertIn(next_marker, data['next'])
class TestPatch(base.FunctionalTest):