From 5127b6f6ddaddba329e2e250399b334a6a58dbba Mon Sep 17 00:00:00 2001 From: Madhuri Kumari Date: Thu, 16 May 2019 12:40:33 +0000 Subject: [PATCH] Add IntelIPMIHardware IntelIPMIHardware is a hardware that supports configuring Intel Speed Select Performance Profile using ipmitool. Change-Id: I49abccb4735b43aed3155ea7e24b2fa0416c83b2 Story: 2005390 Task: 30484 --- ironic/drivers/intel_ipmi.py | 27 +++++ ironic/drivers/modules/intel_ipmi/__init__.py | 0 .../drivers/modules/intel_ipmi/management.py | 86 +++++++++++++++ .../drivers/modules/intel_ipmi/__init__.py | 0 .../unit/drivers/modules/intel_ipmi/base.py | 29 +++++ .../modules/intel_ipmi/test_intel_ipmi.py | 103 ++++++++++++++++++ .../modules/intel_ipmi/test_management.py | 86 +++++++++++++++ .../intel-ipmi-hardware-30aaa65cdbcb779a.yaml | 9 ++ setup.cfg | 2 + 9 files changed, 342 insertions(+) create mode 100644 ironic/drivers/intel_ipmi.py create mode 100644 ironic/drivers/modules/intel_ipmi/__init__.py create mode 100644 ironic/drivers/modules/intel_ipmi/management.py create mode 100644 ironic/tests/unit/drivers/modules/intel_ipmi/__init__.py create mode 100644 ironic/tests/unit/drivers/modules/intel_ipmi/base.py create mode 100644 ironic/tests/unit/drivers/modules/intel_ipmi/test_intel_ipmi.py create mode 100644 ironic/tests/unit/drivers/modules/intel_ipmi/test_management.py create mode 100644 releasenotes/notes/intel-ipmi-hardware-30aaa65cdbcb779a.yaml diff --git a/ironic/drivers/intel_ipmi.py b/ironic/drivers/intel_ipmi.py new file mode 100644 index 0000000000..905e93048c --- /dev/null +++ b/ironic/drivers/intel_ipmi.py @@ -0,0 +1,27 @@ +# 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.drivers import ipmi +from ironic.drivers.modules.intel_ipmi import management + + +class IntelIPMIHardware(ipmi.IPMIHardware): + """Intel IPMI hardware type. + + Uses ``ipmitool`` to implement power and management. + Provides serial console implementations via ``shellinabox`` or ``socat``. + Supports Intel SST-PP feature. + """ + @property + def supported_management_interfaces(self): + """List of supported management interfaces.""" + return [management.IntelIPMIManagement] diff --git a/ironic/drivers/modules/intel_ipmi/__init__.py b/ironic/drivers/modules/intel_ipmi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ironic/drivers/modules/intel_ipmi/management.py b/ironic/drivers/modules/intel_ipmi/management.py new file mode 100644 index 0000000000..8661b494ef --- /dev/null +++ b/ironic/drivers/modules/intel_ipmi/management.py @@ -0,0 +1,86 @@ +# coding=utf-8 +# 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. + +""" +Intel IPMI Hardware. + +Supports Intel Speed Select Performance Profile. +""" + +from oslo_log import log as logging + +from ironic.common import exception +from ironic.common.i18n import _ +from ironic.drivers import base +from ironic.drivers.modules import ipmitool + + +LOG = logging.getLogger(__name__) +INTEL_SST_PP_CONFIG_HEXA_CODES = ['0x00', '0x01', '0x02'] + + +class IntelIPMIManagement(ipmitool.IPMIManagement): + + def _validate_input(self, config, sockets): + if config not in INTEL_SST_PP_CONFIG_HEXA_CODES: + raise exception.InvalidParameterValue(_( + "Invalid Intel SST-PP configuration value %(config)s " + "specified. Valid values are %(config_list)s.") + % {"config": config, + "config_list": INTEL_SST_PP_CONFIG_HEXA_CODES}) + try: + socket_count = int(sockets) + if socket_count <= 0: + raise ValueError + except ValueError: + raise exception.InvalidParameterValue(_( + "Invalid number of socket %(socket)s value specified. " + "Expected a positive integer.") % {"socket": sockets}) + + @base.deploy_step(priority=200, argsinfo={ + 'intel_speedselect_config': { + 'description': ( + "Hexadecimal code of Intel SST-PP configuration provided. " + "Input value should be string. Accepted values are " + "['0x00', '0x01', '0x02']. " + ), + 'required': True + }, + 'socket_count': { + 'description': ( + "Number of sockets. Input value should be a positive integer." + ) + } + }) + def configure_intel_speedselect(self, task, **kwargs): + config = kwargs.get('intel_speedselect_config') + socket_count = kwargs.get('socket_count', 1) + self._validate_input(config, socket_count) + LOG.debug("Going to set Intel SST-PP configuration level %(config)s " + "for node %(node)s with socket count %(socket)s", + {"config": config, "node": task.node.uuid, + "socket": socket_count}) + self._configure_intel_speed_select(task, config, socket_count) + + def _configure_intel_speed_select(self, task, config, socket_count): + iss_conf = "0x2c 0x41 0x04 0x00 0x0%s %s" + for socket in range(socket_count): + hexa_code = iss_conf % (socket, config) + try: + ipmitool.send_raw(task, hexa_code) + except exception.IPMIFailure as e: + msg = ("Failed to set Intel SST-PP configuration level " + "%(cfg)s on socket number %(skt)s due to reason " + "%(exc)s." % {"cfg": config, "skt": socket, "exc": e}) + LOG.exception(msg) + raise exception.IPMIFailure(message=msg) diff --git a/ironic/tests/unit/drivers/modules/intel_ipmi/__init__.py b/ironic/tests/unit/drivers/modules/intel_ipmi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ironic/tests/unit/drivers/modules/intel_ipmi/base.py b/ironic/tests/unit/drivers/modules/intel_ipmi/base.py new file mode 100644 index 0000000000..0e9fbe0161 --- /dev/null +++ b/ironic/tests/unit/drivers/modules/intel_ipmi/base.py @@ -0,0 +1,29 @@ +# +# 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 base class for iBMC Driver.""" + +from ironic.tests.unit.db import base as db_base +from ironic.tests.unit.db import utils as db_utils +from ironic.tests.unit.objects import utils as obj_utils + + +class IntelIPMITestCase(db_base.DbTestCase): + + def setUp(self): + super(IntelIPMITestCase, self).setUp() + self.driver_info = db_utils.get_test_ipmi_info() + self.config(enabled_hardware_types=['intel-ipmi'], + enabled_management_interfaces=['intel-ipmitool'], + enabled_power_interfaces=['ipmitool']) + self.node = obj_utils.create_test_node( + self.context, driver='intel-ipmi', driver_info=self.driver_info) diff --git a/ironic/tests/unit/drivers/modules/intel_ipmi/test_intel_ipmi.py b/ironic/tests/unit/drivers/modules/intel_ipmi/test_intel_ipmi.py new file mode 100644 index 0000000000..e92d8270f1 --- /dev/null +++ b/ironic/tests/unit/drivers/modules/intel_ipmi/test_intel_ipmi.py @@ -0,0 +1,103 @@ +# 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.conductor import task_manager +from ironic.drivers.modules import agent +from ironic.drivers.modules.intel_ipmi import management as intel_management +from ironic.drivers.modules import ipmitool +from ironic.drivers.modules import iscsi_deploy +from ironic.drivers.modules import noop +from ironic.drivers.modules import pxe +from ironic.drivers.modules.storage import cinder +from ironic.drivers.modules.storage import noop as noop_storage +from ironic.tests.unit.db import base as db_base +from ironic.tests.unit.objects import utils as obj_utils + + +class IntelIPMIHardwareTestCase(db_base.DbTestCase): + + def setUp(self): + super(IntelIPMIHardwareTestCase, self).setUp() + self.config(enabled_hardware_types=['intel-ipmi'], + enabled_power_interfaces=['ipmitool'], + enabled_management_interfaces=['intel-ipmitool', + 'noop'], + enabled_raid_interfaces=['no-raid', 'agent'], + enabled_console_interfaces=['no-console'], + enabled_vendor_interfaces=['ipmitool', 'no-vendor']) + + def _validate_interfaces(self, task, **kwargs): + self.assertIsInstance( + task.driver.management, + kwargs.get('management', intel_management.IntelIPMIManagement)) + self.assertIsInstance( + task.driver.power, + kwargs.get('power', ipmitool.IPMIPower)) + self.assertIsInstance( + task.driver.boot, + kwargs.get('boot', pxe.PXEBoot)) + self.assertIsInstance( + task.driver.deploy, + kwargs.get('deploy', iscsi_deploy.ISCSIDeploy)) + self.assertIsInstance( + task.driver.console, + kwargs.get('console', noop.NoConsole)) + self.assertIsInstance( + task.driver.raid, + kwargs.get('raid', noop.NoRAID)) + self.assertIsInstance( + task.driver.vendor, + kwargs.get('vendor', ipmitool.VendorPassthru)) + self.assertIsInstance( + task.driver.storage, + kwargs.get('storage', noop_storage.NoopStorage)) + self.assertIsInstance( + task.driver.rescue, + kwargs.get('rescue', noop.NoRescue)) + + def test_default_interfaces(self): + node = obj_utils.create_test_node(self.context, driver='intel-ipmi') + with task_manager.acquire(self.context, node.id) as task: + self._validate_interfaces(task) + + def test_override_with_shellinabox(self): + self.config(enabled_console_interfaces=['ipmitool-shellinabox', + 'ipmitool-socat']) + node = obj_utils.create_test_node( + self.context, driver='intel-ipmi', + deploy_interface='direct', + raid_interface='agent', + console_interface='ipmitool-shellinabox', + vendor_interface='no-vendor') + with task_manager.acquire(self.context, node.id) as task: + self._validate_interfaces( + task, + deploy=agent.AgentDeploy, + console=ipmitool.IPMIShellinaboxConsole, + raid=agent.AgentRAID, + vendor=noop.NoVendor) + + def test_override_with_cinder_storage(self): + self.config(enabled_storage_interfaces=['noop', 'cinder']) + node = obj_utils.create_test_node( + self.context, driver='intel-ipmi', + storage_interface='cinder') + with task_manager.acquire(self.context, node.id) as task: + self._validate_interfaces(task, storage=cinder.CinderStorage) + + def test_override_with_agent_rescue(self): + self.config(enabled_rescue_interfaces=['no-rescue', 'agent']) + node = obj_utils.create_test_node( + self.context, driver='intel-ipmi', + rescue_interface='agent') + with task_manager.acquire(self.context, node.id) as task: + self._validate_interfaces(task, rescue=agent.AgentRescue) diff --git a/ironic/tests/unit/drivers/modules/intel_ipmi/test_management.py b/ironic/tests/unit/drivers/modules/intel_ipmi/test_management.py new file mode 100644 index 0000000000..cb8a80d576 --- /dev/null +++ b/ironic/tests/unit/drivers/modules/intel_ipmi/test_management.py @@ -0,0 +1,86 @@ +# Copyright 2015, Cisco Systems. +# +# 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. + +import mock + +from ironic.common import exception +from ironic.conductor import task_manager +from ironic.drivers.modules import ipmitool +from ironic.tests.unit.drivers.modules.intel_ipmi import base + + +class IntelIPMIManagementTestCase(base.IntelIPMITestCase): + def test_configure_intel_speedselect_empty(self): + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaises( + exception.InvalidParameterValue, + task.driver.management.configure_intel_speedselect, + task) + + @mock.patch.object(ipmitool, "send_raw", spec_set=True, + autospec=True) + def test_configure_intel_speedselect(self, send_raw_mock): + send_raw_mock.return_value = [None, None] + config = {"intel_speedselect_config": "0x02", "socket_count": 1} + with task_manager.acquire(self.context, self.node.uuid) as task: + ret = task.driver.management.configure_intel_speedselect(task, + **config) + self.assertIsNone(ret) + send_raw_mock.assert_called_once_with(task, + '0x2c 0x41 0x04 0x00 0x00 0x02') + + @mock.patch.object(ipmitool, "send_raw", spec_set=True, + autospec=True) + def test_configure_intel_speedselect_more_socket(self, send_raw_mock): + send_raw_mock.return_value = [None, None] + config = {"intel_speedselect_config": "0x02", "socket_count": 4} + with task_manager.acquire(self.context, self.node.uuid) as task: + ret = task.driver.management.configure_intel_speedselect(task, + **config) + self.assertIsNone(ret) + self.assertEqual(send_raw_mock.call_count, 4) + calls = [ + mock.call(task, '0x2c 0x41 0x04 0x00 0x00 0x02'), + mock.call(task, '0x2c 0x41 0x04 0x00 0x01 0x02'), + mock.call(task, '0x2c 0x41 0x04 0x00 0x02 0x02'), + mock.call(task, '0x2c 0x41 0x04 0x00 0x03 0x02') + ] + send_raw_mock.assert_has_calls(calls) + + @mock.patch.object(ipmitool, "send_raw", spec_set=True, + autospec=True) + def test_configure_intel_speedselect_error(self, send_raw_mock): + send_raw_mock.side_effect = exception.IPMIFailure('err') + config = {"intel_speedselect_config": "0x02", "socket_count": 1} + with task_manager.acquire(self.context, self.node.uuid) as task: + e = self.assertRaises( + exception.IPMIFailure, + task.driver.management.configure_intel_speedselect, + task, **config) + self.assertIn("Failed to set Intel SST-PP configuration", str(e)) + + def test_configure_intel_speedselect_invalid_input(self): + config = {"intel_speedselect_config": "0", "socket_count": 1} + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaises( + exception.InvalidParameterValue, + task.driver.management.configure_intel_speedselect, + task, **config) + + config = {"intel_speedselect_config": "0x00", "socket_count": -1} + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaises( + exception.InvalidParameterValue, + task.driver.management.configure_intel_speedselect, + task, **config) diff --git a/releasenotes/notes/intel-ipmi-hardware-30aaa65cdbcb779a.yaml b/releasenotes/notes/intel-ipmi-hardware-30aaa65cdbcb779a.yaml new file mode 100644 index 0000000000..20e5b89aae --- /dev/null +++ b/releasenotes/notes/intel-ipmi-hardware-30aaa65cdbcb779a.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds support for the Intel IPMI Hardware with hardware type + ``intel-ipmitool``. This hardware type is same as ``ipmi`` hardware type + with additional support of `Intel Speed Select Performance Profile + Technology `. + It uses management interface ``intel-ipmitool``. It supports setting + the desired configuration level for Intel SST-PP. diff --git a/setup.cfg b/setup.cfg index 7638c911fb..02b5172f7a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,6 +98,7 @@ ironic.hardware.interfaces.management = ibmc = ironic.drivers.modules.ibmc.management:IBMCManagement idrac = ironic.drivers.modules.drac.management:DracManagement ilo = ironic.drivers.modules.ilo.management:IloManagement + intel-ipmitool = ironic.drivers.modules.intel_ipmi.management:IntelIPMIManagement ipmitool = ironic.drivers.modules.ipmitool:IPMIManagement irmc = ironic.drivers.modules.irmc.management:IRMCManagement noop = ironic.drivers.modules.noop_mgmt:NoopManagement @@ -158,6 +159,7 @@ ironic.hardware.types = idrac = ironic.drivers.drac:IDRACHardware ilo = ironic.drivers.ilo:IloHardware ilo5 = ironic.drivers.ilo:Ilo5Hardware + intel-ipmi = ironic.drivers.intel_ipmi:IntelIPMIHardware ipmi = ironic.drivers.ipmi:IPMIHardware irmc = ironic.drivers.irmc:IRMCHardware manual-management = ironic.drivers.generic:ManualManagementHardware