Merge "Remove ibmc hardware type"

This commit is contained in:
Zuul 2024-06-19 16:59:01 +00:00 committed by Gerrit Code Review
commit 941f2e36fa
27 changed files with 5 additions and 2550 deletions

View File

@ -842,26 +842,11 @@ function is_deployed_by_irmc {
return 1
}
function is_deployed_by_ibmc {
[[ "$IRONIC_DEPLOY_DRIVER" == ibmc ]] && return 0
return 1
}
function is_drac_enabled {
[[ -z "${IRONIC_ENABLED_HARDWARE_TYPES%%*idrac*}" ]] && return 0
return 1
}
function is_ibmc_enabled {
[[ -z "${IRONIC_ENABLED_HARDWARE_TYPES%%*ibmc*}" ]] && return 0
return 1
}
function is_irmc_enabled {
[[ -z "${IRONIC_ENABLED_HARDWARE_TYPES%%*irmc*}" ]] && return 0
return 1
}
function is_ansible_deploy_enabled {
[[ -z "${IRONIC_ENABLED_DEPLOY_INTERFACES%%*ansible*}" ]] && return 0
return 1
@ -1183,10 +1168,6 @@ function install_ironic {
pip_install python-dracclient
fi
if is_ibmc_enabled; then
pip_install python-ibmcclient
fi
if is_irmc_enabled; then
pip_install python-scciclient pysnmp
fi
@ -2598,11 +2579,6 @@ function enroll_nodes {
if [[ -n "$IRONIC_DEPLOY_ISO_ID" ]]; then
node_options+=" --driver-info deploy_iso=$IRONIC_DEPLOY_ISO_ID"
fi
elif is_deployed_by_ibmc; then
node_options+=" --driver-info ibmc_address=$bmc_address \
--driver-info ibmc_username=$bmc_username \
--driver-info ibmc_password=$bmc_passwd \
--driver-info ibmc_verify_ca=False"
fi
interface_info="${mac_address}"

View File

@ -17,7 +17,6 @@ Hardware Types
.. toctree::
:maxdepth: 1
drivers/ibmc
drivers/idrac
drivers/ilo
drivers/intel-ipmi

View File

@ -1,323 +0,0 @@
===============
iBMC driver
===============
Overview
========
.. warning::
The ``ibmc`` driver has been deprecated and is anticipated to be removed
from Ironic at some point during or after the 2024.2 development cycle.
The anticipated forward management path is to migrate to the ``redfish``
hardware type.
The ``ibmc`` driver is targeted for Huawei V5 series rack server such as
2288H V5, CH121 V5. The iBMC hardware type enables the user to take advantage
of features of `Huawei iBMC`_ to control Huawei server.
The ``ibmc`` hardware type supports the following Ironic interfaces:
* Management Interface: Boot device management
* Power Interface: Power management
* `RAID Interface`_: RAID controller and disk management
* `Vendor Interface`_: ibmc passthru interfaces
Prerequisites
=============
The `HUAWEI iBMC Client library`_ should be installed on the ironic conductor
node(s).
For example, it can be installed with ``pip``::
sudo pip install python-ibmcclient
Enabling the iBMC driver
============================
#. Add ``ibmc`` to the list of ``enabled_hardware_types``,
``enabled_power_interfaces``, ``enabled_vendor_interfaces``
and ``enabled_management_interfaces`` in ``/etc/ironic/ironic.conf``. For example::
[DEFAULT]
...
enabled_hardware_types = ibmc
enabled_power_interfaces = ibmc
enabled_management_interfaces = ibmc
enabled_raid_interfaces = ibmc
enabled_vendor_interfaces = ibmc
#. Restart the ironic conductor service::
sudo service ironic-conductor restart
# Or, for RDO:
sudo systemctl restart openstack-ironic-conductor
Registering a node with the iBMC driver
===========================================
Nodes configured to use the driver should have the ``driver`` property
set to ``ibmc``.
The following properties are specified in the node's ``driver_info``
field:
- ``ibmc_address``:
The URL address to the ibmc controller. It must
include the authority portion of the URL, and can
optionally include the scheme. If the scheme is
missing, https is assumed.
For example: https://ibmc.example.com. This is required.
- ``ibmc_username``:
User account with admin/server-profile access
privilege. This is required.
- ``ibmc_password``:
User account password. This is required.
- ``ibmc_verify_ca``:
If ibmc_address has the **https** scheme, the
driver will use a secure (TLS_) connection when
talking to the ibmc controller. By default
(if this is set to True), the driver will try to
verify the host certificates. This can be set to
the path of a certificate file or directory with
trusted certificates that the driver will use for
verification. To disable verifying TLS_, set this
to False. This is optional.
The ``baremetal node create`` command can be used to enroll
a node with the ``ibmc`` driver. For example:
.. code-block:: bash
baremetal node create --driver ibmc
--driver-info ibmc_address=https://example.com \
--driver-info ibmc_username=admin \
--driver-info ibmc_password=password
For more information about enrolling nodes see :ref:`enrollment`
in the install guide.
RAID Interface
==============
Currently, only RAID controller which supports OOB management can be managed.
See :doc:`/admin/raid` for more information on Ironic RAID support.
The following properties are supported by the iBMC raid interface
implementation, ``ibmc``:
Mandatory properties
--------------------
* ``size_gb``: Size in gigabytes (integer) for the logical disk. Use ``MAX`` as
``size_gb`` if this logical disk is supposed to use the rest of the space
available.
* ``raid_level``: RAID level for the logical disk. Valid values are
``JBOD``, ``0``, ``1``, ``5``, ``6``, ``1+0``, ``5+0`` and ``6+0``. And it
is possible that some RAID controllers can only support a subset RAID
levels.
.. NOTE::
RAID level ``2`` is not supported by ``iBMC`` driver.
Optional properties
-------------------
* ``is_root_volume``: Optional. Specifies whether this disk is a root volume.
By default, this is ``False``.
* ``volume_name``: Optional. Name of the volume to be created. If this is not
specified, it will be N/A.
Backing physical disk hints
---------------------------
See :doc:`/admin/raid` for more information on backing disk hints.
These are machine-independent properties. The hints are specified for each
logical disk to help Ironic find the desired disks for RAID configuration.
* ``share_physical_disks``
* ``disk_type``
* ``interface_type``
* ``number_of_physical_disks``
Backing physical disks
----------------------
These are HUAWEI RAID controller dependent properties:
* ``controller``: Optional. Supported values are: RAID storage id,
RAID storage name or RAID controller name. If a bare metal server have more
than one controller, this is mandatory. Typical values would look like:
* RAID Storage Id: ``RAIDStorage0``
* RAID Storage Name: ``RAIDStorage0``
* RAID Controller Name: ``RAID Card1 Controller``.
* ``physical_disks``: Optional. Supported values are: disk-id, disk-name or
disk serial number. Typical values for hdd disk would look like:
* Disk Id: ``HDDPlaneDisk0``
* Disk Name: ``Disk0``.
* Disk SerialNumber: ``38DGK77LF77D``
Delete RAID configuration
-------------------------
For ``delete_configuration`` step, ``ibmc`` will do:
* delete all logical disks
* delete all hot-spare disks
Logical disks creation priority
-------------------------------
Logical Disks creation priority based on three properties:
* ``share_physical_disks``
* ``physical_disks``
* ``size_gb``
The logical disks creation priority strictly follow the table below, if
multiple logical disks have the same priority, then they will be created with
the same order in ``logical_disks`` array.
==================== ========================== =========
Share physical disks Specified Physical Disks Size
==================== ========================== =========
no yes int|max
no no int
yes yes int
yes yes max
yes no int
yes no max
no no max
==================== ========================== =========
Physical disks choice strategy
------------------------------
.. note::
physical-disk-group: a group of physical disks which have been used by some
logical-disks with same RAID level.
* If no ``physical_disks`` are specified, the "waste least" strategy will be
used to choose the physical disks.
* waste least disk capacity: when using disks with different capacity, it
will cause a waste of disk capacity. This is to avoid with highest
priority.
* using least total disk capacity: for example, we can create 400G RAID 5
with both 5 100G-disks and 3 200G-disks. 5 100G disks is a better
strategy because it uses a 500G capacity totally. While 3 200G-disks
are 600G totally.
* using least disk count: finally, if waste capacity and total disk
capacity are both the same (it rarely happens?), we will choose the one
with the minimum number of disks.
* when ``share_physical_disks`` option is present, ``ibmc`` driver will
create logical disk upon existing physical-disk-group list first. Only
when no existing physical-disk-group matches, then it chooses unused
physical disks with same strategy described above. When multiple exists
physical-disk-groups matches, it will use "waste least" strategy too,
the bigger capacity left the better. For example, to create a logical disk
shown below on a ``ibmc`` server which has two RAID5 logical disks already.
And the shareable capacity of this two logical-disks are 500G and 300G,
then ``ibmc`` driver will choose the second one.
.. code-block:: json
{
"logical_disks": [
{
"controller": "RAID Card1 Controller",
"raid_level": "5",
"size_gb": 100,
"share_physical_disks": true
}
]
}
And the ``ibmc`` server has two RAID5 logical disks already.
* When ``size_gb`` is set to ``MAX``, ``ibmc`` driver will auto work through
all possible cases and choose the "best" solution which has the biggest
capacity and use least capacity. For example: to create a RAID 5+0 logical
disk with MAX size in a server has 9 200G-disks, it will finally choose
"8 disks + span-number 2" but not "9 disks + span-number 3". Although they
both have 1200G capacity totally, but the former uses only 8 disks and the
latter uses 9 disks. If you want to choose the latter solution, you can
specified the disk count to use by adding ``number_of_physical_disks``
option.
.. code-block:: json
{
"logical_disks": [
{
"controller": "RAID Card1 Controller",
"raid_level": "5+0",
"size_gb": "MAX"
}
]
}
Examples
--------
In a typical scenario we may want to create:
* RAID 5, 500G, root OS volume with 3 disks
* RAID 5, rest available space, data volume with rest disks
.. code-block:: json
{
"logical_disks": [
{
"volume_name": "os_volume",
"controller": "RAID Card1 Controller",
"is_root_volume": "True",
"physical_disks": [
"Disk0",
"Disk1",
"Disk2"
],
"raid_level": "5",
"size_gb": "500"
},
{
"volume_name": "data_volume",
"controller": "RAID Card1 Controller",
"raid_level": "5",
"size_gb": "MAX"
}
]
}
Vendor Interface
=========================================
The ``ibmc`` hardware type provides vendor passthru interfaces shown below:
======================== ============ ======================================
Method Name HTTP Method Description
======================== ============ ======================================
boot_up_seq GET Query boot up sequence
get_raid_controller_list GET Query RAID controller summary info
======================== ============ ======================================
.. _Huawei iBMC: https://e.huawei.com/en/products/computing/kunpeng/accessories/ibmc
.. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security
.. _HUAWEI iBMC Client library: https://pypi.org/project/python-ibmcclient/

View File

@ -14,8 +14,5 @@ python-dracclient>=5.1.0,<9.0.0
# Ansible-deploy interface
ansible>=2.7
# HUAWEI iBMC hardware type uses the python-ibmcclient library
python-ibmcclient>=0.2.2,<0.3.0
# Dell EMC iDRAC sushy OEM extension
sushy-oem-idrac>=5.0.0,<6.0.0

View File

@ -720,14 +720,6 @@ class InvalidKickstartFile(Invalid):
_msg_fmt = _("The kickstart file is not valid.")
class IBMCError(DriverOperationError):
_msg_fmt = _("IBMC exception occurred on node %(node)s. Error: %(error)s")
class IBMCConnectionError(IBMCError):
_msg_fmt = _("IBMC connection failed for node %(node)s: %(error)s")
class ClientSideError(RuntimeError):
def __init__(self, msg=None, status_code=400, faultcode='Client'):
self.msg = msg

View File

@ -32,7 +32,6 @@ from ironic.conf import drac
from ironic.conf import fake
from ironic.conf import glance
from ironic.conf import healthcheck
from ironic.conf import ibmc
from ironic.conf import ilo
from ironic.conf import inspector
from ironic.conf import inventory
@ -69,7 +68,6 @@ dnsmasq.register_opts(CONF)
fake.register_opts(CONF)
glance.register_opts(CONF)
healthcheck.register_opts(CONF)
ibmc.register_opts(CONF)
ilo.register_opts(CONF)
inspector.register_opts(CONF)
inventory.register_opts(CONF)

View File

@ -1,35 +0,0 @@
#
# 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.
# Version 1.0.0
from oslo_config import cfg
from ironic.common.i18n import _
opts = [
cfg.IntOpt('connection_attempts',
min=1,
default=5,
help=_('Maximum number of attempts to try to connect '
'to iBMC')),
cfg.IntOpt('connection_retry_interval',
min=1,
default=4,
help=_('Number of seconds to wait between attempts to '
'connect to iBMC'))
]
def register_opts(conf):
conf.register_opts(opts, group='ibmc')

View File

@ -1,47 +0,0 @@
#
# 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.
"""
iBMC Driver for managing HUAWEI V5 series rack servers such as 2288H V5,
CH121 V5.
"""
from ironic.drivers import generic
from ironic.drivers.modules.ibmc import management as ibmc_mgmt
from ironic.drivers.modules.ibmc import power as ibmc_power
from ironic.drivers.modules.ibmc import raid as ibmc_raid
from ironic.drivers.modules.ibmc import vendor as ibmc_vendor
from ironic.drivers.modules import noop
class IBMCHardware(generic.GenericHardware):
"""Huawei iBMC hardware type."""
@property
def supported_management_interfaces(self):
"""List of supported management interfaces."""
return [ibmc_mgmt.IBMCManagement]
@property
def supported_power_interfaces(self):
"""List of supported power interfaces."""
return [ibmc_power.IBMCPower]
@property
def supported_vendor_interfaces(self):
"""List of supported vendor interfaces."""
return [ibmc_vendor.IBMCVendor, noop.NoVendor]
@property
def supported_raid_interfaces(self):
"""List of supported raid interfaces."""
return [ibmc_raid.IbmcRAID, noop.NoRAID]

View File

@ -1,241 +0,0 @@
# Copyright 2019 HUAWEI, Inc. All Rights Reserved.
# Copyright 2017 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.
"""
iBMC Management Interface
"""
from oslo_log import log
from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules.ibmc import mappings
from ironic.drivers.modules.ibmc import utils
constants = importutils.try_import('ibmc_client.constants')
ibmc_client = importutils.try_import('ibmc_client')
LOG = log.getLogger(__name__)
class IBMCManagement(base.ManagementInterface):
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
def __init__(self):
"""Initialize the iBMC management interface
:raises: DriverLoadError if the driver can't be loaded due to
missing dependencies
"""
super(IBMCManagement, self).__init__()
if not ibmc_client:
raise exception.DriverLoadError(
driver='ibmc',
reason=_('Unable to import the python-ibmcclient library'))
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return utils.COMMON_PROPERTIES.copy()
def validate(self, task):
"""Validates the driver information needed by the iBMC driver.
:param task: A TaskManager instance containing the node to act on.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
"""
utils.parse_driver_info(task.node)
@utils.handle_ibmc_exception('get iBMC supported boot devices')
def get_supported_boot_devices(self, task):
"""Get a list of the supported boot devices.
:param task: a task from TaskManager.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
:returns: A list with the supported boot devices defined
in :mod:`ironic.common.boot_devices`.
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
system = conn.system.get()
boot_source_override = system.boot_source_override
return list(map(mappings.GET_BOOT_DEVICE_MAP.get,
boot_source_override.supported_boot_devices))
@task_manager.require_exclusive_lock
@utils.handle_ibmc_exception('set iBMC boot device')
def set_boot_device(self, task, device, persistent=False):
"""Set the boot device for a node.
:param task: A task from TaskManager.
:param device: The boot device, one of
:mod:`ironic.common.boot_device`.
:param persistent: Boolean value. True if the boot device will
persist to all future boots, False if not.
Default: False.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
boot_device = mappings.SET_BOOT_DEVICE_MAP[device]
enabled = mappings.SET_BOOT_DEVICE_PERSISTENT_MAP[persistent]
conn.system.set_boot_source(boot_device, enabled=enabled)
@utils.handle_ibmc_exception('get iBMC boot device')
def get_boot_device(self, task):
"""Get the current boot device for a node.
:param task: A task from TaskManager.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
:returns: a dictionary containing:
:boot_device:
the boot device, one of :mod:`ironic.common.boot_devices` or
None if it is unknown.
:persistent:
Boolean value or None, True if the boot device persists,
False otherwise. None if it's disabled.
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
system = conn.system.get()
boot_source_override = system.boot_source_override
boot_device = boot_source_override.target
enabled = boot_source_override.enabled
return {
'boot_device': mappings.GET_BOOT_DEVICE_MAP.get(boot_device),
'persistent':
mappings.GET_BOOT_DEVICE_PERSISTENT_MAP.get(enabled)
}
def get_supported_boot_modes(self, task):
"""Get a list of the supported boot modes.
:param task: A task from TaskManager.
:returns: A list with the supported boot modes defined
in :mod:`ironic.common.boot_modes`. If boot
mode support can't be determined, empty list
is returned.
"""
return list(mappings.SET_BOOT_MODE_MAP)
@task_manager.require_exclusive_lock
@utils.handle_ibmc_exception('set iBMC boot mode')
def set_boot_mode(self, task, mode):
"""Set the boot mode for a node.
Set the boot mode to use on next reboot of the node.
:param task: A task from TaskManager.
:param mode: The boot mode, one of
:mod:`ironic.common.boot_modes`.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
system = conn.system.get()
boot_source_override = system.boot_source_override
boot_device = boot_source_override.target
boot_override = boot_source_override.enabled
# Copied from redfish driver
# TODO(Qianbiao.NG) what if boot device is "NONE"?
if not boot_device:
error_msg = (_('Cannot change boot mode on node %(node)s '
'because its boot device is not set.') %
{'node': task.node.uuid})
LOG.error(error_msg)
raise exception.IBMCError(error_msg)
# TODO(Qianbiao.NG) what if boot override is "disabled"?
if not boot_override:
i18n = _('Cannot change boot mode on node %(node)s '
'because its boot source override is not set.')
error_msg = i18n % {'node': task.node.uuid}
LOG.error(error_msg)
raise exception.IBMCError(error_msg)
boot_mode = mappings.SET_BOOT_MODE_MAP[mode]
conn.system.set_boot_source(boot_device,
enabled=boot_override,
mode=boot_mode)
@utils.handle_ibmc_exception('get iBMC boot mode')
def get_boot_mode(self, task):
"""Get the current boot mode for a node.
Provides the current boot mode of the node.
:param task: A task from TaskManager.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
:returns: The boot mode, one of :mod:`ironic.common.boot_mode` or
None if it is unknown.
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
system = conn.system.get()
boot_source_override = system.boot_source_override
boot_mode = boot_source_override.mode
return mappings.GET_BOOT_MODE_MAP.get(boot_mode)
def get_sensors_data(self, task):
"""Get sensors data.
Not implemented for this driver.
:raises: NotImplementedError
"""
raise NotImplementedError()
@task_manager.require_exclusive_lock
@utils.handle_ibmc_exception('inject iBMC NMI')
def inject_nmi(self, task):
"""Inject NMI, Non Maskable Interrupt.
Inject NMI (Non Maskable Interrupt) for a node immediately.
:param task: A TaskManager instance containing the node to act on.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
conn.system.reset(constants.RESET_NMI)

View File

@ -1,70 +0,0 @@
#
# 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.
"""
iBMC and Ironic constants mapping
"""
from oslo_utils import importutils
from ironic.common import boot_devices
from ironic.common import boot_modes
from ironic.common import states
from ironic.drivers.modules.ibmc import utils
constants = importutils.try_import('ibmc_client.constants')
if constants:
# Set power state mapping
SET_POWER_STATE_MAP = {
states.POWER_ON: constants.RESET_ON,
states.POWER_OFF: constants.RESET_FORCE_OFF,
states.REBOOT: constants.RESET_FORCE_RESTART,
states.SOFT_REBOOT: constants.RESET_FORCE_POWER_CYCLE,
states.SOFT_POWER_OFF: constants.RESET_GRACEFUL_SHUTDOWN,
}
# Get power state mapping
GET_POWER_STATE_MAP = {
constants.SYSTEM_POWER_STATE_ON: states.POWER_ON,
constants.SYSTEM_POWER_STATE_OFF: states.POWER_OFF,
}
# Boot device mapping
GET_BOOT_DEVICE_MAP = {
constants.BOOT_SOURCE_TARGET_NONE: 'none',
constants.BOOT_SOURCE_TARGET_PXE: boot_devices.PXE,
constants.BOOT_SOURCE_TARGET_FLOPPY: 'floppy',
constants.BOOT_SOURCE_TARGET_CD: boot_devices.CDROM,
constants.BOOT_SOURCE_TARGET_HDD: boot_devices.DISK,
constants.BOOT_SOURCE_TARGET_BIOS_SETUP: boot_devices.BIOS,
}
SET_BOOT_DEVICE_MAP = utils.revert_dictionary(GET_BOOT_DEVICE_MAP)
# Boot mode mapping
GET_BOOT_MODE_MAP = {
constants.BOOT_SOURCE_MODE_BIOS: boot_modes.LEGACY_BIOS,
constants.BOOT_SOURCE_MODE_UEFI: boot_modes.UEFI,
}
SET_BOOT_MODE_MAP = utils.revert_dictionary(GET_BOOT_MODE_MAP)
# Boot device persistent mapping
GET_BOOT_DEVICE_PERSISTENT_MAP = {
constants.BOOT_SOURCE_ENABLED_ONCE: False,
constants.BOOT_SOURCE_ENABLED_CONTINUOUS: True,
constants.BOOT_SOURCE_ENABLED_DISABLED: None,
}
SET_BOOT_DEVICE_PERSISTENT_MAP = utils.revert_dictionary(
GET_BOOT_DEVICE_PERSISTENT_MAP)

View File

@ -1,149 +0,0 @@
#
# 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.
"""
iBMC Power Interface
"""
from oslo_log import log
from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states
from ironic.conductor import task_manager
from ironic.conductor import utils as cond_utils
from ironic.drivers import base
from ironic.drivers.modules.ibmc import mappings
from ironic.drivers.modules.ibmc import utils
constants = importutils.try_import('ibmc_client.constants')
ibmc_client = importutils.try_import('ibmc_client')
LOG = log.getLogger(__name__)
EXPECT_POWER_STATE_MAP = {
states.REBOOT: states.POWER_ON,
states.SOFT_REBOOT: states.POWER_ON,
states.SOFT_POWER_OFF: states.POWER_OFF,
}
class IBMCPower(base.PowerInterface):
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
def __init__(self):
"""Initialize the iBMC power interface.
:raises: DriverLoadError if the driver can't be loaded due to
missing dependencies
"""
super(IBMCPower, self).__init__()
if not ibmc_client:
raise exception.DriverLoadError(
driver='ibmc',
reason=_('Unable to import the python-ibmcclient library'))
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return utils.COMMON_PROPERTIES.copy()
def validate(self, task):
"""Validates the driver information needed by the iBMC driver.
:param task: A TaskManager instance containing the node to act on.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
"""
utils.parse_driver_info(task.node)
@utils.handle_ibmc_exception('get iBMC power state')
def get_power_state(self, task):
"""Get the current power state of the task's node.
:param task: A TaskManager instance containing the node to act on.
:returns: A power state. One of :mod:`ironic.common.states`.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
system = conn.system.get()
return mappings.GET_POWER_STATE_MAP.get(system.power_state)
@task_manager.require_exclusive_lock
@utils.handle_ibmc_exception('set iBMC power state')
def set_power_state(self, task, power_state, timeout=None):
"""Set the power state of the task's node.
:param task: A TaskManager instance containing the node to act on.
:param power_state: Any power state from :mod:`ironic.common.states`.
:param timeout: Time to wait for the node to reach the requested state.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue if a required parameter is missing.
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
reset_type = mappings.SET_POWER_STATE_MAP.get(power_state)
conn.system.reset(reset_type)
target_state = EXPECT_POWER_STATE_MAP.get(power_state, power_state)
cond_utils.node_wait_for_power_state(task, target_state,
timeout=timeout)
@task_manager.require_exclusive_lock
@utils.handle_ibmc_exception('reboot iBMC')
def reboot(self, task, timeout=None):
"""Perform a hard reboot of the task's node.
:param task: A TaskManager instance containing the node to act on.
:param timeout: Time to wait for the node to become powered on.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue if a required parameter is missing.
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
system = conn.system.get()
current_power_state = (
mappings.GET_POWER_STATE_MAP.get(system.power_state)
)
if current_power_state == states.POWER_ON:
conn.system.reset(
mappings.SET_POWER_STATE_MAP.get(states.REBOOT))
else:
conn.system.reset(
mappings.SET_POWER_STATE_MAP.get(states.POWER_ON))
cond_utils.node_wait_for_power_state(task, states.POWER_ON,
timeout=timeout)
def get_supported_power_states(self, task):
"""Get a list of the supported power states.
:param task: A TaskManager instance containing the node to act on.
Not used by this driver at the moment.
:returns: A list with the supported power states defined
in :mod:`ironic.common.states`.
"""
return list(mappings.SET_POWER_STATE_MAP)

View File

@ -1,203 +0,0 @@
# 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.
"""
iBMC RAID configuration specific methods
"""
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from ironic.common.i18n import _
from ironic.common import raid
from ironic import conf
from ironic.drivers import base
from ironic.drivers.modules.ibmc import utils
constants = importutils.try_import('ibmc_client.constants')
ibmc_client = importutils.try_import('ibmc_client')
ibmc_error = importutils.try_import('ibmc_client.exceptions')
CONF = conf.CONF
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
class IbmcRAID(base.RAIDInterface):
"""Implementation of RAIDInterface for iBMC."""
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
RAID_APPLY_CONFIGURATION_ARGSINFO = {
"raid_config": {
"description": "The RAID configuration to apply.",
"required": True,
},
"create_root_volume": {
"description": (
"Setting this to 'False' indicates not to create root "
"volume that is specified in 'raid_config'. Default "
"value is 'True'."
),
"required": False,
},
"create_nonroot_volumes": {
"description": (
"Setting this to 'False' indicates not to create "
"non-root volumes (all except the root volume) in "
"'raid_config'. Default value is 'True'."
),
"required": False,
},
"delete_existing": {
"description": (
"Setting this to 'True' indicates to delete existing RAID "
"configuration prior to creating the new configuration. "
"Default value is 'True'."
),
"required": False,
}
}
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return utils.COMMON_PROPERTIES.copy()
@utils.handle_ibmc_exception('delete iBMC RAID configuration')
def _delete_raid_configuration(self, task):
"""Delete the RAID configuration through `python-ibmcclient` lib.
:param task: a TaskManager instance containing the node to act on.
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
# NOTE(qianbiao.ng): To reduce review workload, we should keep all
# delete logic in python-ibmcclient. And delete raid configuration
# logic should be synchronized. if async required, do it in
# python-ibmcclient.
conn.system.storage.delete_all_raid_configuration()
@utils.handle_ibmc_exception('create iBMC RAID configuration')
def _create_raid_configuration(self, task, logical_disks):
"""Create the RAID configuration through `python-ibmcclient` lib.
:param task: a TaskManager instance containing the node to act on.
:param logical_disks: a list of JSON dictionaries which represents
the logical disks to be created. The JSON dictionary should match
the (ironic.drivers.raid_config_schema.json) scheme.
"""
ibmc = utils.parse_driver_info(task.node)
with ibmc_client.connect(**ibmc) as conn:
# NOTE(qianbiao.ng): To reduce review workload, we should keep all
# apply logic in python-ibmcclient. And apply raid configuration
# logic should be synchronized. if async required, do it in
# python-ibmcclient.
conn.system.storage.apply_raid_configuration(logical_disks)
@base.deploy_step(priority=0,
argsinfo=RAID_APPLY_CONFIGURATION_ARGSINFO)
def apply_configuration(self, task, raid_config, create_root_volume=True,
create_nonroot_volumes=False):
return super(IbmcRAID, self).apply_configuration(
task, raid_config, create_root_volume=create_root_volume,
create_nonroot_volumes=create_nonroot_volumes)
@METRICS.timer('IbmcRAID.create_configuration')
@base.clean_step(priority=0, abortable=False, argsinfo={
'create_root_volume': {
'description': ('This specifies whether to create the root '
'volume. Defaults to `True`.'),
'required': False
},
'create_nonroot_volumes': {
'description': ('This specifies whether to create the non-root '
'volumes. Defaults to `True`.'),
'required': False
},
"delete_existing": {
"description": ("Setting this to 'True' indicates to delete "
"existing RAID configuration prior to creating "
"the new configuration. "
"Default value is 'False'."),
"required": False,
}
})
def create_configuration(self, task, create_root_volume=True,
create_nonroot_volumes=True,
delete_existing=False):
"""Create a RAID configuration.
This method creates a RAID configuration on the given node.
:param task: a TaskManager instance.
:param create_root_volume: If True, a root volume is created
during RAID configuration. Otherwise, no root volume is
created. Default is True.
:param create_nonroot_volumes: If True, non-root volumes are
created. If False, no non-root volumes are created. Default
is True.
:param delete_existing: Setting this to True indicates to delete RAID
configuration prior to creating the new configuration. Default is
False.
:raises: MissingParameterValue, if node.target_raid_config is missing
or empty after skipping root volume and/or non-root volumes.
:raises: IBMCError, on failure to execute step.
"""
node = task.node
raid_config = raid.filter_target_raid_config(
node, create_root_volume=create_root_volume,
create_nonroot_volumes=create_nonroot_volumes)
LOG.info(_("Invoke RAID create_configuration step for node %s(uuid). "
"Current provision state is: %(status)s. "
"Target RAID configuration is: %(config)s."),
{'uuid': node.uuid, 'status': node.provision_state,
'target': raid_config})
# cache current raid config to node's driver_internal_info
node.driver_internal_info['raid_config'] = raid_config
node.save()
# delete exist volumes if necessary
if delete_existing:
self._delete_raid_configuration(task)
# create raid configuration
logical_disks = raid_config.get('logical_disks', [])
self._create_raid_configuration(task, logical_disks)
LOG.info(_("Succeed to create raid configuration on node %s."),
task.node.uuid)
@METRICS.timer('IbmcRAID.delete_configuration')
@base.clean_step(priority=0, abortable=False)
@base.deploy_step(priority=0)
def delete_configuration(self, task):
"""Delete the RAID configuration.
:param task: a TaskManager instance containing the node to act on.
:returns: states.CLEANWAIT if cleaning operation in progress
asynchronously or states.DEPLOYWAIT if deploy operation in
progress synchronously or None if it is completed.
:raises: IBMCError, on failure to execute step.
"""
node = task.node
LOG.info("Invoke RAID delete_configuration step for node %s(uuid). "
"Current provision state is: %(status)s. ",
{'uuid': node.uuid, 'status': node.provision_state})
self._delete_raid_configuration(task)
LOG.info(_("Succeed to delete raid configuration on node %s."),
task.node.uuid)

View File

@ -1,172 +0,0 @@
#
# 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.
"""
iBMC Driver common utils
"""
import functools
import os
from oslo_log import log
from oslo_utils import importutils
from oslo_utils import netutils
from oslo_utils import strutils
import tenacity
from ironic.common import exception
from ironic.common.i18n import _
from ironic.conductor import task_manager
from ironic.conf import CONF
ibmc_client = importutils.try_import('ibmcclient')
ibmc_error = importutils.try_import('ibmc_client.exceptions')
LOG = log.getLogger(__name__)
REQUIRED_PROPERTIES = {
'ibmc_address': _('The URL address to the iBMC controller. It must '
'include the authority portion of the URL. '
'If the scheme is missing, https is assumed. '
'For example: https://mgmt.vendor.com. Required.'),
'ibmc_username': _('User account with admin/server-profile access '
'privilege. Required.'),
'ibmc_password': _('User account password. Required.'),
}
OPTIONAL_PROPERTIES = {
'ibmc_verify_ca': _('Either a Boolean value, a path to a CA_BUNDLE '
'file or directory with certificates of trusted '
'CAs. If set to True the driver will verify the '
'host certificates; if False the driver will '
'ignore verifying the SSL certificate. If it\'s '
'a path the driver will use the specified '
'certificate or one of the certificates in the '
'directory. Defaults to True. Optional.'),
}
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
def parse_driver_info(node):
"""Parse the information required for Ironic to connect to iBMC.
:param node: an Ironic node object
:returns: dictionary of parameters
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
"""
driver_info = node.driver_info or {}
missing_info = [key for key in REQUIRED_PROPERTIES
if not driver_info.get(key)]
if missing_info:
raise exception.MissingParameterValue(_(
'Missing the following iBMC properties in node '
'%(node)s driver_info: %(info)s') % {'node': node.uuid,
'info': missing_info})
# Validate the iBMC address
address = driver_info['ibmc_address']
if '://' not in address:
address = 'https://%s' % address
parsed = netutils.urlsplit(address)
if not parsed.netloc:
raise exception.InvalidParameterValue(
_('Invalid iBMC address %(address)s set in '
'driver_info/ibmc_address on node %(node)s') %
{'address': address, 'node': node.uuid})
# Check if verify_ca is a Boolean or a file/directory in the file-system
verify_ca = driver_info.get('ibmc_verify_ca', True)
if isinstance(verify_ca, str):
if not os.path.exists(verify_ca):
try:
verify_ca = strutils.bool_from_string(verify_ca, strict=True)
except ValueError:
raise exception.InvalidParameterValue(
_('Invalid value type set in driver_info/'
'ibmc_verify_ca on node %(node)s. '
'The value should be a Boolean or the path '
'to a file/directory, not "%(value)s"'
) % {'value': verify_ca, 'node': node.uuid})
elif not isinstance(verify_ca, bool):
raise exception.InvalidParameterValue(
_('Invalid value type set in driver_info/ibmc_verify_ca '
'on node %(node)s. The value should be a Boolean or the path '
'to a file/directory, not "%(value)s"') % {'value': verify_ca,
'node': node.uuid})
return {'address': address,
'username': driver_info.get('ibmc_username'),
'password': driver_info.get('ibmc_password'),
'verify_ca': verify_ca}
def revert_dictionary(d):
return {v: k for k, v in d.items()}
def handle_ibmc_exception(action):
"""Decorator to handle iBMC client exception.
Decorated functions must take a :class:`TaskManager` as the first
parameter.
"""
def decorator(f):
def should_retry(e):
connect_error = isinstance(e, exception.IBMCConnectionError)
if connect_error:
LOG.info(_('Failed to connect to iBMC, will retry now. '
'Max retry times is %(retry_times)d.'),
{'retry_times': CONF.ibmc.connection_attempts})
return connect_error
@tenacity.retry(
retry=tenacity.retry_if_exception(should_retry),
stop=tenacity.stop_after_attempt(CONF.ibmc.connection_attempts),
wait=tenacity.wait_fixed(CONF.ibmc.connection_retry_interval),
reraise=True)
@functools.wraps(f)
def wrapper(*args, **kwargs):
# NOTE(dtantsur): this code could be written simpler, but then unit
# testing decorated functions is pretty hard, as we usually pass a
# Mock object instead of TaskManager there.
if len(args) > 1:
is_task_mgr = isinstance(args[1], task_manager.TaskManager)
task = args[1] if is_task_mgr else args[0]
else:
task = args[0]
node = task.node
try:
return f(*args, **kwargs)
except ibmc_error.IBMCConnectionError as e:
error = (_('Failed to connect to iBMC for node %(node)s, '
'Error: %(error)s')
% {'node': node.uuid, 'error': e})
LOG.error(error)
raise exception.IBMCConnectionError(node=node.uuid,
error=error)
except ibmc_error.IBMCClientError as e:
error = (_('Failed to %(action)s for node %(node)s, '
'Error %(error)s')
% {'node': node.uuid, 'action': action, 'error': e})
LOG.error(error)
raise exception.IBMCError(node=node.uuid, error=error)
return wrapper
return decorator

View File

@ -1,112 +0,0 @@
#
# 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.
"""
iBMC Vendor Interface
"""
from oslo_log import log
from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.drivers import base
from ironic.drivers.modules.ibmc import utils
ibmc_client = importutils.try_import('ibmc_client')
LOG = log.getLogger(__name__)
class IBMCVendor(base.VendorInterface):
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
def __init__(self):
"""Initialize the iBMC vendor interface.
:raises: DriverLoadError if the driver can't be loaded due to
missing dependencies
"""
super(IBMCVendor, self).__init__()
if not ibmc_client:
raise exception.DriverLoadError(
driver='ibmc',
reason=_('Unable to import the python-ibmcclient library'))
def validate(self, task, method=None, **kwargs):
"""Validate vendor-specific actions.
If invalid, raises an exception; otherwise returns None.
:param task: A task from TaskManager.
:param method: Method to be validated
:param kwargs: Info for action.
:raises: UnsupportedDriverExtension if 'method' can not be mapped to
the supported interfaces.
:raises: InvalidParameterValue if kwargs does not contain 'method'.
:raises: MissingParameterValue
"""
utils.parse_driver_info(task.node)
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return utils.COMMON_PROPERTIES.copy()
@base.passthru(['GET'], async_call=False,
description=_('Returns a dictionary, '
'containing node boot up sequence, '
'in ascending order'))
@utils.handle_ibmc_exception('get iBMC boot up sequence')
def boot_up_seq(self, task, **kwargs):
"""List boot type order of the node.
:param task: A TaskManager instance containing the node to act on.
:param kwargs: Not used.
:raises: InvalidParameterValue if kwargs does not contain 'method'.
:raises: MissingParameterValue
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
:returns: A dictionary, containing node boot up sequence,
in ascending order.
"""
driver_info = utils.parse_driver_info(task.node)
with ibmc_client.connect(**driver_info) as conn:
system = conn.system.get()
boot_sequence = system.boot_sequence
return {'boot_up_sequence': boot_sequence}
@base.passthru(['GET'], async_call=False,
description=_('Returns a list of dictionary, every '
'dictionary represents a RAID controller '
'summary info'))
@utils.handle_ibmc_exception('get iBMC RAID controller summary')
def get_raid_controller_list(self, task, **kwargs):
"""List RAID controllers summary info of the node.
:param task: A TaskManager instance containing the node to act on.
:param kwargs: Not used.
:raises: IBMCConnectionError when it fails to connect to iBMC
:raises: IBMCError when iBMC responses an error information
:returns: A list of dictionaries, every dictionary represents a RAID
controller summary of node.
"""
driver_info = utils.parse_driver_info(task.node)
with ibmc_client.connect(**driver_info) as conn:
controllers = conn.system.storage.list()
summaries = [ctrl.summary() for ctrl in controllers]
return summaries

View File

@ -679,15 +679,6 @@ def create_test_deploy_template(**kw):
return dbapi.create_deploy_template(template)