iLO BIOS interface implementation

Adds support for manual cleaning steps "apply_configuration" and
"factory_reset" which manage BIOS settings on the iLO. This requires to
implement 'ilo' BIOS interface for 'ilo' hardware type.

Story: #2002899
Task: #22863
Change-Id: Ia014ea3d55504e8e0c815fb4f19bb87b1fc0d6ef
This commit is contained in:
vmud213 2018-06-20 15:02:26 +05:30
parent afbe8e7679
commit 66e1ccb7c5
8 changed files with 484 additions and 1 deletions

View File

@ -4,7 +4,7 @@
# python projects they should package as optional dependencies for Ironic.
# These are available on pypi
proliantutils>=2.5.0
proliantutils>=2.6.0
pysnmp>=4.3.0,<5.0.0
python-ironic-inspector-client>=1.5.0
python-oneviewclient<3.0.0,>=2.5.2

View File

@ -16,6 +16,7 @@ iLO Driver for managing HP Proliant Gen8 and above servers.
"""
from ironic.drivers import generic
from ironic.drivers.modules.ilo import bios
from ironic.drivers.modules.ilo import boot
from ironic.drivers.modules.ilo import console
from ironic.drivers.modules.ilo import inspect
@ -38,6 +39,11 @@ class IloHardware(generic.GenericHardware):
"""List of supported boot interfaces."""
return [boot.IloVirtualMediaBoot, boot.IloPXEBoot]
@property
def supported_bios_interfaces(self):
"""List of supported bios interfaces."""
return [bios.IloBIOS, noop.NoBIOS]
@property
def supported_console_interfaces(self):
"""List of supported console interfaces."""

View File

@ -0,0 +1,157 @@
# Copyright 2018 Hewlett-Packard Development Company, L.P.
#
# 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.
"""
iLO BIOS Interface
"""
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.drivers import base
from ironic.drivers.modules.ilo import common as ilo_common
from ironic import objects
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
ilo_error = importutils.try_import('proliantutils.exception')
class IloBIOS(base.BIOSInterface):
def get_properties(self):
return ilo_common.REQUIRED_PROPERTIES
@METRICS.timer('IloBIOS.validate')
def validate(self, task):
"""Check that 'driver_info' contains required ILO credentials.
Validates whether the 'driver_info' property of the supplied
task's node contains the required credentials information.
:param task: a task from TaskManager.
:raises: InvalidParameterValue if required iLO parameters
are not valid.
:raises: MissingParameterValue if a required parameter is missing.
"""
ilo_common.parse_driver_info(task.node)
@METRICS.timer('IloBIOS.apply_configuration')
@base.clean_step(priority=0, abortable=False, argsinfo={
'settings': {
'description': "Dictionary with current BIOS configuration.",
'required': True
}
})
def apply_configuration(self, task, settings):
"""Applies the provided configuration on the node.
:param task: a TaskManager instance.
:param settings: Settings intended to be applied on the node.
:raises: NodeCleaningFailure when applying the configuration on
the node fails.
"""
data = {}
for setting in settings:
data.update({setting['name']: setting['value']})
node = task.node
errmsg = _("Clean step \"apply_configuration\" failed "
"on node %(node)s with error: %(err)s")
try:
ilo_object = ilo_common.get_ilo_object(node)
ilo_object.set_bios_settings(data)
except (exception.MissingParameterValue,
exception.InvalidParameterValue,
ilo_error.IloError,
ilo_error.IloCommandNotSupportedError) as ir_exception:
raise exception.NodeCleaningFailure(
errmsg % {'node': node.uuid, 'err': ir_exception})
@METRICS.timer('IloBIOS.factory_reset')
@base.clean_step(priority=0, abortable=False)
def factory_reset(self, task):
"""Reset the BIOS settings to factory configuration.
:param task: a TaskManager instance.
:raises: NodeCleaningFailure when IloError or any other exception
is caught.
"""
node = task.node
errmsg = _("Clean step \"factory_reset\" failed "
"on node %(node)s with error: %(err)s")
try:
ilo_object = ilo_common.get_ilo_object(node)
ilo_object.reset_bios_to_default()
except (exception.MissingParameterValue,
exception.InvalidParameterValue,
ilo_error.IloError,
ilo_error.IloCommandNotSupportedError) as ir_exception:
raise exception.NodeCleaningFailure(
errmsg % {'node': node.uuid, 'err': ir_exception})
@METRICS.timer('IloBIOS.cache_bios_settings')
def cache_bios_settings(self, task):
"""Store the BIOS settings in the database.
:param task: a TaskManager instance.
:raises: NodeCleaningFailure when IloError or any other exception
is caught.
"""
node = task.node
nodeid = node.id
errmsg = _("Caching BIOS settings failed "
"on node %(node)s with error: %(err)s")
try:
ilo_object = ilo_common.get_ilo_object(node)
bios_settings = ilo_object.get_pending_bios_settings()
except (exception.MissingParameterValue,
exception.InvalidParameterValue,
ilo_error.IloError,
ilo_error.IloCommandNotSupportedError) as ir_exception:
raise exception.NodeCleaningFailure(
errmsg % {'node': node.uuid, 'err': ir_exception})
fmt_bios_settings = []
for setting in bios_settings:
fmt_bios_settings.append({"name": setting,
"value": bios_settings[setting]})
create_list, update_list, delete_list, nochange_list = (
objects.BIOSSettingList.sync_node_setting(task.context,
nodeid,
fmt_bios_settings))
if len(create_list) > 0:
objects.BIOSSettingList.create(task.context, nodeid, create_list)
if len(update_list) > 0:
objects.BIOSSettingList.save(task.context, nodeid, update_list)
if len(delete_list) > 0:
delete_name_list = [delete_name.get(
"name") for delete_name in delete_list]
objects.BIOSSettingList.delete(
task.context, nodeid, delete_name_list)

View File

@ -0,0 +1,287 @@
# Copyright 2018 Hewlett-Packard Development Company, L.P.
# 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.
"""Test class for IloPower module."""
import mock
from oslo_config import cfg
from oslo_utils import importutils
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers.modules.ilo import common as ilo_common
from ironic import objects
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.drivers.modules.ilo import test_common
ilo_error = importutils.try_import('proliantutils.exception')
INFO_DICT = db_utils.get_test_ilo_info()
CONF = cfg.CONF
class IloBiosTestCase(test_common.BaseIloTest):
def test_get_properties(self):
expected = ilo_common.REQUIRED_PROPERTIES
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertEqual(expected, task.driver.bios.get_properties())
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test_validate(self, mock_drvinfo):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.bios.validate(task)
mock_drvinfo.assert_called_once_with(task.node)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def _test_ilo_error(self, error_type,
test_methods_not_called, method_details, ilo_mock):
error_dict = {
"missing_parameter": exception.MissingParameterValue,
"invalid_parameter": exception.InvalidParameterValue
}
exc = error_dict.get(error_type)('error')
ilo_mock.side_effect = exc
method = method_details.get("name")
args = method_details.get("args")
self.assertRaises(exception.NodeCleaningFailure,
method,
*args)
for test_method in test_methods_not_called:
eval("ilo_mock.return_value.%s.assert_not_called()" % (
test_method))
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_apply_configuration(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
data = [
{
"name": "SET_A", "value": "VAL_A",
},
{
"name": "SET_B", "value": "VAL_B",
},
{
"name": "SET_C", "value": "VAL_C",
},
{
"name": "SET_D", "value": "VAL_D",
}
]
task.driver.bios.apply_configuration(task, data)
expected = {
"SET_A": "VAL_A",
"SET_B": "VAL_B",
"SET_C": "VAL_C",
"SET_D": "VAL_D"
}
ilo_object_mock.set_bios_settings.assert_called_once_with(expected)
def test_apply_configuration_missing_parameter(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mdobj = {
"name": task.driver.bios.apply_configuration,
"args": (task, [])
}
self._test_ilo_error("missing_parameter", ["set_bios_settings"],
mdobj)
def test_apply_configuration_invalid_parameter(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mdobj = {
"name": task.driver.bios.apply_configuration,
"args": (task, [])
}
self._test_ilo_error("invalid_parameter", ["set_bios_settings"],
mdobj)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_apply_configuration_with_ilo_error(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
data = [
{
"name": "SET_A", "value": "VAL_A",
},
{
"name": "SET_B", "value": "VAL_B",
},
]
exc = ilo_error.IloError('error')
ilo_object_mock.set_bios_settings.side_effect = exc
self.assertRaises(exception.NodeCleaningFailure,
task.driver.bios.apply_configuration,
task, data)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_factory_reset(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
task.driver.bios.factory_reset(task)
ilo_object_mock.reset_bios_to_default.assert_called_once_with()
def test_factory_reset_missing_parameter(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mdobj = {
"name": task.driver.bios.factory_reset,
"args": (task,)
}
self._test_ilo_error("missing_parameter",
["reset_bios_to_default"], mdobj)
def test_factory_reset_invalid_parameter(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mdobj = {
"name": task.driver.bios.factory_reset,
"args": (task,)
}
self._test_ilo_error("invalid_parameter",
["reset_bios_to_default"], mdobj)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_factory_reset_with_ilo_error(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
exc = ilo_error.IloError('error')
ilo_object_mock.reset_bios_to_default.side_effect = exc
self.assertRaises(exception.NodeCleaningFailure,
task.driver.bios.factory_reset, task)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_factory_reset_with_unknown_error(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
exc = ilo_error.IloCommandNotSupportedError('error')
ilo_object_mock.reset_bios_to_default.side_effect = exc
self.assertRaises(exception.NodeCleaningFailure,
task.driver.bios.factory_reset, task)
@mock.patch.object(objects.BIOSSettingList, 'create')
@mock.patch.object(objects.BIOSSettingList, 'save')
@mock.patch.object(objects.BIOSSettingList, 'delete')
@mock.patch.object(objects.BIOSSettingList, 'sync_node_setting')
@mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
def test_cache_bios_settings(self, get_ilo_object_mock, sync_node_mock,
delete_mock, save_mock, create_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
settings = {
"SET_A": True,
"SET_B": True,
"SET_C": True,
"SET_D": True
}
ilo_object_mock.get_pending_bios_settings.return_value = settings
expected_bios_settings = [
{"name": "SET_A", "value": True},
{"name": "SET_B", "value": True},
{"name": "SET_C", "value": True},
{"name": "SET_D", "value": True}
]
sync_node_mock.return_value = ([], [], [], [])
all_settings = (
[
{"name": "C_1", "value": "C_1_VAL"},
{"name": "C_2", "value": "C_2_VAL"}
],
[
{"name": "U_1", "value": "U_1_VAL"},
{"name": "U_2", "value": "U_2_VAL"}
],
[
{"name": "D_1", "value": "D_1_VAL"},
{"name": "D_2", "value": "D_2_VAL"}
],
[]
)
sync_node_mock.return_value = all_settings
task.driver.bios.cache_bios_settings(task)
ilo_object_mock.get_pending_bios_settings.assert_called_once_with()
actual_arg = sorted(sync_node_mock.call_args[0][2],
key=lambda x: x.get("name"))
expected_arg = sorted(expected_bios_settings,
key=lambda x: x.get("name"))
self.assertEqual(actual_arg, expected_arg)
create_mock.assert_called_once_with(
self.context, task.node.id, all_settings[0])
save_mock.assert_called_once_with(
self.context, task.node.id, all_settings[1])
del_names = [setting.get("name") for setting in all_settings[2]]
delete_mock.assert_called_once_with(
self.context, task.node.id, del_names)
def test_cache_bios_settings_missing_parameter(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mdobj = {
"name": task.driver.bios.cache_bios_settings,
"args": (task,)
}
self._test_ilo_error("missing_parameter",
["get_pending_bios_settings"], mdobj)
def test_cache_bios_settings_invalid_parameter(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mdobj = {
"name": task.driver.bios.cache_bios_settings,
"args": (task,)
}
self._test_ilo_error("invalid_parameter",
["get_pending_bios_settings"], mdobj)
@mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
def test_cache_bios_settings_with_ilo_error(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
exc = ilo_error.IloError('error')
ilo_object_mock.get_pending_bios_settings.side_effect = exc
self.assertRaises(exception.NodeCleaningFailure,
task.driver.bios.cache_bios_settings, task)
@mock.patch.object(ilo_common, 'get_ilo_object', autospec=True)
def test_cache_bios_settings_with_unknown_error(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_object_mock = get_ilo_object_mock.return_value
exc = ilo_error.IloCommandNotSupportedError('error')
ilo_object_mock.get_pending_bios_settings.side_effect = exc
self.assertRaises(exception.NodeCleaningFailure,
task.driver.bios.cache_bios_settings, task)

View File

@ -62,6 +62,7 @@ class BaseIloTest(db_base.DbTestCase):
self.config(enabled_hardware_types=['ilo', 'fake-hardware'],
enabled_boot_interfaces=['ilo-pxe', 'ilo-virtual-media',
'fake'],
enabled_bios_interfaces=['ilo', 'no-bios'],
enabled_power_interfaces=['ilo', 'fake'],
enabled_management_interfaces=['ilo', 'fake'],
enabled_inspect_interfaces=['ilo', 'fake', 'no-inspect'],
@ -71,6 +72,7 @@ class BaseIloTest(db_base.DbTestCase):
self.node = obj_utils.create_test_node(
self.context, uuid=uuidutils.generate_uuid(),
driver='ilo', boot_interface=self.boot_interface,
bios_interface='ilo',
driver_info=self.info)

View File

@ -32,6 +32,7 @@ class IloHardwareTestCase(db_base.DbTestCase):
super(IloHardwareTestCase, self).setUp()
self.config(enabled_hardware_types=['ilo'],
enabled_boot_interfaces=['ilo-virtual-media', 'ilo-pxe'],
enabled_bios_interfaces=['no-bios', 'ilo'],
enabled_console_interfaces=['ilo'],
enabled_deploy_interfaces=['iscsi', 'direct'],
enabled_inspect_interfaces=['ilo'],
@ -47,6 +48,8 @@ class IloHardwareTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, node.id) as task:
self.assertIsInstance(task.driver.boot,
ilo.boot.IloVirtualMediaBoot)
self.assertIsInstance(task.driver.bios,
ilo.bios.IloBIOS)
self.assertIsInstance(task.driver.console,
ilo.console.IloConsoleInterface)
self.assertIsInstance(task.driver.deploy,
@ -143,3 +146,22 @@ class IloHardwareTestCase(db_base.DbTestCase):
agent.AgentRescue)
self.assertIsInstance(task.driver.vendor,
ilo.vendor.VendorPassthru)
def test_override_with_no_bios(self):
node = obj_utils.create_test_node(
self.context, driver='ilo',
boot_interface='ilo-pxe',
bios_interface='no-bios',
deploy_interface='direct',
raid_interface='agent')
with task_manager.acquire(self.context, node.id) as task:
self.assertIsInstance(task.driver.boot,
ilo.boot.IloPXEBoot)
self.assertIsInstance(task.driver.bios,
noop.NoBIOS)
self.assertIsInstance(task.driver.console,
ilo.console.IloConsoleInterface)
self.assertIsInstance(task.driver.deploy,
agent.AgentDeploy)
self.assertIsInstance(task.driver.raid,
agent.AgentRAID)

View File

@ -0,0 +1,8 @@
---
features:
- |
Implements ``bios`` interface for ``ilo`` hardware type.
Adds the list of supported bios interfaces for the `ilo` hardware type.
Adds manual cleaning steps ``apply_configuration`` and ``factory_reset``
which support managing the BIOS settings for the iLO servers using `ilo`
hardware type.

View File

@ -54,6 +54,7 @@ ironic.dhcp =
ironic.hardware.interfaces.bios =
fake = ironic.drivers.modules.fake:FakeBIOS
ilo = ironic.drivers.modules.ilo.bios:IloBIOS
irmc = ironic.drivers.modules.irmc.bios:IRMCBIOS
no-bios = ironic.drivers.modules.noop:NoBIOS