Add indicator management harness to ManagementInterface

Adds ``get_indicator_state``, ``set_indicator_state`` and
``get_supported_indicators`` methods to driver management interface.
Drivers can override these methods implementing indicators (e.g.
LEDs) management calls against the BMC of the baremetal nodes being
managed.

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

Change-Id: I952de78312f09897e6b743e11c9cc5dac977dc36
Story: 2005342
Task: 30311
This commit is contained in:
Ilya Etingof 2019-04-03 19:13:10 +02:00
parent a96f926c24
commit d3e4fe50b5
6 changed files with 276 additions and 0 deletions

View File

@ -0,0 +1,33 @@
# Copyright 2019 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.
"""
Mapping of common hardware components of a computer system.
"""
CHASSIS = 'chassis'
"""Chassis enclosing one or more hardware components"""
SYSTEM = 'system'
"""Computing system"""
DISK = 'disk'
"""Storage drive"""
POWER = 'power'
"""Power supply unit"""
NIC = 'nic'
"""Network interface"""

View File

@ -0,0 +1,30 @@
# Copyright 2019 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.
"""
Mapping of the indicator LED states.
"""
OFF = 'off'
"""LED is off"""
ON = 'on'
"""LED is on"""
BLINKING = 'blinking'
"""LED is blinking"""
UNKNOWN = 'unknown'
"""LED state is not known"""

View File

@ -1000,6 +1000,89 @@ class ManagementInterface(BaseInterface):
raise exception.UnsupportedDriverExtension( raise exception.UnsupportedDriverExtension(
driver=task.node.driver, extension='inject_nmi') driver=task.node.driver, extension='inject_nmi')
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": [
"pff",
"on"
]
}
},
'drive':
'ssd0': {
"readonly": true,
"states": [
"off",
"on"
]
}
}
}
"""
raise exception.UnsupportedDriverExtension(
driver=task.node.driver, extension='get_supported_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`).
: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
"""
raise exception.UnsupportedDriverExtension(
driver=task.node.driver, extension='set_indicator_state')
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: InvalidParameterValue if an invalid component or indicator
is specified.
:raises: MissingParameterValue if a required parameter is missing
:returns: Current state of the indicator, one of
:mod:`ironic.common.indicator_states`.
"""
raise exception.UnsupportedDriverExtension(
driver=task.node.driver, extension='get_indicator_state')
class InspectInterface(BaseInterface): class InspectInterface(BaseInterface):
"""Interface for inspection-related actions.""" """Interface for inspection-related actions."""

View File

@ -27,8 +27,10 @@ on separate vendor_passthru methods.
from oslo_log import log from oslo_log import log
from ironic.common import boot_devices from ironic.common import boot_devices
from ironic.common import components
from ironic.common import exception from ironic.common import exception
from ironic.common.i18n import _ from ironic.common.i18n import _
from ironic.common import indicator_states
from ironic.common import states from ironic.common import states
from ironic.drivers import base from ironic.drivers import base
from ironic import objects from ironic import objects
@ -219,6 +221,44 @@ class FakeManagement(base.ManagementInterface):
def get_sensors_data(self, task): def get_sensors_data(self, task):
return {} return {}
def get_supported_indicators(self, task, component=None):
indicators = {
components.CHASSIS: {
'led-0': {
"readonly": True,
"states": [
indicator_states.OFF,
indicator_states.ON
]
}
},
components.SYSTEM: {
'led': {
"readonly": False,
"states": [
indicator_states.BLINKING,
indicator_states.OFF,
indicator_states.ON
]
}
}
}
return {c: indicators[c] for c in indicators
if not component or component == c}
def get_indicator_state(self, task, component, indicator):
indicators = self.get_supported_indicators(task)
if component not in indicators:
raise exception.InvalidParameterValue(_(
"Invalid component %s specified.") % component)
if indicator not in indicators[component]:
raise exception.InvalidParameterValue(_(
"Invalid indicator %s specified.") % indicator)
return indicator_states.ON
class FakeInspect(base.InspectInterface): class FakeInspect(base.InspectInterface):

View File

@ -17,7 +17,9 @@ import json
import mock import mock
from ironic.common import components
from ironic.common import exception from ironic.common import exception
from ironic.common import indicator_states
from ironic.common import raid from ironic.common import raid
from ironic.common import states from ironic.common import states
from ironic.drivers import base as driver_base from ironic.drivers import base as driver_base
@ -783,6 +785,53 @@ class TestManagementInterface(base.TestCase):
self.assertRaises(exception.UnsupportedDriverExtension, self.assertRaises(exception.UnsupportedDriverExtension,
management.get_boot_mode, task_mock) management.get_boot_mode, task_mock)
def test_get_supported_indicators_default_impl(self):
management = fake.FakeManagement()
task_mock = mock.MagicMock(spec_set=['node'])
expected = {
components.CHASSIS: {
'led-0': {
"readonly": True,
"states": [
indicator_states.OFF,
indicator_states.ON
]
}
},
components.SYSTEM: {
'led': {
"readonly": False,
"states": [
indicator_states.BLINKING,
indicator_states.OFF,
indicator_states.ON
]
}
}
}
self.assertEqual(
expected, management.get_supported_indicators(task_mock))
def test_set_indicator_state_default_impl(self):
management = fake.FakeManagement()
task_mock = mock.MagicMock(spec_set=['node'])
self.assertRaises(exception.UnsupportedDriverExtension,
management.set_indicator_state, task_mock,
components.CHASSIS, 'led-0', indicator_states.ON)
def test_get_indicator_state_default_impl(self):
management = fake.FakeManagement()
task_mock = mock.MagicMock(spec_set=['node'])
expected = indicator_states.ON
self.assertEqual(
expected, management.get_indicator_state(
task_mock, components.CHASSIS, 'led-0'))
class TestBareDriver(base.TestCase): class TestBareDriver(base.TestCase):

View File

@ -20,7 +20,9 @@
from ironic.common import boot_devices from ironic.common import boot_devices
from ironic.common import boot_modes from ironic.common import boot_modes
from ironic.common import components
from ironic.common import exception from ironic.common import exception
from ironic.common import indicator_states
from ironic.common import states from ironic.common import states
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.drivers import base as driver_base from ironic.drivers import base as driver_base
@ -119,6 +121,45 @@ class FakeHardwareTestCase(db_base.DbTestCase):
self.task, boot_modes.LEGACY_BIOS self.task, boot_modes.LEGACY_BIOS
) )
def test_management_interface_get_supported_indicators(self):
expected = {
components.CHASSIS: {
'led-0': {
"readonly": True,
"states": [
indicator_states.OFF,
indicator_states.ON
]
}
},
components.SYSTEM: {
'led': {
"readonly": False,
"states": [
indicator_states.BLINKING,
indicator_states.OFF,
indicator_states.ON
]
}
}
}
self.assertEqual(
expected,
self.driver.management.get_supported_indicators(self.task))
def test_management_interface_get_indicator_state(self):
expected = indicator_states.ON
self.assertEqual(
expected, self.driver.management.get_indicator_state(
self.task, components.CHASSIS, 'led-0'))
def test_management_interface_set_indicator_state_good(self):
self.assertRaises(
exception.UnsupportedDriverExtension,
self.driver.management.set_indicator_state,
self.task, components.CHASSIS, 'led-0', indicator_states.ON)
def test_inspect_interface(self): def test_inspect_interface(self):
self.assertEqual({}, self.driver.inspect.get_properties()) self.assertEqual({}, self.driver.inspect.get_properties())
self.driver.inspect.validate(self.task) self.driver.inspect.validate(self.task)