Add Node BIOS support - REST API
Change-Id: Ie7570736498b4750eff2d9262f63ab0960b3b594 Partial-Bug: #1712032 Co-Authored-By: Yolanda Robla <yroblamo@redhat.com>
This commit is contained in:
parent
01ae88db37
commit
254d370331
@ -2,6 +2,17 @@
|
||||
REST API Version History
|
||||
========================
|
||||
|
||||
1.40 (Rocky, master)
|
||||
---------------------
|
||||
|
||||
Added BIOS properties as sub resources of nodes:
|
||||
|
||||
* GET /v1/nodes/<node_ident>/bios
|
||||
* GET /v1/nodes/<node_ident>/bios/<setting_name>
|
||||
|
||||
Added ``bios_interface`` field to the node object to allow getting and
|
||||
setting the interface.
|
||||
|
||||
1.39 (Rocky, master)
|
||||
--------------------
|
||||
|
||||
|
127
ironic/api/controllers/v1/bios.py
Normal file
127
ironic/api/controllers/v1/bios.py
Normal file
@ -0,0 +1,127 @@
|
||||
# Copyright 2018 Red Hat Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ironic_lib import metrics_utils
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
from ironic.api.controllers.v1 import types
|
||||
from ironic.api.controllers.v1 import utils as api_utils
|
||||
from ironic.api import expose
|
||||
from ironic.common import exception
|
||||
from ironic.common import policy
|
||||
from ironic import objects
|
||||
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
|
||||
|
||||
class BIOSSetting(base.APIBase):
|
||||
"""API representation of a BIOS setting."""
|
||||
|
||||
name = wsme.wsattr(wtypes.text)
|
||||
|
||||
value = wsme.wsattr(wtypes.text)
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = []
|
||||
fields = list(objects.BIOSSetting.fields)
|
||||
for k in fields:
|
||||
if hasattr(self, k):
|
||||
self.fields.append(k)
|
||||
value = kwargs.get(k, wtypes.Unset)
|
||||
setattr(self, k, value)
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(bios, node_uuid, url):
|
||||
"""Add links to the bios setting."""
|
||||
name = bios.name
|
||||
bios.links = [link.Link.make_link('self', url, 'nodes',
|
||||
"%s/bios/%s" % (node_uuid, name)),
|
||||
link.Link.make_link('bookmark', url, 'nodes',
|
||||
"%s/bios/%s" % (node_uuid, name),
|
||||
bookmark=True)]
|
||||
return bios
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_bios, node_uuid):
|
||||
"""Add links to the bios setting."""
|
||||
bios = BIOSSetting(**rpc_bios.as_dict())
|
||||
return cls._convert_with_links(bios, node_uuid, pecan.request.host_url)
|
||||
|
||||
|
||||
class BIOSSettingsCollection(wtypes.Base):
|
||||
"""API representation of the bios settings for a node."""
|
||||
|
||||
bios = [BIOSSetting]
|
||||
"""Node bios settings list"""
|
||||
|
||||
@staticmethod
|
||||
def collection_from_list(node_ident, bios_settings):
|
||||
col = BIOSSettingsCollection()
|
||||
|
||||
bios_list = []
|
||||
for bios_setting in bios_settings:
|
||||
bios_list.append(BIOSSetting.convert_with_links(bios_setting,
|
||||
node_ident))
|
||||
col.bios = bios_list
|
||||
return col
|
||||
|
||||
|
||||
class NodeBiosController(rest.RestController):
|
||||
"""REST controller for bios."""
|
||||
|
||||
def __init__(self, node_ident=None):
|
||||
super(NodeBiosController, self).__init__()
|
||||
self.node_ident = node_ident
|
||||
|
||||
@METRICS.timer('NodeBiosController.get_all')
|
||||
@expose.expose(BIOSSettingsCollection)
|
||||
def get_all(self):
|
||||
"""List node bios settings."""
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
policy.authorize('baremetal:node:bios:get', cdict, cdict)
|
||||
|
||||
node = api_utils.get_rpc_node(self.node_ident)
|
||||
settings = objects.BIOSSettingList.get_by_node_id(
|
||||
pecan.request.context, node.id)
|
||||
return BIOSSettingsCollection.collection_from_list(self.node_ident,
|
||||
settings)
|
||||
|
||||
@METRICS.timer('NodeBiosController.get_one')
|
||||
@expose.expose({wtypes.text: BIOSSetting}, types.name)
|
||||
def get_one(self, setting_name):
|
||||
"""Retrieve information about the given bios setting.
|
||||
|
||||
:param setting_name: Logical name of the setting to retrieve.
|
||||
"""
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
policy.authorize('baremetal:node:bios:get', cdict, cdict)
|
||||
|
||||
node = api_utils.get_rpc_node(self.node_ident)
|
||||
try:
|
||||
setting = objects.BIOSSetting.get(pecan.request.context, node.id,
|
||||
setting_name)
|
||||
except exception.BIOSSettingNotFound:
|
||||
raise exception.BIOSSettingNotFound(node=node.uuid,
|
||||
name=setting_name)
|
||||
|
||||
return {setting_name: BIOSSetting.convert_with_links(setting,
|
||||
node.uuid)}
|
@ -79,6 +79,10 @@ def hide_fields_in_newer_versions(obj):
|
||||
obj.default_rescue_interface = wsme.Unset
|
||||
obj.enabled_rescue_interfaces = wsme.Unset
|
||||
|
||||
if not api_utils.allow_bios_interface():
|
||||
obj.default_bios_interface = wsme.Unset
|
||||
obj.enabled_bios_interfaces = wsme.Unset
|
||||
|
||||
|
||||
class Driver(base.APIBase):
|
||||
"""API representation of a driver."""
|
||||
@ -99,6 +103,7 @@ class Driver(base.APIBase):
|
||||
"""A list containing links to driver properties"""
|
||||
|
||||
"""Default interface for a hardware type"""
|
||||
default_bios_interface = wtypes.text
|
||||
default_boot_interface = wtypes.text
|
||||
default_console_interface = wtypes.text
|
||||
default_deploy_interface = wtypes.text
|
||||
@ -112,6 +117,7 @@ class Driver(base.APIBase):
|
||||
default_vendor_interface = wtypes.text
|
||||
|
||||
"""A list of enabled interfaces for a hardware type"""
|
||||
enabled_bios_interfaces = [wtypes.text]
|
||||
enabled_boot_interfaces = [wtypes.text]
|
||||
enabled_console_interfaces = [wtypes.text]
|
||||
enabled_deploy_interfaces = [wtypes.text]
|
||||
|
@ -28,6 +28,7 @@ from wsme import types as wtypes
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
from ironic.api.controllers.v1 import bios
|
||||
from ironic.api.controllers.v1 import collection
|
||||
from ironic.api.controllers.v1 import notification_utils as notify
|
||||
from ironic.api.controllers.v1 import port
|
||||
@ -162,6 +163,9 @@ def hide_fields_in_newer_versions(obj):
|
||||
if not api_utils.allow_rescue_interface():
|
||||
obj.rescue_interface = wsme.Unset
|
||||
|
||||
if not api_utils.allow_bios_interface():
|
||||
obj.bios_interface = wsme.Unset
|
||||
|
||||
|
||||
def update_state_in_older_versions(obj):
|
||||
"""Change provision state names for API backwards compatibility.
|
||||
@ -1040,6 +1044,9 @@ class Node(base.APIBase):
|
||||
traits = wtypes.ArrayType(str)
|
||||
"""The traits associated with this node"""
|
||||
|
||||
bios_interface = wsme.wsattr(wtypes.text)
|
||||
"""The bios interface to be used for this node"""
|
||||
|
||||
# NOTE(deva): "conductor_affinity" shouldn't be presented on the
|
||||
# API because it's an internal value. Don't add it here.
|
||||
|
||||
@ -1194,7 +1201,8 @@ class Node(base.APIBase):
|
||||
deploy_interface=None, inspect_interface=None,
|
||||
management_interface=None, power_interface=None,
|
||||
raid_interface=None, vendor_interface=None,
|
||||
storage_interface=None, traits=[], rescue_interface=None)
|
||||
storage_interface=None, traits=[], rescue_interface=None,
|
||||
bios_interface=None)
|
||||
# NOTE(matty_dubs): The chassis_uuid getter() is based on the
|
||||
# _chassis_uuid variable:
|
||||
sample._chassis_uuid = 'edcad704-b2da-41d5-96d9-afd580ecfa12'
|
||||
@ -1463,6 +1471,7 @@ class NodesController(rest.RestController):
|
||||
'vifs': NodeVIFController,
|
||||
'volume': volume.VolumeController,
|
||||
'traits': NodeTraitsController,
|
||||
'bios': bios.NodeBiosController,
|
||||
}
|
||||
|
||||
@pecan.expose()
|
||||
@ -1476,7 +1485,9 @@ class NodesController(rest.RestController):
|
||||
if ((remainder[0] == 'portgroups'
|
||||
and not api_utils.allow_portgroups_subcontrollers())
|
||||
or (remainder[0] == 'vifs'
|
||||
and not api_utils.allow_vifs_subcontroller())):
|
||||
and not api_utils.allow_vifs_subcontroller())
|
||||
or (remainder[0] == 'bios' and
|
||||
not api_utils.allow_bios_interface())):
|
||||
pecan.abort(http_client.NOT_FOUND)
|
||||
if remainder[0] == 'traits' and not api_utils.allow_traits():
|
||||
# NOTE(mgoddard): Returning here will ensure we exhibit the
|
||||
@ -1827,6 +1838,10 @@ class NodesController(rest.RestController):
|
||||
and node.storage_interface is not wtypes.Unset):
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
if (not api_utils.allow_bios_interface() and
|
||||
node.bios_interface is not wtypes.Unset):
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
if node.traits is not wtypes.Unset:
|
||||
msg = _("Cannot specify node traits on node creation. Traits must "
|
||||
"be set via the node traits API.")
|
||||
@ -1874,19 +1889,8 @@ class NodesController(rest.RestController):
|
||||
chassis_uuid=api_node.chassis_uuid)
|
||||
return api_node
|
||||
|
||||
@METRICS.timer('NodesController.patch')
|
||||
@wsme.validate(types.uuid, [NodePatchType])
|
||||
@expose.expose(Node, types.uuid_or_name, body=[NodePatchType])
|
||||
def patch(self, node_ident, patch):
|
||||
"""Update an existing node.
|
||||
|
||||
:param node_ident: UUID or logical name of a node.
|
||||
:param patch: a json PATCH document to apply to this node.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
cdict = context.to_policy_values()
|
||||
policy.authorize('baremetal:node:update', cdict, cdict)
|
||||
|
||||
# NOTE (yolanda): isolate validation to avoid patch too complex pep error
|
||||
def _validate_patch(self, patch):
|
||||
if self.from_chassis:
|
||||
raise exception.OperationNotPermitted()
|
||||
|
||||
@ -1917,6 +1921,25 @@ class NodesController(rest.RestController):
|
||||
if r_interface and not api_utils.allow_rescue_interface():
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
b_interface = api_utils.get_patch_values(patch, '/bios_interface')
|
||||
if b_interface and not api_utils.allow_bios_interface():
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
@METRICS.timer('NodesController.patch')
|
||||
@wsme.validate(types.uuid, [NodePatchType])
|
||||
@expose.expose(Node, types.uuid_or_name, body=[NodePatchType])
|
||||
def patch(self, node_ident, patch):
|
||||
"""Update an existing node.
|
||||
|
||||
:param node_ident: UUID or logical name of a node.
|
||||
:param patch: a json PATCH document to apply to this node.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
cdict = context.to_policy_values()
|
||||
policy.authorize('baremetal:node:update', cdict, cdict)
|
||||
|
||||
self._validate_patch(patch)
|
||||
|
||||
rpc_node = api_utils.get_rpc_node_with_suffix(node_ident)
|
||||
|
||||
remove_inst_uuid_patch = [{'op': 'remove', 'path': '/instance_uuid'}]
|
||||
|
@ -362,6 +362,8 @@ def check_allowed_fields(fields):
|
||||
"""
|
||||
if fields is None:
|
||||
return
|
||||
if 'bios_interface' in fields and not allow_bios_interface():
|
||||
raise exception.NotAcceptable()
|
||||
if 'network_interface' in fields and not allow_network_interface():
|
||||
raise exception.NotAcceptable()
|
||||
if 'resource_class' in fields and not allow_resource_class():
|
||||
@ -693,6 +695,14 @@ def allow_rescue_interface():
|
||||
return pecan.request.version.minor >= versions.MINOR_38_RESCUE_INTERFACE
|
||||
|
||||
|
||||
def allow_bios_interface():
|
||||
"""Check if we should support bios interface.
|
||||
|
||||
Version 1.40 of the API added support for bios interface.
|
||||
"""
|
||||
return pecan.request.version.minor >= versions.MINOR_40_BIOS_INTERFACE
|
||||
|
||||
|
||||
def get_controller_reserved_names(cls):
|
||||
"""Get reserved names for a given controller.
|
||||
|
||||
|
@ -76,6 +76,8 @@ BASE_VERSION = 1
|
||||
# v1.37: Add node traits.
|
||||
# v1.38: Add rescue and unrescue provision states
|
||||
# v1.39: Add inspect wait provision state.
|
||||
# v1.40: Add bios.properties.
|
||||
# Add bios_interface to the node object.
|
||||
|
||||
MINOR_0_JUNO = 0
|
||||
MINOR_1_INITIAL_VERSION = 1
|
||||
@ -117,6 +119,7 @@ MINOR_36_AGENT_VERSION_HEARTBEAT = 36
|
||||
MINOR_37_NODE_TRAITS = 37
|
||||
MINOR_38_RESCUE_INTERFACE = 38
|
||||
MINOR_39_INSPECT_WAIT = 39
|
||||
MINOR_40_BIOS_INTERFACE = 40
|
||||
|
||||
# When adding another version, update:
|
||||
# - MINOR_MAX_VERSION
|
||||
@ -124,7 +127,7 @@ MINOR_39_INSPECT_WAIT = 39
|
||||
# explanation of what changed in the new version
|
||||
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
||||
|
||||
MINOR_MAX_VERSION = MINOR_39_INSPECT_WAIT
|
||||
MINOR_MAX_VERSION = MINOR_40_BIOS_INTERFACE
|
||||
|
||||
# String representations of the minor and maximum versions
|
||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||
|
@ -199,6 +199,13 @@ node_policies = [
|
||||
[{'path': '/nodes/{node_ident}/traits', 'method': 'DELETE'},
|
||||
{'path': '/nodes/{node_ident}/traits/{trait}',
|
||||
'method': 'DELETE'}]),
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
'baremetal:node:bios:get',
|
||||
'rule:is_admin or rule:is_observer',
|
||||
'Retrieve Node BIOS information',
|
||||
[{'path': '/nodes/{node_ident}/bios', 'method': 'GET'},
|
||||
{'path': '/nodes/{node_ident}/bios/{setting}', 'method': 'GET'}])
|
||||
]
|
||||
|
||||
port_policies = [
|
||||
|
@ -100,7 +100,7 @@ RELEASE_MAPPING = {
|
||||
}
|
||||
},
|
||||
'master': {
|
||||
'api': '1.39',
|
||||
'api': '1.40',
|
||||
'rpc': '1.44',
|
||||
'objects': {
|
||||
'Node': ['1.24'],
|
||||
|
@ -121,6 +121,7 @@ class TestListNodes(test_api_base.BaseApiTest):
|
||||
self.assertNotIn('traits', data['nodes'][0])
|
||||
# never expose the chassis_id
|
||||
self.assertNotIn('chassis_id', data['nodes'][0])
|
||||
self.assertNotIn('bios_interface', data['nodes'][0])
|
||||
|
||||
def test_get_one(self):
|
||||
node = obj_utils.create_test_node(self.context,
|
||||
@ -156,6 +157,7 @@ class TestListNodes(test_api_base.BaseApiTest):
|
||||
self.assertIn('traits', data)
|
||||
# never expose the chassis_id
|
||||
self.assertNotIn('chassis_id', data)
|
||||
self.assertIn('bios_interface', data)
|
||||
|
||||
def test_get_one_with_json(self):
|
||||
# Test backward compatibility with guess_content_type_from_ext
|
||||
@ -227,6 +229,13 @@ class TestListNodes(test_api_base.BaseApiTest):
|
||||
headers={api_base.Version.string: '1.36'})
|
||||
self.assertNotIn('traits', data)
|
||||
|
||||
def test_node_bios_hidden_in_lower_version(self):
|
||||
node = obj_utils.create_test_node(self.context)
|
||||
data = self.get_json(
|
||||
'/nodes/%s' % node.uuid,
|
||||
headers={api_base.Version.string: '1.39'})
|
||||
self.assertNotIn('bios_interface', data)
|
||||
|
||||
def test_node_inspect_wait_state_between_api_versions(self):
|
||||
node = obj_utils.create_test_node(self.context,
|
||||
provision_state='inspect wait')
|
||||
@ -2345,10 +2354,11 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
self.assertEqual('neutron', result['network_interface'])
|
||||
|
||||
def test_create_node_specify_interfaces(self):
|
||||
headers = {api_base.Version.string: '1.38'}
|
||||
headers = {api_base.Version.string: '1.40'}
|
||||
all_interface_fields = api_utils.V31_FIELDS + ['network_interface',
|
||||
'rescue_interface',
|
||||
'storage_interface']
|
||||
'storage_interface',
|
||||
'bios_interface']
|
||||
for field in all_interface_fields:
|
||||
if field == 'network_interface':
|
||||
cfg.CONF.set_override('enabled_%ss' % field, ['flat'])
|
||||
@ -2841,6 +2851,14 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
|
||||
def test_create_node_invalid_bios_interface(self):
|
||||
ndict = test_api_utils.post_get_test_node(bios_interface='foo')
|
||||
response = self.post_json('/nodes', ndict, expect_errors=True,
|
||||
headers={api_base.Version.string:
|
||||
str(api_v1.max_version())})
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
|
||||
|
||||
class TestDelete(test_api_base.BaseApiTest):
|
||||
|
||||
@ -4404,6 +4422,68 @@ class TestAttachDetachVif(test_api_base.BaseApiTest):
|
||||
self.assertTrue(ret.json['error_message'])
|
||||
|
||||
|
||||
class TestBIOS(test_api_base.BaseApiTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBIOS, self).setUp()
|
||||
self.version = "1.40"
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, id=1)
|
||||
self.bios = obj_utils.create_test_bios_setting(self.context,
|
||||
node_id=self.node.id)
|
||||
|
||||
def test_get_all_bios(self):
|
||||
ret = self.get_json('/nodes/%s/bios' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version})
|
||||
|
||||
expected_json = [
|
||||
{u'created_at': ret['bios'][0]['created_at'],
|
||||
u'updated_at': ret['bios'][0]['updated_at'],
|
||||
u'links': [
|
||||
{u'href': u'http://localhost/v1/nodes/' + self.node.uuid +
|
||||
'/bios/virtualization', u'rel': u'self'},
|
||||
{u'href': u'http://localhost/nodes/' + self.node.uuid +
|
||||
'/bios/virtualization', u'rel': u'bookmark'}], u'name':
|
||||
u'virtualization', u'value': u'on'}]
|
||||
self.assertEqual({u'bios': expected_json}, ret)
|
||||
|
||||
def test_get_all_bios_fails_with_bad_version(self):
|
||||
ret = self.get_json('/nodes/%s/bios' % self.node.uuid,
|
||||
headers={api_base.Version.string: "1.39"},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
|
||||
|
||||
def test_get_one_bios(self):
|
||||
ret = self.get_json('/nodes/%s/bios/virtualization' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version})
|
||||
|
||||
expected_json = {
|
||||
u'virtualization': {
|
||||
u'created_at': ret['virtualization']['created_at'],
|
||||
u'updated_at': ret['virtualization']['updated_at'],
|
||||
u'links': [
|
||||
{u'href': u'http://localhost/v1/nodes/' + self.node.uuid +
|
||||
'/bios/virtualization', u'rel': u'self'},
|
||||
{u'href': u'http://localhost/nodes/' + self.node.uuid +
|
||||
'/bios/virtualization', u'rel': u'bookmark'}],
|
||||
u'name': u'virtualization', u'value': u'on'}}
|
||||
self.assertEqual(expected_json, ret)
|
||||
|
||||
def test_get_one_bios_fails_with_bad_version(self):
|
||||
ret = self.get_json('/nodes/%s/bios/virtualization' % self.node.uuid,
|
||||
headers={api_base.Version.string: "1.39"},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
|
||||
|
||||
def test_get_one_bios_fails_if_not_found(self):
|
||||
ret = self.get_json('/nodes/%s/bios/fake_setting' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
|
||||
self.assertIn("fake_setting", ret.json['error_message'])
|
||||
self.assertNotIn(self.node.id, ret.json['error_message'])
|
||||
|
||||
|
||||
class TestTraits(test_api_base.BaseApiTest):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -230,6 +230,31 @@ def create_test_volume_target(ctxt, **kw):
|
||||
return volume_target
|
||||
|
||||
|
||||
def get_test_bios_setting(ctxt, **kw):
|
||||
"""Return a BiosSettingList object with appropriate attributes.
|
||||
|
||||
NOTE: The object leaves the attributes marked as changed, such
|
||||
that a create() could be used to commit it to the DB.
|
||||
"""
|
||||
kw['object_type'] = 'bios'
|
||||
db_bios_setting = db_utils.get_test_bios_setting(**kw)
|
||||
bios_setting = objects.BIOSSetting(ctxt)
|
||||
for key in db_bios_setting:
|
||||
setattr(bios_setting, key, db_bios_setting[key])
|
||||
return bios_setting
|
||||
|
||||
|
||||
def create_test_bios_setting(ctxt, **kw):
|
||||
"""Create and return a test bios setting list object.
|
||||
|
||||
Create a BIOS setting list in the DB and return a BIOSSettingList
|
||||
object with appropriate attributes.
|
||||
"""
|
||||
bios_setting = get_test_bios_setting(ctxt, **kw)
|
||||
bios_setting.create()
|
||||
return bios_setting
|
||||
|
||||
|
||||
def get_payloads_with_schemas(from_module):
|
||||
"""Get the Payload classes with SCHEMAs defined.
|
||||
|
||||
|
6
releasenotes/notes/add-node-bios-9c1c3d442e8acdac.yaml
Normal file
6
releasenotes/notes/add-node-bios-9c1c3d442e8acdac.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Adds support for reading and changing the node's ``bios_interface`` field
|
||||
and enables the GET endpoints to check BIOS settings, if they have already
|
||||
been cached. This requires a compatible ``bios_interface`` to be set.
|
||||
This feature is available starting with API version 1.40.
|
Loading…
x
Reference in New Issue
Block a user