Merge "API GET to return only minimal data"
This commit is contained in:
commit
98670162c7
@ -434,8 +434,9 @@ Usage
|
||||
======= ============= ==========
|
||||
Verb Path Response
|
||||
======= ============= ==========
|
||||
GET /nodes List nodes.
|
||||
GET /nodes/<id> Retrieve a specific node.
|
||||
GET /nodes List nodes
|
||||
GET /nodes/detail Lists all details for all nodes
|
||||
GET /nodes/<id> Retrieve a specific node
|
||||
POST /nodes Create a new node
|
||||
PATCH /nodes/<id> Update a node
|
||||
DELETE /nodes/<id> Delete node and all associated ports
|
||||
@ -570,6 +571,7 @@ Usage
|
||||
Verb Path Response
|
||||
======= ============= ==========
|
||||
GET /chassis List chassis
|
||||
GET /chassis/detail Lists all details for all chassis
|
||||
GET /chassis/<id> Retrieve a specific chassis
|
||||
POST /chassis Create a new chassis
|
||||
PATCH /chassis/<id> Update a chassis
|
||||
@ -635,6 +637,7 @@ Usage
|
||||
Verb Path Response
|
||||
======= ============= ==========
|
||||
GET /ports List ports
|
||||
GET /ports/detail Lists all details for all ports
|
||||
GET /ports/<id> Retrieve a specific port
|
||||
POST /ports Create a new port
|
||||
PATCH /ports/<id> Update a port
|
||||
|
@ -28,5 +28,12 @@ class APIBase(wtypes.Base):
|
||||
getattr(self, k) != wsme.Unset)
|
||||
|
||||
@classmethod
|
||||
def from_rpc_object(cls, m):
|
||||
return cls(**m.as_dict())
|
||||
def from_rpc_object(cls, m, fields=None):
|
||||
"""Convert a RPC object to an API object."""
|
||||
obj_dict = m.as_dict()
|
||||
# Unset non-required fields so they do not appear
|
||||
# in the message body
|
||||
obj_dict.update(dict((k, wsme.Unset)
|
||||
for k in obj_dict.keys()
|
||||
if fields and k not in fields))
|
||||
return cls(**obj_dict)
|
||||
|
@ -67,16 +67,20 @@ class Chassis(base.APIBase):
|
||||
setattr(self, k, kwargs.get(k))
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_chassis):
|
||||
chassis = Chassis.from_rpc_object(rpc_chassis)
|
||||
chassis.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||
def convert_with_links(cls, rpc_chassis, expand=True):
|
||||
fields = ['uuid', 'description'] if not expand else None
|
||||
chassis = Chassis.from_rpc_object(rpc_chassis, fields)
|
||||
chassis.links = [link.Link.make_link('self',
|
||||
pecan.request.host_url,
|
||||
'chassis', chassis.uuid),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'chassis', chassis.uuid,
|
||||
bookmark=True)
|
||||
'chassis', chassis.uuid)
|
||||
]
|
||||
chassis.nodes = [link.Link.make_link('self', pecan.request.host_url,
|
||||
|
||||
if expand:
|
||||
chassis.nodes = [link.Link.make_link('self',
|
||||
pecan.request.host_url,
|
||||
'chassis',
|
||||
chassis.uuid + "/nodes"),
|
||||
link.Link.make_link('bookmark',
|
||||
@ -98,38 +102,62 @@ class ChassisCollection(collection.Collection):
|
||||
self._type = 'chassis'
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, chassis, limit, **kwargs):
|
||||
def convert_with_links(cls, chassis, limit, url=None,
|
||||
expand=False, **kwargs):
|
||||
collection = ChassisCollection()
|
||||
collection.chassis = [Chassis.convert_with_links(ch) for ch in chassis]
|
||||
collection.next = collection.get_next(limit, **kwargs)
|
||||
collection.chassis = [Chassis.convert_with_links(ch, expand)
|
||||
for ch in chassis]
|
||||
url = url or None
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
|
||||
|
||||
class ChassisController(rest.RestController):
|
||||
"""REST controller for Chassis."""
|
||||
|
||||
nodes = node.NodesController(from_chassis=True)
|
||||
"Expose nodes as a sub-element of chassis"
|
||||
|
||||
_custom_actions = {
|
||||
'nodes': ['GET'],
|
||||
'detail': ['GET'],
|
||||
}
|
||||
|
||||
@wsme_pecan.wsexpose(ChassisCollection, int, unicode, unicode, unicode)
|
||||
def get_all(self, limit=None, marker=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of chassis."""
|
||||
def _get_chassis(self, marker, limit, sort_key, sort_dir):
|
||||
limit = utils.validate_limit(limit)
|
||||
sort_dir = utils.validate_sort_dir(sort_dir)
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.Chassis.get_by_uuid(pecan.request.context,
|
||||
marker)
|
||||
|
||||
chassis = pecan.request.dbapi.get_chassis_list(limit, marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return chassis
|
||||
|
||||
@wsme_pecan.wsexpose(ChassisCollection, unicode, int, unicode, unicode)
|
||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of chassis."""
|
||||
chassis = self._get_chassis(marker, limit, sort_key, sort_dir)
|
||||
return ChassisCollection.convert_with_links(chassis, limit,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(ChassisCollection, unicode, int, unicode, unicode)
|
||||
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of chassis with detail."""
|
||||
# /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
if parent != "chassis":
|
||||
raise exception.HTTPNotFound
|
||||
|
||||
chassis = self._get_chassis(marker, limit, sort_key, sort_dir)
|
||||
resource_url = '/'.join(['chassis', 'detail'])
|
||||
return ChassisCollection.convert_with_links(chassis, limit,
|
||||
url=resource_url,
|
||||
expand=True,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(Chassis, unicode)
|
||||
def get_one(self, uuid):
|
||||
"""Retrieve information about the given chassis."""
|
||||
@ -183,28 +211,3 @@ class ChassisController(rest.RestController):
|
||||
def delete(self, uuid):
|
||||
"""Delete a chassis."""
|
||||
pecan.request.dbapi.destroy_chassis(uuid)
|
||||
|
||||
@wsme_pecan.wsexpose(node.NodeCollection, unicode, int, unicode,
|
||||
unicode, unicode)
|
||||
def nodes(self, chassis_uuid, limit=None, marker=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of nodes contained in the chassis."""
|
||||
limit = utils.validate_limit(limit)
|
||||
sort_dir = utils.validate_sort_dir(sort_dir)
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.Node.get_by_uuid(pecan.request.context,
|
||||
marker)
|
||||
|
||||
nodes = pecan.request.dbapi.get_nodes_by_chassis(chassis_uuid, limit,
|
||||
marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
collection = node.NodeCollection()
|
||||
collection.nodes = [node.Node.convert_with_links(n) for n in nodes]
|
||||
resource_url = '/'.join(['chassis', chassis_uuid, 'nodes'])
|
||||
collection.next = collection.get_next(limit, url=resource_url,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return collection
|
||||
|
@ -223,8 +223,12 @@ class Node(base.APIBase):
|
||||
setattr(self, k, kwargs.get(k))
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_node):
|
||||
node = Node.from_rpc_object(rpc_node)
|
||||
def convert_with_links(cls, rpc_node, expand=True):
|
||||
minimum_fields = ['uuid', 'power_state', 'target_power_state',
|
||||
'provision_state', 'target_provision_state',
|
||||
'instance_uuid']
|
||||
fields = minimum_fields if not expand else None
|
||||
node = Node.from_rpc_object(rpc_node, fields)
|
||||
node.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'nodes', node.uuid),
|
||||
link.Link.make_link('bookmark',
|
||||
@ -232,6 +236,7 @@ class Node(base.APIBase):
|
||||
'nodes', node.uuid,
|
||||
bookmark=True)
|
||||
]
|
||||
if expand:
|
||||
node.ports = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'nodes', node.uuid + "/ports"),
|
||||
link.Link.make_link('bookmark',
|
||||
@ -252,10 +257,11 @@ class NodeCollection(collection.Collection):
|
||||
self._type = 'nodes'
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, nodes, limit, **kwargs):
|
||||
def convert_with_links(cls, nodes, limit, url=None,
|
||||
expand=False, **kwargs):
|
||||
collection = NodeCollection()
|
||||
collection.nodes = [Node.convert_with_links(n) for n in nodes]
|
||||
collection.next = collection.get_next(limit, **kwargs)
|
||||
collection.nodes = [Node.convert_with_links(n, expand) for n in nodes]
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
|
||||
|
||||
@ -292,13 +298,21 @@ class NodesController(rest.RestController):
|
||||
vendor_passthru = NodeVendorPassthruController()
|
||||
"A resource used for vendors to expose a custom functionality in the API"
|
||||
|
||||
ports = port.PortsController(from_nodes=True)
|
||||
"Expose ports as a sub-element of nodes"
|
||||
|
||||
_custom_actions = {
|
||||
'ports': ['GET'],
|
||||
'detail': ['GET'],
|
||||
}
|
||||
|
||||
@wsme_pecan.wsexpose(NodeCollection, int, unicode, unicode, unicode)
|
||||
def get_all(self, limit=None, marker=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of nodes."""
|
||||
def __init__(self, from_chassis=False):
|
||||
self._from_chassis = from_chassis
|
||||
|
||||
def _get_nodes(self, chassis_id, marker, limit, sort_key, sort_dir):
|
||||
if self._from_chassis and not chassis_id:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Chassis id not specified."))
|
||||
|
||||
limit = utils.validate_limit(limit)
|
||||
sort_dir = utils.validate_sort_dir(sort_dir)
|
||||
|
||||
@ -307,22 +321,60 @@ class NodesController(rest.RestController):
|
||||
marker_obj = objects.Node.get_by_uuid(pecan.request.context,
|
||||
marker)
|
||||
|
||||
if chassis_id:
|
||||
nodes = pecan.request.dbapi.get_nodes_by_chassis(chassis_id, limit,
|
||||
marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
else:
|
||||
nodes = pecan.request.dbapi.get_node_list(limit, marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return nodes
|
||||
|
||||
@wsme_pecan.wsexpose(NodeCollection, unicode, unicode, int,
|
||||
unicode, unicode)
|
||||
def get_all(self, chassis_id=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of nodes."""
|
||||
nodes = self._get_nodes(chassis_id, marker, limit, sort_key, sort_dir)
|
||||
return NodeCollection.convert_with_links(nodes, limit,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(NodeCollection, unicode, unicode, int,
|
||||
unicode, unicode)
|
||||
def detail(self, chassis_id=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of nodes with detail."""
|
||||
# /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
if parent != "nodes":
|
||||
raise exception.HTTPNotFound
|
||||
|
||||
nodes = self._get_nodes(chassis_id, marker, limit, sort_key, sort_dir)
|
||||
resource_url = '/'.join(['nodes', 'detail'])
|
||||
return NodeCollection.convert_with_links(nodes, limit,
|
||||
url=resource_url,
|
||||
expand=True,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(Node, unicode)
|
||||
def get_one(self, uuid):
|
||||
"""Retrieve information about the given node."""
|
||||
if self._from_chassis:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
rpc_node = objects.Node.get_by_uuid(pecan.request.context, uuid)
|
||||
return Node.convert_with_links(rpc_node)
|
||||
|
||||
@wsme_pecan.wsexpose(Node, body=Node)
|
||||
def post(self, node):
|
||||
"""Create a new node."""
|
||||
if self._from_chassis:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
try:
|
||||
new_node = pecan.request.dbapi.create_node(node.as_dict())
|
||||
except Exception as e:
|
||||
@ -336,6 +388,9 @@ class NodesController(rest.RestController):
|
||||
|
||||
TODO(deva): add exception handling
|
||||
"""
|
||||
if self._from_chassis:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
node = objects.Node.get_by_uuid(pecan.request.context, uuid)
|
||||
node_dict = node.as_dict()
|
||||
|
||||
@ -407,29 +462,7 @@ class NodesController(rest.RestController):
|
||||
|
||||
TODO(deva): don't allow deletion of an associated node.
|
||||
"""
|
||||
if self._from_chassis:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
pecan.request.dbapi.destroy_node(node_id)
|
||||
|
||||
@wsme_pecan.wsexpose(port.PortCollection, unicode, int, unicode,
|
||||
unicode, unicode)
|
||||
def ports(self, node_uuid, limit=None, marker=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of ports on this node."""
|
||||
limit = utils.validate_limit(limit)
|
||||
sort_dir = utils.validate_sort_dir(sort_dir)
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.Port.get_by_uuid(pecan.request.context,
|
||||
marker)
|
||||
|
||||
ports = pecan.request.dbapi.get_ports_by_node(node_uuid, limit,
|
||||
marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
collection = port.PortCollection()
|
||||
collection.ports = [port.Port.convert_with_links(n) for n in ports]
|
||||
resource_url = '/'.join(['nodes', node_uuid, 'ports'])
|
||||
collection.next = collection.get_next(limit, url=resource_url,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return collection
|
||||
|
@ -60,8 +60,9 @@ class Port(base.APIBase):
|
||||
setattr(self, k, kwargs.get(k))
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_port):
|
||||
port = Port.from_rpc_object(rpc_port)
|
||||
def convert_with_links(cls, rpc_port, expand=True):
|
||||
fields = ['uuid', 'address'] if not expand else None
|
||||
port = Port.from_rpc_object(rpc_port, fields)
|
||||
port.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'ports', port.uuid),
|
||||
link.Link.make_link('bookmark',
|
||||
@ -82,19 +83,29 @@ class PortCollection(collection.Collection):
|
||||
self._type = 'ports'
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, ports, limit, **kwargs):
|
||||
def convert_with_links(cls, ports, limit, url=None,
|
||||
expand=False, **kwargs):
|
||||
collection = PortCollection()
|
||||
collection.ports = [Port.convert_with_links(p) for p in ports]
|
||||
collection.next = collection.get_next(limit, **kwargs)
|
||||
collection.ports = [Port.convert_with_links(p, expand) for p in ports]
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
|
||||
|
||||
class PortsController(rest.RestController):
|
||||
"""REST controller for Ports."""
|
||||
|
||||
@wsme_pecan.wsexpose(PortCollection, int, unicode, unicode, unicode)
|
||||
def get_all(self, limit=None, marker=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of ports."""
|
||||
_custom_actions = {
|
||||
'detail': ['GET'],
|
||||
}
|
||||
|
||||
def __init__(self, from_nodes=False):
|
||||
self._from_nodes = from_nodes
|
||||
|
||||
def _get_ports(self, node_id, marker, limit, sort_key, sort_dir):
|
||||
if self._from_nodes and not node_id:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Node id not specified."))
|
||||
|
||||
limit = utils.validate_limit(limit)
|
||||
sort_dir = utils.validate_sort_dir(sort_dir)
|
||||
|
||||
@ -103,22 +114,60 @@ class PortsController(rest.RestController):
|
||||
marker_obj = objects.Port.get_by_uuid(pecan.request.context,
|
||||
marker)
|
||||
|
||||
if node_id:
|
||||
ports = pecan.request.dbapi.get_ports_by_node(node_id, limit,
|
||||
marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
else:
|
||||
ports = pecan.request.dbapi.get_port_list(limit, marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return ports
|
||||
|
||||
@wsme_pecan.wsexpose(PortCollection, unicode, unicode, int,
|
||||
unicode, unicode)
|
||||
def get_all(self, node_id=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of ports."""
|
||||
ports = self._get_ports(node_id, marker, limit, sort_key, sort_dir)
|
||||
return PortCollection.convert_with_links(ports, limit,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(PortCollection, unicode, unicode, int,
|
||||
unicode, unicode)
|
||||
def detail(self, node_id=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of ports."""
|
||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
if parent != "ports":
|
||||
raise exception.HTTPNotFound
|
||||
|
||||
ports = self._get_ports(node_id, marker, limit, sort_key, sort_dir)
|
||||
resource_url = '/'.join(['ports', 'detail'])
|
||||
return PortCollection.convert_with_links(ports, limit,
|
||||
url=resource_url,
|
||||
expand=True,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(Port, unicode)
|
||||
def get_one(self, uuid):
|
||||
"""Retrieve information about the given port."""
|
||||
if self._from_nodes:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
rpc_port = objects.Port.get_by_uuid(pecan.request.context, uuid)
|
||||
return Port.convert_with_links(rpc_port)
|
||||
|
||||
@wsme_pecan.wsexpose(Port, body=Port)
|
||||
def post(self, port):
|
||||
"""Create a new port."""
|
||||
if self._from_nodes:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
try:
|
||||
new_port = pecan.request.dbapi.create_port(port.as_dict())
|
||||
except exception.IronicException as e:
|
||||
@ -129,6 +178,9 @@ class PortsController(rest.RestController):
|
||||
@wsme_pecan.wsexpose(Port, unicode, body=[unicode])
|
||||
def patch(self, uuid, patch):
|
||||
"""Update an existing port."""
|
||||
if self._from_nodes:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
port = objects.Port.get_by_uuid(pecan.request.context, uuid)
|
||||
port_dict = port.as_dict()
|
||||
|
||||
@ -161,4 +213,7 @@ class PortsController(rest.RestController):
|
||||
@wsme_pecan.wsexpose(None, unicode, status_code=204)
|
||||
def delete(self, port_id):
|
||||
"""Delete a port."""
|
||||
if self._from_nodes:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
pecan.request.dbapi.destroy_port(port_id)
|
||||
|
@ -173,6 +173,10 @@ class PolicyNotAuthorized(NotAuthorized):
|
||||
message = _("Policy doesn't allow %(action)s to be performed.")
|
||||
|
||||
|
||||
class OperationNotPermitted(NotAuthorized):
|
||||
message = _("Operation not permitted.")
|
||||
|
||||
|
||||
class Invalid(IronicException):
|
||||
message = _("Unacceptable parameters.")
|
||||
code = 400
|
||||
|
@ -32,6 +32,23 @@ class TestListChassis(base.FunctionalTest):
|
||||
chassis = self.dbapi.create_chassis(ndict)
|
||||
data = self.get_json('/chassis')
|
||||
self.assertEqual(chassis['uuid'], data['chassis'][0]["uuid"])
|
||||
self.assertNotIn('extra', data['chassis'][0])
|
||||
self.assertNotIn('nodes', data['chassis'][0])
|
||||
|
||||
def test_detail(self):
|
||||
cdict = dbutils.get_test_chassis()
|
||||
chassis = self.dbapi.create_chassis(cdict)
|
||||
data = self.get_json('/chassis/detail')
|
||||
self.assertEqual(chassis['uuid'], data['chassis'][0]["uuid"])
|
||||
self.assertIn('extra', data['chassis'][0])
|
||||
self.assertIn('nodes', data['chassis'][0])
|
||||
|
||||
def test_detail_against_single(self):
|
||||
cdict = dbutils.get_test_chassis()
|
||||
chassis = self.dbapi.create_chassis(cdict)
|
||||
response = self.get_json('/chassis/%s/detail' % chassis['uuid'],
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
def test_many(self):
|
||||
ch_list = []
|
||||
@ -93,6 +110,15 @@ class TestListChassis(base.FunctionalTest):
|
||||
self.assertEqual(len(data['nodes']), 1)
|
||||
self.assertIn('next', data.keys())
|
||||
|
||||
def test_nodes_subresource_noid(self):
|
||||
cdict = dbutils.get_test_chassis()
|
||||
self.dbapi.create_chassis(cdict)
|
||||
ndict = dbutils.get_test_node(chassis_id=cdict['id'])
|
||||
self.dbapi.create_node(ndict)
|
||||
# No chassis id specified
|
||||
response = self.get_json('/chassis/nodes', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 400)
|
||||
|
||||
|
||||
class TestPatch(base.FunctionalTest):
|
||||
|
||||
@ -203,6 +229,13 @@ class TestPatch(base.FunctionalTest):
|
||||
expected = {"foo1": "bar1", "foo2": "bar2"}
|
||||
self.assertEqual(result['extra'], expected)
|
||||
|
||||
def test_patch_nodes_subresource(self):
|
||||
cdict = dbutils.get_test_chassis()
|
||||
response = self.patch_json('/chassis/%s/nodes' % cdict['uuid'],
|
||||
[{'path': '/extra/foo', 'value': 'bar',
|
||||
'op': 'add'}], expect_errors=True)
|
||||
self.assertEqual(response.status_int, 403)
|
||||
|
||||
|
||||
class TestPost(base.FunctionalTest):
|
||||
|
||||
@ -221,6 +254,14 @@ class TestPost(base.FunctionalTest):
|
||||
result['chassis'][0]['description'])
|
||||
self.assertTrue(uuidutils.is_uuid_like(result['chassis'][0]['uuid']))
|
||||
|
||||
def test_post_nodes_subresource(self):
|
||||
cdict = dbutils.get_test_chassis()
|
||||
self.post_json('/chassis', cdict)
|
||||
ndict = dbutils.get_test_node(chassis_id=cdict['id'])
|
||||
response = self.post_json('/chassis/nodes', ndict,
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 403)
|
||||
|
||||
|
||||
class TestDelete(base.FunctionalTest):
|
||||
|
||||
@ -251,3 +292,10 @@ class TestDelete(base.FunctionalTest):
|
||||
self.assertEqual(response.status_int, 404)
|
||||
self.assertEqual(response.content_type, 'application/json')
|
||||
self.assertTrue(response.json['error_message'])
|
||||
|
||||
def test_delete_nodes_subresource(self):
|
||||
cdict = dbutils.get_test_chassis()
|
||||
self.post_json('/chassis', cdict)
|
||||
response = self.delete('/chassis/%s/nodes' % cdict['uuid'],
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 403)
|
||||
|
@ -38,6 +38,29 @@ class TestListNodes(base.FunctionalTest):
|
||||
node = self.dbapi.create_node(ndict)
|
||||
data = self.get_json('/nodes')
|
||||
self.assertEqual(node['uuid'], data['nodes'][0]["uuid"])
|
||||
self.assertNotIn('driver', data['nodes'][0])
|
||||
self.assertNotIn('driver_info', data['nodes'][0])
|
||||
self.assertNotIn('extra', data['nodes'][0])
|
||||
self.assertNotIn('properties', data['nodes'][0])
|
||||
self.assertNotIn('chassis_id', data['nodes'][0])
|
||||
|
||||
def test_detail(self):
|
||||
ndict = dbutils.get_test_node()
|
||||
node = self.dbapi.create_node(ndict)
|
||||
data = self.get_json('/nodes/detail')
|
||||
self.assertEqual(node['uuid'], data['nodes'][0]["uuid"])
|
||||
self.assertIn('driver', data['nodes'][0])
|
||||
self.assertIn('driver_info', data['nodes'][0])
|
||||
self.assertIn('extra', data['nodes'][0])
|
||||
self.assertIn('properties', data['nodes'][0])
|
||||
self.assertIn('chassis_id', data['nodes'][0])
|
||||
|
||||
def test_detail_against_single(self):
|
||||
ndict = dbutils.get_test_node()
|
||||
node = self.dbapi.create_node(ndict)
|
||||
response = self.get_json('/nodes/%s/detail' % node['uuid'],
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
def test_many(self):
|
||||
nodes = []
|
||||
@ -99,6 +122,15 @@ class TestListNodes(base.FunctionalTest):
|
||||
self.assertEqual(len(data['ports']), 1)
|
||||
self.assertIn('next', data.keys())
|
||||
|
||||
def test_nodes_subresource_noid(self):
|
||||
ndict = dbutils.get_test_node()
|
||||
self.dbapi.create_node(ndict)
|
||||
pdict = dbutils.get_test_port(node_id=ndict['id'])
|
||||
self.dbapi.create_port(pdict)
|
||||
# No node id specified
|
||||
response = self.get_json('/nodes/ports', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 400)
|
||||
|
||||
def test_state(self):
|
||||
ndict = dbutils.get_test_node()
|
||||
self.dbapi.create_node(ndict)
|
||||
@ -239,6 +271,12 @@ class TestPatch(base.FunctionalTest):
|
||||
[{'path': '/extra/foo', 'value': 'bar',
|
||||
'op': 'add'}])
|
||||
|
||||
def test_patch_ports_subresource(self):
|
||||
response = self.patch_json('/nodes/%s/ports' % self.node['uuid'],
|
||||
[{'path': '/extra/foo', 'value': 'bar',
|
||||
'op': 'add'}], expect_errors=True)
|
||||
self.assertEqual(response.status_int, 403)
|
||||
|
||||
|
||||
class TestPost(base.FunctionalTest):
|
||||
|
||||
@ -271,6 +309,14 @@ class TestPost(base.FunctionalTest):
|
||||
'/nodes/%s/vendor_passthru' % ndict['uuid'],
|
||||
{'foo': 'bar'})
|
||||
|
||||
def test_post_ports_subresource(self):
|
||||
ndict = dbutils.get_test_node()
|
||||
self.post_json('/nodes', ndict)
|
||||
pdict = dbutils.get_test_port()
|
||||
response = self.post_json('/nodes/ports', pdict,
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 403)
|
||||
|
||||
|
||||
class TestDelete(base.FunctionalTest):
|
||||
|
||||
@ -284,6 +330,13 @@ class TestDelete(base.FunctionalTest):
|
||||
self.assertEqual(response.content_type, 'application/json')
|
||||
self.assertTrue(response.json['error_message'])
|
||||
|
||||
def test_delete_ports_subresource(self):
|
||||
ndict = dbutils.get_test_node()
|
||||
self.post_json('/nodes', ndict)
|
||||
response = self.delete('/nodes/%s/ports' % ndict['uuid'],
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 403)
|
||||
|
||||
|
||||
class TestPut(base.FunctionalTest):
|
||||
|
||||
|
@ -32,6 +32,21 @@ class TestListPorts(base.FunctionalTest):
|
||||
port = self.dbapi.create_port(ndict)
|
||||
data = self.get_json('/ports')
|
||||
self.assertEqual(port['uuid'], data['ports'][0]["uuid"])
|
||||
self.assertNotIn('extra', data['ports'][0])
|
||||
|
||||
def test_detail(self):
|
||||
pdict = dbutils.get_test_port()
|
||||
port = self.dbapi.create_port(pdict)
|
||||
data = self.get_json('/ports/detail')
|
||||
self.assertEqual(port['uuid'], data['ports'][0]["uuid"])
|
||||
self.assertIn('extra', data['ports'][0])
|
||||
|
||||
def test_detail_against_single(self):
|
||||
pdict = dbutils.get_test_port()
|
||||
port = self.dbapi.create_port(pdict)
|
||||
response = self.get_json('/ports/%s/detail' % port['uuid'],
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
def test_many(self):
|
||||
ports = []
|
||||
|
Loading…
Reference in New Issue
Block a user