Add Support testing for HttpBootUri

Maps the HttpBootUri to the virtual media functionality, so we can
leverage sushy-tools for testing HttpBootUri functionality.

Change-Id: I51a7e684bafdb6b3aa9adda35ae438c747c9847a
This commit is contained in:
Julia Kreger 2023-09-29 14:03:43 -07:00
parent 0246649c06
commit e14cfe361d
9 changed files with 136 additions and 6 deletions

View File

@ -0,0 +1,6 @@
---
features:
- |
Adds support to handle ``HttpBootUri`` being posted to the node, which
maps to the virtual media functionality, because there is not a direct
analog setting when interacting with libvirt.

View File

@ -0,0 +1,9 @@
---
features:
- |
Adds basic functionality for ``HttpBootUri`` to be passed through to
the libvirt driver, enabling boot operations utilizing supplied media.
This does not influence the default URL to boot from due to a lack of
capability in libvirt, but instead treats it similar to virtual media.
In this case, an override boot target of ``UefiHttp`` is also re-mapped
to ``Cd`` to facilitate testing.

View File

@ -392,7 +392,8 @@ def system_resource(identity):
managers=app.managers.get_managers_for_system(identity),
chassis=app.chassis.chassis[:1],
indicator_led=app.indicators.get_indicator_state(
app.systems.uuid(identity))
app.systems.uuid(identity)),
http_boot_uri=try_get(app.systems.get_http_boot_uri)
)
elif flask.request.method == 'PATCH':
@ -405,6 +406,12 @@ def system_resource(identity):
if boot:
target = boot.get('BootSourceOverrideTarget')
if target == 'UefiHttp':
# Reset to Cd, in our case, since we can't force override
# the network boot to a specific URL. This is sort of a hack
# but testing functionality overall is a bit more important.
target = 'Cd'
if target:
# NOTE(lucasagomes): In libvirt we always set the boot
# device frequency to "continuous" so, we are ignoring the
@ -423,9 +430,30 @@ def system_resource(identity):
app.logger.info('Set boot mode to "%s" for system "%s"',
mode, identity)
if not target and not mode:
http_uri = boot.get('HttpBootUri')
if http_uri:
try:
# Download the image
image_path = flask.current_app.vmedia.insert_image(
identity, 'Cd', http_uri)
# Mount it as an ISO
flask.current_app.systems.set_boot_image(
'Cd', boot_image=image_path,
write_protected=True)
# Set it for our emulator's API surface to return it
# if queried.
flask.current_app.systems.set_http_boot_uri(http_uri)
except Exception as e:
app.logger.error('Unable to load HttpBootUri for boot '
'operation. Error: %s', e)
return '', 400
if not target and not mode and not http_uri:
return ('Missing the BootSourceOverrideTarget and/or '
'BootSourceOverrideMode element', 400)
'BootSourceOverrideMode and/or HttpBootUri '
'element', 400)
if indicator_led_state:
app.indicators.set_indicator_state(

View File

@ -233,3 +233,21 @@ class AbstractSystemsDriver(metaclass=abc.ABCMeta):
:returns: Id of the volume if successfully found/created else None
"""
raise error.NotSupportedError('Not implemented')
def get_http_boot_uri(self, identity):
"""Return the URI stored for the HttpBootUri.
:param identity: The libvirt identity. Unused, exists for internal
sushy-tools compatability.
:returns: Stored URI value for HttpBootURI.
"""
raise error.NotSupportedError('Not implemented')
def set_http_boot_uri(self, uri):
"""Stores the Uri for HttpBootURI.
:param uri: String to return
:returns: None
"""
raise error.NotSupportedError('Not implemented')

View File

@ -172,6 +172,7 @@ class LibvirtDriver(AbstractSystemsDriver):
cls.SECURE_BOOT_DISABLED_NVRAM)
cls.SUSHY_EMULATOR_IGNORE_BOOT_DEVICE = \
cls._config.get('SUSHY_EMULATOR_IGNORE_BOOT_DEVICE', False)
cls._http_boot_uri = None
return cls
@memoize.memoize()
@ -1353,3 +1354,21 @@ class LibvirtDriver(AbstractSystemsDriver):
self._logger.debug(msg)
return
return data['Id']
def get_http_boot_uri(self, identity):
"""Return the URI stored for the HttpBootUri.
:param identity: The libvirt identity. Unused, exists for internal
sushy-tools compatability.
:returns: Stored URI value for HttpBootURI.
"""
return self._http_boot_uri
def set_http_boot_uri(self, uri):
"""Stores the Uri for HttpBootURI.
:param uri: String to return
:returns: None
"""
self._http_boot_uri = uri

View File

@ -19,16 +19,21 @@
"BootSourceOverrideTarget@Redfish.AllowableValues": [
"Pxe",
"Cd",
"Hdd"
{%- if boot_source_mode %}
],
{%- if 'uefi' in boot_source_mode.lower() %}
"Hdd",
"UefiHttp"
],
"BootSourceOverrideMode": {{ boot_source_mode|string|tojson }},
"UefiTargetBootSourceOverride": "/0x31/0x33/0x01/0x01"
"UefiTargetBootSourceOverride": "/0x31/0x33/0x01/0x01",
"HttpBootUri": {{ http_boot_uri|string|tojson }}
{%- else %}
"Hdd"
],
"BootSourceOverrideMode": {{ boot_source_mode|string|tojson }}
{%- endif %}
{%- else %}
"Hdd"
]
{%- endif %}
{%- else %}

View File

@ -1245,3 +1245,17 @@ class LibvirtDriverTestCase(base.BaseTestCase):
self.assertRaises(error.NotSupportedError,
self.test_driver.set_secure_boot, self.uuid, True)
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_get_http_boot_uri(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35.xml', 'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
self.assertIsNone(self.test_driver.get_http_boot_uri(None))
uri = 'http://host.path/meow'
self.test_driver.set_http_boot_uri(uri)
self.assertEqual(uri, self.test_driver.get_http_boot_uri(None))

View File

@ -287,3 +287,11 @@ class NovaDriverTestCase(base.BaseTestCase):
self.assertRaises(
error.NotSupportedError, self.test_driver.set_secure_boot,
self.uuid, True)
def test_set_get_http_boot_uri(self):
self.assertRaises(error.NotSupportedError,
self.test_driver.get_http_boot_uri,
None)
self.assertRaises(error.NotSupportedError,
self.test_driver.set_http_boot_uri,
None)

View File

@ -273,6 +273,29 @@ class SystemsTestCase(EmulatorTestCase):
set_boot_device = systems_mock.return_value.set_boot_device
set_boot_device.assert_called_once_with('xxxx-yyyy-zzzz', 'Cd')
@patch_resource('vmedia')
@patch_resource('systems')
def test_system_boot_http_uri(self, systems_mock, vmedia_mock):
data = {'Boot': {'BootSourceOverrideMode': 'UEFI',
'BootSourceOverrideTarget': 'UefiHttp',
'HttpBootUri': 'http://test.url/boot.iso'}}
insert_image = vmedia_mock.return_value.insert_image
insert_image.return_value = '/path/to/file.iso'
response = self.app.patch('/redfish/v1/Systems/xxxx-yyyy-zzzz',
json=data)
self.assertEqual(204, response.status_code)
insert_image.assert_called_once_with('xxxx-yyyy-zzzz', 'Cd',
'http://test.url/boot.iso')
set_boot_device = systems_mock.return_value.set_boot_device
set_boot_image = systems_mock.return_value.set_boot_image
set_boot_mode = systems_mock.return_value.set_boot_mode
set_http_boot_uri = systems_mock.return_value.set_http_boot_uri
set_boot_device.assert_called_once_with('xxxx-yyyy-zzzz', 'Cd')
set_boot_image.assert_called_once_with(
'Cd', boot_image='/path/to/file.iso', write_protected=True)
set_boot_mode.assert_called_once_with('xxxx-yyyy-zzzz', 'UEFI')
set_http_boot_uri.assert_called_once_with('http://test.url/boot.iso')
@patch_resource('systems')
def test_system_reset_action(self, systems_mock):
set_power_state = systems_mock.return_value.set_power_state