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. setting is ignored.
Configuration drives are supported starting with the Wallaby release 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 Layer 3 or DHCP-less ramdisk booting
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -580,3 +580,9 @@ def wrap_ipv6(ip):
if ipaddress.ip_address(ip).version == 6: if ipaddress.ip_address(ip).version == 6:
return "[%s]" % ip return "[%s]" % ip
return 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.i18n import _
from ironic.common import images from ironic.common import images
from ironic.common import swift from ironic.common import swift
from ironic.common import utils
from ironic.conf import CONF from ironic.conf import CONF
from ironic.drivers.modules import boot_mode_utils from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_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: with tempfile.TemporaryFile(dir=CONF.tempdir) as comp_tmpfile_obj:
if '://' in content: 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) images.fetch_into(task.context, content, tmpfile2)
tmpfile2.seek(0) tmpfile2.flush()
base64.decode(tmpfile2, comp_tmpfile_obj)
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: else:
comp_tmpfile_obj.write(base64.b64decode(content)) comp_tmpfile_obj.write(base64.b64decode(content))
comp_tmpfile_obj.seek(0) comp_tmpfile_obj.seek(0)

View File

@ -20,6 +20,7 @@ from unittest import mock
from oslo_utils import importutils from oslo_utils import importutils
from ironic.common import images from ironic.common import images
from ironic.common import utils
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.drivers.modules import image_utils from ironic.drivers.modules import image_utils
from ironic.tests.unit.db import base as db_base 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) result = image_utils.prepare_configdrive_image(task, encoded)
self.assertEqual(expected_url, result) self.assertEqual(expected_url, result)
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(images, 'fetch_into', autospec=True) @mock.patch.object(images, 'fetch_into', autospec=True)
@mock.patch.object(image_utils, 'prepare_disk_image', 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' content = 'https://swift/path'
expected_url = 'https://a.b/c.f?e=f' expected_url = 'https://a.b/c.f?e=f'
encoded = b'H4sIAPJ8418C/0vOzytJzSsBAKkwxf4HAAAA' encoded = b'H4sIAPJ8418C/0vOzytJzSsBAKkwxf4HAAAA'
@ -322,6 +325,33 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
mock_fetch.side_effect = _fetch mock_fetch.side_effect = _fetch
mock_prepare.side_effect = _prepare 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, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: shared=True) as task: