Firmware Interface
FirmwareInterface base New Config options [default] - enabled_firmware_interfaces - default_firmware_interface New FirmwareInterface base with update method Implementations of FirmwareInterface - FakeFirmware (fake) - NoFirmware (no-firmware) New entrypoint ironic.hardware.interfaces.firmware * fake and no-firmware Api Controllers - Updated: driver/node/utils/versions - Created: firmware Unit tests api-ref for Node Firmware Fake and Noop implementation for FirmwareInterface Change-Id: Ib3b9cb22099819f97d5eab1e3f1b670cb91cbb25
This commit is contained in:
parent
f5e33812bd
commit
aecb581082
48
api-ref/source/baremetal-api-v1-nodes-firmware.inc
Normal file
48
api-ref/source/baremetal-api-v1-nodes-firmware.inc
Normal file
@ -0,0 +1,48 @@
|
||||
.. -*- rst -*-
|
||||
|
||||
=====================
|
||||
Node Firmware (nodes)
|
||||
=====================
|
||||
|
||||
.. versionadded:: 1.84
|
||||
|
||||
Given a Node identifier (``uuid`` or ``name``), the API exposes the list of
|
||||
all Firmware Components associated with that Node.
|
||||
|
||||
These endpoints do not allow modification of the Firmware Components; that
|
||||
should be done by using ``clean steps``.
|
||||
|
||||
List all Firmware Components by Node
|
||||
====================================
|
||||
|
||||
.. rest_method:: GET /v1/nodes/{node_ident}/firmware
|
||||
|
||||
Return a list of Firmware Components associated with ``node_ident``.
|
||||
|
||||
Normal response code: 200
|
||||
|
||||
Error codes: 404
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- node_ident: node_ident
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- firmware: firmware_components
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
- component: firmware_component
|
||||
- initial_version: firmware_component_initial_version
|
||||
- current_version: firmware_component_current_version
|
||||
- last_version_flashed: firmware_component_last_version_flashed
|
||||
|
||||
**Example list of a Node's Firmware Components:**
|
||||
|
||||
.. literalinclude:: samples/node-firmware-components-list-response.json
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"firmware": [
|
||||
{
|
||||
"created_at": "2016-08-18T22:28:49.653974+00:00",
|
||||
"updated_at": "2016-08-18T22:28:49.653974+00:00",
|
||||
"component": "BMC",
|
||||
"initial_version": "v1.0.0",
|
||||
"current_version": "v1.2.0",
|
||||
"last_version_flashed": "v1.2.0"
|
||||
},
|
||||
{
|
||||
"created_at": "2016-08-18T22:28:49.653974+00:00",
|
||||
"updated_at": "2016-08-18T22:28:49.653974+00:00",
|
||||
"component": "BIOS",
|
||||
"initial_version": "v1.0.0",
|
||||
"current_version": "v1.1.5",
|
||||
"last_version_flashed": "v1.1.5"
|
||||
}
|
||||
]
|
||||
}
|
@ -80,6 +80,10 @@ def hide_fields_in_newer_versions(driver):
|
||||
driver.pop('default_bios_interface', None)
|
||||
driver.pop('enabled_bios_interfaces', None)
|
||||
|
||||
if not api_utils.allow_firmware_interface():
|
||||
driver.pop('default_firmware_interface', None)
|
||||
driver.pop('enabled_firmware_interfaces', None)
|
||||
|
||||
|
||||
def convert_with_links(name, hosts, detail=False, interface_info=None,
|
||||
fields=None, sanitize=True):
|
||||
|
75
ironic/api/controllers/v1/firmware.py
Normal file
75
ironic/api/controllers/v1/firmware.py
Normal file
@ -0,0 +1,75 @@
|
||||
# Copyright 2023 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
|
||||
from pecan import rest
|
||||
|
||||
from ironic import api
|
||||
from ironic.api.controllers.v1 import utils as api_utils
|
||||
from ironic.api import method
|
||||
from ironic.common import args
|
||||
from ironic import objects
|
||||
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
|
||||
_DEFAULT_RETURN_FIELDS = ('component', 'initial_version', 'current_version',
|
||||
'last_version_flashed')
|
||||
|
||||
|
||||
# NOTE(iurygregory): Keeping same parameters just in case we decide
|
||||
# to support /v1/nodes/<node_uuid>/firmware/<component>
|
||||
def convert_with_links(rpc_firmware, node_uuid, detail=None, fields=None):
|
||||
"""Build a dict containing a firmware component."""
|
||||
|
||||
fw_component = api_utils.object_to_dict(
|
||||
rpc_firmware,
|
||||
include_uuid=False,
|
||||
fields=fields,
|
||||
)
|
||||
return fw_component
|
||||
|
||||
|
||||
def collection_from_list(node_ident, firmware_components, detail=None,
|
||||
fields=None):
|
||||
firmware_list = []
|
||||
for fw_cmp in firmware_components:
|
||||
firmware_list.append(convert_with_links(fw_cmp, node_ident,
|
||||
detail, fields))
|
||||
return {'firmware': firmware_list}
|
||||
|
||||
|
||||
class NodeFirmwareController(rest.RestController):
|
||||
"""REST controller for Firmware."""
|
||||
|
||||
def __init__(self, node_ident=None):
|
||||
super(NodeFirmwareController, self).__init__()
|
||||
self.node_ident = node_ident
|
||||
|
||||
@METRICS.timer('NodeFirmwareController.get_all')
|
||||
@method.expose()
|
||||
@args.validate(fields=args.string_list, detail=args.boolean)
|
||||
def get_all(self, detail=None, fields=None):
|
||||
"""List node firmware components."""
|
||||
node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:firmware:get', self.node_ident)
|
||||
|
||||
allow_query = api_utils.allow_firmware_interface
|
||||
fields = api_utils.get_request_return_fields(fields, detail,
|
||||
_DEFAULT_RETURN_FIELDS,
|
||||
allow_query, allow_query)
|
||||
components = objects.FirmwareComponentList.get_by_node_id(
|
||||
api.request.context, node.id)
|
||||
return collection_from_list(self.node_ident, components,
|
||||
detail, fields)
|
@ -32,6 +32,7 @@ from ironic.api.controllers import link
|
||||
from ironic.api.controllers.v1 import allocation
|
||||
from ironic.api.controllers.v1 import bios
|
||||
from ironic.api.controllers.v1 import collection
|
||||
from ironic.api.controllers.v1 import firmware
|
||||
from ironic.api.controllers.v1 import notification_utils as notify
|
||||
from ironic.api.controllers.v1 import port
|
||||
from ironic.api.controllers.v1 import portgroup
|
||||
@ -169,6 +170,7 @@ def node_schema():
|
||||
'driver': {'type': 'string'},
|
||||
'driver_info': {'type': ['object', 'null']},
|
||||
'extra': {'type': ['object', 'null']},
|
||||
'firmware_interface': {'type': ['string', 'null']},
|
||||
'inspect_interface': {'type': ['string', 'null']},
|
||||
'instance_info': {'type': ['object', 'null']},
|
||||
'instance_uuid': {'type': ['string', 'null']},
|
||||
@ -283,7 +285,8 @@ PATCH_ALLOWED_FIELDS = [
|
||||
'shard',
|
||||
'storage_interface',
|
||||
'vendor_interface',
|
||||
'parent_node'
|
||||
'parent_node',
|
||||
'firmware_interface'
|
||||
]
|
||||
|
||||
TRAITS_SCHEMA = {
|
||||
@ -1395,6 +1398,7 @@ def _get_fields_for_node_query(fields=None):
|
||||
'driver_internal_info',
|
||||
'extra',
|
||||
'fault',
|
||||
'firmware_interface',
|
||||
'inspection_finished_at',
|
||||
'inspection_started_at',
|
||||
'inspect_interface',
|
||||
@ -2114,6 +2118,7 @@ class NodesController(rest.RestController):
|
||||
'history': NodeHistoryController,
|
||||
'inventory': NodeInventoryController,
|
||||
'children': NodeChildrenController,
|
||||
'firmware': firmware.NodeFirmwareController,
|
||||
}
|
||||
|
||||
@pecan.expose()
|
||||
@ -2139,7 +2144,9 @@ class NodesController(rest.RestController):
|
||||
or (remainder[0] == 'history'
|
||||
and not api_utils.allow_node_history())
|
||||
or (remainder[0] == 'inventory'
|
||||
and not api_utils.allow_node_inventory())):
|
||||
and not api_utils.allow_node_inventory())
|
||||
or (remainder[0] == 'firmware'
|
||||
and not api_utils.allow_firmware_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
|
||||
|
@ -807,7 +807,8 @@ VERSIONED_FIELDS = {
|
||||
'boot_mode': versions.MINOR_75_NODE_BOOT_MODE,
|
||||
'secure_boot': versions.MINOR_75_NODE_BOOT_MODE,
|
||||
'shard': versions.MINOR_82_NODE_SHARD,
|
||||
'parent_node': versions.MINOR_83_PARENT_CHILD_NODES
|
||||
'parent_node': versions.MINOR_83_PARENT_CHILD_NODES,
|
||||
'firmware_interface': versions.MINOR_86_FIRMWARE_INTERFACE
|
||||
}
|
||||
|
||||
for field in V31_FIELDS:
|
||||
@ -2006,3 +2007,11 @@ def allow_continue_inspection_endpoint():
|
||||
"""
|
||||
return (new_continue_inspection_endpoint()
|
||||
or api.request.version.minor == versions.MINOR_1_INITIAL_VERSION)
|
||||
|
||||
|
||||
def allow_firmware_interface():
|
||||
"""Check if we should support firmware interface and endpoints.
|
||||
|
||||
Version 1.84 of the API added support for firmware interface.
|
||||
"""
|
||||
return api.request.version.minor >= versions.MINOR_86_FIRMWARE_INTERFACE
|
||||
|
@ -209,6 +209,7 @@ MINOR_82_NODE_SHARD = 82
|
||||
MINOR_83_PARENT_CHILD_NODES = 83
|
||||
MINOR_84_CONTINUE_INSPECTION = 84
|
||||
MINOR_85_UNHOLD_VERB = 85
|
||||
MINOR_86_FIRMWARE_INTERFACE = 86
|
||||
|
||||
# When adding another version, update:
|
||||
# - MINOR_MAX_VERSION
|
||||
@ -216,7 +217,7 @@ MINOR_85_UNHOLD_VERB = 85
|
||||
# explanation of what changed in the new version
|
||||
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
||||
|
||||
MINOR_MAX_VERSION = MINOR_85_UNHOLD_VERB
|
||||
MINOR_MAX_VERSION = MINOR_86_FIRMWARE_INTERFACE
|
||||
|
||||
# String representations of the minor and maximum versions
|
||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||
|
@ -1009,6 +1009,15 @@ node_policies = [
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:firmware:get',
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve Node Firmware components information',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/firmware', 'method': 'GET'}
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
deprecated_port_reason = """
|
||||
|
@ -574,12 +574,12 @@ RELEASE_MAPPING = {
|
||||
}
|
||||
},
|
||||
'master': {
|
||||
'api': '1.85',
|
||||
'api': '1.86',
|
||||
'rpc': '1.56',
|
||||
'objects': {
|
||||
'Allocation': ['1.1'],
|
||||
'BIOSSetting': ['1.1'],
|
||||
'Node': ['1.38', '1.37'],
|
||||
'Node': ['1.39', '1.38', '1.37'],
|
||||
'NodeHistory': ['1.0'],
|
||||
'NodeInventory': ['1.0'],
|
||||
'Conductor': ['1.3'],
|
||||
|
@ -115,6 +115,11 @@ driver_opts = [
|
||||
help=_ENABLED_IFACE_HELP.format('deploy')),
|
||||
cfg.StrOpt('default_deploy_interface',
|
||||
help=_DEFAULT_IFACE_HELP.format('deploy')),
|
||||
cfg.ListOpt('enabled_firmware_interfaces',
|
||||
default=['no-firmware'],
|
||||
help=_ENABLED_IFACE_HELP.format('firmware')),
|
||||
cfg.StrOpt('default_firmware_interface',
|
||||
help=_DEFAULT_IFACE_HELP.format('firmware')),
|
||||
cfg.ListOpt('enabled_inspect_interfaces',
|
||||
default=['no-inspect', 'redfish'],
|
||||
help=_ENABLED_IFACE_HELP.format('inspect')),
|
||||
|
@ -78,6 +78,12 @@ opts = [
|
||||
'rescue driver. Two comma-delimited values will '
|
||||
'result in a delay with a triangular random '
|
||||
'distribution, weighted on the first value.')),
|
||||
cfg.StrOpt('firmware_delay',
|
||||
default='0',
|
||||
help=_('Delay in seconds for operations with the fake '
|
||||
'firmware driver. Two comma-delimited values will '
|
||||
'result in a delay with a triangular random '
|
||||
'distribution, weighted on the first value.')),
|
||||
]
|
||||
|
||||
|
||||
|
@ -105,6 +105,12 @@ class BareDriver(object):
|
||||
A reference to an instance of :class:DeployInterface.
|
||||
"""
|
||||
|
||||
firmware = None
|
||||
"""`Standard` attribute for inspection related features.
|
||||
|
||||
A reference to an instance of :class:FirmwareInterface.
|
||||
"""
|
||||
|
||||
inspect = None
|
||||
"""`Standard` attribute for inspection related features.
|
||||
|
||||
@ -161,7 +167,8 @@ class BareDriver(object):
|
||||
@property
|
||||
def optional_interfaces(self):
|
||||
"""Interfaces that can be no-op."""
|
||||
return ['bios', 'console', 'inspect', 'raid', 'rescue', 'storage']
|
||||
return ['bios', 'console', 'firmware', 'inspect', 'raid', 'rescue',
|
||||
'storage']
|
||||
|
||||
@property
|
||||
def all_interfaces(self):
|
||||
@ -1736,6 +1743,55 @@ class StorageInterface(BaseInterface, metaclass=abc.ABCMeta):
|
||||
"""
|
||||
|
||||
|
||||
def cache_firmware_components(func):
|
||||
"""A decorator to cache firmware components after running the function.
|
||||
|
||||
:param func: Function or method to wrap.
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapped(self, task, *args, **kwargs):
|
||||
result = func(self, task, *args, **kwargs)
|
||||
self.cache_firmware_components(task)
|
||||
return result
|
||||
return wrapped
|
||||
|
||||
|
||||
class FirmwareInterface(BaseInterface):
|
||||
"""Base class for firmware interface"""
|
||||
|
||||
interface_type = 'firmware'
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, task, settings):
|
||||
"""Update the Firmware on the given using the settings for components.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param settings: a list of dictionaries, each dictionary contains the
|
||||
component name and the url that will be used to update the
|
||||
firmware.
|
||||
:raises: UnsupportedDriverExtension, if the node's driver doesn't
|
||||
support update via the interface.
|
||||
:raises: InvalidParameterValue, if validation of the settings fails.
|
||||
:raises: MissingParamterValue, if some required parameters are
|
||||
missing.
|
||||
:returns: states.CLEANWAIT if Firmware update with the settings is in
|
||||
progress asynchronously of None if it is complete.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def cache_firmware_components(self, task):
|
||||
"""Store or update Firmware Components on the given node.
|
||||
|
||||
This method stores Firmware Components to the firmware_information
|
||||
table during 'cleaning' operation. It will also update the timestamp
|
||||
of each Firmware Component.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: UnsupportedDriverExtension, if the node's driver doesn't
|
||||
support getting Firmware Components from bare metal.
|
||||
"""
|
||||
|
||||
|
||||
def _validate_argsinfo(argsinfo):
|
||||
"""Validate args info.
|
||||
|
||||
|
@ -86,3 +86,8 @@ class FakeHardware(generic.GenericHardware):
|
||||
return [
|
||||
fake.FakeVendorB, fake.FakeVendorA
|
||||
] + super().supported_vendor_interfaces
|
||||
|
||||
@property
|
||||
def supported_firmware_interfaces(self):
|
||||
"""List of classes of supported bios interfaces."""
|
||||
return [fake.FakeFirmware] + super().supported_firmware_interfaces
|
||||
|
@ -86,6 +86,11 @@ class GenericHardware(hardware_type.AbstractHardwareType):
|
||||
return [noop_storage.NoopStorage, cinder.CinderStorage,
|
||||
external_storage.ExternalStorage]
|
||||
|
||||
@property
|
||||
def supported_firmware_interfaces(self):
|
||||
"""List of supported firmware interfaces."""
|
||||
return [noop.NoFirmware]
|
||||
|
||||
|
||||
class ManualManagementHardware(GenericHardware):
|
||||
"""Hardware type that uses manual power and boot management.
|
||||
|
@ -103,6 +103,11 @@ class AbstractHardwareType(object, metaclass=abc.ABCMeta):
|
||||
"""List of supported vendor interfaces."""
|
||||
return [noop.NoVendor]
|
||||
|
||||
@property
|
||||
def supported_firmware_interfaces(self):
|
||||
"""List of supported firmware interfaces."""
|
||||
return [noop.NoFirmware]
|
||||
|
||||
def get_properties(self):
|
||||
"""Get the properties of the hardware type.
|
||||
|
||||
|
@ -443,3 +443,24 @@ class FakeRescue(base.RescueInterface):
|
||||
def unrescue(self, task):
|
||||
sleep(CONF.fake.rescue_delay)
|
||||
return states.ACTIVE
|
||||
|
||||
|
||||
class FakeFirmware(base.FirmwareInterface):
|
||||
"""Example implementation of a simple firmware interface."""
|
||||
|
||||
def get_properties(self):
|
||||
return {}
|
||||
|
||||
def validate(self, task):
|
||||
pass
|
||||
|
||||
@base.clean_step(priority=0, argsinfo={
|
||||
'settings': {'description': ('List of Firmware components, each item '
|
||||
'needs to contain a dictionary with name/value pairs'),
|
||||
'required': True}})
|
||||
def update(self, task, settings):
|
||||
sleep(CONF.fake.firmware_delay)
|
||||
|
||||
def cache_firmware_components(self, task):
|
||||
sleep(CONF.fake.firmware_delay)
|
||||
pass
|
||||
|
@ -81,3 +81,13 @@ class NoBIOS(FailMixin, base.BIOSInterface):
|
||||
|
||||
def cache_bios_settings(self, task):
|
||||
pass
|
||||
|
||||
|
||||
class NoFirmware(FailMixin, base.FirmwareInterface):
|
||||
"""Firmware interface implementation that raises errors on all requests"""
|
||||
|
||||
def update(self, task, settings):
|
||||
_fail(self, task, settings)
|
||||
|
||||
def cache_firmware_components(self, task):
|
||||
pass
|
||||
|
@ -80,7 +80,8 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Version 1.36: Add boot_mode and secure_boot fields
|
||||
# Version 1.37: Add shard field
|
||||
# Version 1.38: Add parent_node field
|
||||
VERSION = '1.38'
|
||||
# Version 1.39: Add firmware_interface field
|
||||
VERSION = '1.39'
|
||||
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
@ -155,6 +156,7 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
'boot_interface': object_fields.StringField(nullable=True),
|
||||
'console_interface': object_fields.StringField(nullable=True),
|
||||
'deploy_interface': object_fields.StringField(nullable=True),
|
||||
'firmware_interface': object_fields.StringField(nullable=True),
|
||||
'inspect_interface': object_fields.StringField(nullable=True),
|
||||
'management_interface': object_fields.StringField(nullable=True),
|
||||
'network_interface': object_fields.StringField(nullable=True),
|
||||
@ -662,6 +664,9 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
For versions prior to this, it should be set to None or removed.
|
||||
Version 1.37: shard was added. Default is None. For versions prior to
|
||||
this, it should be set to None or removed.
|
||||
Version 1.39: firmware_interface field was added. Its default value is
|
||||
None. For versions prior to this, it should be set to None (or
|
||||
removed).
|
||||
|
||||
:param target_version: the desired version of the object
|
||||
:param remove_unavailable_fields: True to remove fields that are
|
||||
@ -677,7 +682,8 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
('automated_clean', 28), ('protected_reason', 29),
|
||||
('owner', 30), ('allocation_id', 31), ('description', 32),
|
||||
('retired_reason', 33), ('lessee', 34), ('boot_mode', 36),
|
||||
('secure_boot', 36), ('shard', 37)]
|
||||
('secure_boot', 36), ('shard', 37),
|
||||
('firmware_interface', 39)]
|
||||
|
||||
for name, minor in fields:
|
||||
self._adjust_field_to_version(name, None, target_version,
|
||||
|
@ -72,8 +72,8 @@ class BaseApiTest(db_base.DbTestCase):
|
||||
|
||||
def _make_app(self):
|
||||
# Determine where we are so we can set up paths in the config
|
||||
root_dir = self.path_get()
|
||||
|
||||
root_dir = self.path_get()
|
||||
self.app_config = {
|
||||
'app': {
|
||||
'root': self.root_controller,
|
||||
|
@ -219,7 +219,7 @@ class TestListDrivers(base.BaseApiTest):
|
||||
|
||||
for iface in driver_base.ALL_INTERFACES:
|
||||
if iface != 'bios':
|
||||
if latest_if or iface not in ['rescue', 'storage']:
|
||||
if latest_if or iface not in ['rescue', 'storage', 'firmware']:
|
||||
self.assertIn('default_%s_interface' % iface, data)
|
||||
self.assertIn('enabled_%s_interfaces' % iface, data)
|
||||
|
||||
|
@ -8478,3 +8478,40 @@ class TestNodeParentNodePatch(test_api_base.BaseApiTest):
|
||||
'/nodes/%s' % self.child_node.uuid, body, headers=headers)
|
||||
self.assertEqual(http_client.OK, response.status_code)
|
||||
self.mock_update_node.assert_called_once()
|
||||
|
||||
|
||||
class TestNodeFirmwareComponent(test_api_base.BaseApiTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNodeFirmwareComponent, self).setUp()
|
||||
self.version = "1.86"
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, id=1)
|
||||
|
||||
self.fw_cmp = obj_utils.create_test_firmware_component(
|
||||
self.context, node_id=self.node.id)
|
||||
self.fw_cmp2 = obj_utils.create_test_firmware_component(
|
||||
self.context, node_id=self.node.id, component='BIOS')
|
||||
|
||||
def test_get_all_firmware_components(self):
|
||||
ret = self.get_json('/nodes/%s/firmware' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version})
|
||||
expected_components = [
|
||||
{'created_at': ret['firmware'][0]['created_at'],
|
||||
'updated_at': ret['firmware'][0]['updated_at'],
|
||||
'component': 'BIOS',
|
||||
'initial_version': 'v1.0.0', 'current_version': 'v1.0.0',
|
||||
'last_version_flashed': None},
|
||||
{'created_at': ret['firmware'][1]['created_at'],
|
||||
'updated_at': ret['firmware'][1]['updated_at'],
|
||||
'component': 'bmc',
|
||||
'initial_version': 'v1.0.0', 'current_version': 'v1.0.0',
|
||||
'last_version_flashed': None}]
|
||||
self.assertEqual({'firmware': expected_components}, ret)
|
||||
|
||||
def test_wrong_version_get_all_firmware_components_old_version(self):
|
||||
ret = self.get_json('/nodes/%s/firmware' % self.node.uuid,
|
||||
headers={api_base.Version.string: "1.81"},
|
||||
expect_errors=True)
|
||||
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_int)
|
||||
|
@ -285,6 +285,10 @@ class TestRBACModelBeforeScopesBase(TestACLBase):
|
||||
value=fake_setting)
|
||||
db_utils.create_test_node_trait(
|
||||
node_id=fake_db_node['id'])
|
||||
# Create a Fake Firmware Component BMC
|
||||
db_utils.create_test_firmware_component(
|
||||
node_id=fake_db_node['id'],
|
||||
)
|
||||
fake_history = db_utils.create_test_history(node_id=fake_db_node.id)
|
||||
fake_inventory = db_utils.create_test_inventory(
|
||||
node_id=fake_db_node.id)
|
||||
|
@ -3946,3 +3946,35 @@ lessee_cannot_get_a_nodes_children:
|
||||
method: get
|
||||
headers: *lessee_reader_headers
|
||||
assert_status: 404
|
||||
|
||||
# Node Firmware
|
||||
|
||||
owner_reader_can_get_firmware_components:
|
||||
path: '/v1/nodes/{owner_node_ident}/firmware'
|
||||
method: get
|
||||
headers: *owner_reader_headers
|
||||
assert_status: 200
|
||||
|
||||
lessee_reader_can_get_firmware_components:
|
||||
path: '/v1/nodes/{lessee_node_ident}/firmware'
|
||||
method: get
|
||||
headers: *lessee_reader_headers
|
||||
assert_status: 200
|
||||
|
||||
third_party_admin_cannot_get_firmware_components:
|
||||
path: '/v1/nodes/{owner_node_ident}/firmware'
|
||||
method: get
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 404
|
||||
|
||||
service_can_get_firmware_components_owner_project:
|
||||
path: '/v1/nodes/{owner_node_ident}/firmware'
|
||||
method: get
|
||||
headers: *service_headers_owner_project
|
||||
assert_status: 200
|
||||
|
||||
service_cannot_get_firmware_components:
|
||||
path: '/v1/nodes/{owner_node_ident}/firmware'
|
||||
method: get
|
||||
headers: *service_headers
|
||||
assert_status: 404
|
||||
|
@ -2340,3 +2340,23 @@ parent_node_patch_by_reader:
|
||||
headers: *reader_headers
|
||||
body: *patch_parent_node
|
||||
assert_status: 403
|
||||
|
||||
# Node Firmware - baremetal:node:firmware:get
|
||||
|
||||
nodes_firmware_component_get_admin:
|
||||
path: '/v1/nodes/{node_ident}/firmware'
|
||||
method: get
|
||||
headers: *admin_headers
|
||||
assert_status: 200
|
||||
|
||||
nodes_firmware_component_get_member:
|
||||
path: '/v1/nodes/{node_ident}/firmware'
|
||||
method: get
|
||||
headers: *scoped_member_headers
|
||||
assert_status: 200
|
||||
|
||||
nodes_firmware_component_get_reader:
|
||||
path: '/v1/nodes/{node_ident}/firmware'
|
||||
method: get
|
||||
headers: *reader_headers
|
||||
assert_status: 200
|
||||
|
@ -378,6 +378,11 @@ class TestFakeHardware(hardware_type.AbstractHardwareType):
|
||||
"""List of supported deploy interfaces."""
|
||||
return [fake.FakeDeploy]
|
||||
|
||||
@property
|
||||
def supported_firmware_interfaces(self):
|
||||
"""List of supported firmware interfaces."""
|
||||
return [fake.FakeFirmware]
|
||||
|
||||
@property
|
||||
def supported_inspect_interfaces(self):
|
||||
"""List of supported inspect interfaces."""
|
||||
@ -586,6 +591,7 @@ class HardwareTypeLoadTestCase(db_base.DbTestCase):
|
||||
'boot': set(['fake']),
|
||||
'console': set(['fake', 'no-console']),
|
||||
'deploy': set(['fake']),
|
||||
'firmware': set(['fake', 'no-firmware']),
|
||||
'inspect': set(['fake', 'no-inspect']),
|
||||
'management': set(['fake']),
|
||||
'network': set(['noop']),
|
||||
|
@ -3578,7 +3578,8 @@ class MiscTestCase(mgr_utils.ServiceSetUpMixin, mgr_utils.CommonMixIn,
|
||||
'network': {'result': True},
|
||||
'storage': {'result': True},
|
||||
'rescue': {'result': True},
|
||||
'bios': {'result': True}}
|
||||
'bios': {'result': True},
|
||||
'firmware': {'result': True}}
|
||||
self.assertEqual(expected, ret)
|
||||
mock_iwdi.assert_called_once_with(self.context, expected_info)
|
||||
|
||||
|
@ -839,12 +839,43 @@ class TestManagementInterface(base.TestCase):
|
||||
management.get_mac_addresses, task_mock)
|
||||
|
||||
|
||||
class MyFirmwareInterface(driver_base.FirmwareInterface):
|
||||
|
||||
def get_properties(self):
|
||||
pass
|
||||
|
||||
def validate(self, task):
|
||||
pass
|
||||
|
||||
@driver_base.cache_firmware_components
|
||||
def update(self, task, settings):
|
||||
return "return_update"
|
||||
|
||||
def cache_firmware_components(self, task):
|
||||
pass
|
||||
|
||||
|
||||
class TestFirmwareInterface(base.TestCase):
|
||||
|
||||
@mock.patch.object(MyFirmwareInterface, 'cache_firmware_components',
|
||||
autospec=True)
|
||||
def test_update_with_wrapper(self, cache_firmware_components_mock):
|
||||
firmware = MyFirmwareInterface()
|
||||
task_mock = mock.MagicMock()
|
||||
|
||||
actual = firmware.update(task_mock, "")
|
||||
cache_firmware_components_mock.assert_called_once_with(
|
||||
firmware, task_mock)
|
||||
self.assertEqual(actual, "return_update")
|
||||
|
||||
|
||||
class TestBareDriver(base.TestCase):
|
||||
|
||||
def test_class_variables(self):
|
||||
self.assertEqual(['boot', 'deploy', 'management', 'network', 'power'],
|
||||
driver_base.BareDriver().core_interfaces)
|
||||
self.assertEqual(
|
||||
['bios', 'console', 'inspect', 'raid', 'rescue', 'storage'],
|
||||
['bios', 'console', 'firmware', 'inspect', 'raid',
|
||||
'rescue', 'storage'],
|
||||
driver_base.BareDriver().optional_interfaces
|
||||
)
|
||||
|
@ -1377,6 +1377,68 @@ class TestConvertToVersion(db_base.DbTestCase):
|
||||
self.assertIsNone(node.secure_boot)
|
||||
self.assertEqual({}, node.obj_get_changes())
|
||||
|
||||
def test_firmware_supported_missing(self):
|
||||
# firmware_interface not set, should be set to default.
|
||||
node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
||||
delattr(node, 'firmware_interface')
|
||||
node.obj_reset_changes()
|
||||
|
||||
node._convert_to_version("1.39")
|
||||
|
||||
self.assertIsNone(node.firmware_interface)
|
||||
self.assertEqual({'firmware_interface': None},
|
||||
node.obj_get_changes())
|
||||
|
||||
def test_firmware_supported_set(self):
|
||||
# firmware_interface set, no change required.
|
||||
node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
||||
|
||||
node.firmware_interface = 'fake'
|
||||
node.obj_reset_changes()
|
||||
node._convert_to_version("1.39")
|
||||
self.assertEqual('fake', node.firmware_interface)
|
||||
self.assertEqual({}, node.obj_get_changes())
|
||||
|
||||
def test_firmware_unsupported_missing(self):
|
||||
# firmware_interface not set, no change required.
|
||||
node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
||||
|
||||
delattr(node, 'firmware_interface')
|
||||
node.obj_reset_changes()
|
||||
node._convert_to_version("1.38")
|
||||
self.assertNotIn('firmware_interface', node)
|
||||
self.assertEqual({}, node.obj_get_changes())
|
||||
|
||||
def test_firmware_unsupported_set_remove(self):
|
||||
# firmware_interface set, should be removed.
|
||||
node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
||||
|
||||
node.firmware_interface = 'fake'
|
||||
node.obj_reset_changes()
|
||||
node._convert_to_version("1.38")
|
||||
self.assertNotIn('firmware_interface', node)
|
||||
self.assertEqual({}, node.obj_get_changes())
|
||||
|
||||
def test_firmware_unsupported_set_no_remove_non_default(self):
|
||||
# firmware_interface set, should be set to default.
|
||||
node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
||||
|
||||
node.firmware_interface = 'fake'
|
||||
node.obj_reset_changes()
|
||||
node._convert_to_version("1.38", False)
|
||||
self.assertIsNone(node.firmware_interface)
|
||||
self.assertEqual({'firmware_interface': None}, node.obj_get_changes())
|
||||
|
||||
def test_firmware_unsupported_set_no_remove_default(self):
|
||||
# firmware_interface set, no change required.
|
||||
node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
||||
|
||||
node.firmware_interface = None
|
||||
node.obj_reset_changes()
|
||||
node._convert_to_version("1.38", False)
|
||||
self.assertIsNone(node.firmware_interface)
|
||||
self.assertEqual({}, node.obj_get_changes())
|
||||
|
||||
|
||||
class TestNodePayloads(db_base.DbTestCase):
|
||||
|
||||
|
@ -676,7 +676,7 @@ class TestObject(_LocalTest, _TestObject):
|
||||
# version bump. It is an MD5 hash of the object fields and remotable methods.
|
||||
# The fingerprint values should only be changed if there is a version bump.
|
||||
expected_object_fingerprints = {
|
||||
'Node': '1.38-7e7fdaa2c2bb01153ad567c9f1081cb7',
|
||||
'Node': '1.39-ee3f5ff28b79f9fabf84a50e34a71684',
|
||||
'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6',
|
||||
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
|
||||
'Port': '1.11-97bf15b61224f26c65e90f007d78bfd2',
|
||||
|
@ -93,6 +93,10 @@ ironic.hardware.interfaces.deploy =
|
||||
fake = ironic.drivers.modules.fake:FakeDeploy
|
||||
ramdisk = ironic.drivers.modules.ramdisk:RamdiskDeploy
|
||||
|
||||
ironic.hardware.interfaces.firmware =
|
||||
fake = ironic.drivers.modules.fake:FakeFirmware
|
||||
no-firmware = ironic.drivers.modules.noop:NoFirmware
|
||||
|
||||
ironic.hardware.interfaces.inspect =
|
||||
fake = ironic.drivers.modules.fake:FakeInspect
|
||||
idrac = ironic.drivers.modules.drac.inspect:DracInspect
|
||||
|
Loading…
Reference in New Issue
Block a user