API GET to return only minimal data
Requests to list top-level resources like nodes, chassis or ports will now return only a subset of it's attributes, a subresource called /detail could be used to get the full details of the resource. This changes is supposed to improve performance and UX, also, others OpenStack APIs already do it the same way so it's also about being consistent between other APIs. Change-Id: Ida45febf60e44d50e506f3680ab371e1027010c4 Closes-Bug: #1227431
This commit is contained in:
parent
59c2862d65
commit
3dd85586b6
@ -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
|
||||
@ -566,16 +567,17 @@ Chassis
|
||||
Usage
|
||||
^^^^^^
|
||||
|
||||
======= ============= ==========
|
||||
Verb Path Response
|
||||
======= ============= ==========
|
||||
GET /chassis List chassis
|
||||
GET /chassis/<id> Retrieve a specific chassis
|
||||
POST /chassis Create a new chassis
|
||||
PATCH /chassis/<id> Update a chassis
|
||||
DELETE /chassis/<id> Delete chassis and remove all associations between
|
||||
nodes
|
||||
======= ============= ==========
|
||||
======= ============= ==========
|
||||
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
|
||||
DELETE /chassis/<id> Delete chassis and remove all associations between
|
||||
nodes
|
||||
======= ============= ==========
|
||||
|
||||
|
||||
Fields
|
||||
@ -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,24 +67,28 @@ 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.nodes = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'chassis',
|
||||
chassis.uuid + "/nodes"),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'chassis',
|
||||
chassis.uuid + "/nodes",
|
||||
bookmark=True)
|
||||
'chassis', chassis.uuid)
|
||||
]
|
||||
|
||||
if expand:
|
||||
chassis.nodes = [link.Link.make_link('self',
|
||||
pecan.request.host_url,
|
||||
'chassis',
|
||||
chassis.uuid + "/nodes"),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'chassis',
|
||||
chassis.uuid + "/nodes",
|
||||
bookmark=True)
|
||||
]
|
||||
return chassis
|
||||
|
||||
|
||||
@ -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,13 +236,14 @@ class Node(base.APIBase):
|
||||
'nodes', node.uuid,
|
||||
bookmark=True)
|
||||
]
|
||||
node.ports = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'nodes', node.uuid + "/ports"),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'nodes', node.uuid + "/ports",
|
||||
bookmark=True)
|
||||
]
|
||||
if expand:
|
||||
node.ports = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'nodes', node.uuid + "/ports"),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'nodes', node.uuid + "/ports",
|
||||
bookmark=True)
|
||||
]
|
||||
return node
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
nodes = pecan.request.dbapi.get_node_list(limit, marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
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)
|
||||
|
||||
ports = pecan.request.dbapi.get_port_list(limit, marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
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