Add vendor_passthru method for virtual media

Add a vendor_passthru method to eject_vmedia for Redfish and idrac.

Story: 2008363
Task: 41271

Change-Id: Ib5ae16bacfd79f479a9aa8fbf69edc5cfdf73ce3
This commit is contained in:
Bob Fournier 2020-11-17 08:18:10 -05:00
parent b5932bc6bf
commit 98958cd0a4
9 changed files with 259 additions and 27 deletions

View File

@ -81,4 +81,5 @@ class IDRACHardware(generic.GenericHardware):
def supported_vendor_interfaces(self):
"""List of supported vendor interfaces."""
return [vendor_passthru.DracWSManVendorPassthru,
vendor_passthru.DracVendorPassthru, noop.NoVendor]
vendor_passthru.DracVendorPassthru,
vendor_passthru.DracRedfishVendorPassthru, noop.NoVendor]

View File

@ -24,6 +24,7 @@ from ironic.drivers import base
from ironic.drivers.modules.drac import bios as drac_bios
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import job as drac_job
from ironic.drivers.modules.redfish import vendor as redfish_vendor
LOG = logging.getLogger(__name__)
@ -190,3 +191,10 @@ class DracVendorPassthru(DracWSManVendorPassthru):
LOG.warning("Vendor passthru interface 'idrac' is deprecated and may "
"be removed in a future release. Use 'idrac-wsman' "
"instead.")
class DracRedfishVendorPassthru(redfish_vendor.RedfishVendorPassthru):
"""iDRAC Redfish interface for vendor_passthru.
Use the Redfish implementation for vendor passthru.
"""

View File

