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.
|
Lists all drivers.
|
||||||
|
|
||||||
|
.. versionadded:: 1.77
|
||||||
|
Added ``fields`` selector to query for particular fields.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Request
|
Request
|
||||||
@ -58,6 +61,7 @@ Request
|
|||||||
|
|
||||||
- type: driver_type
|
- type: driver_type
|
||||||
- detail: driver_detail
|
- detail: driver_detail
|
||||||
|
- fields: fields
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
-------------------
|
-------------------
|
||||||
@ -125,6 +129,9 @@ Show driver details
|
|||||||
|
|
||||||
Shows details for a driver.
|
Shows details for a driver.
|
||||||
|
|
||||||
|
.. versionadded:: 1.77
|
||||||
|
Added ``fields`` selector to query for particular fields.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Request
|
Request
|
||||||
@ -133,6 +140,7 @@ Request
|
|||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- driver_name: driver_ident
|
- driver_name: driver_ident
|
||||||
|
- fields: fields
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
REST API Version History
|
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, ?)
|
1.76 (Xena, ?)
|
||||||
----------------------
|
----------------------
|
||||||
Add endpoints for changing boot mode and secure boot state of node
|
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)
|
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.
|
"""Convert driver/hardware type info to a dict.
|
||||||
|
|
||||||
:param name: name of a hardware type.
|
: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.
|
the 'type' field and default/enabled interfaces fields.
|
||||||
:param interface_info: optional list of dicts of hardware interface
|
:param interface_info: optional list of dicts of hardware interface
|
||||||
info.
|
info.
|
||||||
|
:param fields: list of fields to preserve, or ``None`` to preserve default
|
||||||
|
:param sanitize: boolean, sanitize driver
|
||||||
:returns: dict representing the driver object.
|
:returns: dict representing the driver object.
|
||||||
"""
|
"""
|
||||||
driver = {
|
driver = {
|
||||||
@ -143,16 +146,35 @@ def convert_with_links(name, hosts, detail=False, interface_info=None):
|
|||||||
driver[enabled_key] = list(enabled)
|
driver[enabled_key] = list(enabled)
|
||||||
|
|
||||||
hide_fields_in_newer_versions(driver)
|
hide_fields_in_newer_versions(driver)
|
||||||
|
|
||||||
|
if not sanitize:
|
||||||
|
return driver
|
||||||
|
|
||||||
|
driver_sanitize(driver, fields)
|
||||||
|
|
||||||
return driver
|
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.
|
"""Convert drivers and hardware types to an API-serializable object.
|
||||||
|
|
||||||
:param hardware_types: dict mapping hardware type names to conductor
|
:param hardware_types: dict mapping hardware type names to conductor
|
||||||
hostnames.
|
hostnames.
|
||||||
:param detail: boolean, whether to include detailed info, such as
|
:param detail: boolean, whether to include detailed info, such as
|
||||||
the 'type' field and default/enabled interfaces fields.
|
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.
|
:returns: an API-serializable driver collection object.
|
||||||
"""
|
"""
|
||||||
drivers = []
|
drivers = []
|
||||||
@ -177,7 +199,8 @@ def list_convert_with_links(hardware_types, detail=False):
|
|||||||
convert_with_links(htname,
|
convert_with_links(htname,
|
||||||
list(hardware_types[htname]),
|
list(hardware_types[htname]),
|
||||||
detail=detail,
|
detail=detail,
|
||||||
interface_info=interface_info))
|
interface_info=interface_info,
|
||||||
|
fields=fields))
|
||||||
return collection
|
return collection
|
||||||
|
|
||||||
|
|
||||||
@ -294,16 +317,22 @@ class DriversController(rest.RestController):
|
|||||||
|
|
||||||
@METRICS.timer('DriversController.get_all')
|
@METRICS.timer('DriversController.get_all')
|
||||||
@method.expose()
|
@method.expose()
|
||||||
@args.validate(type=args.string, detail=args.boolean)
|
@args.validate(type=args.string, detail=args.boolean,
|
||||||
def get_all(self, type=None, detail=None):
|
fields=args.string_list)
|
||||||
|
def get_all(self, type=None, detail=None, fields=None):
|
||||||
"""Retrieve a list of drivers."""
|
"""Retrieve a list of drivers."""
|
||||||
# FIXME(tenbrae): formatting of the auto-generated REST API docs
|
# FIXME(tenbrae): formatting of the auto-generated REST API docs
|
||||||
# will break from a single-line doc string.
|
# will break from a single-line doc string.
|
||||||
# This is a result of a bug in sphinxcontrib-pecanwsme
|
# This is a result of a bug in sphinxcontrib-pecanwsme
|
||||||
# https://github.com/dreamhost/sphinxcontrib-pecanwsme/issues/8
|
# 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_policy('baremetal:driver:get')
|
||||||
api_utils.check_allow_driver_detail(detail)
|
api_utils.check_allow_driver_detail(detail)
|
||||||
api_utils.check_allow_filter_driver_type(type)
|
api_utils.check_allow_filter_driver_type(type)
|
||||||
|
_check_allow_driver_fields(fields)
|
||||||
if type not in (None, 'classic', 'dynamic'):
|
if type not in (None, 'classic', 'dynamic'):
|
||||||
raise exception.Invalid(_(
|
raise exception.Invalid(_(
|
||||||
'"type" filter must be one of "classic" or "dynamic", '
|
'"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
|
# NOTE(dtantsur): we don't support classic drivers starting with
|
||||||
# the Rocky release.
|
# the Rocky release.
|
||||||
hw_type_dict = {}
|
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')
|
@METRICS.timer('DriversController.get_one')
|
||||||
@method.expose()
|
@method.expose()
|
||||||
@args.validate(driver_name=args.string)
|
@args.validate(driver_name=args.string, fields=args.string_list)
|
||||||
def get_one(self, driver_name):
|
def get_one(self, driver_name, fields=None):
|
||||||
"""Retrieve a single driver."""
|
"""Retrieve a single driver."""
|
||||||
# NOTE(russell_h): There is no way to make this more efficient than
|
# NOTE(russell_h): There is no way to make this more efficient than
|
||||||
# retrieving a list of drivers using the current sqlalchemy schema, but
|
# retrieving a list of drivers using the current sqlalchemy schema, but
|
||||||
@ -328,11 +358,13 @@ class DriversController(rest.RestController):
|
|||||||
# choose to expose below it.
|
# choose to expose below it.
|
||||||
api_utils.check_policy('baremetal:driver:get')
|
api_utils.check_policy('baremetal:driver:get')
|
||||||
|
|
||||||
|
_check_allow_driver_fields(fields)
|
||||||
|
|
||||||
hw_type_dict = api.request.dbapi.get_active_hardware_type_dict()
|
hw_type_dict = api.request.dbapi.get_active_hardware_type_dict()
|
||||||
for name, hosts in hw_type_dict.items():
|
for name, hosts in hw_type_dict.items():
|
||||||
if name == driver_name:
|
if name == driver_name:
|
||||||
return convert_with_links(name, list(hosts),
|
return convert_with_links(name, list(hosts),
|
||||||
detail=True)
|
detail=True, fields=fields)
|
||||||
|
|
||||||
raise exception.DriverNotFound(driver_name=driver_name)
|
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.74: Add bios registry to /v1/nodes/{node}/bios/{setting}
|
||||||
# v1.75: Add boot_mode, secure_boot fields to node object.
|
# v1.75: Add boot_mode, secure_boot fields to node object.
|
||||||
# v1.76: Add support for changing boot_mode and secure_boot state
|
# 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_0_JUNO = 0
|
||||||
MINOR_1_INITIAL_VERSION = 1
|
MINOR_1_INITIAL_VERSION = 1
|
||||||
@ -192,6 +193,7 @@ MINOR_73_DEPLOY_UNDEPLOY_VERBS = 73
|
|||||||
MINOR_74_BIOS_REGISTRY = 74
|
MINOR_74_BIOS_REGISTRY = 74
|
||||||
MINOR_75_NODE_BOOT_MODE = 75
|
MINOR_75_NODE_BOOT_MODE = 75
|
||||||
MINOR_76_NODE_CHANGE_BOOT_MODE = 76
|
MINOR_76_NODE_CHANGE_BOOT_MODE = 76
|
||||||
|
MINOR_77_DRIVER_FIELDS_SELECTOR = 77
|
||||||
|
|
||||||
# When adding another version, update:
|
# When adding another version, update:
|
||||||
# - MINOR_MAX_VERSION
|
# - MINOR_MAX_VERSION
|
||||||
@ -199,7 +201,7 @@ MINOR_76_NODE_CHANGE_BOOT_MODE = 76
|
|||||||
# 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_76_NODE_CHANGE_BOOT_MODE
|
MINOR_MAX_VERSION = MINOR_77_DRIVER_FIELDS_SELECTOR
|
||||||
|
|
||||||
# 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)
|
||||||
|
@ -371,7 +371,7 @@ RELEASE_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'master': {
|
'master': {
|
||||||
'api': '1.76',
|
'api': '1.77',
|
||||||
'rpc': '1.55',
|
'rpc': '1.55',
|
||||||
'objects': {
|
'objects': {
|
||||||
'Allocation': ['1.1'],
|
'Allocation': ['1.1'],
|
||||||
|
@ -274,6 +274,77 @@ class TestListDrivers(base.BaseApiTest):
|
|||||||
response = self.get_json('/drivers/nope', expect_errors=True)
|
response = self.get_json('/drivers/nope', expect_errors=True)
|
||||||
self.assertEqual(http_client.NOT_FOUND, response.status_int)
|
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):
|
def _test_links(self, public_url=None):
|
||||||
cfg.CONF.set_override('public_endpoint', public_url, 'api')
|
cfg.CONF.set_override('public_endpoint', public_url, 'api')
|
||||||
self.register_fake_conductors()
|
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