Allow usage of virtual media via System

DMTF now changes their Virtual Media URI to support Systems. Redfish driver now
support this resource to boot, so it is needed that Ironic have a way to use it.

Closes-Bug: #2039458
Change-Id: I66e8edb847e93f96374072525222f05e7561fb07
This commit is contained in:
Winicius Silva 2024-03-09 19:37:04 -03:00
parent 2f3448a421
commit 6d046ad7b4
4 changed files with 684 additions and 128 deletions

View File

@ -167,10 +167,36 @@ def _test_retry(exception):
return False
@tenacity.retry(retry=tenacity.retry_if_exception(_test_retry),
stop=tenacity.stop_after_attempt(3),
wait=tenacity.wait_fixed(3),
reraise=True)
def _has_vmedia_via_systems(system):
"""Indicates if virtual media is available trough Systems
:param system: A redfish System object
:return: True if the System has virtual media, else False
"""
try:
system.virtual_media
return True
except sushy.exceptions.MissingAttributeError:
return False
except AttributeError:
# NOTE(wncslln): In case of older versions of sushy are
# used.
return False
def _has_vmedia_via_manager(manager):
"""Indicates if virtual media is available in the Manager
:param manager: A redfish System object
:return: True if the System has virtual media, else False
"""
try:
manager.virtual_media
return True
except sushy.exceptions.MissingAttributeError:
return False
def _insert_vmedia(task, managers, boot_url, boot_device):
"""Insert bootable ISO image into virtual CD or DVD
@ -182,73 +208,105 @@ def _insert_vmedia(task, managers, boot_url, boot_device):
:raises: InvalidParameterValue, if no suitable virtual CD or DVD is
found on the node.
"""
err_msg = None
for manager in managers:
for v_media in manager.virtual_media.get_members():
if boot_device not in v_media.media_types:
# NOTE(janders): this conditional allows v_media that only
# support DVD MediaType and NOT CD to also be used.
# if v_media.media_types contains sushy.VIRTUAL_MEDIA_DVD
# we follow the usual steps of checking if v_media is inserted
# and if not, attempt to insert it. Otherwise we skip to the
# next v_media device, if any
# This is needed to add support to Cisco UCSB and UCSX blades
# reference: https://bugs.launchpad.net/ironic/+bug/2031595
if (boot_device == sushy.VIRTUAL_MEDIA_CD
and sushy.VIRTUAL_MEDIA_DVD in v_media.media_types):
LOG.debug("While looking for %(requested_device)s virtual "
"media device, found %(available_device)s "
"instead. Attempting to configure it.",
{'requested_device': sushy.VIRTUAL_MEDIA_CD,
'available_device': sushy.VIRTUAL_MEDIA_DVD})
else:
continue
if v_media.inserted:
if v_media.image == boot_url:
LOG.debug("Boot media %(boot_url)s is already "
"inserted into %(boot_device)s for node "
"%(node)s", {'node': task.node.uuid,
'boot_url': boot_url,
'boot_device': boot_device})
return
continue
try:
v_media.insert_media(boot_url, inserted=True,
write_protected=True)
# NOTE(janders): On Cisco UCSB and UCSX blades there are several
# vMedia devices. Some of those are only meant for internal use
# by CIMC vKVM - attempts to InsertMedia into those will result
# in BadRequestError. We catch the exception here so that we don't
# fail out and try the next available device instead, if available.
except sushy.exceptions.BadRequestError:
err_msg = ("Inserting virtual media into %(boot_device)s "
"failed for node %(node)s, moving to next virtual "
"media device, if available",
{'node': task.node.uuid,
'boot_device': boot_device})
LOG.warning(err_msg)
continue
except sushy.exceptions.ServerSideError as e:
e.node_uuid = task.node.uuid
raise
LOG.info("Inserted boot media %(boot_url)s into "
"%(boot_device)s for node "
"%(node)s", {'node': task.node.uuid,
'boot_url': boot_url,
'boot_device': boot_device})
err_msgs = []
system = redfish_utils.get_system(task.node)
if _has_vmedia_via_systems(system):
inserted = _insert_vmedia_in_resource(task, system, boot_url,
boot_device, err_msgs)
if inserted:
return
if (err_msg is not None):
else:
for manager in managers:
inserted = _insert_vmedia_in_resource(task, manager, boot_url,
boot_device, err_msgs)
if inserted:
return
if err_msgs:
exc_msg = ("All virtual media mount attempts failed. "
"Most recent error: ", err_msg)
"Most recent error: ", err_msgs[-1])
else:
exc_msg = 'No suitable virtual media device found'
raise exception.InvalidParameterValue(exc_msg)
@tenacity.retry(retry=tenacity.retry_if_exception(_test_retry),
stop=tenacity.stop_after_attempt(3),
wait=tenacity.wait_fixed(3),
reraise=True)
def _insert_vmedia_in_resource(task, resource, boot_url, boot_device,
err_msgs):
"""Insert virtual media from a given redfish resource (System/Manager)
:param task: A task from TaskManager.
:param resource: A redfish resource either a System or Manager.
:param boot_url: URL to a bootable ISO image
:param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
`VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY`
:param err_msgs: A list that will contain all errors found
:raises: InvalidParameterValue, if no suitable virtual CD or DVD is
found on the node.
"""
for v_media in resource.virtual_media.get_members():
if boot_device not in v_media.media_types:
# NOTE(janders): this conditional allows v_media that only
# support DVD MediaType and NOT CD to also be used.
# if v_media.media_types contains sushy.VIRTUAL_MEDIA_DVD
# we follow the usual steps of checking if v_media is inserted
# and if not, attempt to insert it. Otherwise we skip to the
# next v_media device, if any
# This is needed to add support to Cisco UCSB and UCSX blades
# reference: https://bugs.launchpad.net/ironic/+bug/2031595
if (boot_device == sushy.VIRTUAL_MEDIA_CD
and sushy.VIRTUAL_MEDIA_DVD in v_media.media_types):
LOG.debug("While looking for %(requested_device)s virtual "
"media device, found %(available_device)s "
"instead. Attempting to configure it.",
{'requested_device': sushy.VIRTUAL_MEDIA_CD,
'available_device': sushy.VIRTUAL_MEDIA_DVD})
else:
continue
if v_media.inserted:
if v_media.image == boot_url:
LOG.debug("Boot media %(boot_url)s is already "
"inserted into %(boot_device)s for node "
"%(node)s", {'node': task.node.uuid,
'boot_url': boot_url,
'boot_device': boot_device})
return True
continue
try:
v_media.insert_media(boot_url, inserted=True,
write_protected=True)
# NOTE(janders): On Cisco UCSB and UCSX blades there are several
# vMedia devices. Some of those are only meant for internal use
# by CIMC vKVM - attempts to InsertMedia into those will result
# in BadRequestError. We catch the exception here so that we don't
# fail out and try the next available device instead, if available.
except sushy.exceptions.BadRequestError:
err_msg = ("Inserting virtual media into %(boot_device)s "
"failed for node %(node)s, moving to next virtual "
"media device, if available",
{'node': task.node.uuid,
'boot_device': boot_device})
err_msgs.append(err_msg)
LOG.warning(err_msg)
continue
except sushy.exceptions.ServerSideError as e:
e.node_uuid = task.node.uuid
raise
LOG.info("Inserted boot media %(boot_url)s into "
"%(boot_device)s for node "
"%(node)s", {'node': task.node.uuid,
'boot_url': boot_url,
'boot_device': boot_device})
return True
def _eject_vmedia(task, managers, boot_device=None):
"""Eject virtual CDs and DVDs
@ -262,37 +320,66 @@ def _eject_vmedia(task, managers, boot_device=None):
found on the node.
"""
found = False
system = redfish_utils.get_system(task.node)
# NOTE(wncslln): we will attempt to eject virtual media in Systems
# and in Managers.
if _has_vmedia_via_systems(system):
ejected = _eject_vmedia_from_resource(task, resource=system,
boot_device=boot_device)
if ejected:
found = True
for manager in managers:
for v_media in manager.virtual_media.get_members():
if boot_device and boot_device not in v_media.media_types:
# NOTE(iurygregory): this conditional allows v_media that only
# support DVD MediaType and NOT CD to also be used.
# if v_media.media_types contains sushy.VIRTUAL_MEDIA_DVD
# we follow the usual steps of checking if v_media is inserted
# and eject it. Otherwise we skip to the
# next v_media device, if any.
# This is needed to add support to Cisco UCSB and UCSX blades
# reference: https://bugs.launchpad.net/ironic/+bug/2039042
if (boot_device == sushy.VIRTUAL_MEDIA_CD
and sushy.VIRTUAL_MEDIA_DVD in v_media.media_types):
LOG.debug('While looking for %(requested_device)s virtual '
'media device, found %(available_device)s '
'instead. Attempting to use it to eject media.',
{'requested_device': sushy.VIRTUAL_MEDIA_CD,
'available_device': sushy.VIRTUAL_MEDIA_DVD})
else:
continue
inserted = v_media.inserted
if inserted:
v_media.eject_media()
if _has_vmedia_via_manager(manager):
ejected = _eject_vmedia_from_resource(task, resource=manager,
boot_device=boot_device)
if ejected:
found = True
continue
LOG.info("Boot media is%(already)s ejected from "
"%(boot_device)s for node %(node)s"
"", {'node': task.node.uuid,
'already': '' if inserted else ' already',
'boot_device': v_media.name})
return found
def _eject_vmedia_from_resource(task, resource, boot_device=None):
"""Eject virtual media from a given redfish resource (System/Manager)
:param task: A task from TaskManager.
:param resource: A redfish resource either a System or Manager.
:param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
`VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY` or `None` to
eject everything (default).
:return: True if any device was ejected, else False
"""
found = False
for v_media in resource.virtual_media.get_members():
if boot_device and boot_device not in v_media.media_types:
# NOTE(iurygregory): this conditional allows v_media that only
# support DVD MediaType and NOT CD to also be used.
# if v_media.media_types contains sushy.VIRTUAL_MEDIA_DVD
# we follow the usual steps of checking if v_media is inserted
# and eject it. Otherwise we skip to the
# next v_media device, if any.
# This is needed to add support to Cisco UCSB and UCSX blades
# reference: https://bugs.launchpad.net/ironic/+bug/2039042
if (boot_device == sushy.VIRTUAL_MEDIA_CD
and sushy.VIRTUAL_MEDIA_DVD in v_media.media_types):
LOG.debug('While looking for %(requested_device)s virtual '
'media device, found %(available_device)s '
'instead. Attempting to use it to eject media.',
{'requested_device': sushy.VIRTUAL_MEDIA_CD,
'available_device': sushy.VIRTUAL_MEDIA_DVD})
else:
continue
inserted = v_media.inserted
if inserted:
v_media.eject_media()
found = True
LOG.info("Boot media is%(already)s ejected from "
"%(boot_device)s for node %(node)s"
"", {'node': task.node.uuid,
'already': '' if inserted else ' already',
'boot_device': v_media.name})
return found
@ -318,8 +405,8 @@ def eject_vmedia(task, boot_device=None):
image_utils.cleanup_iso_image(task)
def _has_vmedia_device(managers, boot_device, inserted=None):
"""Indicate if device exists at any of the managers
def _has_vmedia_device(managers, boot_device, inserted=None, system=None):
"""Indicate if device exists at any of the managers or system
:param managers: A list of System managers.
:param boot_device: One or more sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
@ -333,14 +420,23 @@ def _has_vmedia_device(managers, boot_device, inserted=None):
boot_device = [boot_device]
for dev in boot_device:
for manager in managers:
for v_media in manager.virtual_media.get_members():
if _has_vmedia_via_systems(system):
for v_media in system.virtual_media.get_members():
if dev not in v_media.media_types:
continue
if (inserted is not None
and bool(v_media.inserted) is not inserted):
continue
return dev
else:
for manager in managers:
for v_media in manager.virtual_media.get_members():
if dev not in v_media.media_types:
continue
if (inserted is not None
and bool(v_media.inserted) is not inserted):
continue
return dev
return False
@ -526,13 +622,14 @@ class RedfishVirtualMediaBoot(base.BootInterface):
return
d_info = _parse_driver_info(node)
managers = redfish_utils.get_system(task.node).managers
system = redfish_utils.get_system(task.node)
managers = system.managers
self._validate_vendor(task, managers)
if manager_utils.is_fast_track(task):
if _has_vmedia_device(managers, sushy.VIRTUAL_MEDIA_CD,
inserted=True):
inserted=True, system=system):
LOG.debug('Fast track operation for node %s, not inserting '
'any devices', node.uuid)
return
@ -570,7 +667,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
removable = _has_vmedia_device(
managers,
# Prefer USB devices since floppies are outdated
[sushy.VIRTUAL_MEDIA_USBSTICK, sushy.VIRTUAL_MEDIA_FLOPPY])
[sushy.VIRTUAL_MEDIA_USBSTICK, sushy.VIRTUAL_MEDIA_FLOPPY],
system=system)
if removable:
floppy_ref = image_utils.prepare_floppy_image(
task, params=ramdisk_params)

