summaryrefslogtreecommitdiff
path: root/ironic
diff options
context:
space:
mode:
authorTadeas Kot <tkot@redhat.com>2021-08-12 17:53:16 +0200
committerTadeas Kot <tkot@redhat.com>2021-08-27 11:57:41 +0200
commitee06761b0ec5edb42199c1b96ddcd4900d50002b (patch)
tree215a8ce6cc16947bb9ad9904d0d33bebc46701c0 /ironic
parent183325d4643561a951fb575a7781e92643b41e67 (diff)
downloadironic-ee06761b0ec5edb42199c1b96ddcd4900d50002b.tar.gz
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
Diffstat (limited to 'ironic')
-rw-r--r--ironic/api/controllers/v1/driver.py50
-rw-r--r--ironic/api/controllers/v1/versions.py4
-rw-r--r--ironic/common/release_mappings.py2
-rw-r--r--ironic/tests/unit/api/controllers/v1/test_driver.py71
4 files changed, 116 insertions, 11 deletions
diff --git a/ironic/api/controllers/v1/driver.py b/ironic/api/controllers/v1/driver.py
index 9027e4638..2775ed284 100644
--- a/ironic/api/controllers/v1/driver.py
+++ b/ironic/api/controllers/v1/driver.py
@@ -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)
diff --git a/ironic/api/controllers/v1/versions.py b/ironic/api/controllers/v1/versions.py
index 9cd890e8f..0489b1dbe 100644
--- a/ironic/api/controllers/v1/versions.py
+++ b/ironic/api/controllers/v1/versions.py
@@ -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)
diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py
index ee7634aab..6af1074e7 100644
--- a/ironic/common/release_mappings.py
+++ b/ironic/common/release_mappings.py
@@ -371,7 +371,7 @@ RELEASE_MAPPING = {
}
},
'master': {
- 'api': '1.76',
+ 'api': '1.77',
'rpc': '1.55',
'objects': {
'Allocation': ['1.1'],
diff --git a/ironic/tests/unit/api/controllers/v1/test_driver.py b/ironic/tests/unit/api/controllers/v1/test_driver.py
index b991ac7af..6bf04297f 100644
--- a/ironic/tests/unit/api/controllers/v1/test_driver.py
+++ b/ironic/tests/unit/api/controllers/v1/test_driver.py
@@ -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()