Merge "Add detail=[True, False] query string to API list endpoints"
This commit is contained in:
commit
fbc74b7924
@ -203,6 +203,10 @@ List chassis
|
|||||||
|
|
||||||
Lists all chassis.
|
Lists all chassis.
|
||||||
|
|
||||||
|
.. versionadded:: 1.43
|
||||||
|
Added the ``detail`` boolean request parameter. When specified ``True`` this
|
||||||
|
causes the response to include complete details about each chassis.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
.. TODO: add error codes
|
.. TODO: add error codes
|
||||||
@ -217,6 +221,7 @@ Request
|
|||||||
- sort_dir: sort_dir
|
- sort_dir: sort_dir
|
||||||
- sort_key: sort_key
|
- sort_key: sort_key
|
||||||
- fields: fields
|
- fields: fields
|
||||||
|
- detail: detail
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -132,7 +132,7 @@ and any defaults added for non-specified fields. Most fields default to "null"
|
|||||||
or "".
|
or "".
|
||||||
|
|
||||||
The list and example below are representative of the response as of API
|
The list and example below are representative of the response as of API
|
||||||
microversion 1.42.
|
microversion 1.43.
|
||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
@ -215,6 +215,10 @@ provision state, and maintenance setting for each Node.
|
|||||||
.. versionadded:: 1.42
|
.. versionadded:: 1.42
|
||||||
Introduced the ``fault`` field.
|
Introduced the ``fault`` field.
|
||||||
|
|
||||||
|
.. versionadded:: 1.43
|
||||||
|
Added the ``detail`` boolean request parameter. When specified ``True`` this
|
||||||
|
causes the response to include complete details about each node.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Error codes: 400,403,406
|
Error codes: 400,403,406
|
||||||
@ -236,6 +240,7 @@ Request
|
|||||||
- marker: marker
|
- marker: marker
|
||||||
- sort_dir: sort_dir
|
- sort_dir: sort_dir
|
||||||
- sort_key: sort_key
|
- sort_key: sort_key
|
||||||
|
- detail: detail
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
@ -261,6 +266,9 @@ List Nodes Detailed
|
|||||||
|
|
||||||
.. rest_method:: GET /v1/nodes/detail
|
.. rest_method:: GET /v1/nodes/detail
|
||||||
|
|
||||||
|
.. deprecated::
|
||||||
|
Use ?detail=True query string instead.
|
||||||
|
|
||||||
Return a list of bare metal Nodes with complete details. Some filtering is
|
Return a list of bare metal Nodes with complete details. Some filtering is
|
||||||
possible by passing in flags with the request.
|
possible by passing in flags with the request.
|
||||||
|
|
||||||
|
@ -28,6 +28,10 @@ some parameters with the request.
|
|||||||
|
|
||||||
By default, this query will return the UUID, name and address for each Portgroup.
|
By default, this query will return the UUID, name and address for each Portgroup.
|
||||||
|
|
||||||
|
.. versionadded:: 1.43
|
||||||
|
Added the ``detail`` boolean request parameter. When specified ``True`` this
|
||||||
|
causes the response to include complete details about each portgroup.
|
||||||
|
|
||||||
Normal response code: 200
|
Normal response code: 200
|
||||||
|
|
||||||
Error codes: 400,401,403,404
|
Error codes: 400,401,403,404
|
||||||
@ -44,6 +48,7 @@ Request
|
|||||||
- marker: marker
|
- marker: marker
|
||||||
- sort_dir: sort_dir
|
- sort_dir: sort_dir
|
||||||
- sort_key: sort_key
|
- sort_key: sort_key
|
||||||
|
- detail: detail
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
|
@ -42,6 +42,10 @@ By default, this query will return the uuid and address for each Port.
|
|||||||
.. versionadded:: 1.34
|
.. versionadded:: 1.34
|
||||||
Added the ``physical_network`` field.
|
Added the ``physical_network`` field.
|
||||||
|
|
||||||
|
.. versionadded:: 1.43
|
||||||
|
Added the ``detail`` boolean request parameter. When specified ``True`` this
|
||||||
|
causes the response to include complete details about each port.
|
||||||
|
|
||||||
Normal response code: 200
|
Normal response code: 200
|
||||||
|
|
||||||
Request
|
Request
|
||||||
@ -58,6 +62,7 @@ Request
|
|||||||
- marker: marker
|
- marker: marker
|
||||||
- sort_dir: sort_dir
|
- sort_dir: sort_dir
|
||||||
- sort_key: sort_key
|
- sort_key: sort_key
|
||||||
|
- detail: detail
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
REST API Version History
|
REST API Version History
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
1.43 (Rocky, master)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Added ``?detail=`` boolean query to the API list endpoints to provide a more
|
||||||
|
RESTful alternative to the existing ``/nodes/detail`` and similar endpoints.
|
||||||
|
|
||||||
1.42 (Rocky, master)
|
1.42 (Rocky, master)
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ class ChassisController(rest.RestController):
|
|||||||
invalid_sort_key_list = ['extra']
|
invalid_sort_key_list = ['extra']
|
||||||
|
|
||||||
def _get_chassis_collection(self, marker, limit, sort_key, sort_dir,
|
def _get_chassis_collection(self, marker, limit, sort_key, sort_dir,
|
||||||
resource_url=None, fields=None):
|
resource_url=None, fields=None, detail=None):
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||||
marker_obj = None
|
marker_obj = None
|
||||||
@ -190,17 +190,22 @@ class ChassisController(rest.RestController):
|
|||||||
chassis = objects.Chassis.list(pecan.request.context, limit,
|
chassis = objects.Chassis.list(pecan.request.context, limit,
|
||||||
marker_obj, sort_key=sort_key,
|
marker_obj, sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
parameters = {}
|
||||||
|
if detail is not None:
|
||||||
|
parameters['detail'] = detail
|
||||||
|
|
||||||
return ChassisCollection.convert_with_links(chassis, limit,
|
return ChassisCollection.convert_with_links(chassis, limit,
|
||||||
url=resource_url,
|
url=resource_url,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
@METRICS.timer('ChassisController.get_all')
|
@METRICS.timer('ChassisController.get_all')
|
||||||
@expose.expose(ChassisCollection, types.uuid, int,
|
@expose.expose(ChassisCollection, types.uuid, int,
|
||||||
wtypes.text, wtypes.text, types.listtype)
|
wtypes.text, wtypes.text, types.listtype, types.boolean)
|
||||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc',
|
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc',
|
||||||
fields=None):
|
fields=None, detail=None):
|
||||||
"""Retrieve a list of chassis.
|
"""Retrieve a list of chassis.
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
@ -217,10 +222,12 @@ class ChassisController(rest.RestController):
|
|||||||
policy.authorize('baremetal:chassis:get', cdict, cdict)
|
policy.authorize('baremetal:chassis:get', cdict, cdict)
|
||||||
|
|
||||||
api_utils.check_allow_specify_fields(fields)
|
api_utils.check_allow_specify_fields(fields)
|
||||||
if fields is None:
|
|
||||||
fields = _DEFAULT_RETURN_FIELDS
|
fields = api_utils.get_request_return_fields(fields, detail,
|
||||||
|
_DEFAULT_RETURN_FIELDS)
|
||||||
|
|
||||||
return self._get_chassis_collection(marker, limit, sort_key, sort_dir,
|
return self._get_chassis_collection(marker, limit, sort_key, sort_dir,
|
||||||
fields=fields)
|
fields=fields, detail=detail)
|
||||||
|
|
||||||
@METRICS.timer('ChassisController.detail')
|
@METRICS.timer('ChassisController.detail')
|
||||||
@expose.expose(ChassisCollection, types.uuid, int,
|
@expose.expose(ChassisCollection, types.uuid, int,
|
||||||
|
@ -1524,7 +1524,7 @@ class NodesController(rest.RestController):
|
|||||||
maintenance, provision_state, marker, limit,
|
maintenance, provision_state, marker, limit,
|
||||||
sort_key, sort_dir, driver=None,
|
sort_key, sort_dir, driver=None,
|
||||||
resource_class=None, resource_url=None,
|
resource_class=None, resource_url=None,
|
||||||
fields=None, fault=None):
|
fields=None, fault=None, detail=None):
|
||||||
if self.from_chassis and not chassis_uuid:
|
if self.from_chassis and not chassis_uuid:
|
||||||
raise exception.MissingParameterValue(
|
raise exception.MissingParameterValue(
|
||||||
_("Chassis id not specified."))
|
_("Chassis id not specified."))
|
||||||
@ -1583,6 +1583,9 @@ class NodesController(rest.RestController):
|
|||||||
if maintenance:
|
if maintenance:
|
||||||
parameters['maintenance'] = maintenance
|
parameters['maintenance'] = maintenance
|
||||||
|
|
||||||
|
if detail is not None:
|
||||||
|
parameters['detail'] = detail
|
||||||
|
|
||||||
return NodeCollection.convert_with_links(nodes, limit,
|
return NodeCollection.convert_with_links(nodes, limit,
|
||||||
url=resource_url,
|
url=resource_url,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
@ -1676,11 +1679,11 @@ class NodesController(rest.RestController):
|
|||||||
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
||||||
types.boolean, wtypes.text, types.uuid, int, wtypes.text,
|
types.boolean, wtypes.text, types.uuid, int, wtypes.text,
|
||||||
wtypes.text, wtypes.text, types.listtype, wtypes.text,
|
wtypes.text, wtypes.text, types.listtype, wtypes.text,
|
||||||
wtypes.text)
|
wtypes.text, types.boolean)
|
||||||
def get_all(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
def get_all(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
||||||
maintenance=None, provision_state=None, marker=None,
|
maintenance=None, provision_state=None, marker=None,
|
||||||
limit=None, sort_key='id', sort_dir='asc', driver=None,
|
limit=None, sort_key='id', sort_dir='asc', driver=None,
|
||||||
fields=None, resource_class=None, fault=None):
|
fields=None, resource_class=None, fault=None, detail=None):
|
||||||
"""Retrieve a list of nodes.
|
"""Retrieve a list of nodes.
|
||||||
|
|
||||||
:param chassis_uuid: Optional UUID of a chassis, to get only nodes for
|
:param chassis_uuid: Optional UUID of a chassis, to get only nodes for
|
||||||
@ -1720,15 +1723,18 @@ class NodesController(rest.RestController):
|
|||||||
api_utils.check_allow_specify_driver(driver)
|
api_utils.check_allow_specify_driver(driver)
|
||||||
api_utils.check_allow_specify_resource_class(resource_class)
|
api_utils.check_allow_specify_resource_class(resource_class)
|
||||||
api_utils.check_allow_filter_by_fault(fault)
|
api_utils.check_allow_filter_by_fault(fault)
|
||||||
if fields is None:
|
|
||||||
fields = _DEFAULT_RETURN_FIELDS
|
fields = api_utils.get_request_return_fields(fields, detail,
|
||||||
|
_DEFAULT_RETURN_FIELDS)
|
||||||
|
|
||||||
return self._get_nodes_collection(chassis_uuid, instance_uuid,
|
return self._get_nodes_collection(chassis_uuid, instance_uuid,
|
||||||
associated, maintenance,
|
associated, maintenance,
|
||||||
provision_state, marker,
|
provision_state, marker,
|
||||||
limit, sort_key, sort_dir,
|
limit, sort_key, sort_dir,
|
||||||
driver=driver,
|
driver=driver,
|
||||||
resource_class=resource_class,
|
resource_class=resource_class,
|
||||||
fields=fields, fault=fault)
|
fields=fields, fault=fault,
|
||||||
|
detail=detail)
|
||||||
|
|
||||||
@METRICS.timer('NodesController.detail')
|
@METRICS.timer('NodesController.detail')
|
||||||
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
||||||
|
@ -314,7 +314,7 @@ class PortsController(rest.RestController):
|
|||||||
|
|
||||||
def _get_ports_collection(self, node_ident, address, portgroup_ident,
|
def _get_ports_collection(self, node_ident, address, portgroup_ident,
|
||||||
marker, limit, sort_key, sort_dir,
|
marker, limit, sort_key, sort_dir,
|
||||||
resource_url=None, fields=None):
|
resource_url=None, fields=None, detail=None):
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||||
@ -362,12 +362,17 @@ class PortsController(rest.RestController):
|
|||||||
ports = objects.Port.list(pecan.request.context, limit,
|
ports = objects.Port.list(pecan.request.context, limit,
|
||||||
marker_obj, sort_key=sort_key,
|
marker_obj, sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
if detail is not None:
|
||||||
|
parameters['detail'] = detail
|
||||||
|
|
||||||
return PortCollection.convert_with_links(ports, limit,
|
return PortCollection.convert_with_links(ports, limit,
|
||||||
url=resource_url,
|
url=resource_url,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
def _get_ports_by_address(self, address):
|
def _get_ports_by_address(self, address):
|
||||||
"""Retrieve a port by its address.
|
"""Retrieve a port by its address.
|
||||||
@ -407,10 +412,11 @@ class PortsController(rest.RestController):
|
|||||||
@METRICS.timer('PortsController.get_all')
|
@METRICS.timer('PortsController.get_all')
|
||||||
@expose.expose(PortCollection, types.uuid_or_name, types.uuid,
|
@expose.expose(PortCollection, types.uuid_or_name, types.uuid,
|
||||||
types.macaddress, types.uuid, int, wtypes.text,
|
types.macaddress, types.uuid, int, wtypes.text,
|
||||||
wtypes.text, types.listtype, types.uuid_or_name)
|
wtypes.text, types.listtype, types.uuid_or_name,
|
||||||
|
types.boolean)
|
||||||
def get_all(self, node=None, node_uuid=None, address=None, marker=None,
|
def get_all(self, node=None, node_uuid=None, address=None, marker=None,
|
||||||
limit=None, sort_key='id', sort_dir='asc', fields=None,
|
limit=None, sort_key='id', sort_dir='asc', fields=None,
|
||||||
portgroup=None):
|
portgroup=None, detail=None):
|
||||||
"""Retrieve a list of ports.
|
"""Retrieve a list of ports.
|
||||||
|
|
||||||
Note that the 'node_uuid' interface is deprecated in favour
|
Note that the 'node_uuid' interface is deprecated in favour
|
||||||
@ -441,11 +447,12 @@ class PortsController(rest.RestController):
|
|||||||
api_utils.check_allow_specify_fields(fields)
|
api_utils.check_allow_specify_fields(fields)
|
||||||
self._check_allowed_port_fields(fields)
|
self._check_allowed_port_fields(fields)
|
||||||
self._check_allowed_port_fields([sort_key])
|
self._check_allowed_port_fields([sort_key])
|
||||||
|
|
||||||
if portgroup and not api_utils.allow_portgroups_subcontrollers():
|
if portgroup and not api_utils.allow_portgroups_subcontrollers():
|
||||||
raise exception.NotAcceptable()
|
raise exception.NotAcceptable()
|
||||||
|
|
||||||
if fields is None:
|
fields = api_utils.get_request_return_fields(fields, detail,
|
||||||
fields = _DEFAULT_RETURN_FIELDS
|
_DEFAULT_RETURN_FIELDS)
|
||||||
|
|
||||||
if not node_uuid and node:
|
if not node_uuid and node:
|
||||||
# We're invoking this interface using positional notation, or
|
# We're invoking this interface using positional notation, or
|
||||||
@ -457,7 +464,8 @@ class PortsController(rest.RestController):
|
|||||||
|
|
||||||
return self._get_ports_collection(node_uuid or node, address,
|
return self._get_ports_collection(node_uuid or node, address,
|
||||||
portgroup, marker, limit, sort_key,
|
portgroup, marker, limit, sort_key,
|
||||||
sort_dir, fields=fields)
|
sort_dir, fields=fields,
|
||||||
|
detail=detail)
|
||||||
|
|
||||||
@METRICS.timer('PortsController.detail')
|
@METRICS.timer('PortsController.detail')
|
||||||
@expose.expose(PortCollection, types.uuid_or_name, types.uuid,
|
@expose.expose(PortCollection, types.uuid_or_name, types.uuid,
|
||||||
|
@ -262,7 +262,8 @@ class PortgroupsController(pecan.rest.RestController):
|
|||||||
|
|
||||||
def _get_portgroups_collection(self, node_ident, address,
|
def _get_portgroups_collection(self, node_ident, address,
|
||||||
marker, limit, sort_key, sort_dir,
|
marker, limit, sort_key, sort_dir,
|
||||||
resource_url=None, fields=None):
|
resource_url=None, fields=None,
|
||||||
|
detail=None):
|
||||||
"""Return portgroups collection.
|
"""Return portgroups collection.
|
||||||
|
|
||||||
:param node_ident: UUID or name of a node.
|
:param node_ident: UUID or name of a node.
|
||||||
@ -305,12 +306,16 @@ class PortgroupsController(pecan.rest.RestController):
|
|||||||
portgroups = objects.Portgroup.list(pecan.request.context, limit,
|
portgroups = objects.Portgroup.list(pecan.request.context, limit,
|
||||||
marker_obj, sort_key=sort_key,
|
marker_obj, sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
parameters = {}
|
||||||
|
if detail is not None:
|
||||||
|
parameters['detail'] = detail
|
||||||
|
|
||||||
return PortgroupCollection.convert_with_links(portgroups, limit,
|
return PortgroupCollection.convert_with_links(portgroups, limit,
|
||||||
url=resource_url,
|
url=resource_url,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
def _get_portgroups_by_address(self, address):
|
def _get_portgroups_by_address(self, address):
|
||||||
"""Retrieve a portgroup by its address.
|
"""Retrieve a portgroup by its address.
|
||||||
@ -330,9 +335,11 @@ class PortgroupsController(pecan.rest.RestController):
|
|||||||
|
|
||||||
@METRICS.timer('PortgroupsController.get_all')
|
@METRICS.timer('PortgroupsController.get_all')
|
||||||
@expose.expose(PortgroupCollection, types.uuid_or_name, types.macaddress,
|
@expose.expose(PortgroupCollection, types.uuid_or_name, types.macaddress,
|
||||||
types.uuid, int, wtypes.text, wtypes.text, types.listtype)
|
types.uuid, int, wtypes.text, wtypes.text, types.listtype,
|
||||||
|
types.boolean)
|
||||||
def get_all(self, node=None, address=None, marker=None,
|
def get_all(self, node=None, address=None, marker=None,
|
||||||
limit=None, sort_key='id', sort_dir='asc', fields=None):
|
limit=None, sort_key='id', sort_dir='asc', fields=None,
|
||||||
|
detail=None):
|
||||||
"""Retrieve a list of portgroups.
|
"""Retrieve a list of portgroups.
|
||||||
|
|
||||||
:param node: UUID or name of a node, to get only portgroups for that
|
:param node: UUID or name of a node, to get only portgroups for that
|
||||||
@ -358,13 +365,14 @@ class PortgroupsController(pecan.rest.RestController):
|
|||||||
api_utils.check_allowed_portgroup_fields(fields)
|
api_utils.check_allowed_portgroup_fields(fields)
|
||||||
api_utils.check_allowed_portgroup_fields([sort_key])
|
api_utils.check_allowed_portgroup_fields([sort_key])
|
||||||
|
|
||||||
if fields is None:
|
fields = api_utils.get_request_return_fields(fields, detail,
|
||||||
fields = _DEFAULT_RETURN_FIELDS
|
_DEFAULT_RETURN_FIELDS)
|
||||||
|
|
||||||
return self._get_portgroups_collection(node, address,
|
return self._get_portgroups_collection(node, address,
|
||||||
marker, limit,
|
marker, limit,
|
||||||
sort_key, sort_dir,
|
sort_key, sort_dir,
|
||||||
fields=fields)
|
fields=fields,
|
||||||
|
detail=detail)
|
||||||
|
|
||||||
@METRICS.timer('PortgroupsController.detail')
|
@METRICS.timer('PortgroupsController.detail')
|
||||||
@expose.expose(PortgroupCollection, types.uuid_or_name, types.macaddress,
|
@expose.expose(PortgroupCollection, types.uuid_or_name, types.macaddress,
|
||||||
|
@ -850,3 +850,46 @@ def handle_patch_port_like_extra_vif(rpc_object, api_object, patch):
|
|||||||
int_info = rpc_object.internal_info.get('tenant_vif_port_id')
|
int_info = rpc_object.internal_info.get('tenant_vif_port_id')
|
||||||
if (int_info and int_info == rpc_object.extra.get('vif_port_id')):
|
if (int_info and int_info == rpc_object.extra.get('vif_port_id')):
|
||||||
api_object.internal_info.pop('tenant_vif_port_id')
|
api_object.internal_info.pop('tenant_vif_port_id')
|
||||||
|
|
||||||
|
|
||||||
|
def allow_detail_query():
|
||||||
|
"""Check if passing a detail=True query string is allowed.
|
||||||
|
|
||||||
|
Version 1.43 allows a user to pass the detail query string to
|
||||||
|
list the resource with all the fields.
|
||||||
|
"""
|
||||||
|
return (pecan.request.version.minor >=
|
||||||
|
versions.MINOR_43_ENABLE_DETAIL_QUERY)
|
||||||
|
|
||||||
|
|
||||||
|
def get_request_return_fields(fields, detail, default_fields):
|
||||||
|
"""Calculate fields to return from an API request
|
||||||
|
|
||||||
|
The fields query and detail=True query can not be passed into a request at
|
||||||
|
the same time. To use the detail query we need to be on a version of the
|
||||||
|
API greater than 1.43. This function raises an InvalidParameterValue
|
||||||
|
exception if either of these conditions are not met.
|
||||||
|
|
||||||
|
If these checks pass then this function will return either the fields
|
||||||
|
passed in or the default fields provided.
|
||||||
|
|
||||||
|
:param fields: The fields query passed into the API request.
|
||||||
|
:param detail: The detail query passed into the API request.
|
||||||
|
:param default_fields: The default fields to return if fields=None and
|
||||||
|
detail=None.
|
||||||
|
:raises: InvalidParameterValue if there is an invalid combination of query
|
||||||
|
strings or API version.
|
||||||
|
:returns: 'fields' passed in value or 'default_fields'
|
||||||
|
"""
|
||||||
|
|
||||||
|
if detail is not None and not allow_detail_query():
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
"Invalid query parameter ?detail=%s received." % detail)
|
||||||
|
|
||||||
|
if fields is not None and detail:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
"Can not specify ?detail=True and fields in the same request.")
|
||||||
|
|
||||||
|
if fields is None and not detail:
|
||||||
|
return default_fields
|
||||||
|
return fields
|
||||||
|
@ -80,6 +80,7 @@ BASE_VERSION = 1
|
|||||||
# Add bios_interface to the node object.
|
# Add bios_interface to the node object.
|
||||||
# v1.41: Add inspection abort support.
|
# v1.41: Add inspection abort support.
|
||||||
# v1.42: Expose fault field to node.
|
# v1.42: Expose fault field to node.
|
||||||
|
# v1.43: Add detail=True flag to all API endpoints
|
||||||
|
|
||||||
MINOR_0_JUNO = 0
|
MINOR_0_JUNO = 0
|
||||||
MINOR_1_INITIAL_VERSION = 1
|
MINOR_1_INITIAL_VERSION = 1
|
||||||
@ -124,6 +125,7 @@ MINOR_39_INSPECT_WAIT = 39
|
|||||||
MINOR_40_BIOS_INTERFACE = 40
|
MINOR_40_BIOS_INTERFACE = 40
|
||||||
MINOR_41_INSPECTION_ABORT = 41
|
MINOR_41_INSPECTION_ABORT = 41
|
||||||
MINOR_42_FAULT = 42
|
MINOR_42_FAULT = 42
|
||||||
|
MINOR_43_ENABLE_DETAIL_QUERY = 43
|
||||||
|
|
||||||
# When adding another version, update:
|
# When adding another version, update:
|
||||||
# - MINOR_MAX_VERSION
|
# - MINOR_MAX_VERSION
|
||||||
@ -131,7 +133,7 @@ MINOR_42_FAULT = 42
|
|||||||
# explanation of what changed in the new version
|
# explanation of what changed in the new version
|
||||||
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
||||||
|
|
||||||
MINOR_MAX_VERSION = MINOR_42_FAULT
|
MINOR_MAX_VERSION = MINOR_43_ENABLE_DETAIL_QUERY
|
||||||
|
|
||||||
# String representations of the minor and maximum versions
|
# String representations of the minor and maximum versions
|
||||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||||
|
@ -100,7 +100,7 @@ RELEASE_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'master': {
|
'master': {
|
||||||
'api': '1.42',
|
'api': '1.43',
|
||||||
'rpc': '1.44',
|
'rpc': '1.44',
|
||||||
'objects': {
|
'objects': {
|
||||||
'Node': ['1.25'],
|
'Node': ['1.25'],
|
||||||
|
@ -123,6 +123,49 @@ class TestListChassis(test_api_base.BaseApiTest):
|
|||||||
self.assertIn('extra', data['chassis'][0])
|
self.assertIn('extra', data['chassis'][0])
|
||||||
self.assertIn('nodes', data['chassis'][0])
|
self.assertIn('nodes', data['chassis'][0])
|
||||||
|
|
||||||
|
def test_detail_query(self):
|
||||||
|
chassis = obj_utils.create_test_chassis(self.context)
|
||||||
|
data = self.get_json(
|
||||||
|
'/chassis?detail=True',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertEqual(chassis.uuid, data['chassis'][0]["uuid"])
|
||||||
|
self.assertIn('extra', data['chassis'][0])
|
||||||
|
self.assertIn('nodes', data['chassis'][0])
|
||||||
|
|
||||||
|
def test_detail_query_false(self):
|
||||||
|
obj_utils.create_test_chassis(self.context)
|
||||||
|
data1 = self.get_json(
|
||||||
|
'/chassis',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
data2 = self.get_json(
|
||||||
|
'/chassis?detail=False',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertEqual(data1['chassis'], data2['chassis'])
|
||||||
|
|
||||||
|
def test_detail_using_query_and_fields(self):
|
||||||
|
obj_utils.create_test_chassis(self.context)
|
||||||
|
response = self.get_json(
|
||||||
|
'/chassis?detail=True&fields=description',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
|
def test_detail_using_query_false_and_fields(self):
|
||||||
|
obj_utils.create_test_chassis(self.context)
|
||||||
|
data = self.get_json(
|
||||||
|
'/chassis?detail=False&fields=description',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertIn('description', data['chassis'][0])
|
||||||
|
self.assertNotIn('uuid', data['chassis'][0])
|
||||||
|
|
||||||
|
def test_detail_using_query_old_version(self):
|
||||||
|
obj_utils.create_test_chassis(self.context)
|
||||||
|
response = self.get_json(
|
||||||
|
'/chassis?detail=True',
|
||||||
|
headers={api_base.Version.string: str(api_v1.min_version())},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
def test_detail_against_single(self):
|
def test_detail_against_single(self):
|
||||||
chassis = obj_utils.create_test_chassis(self.context)
|
chassis = obj_utils.create_test_chassis(self.context)
|
||||||
response = self.get_json('/chassis/%s/detail' % chassis['uuid'],
|
response = self.get_json('/chassis/%s/detail' % chassis['uuid'],
|
||||||
|
@ -430,6 +430,72 @@ class TestListNodes(test_api_base.BaseApiTest):
|
|||||||
# never expose the chassis_id
|
# never expose the chassis_id
|
||||||
self.assertNotIn('chassis_id', data['nodes'][0])
|
self.assertNotIn('chassis_id', data['nodes'][0])
|
||||||
|
|
||||||
|
def test_detail_using_query(self):
|
||||||
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
chassis_id=self.chassis.id)
|
||||||
|
data = self.get_json(
|
||||||
|
'/nodes?detail=True',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertEqual(node.uuid, data['nodes'][0]["uuid"])
|
||||||
|
self.assertIn('name', data['nodes'][0])
|
||||||
|
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_uuid', data['nodes'][0])
|
||||||
|
self.assertIn('reservation', data['nodes'][0])
|
||||||
|
self.assertIn('maintenance', data['nodes'][0])
|
||||||
|
self.assertIn('console_enabled', data['nodes'][0])
|
||||||
|
self.assertIn('target_power_state', data['nodes'][0])
|
||||||
|
self.assertIn('target_provision_state', data['nodes'][0])
|
||||||
|
self.assertIn('provision_updated_at', data['nodes'][0])
|
||||||
|
self.assertIn('inspection_finished_at', data['nodes'][0])
|
||||||
|
self.assertIn('inspection_started_at', data['nodes'][0])
|
||||||
|
self.assertIn('raid_config', data['nodes'][0])
|
||||||
|
self.assertIn('target_raid_config', data['nodes'][0])
|
||||||
|
self.assertIn('network_interface', data['nodes'][0])
|
||||||
|
self.assertIn('resource_class', data['nodes'][0])
|
||||||
|
for field in api_utils.V31_FIELDS:
|
||||||
|
self.assertIn(field, data['nodes'][0])
|
||||||
|
# never expose the chassis_id
|
||||||
|
self.assertNotIn('chassis_id', data['nodes'][0])
|
||||||
|
|
||||||
|
def test_detail_query_false(self):
|
||||||
|
obj_utils.create_test_node(self.context)
|
||||||
|
data1 = self.get_json(
|
||||||
|
'/nodes',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
data2 = self.get_json(
|
||||||
|
'/nodes?detail=False',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertEqual(data1['nodes'], data2['nodes'])
|
||||||
|
|
||||||
|
def test_detail_using_query_false_and_fields(self):
|
||||||
|
obj_utils.create_test_node(self.context)
|
||||||
|
data = self.get_json(
|
||||||
|
'/nodes?detail=False&fields=name',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertIn('name', data['nodes'][0])
|
||||||
|
self.assertNotIn('uuid', data['nodes'][0])
|
||||||
|
|
||||||
|
def test_detail_using_query_and_fields(self):
|
||||||
|
obj_utils.create_test_node(self.context,
|
||||||
|
chassis_id=self.chassis.id)
|
||||||
|
response = self.get_json(
|
||||||
|
'/nodes?detail=True&fields=name',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
|
def test_detail_using_query_old_version(self):
|
||||||
|
obj_utils.create_test_node(self.context,
|
||||||
|
chassis_id=self.chassis.id)
|
||||||
|
response = self.get_json(
|
||||||
|
'/nodes?detail=True',
|
||||||
|
headers={api_base.Version.string: str(api_v1.min_version())},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
def test_detail_against_single(self):
|
def test_detail_against_single(self):
|
||||||
node = obj_utils.create_test_node(self.context)
|
node = obj_utils.create_test_node(self.context)
|
||||||
response = self.get_json('/nodes/%s/detail' % node.uuid,
|
response = self.get_json('/nodes/%s/detail' % node.uuid,
|
||||||
|
@ -442,6 +442,74 @@ class TestListPorts(test_api_base.BaseApiTest):
|
|||||||
self.assertNotIn('node_id', data['ports'][0])
|
self.assertNotIn('node_id', data['ports'][0])
|
||||||
self.assertNotIn('portgroup_id', data['ports'][0])
|
self.assertNotIn('portgroup_id', data['ports'][0])
|
||||||
|
|
||||||
|
def test_detail_query(self):
|
||||||
|
llc = {'switch_info': 'switch', 'switch_id': 'aa:bb:cc:dd:ee:ff',
|
||||||
|
'port_id': 'Gig0/1'}
|
||||||
|
portgroup = obj_utils.create_test_portgroup(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||||
|
portgroup_id=portgroup.id,
|
||||||
|
pxe_enabled=False,
|
||||||
|
local_link_connection=llc,
|
||||||
|
physical_network='physnet1')
|
||||||
|
data = self.get_json(
|
||||||
|
'/ports?detail=True',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())}
|
||||||
|
)
|
||||||
|
self.assertEqual(port.uuid, data['ports'][0]["uuid"])
|
||||||
|
self.assertIn('extra', data['ports'][0])
|
||||||
|
self.assertIn('internal_info', data['ports'][0])
|
||||||
|
self.assertIn('node_uuid', data['ports'][0])
|
||||||
|
self.assertIn('pxe_enabled', data['ports'][0])
|
||||||
|
self.assertIn('local_link_connection', data['ports'][0])
|
||||||
|
self.assertIn('portgroup_uuid', data['ports'][0])
|
||||||
|
self.assertIn('physical_network', data['ports'][0])
|
||||||
|
# never expose the node_id and portgroup_id
|
||||||
|
self.assertNotIn('node_id', data['ports'][0])
|
||||||
|
self.assertNotIn('portgroup_id', data['ports'][0])
|
||||||
|
|
||||||
|
def test_detail_query_false(self):
|
||||||
|
obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||||
|
pxe_enabled=False,
|
||||||
|
physical_network='physnet1')
|
||||||
|
data1 = self.get_json(
|
||||||
|
'/ports',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
data2 = self.get_json(
|
||||||
|
'/ports?detail=False',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertEqual(data1['ports'], data2['ports'])
|
||||||
|
|
||||||
|
def test_detail_using_query_false_and_fields(self):
|
||||||
|
obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||||
|
pxe_enabled=False,
|
||||||
|
physical_network='physnet1')
|
||||||
|
data = self.get_json(
|
||||||
|
'/ports?detail=False&fields=internal_info',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertIn('internal_info', data['ports'][0])
|
||||||
|
self.assertNotIn('uuid', data['ports'][0])
|
||||||
|
|
||||||
|
def test_detail_using_query_and_fields(self):
|
||||||
|
obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||||
|
pxe_enabled=False,
|
||||||
|
physical_network='physnet1')
|
||||||
|
response = self.get_json(
|
||||||
|
'/ports?detail=True&fields=name',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
|
def test_detail_using_query_old_version(self):
|
||||||
|
obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||||
|
pxe_enabled=False,
|
||||||
|
physical_network='physnet1')
|
||||||
|
response = self.get_json(
|
||||||
|
'/ports?detail=True',
|
||||||
|
headers={api_base.Version.string: str(api_v1.min_version())},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
def test_detail_against_single(self):
|
def test_detail_against_single(self):
|
||||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id)
|
port = obj_utils.create_test_port(self.context, node_id=self.node.id)
|
||||||
response = self.get_json('/ports/%s/detail' % port.uuid,
|
response = self.get_json('/ports/%s/detail' % port.uuid,
|
||||||
|
@ -201,6 +201,55 @@ class TestListPortgroups(test_api_base.BaseApiTest):
|
|||||||
# never expose the node_id
|
# never expose the node_id
|
||||||
self.assertNotIn('node_id', data['portgroups'][0])
|
self.assertNotIn('node_id', data['portgroups'][0])
|
||||||
|
|
||||||
|
def test_detail_query(self):
|
||||||
|
portgroup = obj_utils.create_test_portgroup(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
data = self.get_json('/portgroups?detail=True', headers=self.headers)
|
||||||
|
self.assertEqual(portgroup.uuid, data['portgroups'][0]["uuid"])
|
||||||
|
self.assertIn('extra', data['portgroups'][0])
|
||||||
|
self.assertIn('node_uuid', data['portgroups'][0])
|
||||||
|
self.assertIn('standalone_ports_supported', data['portgroups'][0])
|
||||||
|
# never expose the node_id
|
||||||
|
self.assertNotIn('node_id', data['portgroups'][0])
|
||||||
|
|
||||||
|
def test_detail_query_false(self):
|
||||||
|
obj_utils.create_test_portgroup(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
data1 = self.get_json(
|
||||||
|
'/portgroups',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
data2 = self.get_json(
|
||||||
|
'/portgroups?detail=False',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertEqual(data1['portgroups'], data2['portgroups'])
|
||||||
|
|
||||||
|
def test_detail_using_query_false_and_fields(self):
|
||||||
|
obj_utils.create_test_portgroup(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
data = self.get_json(
|
||||||
|
'/portgroups?detail=False&fields=internal_info',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertIn('internal_info', data['portgroups'][0])
|
||||||
|
self.assertNotIn('uuid', data['portgroups'][0])
|
||||||
|
|
||||||
|
def test_detail_using_query_and_fields(self):
|
||||||
|
obj_utils.create_test_portgroup(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
response = self.get_json(
|
||||||
|
'/portgroups?detail=True&fields=name',
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
|
def test_detail_using_query_old_version(self):
|
||||||
|
obj_utils.create_test_portgroup(self.context,
|
||||||
|
node_id=self.node.id)
|
||||||
|
response = self.get_json(
|
||||||
|
'/portgroups?detail=True',
|
||||||
|
headers={api_base.Version.string: '1.42'},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
def test_detail_invalid_api_version(self):
|
def test_detail_invalid_api_version(self):
|
||||||
response = self.get_json(
|
response = self.get_json(
|
||||||
'/portgroups/detail',
|
'/portgroups/detail',
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add ``?detail=`` boolean query to the API list endpoints to provide a more
|
||||||
|
RESTful alternative to the existing ``/nodes/detail`` and similar endpoints. The
|
||||||
|
default is False. Now these API requests are possible:
|
||||||
|
|
||||||
|
* ``/nodes?detail=True``
|
||||||
|
* ``/ports?detail=True``
|
||||||
|
* ``/chassis?detail=True``
|
||||||
|
* ``/portgroups?detail=True``
|
Loading…
Reference in New Issue
Block a user