Add indicator management to redfish hw type

Implements necessary indicator management calls to redfish driver
to actually read/set system/chassis and drive LEDs through Redfish.

The spec: https://review.opendev.org/#/c/655685/7/specs/approved/expose-hardware-indicators.rst

Change-Id: Ib9ccdfa41974cd353af8a517dc2917f129e49e03
Story: 2005342
Task: 30471
This commit is contained in:
Ilya Etingof 2019-04-15 15:49:59 +02:00
parent 8990e7dcbc
commit e8f2120405
4 changed files with 340 additions and 0 deletions

View File

@ -20,8 +20,10 @@ from oslo_utils import importutils
from ironic.common import boot_devices
from ironic.common import boot_modes
from ironic.common import components
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import indicator_states
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules.redfish import utils as redfish_utils
@ -55,6 +57,16 @@ if sushy:
BOOT_DEVICE_PERSISTENT_MAP_REV = {v: k for k, v in
BOOT_DEVICE_PERSISTENT_MAP.items()}
INDICATOR_MAP = {
sushy.INDICATOR_LED_LIT: indicator_states.ON,
sushy.INDICATOR_LED_OFF: indicator_states.OFF,
sushy.INDICATOR_LED_BLINKING: indicator_states.BLINKING,
sushy.INDICATOR_LED_UNKNOWN: indicator_states.UNKNOWN
}
INDICATOR_MAP_REV = {
v: k for k, v in INDICATOR_MAP.items()}
class RedfishManagement(base.ManagementInterface):
@ -393,3 +405,200 @@ class RedfishManagement(base.ManagementInterface):
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)
def get_supported_indicators(self, task, component=None):
"""Get a map of the supported indicators (e.g. LEDs).
:param task: A task from TaskManager.
:param component: If not `None`, return indicator information
for just this component, otherwise return indicators for
all existing components.
:returns: A dictionary of hardware components
(:mod:`ironic.common.components`) as keys with values
being dictionaries having indicator IDs as keys and indicator
properties as values.
::
{
'chassis': {
'enclosure-0': {
"readonly": true,
"states": [
"OFF",
"ON"
]
}
},
'system':
'blade-A': {
"readonly": true,
"states": [
"OFF",
"ON"
]
}
},
'drive':
'ssd0': {
"readonly": true,
"states": [
"OFF",
"ON"
]
}
}
}
"""
properties = {
"readonly": False,
"states": [
indicator_states.BLINKING,
indicator_states.OFF,
indicator_states.ON
]
}
indicators = {}
system = redfish_utils.get_system(task.node)
try:
if component in (None, components.CHASSIS) and system.chassis:
indicators[components.CHASSIS] = {
chassis.uuid: properties for chassis in system.chassis
if chassis.indicator_led
}
except sushy.exceptions.SushyError as e:
LOG.debug('Chassis indicator not available for node %(node)s: '
'%(error)s', {'node': task.node.uuid, 'error': e})
try:
if component in (None, components.SYSTEM) and system.indicator_led:
indicators[components.SYSTEM] = {
system.uuid: properties
}
except sushy.exceptions.SushyError as e:
LOG.debug('System indicator not available for node %(node)s: '
'%(error)s', {'node': task.node.uuid, 'error': e})
try:
if (component in (None, components.DISK) and
system.simple_storage and system.simple_storage.drives):
indicators[components.DISK] = {
drive.uuid: properties
for drive in system.simple_storage.drives
if drive.indicator_led
}
except sushy.exceptions.SushyError as e:
LOG.debug('Drive indicator not available for node %(node)s: '
'%(error)s', {'node': task.node.uuid, 'error': e})
return indicators
def set_indicator_state(self, task, component, indicator, state):
"""Set indicator on the hardware component to the desired state.
:param task: A task from TaskManager.
:param component: The hardware component, one of
:mod:`ironic.common.components`.
:param indicator: Indicator ID (as reported by
`get_supported_indicators`).
:param state: Desired state of the indicator, one of
:mod:`ironic.common.indicator_states`.
:raises: InvalidParameterValue if an invalid component, indicator
or state is specified.
:raises: MissingParameterValue if a required parameter is missing
:raises: RedfishError on an error from the Sushy library
"""
system = redfish_utils.get_system(task.node)
try:
if (component == components.SYSTEM
and indicator == system.uuid):
system.set_indicator_led(INDICATOR_MAP_REV[state])
return
elif (component == components.CHASSIS
and system.chassis):
for chassis in system.chassis:
if chassis.uuid == indicator:
chassis.set_indicator_led(
INDICATOR_MAP_REV[state])
return
elif (component == components.DISK and
system.simple_storage and system.simple_storage.drives):
for drive in system.simple_storage.drives:
if drive.uuid == indicator:
drive.set_indicator_led(
INDICATOR_MAP_REV[state])
return
except sushy.exceptions.SushyError as e:
error_msg = (_('Redfish set %(component)s indicator %(indicator)s '
'state %(state)s failed for node %(node)s. Error: '
'%(error)s') % {'component': component,
'indicator': indicator,
'state': state,
'node': task.node.uuid,
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)
raise exception.MissingParameterValue(_(
"Unknown indicator %(indicator)s for component %(component)s of "
"node %(uuid)s") % {'indicator': indicator,
'component': component,
'uuid': task.node.uuid})
def get_indicator_state(self, task, component, indicator):
"""Get current state of the indicator of the hardware component.
:param task: A task from TaskManager.
:param component: The hardware component, one of
:mod:`ironic.common.components`.
:param indicator: Indicator ID (as reported by
`get_supported_indicators`).
:raises: MissingParameterValue if a required parameter is missing
:raises: RedfishError on an error from the Sushy library
:returns: Current state of the indicator, one of
:mod:`ironic.common.indicator_states`.
"""
system = redfish_utils.get_system(task.node)
try:
if (component == components.SYSTEM
and indicator == system.uuid):
return INDICATOR_MAP[system.indicator_led]
if (component == components.CHASSIS
and system.chassis):
for chassis in system.chassis:
if chassis.uuid == indicator:
return INDICATOR_MAP[chassis.indicator_led]
if (component == components.DISK and
system.simple_storage and system.simple_storage.drives):
for drive in system.simple_storage.drives:
if drive.uuid == indicator:
return INDICATOR_MAP[drive.indicator_led]
except sushy.exceptions.SushyError as e:
error_msg = (_('Redfish get %(component)s indicator %(indicator)s '
'state failed for node %(node)s. Error: '
'%(error)s') % {'component': component,
'indicator': indicator,
'node': task.node.uuid,
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)
raise exception.MissingParameterValue(_(
"Unknown indicator %(indicator)s for component %(component)s of "
"node %(uuid)s") % {'indicator': indicator,
'component': component,
'uuid': task.node.uuid})

View File

@ -18,7 +18,9 @@ from oslo_utils import importutils
from ironic.common import boot_devices
from ironic.common import boot_modes
from ironic.common import components
from ironic.common import exception
from ironic.common import indicator_states
from ironic.conductor import task_manager
from ironic.drivers.modules.redfish import management as redfish_mgmt
from ironic.drivers.modules.redfish import utils as redfish_utils
@ -44,6 +46,10 @@ class RedfishManagementTestCase(db_base.DbTestCase):
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
self.system_uuid = 'ZZZ--XXX-YYY'
self.chassis_uuid = 'XXX-YYY-ZZZ'
self.drive_uuid = 'ZZZ-YYY-XXX'
@mock.patch.object(redfish_mgmt, 'sushy', None)
def test_loading_error(self):
self.assertRaisesRegex(
@ -437,3 +443,112 @@ class RedfishManagementTestCase(db_base.DbTestCase):
fake_system.reset_system.assert_called_once_with(
sushy.RESET_NMI)
mock_get_system.assert_called_once_with(task.node)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_get_supported_indicators(self, mock_get_system):
fake_chassis = mock.Mock(
uuid=self.chassis_uuid,
indicator_led=sushy.INDICATOR_LED_LIT)
fake_drive = mock.Mock(
uuid=self.drive_uuid,
indicator_led=sushy.INDICATOR_LED_LIT)
fake_system = mock.Mock(
uuid=self.system_uuid,
chassis=[fake_chassis],
simple_storage=mock.MagicMock(drives=[fake_drive]),
indicator_led=sushy.INDICATOR_LED_LIT)
mock_get_system.return_value = fake_system
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
supported_indicators = (
task.driver.management.get_supported_indicators(task))
expected = {
components.CHASSIS: {
'XXX-YYY-ZZZ': {
"readonly": False,
"states": [
indicator_states.BLINKING,
indicator_states.OFF,
indicator_states.ON
]
}
},
components.SYSTEM: {
'ZZZ--XXX-YYY': {
"readonly": False,
"states": [
indicator_states.BLINKING,
indicator_states.OFF,
indicator_states.ON
]
}
},
components.DISK: {
'ZZZ-YYY-XXX': {
"readonly": False,
"states": [
indicator_states.BLINKING,
indicator_states.OFF,
indicator_states.ON
]
}
}
}
self.assertEqual(expected, supported_indicators)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_set_indicator_state(self, mock_get_system):
fake_chassis = mock.Mock(
uuid=self.chassis_uuid,
indicator_led=sushy.INDICATOR_LED_LIT)
fake_drive = mock.Mock(
uuid=self.drive_uuid,
indicator_led=sushy.INDICATOR_LED_LIT)
fake_system = mock.Mock(
uuid=self.system_uuid,
chassis=[fake_chassis],
simple_storage=mock.MagicMock(drives=[fake_drive]),
indicator_led=sushy.INDICATOR_LED_LIT)
mock_get_system.return_value = fake_system
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.management.set_indicator_state(
task, components.SYSTEM, self.system_uuid, indicator_states.ON)
fake_system.set_indicator_led.assert_called_once_with(
sushy.INDICATOR_LED_LIT)
mock_get_system.assert_called_once_with(task.node)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_get_indicator_state(self, mock_get_system):
fake_chassis = mock.Mock(
uuid=self.chassis_uuid,
indicator_led=sushy.INDICATOR_LED_LIT)
fake_drive = mock.Mock(
uuid=self.drive_uuid,
indicator_led=sushy.INDICATOR_LED_LIT)
fake_system = mock.Mock(
uuid=self.system_uuid,
chassis=[fake_chassis],
simple_storage=mock.MagicMock(drives=[fake_drive]),
indicator_led=sushy.INDICATOR_LED_LIT)
mock_get_system.return_value = fake_system
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
state = task.driver.management.get_indicator_state(
task, components.SYSTEM, self.system_uuid)
mock_get_system.assert_called_once_with(task.node)
self.assertEqual(indicator_states.ON, state)

View File

@ -119,6 +119,18 @@ SUSHY_SPEC = (
'BOOT_SOURCE_TARGET_HDD',
'BOOT_SOURCE_TARGET_CD',
'BOOT_SOURCE_TARGET_BIOS_SETUP',
'CHASSIS_INDICATOR_LED_LIT',
'CHASSIS_INDICATOR_LED_BLINKING',
'CHASSIS_INDICATOR_LED_OFF',
'CHASSIS_INDICATOR_LED_UNKNOWN',
'DRIVE_INDICATOR_LED_LIT',
'DRIVE_INDICATOR_LED_BLINKING',
'DRIVE_INDICATOR_LED_OFF',
'DRIVE_INDICATOR_LED_UNKNOWN',
'INDICATOR_LED_LIT',
'INDICATOR_LED_BLINKING',
'INDICATOR_LED_OFF',
'INDICATOR_LED_UNKNOWN',
'SYSTEM_POWER_STATE_ON',
'SYSTEM_POWER_STATE_POWERING_ON',
'SYSTEM_POWER_STATE_OFF',

View File

@ -181,6 +181,10 @@ if not sushy:
BOOT_SOURCE_TARGET_HDD='Hdd',
BOOT_SOURCE_TARGET_CD='Cd',
BOOT_SOURCE_TARGET_BIOS_SETUP='BiosSetup',
INDICATOR_LED_LIT='indicator led lit',
INDICATOR_LED_BLINKING='indicator led blinking',
INDICATOR_LED_OFF='indicator led off',
INDICATOR_LED_UNKNOWN='indicator led unknown',
SYSTEM_POWER_STATE_ON='on',
SYSTEM_POWER_STATE_POWERING_ON='powering on',
SYSTEM_POWER_STATE_OFF='off',