From bb318008b91da907658678ee93d4ab7ae7576c00 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Fri, 22 Jan 2021 15:56:13 +0100 Subject: [PATCH] redfish-virtual-media: allow a link to raw configdrive image For historical reasons we always base64+gzip configdrives, even when accessing them via a URL. This change allows binary images to work for the redfish-virtual-media case. Change-Id: If19144de800b67275e3f8fb297f0a5c4a54b2981 --- doc/source/admin/drivers/redfish.rst | 14 +++++++- ironic/common/utils.py | 6 ++++ ironic/drivers/modules/image_utils.py | 14 ++++++-- .../unit/drivers/modules/test_image_utils.py | 32 ++++++++++++++++++- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/doc/source/admin/drivers/redfish.rst b/doc/source/admin/drivers/redfish.rst index 80b981984a..cc107fd2a5 100644 --- a/doc/source/admin/drivers/redfish.rst +++ b/doc/source/admin/drivers/redfish.rst @@ -272,7 +272,19 @@ parameter injection, as such the ``[instance_info]/kernel_append_params`` setting is ignored. Configuration drives are supported starting with the Wallaby release -for nodes that have a free virtual USB slot. +for nodes that have a free virtual USB slot: + +.. code-block:: bash + + baremetal node deploy \ + --config-drive '{"meta_data": {...}, "user_data": "..."}' + +or via a link to a raw image: + +.. code-block:: bash + + baremetal node deploy \ + --config-drive http://example.com/config.img Layer 3 or DHCP-less ramdisk booting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/ironic/common/utils.py b/ironic/common/utils.py index 7cc0199bfc..529e024774 100644 --- a/ironic/common/utils.py +++ b/ironic/common/utils.py @@ -580,3 +580,9 @@ def wrap_ipv6(ip): if ipaddress.ip_address(ip).version == 6: return "[%s]" % ip return ip + + +def file_mime_type(path): + """Gets a mime type of the given file.""" + return execute('file', '--brief', '--mime-type', path, + use_standard_locale=True)[0].strip() diff --git a/ironic/drivers/modules/image_utils.py b/ironic/drivers/modules/image_utils.py index d12d23b0a2..fb61a8d1dc 100644 --- a/ironic/drivers/modules/image_utils.py +++ b/ironic/drivers/modules/image_utils.py @@ -29,6 +29,7 @@ from ironic.common import exception from ironic.common.i18n import _ from ironic.common import images from ironic.common import swift +from ironic.common import utils from ironic.conf import CONF from ironic.drivers.modules import boot_mode_utils from ironic.drivers.modules import deploy_utils @@ -311,10 +312,17 @@ def prepare_configdrive_image(task, content): """ with tempfile.TemporaryFile(dir=CONF.tempdir) as comp_tmpfile_obj: if '://' in content: - with tempfile.TemporaryFile(dir=CONF.tempdir) as tmpfile2: + with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as tmpfile2: images.fetch_into(task.context, content, tmpfile2) - tmpfile2.seek(0) - base64.decode(tmpfile2, comp_tmpfile_obj) + tmpfile2.flush() + + if utils.file_mime_type(tmpfile2.name) == "text/plain": + tmpfile2.seek(0) + base64.decode(tmpfile2, comp_tmpfile_obj) + else: + # A binary image, use it as it is. + return prepare_disk_image(task, tmpfile2.name, + prefix='configdrive') else: comp_tmpfile_obj.write(base64.b64decode(content)) comp_tmpfile_obj.seek(0) diff --git a/ironic/tests/unit/drivers/modules/test_image_utils.py b/ironic/tests/unit/drivers/modules/test_image_utils.py index c4d817af20..2ebb84c5ef 100644 --- a/ironic/tests/unit/drivers/modules/test_image_utils.py +++ b/ironic/tests/unit/drivers/modules/test_image_utils.py @@ -20,6 +20,7 @@ from unittest import mock from oslo_utils import importutils from ironic.common import images +from ironic.common import utils from ironic.conductor import task_manager from ironic.drivers.modules import image_utils from ironic.tests.unit.db import base as db_base @@ -304,9 +305,11 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): result = image_utils.prepare_configdrive_image(task, encoded) self.assertEqual(expected_url, result) + @mock.patch.object(utils, 'execute', autospec=True) @mock.patch.object(images, 'fetch_into', autospec=True) @mock.patch.object(image_utils, 'prepare_disk_image', autospec=True) - def test_prepare_configdrive_image_url(self, mock_prepare, mock_fetch): + def test_prepare_configdrive_image_url(self, mock_prepare, mock_fetch, + mock_execute): content = 'https://swift/path' expected_url = 'https://a.b/c.f?e=f' encoded = b'H4sIAPJ8418C/0vOzytJzSsBAKkwxf4HAAAA' @@ -322,6 +325,33 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): mock_fetch.side_effect = _fetch mock_prepare.side_effect = _prepare + mock_execute.return_value = 'text/plain', '' + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + result = image_utils.prepare_configdrive_image(task, content) + self.assertEqual(expected_url, result) + + @mock.patch.object(utils, 'execute', autospec=True) + @mock.patch.object(images, 'fetch_into', autospec=True) + @mock.patch.object(image_utils, 'prepare_disk_image', autospec=True) + def test_prepare_configdrive_image_binary_url(self, mock_prepare, + mock_fetch, mock_execute): + content = 'https://swift/path' + expected_url = 'https://a.b/c.f?e=f' + + def _fetch(context, image_href, image_file): + self.assertEqual(content, image_href) + image_file.write(b'content') + + def _prepare(task, content, prefix): + with open(content, 'rb') as fp: + self.assertEqual(b'content', fp.read()) + return expected_url + + mock_fetch.side_effect = _fetch + mock_prepare.side_effect = _prepare + mock_execute.return_value = 'application/octet-stream' with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: