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.
|
||||
|
||||
.. 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
|
||||
|
||||
.. TODO: add error codes
|
||||
@ -217,6 +221,7 @@ Request
|
||||
- sort_dir: sort_dir
|
||||
- sort_key: sort_key
|
||||
- fields: fields
|
||||
- detail: detail
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
@ -132,7 +132,7 @@ and any defaults added for non-specified fields. Most fields default to "null"
|
||||
or "".
|
||||
|
||||
The list and example below are representative of the response as of API
|
||||
microversion 1.42.
|
||||
microversion 1.43.
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
@ -215,6 +215,10 @@ provision state, and maintenance setting for each Node.
|
||||
.. versionadded:: 1.42
|
||||
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
|
||||
|
||||
Error codes: 400,403,406
|
||||
@ -236,6 +240,7 @@ Request
|
||||
- marker: marker
|
||||
- sort_dir: sort_dir
|
||||
- sort_key: sort_key
|
||||
- detail: detail
|
||||
|
||||
Response
|
||||
--------
|
||||
@ -261,6 +266,9 @@ List Nodes Detailed
|
||||
|
||||
.. 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
|
||||
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.
|
||||
|
||||
.. 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
|
||||
|
||||
Error codes: 400,401,403,404
|
||||
@ -44,6 +48,7 @@ Request
|
||||
- marker: marker
|
||||
- sort_dir: sort_dir
|
||||
- sort_key: sort_key
|
||||
- detail: detail
|
||||
|
||||
Response
|
||||
--------
|
||||
|
@ -42,6 +42,10 @@ By default, this query will return the uuid and address for each Port.
|
||||
.. versionadded:: 1.34
|
||||
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
|
||||
|
||||
Request
|
||||
@ -58,6 +62,7 @@ Request
|
||||
- marker: marker
|
||||
- sort_dir: sort_dir
|
||||
- sort_key: sort_key
|
||||
- detail: detail
|
||||
|
||||
Response
|
||||
--------
|
||||
|
@ -2,6 +2,12 @@
|
||||
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)
|
||||
--------------------
|
||||
|
||||
|
@ -174,7 +174,7 @@ class ChassisController(rest.RestController):
|
||||
invalid_sort_key_list = ['extra']
|
||||
|
||||
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)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
marker_obj = None
|
||||
@ -190,17 +190,22 @@ class ChassisController(rest.RestController):
|
||||
chassis = objects.Chassis.list(pecan.request.context, limit,
|
||||
marker_obj, sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
parameters = {}
|
||||
if detail is not None:
|
||||
parameters['detail'] = detail
|
||||
|
||||
return ChassisCollection.convert_with_links(chassis, limit,
|
||||
url=resource_url,
|
||||
fields=fields,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
**parameters)
|
||||
|
||||
@METRICS.timer('ChassisController.get_all')
|
||||
@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',
|
||||
fields=None):
|
||||
fields=None, detail=None):
|
||||
"""Retrieve a list of chassis.
|
||||
|
||||
:param marker: pagination marker for large data sets.
|
||||
@ -217,10 +222,12 @@ class ChassisController(rest.RestController):
|
||||
policy.authorize('baremetal:chassis:get', cdict, cdict)
|
||||
|
||||
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,
|
||||
fields=fields)
|
||||
fields=fields, detail=detail)
|
||||
|
||||
@METRICS.timer('ChassisController.detail')
|
||||
@expose.expose(ChassisCollection, types.uuid, int,
|
||||
|
@ -1524,7 +1524,7 @@ class NodesController(rest.RestController):
|
||||
maintenance, provision_state, marker, limit,
|
||||
sort_key, sort_dir, driver=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:
|
||||
raise exception.MissingParameterValue(
|
||||
_("Chassis id not specified."))
|
||||
@ -1583,6 +1583,9 @@ class NodesController(rest.RestController):
|
||||
if maintenance:
|
||||
parameters['maintenance'] = maintenance
|
||||
|
||||
if detail is not None:
|
||||
parameters['detail'] = detail
|
||||
|
||||
return NodeCollection.convert_with_links(nodes, limit,
|
||||
url=resource_url,
|
||||
fields=fields,
|
||||
@ -1676,11 +1679,11 @@ class NodesController(rest.RestController):
|
||||
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
||||
types.boolean, wtypes.text, types.uuid, int, 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,
|
||||
maintenance=None, provision_state=None, marker=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.
|
||||
|
||||
: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_resource_class(resource_class)
|
||||
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,
|
||||
associated, maintenance,
|
||||
provision_state, marker,
|
||||
limit, sort_key, sort_dir,
|
||||
driver=driver,
|
||||
resource_class=resource_class,
|
||||
fields=fields, fault=fault)
|
||||
fields=fields, fault=fault,
|
||||
detail=detail)
|
||||
|
||||
@METRICS.timer('NodesController.detail')
|
||||
@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,
|
||||
marker, limit, sort_key, sort_dir,
|
||||
resource_url=None, fields=None):
|
||||
resource_url=None, fields=None, detail=None):
|
||||
|
||||
limit = api_utils.validate_limit(limit)
|
||||
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,
|
||||
marker_obj, sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
parameters = {}
|
||||
|
||||
if detail is not None:
|
||||
parameters['detail'] = detail
|
||||
|
||||
return PortCollection.convert_with_links(ports, limit,
|
||||
url=resource_url,
|
||||
fields=fields,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
**parameters)
|
||||
|
||||
def _get_ports_by_address(self, address):
|
||||
"""Retrieve a port by its address.
|
||||
@ -407,10 +412,11 @@ class PortsController(rest.RestController):
|
||||
@METRICS.timer('PortsController.get_all')
|
||||
@expose.expose(PortCollection, types.uuid_or_name, types.uuid,
|
||||
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,
|
||||
limit=None, sort_key='id', sort_dir='asc', fields=None,
|
||||
portgroup=None):
|
||||
portgroup=None, detail=None):
|
||||
"""Retrieve a list of ports.
|
||||
|
||||
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)
|
||||
self._check_allowed_port_fields(fields)
|
||||
self._check_allowed_port_fields([sort_key])
|
||||
|
||||
if portgroup and not api_utils.allow_portgroups_subcontrollers():
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
if fields is None:
|
||||
fields = _DEFAULT_RETURN_FIELDS
|
||||
fields = api_utils.get_request_return_fields(fields, detail,
|
||||
_DEFAULT_RETURN_FIELDS)
|
||||
|
||||
if not node_uuid and node:
|
||||
# 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,
|
||||
portgroup, marker, limit, sort_key,
|
||||
sort_dir, fields=fields)
|
||||
sort_dir, fields=fields,
|
||||
detail=detail)
|
||||
|
||||
@METRICS.timer('PortsController.detail')
|
||||
@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,
|
||||
marker, limit, sort_key, sort_dir,
|
||||
resource_url=None, fields=None):
|
||||
resource_url=None, fields=None,
|
||||
detail=None):
|
||||
"""Return portgroups collection.
|
||||
|
||||
: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,
|
||||
marker_obj, sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
parameters = {}
|
||||
if detail is not None:
|
||||
parameters['detail'] = detail
|
||||
|
||||
return PortgroupCollection.convert_with_links(portgroups, limit,
|
||||
url=resource_url,
|
||||
fields=fields,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
**parameters)
|
||||
|
||||
def _get_portgroups_by_address(self, address):
|
||||
"""Retrieve a portgroup by its address.
|
||||
@ -330,9 +335,11 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
|
||||
@METRICS.timer('PortgroupsController.get_all')
|
||||
@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,
|
||||
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.
|
||||
|
||||
: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([sort_key])
|
||||
|
||||
if fields is None:
|
||||
fields = _DEFAULT_RETURN_FIELDS
|
||||
fields = api_utils.get_request_return_fields(fields, detail,
|
||||
_DEFAULT_RETURN_FIELDS)
|
||||
|
||||
return self._get_portgroups_collection(node, address,
|
||||
marker, limit,
|
||||
sort_key, sort_dir,
|
||||
fields=fields)
|
||||
fields=fields,
|
||||
detail=detail)
|
||||
|
||||
@METRICS.timer('PortgroupsController.detail')
|
||||
@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')
|
||||
if (int_info and int_info == rpc_object.extra.get('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.
|
||||
# v1.41: Add inspection abort support.
|
||||
# v1.42: Expose fault field to node.
|
||||
# v1.43: Add detail=True flag to all API endpoints
|
||||
|
||||
MINOR_0_JUNO = 0
|
||||
MINOR_1_INITIAL_VERSION = 1
|
||||
@ -124,6 +125,7 @@ MINOR_39_INSPECT_WAIT = 39
|
||||
MINOR_40_BIOS_INTERFACE = 40
|
||||
MINOR_41_INSPECTION_ABORT = 41
|
||||
MINOR_42_FAULT = 42
|
||||
MINOR_43_ENABLE_DETAIL_QUERY = 43
|
||||
|
||||
# When adding another version, update:
|
||||
# - MINOR_MAX_VERSION
|
||||
@ -131,7 +133,7 @@ MINOR_42_FAULT = 42
|
||||
# explanation of what changed in the new version
|
||||
# - 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
|
||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||
|
@ -100,7 +100,7 @@ RELEASE_MAPPING = {
|
||||
}
|
||||
},
|
||||
'master': {
|
||||
'api': '1.42',
|
||||
'api': '1.43',
|
||||
'rpc': '1.44',
|
||||
'objects': {
|
||||
'Node': ['1.25'],
|
||||
|
@ -123,6 +123,49 @@ class TestListChassis(test_api_base.BaseApiTest):
|
||||
self.assertIn('extra', 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):
|
||||
chassis = obj_utils.create_test_chassis(self.context)
|
||||
response = self.get_json('/chassis/%s/detail' % chassis['uuid'],
|
||||
|
@ -430,6 +430,72 @@ class TestListNodes(test_api_base.BaseApiTest):
|
||||
# never expose the chassis_id
|
||||
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):
|
||||
node = obj_utils.create_test_node(self.context)
|
||||
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('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):
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id)
|
||||
response = self.get_json('/ports/%s/detail' % port.uuid,
|
||||
|
@ -201,6 +201,55 @@ class TestListPortgroups(test_api_base.BaseApiTest):
|
||||
# never expose the node_id
|
||||
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):
|
||||
response = self.get_json(
|
||||
'/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