diff --git a/releasenotes/notes/add-http-boot-uri-handling-c678bd89c7f6346c.yaml b/releasenotes/notes/add-http-boot-uri-handling-c678bd89c7f6346c.yaml new file mode 100644 index 00000000..0e874f66 --- /dev/null +++ b/releasenotes/notes/add-http-boot-uri-handling-c678bd89c7f6346c.yaml @@ -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. diff --git a/releasenotes/notes/add-http-to-vmedia-ef04c156e66fc121.yaml b/releasenotes/notes/add-http-to-vmedia-ef04c156e66fc121.yaml new file mode 100644 index 00000000..21700bc2 --- /dev/null +++ b/releasenotes/notes/add-http-to-vmedia-ef04c156e66fc121.yaml @@ -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. diff --git a/sushy_tools/emulator/main.py b/sushy_tools/emulator/main.py index 192fb9ad..3803bd78 100755 --- a/sushy_tools/emulator/main.py +++ b/sushy_tools/emulator/main.py @@ -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( diff --git a/sushy_tools/emulator/resources/systems/base.py b/sushy_tools/emulator/resources/systems/base.py index 440e8cd6..5907216d 100644 --- a/sushy_tools/emulator/resources/systems/base.py +++ b/sushy_tools/emulator/resources/systems/base.py @@ -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') diff --git a/sushy_tools/emulator/resources/systems/libvirtdriver.py b/sushy_tools/emulator/resources/systems/libvirtdriver.py index 66a517d9..be943dd9 100644 --- a/sushy_tools/emulator/resources/systems/libvirtdriver.py +++ b/sushy_tools/emulator/resources/systems/libvirtdriver.py @@ -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 diff --git a/sushy_tools/emulator/templates/system.json b/sushy_tools/emulator/templates/system.json index 5237b4c5..b982378d 100644 --- a/sushy_tools/emulator/templates/system.json +++ b/sushy_tools/emulator/templates/system.json @@ -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 %} diff --git a/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py b/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py index 0317903e..ed5812fc 100644 --- a/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py +++ b/sushy_tools/tests/unit/emulator/resources/systems/test_libvirt.py @@ -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)) diff --git a/sushy_tools/tests/unit/emulator/resources/systems/test_nova.py b/sushy_tools/tests/unit/emulator/resources/systems/test_nova.py index 9b10a0f9..e454a27e 100644 --- a/sushy_tools/tests/unit/emulator/resources/systems/test_nova.py +++ b/sushy_tools/tests/unit/emulator/resources/systems/test_nova.py @@ -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) diff --git a/sushy_tools/tests/unit/emulator/test_main.py b/sushy_tools/tests/unit/emulator/test_main.py index 9bc43132..2e6d1d28 100644 --- a/sushy_tools/tests/unit/emulator/test_main.py +++ b/sushy_tools/tests/unit/emulator/test_main.py @@ -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