Merge "Remove ibmc hardware type"
This commit is contained in:
commit
941f2e36fa
@ -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}"
|
||||
|
@ -17,7 +17,6 @@ Hardware Types
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
drivers/ibmc
|
||||
drivers/idrac
|
||||
drivers/ilo
|
||||
drivers/intel-ipmi
|
||||
|
@ -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/
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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')
|
@ -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]
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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
|
@ -679,15 +679,6 @@ def create_test_deploy_template(**kw):
|
||||
return dbapi.create_deploy_template(template)
|
||||
|
||||
|
||||