Add support for fields in drivers API
This commit add support for the fields query parameter to the GET /v1/drivers?fields=... and GET /v1/drivers/<driver_name>?fields=.... Story: 1674775 Task: 10581 Change-Id: I2ca4eb490e320e736a93851eed526ec862be901e
This commit is contained in:
parent
183325d464
commit
ee06761b0e
@ -49,6 +49,9 @@ List drivers
|
||||
|
||||
Lists all drivers.
|
||||
|
||||
.. versionadded:: 1.77
|
||||
Added ``fields`` selector to query for particular fields.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Request
|
||||
@ -58,6 +61,7 @@ Request
|
||||
|
||||
- type: driver_type
|
||||
- detail: driver_detail
|
||||
- fields: fields
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
@ -125,6 +129,9 @@ Show driver details
|
||||
|
||||
Shows details for a driver.
|
||||
|
||||
.. versionadded:: 1.77
|
||||
Added ``fields`` selector to query for particular fields.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Request
|
||||
@ -133,6 +140,7 @@ Request
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- driver_name: driver_ident
|
||||
- fields: fields
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
@ -2,6 +2,13 @@
|
||||
REST API Version History
|
||||
========================
|
||||
|
||||
1.77
|
||||
----------------------
|
||||
Add a fields selector to the the Drivers list:
|
||||
* ``GET /v1/drivers?fields=``
|
||||
Also add a fields selector to the the Driver detail:
|
||||
* ``GET /v1/drivers/{driver_name}?fields=``
|
||||
|
||||
1.76 (Xena, ?)
|
||||
----------------------
|
||||
Add endpoints for changing boot mode and secure boot state of node
|
||||
|
@ -81,7 +81,8 @@ def hide_fields_in_newer_versions(driver):
|
||||
driver.pop('enabled_bios_interfaces', None)
|
||||
|
||||
|
||||
def convert_with_links(name, hosts, detail=False, interface_info=None):
|
||||
def convert_with_links(name, hosts, detail=False, interface_info=None,
|
||||
fields=None, sanitize=True):
|
||||
"""Convert driver/hardware type info to a dict.
|
||||
|
||||
:param name: name of a hardware type.
|
||||
@ -90,6 +91,8 @@ def convert_with_links(name, hosts, detail=False, interface_info=None):
|
||||
the 'type' field and default/enabled interfaces fields.
|
||||
:param interface_info: optional list of dicts of hardware interface
|
||||
info.
|
||||
:param fields: list of fields to preserve, or ``None`` to preserve default
|
||||
:param sanitize: boolean, sanitize driver
|
||||
:returns: dict representing the driver object.
|
||||
"""
|
||||
driver = {
|
||||
@ -143,16 +146,35 @@ def convert_with_links(name, hosts, detail=False, interface_info=None):
|
||||
driver[enabled_key] = list(enabled)
|
||||
|
||||
hide_fields_in_newer_versions(driver)
|
||||
|
||||
if not sanitize:
|
||||
return driver
|
||||
|
||||
driver_sanitize(driver, fields)
|
||||
|
||||
return driver
|
||||
|
||||
|
||||
def list_convert_with_links(hardware_types, detail=False):
|
||||
def driver_sanitize(driver, fields=None):
|
||||
if fields is not None:
|
||||
api_utils.sanitize_dict(driver, fields)
|
||||
api_utils.check_for_invalid_fields(fields, driver)
|
||||
|
||||
|
||||
def _check_allow_driver_fields(fields):
|
||||
if (fields is not None and api.request.version.minor
|
||||
< api.controllers.v1.versions.MINOR_77_DRIVER_FIELDS_SELECTOR):
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
|
||||
def list_convert_with_links(hardware_types, detail=False, fields=None):
|
||||
"""Convert drivers and hardware types to an API-serializable object.
|
||||
|
||||
:param hardware_types: dict mapping hardware type names to conductor
|
||||
hostnames.
|
||||
:param detail: boolean, whether to include detailed info, such as
|
||||
the 'type' field and default/enabled interfaces fields.
|
||||
:param fields: list of fields to preserve, or ``None`` to preserve default
|
||||
:returns: an API-serializable driver collection object.
|
||||
"""
|
||||
drivers = []
|
||||
@ -177,7 +199,8 @@ def list_convert_with_links(hardware_types, detail=False):
|
||||
convert_with_links(htname,
|
||||
list(hardware_types[htname]),
|
||||
detail=detail,
|
||||
interface_info=interface_info))
|
||||
interface_info=interface_info,
|
||||
fields=fields))
|
||||
return collection
|
||||
|
||||
|
||||
@ -294,16 +317,22 @@ class DriversController(rest.RestController):
|
||||
|
||||
@METRICS.timer('DriversController.get_all')
|
||||
@method.expose()
|
||||
@args.validate(type=args.string, detail=args.boolean)
|
||||
def get_all(self, type=None, detail=None):
|
||||
@args.validate(type=args.string, detail=args.boolean,
|
||||
fields=args.string_list)
|
||||
def get_all(self, type=None, detail=None, fields=None):
|
||||
"""Retrieve a list of drivers."""
|
||||
# FIXME(tenbrae): formatting of the auto-generated REST API docs
|
||||
# will break from a single-line doc string.
|
||||
# This is a result of a bug in sphinxcontrib-pecanwsme
|
||||
# https://github.com/dreamhost/sphinxcontrib-pecanwsme/issues/8
|
||||
if fields and detail:
|
||||
raise exception.InvalidParameterValue(
|
||||
"Can not specify ?detail=True and fields in the same request.")
|
||||
|
||||
api_utils.check_policy('baremetal:driver:get')
|
||||
api_utils.check_allow_driver_detail(detail)
|
||||
api_utils.check_allow_filter_driver_type(type)
|
||||
_check_allow_driver_fields(fields)
|
||||
if type not in (None, 'classic', 'dynamic'):
|
||||
raise exception.Invalid(_(
|
||||
'"type" filter must be one of "classic" or "dynamic", '
|
||||
@ -315,12 +344,13 @@ class DriversController(rest.RestController):
|
||||
# NOTE(dtantsur): we don't support classic drivers starting with
|
||||
# the Rocky release.
|
||||
hw_type_dict = {}
|
||||
return list_convert_with_links(hw_type_dict, detail=detail)
|
||||
return list_convert_with_links(hw_type_dict, detail=detail,
|
||||
fields=fields)
|
||||
|
||||
@METRICS.timer('DriversController.get_one')
|
||||
@method.expose()
|
||||
@args.validate(driver_name=args.string)
|
||||
def get_one(self, driver_name):
|
||||
@args.validate(driver_name=args.string, fields=args.string_list)
|
||||
def get_one(self, driver_name, fields=None):
|
||||
"""Retrieve a single driver."""
|
||||
# NOTE(russell_h): There is no way to make this more efficient than
|
||||
# retrieving a list of drivers using the current sqlalchemy schema, but
|
||||
@ -328,11 +358,13 @@ class DriversController(rest.RestController):
|
||||
# choose to expose below it.
|
||||
api_utils.check_policy('baremetal:driver:get')
|
||||
|
||||
_check_allow_driver_fields(fields)
|
||||
|
||||
hw_type_dict = api.request.dbapi.get_active_hardware_type_dict()
|
||||
for name, hosts in hw_type_dict.items():
|
||||
if name == driver_name:
|
||||
return convert_with_links(name, list(hosts),
|
||||
detail=True)
|
||||
detail=True, fields=fields)
|
||||
|
||||
raise exception.DriverNotFound(driver_name=driver_name)
|
||||
|
||||
|
@ -114,6 +114,7 @@ BASE_VERSION = 1
|
||||
# v1.74: Add bios registry to /v1/nodes/{node}/bios/{setting}
|
||||
# v1.75: Add boot_mode, secure_boot fields to node object.
|
||||
# v1.76: Add support for changing boot_mode and secure_boot state
|
||||
# v1.77: Add fields selector to drivers list and driver detail.
|
||||
|
||||
MINOR_0_JUNO = 0
|
||||
MINOR_1_INITIAL_VERSION = 1
|
||||
@ -192,6 +193,7 @@ MINOR_73_DEPLOY_UNDEPLOY_VERBS = 73
|
||||
MINOR_74_BIOS_REGISTRY = 74
|
||||
MINOR_75_NODE_BOOT_MODE = 75
|
||||
MINOR_76_NODE_CHANGE_BOOT_MODE = 76
|
||||
MINOR_77_DRIVER_FIELDS_SELECTOR = 77
|
||||
|
||||
# When adding another version, update:
|
||||
# - MINOR_MAX_VERSION
|
||||
@ -199,7 +201,7 @@ MINOR_76_NODE_CHANGE_BOOT_MODE = 76
|
||||
# explanation of what changed in the new version
|
||||
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
||||
|
||||
MINOR_MAX_VERSION = MINOR_76_NODE_CHANGE_BOOT_MODE
|
||||
MINOR_MAX_VERSION = MINOR_77_DRIVER_FIELDS_SELECTOR
|
||||
|
||||
# String representations of the minor and maximum versions
|
||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||
|
@ -371,7 +371,7 @@ RELEASE_MAPPING = {
|
||||
}
|
||||
},
|
||||
'master': {
|
||||
'api': '1.76',
|
||||
'api': '1.77',
|
||||
'rpc': '1.55',
|
||||
'objects': {
|
||||
'Allocation': ['1.1'],
|
||||
|
@ -274,6 +274,77 @@ class TestListDrivers(base.BaseApiTest):
|
||||
response = self.get_json('/drivers/nope', expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, response.status_int)
|
||||
|
||||
def test_drivers_collection_custom_fields(self):
|
||||
self.register_fake_conductors()
|
||||
fields = "name,hosts"
|
||||
data = self.get_json('/drivers?fields=%s' % fields,
|
||||
headers={api_base.Version.string: '1.77'})
|
||||
for data_driver in data['drivers']:
|
||||
self.assertCountEqual(['name', 'hosts', 'links'], data_driver)
|
||||
|
||||
def test_get_one_custom_fields(self):
|
||||
self.register_fake_conductors()
|
||||
driver = self.hw1
|
||||
fields = "name,hosts"
|
||||
data = self.get_json('/drivers/%s?fields=%s' % (driver, fields),
|
||||
headers={api_base.Version.string: '1.77'})
|
||||
self.assertCountEqual(['name', 'hosts', 'links'], data)
|
||||
|
||||
def test_get_custom_fields_invalid_api_version(self):
|
||||
self.register_fake_conductors()
|
||||
driver = self.hw1
|
||||
fields = "name,hosts"
|
||||
|
||||
response = self.get_json('/drivers?fields=%s' % fields,
|
||||
headers={api_base.Version.string: '1.76'},
|
||||
expect_errors=True)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_int)
|
||||
|
||||
response = self.get_json('/drivers/%s?fields=%s' % (driver, fields),
|
||||
headers={api_base.Version.string: '1.76'},
|
||||
expect_errors=True)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_int)
|
||||
|
||||
def test_drivers_collection_custom_fields_with_detail_true(self):
|
||||
self.register_fake_conductors()
|
||||
fields = "name,hosts"
|
||||
response = self.get_json('/drivers?detail=true&fields=%s' % fields,
|
||||
headers={api_base.Version.string: '1.77'},
|
||||
expect_errors=True)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
|
||||
def test_drivers_collection_custom_fields_with_detail_false(self):
|
||||
self.register_fake_conductors()
|
||||
fields = "name,hosts"
|
||||
data = self.get_json('/drivers?fields=%s&detail=false' % fields,
|
||||
headers={api_base.Version.string: '1.77'})
|
||||
for data_driver in data['drivers']:
|
||||
self.assertCountEqual(['name', 'hosts', 'links'], data_driver)
|
||||
|
||||
def test_drivers_collection_invalid_custom_fields(self):
|
||||
self.register_fake_conductors()
|
||||
fields = "name,invalid"
|
||||
response = self.get_json('/drivers?fields=%s' % fields,
|
||||
headers={api_base.Version.string: '1.77'},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertIn('invalid', response.json['error_message'])
|
||||
|
||||
def test_get_one_invalid_custom_fields(self):
|
||||
self.register_fake_conductors()
|
||||
driver = self.hw1
|
||||
fields = "name,invalid"
|
||||
response = self.get_json('/drivers/%s?fields=%s' % (driver, fields),
|
||||
headers={api_base.Version.string: '1.77'},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertIn('invalid', response.json['error_message'])
|
||||
|
||||
def _test_links(self, public_url=None):
|
||||
cfg.CONF.set_override('public_endpoint', public_url, 'api')
|
||||
self.register_fake_conductors()
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for fields selector in driver api.
|
||||
See `story 1674775
|
||||
<https://storyboard.openstack.org/#!/story/1674775>`_.
|
||||
|
||||
* ``GET /v1/drivers?fields=...``
|
||||
* ``GET /v1/drivers/{driver_name}?fields=...``
|
Loading…
Reference in New Issue
Block a user