diff --git a/devstack/lib/ironic b/devstack/lib/ironic index 41a2658bc5..877e171748 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -167,7 +167,7 @@ IRONIC_ENABLED_BOOT_INTERFACES=${IRONIC_ENABLED_BOOT_INTERFACES:-"fake,pxe"} IRONIC_ENABLED_CONSOLE_INTERFACES=${IRONIC_ENABLED_CONSOLE_INTERFACES:-"fake,no-console"} IRONIC_ENABLED_DEPLOY_INTERFACES=${IRONIC_ENABLED_DEPLOY_INTERFACES:-"fake,iscsi,direct"} IRONIC_ENABLED_INSPECT_INTERFACES=${IRONIC_ENABLED_INSPECT_INTERFACES:-"fake,no-inspect"} -IRONIC_ENABLED_MANAGEMENT_INTERFACES=${IRONIC_ENABLED_MANAGEMENT_INTERFACES:-"fake,ipmitool"} +IRONIC_ENABLED_MANAGEMENT_INTERFACES=${IRONIC_ENABLED_MANAGEMENT_INTERFACES:-"fake,ipmitool,noop"} IRONIC_ENABLED_NETWORK_INTERFACES=${IRONIC_ENABLED_NETWORK_INTERFACES:-"flat,noop"} IRONIC_ENABLED_POWER_INTERFACES=${IRONIC_ENABLED_POWER_INTERFACES:-"fake,ipmitool"} IRONIC_ENABLED_RAID_INTERFACES=${IRONIC_ENABLED_RAID_INTERFACES:-"fake,agent,no-raid"} diff --git a/doc/source/admin/drivers/ipmitool.rst b/doc/source/admin/drivers/ipmitool.rst index 160f742f99..b6d64f17d5 100644 --- a/doc/source/admin/drivers/ipmitool.rst +++ b/doc/source/admin/drivers/ipmitool.rst @@ -30,6 +30,21 @@ Please see :doc:`/install/configure-ipmi` for the required dependencies. [DEFAULT] enabled_hardware_types = ipmi + enabled_management_interfaces = ipmitool,noop + enabled_power_interfaces = ipmitool + + Optionally, enable the :doc:`vendor passthru interface + ` and either or both :doc:`console interfaces + `: + + .. code-block:: ini + + [DEFAULT] + enabled_hardware_types = ipmi + enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console + enabled_management_interfaces = ipmitool,noop + enabled_power_interfaces = ipmitool + enabled_vendor_interfaces = ipmitool,no-vendor #. Restart the Ironic conductor service. @@ -153,6 +168,34 @@ protocol version:: Version *1.5* of the IPMI protocol does not support encryption. Therefore, it is highly recommended that version 2.0 is used. +Static boot order configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some hardware is known to misbehave when changing the boot device through the +IPMI protocol. To work around it you can use the ``noop`` management interface +implementation with the ``ipmi`` hardware type. In this case the Bare Metal +service will not change the boot device for you, leaving the pre-configured +boot order. + +For example, in case of the :ref:`pxe-boot`: + +#. Via any available means configure the boot order on the node as follows: + + #. Boot from PXE/iPXE on the provisioning NIC. + + .. warning:: + If it is not possible to limit network boot to only provisioning NIC, + make sure that no other DHCP/PXE servers are accessible by the node. + + #. Boot from hard drive. + +#. Make sure the ``noop`` management interface is enabled, see example in + `Enabling the IPMI hardware type`_. + +#. Change the node to use the ``noop`` management interface:: + + openstack baremetal node set --management-interface noop + .. TODO(lucasagomes): Write about privilege level .. TODO(lucasagomes): Write about force boot device diff --git a/ironic/drivers/ipmi.py b/ironic/drivers/ipmi.py index 96a091b418..3810c894fc 100644 --- a/ironic/drivers/ipmi.py +++ b/ironic/drivers/ipmi.py @@ -17,6 +17,7 @@ Hardware type for IPMI (using ipmitool). from ironic.drivers import generic from ironic.drivers.modules import ipmitool from ironic.drivers.modules import noop +from ironic.drivers.modules import noop_mgmt class IPMIHardware(generic.GenericHardware): @@ -35,7 +36,7 @@ class IPMIHardware(generic.GenericHardware): @property def supported_management_interfaces(self): """List of supported management interfaces.""" - return [ipmitool.IPMIManagement] + return [ipmitool.IPMIManagement, noop_mgmt.NoopManagement] @property def supported_power_interfaces(self): diff --git a/ironic/drivers/modules/noop_mgmt.py b/ironic/drivers/modules/noop_mgmt.py new file mode 100644 index 0000000000..0efc089e99 --- /dev/null +++ b/ironic/drivers/modules/noop_mgmt.py @@ -0,0 +1,58 @@ +# 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. + +"""No-op management interface implementation.""" + +from oslo_log import log + +from ironic.common import boot_devices +from ironic.common import exception +from ironic.common.i18n import _ +from ironic.drivers import base + + +LOG = log.getLogger(__name__) + + +class NoopManagement(base.ManagementInterface): + """No-op management interface implementation. + + Using this implementation requires the boot order to be preconfigured + to first try PXE booting, then fall back to hard drives. + """ + + def get_properties(self): + return {} + + def validate(self, task): + pass + + def get_supported_boot_devices(self, task): + return [boot_devices.PXE, boot_devices.DISK] + + def set_boot_device(self, task, device, persistent=False): + supported = self.get_supported_boot_devices(task) + if device not in supported: + raise exception.InvalidParameterValue( + _("Invalid boot device %(dev)s specified, supported are " + "%(supported)s.") % {'dev': device, + 'supported': ', '.join(supported)}) + LOG.debug('Setting boot device to %(target)s requested for node ' + '%(node)s with noop management. Assuming the correct ' + 'boot order is already configured', + {'target': device, 'node': task.node.uuid}) + + def get_boot_device(self, task): + return {'boot_device': boot_devices.PXE, 'persistent': True} + + def get_sensors_data(self, task): + raise NotImplementedError() diff --git a/ironic/tests/unit/drivers/modules/test_noop_mgmt.py b/ironic/tests/unit/drivers/modules/test_noop_mgmt.py new file mode 100644 index 0000000000..c34b05fe33 --- /dev/null +++ b/ironic/tests/unit/drivers/modules/test_noop_mgmt.py @@ -0,0 +1,37 @@ +# 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 boot_devices +from ironic.common import exception +from ironic.drivers.modules import noop_mgmt +from ironic.tests import base + + +class TestNoopManagement(base.TestCase): + iface = noop_mgmt.NoopManagement() + + def test_dummy_methods(self): + self.assertEqual({}, self.iface.get_properties()) + self.assertIsNone(self.iface.validate("task")) + self.assertEqual([boot_devices.PXE, boot_devices.DISK], + self.iface.get_supported_boot_devices("task")) + self.assertEqual({'boot_device': boot_devices.PXE, + 'persistent': True}, + self.iface.get_boot_device("task")) + + def test_set_boot_device(self): + self.iface.set_boot_device(mock.Mock(), boot_devices.DISK) + self.assertRaises(exception.InvalidParameterValue, + self.iface.set_boot_device, mock.Mock(), + boot_devices.CDROM) diff --git a/ironic/tests/unit/drivers/test_ipmi.py b/ironic/tests/unit/drivers/test_ipmi.py index d302d60ecd..c3e822c52f 100644 --- a/ironic/tests/unit/drivers/test_ipmi.py +++ b/ironic/tests/unit/drivers/test_ipmi.py @@ -15,6 +15,7 @@ from ironic.drivers.modules import agent from ironic.drivers.modules import ipmitool from ironic.drivers.modules import iscsi_deploy from ironic.drivers.modules import noop +from ironic.drivers.modules import noop_mgmt from ironic.drivers.modules import pxe from ironic.drivers.modules.storage import cinder from ironic.drivers.modules.storage import noop as noop_storage @@ -28,7 +29,7 @@ class IPMIHardwareTestCase(db_base.DbTestCase): super(IPMIHardwareTestCase, self).setUp() self.config(enabled_hardware_types=['ipmi'], enabled_power_interfaces=['ipmitool'], - enabled_management_interfaces=['ipmitool'], + enabled_management_interfaces=['ipmitool', 'noop'], enabled_raid_interfaces=['no-raid', 'agent'], enabled_console_interfaces=['no-console'], enabled_vendor_interfaces=['ipmitool', 'no-vendor']) @@ -99,3 +100,12 @@ class IPMIHardwareTestCase(db_base.DbTestCase): rescue_interface='agent') with task_manager.acquire(self.context, node.id) as task: self._validate_interfaces(task, rescue=agent.AgentRescue) + + def test_override_with_noop_mgmt(self): + self.config(enabled_management_interfaces=['ipmitool', 'noop']) + node = obj_utils.create_test_node( + self.context, driver='ipmi', + management_interface='noop') + with task_manager.acquire(self.context, node.id) as task: + self._validate_interfaces(task, + management=noop_mgmt.NoopManagement) diff --git a/releasenotes/notes/ipmi-noop-mgmt-8fad89dc2b4665b8.yaml b/releasenotes/notes/ipmi-noop-mgmt-8fad89dc2b4665b8.yaml new file mode 100644 index 0000000000..a2215b9e59 --- /dev/null +++ b/releasenotes/notes/ipmi-noop-mgmt-8fad89dc2b4665b8.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds support for the new ``noop`` interface to the ``ipmi`` hardware type. + This interface targets hardware that does not correctly change boot mode + via the IPMI protocol. Using it requires pre-configuring the boot order + on a node to try PXE, then fall back to local booting. diff --git a/setup.cfg b/setup.cfg index 0a1df11ea5..eb4cfb2e8a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,6 +98,7 @@ ironic.hardware.interfaces.management = ilo = ironic.drivers.modules.ilo.management:IloManagement ipmitool = ironic.drivers.modules.ipmitool:IPMIManagement irmc = ironic.drivers.modules.irmc.management:IRMCManagement + noop = ironic.drivers.modules.noop_mgmt:NoopManagement oneview = ironic.drivers.modules.oneview.management:OneViewManagement redfish = ironic.drivers.modules.redfish.management:RedfishManagement ucsm = ironic.drivers.modules.ucs.management:UcsManagement