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