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
This commit is contained in:
Dmitry Tantsur 2021-01-22 15:56:13 +01:00
parent 5640860c81
commit bb318008b9
4 changed files with 61 additions and 5 deletions

View File

@ -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 <node name or UUID> \
--config-drive '{"meta_data": {...}, "user_data": "..."}'
or via a link to a raw image:
.. code-block:: bash
baremetal node deploy <node name or UUID> \
--config-drive http://example.com/config.img
Layer 3 or DHCP-less ramdisk booting
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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()

View File

@ -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)

View File

@ -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: