Fix distribution compatability for configdrive build

Previously, the configuration drive generation code made
use of ``genisoimage``, however ``genisoimage`` is not shipped
universally on all linux distributions, and largely has been
replaced in distributions with other forks, as the tooling has
evolved, forked, and changed over the past quarter century.

We now attempt to utilize multiple different commands, including
the original ``mkisofs`` command and the newer ``xorrisofs``
command when attempting to generate the ISO image, falling back
until one works.

Credit goes to I720f25921f8e52f20a631f238a528dedf65a91c6 for
the base pattern in OpenstackSDK.

Story: 2009230
Task: 43328
Change-Id: Ic732c2e6c77474e0d5b701c47758959c3511743b
This commit is contained in:
Julia Kreger 2021-09-15 09:50:34 -07:00
parent bc642ccabc
commit 0555b6ba72
3 changed files with 69 additions and 15 deletions

View File

@ -267,20 +267,30 @@ def make_configdrive(path):
with tempfile.NamedTemporaryFile() as tmpfile:
with tempfile.NamedTemporaryFile() as tmpzipfile:
publisher = 'ironicclient-configdrive 0.1'
try:
p = subprocess.Popen(['genisoimage', '-o', tmpfile.name,
'-ldots', '-allow-lowercase',
'-allow-multidot', '-l',
'-publisher', publisher,
'-quiet', '-J',
'-r', '-V', 'config-2',
path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except OSError as e:
# NOTE(toabctl): Luckily, genisoimage, mkisofs and xorrisofs
# understand the same parameters which are currently used.
cmds = ['genisoimage', 'mkisofs', 'xorrisofs']
for c in cmds:
try:
p = subprocess.Popen([c, '-o', tmpfile.name,
'-ldots', '-allow-lowercase',
'-allow-multidot', '-l',
'-publisher', publisher,
'-quiet', '-J',
'-r', '-V', 'config-2',
path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except OSError as e:
error = e
else:
error = None
break
if error:
raise exc.CommandError(
_('Error generating the config drive. Make sure the '
'"genisoimage" tool is installed. Error: %s') % e)
'"genisoimage", "mkisofs", or "xorriso" tool is '
'installed. Error: %s') % error)
stdout, stderr = p.communicate()
if p.returncode != 0:

View File

@ -265,6 +265,20 @@ class MakeConfigDriveTest(test_utils.BaseTestCase):
'-quiet', '-J', '-r', '-V',
'config-2', mock.ANY]
self.mkisofs_cmd = ['mkisofs', '-o', mock.ANY,
'-ldots', '-allow-lowercase',
'-allow-multidot', '-l',
'-publisher', 'ironicclient-configdrive 0.1',
'-quiet', '-J', '-r', '-V',
'config-2', mock.ANY]
self.xorrisofs_cmd = ['xorrisofs', '-o', mock.ANY,
'-ldots', '-allow-lowercase',
'-allow-multidot', '-l',
'-publisher', 'ironicclient-configdrive 0.1',
'-quiet', '-J', '-r', '-V',
'config-2', mock.ANY]
def test_make_configdrive(self, mock_popen):
fake_process = mock.Mock(returncode=0)
fake_process.communicate.return_value = ('', '')
@ -278,6 +292,24 @@ class MakeConfigDriveTest(test_utils.BaseTestCase):
stdout=subprocess.PIPE)
fake_process.communicate.assert_called_once_with()
def test_make_configdrive_fallsback(self, mock_popen):
fake_process = mock.Mock(returncode=0)
fake_process.communicate.return_value = ('', '')
mock_popen.side_effect = iter([OSError('boom'),
OSError('boom'),
fake_process])
with utils.tempdir() as dirname:
utils.make_configdrive(dirname)
mock_popen.assert_has_calls([
mock.call(self.genisoimage_cmd, stderr=subprocess.PIPE,
stdout=subprocess.PIPE),
mock.call(self.mkisofs_cmd, stderr=subprocess.PIPE,
stdout=subprocess.PIPE),
mock.call(self.xorrisofs_cmd, stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
])
fake_process.communicate.assert_called_once_with()
@mock.patch.object(os, 'access', autospec=True)
def test_make_configdrive_non_readable_dir(self, mock_access, mock_popen):
mock_access.return_value = False
@ -292,9 +324,14 @@ class MakeConfigDriveTest(test_utils.BaseTestCase):
self.assertRaises(exc.CommandError, utils.make_configdrive, 'fake-dir')
mock_access.assert_called_once_with('fake-dir', os.R_OK)
mock_popen.assert_called_once_with(self.genisoimage_cmd,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
mock_popen.assert_has_calls([
mock.call(self.genisoimage_cmd, stderr=subprocess.PIPE,
stdout=subprocess.PIPE),
mock.call(self.mkisofs_cmd, stderr=subprocess.PIPE,
stdout=subprocess.PIPE),
mock.call(self.xorrisofs_cmd, stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
])
@mock.patch.object(os, 'access', autospec=True)
def test_make_configdrive_non_zero_returncode(self, mock_access,

View File

@ -0,0 +1,7 @@
---
fixes:
- |
Embedded configuration drive generation support has been updated to
support ``mkisofs`` and ``xorrisofs`` in adition to the previously
supported ``genisoimage`` utility. This is as distributions such as
Debian and OpenSUSE do not ship ``genisoimage``.