Include bios registry fields in bios API

Provide the fields in the BIOS setting API -
``/v1/nodes/{node}/bios/{setting}``, and in the BIOS setting list API
when details are requested - ``/v1/nodes/<node>/bios?detail=True``.

Story: #2008571
Task: #42483
Change-Id: Ie86ec57e428e2bb2efd099a839105e51a94824ab
This commit is contained in:
Bob Fournier 2021-04-16 15:15:53 -04:00
parent caa4c8fd29
commit e15440370c
24 changed files with 614 additions and 48 deletions

View File

@ -19,6 +19,53 @@ List all Bios settings by Node
Return a list of Bios settings associated with ``node_ident``.
.. versionadded:: 1.74
Added additional fields from bios registry which can be retrieved using
``?detail=True`` (see detailed response below).
Added ``fields`` selector to query for particular fields.
Normal response code: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- node_ident: node_ident
- fields: fields
- detail: detail
Response
--------
.. rest_parameters:: parameters.yaml
- bios: bios_settings
- created_at: created_at
- updated_at: updated_at
- links: links
- name: bios_setting_name
- value: bios_setting_value
**Example list of a Node's Bios settings:**
.. literalinclude:: samples/node-bios-list-response.json
List detailed Bios settings by Node
===================================
.. rest_method:: GET /v1/nodes/{node_ident}/bios/?detail=True
Return a list of detailed Bios settings associated with ``node_ident``.
The detailed list includes the BIOS Attribute Registry information
retrieved via Redfish.
.. versionadded:: 1.74
Introduced
Normal response code: 200
Error codes: 404
@ -41,10 +88,19 @@ Response
- links: links
- name: bios_setting_name
- value: bios_setting_value
- attribute_type: bios_setting_attribute_type
- allowable_values: bios_setting_allowable_values
- lower_bound: bios_setting_lower_bound
- max_length: bios_setting_max_length
- min_length: bios_setting_min_length
- read_only: bios_setting_read_only
- reset_required: bios_setting_reset_required
- unique: bios_setting_unique
- upper_bound: bios_setting_upper_bound
**Example list of a Node's Bios settings:**
.. literalinclude:: samples/node-bios-list-response.json
.. literalinclude:: samples/node-bios-list-details-response.json
Show single Bios setting of a Node
@ -55,6 +111,9 @@ Show single Bios setting of a Node
Return the content of the specific bios ``bios_setting`` associated with
``node_ident``.
. versionadded:: 1.74
Introduced fields from the BIOS registry.
Normal response code: 200
Error codes: 404
@ -78,6 +137,15 @@ Response
- links: links
- name: bios_setting_name
- value: bios_setting_value
- attribute_type: bios_setting_attribute_type
- allowable_values: bios_setting_allowable_values
- lower_bound: bios_setting_lower_bound
- max_length: bios_setting_max_length
- min_length: bios_setting_min_length
- read_only: bios_setting_read_only
- reset_required: bios_setting_reset_required
- unique: bios_setting_unique
- upper_bound: bios_setting_upper_bound
**Example details of a Node's Bios setting details:**

View File

@ -505,12 +505,75 @@ bios_interface:
in: body
required: true
type: string
bios_setting_allowable_values:
description: |
A list of allowable values when the attribute_type is "Enumeration",
otherwise None.
in: body
required: true
type: array
bios_setting_attribute_type:
description: |
A string describing the type of the Bios setting - "Enumeration",
"Integer", "String", "Boolean", or "Password". May be None.
in: body
required: true
type: string
bios_setting_lower_bound:
description: |
The lowest allowed value when attribute_type is "Integer".
May be None.
in: body
required: true
type: integer
bios_setting_max_length:
description: |
The maximum length when attribute_type is "String".
May be None.
in: body
required: true
type: integer
bios_setting_min_length:
description: |
The minimum length when attribute_type is "String".
May be None.
in: body
required: true
type: integer
bios_setting_name:
description: |
The name of a Bios setting for a Node, eg. "virtualization".
in: body
required: true
type: string
bios_setting_read_only:
description: |
This Bios seting is read only and can't be changed.
May be None.
in: body
required: true
type: boolean
bios_setting_reset_required:
description: |
After setting this Bios setting a node reboot is required.
May be None.
in: body
required: true
type: boolean
bios_setting_unique:
description: |
This Bios setting is unique to this node.
May be None.
in: body
required: true
type: boolean
bios_setting_upper_bound:
description: |
The lowest allowed value when attribute_type is "Integer".
May be None.
in: body
required: true
type: integer
bios_setting_value:
description: |
The value of a Bios setting for a Node, eg. "on".
@ -520,7 +583,9 @@ bios_setting_value:
bios_settings:
description: |
Optional list of one or more Bios settings. It includes following fields
"created_at", "updated_at", "links", "name", "value".
"created_at", "updated_at", "links", "name", "value", "attribute_type",
"allowable_values", "lower_bound", "max_length", "min_length", "read_only",
"reset_required", "unique", "upper_bound"
in: body
required: true
type: array

View File

@ -12,7 +12,16 @@
"rel": "bookmark"
}
],
"name": "virtualization",
"value": "on"
"name": "Virtualization",
"value": "Enabled",
"attribute_type": "Enumeration",
"allowable_values": ["Enabled", "Disabled"],
"lower_bound": None,
"max_length": None,
"min_length": None,
"read_only": false,
"reset_required": None,
"unique": None,
"upper_bound": None
}
}

View File

@ -0,0 +1,30 @@
{
"bios": [
{
"created_at": "2016-08-18T22:28:49.653974+00:00",
"updated_at": "2016-08-18T22:28:49.653974+00:00",
"links": [
{
"href": "http://127.0.0.1:6385/v1/nodes/6d85703a-565d-469a-96ce-30b6de53079d/bios/virtualization",
"rel": "self"
},
{
"href": "http://127.0.0.1:6385/v1/nodes/6d85703a-565d-469a-96ce-30b6de53079d/bios/virtualization",
"rel": "bookmark"
}
],
"name": "Virtualization",
"value": "Enabled",
"attribute_type": "Enumeration",
"allowable_values": ["Enabled", "Disabled"],
"lower_bound": None,
"max_length": None,
"min_length": None,
"read_only": false,
"reset_required": None,
"unique": None,
"upper_bound": None
}
]
}

View File

@ -13,8 +13,8 @@
"rel": "bookmark"
}
],
"name": "virtualization",
"value": "on"
"name": "Virtualization",
"value": "Enabled"
}
]
}

View File

@ -2,8 +2,19 @@
REST API Version History
========================
1.74 (Xena)
----------------------
Add support for BIOS registry fields which include details about the BIOS
setting. Included in the ``/v1/nodes/{node_ident}/bios/{setting}`` response.
Add a new selector to include the fields in the BIOS settings list:
* ``/v1/nodes/{node_ident}/bios/?detail=``
Also add a fields selector to the the BIOS settings list:
* ``/v1/nodes/{node_ident}/bios/?fields=``
1.73 (Xena)
----------------------
Add a new ``deploy`` verb as an alias to ``active`` and
``undeploy`` verb as an alias to ``deleted``.

View File

@ -25,23 +25,34 @@ from ironic import objects
METRICS = metrics_utils.get_metrics_logger(__name__)
_DEFAULT_RETURN_FIELDS = ('name', 'value')
_DEFAULT_FIELDS_WITH_REGISTRY = ('name', 'value', 'attribute_type',
'allowable_values', 'lower_bound',
'max_length', 'min_length', 'read_only',
'reset_required', 'unique', 'upper_bound')
def convert_with_links(rpc_bios, node_uuid):
def convert_with_links(rpc_bios, node_uuid, detail=None, fields=None):
"""Build a dict containing a bios setting value."""
if detail:
fields = _DEFAULT_FIELDS_WITH_REGISTRY
bios = api_utils.object_to_dict(
rpc_bios,
include_uuid=False,
fields=('name', 'value'),
fields=fields,
link_resource='nodes',
link_resource_args="%s/bios/%s" % (node_uuid, rpc_bios.name),
)
return bios
def collection_from_list(node_ident, bios_settings):
def collection_from_list(node_ident, bios_settings, detail=None, fields=None):
bios_list = []
for bios_setting in bios_settings:
bios_list.append(convert_with_links(bios_setting, node_ident))
bios_list.append(convert_with_links(bios_setting, node_ident,
detail, fields))
return {'bios': bios_list}
@ -54,14 +65,23 @@ class NodeBiosController(rest.RestController):
@METRICS.timer('NodeBiosController.get_all')
@method.expose()
def get_all(self):
@args.validate(fields=args.string_list, detail=args.boolean)
def get_all(self, detail=None, fields=None):
"""List node bios settings."""
node = api_utils.check_node_policy_and_retrieve(
'baremetal:node:bios:get', self.node_ident)
# The BIOS detail and fields query were added in a later
# version, check if they are valid based on version
allow_query = api_utils.allow_query_bios
fields = api_utils.get_request_return_fields(fields, detail,
_DEFAULT_RETURN_FIELDS,
allow_query, allow_query)
settings = objects.BIOSSettingList.get_by_node_id(
api.request.context, node.id)
return collection_from_list(self.node_ident, settings)
return collection_from_list(self.node_ident, settings,
detail, fields)
@METRICS.timer('NodeBiosController.get_one')
@method.expose()
@ -81,4 +101,11 @@ class NodeBiosController(rest.RestController):
raise exception.BIOSSettingNotFound(node=node.uuid,
name=setting_name)
return {setting_name: convert_with_links(setting, node.uuid)}
# Return fields based on version
if api_utils.allow_query_bios():
fields = _DEFAULT_FIELDS_WITH_REGISTRY
else:
fields = _DEFAULT_RETURN_FIELDS
return {setting_name: convert_with_links(setting, node.uuid,
fields=fields)}

View File

@ -1321,18 +1321,27 @@ def allow_detail_query():
return api.request.version.minor >= versions.MINOR_43_ENABLE_DETAIL_QUERY
def allow_query_bios():
"""Check if BIOS queries should be allowed based on version"""
return api.request.version.minor >= versions.MINOR_74_BIOS_REGISTRY
def allow_reset_interfaces():
"""Check if passing a reset_interfaces query string is allowed."""
return api.request.version.minor >= versions.MINOR_45_RESET_INTERFACES
def get_request_return_fields(fields, detail, default_fields):
def get_request_return_fields(fields, detail, default_fields,
check_detail_version=allow_detail_query,
check_fields_version=None):
"""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.
API greater than expected, likewise some APIs require a certain version for
the fields query. This function raises an InvalidParameterValue exception
if any 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.
@ -1341,15 +1350,24 @@ def get_request_return_fields(fields, detail, default_fields):
:param detail: The detail query passed into the API request.
:param default_fields: The default fields to return if fields=None and
detail=None.
:param check_detail_version: Function to check if detail query is allowed
based on the version.
:param check_fields_version: Function to check if fields query is allowed
based on the version.
: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():
if detail is not None and not check_detail_version():
raise exception.InvalidParameterValue(
"Invalid query parameter ?detail=%s received." % detail)
if (fields is not None and callable(check_fields_version)
and not check_fields_version()):
raise exception.InvalidParameterValue(
"Invalid query parameter ?fields=%s received." % fields)
if fields is not None and detail:
raise exception.InvalidParameterValue(
"Can not specify ?detail=True and fields in the same request.")

View File

@ -111,6 +111,7 @@ BASE_VERSION = 1
# v1.71: Add signifier for Scope based roles.
# v1.72: Add agent_status and agent_status_message to /v1/heartbeat
# v1.73: Add support for deploy and undeploy verbs
# v1.74: Add bios registry to /v1/nodes/{node}/bios/{setting}
MINOR_0_JUNO = 0
MINOR_1_INITIAL_VERSION = 1
@ -186,6 +187,7 @@ MINOR_70_CLEAN_DISABLE_RAMDISK = 70
MINOR_71_RBAC_SCOPES = 71
MINOR_72_HEARTBEAT_STATUS = 72
MINOR_73_DEPLOY_UNDEPLOY_VERBS = 73
MINOR_74_BIOS_REGISTRY = 74
# When adding another version, update:
# - MINOR_MAX_VERSION
@ -193,7 +195,7 @@ MINOR_73_DEPLOY_UNDEPLOY_VERBS = 73
# explanation of what changed in the new version
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
MINOR_MAX_VERSION = MINOR_73_DEPLOY_UNDEPLOY_VERBS
MINOR_MAX_VERSION = MINOR_74_BIOS_REGISTRY
# String representations of the minor and maximum versions
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)

View File

@ -320,10 +320,11 @@ RELEASE_MAPPING = {
}
},
'master': {
'api': '1.73',
'api': '1.74',
'rpc': '1.54',
'objects': {
'Allocation': ['1.1'],
'BIOSSetting': ['1.1'],
'Node': ['1.35'],
'Conductor': ['1.3'],
'Chassis': ['1.3'],

View File

@ -1385,8 +1385,7 @@ def store_agent_certificate(node, agent_verify_ca):
def node_cache_bios_settings(task, node):
"""Do caching of bios settings if supported by driver"""
try:
LOG.debug('BF getting BIOS info for node %s',
node.uuid)
LOG.debug('Getting BIOS info for node %s', node.uuid)
task.driver.bios.cache_bios_settings(task)
except exception.UnsupportedDriverExtension:
LOG.warning('BIOS settings are not supported for node %s, '

View File

@ -1054,10 +1054,12 @@ class Connection(object, metaclass=abc.ABCMeta):
{
'name': String,
'value': String,
additional settings from BIOS registry
},
{
'name': String,
'value': String,
additional settings from BIOS registry
},
...
]
@ -1081,10 +1083,12 @@ class Connection(object, metaclass=abc.ABCMeta):
{
'name': String,
'value': String,
additional settings from BIOS registry
},
{
'name': String,
'value': String,
additional settings from BIOS registry
},
...
]

View File

@ -0,0 +1,46 @@
# 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.
"""Add fields from BIOS registry
Revision ID: 2bbd96b6ccb9
Revises: ac00b586ab95
Create Date: 2021-04-29 08:52:23.938863
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '2bbd96b6ccb9'
down_revision = 'ac00b586ab95'
def upgrade():
op.add_column('bios_settings', sa.Column('attribute_type',
sa.String(length=255), nullable=True))
op.add_column('bios_settings', sa.Column('allowable_values',
sa.Text(), nullable=True))
op.add_column('bios_settings', sa.Column('lower_bound',
sa.Integer(), nullable=True))
op.add_column('bios_settings', sa.Column('max_length',
sa.Integer(), nullable=True))
op.add_column('bios_settings', sa.Column('min_length',
sa.Integer(), nullable=True))
op.add_column('bios_settings', sa.Column('read_only',
sa.Boolean(), nullable=True))
op.add_column('bios_settings', sa.Column('reset_required',
sa.Boolean(), nullable=True))
op.add_column('bios_settings', sa.Column('unique',
sa.Boolean(), nullable=True))
op.add_column('bios_settings', sa.Column('upper_bound',
sa.Integer(), nullable=True))