View File

@ -635,7 +635,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
mock__has_vmedia_device, mock_prepare_deploy_iso,
mock_prepare_floppy_image, mock_node_set_boot_device):
system = mock_system.return_value
managers = mock_system.return_value.managers
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
@ -658,7 +658,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__has_vmedia_device.assert_called_once_with(
managers,
[sushy.VIRTUAL_MEDIA_USBSTICK, sushy.VIRTUAL_MEDIA_FLOPPY])
[sushy.VIRTUAL_MEDIA_USBSTICK, sushy.VIRTUAL_MEDIA_FLOPPY],
system=system)
eject_calls = [
mock.call(task, managers, dev)
@ -708,7 +709,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
mock__has_vmedia_device, mock_prepare_deploy_iso,
mock_prepare_floppy_image, mock_node_set_boot_device):
system = mock_system.return_value
managers = mock_system.return_value.managers
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
@ -731,7 +732,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__has_vmedia_device.assert_called_once_with(
managers,
[sushy.VIRTUAL_MEDIA_USBSTICK, sushy.VIRTUAL_MEDIA_FLOPPY])
[sushy.VIRTUAL_MEDIA_USBSTICK, sushy.VIRTUAL_MEDIA_FLOPPY],
system=system)
eject_calls = [
mock.call(task, managers, dev)
@ -834,7 +836,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
mock__has_vmedia_device,
mock_prepare_deploy_iso, mock_node_set_boot_device):
system = mock_system.return_value
managers = mock_system.return_value.managers
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
@ -844,7 +846,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
task.driver.boot.prepare_ramdisk(task, {})
mock__has_vmedia_device.assert_called_once_with(
managers, sushy.VIRTUAL_MEDIA_CD, inserted=True)
managers, sushy.VIRTUAL_MEDIA_CD, inserted=True,
system=system)
mock_node_power_action.assert_not_called()
mock__eject_vmedia.assert_not_called()
@ -870,7 +873,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
mock__has_vmedia_device,
mock_prepare_deploy_iso, mock_node_set_boot_device):
system = mock_system.return_value
managers = mock_system.return_value.managers
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
@ -883,7 +886,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
task.driver.boot.prepare_ramdisk(task, {})
mock__has_vmedia_device.assert_called_once_with(
managers, sushy.VIRTUAL_MEDIA_CD, inserted=True)
managers, sushy.VIRTUAL_MEDIA_CD, inserted=True,
system=system)
mock_node_power_action.assert_called_once_with(
task, states.POWER_OFF)
@ -1338,10 +1342,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__eject_vmedia.assert_has_calls(eject_calls)
mock_secure_boot.assert_called_once_with(task)
def test__insert_vmedia_anew(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_anew(self, mock_sys, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
@ -1362,8 +1369,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
self.assertFalse(mock_vmedia_floppy.insert_media.call_count)
def test__insert_vmedia_anew_dvd(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_anew_dvd(self, mock_sys, mock_vmd_sys):
mock_vmd_sys.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_dvd = mock.MagicMock(
@ -1382,8 +1391,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
'img-url', inserted=True, write_protected=True)
@mock.patch('time.sleep', lambda *args, **kwargs: None)
def test__insert_vmedia_anew_dvd_retry(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_anew_dvd_retry(self, mock_sys, mock_vmd_sys):
mock_vmd_sys.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_dvd_1 = mock.MagicMock(
@ -1409,10 +1420,12 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
self.assertEqual(mock_vmedia_dvd_2.insert_media.call_count, 1)
def test__insert_vmedia_already_inserted(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_already_inserted(self, mock_sys, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=True,
image='img-url',
@ -1428,10 +1441,12 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
self.assertFalse(mock_vmedia_cd.insert_media.call_count)
@mock.patch('time.sleep', lambda *args, **kwargs: None)
def test__insert_vmedia_while_ejecting(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_while_ejecting(self, mock_sys, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=False,
image='img-url',
@ -1453,8 +1468,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
self.assertEqual(mock_vmedia_cd.insert_media.call_count, 2)
@mock.patch('time.sleep', lambda *args, **kwargs: None)
def test__insert_vmedia_bad_device(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_bad_device(self, mock_sys, mock_vmd_sys):
mock_vmd_sys.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_floppy = mock.MagicMock(
@ -1472,10 +1489,11 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@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, mock_vmd_sys,
mock_cleanup_iso, mock_cleanup_disk):
mock_vmd_sys.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_cd = mock.MagicMock(
@ -1507,10 +1525,11 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@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, mock_vmd_sys,
mock_cleanup_iso, mock_cleanup_disk):
mock_vmd_sys.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_cd = mock.MagicMock(
@ -1538,14 +1557,16 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_boot.LOG, 'debug', autospec=True)
@mock.patch.object(redfish_boot.LOG, 'info', autospec=True)
def test_eject_vmedia_with_dvd_cisco_ucs(self, mock_log_info,
mock_log_debug,
mock_vmd_sys,
mock_redfish_utils,
mock_cleanup_iso,
mock_cleanup_disk):
mock_vmd_sys.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_dvd_1 = mock.MagicMock(
@ -1573,11 +1594,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_cleanup_iso.assert_called_once_with(task)
mock_cleanup_disk.assert_not_called()
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@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, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
@ -1598,11 +1621,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
self.assertFalse(mock_vmedia_cd.eject_media.call_count)
self.assertFalse(mock_vmedia_floppy.eject_media.call_count)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@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, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
@ -1619,7 +1644,9 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
self.assertFalse(mock_vmedia_cd.eject_media.call_count)
def test__has_vmedia_device(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
def test__has_vmedia_device(self, mock_vmd_sys):
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
@ -1645,7 +1672,9 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
redfish_boot._has_vmedia_device(
[mock_manager], sushy.VIRTUAL_MEDIA_USBSTICK))
def test__has_vmedia_device_inserted(self):
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
def test__has_vmedia_device_inserted(self, mock_vmd_sys):
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
@ -2512,3 +2541,426 @@ class RedfishHTTPBootTestCase(db_base.DbTestCase):
mock_secure_boot.assert_called_once_with(task)
mock_cleanup_disk_image.assert_not_called()
@mock.patch('oslo_utils.eventletutils.EventletEvent.wait',
lambda *args, **kwargs: None)
class RedfishVirtualMediaBootViaSystemTestCase(db_base.DbTestCase):
def setUp(self):
super(RedfishVirtualMediaBootViaSystemTestCase, self).setUp()
self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'],
enabled_boot_interfaces=['redfish-virtual-media'],
enabled_management_interfaces=['redfish'],
enabled_inspect_interfaces=['redfish'],
enabled_bios_interfaces=['redfish'])
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_anew(self, mock_system, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = True
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_vmedia_floppy = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_FLOPPY])
mock_manager = mock.MagicMock()
mock_system.return_value.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy]
redfish_boot._insert_vmedia(
task, [mock_manager], 'img-url', sushy.VIRTUAL_MEDIA_CD)
mock_vmedia_cd.insert_media.assert_called_once_with(
'img-url', inserted=True, write_protected=True)
self.assertFalse(mock_vmedia_floppy.insert_media.call_count)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_anew_dvd(self, mock_system, mock_vmd_sys):
mock_vmd_sys.return_value = True
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_dvd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_DVD])
mock_manager = mock.MagicMock()
mock_system.return_value.virtual_media.get_members.return_value = [
mock_vmedia_dvd]
redfish_boot._insert_vmedia(
task, [mock_manager], 'img-url', sushy.VIRTUAL_MEDIA_CD)
mock_vmedia_dvd.insert_media.assert_called_once_with(
'img-url', inserted=True, write_protected=True)
@mock.patch('time.sleep', lambda *args, **kwargs: None)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_anew_dvd_retry(self, mock_system, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = True
mock_vmedia_dvd_1 = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_DVD])
mock_vmedia_dvd_2 = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_DVD])
mock_manager = mock.MagicMock()
def clear_and_raise(*args, **kwargs):
mock_vmedia_dvd_1.insert_media.side_effect = None
raise sushy.exceptions.BadRequestError(
"POST", 'img-url', mock.MagicMock())
mock_vmedia_dvd_1.insert_media.side_effect = clear_and_raise
mock_system.return_value.virtual_media.get_members.return_value = [
mock_vmedia_dvd_1, mock_vmedia_dvd_2]
redfish_boot._insert_vmedia(
task, [mock_manager], 'img-url', sushy.VIRTUAL_MEDIA_CD)
self.assertEqual(mock_vmedia_dvd_2.insert_media.call_count, 1)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_already_inserted(self, mock_sys, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = False
mock_vmedia_cd = mock.MagicMock(
inserted=True,
image='img-url',
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_manager = mock.MagicMock()
mock_manager.virtual_media.get_members.return_value = [
mock_vmedia_cd]
redfish_boot._insert_vmedia(
task, [mock_manager], 'img-url', sushy.VIRTUAL_MEDIA_CD)
self.assertFalse(mock_vmedia_cd.insert_media.call_count)
@mock.patch('time.sleep', lambda *args, **kwargs: None)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_while_ejecting(self, mock_system, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = True
mock_vmedia_cd = mock.MagicMock(
inserted=False,
image='img-url',
media_types=[sushy.VIRTUAL_MEDIA_CD],
)
mock_manager = mock.MagicMock()
def clear_and_raise(*args, **kwargs):
mock_vmedia_cd.insert_media.side_effect = None
raise sushy.exceptions.ServerSideError(
"POST", 'img-url', mock.MagicMock())
mock_vmedia_cd.insert_media.side_effect = clear_and_raise
mock_system.return_value.virtual_media.get_members.return_value = [
mock_vmedia_cd]
redfish_boot._insert_vmedia(
task, [mock_manager], 'img-url', sushy.VIRTUAL_MEDIA_CD)
self.assertEqual(mock_vmedia_cd.insert_media.call_count, 2)
@mock.patch('time.sleep', lambda *args, **kwargs: None)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test__insert_vmedia_bad_device(self, mock_sys, mock_vmd_sys):
mock_vmd_sys.return_value = True
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_floppy = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_FLOPPY])
mock_system = mock.MagicMock()
mock_manager = mock.MagicMock()
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_floppy]
self.assertRaises(
exception.InvalidParameterValue,
redfish_boot._insert_vmedia,
task, [mock_manager], 'img-url', sushy.VIRTUAL_MEDIA_CD)
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test_eject_vmedia_everything(self, mock_redfish_utils, mock_vmd_sys,
mock_cleanup_iso, mock_cleanup_disk):
mock_vmd_sys.return_value = True
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_vmedia_dvd = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_DVD])
mock_manager = mock.MagicMock()
mock_system = mock.MagicMock()
mock_redfish_utils.get_system.return_value = mock_system
mock_system.managers = [
mock_manager]
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy, mock_vmedia_dvd]
redfish_boot.eject_vmedia(task)
mock_vmedia_cd.eject_media.assert_called_once_with()
mock_vmedia_floppy.eject_media.assert_called_once_with()
mock_vmedia_dvd.eject_media.assert_called_once_with()
mock_cleanup_iso.assert_called_once_with(task)
mock_cleanup_disk.assert_called_once_with(task,
prefix='configdrive')
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_manager', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test_eject_vmedia_from_all_resources(self, mock_redfish_utils,
mock_vmd_sys, mock_vmd_mg,
mock_cleanup_iso,
mock_cleanup_disk):
mock_vmd_sys.return_value = True
mock_vmd_mg.return_value = True
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_vmedia_dvd = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_DVD])
mock_manager = mock.MagicMock()
mock_system = mock.MagicMock()
mock_redfish_utils.get_system.return_value = mock_system
mock_system.managers = [
mock_manager]
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_floppy, mock_vmedia_dvd]
mock_manager.virtual_media.get_members.return_value = [
mock_vmedia_cd]
redfish_boot.eject_vmedia(task)
mock_vmedia_cd.eject_media.assert_called_once_with()
mock_vmedia_floppy.eject_media.assert_called_once_with()
mock_vmedia_dvd.eject_media.assert_called_once_with()
mock_cleanup_iso.assert_called_once_with(task)
mock_cleanup_disk.assert_called_once_with(task,
prefix='configdrive')
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test_eject_vmedia_specific(self, mock_redfish_utils, mock_vmd_sys,
mock_cleanup_iso, mock_cleanup_disk):
mock_vmd_sys.return_value = True
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_system = mock.MagicMock()
mock_manager = mock.MagicMock()
mock_redfish_utils.get_system.return_value = mock_system
mock_system.managers = [
mock_manager]
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy]
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_cleanup_iso.assert_called_once_with(task)
mock_cleanup_disk.assert_not_called()
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_boot.LOG, 'debug', autospec=True)
@mock.patch.object(redfish_boot.LOG, 'info', autospec=True)
def test_eject_vmedia_with_dvd_cisco_ucs(self, mock_log_info,
mock_log_debug,
mock_vmd_sys,
mock_redfish_utils,
mock_cleanup_iso,
mock_cleanup_disk):
mock_vmd_sys.return_value = True
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmedia_dvd_1 = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_DVD])
mock_vmedia_dvd_2 = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_DVD])
mock_system = mock.MagicMock()
mock_manager = mock.MagicMock()
mock_redfish_utils.get_system.return_value = mock_system
mock_system.managers = [
mock_manager]
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_dvd_1, mock_vmedia_dvd_2]
redfish_boot.eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
mock_vmedia_dvd_1.eject_media.assert_called_once_with()
mock_vmedia_dvd_2.eject_media.assert_called_once_with()
self.assertEqual(mock_log_info.call_count, 2)
self.assertEqual(mock_log_debug.call_count, 3)
mock_cleanup_iso.assert_called_once_with(task)
mock_cleanup_disk.assert_not_called()
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test_eject_vmedia_not_inserted(self, mock_redfish_utils, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = True
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_vmedia_floppy = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_FLOPPY])
mock_manager = mock.MagicMock()
mock_system = mock.MagicMock()
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy]
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
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, '_has_vmedia_via_systems', autospec=True)
@mock.patch.object(redfish_boot, 'redfish_utils', autospec=True)
def test_eject_vmedia_unknown(self, mock_redfish_utils, mock_vmd_sys):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
mock_vmd_sys.return_value = True
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_manager = mock.MagicMock()
mock_system = mock.MagicMock()
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_cd]
mock_redfish_utils.get_system.return_value.managers = [
mock_manager]
redfish_boot.eject_vmedia(task)
self.assertFalse(mock_vmedia_cd.eject_media.call_count)
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
def test__has_vmedia_device(self, mock_vmd_sys):
mock_vmd_sys.return_value = True
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_vmedia_floppy = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_FLOPPY])
mock_manager = mock.MagicMock()
mock_system = mock.MagicMock()
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy]
self.assertEqual(
sushy.VIRTUAL_MEDIA_CD,
redfish_boot._has_vmedia_device(
[mock_manager], sushy.VIRTUAL_MEDIA_CD,
system=mock_system))
self.assertFalse(
redfish_boot._has_vmedia_device(
[mock_manager], sushy.VIRTUAL_MEDIA_CD,
inserted=True, system=mock_system))
self.assertFalse(
redfish_boot._has_vmedia_device(
[mock_manager], sushy.VIRTUAL_MEDIA_USBSTICK,
system=mock_system))
@mock.patch.object(redfish_boot, '_has_vmedia_via_systems', autospec=True)
def test__has_vmedia_device_inserted(self, mock_vmd_sys):
mock_vmd_sys.return_value = True
mock_vmedia_cd = mock.MagicMock(
inserted=False,
media_types=[sushy.VIRTUAL_MEDIA_CD])
mock_vmedia_floppy = mock.MagicMock(
inserted=True,
media_types=[sushy.VIRTUAL_MEDIA_FLOPPY])
mock_manager = mock.MagicMock()
mock_system = mock.MagicMock()
mock_system.virtual_media.get_members.return_value = [
mock_vmedia_cd, mock_vmedia_floppy]
self.assertEqual(
sushy.VIRTUAL_MEDIA_FLOPPY,
redfish_boot._has_vmedia_device(
[mock_manager], sushy.VIRTUAL_MEDIA_FLOPPY,
inserted=True, system=mock_system))

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Fixes Redfish virtual media boot on BMCs that only expose the VirtualMedia
resource on Systems instead of Managers. For more informations, you can see
`bug 2039458 <https://bugs.launchpad.net/sushy/+bug/2039458>`_.

View File

@ -46,6 +46,6 @@ 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>=4.7.0
sushy>=4.8.0
construct>=2.9.39 # MIT
netaddr>=0.9.0 # BSD