@ -183,7 +183,7 @@ def _insert_vmedia(task, boot_url, boot_device):
_('No suitable virtual media device found'))
def _eject_vmedia(task, boot_device=None):
def eject_vmedia(task, boot_device=None):
"""Eject virtual CDs and DVDs
:param task: A task from TaskManager.
@ -430,7 +430,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
floppy_ref = image_utils.prepare_floppy_image(
task, params=ramdisk_params)
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
_insert_vmedia(
task, floppy_ref, sushy.VIRTUAL_MEDIA_FLOPPY)
@ -447,7 +447,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
iso_ref = image_utils.prepare_deploy_iso(task, ramdisk_params,
mode, d_info)
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
_insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
boot_mode_utils.sync_boot_mode(task)
@ -474,12 +474,12 @@ class RedfishVirtualMediaBoot(base.BootInterface):
LOG.debug("Cleaning up deploy boot for "
"%(node)s", {'node': task.node.uuid})
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
image_utils.cleanup_iso_image(task)
if (config_via_floppy
and _has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY)):
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
image_utils.cleanup_floppy_image(task)
@ -533,7 +533,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
deploy_info = _parse_deploy_info(node)
iso_ref = image_utils.prepare_boot_iso(task, deploy_info, **params)
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
_insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
boot_mode_utils.sync_boot_mode(task)
@ -556,11 +556,11 @@ class RedfishVirtualMediaBoot(base.BootInterface):
LOG.debug("Cleaning up instance boot for "
"%(node)s", {'node': task.node.uuid})
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
d_info = task.node.driver_info
config_via_floppy = d_info.get('config_via_floppy')
if config_via_floppy:
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
image_utils.cleanup_iso_image(task)

View File

@ -0,0 +1,92 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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.
"""
Vendor Interface for Redfish drivers and its supporting methods.
"""
from ironic_lib import metrics_utils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.drivers import base
from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import utils as redfish_utils
METRICS = metrics_utils.get_metrics_logger(__name__)
class RedfishVendorPassthru(base.VendorInterface):
"""Vendor-specific interfaces for Redfish drivers."""
def get_properties(self):
return {}
@METRICS.timer('RedfishVendorPassthru.validate')
def validate(self, task, method, **kwargs):
"""Validate vendor-specific actions.
Checks if a valid vendor passthru method was passed and validates
the parameters for the vendor passthru method.
:param task: a TaskManager instance containing the node to act on.
:param method: method to be validated.
:param kwargs: kwargs containing the vendor passthru method's
parameters.
:raises: InvalidParameterValue, if any of the parameters have invalid
value.
"""
if method == 'eject_vmedia':
self._validate_eject_vmedia(task, kwargs)
return
super(RedfishVendorPassthru, self).validate(task, method, **kwargs)
def _validate_eject_vmedia(self, task, kwargs):
"""Verify that the boot_device input is valid."""
# If a boot device is provided check that it's valid.
# It is OK to eject if already ejected
boot_device = kwargs.get('boot_device')
if not boot_device:
return
system = redfish_utils.get_system(task.node)
for manager in system.managers:
for v_media in manager.virtual_media.get_members():
if boot_device not in v_media.media_types:
raise exception.InvalidParameterValue(_(
"Boot device %s is not a valid value ") % boot_device)
@METRICS.timer('RedfishVendorPassthru.eject_vmedia')
@base.passthru(['POST'],
description=_("Eject a virtual media device. If no device "
"is provided than all attached devices will "
"be ejected. "
"Optional arguments: "
"'boot_device' - the boot device to eject, "
"either 'cd', 'dvd', 'usb', or 'floppy'"))
# @task_manager.require_exclusive_lock
def eject_vmedia(self, task, **kwargs):
"""Eject a virtual media device.
:param task: A TaskManager object.
:param kwargs: The arguments sent with vendor passthru. The optional
kwargs are::
'boot_device': the boot device to eject
"""
# If boot_device not provided all vmedia devices will be ejected
boot_device = kwargs.get('boot_device')
redfish_boot.eject_vmedia(task, boot_device)

View File

@ -24,6 +24,7 @@ from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import inspect as redfish_inspect
from ironic.drivers.modules.redfish import management as redfish_mgmt
from ironic.drivers.modules.redfish import power as redfish_power
from ironic.drivers.modules.redfish import vendor as redfish_vendor
class RedfishHardware(generic.GenericHardware):
@ -57,3 +58,8 @@ class RedfishHardware(generic.GenericHardware):
# vendors support.
return [ipxe.iPXEBoot, pxe.PXEBoot,
redfish_boot.RedfishVirtualMediaBoot]
@property
def supported_vendor_interfaces(self):
"""List of supported vendor interfaces."""
return [redfish_vendor.RedfishVendorPassthru, noop.NoVendor]

View File

@ -326,7 +326,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
autospec=True)
@mock.patch.object(image_utils, 'prepare_deploy_iso', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
@mock.patch.object(redfish_boot.manager_utils, 'node_power_action',
@ -371,7 +371,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
autospec=True)
@mock.patch.object(image_utils, 'prepare_deploy_iso', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
@mock.patch.object(redfish_boot.manager_utils, 'node_power_action',
@ -417,7 +417,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(image_utils, 'prepare_floppy_image', autospec=True)
@mock.patch.object(image_utils, 'prepare_deploy_iso', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_device', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
@mock.patch.object(redfish_boot.manager_utils, 'node_power_action',
@ -482,7 +482,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
@mock.patch.object(redfish_boot, '_has_vmedia_device', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_floppy_image', autospec=True)
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
@ -517,7 +517,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'clean_up_instance', autospec=True)
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
@ -569,7 +569,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'clean_up_instance', autospec=True)
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
@ -617,7 +617,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'clean_up_instance', autospec=True)
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
@ -663,7 +663,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'clean_up_instance', autospec=True)
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
@ -700,7 +700,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
def _test_prepare_instance_local_boot(
@ -733,7 +733,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
self.node.save()
self._test_prepare_instance_local_boot()
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot, 'eject_vmedia', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
def _test_clean_up_instance(self, mock_cleanup_iso_image,
mock__eject_vmedia):
@ -832,7 +832,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
task, 'img-url', sushy.VIRTUAL_MEDIA_CD)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test__eject_vmedia_everything(self, mock_redfish_utils):
def test_eject_vmedia_everything(self, mock_redfish_utils):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
@ -851,13 +851,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
redfish_boot._eject_vmedia(task)
redfish_boot.eject_vmedia(task)
mock_vmedia_cd.eject_media.assert_called_once_with()
mock_vmedia_floppy.eject_media.assert_called_once_with()
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test__eject_vmedia_specific(self, mock_redfish_utils):
def test_eject_vmedia_specific(self, mock_redfish_utils):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
@ -876,13 +876,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
redfish_boot._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
redfish_boot.eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
mock_vmedia_cd.eject_media.assert_called_once_with()
self.assertFalse(mock_vmedia_floppy.eject_media.call_count)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test__eject_vmedia_not_inserted(self, mock_redfish_utils):
def test_eject_vmedia_not_inserted(self, mock_redfish_utils):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
@ -901,13 +901,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
redfish_boot._eject_vmedia(task)
redfish_boot.eject_vmedia(task)
self.assertFalse(mock_vmedia_cd.eject_media.call_count)
self.assertFalse(mock_vmedia_floppy.eject_media.call_count)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test__eject_vmedia_unknown(self, mock_redfish_utils):
def test_eject_vmedia_unknown(self, mock_redfish_utils):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
@ -923,6 +923,6 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
redfish_boot._eject_vmedia(task)
redfish_boot.eject_vmedia(task)
self.assertFalse(mock_vmedia_cd.eject_media.call_count)

View File

@ -0,0 +1,116 @@
# Copyright 2018 DMTF. 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.
from unittest import mock
from oslo_utils import importutils
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import vendor as redfish_vendor
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils
sushy = importutils.try_import('sushy')
INFO_DICT = db_utils.get_test_redfish_info()
class RedfishVendorPassthruTestCase(db_base.DbTestCase):
def setUp(self):
super(RedfishVendorPassthruTestCase, self).setUp()
self.config(enabled_bios_interfaces=['redfish'],
enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'],
enabled_boot_interfaces=['redfish-virtual-media'],
enabled_management_interfaces=['redfish'],
enabled_vendor_interfaces=['redfish'])
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test_eject_vmedia_all(self, mock_redfish_utils):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_cd = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_vmedia_floppy = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_FLOPPY])
mock_manager = mock.MagicMock()
mock_manager.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy]
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
task.driver.vendor.eject_vmedia(task)
mock_vmedia_cd.eject_media.assert_called_once_with()
mock_vmedia_floppy.eject_media.assert_called_once_with()
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test_eject_vmedia_cd(self, mock_redfish_utils):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_cd = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_vmedia_floppy = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_FLOPPY])
mock_manager = mock.MagicMock()
mock_manager.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy]
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
task.driver.vendor.eject_vmedia(task,
boot_device=sushy.VIRTUAL_MEDIA_CD)
mock_vmedia_cd.eject_media.assert_called_once_with()
mock_vmedia_floppy.eject_media.assert_not_called()
@mock.patch.object(redfish_vendor, 'redfish_utils', autospec=True)
def test_eject_vmedia_invalid_dev(self, mock_redfish_utils):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_cd = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_manager = mock.MagicMock()
mock_manager.virtual_media.get_members.return_value = [
mock_vmedia_cd]
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
kwargs = {'boot_device': 'foo'}
self.assertRaises(
exception.InvalidParameterValue,
task.driver.vendor.validate, task, 'eject_vmedia', **kwargs)

View File

@ -0,0 +1,7 @@
---
features:
- |
Provides a new vendor passthru method for Redfish to eject a virtual_media
device. A specific device can be given (either ``cd``, ``dvd``,
``floppy``, or ``usb``), or if no device is provided then all attached
devices will be ejected.

View File

@ -162,9 +162,11 @@ ironic.hardware.interfaces.vendor =
ibmc = ironic.drivers.modules.ibmc.vendor:IBMCVendor
idrac = ironic.drivers.modules.drac.vendor_passthru:DracVendorPassthru
idrac-wsman = ironic.drivers.modules.drac.vendor_passthru:DracWSManVendorPassthru
idrac-redfish = ironic.drivers.modules.drac.vendor_passthru:DracRedfishVendorPassthru
ilo = ironic.drivers.modules.ilo.vendor:VendorPassthru
ipmitool = ironic.drivers.modules.ipmitool:VendorPassthru
no-vendor = ironic.drivers.modules.noop:NoVendor
redfish = ironic.drivers.modules.redfish.vendor:RedfishVendorPassthru
ironic.hardware.types =
fake-hardware = ironic.drivers.fake_hardware:FakeHardware