summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/admin/anaconda-deploy-interface.rst5
-rw-r--r--doc/source/admin/drivers/ilo.rst75
-rw-r--r--doc/source/admin/drivers/redfish.rst52
-rw-r--r--doc/source/admin/drivers/snmp.rst74
-rw-r--r--driver-requirements.txt2
-rw-r--r--ironic/common/pxe_utils.py2
-rw-r--r--ironic/conf/anaconda.py11
-rw-r--r--ironic/conf/deploy.py4
-rw-r--r--ironic/conf/ilo.py5
-rw-r--r--ironic/db/sqlalchemy/__init__.py4
-rw-r--r--ironic/drivers/ilo.py3
-rw-r--r--ironic/drivers/modules/ilo/common.py42
-rw-r--r--ironic/drivers/modules/ilo/management.py79
-rw-r--r--ironic/drivers/modules/ilo/vendor.py43
-rw-r--r--ironic/drivers/modules/ks.cfg.template6
-rw-r--r--ironic/drivers/modules/redfish/utils.py61
-rw-r--r--ironic/drivers/modules/snmp.py339
-rw-r--r--ironic/tests/unit/common/test_pxe_utils.py20
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_common.py52
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_management.py115
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_vendor.py71
-rw-r--r--ironic/tests/unit/drivers/modules/redfish/test_utils.py16
-rw-r--r--ironic/tests/unit/drivers/modules/test_snmp.py80
-rw-r--r--releasenotes/notes/additonal-snmp-drivers-ae1174e6bd6ee3a6.yaml5
-rw-r--r--releasenotes/notes/anaconda-permit-cert-validation-disable-6611d3cb9401031d.yaml8
-rw-r--r--releasenotes/notes/create_csr_clean_step-a720932f61b42118.yaml7
-rw-r--r--releasenotes/notes/ilo-event-subscription-0dadf136411bd16a.yaml7
-rw-r--r--releasenotes/notes/redfish_consider_password_in_session_cache-1fa84234db179053.yaml7
-rw-r--r--releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml10
-rw-r--r--releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po35
-rw-r--r--requirements.txt2
31 files changed, 1152 insertions, 90 deletions
diff --git a/doc/source/admin/anaconda-deploy-interface.rst b/doc/source/admin/anaconda-deploy-interface.rst
index 2c686506a..f48926668 100644
--- a/doc/source/admin/anaconda-deploy-interface.rst
+++ b/doc/source/admin/anaconda-deploy-interface.rst
@@ -277,5 +277,10 @@ Limitations
This deploy interface has only been tested with Red Hat based operating systems
that use anaconda. Other systems are not supported.
+Runtime TLS certifiate injection into ramdisks is not supported. Assets such
+as ``ramdisk`` or a ``stage2`` ramdisk image need to have trusted Certificate
+Authority certificates present within the images *or* the Ironic API endpoint
+utilized should utilize a known trusted Certificate Authority.
+
.. _`anaconda`: https://fedoraproject.org/wiki/Anaconda
.. _`ks.cfg.template`: https://opendev.org/openstack/ironic/src/branch/master/ironic/drivers/modules/ks.cfg.template
diff --git a/doc/source/admin/drivers/ilo.rst b/doc/source/admin/drivers/ilo.rst
index f764a6d89..65ff4f6da 100644
--- a/doc/source/admin/drivers/ilo.rst
+++ b/doc/source/admin/drivers/ilo.rst
@@ -55,6 +55,8 @@ The hardware type ``ilo`` supports following HPE server features:
* `Updating security parameters as manual clean step`_
* `Update Minimum Password Length security parameter as manual clean step`_
* `Update Authentication Failure Logging security parameter as manual clean step`_
+* `Create Certificate Signing Request(CSR) as manual clean step`_
+* `Add HTTPS Certificate as manual clean step`_
* `Activating iLO Advanced license as manual clean step`_
* `Removing CA certificates from iLO as manual clean step`_
* `Firmware based UEFI iSCSI boot from volume support`_
@@ -65,6 +67,7 @@ The hardware type ``ilo`` supports following HPE server features:
* `BIOS configuration support`_
* `IPv6 support`_
* `Layer 3 or DHCP-less ramdisk booting`_
+* `Events subscription`_
Apart from above features hardware type ``ilo5`` also supports following
features:
@@ -200,6 +203,18 @@ The ``ilo`` hardware type supports following hardware interfaces:
enabled_hardware_types = ilo
enabled_rescue_interfaces = agent,no-rescue
+* vendor
+ Supports ``ilo``, ``ilo-redfish`` and ``no-vendor``. The default is
+ ``ilo``. They can be enabled by using the
+ ``[DEFAULT]enabled_vendor_interfaces`` option in ``ironic.conf`` as given
+ below:
+
+ .. code-block:: ini
+
+ [DEFAULT]
+ enabled_hardware_types = ilo
+ enabled_vendor_interfaces = ilo,ilo-redfish,no-vendor
+
The ``ilo5`` hardware type supports all the ``ilo`` interfaces described above,
except for ``boot`` and ``raid`` interfaces. The details of ``boot`` and
@@ -751,6 +766,12 @@ Supported **Manual** Cleaning Operations
``update_auth_failure_logging_threshold``:
Updates the Authentication Failure Logging security parameter. See
`Update Authentication Failure Logging security parameter as manual clean step`_ for user guidance on usage.
+ ``create_csr``:
+ Creates the certificate signing request. See `Create Certificate Signing Request(CSR) as manual clean step`_
+ for user guidance on usage.
+ ``add_https_certificate``:
+ Adds the signed HTTPS certificate to the iLO. See `Add HTTPS Certificate as manual clean step`_ for user
+ guidance on usage.
* iLO with firmware version 1.5 is minimally required to support all the
operations.
@@ -1648,6 +1669,54 @@ Both the arguments ``logging_threshold`` and ``ignore`` are optional. The accept
value be False. If user passes the value of logging_threshold as 0, the Authentication Failure Logging security
parameter will be disabled.
+Create Certificate Signing Request(CSR) as manual clean step
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+iLO driver can invoke ``create_csr`` request as a manual clean step. This step is only supported for iLO5 based hardware.
+
+An example of a manual clean step with ``create_csr`` as the only clean step could be::
+
+ "clean_steps": [{
+ "interface": "management",
+ "step": "create_csr",
+ "args": {
+ "csr_params": {
+ "City": "Bengaluru",
+ "CommonName": "1.1.1.1",
+ "Country": "India",
+ "OrgName": "HPE",
+ "State": "Karnataka"
+ }
+ }
+ }]
+
+The ``[ilo]cert_path`` option in ``ironic.conf`` is used as the directory path for
+creating the CSR, which defaults to ``/var/lib/ironic/ilo``. The CSR is created in the directory location
+given in ``[ilo]cert_path`` in ``node_uuid`` directory as <node_uuid>.csr.
+
+
+Add HTTPS Certificate as manual clean step
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+iLO driver can invoke ``add_https_certificate`` request as a manual clean step. This step is only supported for
+iLO5 based hardware.
+
+An example of a manual clean step with ``add_https_certificate`` as the only clean step could be::
+
+ "clean_steps": [{
+ "interface": "management",
+ "step": "add_https_certificate",
+ "args": {
+ "cert_file": "/test1/iLO.crt"
+ }
+ }]
+
+Argument ``cert_file`` is mandatory. The ``cert_file`` takes the path or url of the certificate file.
+The url schemes supported are: ``file``, ``http`` and ``https``.
+The CSR generated in step ``create_csr`` needs to be signed by a valid CA and the resultant HTTPS certificate should
+be provided in ``cert_file``. It copies the ``cert_file`` to ``[ilo]cert_path`` under ``node.uuid`` as <node_uuid>.crt
+before adding it to iLO.
+
RAID Support
^^^^^^^^^^^^
@@ -2136,6 +2205,12 @@ DHCP-less deploy is supported by ``ilo`` and ``ilo5`` hardware types.
However it would work only with ilo-virtual-media boot interface. See
:doc:`/admin/dhcp-less` for more information.
+Events subscription
+^^^^^^^^^^^^^^^^^^^
+Events subscription is supported by ``ilo`` and ``ilo5`` hardware types with
+``ilo`` vendor interface for Gen10 and Gen10 Plus servers. See
+:ref:`node-vendor-passthru-methods` for more information.
+
.. _`ssacli documentation`: https://support.hpe.com/hpsc/doc/public/display?docId=c03909334
.. _`proliant-tools`: https://docs.openstack.org/diskimage-builder/latest/elements/proliant-tools/README.html
.. _`HPE iLO4 User Guide`: https://h20566.www2.hpe.com/hpsc/doc/public/display?docId=c03334051
diff --git a/doc/source/admin/drivers/redfish.rst b/doc/source/admin/drivers/redfish.rst
index dd19f8bde..eb1f561f4 100644
--- a/doc/source/admin/drivers/redfish.rst
+++ b/doc/source/admin/drivers/redfish.rst
@@ -87,8 +87,18 @@ field:
The "auto" mode first tries "session" and falls back
to "basic" if session authentication is not supported
by the Redfish BMC. Default is set in ironic config
- as ``[redfish]auth_type``.
+ as ``[redfish]auth_type``. Most operators should not
+ need to leverage this setting. Session based
+ authentication should generally be used in most
+ cases as it prevents re-authentication every time
+ a background task checks in with the BMC.
+.. note::
+ The ``redfish_address``, ``redfish_username``, ``redfish_password``,
+ and ``redfish_verify_ca`` fields, if changed, will trigger a new session
+ to be establsihed and cached with the BMC. The ``redfish_auth_type`` field
+ will only be used for the creation of a new cached session, or should
+ one be rejected by the BMC.
The ``baremetal node create`` command can be used to enroll
a node with the ``redfish`` driver. For example:
@@ -533,6 +543,8 @@ settings. The following fields will be returned in the BIOS API
"``unique``", "The setting is specific to this node"
"``reset_required``", "After changing this setting a node reboot is required"
+.. _node-vendor-passthru-methods:
+
Node Vendor Passthru Methods
============================
@@ -620,6 +632,44 @@ Eject Virtual Media
"boot_device (optional)", "body", "string", "Type of the device to eject (all devices by default)"
+Internal Session Cache
+======================
+
+The ``redfish`` hardware type, and derived interfaces, utilizes a built-in
+session cache which prevents Ironic from re-authenticating every time
+Ironic attempts to connect to the BMC for any reason.
+
+This consists of cached connectors objects which are used and tracked by
+a unique consideration of ``redfish_username``, ``redfish_password``,
+``redfish_verify_ca``, and finally ``redfish_address``. Changing any one
+of those values will trigger a new session to be created.
+The ``redfish_system_id`` value is explicitly not considered as Redfish
+has a model of use of one BMC to many systems, which is also a model
+Ironic supports.
+
+The session cache default size is ``1000`` sessions per conductor.
+If you are operating a deployment with a larger number of Redfish
+BMCs, it is advised that you do appropriately tune that number.
+This can be tuned via the API service configuration file,
+``[redfish]connection_cache_size``.
+
+Session Cache Expiration
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, sessions remain cached for as long as possible in
+memory, as long as they have not experienced an authentication,
+connection, or other unexplained error.
+
+Under normal circumstances, the sessions will only be rolled out
+of the cache in order of oldest first when the cache becomes full.
+There is no time based expiration to entries in the session cache.
+
+Of course, the cache is only in memory, and restarting the
+``ironic-conductor`` will also cause the cache to be rebuilt
+from scratch. If this is due to any persistent connectivity issue,
+this may be sign of an unexpected condition, and please consider
+contacting the Ironic developer community for assistance.
+
.. _Redfish: http://redfish.dmtf.org/
.. _Sushy: https://opendev.org/openstack/sushy
.. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security
diff --git a/doc/source/admin/drivers/snmp.rst b/doc/source/admin/drivers/snmp.rst
index 1c402ab9b..eed4ed794 100644
--- a/doc/source/admin/drivers/snmp.rst
+++ b/doc/source/admin/drivers/snmp.rst
@@ -22,39 +22,47 @@ this table could possibly work using a similar driver.
Please report any device status.
-============== ========== ========== =====================
-Manufacturer Model Supported? Driver name
-============== ========== ========== =====================
-APC AP7920 Yes apc_masterswitch
-APC AP9606 Yes apc_masterswitch
-APC AP9225 Yes apc_masterswitchplus
-APC AP7155 Yes apc_rackpdu
-APC AP7900 Yes apc_rackpdu
-APC AP7901 Yes apc_rackpdu
-APC AP7902 Yes apc_rackpdu
-APC AP7911a Yes apc_rackpdu
-APC AP7921 Yes apc_rackpdu
-APC AP7922 Yes apc_rackpdu
-APC AP7930 Yes apc_rackpdu
-APC AP7931 Yes apc_rackpdu
-APC AP7932 Yes apc_rackpdu
-APC AP7940 Yes apc_rackpdu
-APC AP7941 Yes apc_rackpdu
-APC AP7951 Yes apc_rackpdu
-APC AP7960 Yes apc_rackpdu
-APC AP7990 Yes apc_rackpdu
-APC AP7998 Yes apc_rackpdu
-APC AP8941 Yes apc_rackpdu
-APC AP8953 Yes apc_rackpdu
-APC AP8959 Yes apc_rackpdu
-APC AP8961 Yes apc_rackpdu
-APC AP8965 Yes apc_rackpdu
-Aten all? Yes aten
-CyberPower all? Untested cyberpower
-EatonPower all? Untested eatonpower
-Teltronix all? Yes teltronix
-BayTech MRP27 Yes baytech_mrp27
-============== ========== ========== =====================
+============== ============== ========== =====================
+Manufacturer Model Supported? Driver name
+============== ============== ========== =====================
+APC AP7920 Yes apc_masterswitch
+APC AP9606 Yes apc_masterswitch
+APC AP9225 Yes apc_masterswitchplus
+APC AP7155 Yes apc_rackpdu
+APC AP7900 Yes apc_rackpdu
+APC AP7901 Yes apc_rackpdu
+APC AP7902 Yes apc_rackpdu
+APC AP7911a Yes apc_rackpdu
+APC AP7921 Yes apc_rackpdu
+APC AP7922 Yes apc_rackpdu
+APC AP7930 Yes apc_rackpdu
+APC AP7931 Yes apc_rackpdu
+APC AP7932 Yes apc_rackpdu
+APC AP7940 Yes apc_rackpdu
+APC AP7941 Yes apc_rackpdu
+APC AP7951 Yes apc_rackpdu
+APC AP7960 Yes apc_rackpdu
+APC AP7990 Yes apc_rackpdu
+APC AP7998 Yes apc_rackpdu
+APC AP8941 Yes apc_rackpdu
+APC AP8953 Yes apc_rackpdu
+APC AP8959 Yes apc_rackpdu
+APC AP8961 Yes apc_rackpdu
+APC AP8965 Yes apc_rackpdu
+Aten all? Yes aten
+CyberPower all? Untested cyberpower
+EatonPower all? Untested eatonpower
+Teltronix all? Yes teltronix
+BayTech MRP27 Yes baytech_mrp27
+Raritan PX3-5547V-V2 Yes raritan_pdu2
+Raritan PX3-5726V Yes raritan_pdu2
+Raritan PX3-5776U-N2 Yes raritan_pdu2
+Raritan PX3-5969U-V2 Yes raritan_pdu2
+Raritan PX3-5961I2U-V2 Yes raritan_pdu2
+Vertiv NU30212 Yes vertivgeist_pdu
+ServerTech CW-16VE-P32M Yes servertech_sentry3
+ServerTech C2WG24SN Yes servertech_sentry4
+============== ============== ========== =====================
Software Requirements
diff --git a/driver-requirements.txt b/driver-requirements.txt
index bc12a07bb..3725c3ddf 100644
--- a/driver-requirements.txt
+++ b/driver-requirements.txt
@@ -4,7 +4,7 @@
# python projects they should package as optional dependencies for Ironic.
# These are available on pypi
-proliantutils>=2.13.0
+proliantutils>=2.14.0
pysnmp>=4.3.0,<5.0.0
python-scciclient>=0.12.2
python-dracclient>=5.1.0,<9.0.0
diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py
index 1849aaa7d..ec0719b75 100644
--- a/ironic/common/pxe_utils.py
+++ b/ironic/common/pxe_utils.py
@@ -1004,6 +1004,8 @@ def build_kickstart_config_options(task):
if node.driver_internal_info.get('is_source_a_path', False):
# Record a value so it matches as the template opts in.
params['is_source_a_path'] = 'true'
+ if CONF.anaconda.insecure_heartbeat:
+ params['insecure_heartbeat'] = 'true'
params['agent_token'] = node.driver_internal_info['agent_secret_token']
heartbeat_url = '%s/v1/heartbeat/%s' % (
deploy_utils.get_ironic_api_url().rstrip('/'),
diff --git a/ironic/conf/anaconda.py b/ironic/conf/anaconda.py
index 8ae3ab533..4f230ecdc 100644
--- a/ironic/conf/anaconda.py
+++ b/ironic/conf/anaconda.py
@@ -28,6 +28,17 @@ opts = [
help=_('kickstart template to use when no kickstart template '
'is specified in the instance_info or the glance OS '
'image.')),
+ cfg.BoolOpt('insecure_heartbeat',
+ default=False,
+ mutable=True,
+ help=_('Option to allow the kickstart configuration to be '
+ 'informed if SSL/TLS certificate verificaiton should '
+ 'be enforced, or not. This option exists largely to '
+ 'facilitate easy testing and use of the ``anaconda`` '
+ 'deployment interface. When this option is set, '
+ 'heartbeat operations, depending on the contents of '
+ 'the utilized kickstart template, may not enfore TLS '
+ 'certificate verification.')),
]
diff --git a/ironic/conf/deploy.py b/ironic/conf/deploy.py
index 6ae080c83..198e0ac95 100644
--- a/ironic/conf/deploy.py
+++ b/ironic/conf/deploy.py
@@ -133,9 +133,7 @@ opts = [
'to set an explicit value for this option, and if the '
'setting or default differs from nodes, to ensure that '
'nodes are configured specifically for their desired '
- 'boot mode. This option '
- 'only has effect when management interface supports '
- 'boot mode management') % {
+ 'boot mode.') % {
'bios': boot_modes.LEGACY_BIOS,
'uefi': boot_modes.UEFI}),
cfg.BoolOpt('configdrive_use_object_store',
diff --git a/ironic/conf/ilo.py b/ironic/conf/ilo.py
index 364c64c81..197378ce7 100644
--- a/ironic/conf/ilo.py
+++ b/ironic/conf/ilo.py
@@ -120,6 +120,11 @@ opts = [
'/proc/cmdline. Mind severe cmdline size limit! Can be '
'overridden by `instance_info/kernel_append_params` '
'property.')),
+ cfg.StrOpt('cert_path',
+ default='/var/lib/ironic/ilo/',
+ help=_('On the ironic-conductor node, directory where ilo '
+ 'driver stores the CSR and the cert.')),
+
]
diff --git a/ironic/db/sqlalchemy/__init__.py b/ironic/db/sqlalchemy/__init__.py
index 0f792361a..88ac079d0 100644
--- a/ironic/db/sqlalchemy/__init__.py
+++ b/ironic/db/sqlalchemy/__init__.py
@@ -13,4 +13,6 @@
from oslo_db.sqlalchemy import enginefacade
# NOTE(dtantsur): we want sqlite as close to a real database as possible.
-enginefacade.configure(sqlite_fk=True)
+# FIXME(stephenfin): we need to remove reliance on autocommit semantics ASAP
+# since it's not compatible with SQLAlchemy 2.0
+enginefacade.configure(sqlite_fk=True, __autocommit=True)
diff --git a/ironic/drivers/ilo.py b/ironic/drivers/ilo.py
index d8bbafb9e..b6e189ee9 100644
--- a/ironic/drivers/ilo.py
+++ b/ironic/drivers/ilo.py
@@ -1,3 +1,4 @@
+# Copyright 2022 Hewlett Packard Enterprise Development LP
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -67,7 +68,7 @@ class IloHardware(generic.GenericHardware):
@property
def supported_vendor_interfaces(self):
- """List of supported power interfaces."""
+ """List of supported vendor interfaces."""
return [vendor.VendorPassthru, noop.NoVendor]
diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py
index 2b5b8c0db..13f975c67 100644
--- a/ironic/drivers/modules/ilo/common.py
+++ b/ironic/drivers/modules/ilo/common.py
@@ -1,3 +1,4 @@
+# Copyright 2022 Hewlett Packard Enterprise Development LP
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -31,6 +32,7 @@ from ironic.common import boot_devices
from ironic.common import exception
from ironic.common.glance_service import service_utils
from ironic.common.i18n import _
+from ironic.common import image_service
from ironic.common import images
from ironic.common import swift
from ironic.common import utils
@@ -494,6 +496,26 @@ def update_ipmi_properties(task):
task.node.driver_info = info
+def update_redfish_properties(task):
+ """Update redfish properties to node driver_info
+
+ This method updates the node's driver info with redfish driver driver_info.
+ :param task: a task from TaskManager.
+ """
+ node = task.node
+ info = node.driver_info
+
+ # updating redfish credentials
+ info['redfish_address'] = info.get('ilo_address')
+ info['redfish_username'] = info.get('ilo_username')
+ info['redfish_password'] = info.get('ilo_password')
+ info['redfish_verify_ca'] = info.get('ilo_verify_ca')
+ info['redfish_system_id'] = '/redfish/v1/Systems/1'
+
+ # saving redfish credentials to task object
+ task.node.driver_info = info
+
+
def _get_floppy_image_name(node):
"""Returns the floppy image name for a given node.
@@ -1126,3 +1148,23 @@ def setup_uefi_https(task, iso, persistent=False):
except ilo_error.IloError as ilo_exception:
raise exception.IloOperationError(operation=operation,
error=ilo_exception)
+
+
+def download(target_file, file_url):
+ """Downloads file based on the scheme.
+
+ It downloads the file (url) to given location.
+ The supported url schemes are file, http, and https.
+ :param target_file: target file for copying the downloaded file.
+ :param file_url: source file url from where file needs to be downloaded.
+ :raises: ImageDownloadFailed, on failure to download the file.
+ """
+ parsed_url = urlparse.urlparse(file_url)
+ if parsed_url.scheme == "file":
+ src_file = parsed_url.path
+ with open(target_file, 'wb') as fd:
+ image_service.FileImageService().download(src_file, fd)
+ elif parsed_url.scheme in ('http', 'https'):
+ src_file = parsed_url.geturl()
+ with open(target_file, 'wb') as fd:
+ image_service.HttpImageService().download(src_file, fd)
diff --git a/ironic/drivers/modules/ilo/management.py b/ironic/drivers/modules/ilo/management.py
index c9a8259e6..5c4f03fb6 100644
--- a/ironic/drivers/modules/ilo/management.py
+++ b/ironic/drivers/modules/ilo/management.py
@@ -14,7 +14,8 @@
"""
iLO Management Interface
"""
-
+import os
+import shutil
from urllib import parse as urlparse
from ironic_lib import metrics_utils
@@ -79,6 +80,27 @@ _RESET_ILO_CREDENTIALS_ARGSINFO = {
}
}
+_CREATE_CSR_ARGSINFO = {
+ 'csr_params': {
+ 'description': (
+ "This arguments represents the information needed "
+ "to create the CSR certificate. The keys to be provided are "
+ "City, CommonName, OrgName, State."
+ ),
+ 'required': True
+ }
+}
+
+_ADD_HTTPS_CERT_ARGSINFO = {
+ 'cert_file': {
+ 'description': (
+ "This argument represents the path to the signed HTTPS "
+ "certificate which will be added to the iLO."
+ ),
+ 'required': True
+ }
+}
+
_SECURITY_PARAMETER_UPDATE_ARGSINFO = {
'security_parameters': {
'description': (
@@ -574,6 +596,61 @@ class IloManagement(base.ManagementInterface):
"parameter for node %(node)s is updated",
{'node': node.uuid})
+ @METRICS.timer('IloManagement.create_csr')
+ @base.clean_step(priority=0, abortable=False,
+ argsinfo=_CREATE_CSR_ARGSINFO)
+ def create_csr(self, task, **kwargs):
+ """Creates the CSR.
+
+ :param task: a TaskManager object.
+ """
+ node = task.node
+ csr_params = kwargs.get('csr_params')
+ csr_path = CONF.ilo.cert_path
+ path = os.path.join(csr_path, task.node.uuid)
+ if not os.path.exists(path):
+ os.makedirs(path, 0o755)
+
+ LOG.debug("Creating CSR for node %(node)s ..",
+ {'node': node.uuid})
+ _execute_ilo_step(node, 'create_csr', path, csr_params)
+ LOG.info("Creation of CSR for node %(node)s is "
+ "completed.", {'node': node.uuid})
+
+ @METRICS.timer('IloManagement.add_https_certificate')
+ @base.clean_step(priority=0, abortable=False,
+ argsinfo=_ADD_HTTPS_CERT_ARGSINFO)
+ def add_https_certificate(self, task, **kwargs):
+ """Adds the signed HTTPS certificate to the iLO.
+
+ :param task: a TaskManager object.
+ """
+ node = task.node
+ csr_path = CONF.ilo.cert_path
+ path = os.path.join(csr_path, task.node.uuid)
+ if not os.path.exists(path):
+ os.makedirs(path, 0o755)
+ cert_file_name = node.uuid + ".crt"
+ cert_file_path = os.path.join(path, cert_file_name)
+ cert_file = kwargs.get('cert_file')
+ url_scheme = urlparse.urlparse(cert_file).scheme
+ if url_scheme == '':
+ shutil.copy(cert_file, cert_file_path)
+ elif url_scheme in ('http', 'https', 'file'):
+ ilo_common.download(cert_file_path, cert_file)
+ else:
+ msg = (_("The url scheme %(scheme)s not supported with clean step "
+ "%(step)s") % {'scheme': url_scheme,
+ 'step': 'add_https_certificate'})
+ raise exception.IloOperationNotSupported(operation='clean step',
+ error=msg)
+
+ LOG.debug("Adding the signed HTTPS certificate to the "
+ "node %(node)s ..", {'node': node.uuid})
+ _execute_ilo_step(node, 'add_https_certificate', cert_file_path)
+ LOG.info("Adding of HTTPS certificate to the node %(node)s "
+ "is completed.", {'node': node.uuid})
+
@METRICS.timer('IloManagement.update_firmware')
@base.deploy_step(priority=0, argsinfo=_FIRMWARE_UPDATE_ARGSINFO)
@base.clean_step(priority=0, abortable=False,
diff --git a/ironic/drivers/modules/ilo/vendor.py b/ironic/drivers/modules/ilo/vendor.py
index 2f4986a2f..fa0400703 100644
--- a/ironic/drivers/modules/ilo/vendor.py
+++ b/ironic/drivers/modules/ilo/vendor.py
@@ -1,3 +1,4 @@
+# Copyright 2022 Hewlett Packard Enterprise Development LP
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -25,16 +26,14 @@ from ironic.conductor import utils as manager_utils
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.drivers.modules.redfish import vendor as redfish_vendor
METRICS = metrics_utils.get_metrics_logger(__name__)
-class VendorPassthru(base.VendorInterface):
+class VendorPassthru(redfish_vendor.RedfishVendorPassthru):
"""Vendor-specific interfaces for iLO deploy drivers."""
- def get_properties(self):
- return {}
-
@METRICS.timer('IloVendorPassthru.validate')
def validate(self, task, method, **kwargs):
"""Validate vendor-specific actions.
@@ -50,10 +49,26 @@ class VendorPassthru(base.VendorInterface):
passed.
:raises: InvalidParameterValue, if any of the parameters have invalid
value.
+ :raises: IloOperationNotSupported, if the driver does not support the
+ given operation with ilo vendor interface.
"""
if method == 'boot_into_iso':
self._validate_boot_into_iso(task, kwargs)
return
+ redfish_event_methods = ['create_subscription',
+ 'delete_subscription',
+ 'get_all_subscriptions', 'get_subscription']
+ if method in redfish_event_methods:
+ self._validate_is_it_a_supported_system(task)
+ ilo_common.parse_driver_info(task.node)
+ ilo_common.update_redfish_properties(task)
+ if method == 'eject_vmedia':
+ error_message = _(method + (
+ " can not be performed as the driver does not support "
+ "eject_vmedia through ilo vendor interface"))
+ raise exception.IloOperationNotSupported(operation=method,
+ error=error_message)
+
super(VendorPassthru, self).validate(task, method, **kwargs)
def _validate_boot_into_iso(self, task, kwargs):
@@ -99,3 +114,23 @@ class VendorPassthru(base.VendorInterface):
ilo_common.setup_vmedia(task, kwargs['boot_iso_href'],
ramdisk_options=None)
manager_utils.node_power_action(task, states.REBOOT)
+
+ def _validate_is_it_a_supported_system(self, task):
+ """Verify and raise an exception if it is not a supported system.
+
+ :param task: A TaskManager object.
+ :param kwargs: The arguments sent with vendor passthru.
+ :raises: IloOperationNotSupported, if the node is not a Gen10 or
+ Gen10 Plus system.
+ """
+
+ node = task.node
+ ilo_object = ilo_common.get_ilo_object(node)
+ product_name = ilo_object.get_product_name()
+ operation = _("Event methods")
+ error_message = _(operation + (
+ " can not be performed as the driver does not support Event "
+ "methods on the given node"))
+ if 'Gen10' not in product_name:
+ raise exception.IloOperationNotSupported(operation=operation,
+ error=error_message)
diff --git a/ironic/drivers/modules/ks.cfg.template b/ironic/drivers/modules/ks.cfg.template
index ca799953a..93788fdb8 100644
--- a/ironic/drivers/modules/ks.cfg.template
+++ b/ironic/drivers/modules/ks.cfg.template
@@ -36,11 +36,11 @@ liveimg --url {{ ks_options.liveimg_url }}
# Following %pre and %onerror sections are mandatory
%pre
-/usr/bin/curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "start", "agent_status_message": "Deployment starting. Running pre-installation scripts."}' {{ ks_options.heartbeat_url }}
+/usr/bin/curl {% if 'insecure_heartbeat' in ks_options %}--insecure{% endif %} -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "start", "agent_status_message": "Deployment starting. Running pre-installation scripts."}' {{ ks_options.heartbeat_url }}
%end
%onerror
-/usr/bin/curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "error", "agent_status_message": "Error: Deploying using anaconda. Check console for more information."}' {{ ks_options.heartbeat_url }}
+/usr/bin/curl {% if 'insecure_heartbeat' in ks_options %}--insecure{% endif %} -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "error", "agent_status_message": "Error: Deploying using anaconda. Check console for more information."}' {{ ks_options.heartbeat_url }}
%end
# Config-drive information, if any.
@@ -54,5 +54,5 @@ liveimg --url {{ ks_options.liveimg_url }}
# before rebooting.
%post
sync
-/usr/bin/curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "end", "agent_status_message": "Deployment completed successfully."}' {{ ks_options.heartbeat_url }}
+/usr/bin/curl {% if 'insecure_heartbeat' in ks_options %}--insecure{% endif %} -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "end", "agent_status_message": "Deployment completed successfully."}' {{ ks_options.heartbeat_url }}
%end
diff --git a/ironic/drivers/modules/redfish/utils.py b/ironic/drivers/modules/redfish/utils.py
index 40cf33bce..e85e2ec6a 100644
--- a/ironic/drivers/modules/redfish/utils.py
+++ b/ironic/drivers/modules/redfish/utils.py
@@ -15,6 +15,7 @@
# under the License.
import collections
+import hashlib
import os
from urllib import parse as urlparse
@@ -198,43 +199,59 @@ class SessionCache(object):
_sessions = collections.OrderedDict()
def __init__(self, driver_info):
+ # Hash the password in the data structure, so we can
+ # include it in the session key.
+ # NOTE(TheJulia): Multiplying the address by 4, to ensure
+ # we meet a minimum of 16 bytes for salt.
+ pw_hash = hashlib.pbkdf2_hmac(
+ 'sha512',
+ driver_info.get('password').encode('utf-8'),
+ str(driver_info.get('address') * 4).encode('utf-8'), 40)
self._driver_info = driver_info
+ # Assemble the session key and append the hashed password to it,
+ # which forces new sessions to be established when the saved password
+ # is changed, just like the username, or address.
self._session_key = tuple(
self._driver_info.get(key)
for key in ('address', 'username', 'verify_ca')
- )
+ ) + (pw_hash.hex(),)
def __enter__(self):
try:
return self.__class__._sessions[self._session_key]
-
except KeyError:
- auth_type = self._driver_info['auth_type']
+ LOG.debug('A cached redfish session for Redfish endpoint '
+ '%(endpoint)s was not detected, initiating a session.',
+ {'endpoint': self._driver_info['address']})
- auth_class = self.AUTH_CLASSES[auth_type]
+ auth_type = self._driver_info['auth_type']
- authenticator = auth_class(
- username=self._driver_info['username'],
- password=self._driver_info['password']
- )
+ auth_class = self.AUTH_CLASSES[auth_type]
- sushy_params = {'verify': self._driver_info['verify_ca'],
- 'auth': authenticator}
- if 'root_prefix' in self._driver_info:
- sushy_params['root_prefix'] = self._driver_info['root_prefix']
- conn = sushy.Sushy(
- self._driver_info['address'],
- **sushy_params
- )
+ authenticator = auth_class(
+ username=self._driver_info['username'],
+ password=self._driver_info['password']
+ )
+
+ sushy_params = {'verify': self._driver_info['verify_ca'],
+ 'auth': authenticator}
+ if 'root_prefix' in self._driver_info:
+ sushy_params['root_prefix'] = self._driver_info['root_prefix']
+ conn = sushy.Sushy(
+ self._driver_info['address'],
+ **sushy_params
+ )
- if CONF.redfish.connection_cache_size:
- self.__class__._sessions[self._session_key] = conn
+ if CONF.redfish.connection_cache_size:
+ self.__class__._sessions[self._session_key] = conn
+ # Save a secure hash of the password into memory, so if we
+ # observe it change, we can detect the session is no longer valid.
- if (len(self.__class__._sessions)
- > CONF.redfish.connection_cache_size):
- self._expire_oldest_session()
+ if (len(self.__class__._sessions)
+ > CONF.redfish.connection_cache_size):
+ self._expire_oldest_session()
- return conn
+ return conn
def __exit__(self, exc_type, exc_val, exc_tb):
# NOTE(etingof): perhaps this session token is no good
diff --git a/ironic/drivers/modules/snmp.py b/ironic/drivers/modules/snmp.py
index 4e700c6f8..d544d5687 100644
--- a/ironic/drivers/modules/snmp.py
+++ b/ironic/drivers/modules/snmp.py
@@ -799,6 +799,341 @@ class SNMPDriverBaytechMRP27(SNMPDriverSimple):
value_power_on = 1
+class SNMPDriverServerTechSentry3(SNMPDriverBase):
+ """SNMP driver class for Server Technology Sentry 3 PDUs.
+
+ ftp://ftp.servertech.com/Pub/SNMP/sentry3/Sentry3.mib
+
+ SNMP objects for Server Technology Power PDU.
+ 1.3.6.1.4.1.1718.3.2.3.1.5.1.1.<outlet ID> outletStatus
+ Read 0=off, 1=on, 2=off wait, 3=on wait, [...more options follow]
+ 1.3.6.1.4.1.1718.3.2.3.1.11.1.1.<outlet ID> outletControlAction
+ Write 0=no action, 1=on, 2=off, 3=reboot
+ """
+
+ oid_device = (1718, 3, 2, 3, 1)
+ oid_tower_infeed_idx = (1, 1, )
+ oid_power_status = (5,)
+ oid_power_action = (11,)
+
+ status_off = 0
+ status_on = 1
+ status_off_wait = 2
+ status_on_wait = 3
+
+ value_power_on = 1
+ value_power_off = 2
+
+ def __init__(self, *args, **kwargs):
+ super(SNMPDriverServerTechSentry3, self).__init__(*args, **kwargs)
+ # Due to its use of different OIDs for different actions, we only form
+ # an OID that holds the common substring of the OIDs for power
+ # operations.
+ self.oid_base = self.oid_enterprise + self.oid_device
+
+ def _snmp_oid(self, oid):
+ """Return the OID for one of the outlet control objects.
+
+ :param oid: The action-dependent portion of the OID, as a tuple of
+ integers.
+ :returns: The full OID as a tuple of integers.
+ """
+
+ outlet = self.snmp_info['outlet']
+ full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,)
+ return full_oid
+
+ def _snmp_power_state(self):
+ oid = self._snmp_oid(self.oid_power_status)
+ state = self.client.get(oid)
+
+ # Translate the state to an Ironic power state.
+ if state in (self.status_on, self.status_off_wait):
+ power_state = states.POWER_ON
+ elif state in (self.status_off, self.status_on_wait):
+ power_state = states.POWER_OFF
+ else:
+ LOG.warning("SeverTech Sentry3 PDU %(addr)s oid %(oid) outlet "
+ "%(outlet)s: unrecognised power state %(state)s.",
+ {'addr': self.snmp_info['address'],
+ 'oid': oid,
+ 'outlet': self.snmp_info['outlet'],
+ 'state': state})
+ power_state = states.ERROR
+
+ return power_state
+
+ def _snmp_power_on(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_on)
+ self.client.set(oid, value)
+
+ def _snmp_power_off(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_off)
+ self.client.set(oid, value)
+
+
+class SNMPDriverServerTechSentry4(SNMPDriverBase):
+ """SNMP driver class for Server Technology Sentry 4 PDUs.
+
+ https://www.servertech.com/support/sentry-mib-oid-tree-downloads
+
+ SNMP objects for Server Technology Power PDU.
+ 1.3.6.1.4.1.1718.4.1.8.5.1.1<outlet ID> outletStatus
+ notSet (0) fixedOn (1) idleOff (2) idleOn (3) [...more options follow]
+ pendOn (8) pendOff (9) off (10) on (11) [...more options follow]
+ eventOff (16) eventOn (17) eventReboot (18) eventShutdown (19)
+ 1.3.6.1.4.1.1718.4.1.8.5.1.2.<outlet ID> outletControlAction
+ Write 0=no action, 1=on, 2=off, 3=reboot
+ """
+
+ oid_device = (1718, 4, 1, 8, 5, 1)
+ oid_tower_infeed_idx = (1, 1, )
+ oid_power_status = (1,)
+ oid_power_action = (2,)
+
+ notSet = 0
+ fixedOn = 1
+ idleOff = 2
+ idleOn = 3
+ wakeOff = 4
+ wakeOn = 5
+ ocpOff = 6
+ ocpOn = 7
+ status_pendOn = 8
+ status_pendOff = 9
+ status_off = 10
+ status_on = 11
+ reboot = 12
+ shutdown = 13
+ lockedOff = 14
+ lockedOn = 15
+
+ value_power_on = 1
+ value_power_off = 2
+
+ def __init__(self, *args, **kwargs):
+ super(SNMPDriverServerTechSentry4, self).__init__(*args, **kwargs)
+ # Due to its use of different OIDs for different actions, we only form
+ # an OID that holds the common substring of the OIDs for power
+ # operations.
+ self.oid_base = self.oid_enterprise + self.oid_device
+
+ def _snmp_oid(self, oid):
+ """Return the OID for one of the outlet control objects.
+
+ :param oid: The action-dependent portion of the OID, as a tuple of
+ integers.
+ :returns: The full OID as a tuple of integers.
+ """
+
+ outlet = self.snmp_info['outlet']
+ full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,)
+ return full_oid
+
+ def _snmp_power_state(self):
+ oid = self._snmp_oid(self.oid_power_status)
+ state = self.client.get(oid)
+
+ # Translate the state to an Ironic power state.
+ if state in (self.status_on, self.status_pendOn, self.idleOn):
+ power_state = states.POWER_ON
+ elif state in (self.status_off, self.status_pendOff):
+ power_state = states.POWER_OFF
+ else:
+ LOG.warning("ServerTech Sentry4 PDU %(addr)s oid %(oid)s outlet "
+ "%(outlet)s: unrecognised power state %(state)s.",
+ {'addr': self.snmp_info['address'],
+ 'oid': oid,
+ 'outlet': self.snmp_info['outlet'],
+ 'state': state})
+ power_state = states.ERROR
+
+ return power_state
+
+ def _snmp_power_on(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_on)
+ self.client.set(oid, value)
+
+ def _snmp_power_off(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_off)
+ self.client.set(oid, value)
+
+
+class SNMPDriverRaritanPDU2(SNMPDriverBase):
+ """SNMP driver class for Raritan PDU2 PDUs.
+
+ http://support.raritan.com/px2/version-2.4.1/mibs/pdu2-mib-020400-39592.txt
+ http://cdn.raritan.com/download/PX/v1.5.20/PDU-MIB.txt
+
+ Command:
+ snmpset -v2c -c private -m+PDU2-MIB <pdu IP address> \
+ PDU2-MIB::switchingOperation.1.4 = cycle
+ snmpset -v2c -c private <pdu IP address> \
+ .1.3.6.1.4.1.13742.6.4.1.2.1.2.1.4 i 2
+ Output:
+ PDU2-MIB::switchingOperation.1.4 = INTEGER: cycle(2)
+ """
+
+ oid_device = (13742, 6, 4, 1, 2, 1)
+ oid_power_action = (2, )
+ oid_power_status = (3, )
+ oid_tower_infeed_idx = (1, )
+
+ unavailable = -1
+ status_open = 0
+ status_closed = 1
+ belowLowerCritical = 2
+ belowLowerWarning = 3
+ status_normal = 4
+ aboveUpperWarning = 5
+ aboveUpperCritical = 6
+ status_on = 7
+ status_off = 8
+ detected = 9
+ notDetected = 10
+ alarmed = 11
+ ok = 12
+ marginal = 13
+ fail = 14
+ yes = 15
+ no = 16
+ standby = 17
+ one = 18
+ two = 19
+ inSync = 20
+ outOfSync = 21
+
+ value_power_on = 1
+ value_power_off = 0
+
+ def __init__(self, *args, **kwargs):
+ super(SNMPDriverRaritanPDU2, self).__init__(*args, **kwargs)
+ # Due to its use of different OIDs for different actions, we only form
+ # an OID that holds the common substring of the OIDs for power
+ # operations.
+ self.oid_base = self.oid_enterprise + self.oid_device
+
+ def _snmp_oid(self, oid):
+ """Return the OID for one of the outlet control objects.
+
+ :param oid: The action-dependent portion of the OID, as a tuple of
+ integers.
+ :returns: The full OID as a tuple of integers.
+ """
+
+ outlet = self.snmp_info['outlet']
+ full_oid = self.oid_base + oid + self.oid_tower_infeed_idx + (outlet,)
+ return full_oid
+
+ def _snmp_power_state(self):
+ oid = self._snmp_oid(self.oid_power_status)
+ state = self.client.get(oid)
+
+ # Translate the state to an Ironic power state.
+ if state == self.status_on:
+ power_state = states.POWER_ON
+ elif state == self.status_off:
+ power_state = states.POWER_OFF
+ else:
+ LOG.warning("Raritan PDU2 PDU %(addr)s oid %(oid)s outlet "
+ "%(outlet)s: unrecognised power state %(state)s.",
+ {'addr': self.snmp_info['address'],
+ 'oid': oid,
+ 'outlet': self.snmp_info['outlet'],
+ 'state': state})
+ power_state = states.ERROR
+
+ return power_state
+
+ def _snmp_power_on(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_on)
+ self.client.set(oid, value)
+
+ def _snmp_power_off(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_off)
+ self.client.set(oid, value)
+
+
+class SNMPDriverVertivGeistPDU(SNMPDriverBase):
+ """SNMP driver class for VertivGeist NU30017L/NU30019L PDU.
+
+ https://mibs.observium.org/mib/GEIST-V5-MIB/
+
+ """
+
+ oid_device = (21239, 5, 2, 3, 5, 1)
+ oid_power_action = (6, )
+ oid_power_status = (4, )
+ oid_tower_infeed_idx = (1, )
+
+ on = 1
+ off = 2
+ on2off = 3
+ off2on = 4
+ rebootOn = 5
+ rebootOff = 5
+ unavailable = 7
+
+ value_power_on = 2
+ value_power_off = 4
+
+ def __init__(self, *args, **kwargs):
+ super(SNMPDriverVertivGeistPDU, self).__init__(*args, **kwargs)
+ # Due to its use of different OIDs for different actions, we only form
+ # an OID that holds the common substring of the OIDs for power
+ # operations.
+ self.oid_base = self.oid_enterprise + self.oid_device
+
+ def _snmp_oid(self, oid):
+ """Return the OID for one of the outlet control objects.
+
+ :param oid: The action-dependent portion of the OID, as a tuple of
+ integers.
+
+ :returns: The full OID as a tuple of integers.
+ """
+
+ outlet = self.snmp_info['outlet']
+ full_oid = self.oid_base + oid + (outlet,)
+ return full_oid
+
+ def _snmp_power_state(self):
+ oid = self._snmp_oid(self.oid_power_status)
+ state = self.client.get(oid)
+
+ # Translate the state to an Ironic power state.
+ if state in (self.on, self.on2off):
+ power_state = states.POWER_ON
+ elif state in (self.off, self.off2on):
+ power_state = states.POWER_OFF
+ else:
+ LOG.warning("Vertiv Geist PDU %(addr)s oid %(oid)s outlet "
+ "%(outlet)s: unrecognised power state %(state)s.",
+ {'addr': self.snmp_info['address'],
+ 'oid': oid,
+ 'outlet': self.snmp_info['outlet'],
+ 'state': state})
+ power_state = states.ERROR
+
+ return power_state
+
+ def _snmp_power_on(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_on)
+ self.client.set(oid, value)
+
+ def _snmp_power_off(self):
+ oid = self._snmp_oid(self.oid_power_action)
+ value = snmp.Integer(self.value_power_off)
+ self.client.set(oid, value)
+
+
class SNMPDriverAuto(SNMPDriverBase):
SYS_OBJ_OID = (1, 3, 6, 1, 2, 1, 1, 2)
@@ -878,6 +1213,10 @@ DRIVER_CLASSES = {
'eatonpower': SNMPDriverEatonPower,
'teltronix': SNMPDriverTeltronix,
'baytech_mrp27': SNMPDriverBaytechMRP27,
+ 'servertech_sentry3': SNMPDriverServerTechSentry3,
+ 'servertech_sentry4': SNMPDriverServerTechSentry4,
+ 'raritan_pdu2': SNMPDriverRaritanPDU2,
+ 'vertivgeist_pdu': SNMPDriverVertivGeistPDU,
'auto': SNMPDriverAuto,
}
diff --git a/ironic/tests/unit/common/test_pxe_utils.py b/ironic/tests/unit/common/test_pxe_utils.py
index f9d781830..3e57b83ed 100644
--- a/ironic/tests/unit/common/test_pxe_utils.py
+++ b/ironic/tests/unit/common/test_pxe_utils.py
@@ -1628,6 +1628,26 @@ class PXEBuildKickstartConfigOptionsTestCase(db_base.DbTestCase):
params = pxe_utils.build_kickstart_config_options(task)
self.assertTrue(params['ks_options'].pop('agent_token'))
self.assertEqual(expected, params['ks_options'])
+ self.assertNotIn('insecure_heartbeat', params)
+
+ @mock.patch.object(deploy_utils, 'get_ironic_api_url', autospec=True)
+ def test_build_kickstart_config_options_pxe_insecure_heartbeat(
+ self, api_url_mock):
+ api_url_mock.return_value = 'http://ironic-api'
+ self.assertFalse(CONF.anaconda.insecure_heartbeat)
+ CONF.set_override('insecure_heartbeat', True, 'anaconda')
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ expected = {}
+ expected['liveimg_url'] = task.node.instance_info['image_url']
+ expected['config_drive'] = ''
+ expected['heartbeat_url'] = (
+ 'http://ironic-api/v1/heartbeat/%s' % task.node.uuid
+ )
+ expected['insecure_heartbeat'] = 'true'
+ params = pxe_utils.build_kickstart_config_options(task)
+ self.assertTrue(params['ks_options'].pop('agent_token'))
+ self.assertEqual(expected, params['ks_options'])
@mock.patch('ironic.common.utils.render_template', autospec=True)
def test_prepare_instance_kickstart_config_not_anaconda_boot(self,
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_common.py b/ironic/tests/unit/drivers/modules/ilo/test_common.py
index 352eb0837..c3e22453f 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_common.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_common.py
@@ -1,3 +1,4 @@
+# Copyright 2022 Hewlett Packard Enterprise Development LP
# Copyright 2014 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
@@ -30,6 +31,7 @@ from oslo_utils import uuidutils
from ironic.common import boot_devices
from ironic.common import exception
+from ironic.common import image_service
from ironic.common import images
from ironic.common import swift
from ironic.conductor import task_manager
@@ -374,6 +376,22 @@ class IloCommonMethodsTestCase(BaseIloTest):
expected_info = dict(self.info, **ipmi_info)
self.assertEqual(expected_info, actual_info)
+ def test_update_redfish_properties(self):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ redfish_info = {
+ "redfish_address": "1.2.3.4",
+ "redfish_username": "admin",
+ "redfish_password": "fake",
+ "redfish_verify_ca": None,
+ "redfish_system_id": "/redfish/v1/Systems/1"
+ }
+ task.node.driver_info = self.info
+ ilo_common.update_redfish_properties(task)
+ actual_info = task.node.driver_info
+ expected_info = dict(self.info, **redfish_info)
+ self.assertEqual(expected_info, actual_info)
+
def test__get_floppy_image_name(self):
image_name_expected = 'image-' + self.node.uuid
image_name_actual = ilo_common._get_floppy_image_name(self.node)
@@ -1504,3 +1522,37 @@ class IloCommonMethodsTestCase(BaseIloTest):
self.assertRaises(exception.IloOperationError,
ilo_common.setup_uefi_https,
task, iso, True)
+
+ @mock.patch.object(image_service, 'FileImageService', spec_set=True,
+ autospec=True)
+ @mock.patch.object(image_service, 'HttpImageService', spec_set=True,
+ autospec=True)
+ @mock.patch.object(builtins, 'open', autospec=True)
+ def test_download_file_url(self, open_mock, http_mock, file_mock):
+ url = "file:///test1/iLO.crt"
+ target_file = "/a/b/c"
+ fd_mock = mock.MagicMock(spec=io.BytesIO)
+ open_mock.return_value = fd_mock
+ fd_mock.__enter__.return_value = fd_mock
+ ilo_common.download(target_file, url)
+ open_mock.assert_called_once_with(target_file, 'wb')
+ http_mock.assert_not_called()
+ file_mock.return_value.download.assert_called_once_with(
+ "/test1/iLO.crt", fd_mock)
+
+ @mock.patch.object(image_service, 'FileImageService', spec_set=True,
+ autospec=True)
+ @mock.patch.object(image_service, 'HttpImageService', spec_set=True,
+ autospec=True)
+ @mock.patch.object(builtins, 'open', autospec=True)
+ def test_download_http_url(self, open_mock, http_mock, file_mock):
+ url = "http://1.1.1.1/iLO.crt"
+ target_file = "/a/b/c"
+ fd_mock = mock.MagicMock(spec=io.BytesIO)
+ open_mock.return_value = fd_mock
+ fd_mock.__enter__.return_value = fd_mock
+ ilo_common.download(target_file, url)
+ http_mock.return_value.download.assert_called_once_with(
+ "http://1.1.1.1/iLO.crt", fd_mock)
+ file_mock.assert_not_called()
+ open_mock.assert_called_once_with(target_file, 'wb')
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_management.py b/ironic/tests/unit/drivers/modules/ilo/test_management.py
index e4d891c3d..f087c4d58 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_management.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_management.py
@@ -14,9 +14,12 @@
"""Test class for Management Interface used by iLO modules."""
+import os
+import shutil
from unittest import mock
import ddt
+from oslo_config import cfg
from oslo_utils import importutils
from oslo_utils import uuidutils
@@ -42,6 +45,8 @@ ilo_error = importutils.try_import('proliantutils.exception')
INFO_DICT = db_utils.get_test_ilo_info()
+CONF = cfg.CONF
+
@ddt.ddt
class IloManagementTestCase(test_common.BaseIloTest):
@@ -424,6 +429,116 @@ class IloManagementTestCase(test_common.BaseIloTest):
step_mock.assert_called_once_with(
task.node, 'update_authentication_failure_logging', '1', False)
+ @mock.patch.object(ilo_management, '_execute_ilo_step',
+ spec_set=True, autospec=True)
+ @mock.patch.object(os, 'makedirs', spec_set=True, autospec=True)
+ def test_create_csr(self, os_mock, step_mock):
+ csr_params_args = {
+ "City": "Bangalore",
+ "CommonName": "1.1.1.1",
+ "Country": "ABC",
+ "OrgName": "DEF",
+ "State": "IJK"
+ }
+ csr_args = {
+ "csr_params": csr_params_args}
+ CONF.ilo.cert_path = "/var/lib/ironic/ilo"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.management.create_csr(task, **csr_args)
+ cert_path = os.path.join(CONF.ilo.cert_path, self.node.uuid)
+ step_mock.assert_called_once_with(task.node, 'create_csr',
+ cert_path, csr_params_args)
+ os_mock.assert_called_once_with(cert_path, 0o755)
+
+ @mock.patch.object(ilo_management, '_execute_ilo_step',
+ spec_set=True, autospec=True)
+ @mock.patch.object(os, 'makedirs', spec_set=True, autospec=True)
+ @mock.patch.object(shutil, 'copy', spec_set=True, autospec=True)
+ def test_add_https_certificate(self, shutil_mock, os_mock,
+ step_mock):
+ CONF.ilo.cert_path = "/var/lib/ironic/ilo"
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ cert_file_args = {'cert_file': '/test1/cert'}
+ task.driver.management.add_https_certificate(
+ task, **cert_file_args)
+ cert_path = os.path.join(CONF.ilo.cert_path, self.node.uuid)
+ cert_path_name = os.path.join(cert_path, self.node.uuid)
+ filename = cert_path_name + ".crt"
+ step_mock.assert_called_once_with(
+ task.node, 'add_https_certificate', filename)
+ os_mock.assert_called_once_with(cert_path, 0o755)
+ shutil_mock.assert_called_once_with('/test1/cert', filename)
+
+ @mock.patch.object(ilo_management, '_execute_ilo_step',
+ spec_set=True, autospec=True)
+ @mock.patch.object(os, 'makedirs', spec_set=True, autospec=True)
+ @mock.patch.object(shutil, 'copy', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'download', spec_set=True, autospec=True)
+ def test_add_https_certificate_fileurl(self, download_mock, shutil_mock,
+ os_mock, step_mock):
+ CONF.ilo.cert_path = "/var/lib/ironic/ilo"
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ cert_file_args = {'cert_file': 'file:///test1/cert'}
+ task.driver.management.add_https_certificate(
+ task, **cert_file_args)
+ cert_path = os.path.join(CONF.ilo.cert_path, self.node.uuid)
+ cert_path_name = os.path.join(cert_path, self.node.uuid)
+ fname = cert_path_name + ".crt"
+ step_mock.assert_called_once_with(
+ task.node, 'add_https_certificate', fname)
+ os_mock.assert_called_once_with(cert_path, 0o755)
+ shutil_mock.assert_not_called()
+ download_mock.assert_called_once_with(fname, 'file:///test1/cert')
+
+ @mock.patch.object(ilo_management, '_execute_ilo_step',
+ spec_set=True, autospec=True)
+ @mock.patch.object(os, 'makedirs', spec_set=True, autospec=True)
+ @mock.patch.object(shutil, 'copy', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'download', spec_set=True, autospec=True)
+ def test_add_https_certificate_httpurl(self, download_mock, shutil_mock,
+ os_mock, step_mock):
+ CONF.ilo.cert_path = "/var/lib/ironic/ilo"
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ cert_file_args = {'cert_file': 'http://1.1.1.1/cert'}
+ task.driver.management.add_https_certificate(
+ task, **cert_file_args)
+ cert_path = os.path.join(CONF.ilo.cert_path, self.node.uuid)
+ cert_path_name = os.path.join(cert_path, self.node.uuid)
+ fname = cert_path_name + ".crt"
+ step_mock.assert_called_once_with(
+ task.node, 'add_https_certificate', fname)
+ os_mock.assert_called_once_with(cert_path, 0o755)
+ shutil_mock.assert_not_called()
+ download_mock.assert_called_once_with(fname, 'http://1.1.1.1/cert')
+
+ @mock.patch.object(ilo_management, '_execute_ilo_step',
+ spec_set=True, autospec=True)
+ @mock.patch.object(os, 'makedirs', spec_set=True, autospec=True)
+ @mock.patch.object(shutil, 'copy', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'download', spec_set=True, autospec=True)
+ def test_add_https_certificate_url_exception(self, download_mock,
+ shutil_mock, os_mock,
+ step_mock):
+ CONF.ilo.cert_path = "/var/lib/ironic/ilo"
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ cert_file_args = {'cert_file': 'swift://1.1.1.1/cert'}
+ self.assertRaises(exception.IloOperationNotSupported,
+ task.driver.management.add_https_certificate,
+ task,
+ **cert_file_args)
+
+ cert_path = os.path.join(CONF.ilo.cert_path, self.node.uuid)
+ step_mock.assert_not_called()
+ os_mock.assert_called_once_with(cert_path, 0o755)
+ shutil_mock.assert_not_called()
+ download_mock.assert_not_called()
+
@mock.patch.object(deploy_utils, 'build_agent_options',
spec_set=True, autospec=True)
@mock.patch.object(ilo_boot.IloVirtualMediaBoot, 'clean_up_ramdisk',
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_vendor.py b/ironic/tests/unit/drivers/modules/ilo/test_vendor.py
index f3114826e..b7bc3cbce 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_vendor.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_vendor.py
@@ -1,3 +1,4 @@
+# Copyright 2022 Hewlett Packard Enterprise Development LP
# Copyright 2015 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
@@ -30,6 +31,7 @@ from ironic.tests.unit.drivers.modules.ilo import test_common
class VendorPassthruTestCase(test_common.BaseIloTest):
boot_interface = 'ilo-virtual-media'
+ vendor_interface = 'ilo'
@mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
autospec=True)
@@ -95,3 +97,72 @@ class VendorPassthruTestCase(test_common.BaseIloTest):
task, info)
validate_image_prop_mock.assert_called_once_with(
task.context, 'foo')
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test__validate_is_it_a_supported_system(
+ self, get_ilo_object_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.maintenance = True
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_product_name.return_value = (
+ 'ProLiant DL380 Gen10')
+ task.driver.vendor._validate_is_it_a_supported_system(task)
+ get_ilo_object_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test__validate_is_it_a_supported_system_exception(
+ self, get_ilo_object_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.maintenance = True
+ ilo_mock_object = get_ilo_object_mock.return_value
+ ilo_mock_object.get_product_name.return_value = (
+ 'ProLiant DL380 Gen8')
+ self.assertRaises(
+ exception.IloOperationNotSupported,
+ task.driver.vendor._validate_is_it_a_supported_system, task)
+
+ @mock.patch.object(ilo_common, 'parse_driver_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_common, 'update_redfish_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_vendor.VendorPassthru,
+ '_validate_is_it_a_supported_system',
+ spec_set=True, autospec=True)
+ def test_validate_create_subscription(self, validate_redfish_system_mock,
+ redfish_properties_mock,
+ driver_info_mock):
+ self.node.vendor_interface = 'ilo'
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ d_info = {'ilo_address': '1.1.1.1',
+ 'ilo_username': 'user',
+ 'ilo_password': 'password',
+ 'ilo_verify_ca': False}
+ driver_info_mock.return_value = d_info
+ redfish_properties = {'redfish_address': '1.1.1.1',
+ 'redfish_username': 'user',
+ 'redfish_password': 'password',
+ 'redfish_system_id': '/redfish/v1/Systems/1',
+ 'redfish_verify_ca': False}
+ redfish_properties_mock.return_value = redfish_properties
+ kwargs = {'Destination': 'https://someulr',
+ 'Context': 'MyProtocol'}
+ task.driver.vendor.validate(task, 'create_subscription', **kwargs)
+ driver_info_mock.assert_called_once_with(task.node)
+ redfish_properties_mock.assert_called_once_with(task)
+ validate_redfish_system_mock.assert_called_once_with(
+ task.driver.vendor, task)
+
+ def test_validate_operation_exeption(self):
+ self.node.vendor_interface = 'ilo'
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(
+ exception.IloOperationNotSupported,
+ task.driver.vendor.validate, task, 'eject_vmedia')
diff --git a/ironic/tests/unit/drivers/modules/redfish/test_utils.py b/ironic/tests/unit/drivers/modules/redfish/test_utils.py
index ca8aba9da..01b7089c7 100644
--- a/ironic/tests/unit/drivers/modules/redfish/test_utils.py
+++ b/ironic/tests/unit/drivers/modules/redfish/test_utils.py
@@ -252,6 +252,7 @@ class RedfishUtilsAuthTestCase(db_base.DbTestCase):
redfish_utils.get_system(self.node)
redfish_utils.get_system(self.node)
self.assertEqual(1, mock_sushy.call_count)
+ self.assertEqual(len(redfish_utils.SessionCache._sessions), 1)
@mock.patch.object(sushy, 'Sushy', autospec=True)
def test_ensure_new_session_address(self, mock_sushy):
@@ -270,6 +271,21 @@ class RedfishUtilsAuthTestCase(db_base.DbTestCase):
self.assertEqual(2, mock_sushy.call_count)
@mock.patch.object(sushy, 'Sushy', autospec=True)
+ def test_ensure_new_session_password(self, mock_sushy):
+ d_info = self.node.driver_info
+ d_info['redfish_username'] = 'foo'
+ d_info['redfish_password'] = 'bar'
+ self.node.driver_info = d_info
+ self.node.save()
+ redfish_utils.get_system(self.node)
+ d_info['redfish_password'] = 'foo'
+ self.node.driver_info = d_info
+ self.node.save()
+ redfish_utils.SessionCache._sessions = collections.OrderedDict()
+ redfish_utils.get_system(self.node)
+ self.assertEqual(2, mock_sushy.call_count)
+
+ @mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.'
'SessionCache.AUTH_CLASSES', autospec=True)
@mock.patch('ironic.drivers.modules.redfish.utils.SessionCache._sessions',
diff --git a/ironic/tests/unit/drivers/modules/test_snmp.py b/ironic/tests/unit/drivers/modules/test_snmp.py
index 6bdd2da5a..00799dc4d 100644
--- a/ironic/tests/unit/drivers/modules/test_snmp.py
+++ b/ironic/tests/unit/drivers/modules/test_snmp.py
@@ -327,6 +327,34 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase):
info = snmp._parse_driver_info(node)
self.assertEqual('teltronix', info['driver'])
+ def test__parse_driver_info_servertech_sentry3(self):
+ # Make sure the servertech_sentry3 driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='servertech_sentry3')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('servertech_sentry3', info['driver'])
+
+ def test__parse_driver_info_servertech_sentry4(self):
+ # Make sure the servertech_sentry4 driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='servertech_sentry4')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('servertech_sentry4', info['driver'])
+
+ def test__parse_driver_info_raritan_pdu2(self):
+ # Make sure the raritan_pdu2 driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='raritan_pdu2')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('raritan_pdu2', info['driver'])
+
+ def test__parse_driver_info_vertivgeist_pdu(self):
+ # Make sure the vertivgeist_pdu driver type is parsed.
+ info = db_utils.get_test_snmp_info(snmp_driver='vertivgeist_pdu')
+ node = self._get_test_node(info)
+ info = snmp._parse_driver_info(node)
+ self.assertEqual('vertivgeist_pdu', info['driver'])
+
def test__parse_driver_info_snmp_v1(self):
# Make sure SNMPv1 is parsed with a community string.
info = db_utils.get_test_snmp_info(snmp_version='1',
@@ -1260,6 +1288,58 @@ class SNMPDeviceDriverTestCase(db_base.DbTestCase):
def test_apc_rackpdu_power_reset(self, mock_get_client):
self._test_simple_device_power_reset('apc_rackpdu', mock_get_client)
+ def test_raritan_pdu2_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the
+ # Raritan PDU2 driver
+ self._update_driver_info(snmp_driver="raritan_pdu2",
+ snmp_outlet="6")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 13742, 6, 4, 1, 2, 1, 2, 1, 6)
+ action = (2,)
+
+ self.assertEqual(oid, driver._snmp_oid(action))
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(0, driver.value_power_off)
+
+ def test_servertech_sentry3_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the
+ # ServerTech Sentry3 driver
+ self._update_driver_info(snmp_driver="servertech_sentry3",
+ snmp_outlet="6")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 1718, 3, 2, 3, 1, 5, 1, 1, 6)
+ action = (5,)
+
+ self.assertEqual(oid, driver._snmp_oid(action))
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(2, driver.value_power_off)
+
+ def test_servertech_sentry4_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the
+ # ServerTech Sentry4 driver
+ self._update_driver_info(snmp_driver="servertech_sentry4",
+ snmp_outlet="6")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 1718, 4, 1, 8, 5, 1, 2, 1, 1, 6)
+ action = (2,)
+
+ self.assertEqual(oid, driver._snmp_oid(action))
+ self.assertEqual(1, driver.value_power_on)
+ self.assertEqual(2, driver.value_power_off)
+
+ def test_vertivgeist_pdu_snmp_objects(self, mock_get_client):
+ # Ensure the correct SNMP object OIDs and values are used by the
+ # Vertiv Geist PDU driver
+ self._update_driver_info(snmp_driver="vertivgeist_pdu",
+ snmp_outlet="6")
+ driver = snmp._get_driver(self.node)
+ oid = (1, 3, 6, 1, 4, 1, 21239, 5, 2, 3, 5, 1, 4, 6)
+ action = (4,)
+
+ self.assertEqual(oid, driver._snmp_oid(action))
+ self.assertEqual(2, driver.value_power_on)
+ self.assertEqual(4, driver.value_power_off)
+
def test_aten_snmp_objects(self, mock_get_client):
# Ensure the correct SNMP object OIDs and values are used by the
# Aten driver
diff --git a/releasenotes/notes/additonal-snmp-drivers-ae1174e6bd6ee3a6.yaml b/releasenotes/notes/additonal-snmp-drivers-ae1174e6bd6ee3a6.yaml
new file mode 100644
index 000000000..f98f2e607
--- /dev/null
+++ b/releasenotes/notes/additonal-snmp-drivers-ae1174e6bd6ee3a6.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Adds ``raritan_pdu2``, ``servertech_sentry3``, ``servertech_sentry4``,
+ and ``vertivgest_pdu`` snmp drivers to support additional PDU models.
diff --git a/releasenotes/notes/anaconda-permit-cert-validation-disable-6611d3cb9401031d.yaml b/releasenotes/notes/anaconda-permit-cert-validation-disable-6611d3cb9401031d.yaml
new file mode 100644
index 000000000..59d306c5d
--- /dev/null
+++ b/releasenotes/notes/anaconda-permit-cert-validation-disable-6611d3cb9401031d.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+ - |
+ Adds a configuration option, ``[anaconda]insecure_heartbeat`` to allow
+ for TLS certificate validation to be disabled in the ``anaconda``
+ deployment interface, which is needed for continious integration to
+ be able to be performed without substantial substrate image customization.
+ This option is *not* advised for any production usage.
diff --git a/releasenotes/notes/create_csr_clean_step-a720932f61b42118.yaml b/releasenotes/notes/create_csr_clean_step-a720932f61b42118.yaml
new file mode 100644
index 000000000..1951245c1
--- /dev/null
+++ b/releasenotes/notes/create_csr_clean_step-a720932f61b42118.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Adds new clean steps ``create_csr`` and ``add_https_certificate``
+ to ``ilo`` and ``ilo5`` hardware types which allows users to
+ create Certificate Signing Request(CSR) and adds signed HTTPS
+ certificate to the iLO.
diff --git a/releasenotes/notes/ilo-event-subscription-0dadf136411bd16a.yaml b/releasenotes/notes/ilo-event-subscription-0dadf136411bd16a.yaml
new file mode 100644
index 000000000..fcfc515e4
--- /dev/null
+++ b/releasenotes/notes/ilo-event-subscription-0dadf136411bd16a.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Provides vendor passthru methods for ``ilo`` and ``ilo5`` hardware types
+ to create, delete and get subscriptions for BMC events. These methods are
+ supported for ``HPE ProLiant Gen10`` and ``HPE ProLiant Gen10 Plus``
+ servers.
diff --git a/releasenotes/notes/redfish_consider_password_in_session_cache-1fa84234db179053.yaml b/releasenotes/notes/redfish_consider_password_in_session_cache-1fa84234db179053.yaml
new file mode 100644
index 000000000..af48b88fa
--- /dev/null
+++ b/releasenotes/notes/redfish_consider_password_in_session_cache-1fa84234db179053.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Fixes an issue where the Redfish session cache would continue using an
+ old session when a password for a Redfish BMC was changed. Now the old
+ session will not be found in this case, and a new session will be created
+ with the latest credential information available.
diff --git a/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml b/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml
index df9bef955..a829cbd97 100644
--- a/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml
+++ b/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml
@@ -1,8 +1,8 @@
---
fixes:
- |
- Resolved clear_job_queue and reset_idrac verify step failures which occur
- when the functionality is not supported by the iDRAC. When this condition
- is detected, the code in the step handles the exception and logs a warning
- and completes successfully in case of verification steps but fails in case
- of cleaning steps.
+ Resolved ``clear_job_queue`` and ``reset_idrac`` verify step failures which
+ occur when the functionality is not supported by the iDRAC. When this
+ condition is detected, the code in the step handles the exception and logs
+ a warning and completes successfully in case of verification steps but
+ fails in case of cleaning steps.
diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
index f5f519da5..d4d148d41 100644
--- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
+++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
@@ -7,11 +7,11 @@ msgid ""
msgstr ""
"Project-Id-Version: Ironic Release Notes\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-07-06 13:27+0000\n"
+"POT-Creation-Date: 2022-09-06 22:51+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2022-07-24 12:21+0000\n"
+"PO-Revision-Date: 2022-09-05 10:29+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en_GB\n"
@@ -211,8 +211,8 @@ msgstr "13.0.6"
msgid "13.0.7"
msgstr "13.0.7"
-msgid "13.0.7-24"
-msgstr "13.0.7-24"
+msgid "13.0.7-25"
+msgstr "13.0.7-25"
msgid "14.0.0"
msgstr "14.0.0"
@@ -226,8 +226,8 @@ msgstr "15.0.1"
msgid "15.0.2"
msgstr "15.0.2"
-msgid "15.0.2-16"
-msgstr "15.0.2-16"
+msgid "15.0.2-17"
+msgstr "15.0.2-17"
msgid "15.1.0"
msgstr "15.1.0"
@@ -271,8 +271,8 @@ msgstr "17.0.3"
msgid "17.0.4"
msgstr "17.0.4"
-msgid "17.0.4-27"
-msgstr "17.0.4-27"
+msgid "17.0.4-34"
+msgstr "17.0.4-34"
msgid "18.0.0"
msgstr "18.0.0"
@@ -286,8 +286,8 @@ msgstr "18.2.0"
msgid "18.2.1"
msgstr "18.2.1"
-msgid "18.2.1-17"
-msgstr "18.2.1-17"
+msgid "18.2.1-27"
+msgstr "18.2.1-27"
msgid "19.0.0"
msgstr "19.0.0"
@@ -298,14 +298,14 @@ msgstr "20.0.0"
msgid "20.1.0"
msgstr "20.1.0"
-msgid "20.1.0-12"
-msgstr "20.1.0-12"
+msgid "20.1.0-24"
+msgstr "20.1.0-24"
msgid "20.2.0"
msgstr "20.2.0"
-msgid "20.2.0-21"
-msgstr "20.2.0-21"
+msgid "21.0.0"
+msgstr "21.0.0"
msgid "4.0.0 First semver release"
msgstr "4.0.0 First semver release"
@@ -613,6 +613,13 @@ msgstr ""
"mod_wsgi)."
msgid ""
+"A new class ``ironic.drivers.modules.agent.CustomAgentDeploy`` can be used "
+"as a base class for deploy interfaces based on ironic-python-agent."
+msgstr ""
+"A new class ``ironic.drivers.modules.agent.CustomAgentDeploy`` can be used "
+"as a base class for deploying interfaces based on ironic-python-agent."
+
+msgid ""
"A new configuration option ``[agent]require_tls`` allows rejecting ramdisk "
"callback URLs that don't use the ``https://`` schema."
msgstr ""
diff --git a/requirements.txt b/requirements.txt
index 24c09f50c..ae8e14f39 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -47,4 +47,4 @@ psutil>=3.2.2 # BSD
futurist>=1.2.0 # Apache-2.0
tooz>=2.7.0 # Apache-2.0
openstacksdk>=0.48.0 # Apache-2.0
-sushy>=3.10.0
+sushy>=4.3.0