View File

@ -1675,6 +1675,15 @@ class Connection(api.Connection):
node_id=node_id,
name=setting['name'],
value=setting['value'],
attribute_type=setting.get('attribute_type'),
allowable_values=setting.get('allowable_values'),
lower_bound=setting.get('lower_bound'),
max_length=setting.get('max_length'),
min_length=setting.get('min_length'),
read_only=setting.get('read_only'),
reset_required=setting.get('reset_required'),
unique=setting.get('unique'),
upper_bound=setting.get('upper_bound'),
version=version)
bios_settings.append(bios_setting)
session.add(bios_setting)
@ -1695,6 +1704,18 @@ class Connection(api.Connection):
node_id=node_id, name=setting['name'])
ref = query.one()
ref.update({'value': setting['value'],
'attribute_type':
setting.get('attribute_type'),
'allowable_values':
setting.get('allowable_values'),
'lower_bound': setting.get('lower_bound'),
'max_length': setting.get('max_length'),
'min_length': setting.get('min_length'),
'read_only': setting.get('read_only'),
'reset_required':
setting.get('reset_required'),
'unique': setting.get('unique'),
'upper_bound': setting.get('upper_bound'),
'version': version})
bios_settings.append(ref)
session.flush()

View File

@ -339,6 +339,15 @@ class BIOSSetting(Base):
primary_key=True, nullable=False)
name = Column(String(255), primary_key=True, nullable=False)
value = Column(Text, nullable=True)
attribute_type = Column(String(255), nullable=True)
allowable_values = Column(db_types.JsonEncodedList, nullable=True)
lower_bound = Column(Integer, nullable=True)
max_length = Column(Integer, nullable=True)
min_length = Column(Integer, nullable=True)
read_only = Column(Boolean, nullable=True)
reset_required = Column(Boolean, nullable=True)
unique = Column(Boolean, nullable=True)
upper_bound = Column(Integer, nullable=True)
class Allocation(Base):

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import versionutils
from oslo_versionedobjects import base as object_base
from ironic.db import api as dbapi
@ -23,14 +24,29 @@ from ironic.objects import fields as object_fields
@base.IronicObjectRegistry.register
class BIOSSetting(base.IronicObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added registry
VERSION = '1.1'
dbapi = dbapi.get_instance()
registry_fields = ('attribute_type', 'allowable_values', 'lower_bound',
'max_length', 'min_length', 'read_only',
'reset_required', 'unique', 'upper_bound')
fields = {
'node_id': object_fields.StringField(nullable=False),
'name': object_fields.StringField(nullable=False),
'value': object_fields.StringField(nullable=True),
'attribute_type': object_fields.StringField(nullable=True),
'allowable_values': object_fields.ListOfStringsField(
nullable=True),
'lower_bound': object_fields.IntegerField(nullable=True),
'max_length': object_fields.IntegerField(nullable=True),
'min_length': object_fields.IntegerField(nullable=True),
'read_only': object_fields.BooleanField(nullable=True),
'reset_required': object_fields.BooleanField(nullable=True),
'unique': object_fields.BooleanField(nullable=True),
'upper_bound': object_fields.IntegerField(nullable=True)
}
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
@ -50,9 +66,12 @@ class BIOSSetting(base.IronicObject):
:raises: BIOSSettingAlreadyExists if the setting record already exists.
"""
values = self.do_version_changes_for_db()
setting = [{'name': values['name'], 'value': values['value']}]
settings = {'name': values['name'], 'value': values['value']}
for r in self.registry_fields:
settings[r] = values.get(r)
db_bios_setting = self.dbapi.create_bios_setting_list(
values['node_id'], setting, values['version'])
values['node_id'], [settings], values['version'])
self._from_db_object(self._context, self, db_bios_setting[0])
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
@ -72,9 +91,13 @@ class BIOSSetting(base.IronicObject):
:raises: BIOSSettingNotFound if the bios setting name is not found.
"""
values = self.do_version_changes_for_db()
setting = [{'name': values['name'], 'value': values['value']}]
settings = {'name': values['name'], 'value': values['value']}
for r in self.registry_fields:
settings[r] = values.get(r)
updated_bios_setting = self.dbapi.update_bios_setting_list(
values['node_id'], setting, values['version'])
values['node_id'], [settings], values['version'])
self._from_db_object(self._context, self, updated_bios_setting[0])
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
@ -111,6 +134,40 @@ class BIOSSetting(base.IronicObject):
"""
cls.dbapi.delete_bios_setting_list(node_id, [name])
def _convert_to_version(self, target_version,
remove_unavailable_fields=True):
"""Convert to the target version.
Convert the object to the target version. The target version may be
the same, older, or newer than the version of the object. This is
used for DB interactions as well as for serialization/deserialization.
Version 1.74: remove registry field for unsupported versions if
remove_unavailable_fields is True.
:param target_version: the desired version of the object
:param remove_unavailable_fields: True to remove fields that are
unavailable in the target version; set this to True when
(de)serializing. False to set the unavailable fields to appropriate
values; set this to False for DB interactions.
"""
target_version = versionutils.convert_version_to_tuple(target_version)
for field in self.get_registry_fields():
field_is_set = self.obj_attr_is_set(field)
if target_version >= (1, 74):
# target version supports the major/minor specified
if not field_is_set:
# set it to its default value if it is not set
setattr(self, field, None)
elif field_is_set:
# target version does not support the field, and it is set
if remove_unavailable_fields:
# (De)serialising: remove unavailable fields
delattr(self, field)
elif self.registry:
setattr(self, field, None)
@base.IronicObjectRegistry.register
class BIOSSettingList(base.IronicObjectListBase, base.IronicObject):

View File

@ -6685,7 +6685,7 @@ class TestBIOS(test_api_base.BaseApiTest):
def setUp(self):
super(TestBIOS, self).setUp()
self.version = "1.40"
self.version = "1.74"
self.node = obj_utils.create_test_node(
self.context, id=1)
self.bios = obj_utils.create_test_bios_setting(self.context,
@ -6718,13 +6718,40 @@ class TestBIOS(test_api_base.BaseApiTest):
expected_json = {
'virtualization': {
'allowable_values': ['on', 'off'],
'attribute_type': 'Enumeration',
'created_at': ret['virtualization']['created_at'],
'updated_at': ret['virtualization']['updated_at'],
'links': [
{'href': 'http://localhost/v1/nodes/%s/bios/virtualization'
% self.node.uuid, u'rel': u'self'},
{'href': 'http://localhost/nodes/%s/bios/virtualization'
% self.node.uuid, u'rel': u'bookmark'}],
'lower_bound': None,
'min_length': None,
'max_length': None,
'name': 'virtualization',
'read_only': False,
'reset_required': True,
'unique': False,
'updated_at': None,
'upper_bound': None,
'value': 'on'}}
self.assertEqual(expected_json, ret)
def test_get_one_bios_no_registry(self):
ret = self.get_json('/nodes/%s/bios/virtualization' % self.node.uuid,
headers={api_base.Version.string: "1.73"})
expected_json = {
'virtualization': {
'created_at': ret['virtualization']['created_at'],
'updated_at': ret['virtualization']['updated_at'],
'links': [
{'href': 'http://localhost/v1/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'self'},
{'href': 'http://localhost/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'bookmark'}],
'name': 'virtualization', 'value': 'on'}}
self.assertEqual(expected_json, ret)
@ -6742,6 +6769,88 @@ class TestBIOS(test_api_base.BaseApiTest):
self.assertIn("fake_setting", ret.json['error_message'])
self.assertNotIn(self.node.id, ret.json['error_message'])
def test_get_all_bios_with_detail(self):
ret = self.get_json('/nodes/%s/bios?detail=True' % self.node.uuid,
headers={api_base.Version.string: self.version})
expected_json = [
{'allowable_values': ['on', 'off'],
'attribute_type': 'Enumeration',
'created_at': ret['bios'][0]['created_at'],
'links': [
{'href': 'http://localhost/v1/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'self'},
{'href': 'http://localhost/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'bookmark'}],
'lower_bound': None,
'max_length': None,
'min_length': None,
'name': 'virtualization',
'read_only': False,
'reset_required': True,
'unique': False,
'updated_at': None,
'upper_bound': None,
'value': 'on'}]
self.assertEqual({'bios': expected_json}, ret)
def test_get_all_bios_detail_false(self):
ret = self.get_json('/nodes/%s/bios?detail=False' % self.node.uuid,
headers={api_base.Version.string: self.version})
expected_json = [
{'created_at': ret['bios'][0]['created_at'],
'updated_at': ret['bios'][0]['updated_at'],
'links': [
{'href': 'http://localhost/v1/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'self'},
{'href': 'http://localhost/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'bookmark'}],
'name': 'virtualization', 'value': 'on'}]
self.assertEqual({'bios': expected_json}, ret)
def test_get_all_bios_detail_old_version(self):
ret = self.get_json('/nodes/%s/bios?detail=True' % self.node.uuid,
headers={api_base.Version.string: "1.73"},
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, ret.status_int)
def test_get_bios_fields_old_version(self):
ret = self.get_json('/nodes/%s/bios?fields=name,read_only'
% self.node.uuid,
headers={api_base.Version.string: "1.73"},
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, ret.status_int)
def test_get_bios_detail_and_fields(self):
ret = self.get_json('/nodes/%s/bios?detail=True?fields=name,read_only'
% self.node.uuid,
headers={api_base.Version.string: "1.74"},
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, ret.status_int)
def test_get_bios_fields(self):
ret = self.get_json('/nodes/%s/bios?fields=name,read_only'
% self.node.uuid,
headers={api_base.Version.string: self.version})
expected_json = [
{'created_at': ret['bios'][0]['created_at'],
'links': [
{'href': 'http://localhost/v1/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'self'},
{'href': 'http://localhost/nodes/%s/bios/virtualization'
% self.node.uuid, 'rel': 'bookmark'}],
'name': 'virtualization',
'read_only': False,
'updated_at': None}]
self.assertEqual({'bios': expected_json}, ret)
class TestTraits(test_api_base.BaseApiTest):

View File

@ -85,7 +85,7 @@ class ReleaseMappingsTestCase(base.TestCase):
self.assertIn('master', release_mappings.RELEASE_MAPPING)
model_names = set((s.__name__ for s in models.Base.__subclasses__()))
exceptions = set(['NodeTag', 'ConductorHardwareInterfaces',
'NodeTrait', 'BIOSSetting', 'DeployTemplateStep'])
'NodeTrait', 'DeployTemplateStep'])
# NOTE(xek): As a rule, all models which can be changed between
# releases or are sent through RPC should have their counterpart
# versioned objects.

View File

@ -705,6 +705,40 @@ class MigrationCheckersMixin(object):
bios_settings.c.name == setting['name'])).execute().first()
self.assertEqual('on', setting['value'])
def _check_2bbd96b6ccb9(self, engine, data):
bios_settings = db_utils.get_table(engine, 'bios_settings')
col_names = [column.name for column in bios_settings.c]
self.assertIn('attribute_type', col_names)
self.assertIn('allowable_values', col_names)
self.assertIn('lower_bound', col_names)
self.assertIn('max_length', col_names)
self.assertIn('min_length', col_names)
self.assertIn('read_only', col_names)
self.assertIn('reset_required', col_names)
self.assertIn('unique', col_names)
self.assertIn('upper_bound', col_names)
self.assertIsInstance(bios_settings.c.attribute_type.type,
sqlalchemy.types.String)
self.assertIsInstance(bios_settings.c.allowable_values.type,
sqlalchemy.types.TEXT)
self.assertIsInstance(bios_settings.c.lower_bound.type,
sqlalchemy.types.Integer)
self.assertIsInstance(bios_settings.c.max_length.type,
sqlalchemy.types.Integer)
self.assertIsInstance(bios_settings.c.min_length.type,
sqlalchemy.types.Integer)
self.assertIsInstance(bios_settings.c.read_only.type,
(sqlalchemy.types.Boolean,
sqlalchemy.types.Integer))
self.assertIsInstance(bios_settings.c.reset_required.type,
(sqlalchemy.types.Boolean,
sqlalchemy.types.Integer))
self.assertIsInstance(bios_settings.c.unique.type,
(sqlalchemy.types.Boolean,
sqlalchemy.types.Integer))
self.assertIsInstance(bios_settings.c.upper_bound.type,
sqlalchemy.types.Integer)
def _check_2d13bc3d6bba(self, engine, data):
nodes = db_utils.get_table(engine, 'nodes')
col_names = [column.name for column in nodes.c]

View File

@ -29,7 +29,7 @@ class DbBIOSSettingTestCase(base.DbTestCase):
self.assertEqual(result['node_id'], self.node.id)
self.assertEqual(result['name'], 'virtualization')
self.assertEqual(result['value'], 'on')
self.assertEqual(result['version'], '1.0')
self.assertEqual(result['version'], '1.1')
def test_get_bios_setting_node_not_exist(self):
self.assertRaises(exception.NodeNotFound,
@ -50,7 +50,7 @@ class DbBIOSSettingTestCase(base.DbTestCase):
self.assertEqual(result[0]['node_id'], self.node.id)
self.assertEqual(result[0]['name'], 'virtualization')
self.assertEqual(result[0]['value'], 'on')
self.assertEqual(result[0]['version'], '1.0')
self.assertEqual(result[0]['version'], '1.1')
self.assertEqual(len(result), 1)
def test_get_bios_setting_list_node_not_exist(self):
@ -61,7 +61,7 @@ class DbBIOSSettingTestCase(base.DbTestCase):
def test_create_bios_setting_list(self):
settings = db_utils.get_test_bios_setting_setting_list()
result = self.dbapi.create_bios_setting_list(
self.node.id, settings, '1.0')
self.node.id, settings, '1.1')
self.assertCountEqual(['virtualization', 'hyperthread', 'numlock'],
[setting.name for setting in result])
self.assertCountEqual(['on', 'enabled', 'off'],
@ -69,7 +69,7 @@ class DbBIOSSettingTestCase(base.DbTestCase):
def test_create_bios_setting_list_duplicate(self):
settings = db_utils.get_test_bios_setting_setting_list()
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0')
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.1')
self.assertRaises(exception.BIOSSettingAlreadyExists,
self.dbapi.create_bios_setting_list,
self.node.id, settings, '1.0')
@ -81,18 +81,18 @@ class DbBIOSSettingTestCase(base.DbTestCase):
def test_update_bios_setting_list(self):
settings = db_utils.get_test_bios_setting_setting_list()
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0')
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.1')
settings = [{'name': 'virtualization', 'value': 'off'},
{'name': 'hyperthread', 'value': 'disabled'},
{'name': 'numlock', 'value': 'on'}]
result = self.dbapi.update_bios_setting_list(
self.node.id, settings, '1.0')
self.node.id, settings, '1.1')
self.assertCountEqual(['off', 'disabled', 'on'],
[setting.value for setting in result])
def test_update_bios_setting_list_setting_not_exist(self):
settings = db_utils.get_test_bios_setting_setting_list()
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0')
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.1')
for setting in settings:
setting['name'] = 'bios_name'
self.assertRaises(exception.BIOSSettingNotFound,
@ -106,7 +106,7 @@ class DbBIOSSettingTestCase(base.DbTestCase):
def test_delete_bios_setting_list(self):
settings = db_utils.get_test_bios_setting_setting_list()
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0')
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.1')
name_list = [setting['name'] for setting in settings]
self.dbapi.delete_bios_setting_list(self.node.id, name_list)
self.assertRaises(exception.BIOSSettingNotFound,
@ -126,7 +126,7 @@ class DbBIOSSettingTestCase(base.DbTestCase):
def test_delete_bios_setting_list_setting_not_exist(self):
settings = db_utils.get_test_bios_setting_setting_list()
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0')
self.dbapi.create_bios_setting_list(self.node.id, settings, '1.1')
self.assertRaises(exception.BIOSSettingListNotFound,
self.dbapi.delete_bios_setting_list,
self.node.id, ['fake-bios-option'])

View File

@ -566,7 +566,12 @@ def create_test_bios_setting(**kw):
node_id = bios_setting['node_id']
version = bios_setting['version']
settings = [{'name': bios_setting['name'],
'value': bios_setting['value']}]
'value': bios_setting['value'],
'attribute_type': bios_setting['attribute_type'],
'allowable_values': bios_setting['allowable_values'],
'read_only': bios_setting['read_only'],
'reset_required': bios_setting['reset_required'],
'unique': bios_setting['unique']}]
return dbapi.create_bios_setting_list(node_id, settings, version)[0]
@ -575,6 +580,15 @@ def get_test_bios_setting(**kw):
'node_id': kw.get('node_id', '123'),
'name': kw.get('name', 'virtualization'),
'value': kw.get('value', 'on'),
'attribute_type': kw.get('attribute_type', 'Enumeration'),
'allowable_values': kw.get('allowable_values', ['on', 'off']),
'lower_bound': kw.get('lower_bound', None),
'max_length': kw.get('max_length', None),
'min_length': kw.get('max_length', None),
'read_only': kw.get('read_only', False),
'reset_required': kw.get('reset_required', True),
'unique': kw.get('unique', False),
'upper_bound': kw.get('upper_bound', None),
'version': kw.get('version', bios.BIOSSetting.VERSION),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at'),

View File

@ -42,6 +42,15 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
self.assertEqual(self.bios_setting['node_id'], bios_obj.node_id)
self.assertEqual(self.bios_setting['name'], bios_obj.name)
self.assertEqual(self.bios_setting['value'], bios_obj.value)
self.assertEqual(self.bios_setting['attribute_type'],
bios_obj.attribute_type)
self.assertEqual(self.bios_setting['allowable_values'],
bios_obj.allowable_values)
self.assertEqual(self.bios_setting['reset_required'],
bios_obj.reset_required)
self.assertEqual(self.bios_setting['read_only'],
bios_obj.read_only)
self.assertEqual(self.bios_setting['unique'], bios_obj.unique)
@mock.patch.object(dbapi.IMPL, 'get_bios_setting_list', autospec=True)
def test_get_by_node_id(self, mock_get_setting_list):
@ -67,9 +76,22 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
fake_call_args = {'node_id': self.bios_setting['node_id'],
'name': self.bios_setting['name'],
'value': self.bios_setting['value'],
'attribute_type':
self.bios_setting['attribute_type'],
'allowable_values':
self.bios_setting['allowable_values'],
'read_only': self.bios_setting['read_only'],
'reset_required':
self.bios_setting['reset_required'],
'unique': self.bios_setting['unique'],
'version': self.bios_setting['version']}
setting = [{'name': self.bios_setting['name'],
'value': self.bios_setting['value']}]
setting = [{'name': 'virtualization', 'value': 'on', 'attribute_type':
'Enumeration', 'allowable_values': ['on', 'off'],
'lower_bound': None, 'max_length': None,
'min_length': None, 'read_only': False,
'reset_required': True, 'unique': False,
'upper_bound': None}]
bios_obj = objects.BIOSSetting(context=self.context,
**fake_call_args)
mock_create_list.return_value = [self.bios_setting]
@ -81,6 +103,15 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
self.assertEqual(self.bios_setting['node_id'], bios_obj.node_id)
self.assertEqual(self.bios_setting['name'], bios_obj.name)
self.assertEqual(self.bios_setting['value'], bios_obj.value)
self.assertEqual(self.bios_setting['attribute_type'],
bios_obj.attribute_type)
self.assertEqual(self.bios_setting['allowable_values'],
bios_obj.allowable_values)
self.assertEqual(self.bios_setting['read_only'],
bios_obj.read_only)
self.assertEqual(self.bios_setting['reset_required'],
bios_obj.reset_required)
self.assertEqual(self.bios_setting['unique'], bios_obj.unique)
@mock.patch.object(dbapi.IMPL, 'update_bios_setting_list', autospec=True)
def test_save(self, mock_update_list):
@ -89,7 +120,12 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
'value': self.bios_setting['value'],
'version': self.bios_setting['version']}
setting = [{'name': self.bios_setting['name'],
'value': self.bios_setting['value']}]
'value': self.bios_setting['value'],
'attribute_type': None, 'allowable_values': None,
'lower_bound': None, 'max_length': None,
'min_length': None, 'read_only': None,
'reset_required': None, 'unique': None,
'upper_bound': None}]
bios_obj = objects.BIOSSetting(context=self.context,
**fake_call_args)
mock_update_list.return_value = [self.bios_setting]
@ -111,7 +147,7 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
bios_obj_list = objects.BIOSSettingList.create(
self.context, self.node_id, settings)
mock_create_list.assert_called_once_with(self.node_id, settings, '1.0')
mock_create_list.assert_called_once_with(self.node_id, settings, '1.1')
self.assertEqual(self.context, bios_obj_list._context)
self.assertEqual(2, len(bios_obj_list))
self.assertEqual(self.bios_setting['node_id'],
@ -120,7 +156,6 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
self.assertEqual(self.bios_setting['value'], bios_obj_list[0].value)
self.assertEqual(bios_setting2['node_id'], bios_obj_list[1].node_id)
self.assertEqual(bios_setting2['name'], bios_obj_list[1].name)
self.assertEqual(bios_setting2['value'], bios_obj_list[1].value)
@mock.patch.object(dbapi.IMPL, 'update_bios_setting_list', autospec=True)
def test_list_save(self, mock_update_list):
@ -131,7 +166,7 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
bios_obj_list = objects.BIOSSettingList.save(
self.context, self.node_id, settings)
mock_update_list.assert_called_once_with(self.node_id, settings, '1.0')
mock_update_list.assert_called_once_with(self.node_id, settings, '1.1')
self.assertEqual(self.context, bios_obj_list._context)
self.assertEqual(2, len(bios_obj_list))
self.assertEqual(self.bios_setting['node_id'],
@ -189,8 +224,7 @@ class TestBIOSSettingObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
objects.BIOSSettingList.sync_node_setting(self.ctxt, node.id,
settings))
expected_delete = [{'name': bios_obj_1.name,
'value': bios_obj_1.value}]
expected_delete = [{'name': 'virtualization', 'value': 'on'}]
self.assertEqual(create, settings[:2])
self.assertEqual(update, [])
self.assertEqual(delete, expected_delete)

View File

@ -711,7 +711,7 @@ expected_object_fingerprints = {
'VolumeTargetCRUDPayload': '1.0-30dcc4735512c104a3a36a2ae1e2aeb2',
'Trait': '1.0-3f26cb70c8a10a3807d64c219453e347',
'TraitList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'BIOSSetting': '1.0-fd4a791dc2139a7cc21cefbbaedfd9e7',
'BIOSSetting': '1.1-1137db88675a4e2d7f7bcc3a0d52345a',
'BIOSSettingList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'Allocation': '1.1-38937f2854722f1057ec667b12878708',
'AllocationCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',

View File

@ -0,0 +1,8 @@
---
features:
- |
Provide the registry fields in the BIOS setting API and in the BIOS setting
list when detail is requested. Also added fields selector to query API.
See `story
2008571 <https://storyboard.openstack.org/#!/story/2008571>`_.