Skip to content

Commit 69709c1

Browse files
authored
Merge pull request #89 from stackhpc/zed-ossa-2024-002
Zed: Backport fixes for OSSA-2024-002
2 parents b69ae07 + a6a573b commit 69709c1

File tree

5 files changed

+138
-66
lines changed

5 files changed

+138
-66
lines changed

bindep.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,5 @@ pcre-devel [platform:rpm test]
5454
# runtime and in unit tests. Net result is the same that lsscsi will be
5555
# installed for any nova installation.
5656
lsscsi
57+
# NOTE(priteau): Install qemu-img for format inspector tests
58+
qemu-utils [platform:dpkg test]

nova/tests/unit/console/test_websocketproxy.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,9 @@ def test_reject_open_redirect(self, url='//example.com/%2F..'):
635635
# now the same url but with extra leading '/' characters removed.
636636
if expected_cpython in errmsg:
637637
location = result[3].decode()
638-
location = location.removeprefix('Location: ').rstrip('\r\n')
638+
if location.startswith('Location: '):
639+
location = location[len('Location: '):]
640+
location = location.rstrip('\r\n')
639641
self.assertTrue(
640642
location.startswith('/example.com/%2F..'),
641643
msg='Redirect location is not the expected sanitized URL',

nova/tests/unit/virt/libvirt/test_utils.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,12 @@ def test_fetch_initrd_image(self, mock_images):
405405
_context, image_id, target, trusted_certs)
406406

407407
@mock.patch.object(images, 'IMAGE_API')
408-
@mock.patch.object(format_inspector, 'get_inspector')
408+
@mock.patch.object(format_inspector, 'detect_file_format')
409409
@mock.patch.object(compute_utils, 'disk_ops_semaphore')
410410
@mock.patch('nova.privsep.utils.supports_direct_io', return_value=True)
411411
@mock.patch('nova.privsep.qemu.unprivileged_convert_image')
412412
def test_fetch_raw_image(self, mock_convert_image, mock_direct_io,
413-
mock_disk_op_sema, mock_gi, mock_glance):
413+
mock_disk_op_sema, mock_detect, mock_glance):
414414

415415
def fake_rename(old, new):
416416
self.executes.append(('mv', old, new))
@@ -450,7 +450,7 @@ class FakeImgInfo(object):
450450
self.stub_out('oslo_utils.fileutils.delete_if_exists',
451451
fake_rm_on_error)
452452

453-
mock_inspector = mock_gi.return_value.from_file.return_value
453+
mock_inspector = mock_detect.return_value
454454

455455
# Since the remove param of fileutils.remove_path_on_error()
456456
# is initialized at load time, we must provide a wrapper
@@ -464,6 +464,7 @@ class FakeImgInfo(object):
464464

465465
# Make sure qcow2 gets converted to raw
466466
mock_inspector.safety_check.return_value = True
467+
mock_inspector.__str__.return_value = 'qcow2'
467468
mock_glance.get.return_value = {'disk_format': 'qcow2'}
468469
target = 't.qcow2'
469470
self.executes = []
@@ -477,12 +478,13 @@ class FakeImgInfo(object):
477478
CONF.instances_path, False)
478479
mock_convert_image.reset_mock()
479480
mock_inspector.safety_check.assert_called_once_with()
480-
mock_gi.assert_called_once_with('qcow2')
481+
mock_detect.assert_called_once_with('t.qcow2.part')
481482

482483
# Make sure raw does not get converted
483-
mock_gi.reset_mock()
484+
mock_detect.reset_mock()
484485
mock_inspector.safety_check.reset_mock()
485486
mock_inspector.safety_check.return_value = True
487+
mock_inspector.__str__.return_value = 'raw'
486488
mock_glance.get.return_value = {'disk_format': 'raw'}
487489
target = 't.raw'
488490
self.executes = []
@@ -491,12 +493,13 @@ class FakeImgInfo(object):
491493
self.assertEqual(self.executes, expected_commands)
492494
mock_convert_image.assert_not_called()
493495
mock_inspector.safety_check.assert_called_once_with()
494-
mock_gi.assert_called_once_with('raw')
496+
mock_detect.assert_called_once_with('t.raw.part')
495497

496498
# Make sure safety check failure prevents us from proceeding
497-
mock_gi.reset_mock()
499+
mock_detect.reset_mock()
498500
mock_inspector.safety_check.reset_mock()
499501
mock_inspector.safety_check.return_value = False
502+
mock_inspector.__str__.return_value = 'qcow2'
500503
mock_glance.get.return_value = {'disk_format': 'qcow2'}
501504
target = 'backing.qcow2'
502505
self.executes = []
@@ -506,10 +509,10 @@ class FakeImgInfo(object):
506509
self.assertEqual(self.executes, expected_commands)
507510
mock_convert_image.assert_not_called()
508511
mock_inspector.safety_check.assert_called_once_with()
509-
mock_gi.assert_called_once_with('qcow2')
512+
mock_detect.assert_called_once_with('backing.qcow2.part')
510513

511514
# Make sure a format mismatch prevents us from proceeding
512-
mock_gi.reset_mock()
515+
mock_detect.reset_mock()
513516
mock_inspector.safety_check.reset_mock()
514517
mock_inspector.safety_check.side_effect = (
515518
format_inspector.ImageFormatError)
@@ -522,7 +525,7 @@ class FakeImgInfo(object):
522525
self.assertEqual(self.executes, expected_commands)
523526
mock_convert_image.assert_not_called()
524527
mock_inspector.safety_check.assert_called_once_with()
525-
mock_gi.assert_called_once_with('qcow2')
528+
mock_detect.assert_called_once_with('backing.qcow2.part')
526529

527530
del self.executes
528531

nova/tests/unit/virt/test_images.py

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
from nova.compute import utils as compute_utils
2323
from nova import exception
24-
from nova.image import format_inspector
2524
from nova import test
2625
from nova.virt import images
2726

@@ -101,15 +100,16 @@ def test_qemu_img_info_with_disk_not_found(self, exists, mocked_execute):
101100
mocked_execute.assert_called_once()
102101

103102
@mock.patch.object(images, 'IMAGE_API')
104-
@mock.patch('nova.image.format_inspector.get_inspector')
103+
@mock.patch('nova.image.format_inspector.detect_file_format')
105104
@mock.patch.object(images, 'convert_image',
106105
side_effect=exception.ImageUnacceptable)
107106
@mock.patch.object(images, 'qemu_img_info')
108107
@mock.patch.object(images, 'fetch')
109108
def test_fetch_to_raw_errors(self, convert_image, qemu_img_info, fetch,
110-
get_inspector, glance):
111-
inspector = get_inspector.return_value.from_file.return_value
109+
mock_detect, glance):
110+
inspector = mock_detect.return_value
112111
inspector.safety_check.return_value = True
112+
inspector.__str__.return_value = 'qcow2'
113113
glance.get.return_value = {'disk_format': 'qcow2'}
114114
qemu_img_info.backing_file = None
115115
qemu_img_info.file_format = 'qcow2'
@@ -120,16 +120,17 @@ def test_fetch_to_raw_errors(self, convert_image, qemu_img_info, fetch,
120120
None, 'href123', '/no/path')
121121

122122
@mock.patch.object(images, 'IMAGE_API')
123-
@mock.patch('nova.image.format_inspector.get_inspector')
123+
@mock.patch('nova.image.format_inspector.detect_file_format')
124124
@mock.patch.object(images, 'convert_image',
125125
side_effect=exception.ImageUnacceptable)
126126
@mock.patch.object(images, 'qemu_img_info')
127127
@mock.patch.object(images, 'fetch')
128128
def test_fetch_to_raw_data_file(self, convert_image, qemu_img_info_fn,
129-
fetch, mock_gi, mock_glance):
129+
fetch, mock_detect, mock_glance):
130130
mock_glance.get.return_value = {'disk_format': 'qcow2'}
131-
inspector = mock_gi.return_value.from_file.return_value
131+
inspector = mock_detect.return_value
132132
inspector.safety_check.return_value = True
133+
inspector.__str__.return_value = 'qcow2'
133134
# NOTE(danms): the above test needs the following line as well, as it
134135
# is broken without it.
135136
qemu_img_info = qemu_img_info_fn.return_value
@@ -142,16 +143,17 @@ def test_fetch_to_raw_data_file(self, convert_image, qemu_img_info_fn,
142143
images.fetch_to_raw,
143144
None, 'href123', '/no/path')
144145

145-
@mock.patch('nova.image.format_inspector.get_inspector')
146+
@mock.patch('nova.image.format_inspector.detect_file_format')
146147
@mock.patch.object(images, 'IMAGE_API')
147148
@mock.patch('os.rename')
148149
@mock.patch.object(images, 'qemu_img_info')
149150
@mock.patch.object(images, 'fetch')
150151
def test_fetch_to_raw_from_raw(self, fetch, qemu_img_info_fn, mock_rename,
151-
mock_glance, mock_gi):
152+
mock_glance, mock_detect):
152153
# Make sure we support a case where we fetch an already-raw image and
153154
# qemu-img returns None for "format_specific".
154155
mock_glance.get.return_value = {'disk_format': 'raw'}
156+
mock_detect.return_value.__str__.return_value = 'raw'
155157
qemu_img_info = qemu_img_info_fn.return_value
156158
qemu_img_info.file_format = 'raw'
157159
qemu_img_info.backing_file = None
@@ -215,14 +217,15 @@ def test_convert_image_vmdk_allowed_list_checking(self):
215217
format='json'))
216218

217219
@mock.patch.object(images, 'IMAGE_API')
218-
@mock.patch('nova.image.format_inspector.get_inspector')
220+
@mock.patch('nova.image.format_inspector.detect_file_format')
219221
@mock.patch.object(images, 'fetch')
220222
@mock.patch('nova.privsep.qemu.unprivileged_qemu_img_info')
221-
def test_fetch_checks_vmdk_rules(self, mock_info, mock_fetch, mock_gi,
223+
def test_fetch_checks_vmdk_rules(self, mock_info, mock_fetch, mock_detect,
222224
mock_glance):
223225
mock_glance.get.return_value = {'disk_format': 'vmdk'}
224-
inspector = mock_gi.return_value.from_file.return_value
226+
inspector = mock_detect.return_value
225227
inspector.safety_check.return_value = True
228+
inspector.__str__.return_value = 'vmdk'
226229
info = {'format': 'vmdk',
227230
'format-specific': {
228231
'type': 'vmdk',
@@ -235,22 +238,54 @@ def test_fetch_checks_vmdk_rules(self, mock_info, mock_fetch, mock_gi,
235238
images.fetch_to_raw, None, 'foo', 'anypath')
236239
self.assertIn('Invalid VMDK create-type specified', str(e))
237240

241+
@mock.patch('os.rename')
238242
@mock.patch.object(images, 'IMAGE_API')
239243
@mock.patch('nova.image.format_inspector.get_inspector')
244+
@mock.patch('nova.image.format_inspector.detect_file_format')
245+
@mock.patch.object(images, 'fetch')
246+
@mock.patch('nova.privsep.qemu.unprivileged_qemu_img_info')
247+
def test_fetch_iso_is_raw(
248+
self, mock_info, mock_fetch, mock_detect_file_format, mock_gi,
249+
mock_glance, mock_rename):
250+
mock_glance.get.return_value = {'disk_format': 'iso'}
251+
inspector = mock_gi.return_value.from_file.return_value
252+
inspector.safety_check.return_value = True
253+
inspector.__str__.return_value = 'iso'
254+
mock_detect_file_format.return_value = inspector
255+
# qemu-img does not have a parser for iso so it is treated as raw
256+
info = {
257+
"virtual-size": 356352,
258+
"filename": "foo.iso",
259+
"format": "raw",
260+
"actual-size": 356352,
261+
"dirty-flag": False
262+
}
263+
mock_info.return_value = jsonutils.dumps(info)
264+
with mock.patch('os.path.exists', return_value=True):
265+
images.fetch_to_raw(None, 'foo', 'anypath')
266+
# Make sure we called info with -f raw for an iso, since qemu-img does
267+
# not support iso
268+
mock_info.assert_called_once_with('anypath.part', format=None)
269+
# Make sure that since we considered this to be a raw file, we did the
270+
# just-rename-don't-convert path
271+
mock_rename.assert_called_once_with('anypath.part', 'anypath')
272+
273+
@mock.patch.object(images, 'IMAGE_API')
274+
@mock.patch('nova.image.format_inspector.detect_file_format')
240275
@mock.patch.object(images, 'qemu_img_info')
241276
@mock.patch.object(images, 'fetch')
242-
def test_fetch_to_raw_inspector(self, fetch, qemu_img_info, mock_gi,
277+
def test_fetch_to_raw_inspector(self, fetch, qemu_img_info, mock_detect,
243278
mock_glance):
244279
# Image claims to be qcow2, is qcow2, but fails safety check, so we
245280
# abort before qemu-img-info
246281
mock_glance.get.return_value = {'disk_format': 'qcow2'}
247-
inspector = mock_gi.return_value.from_file.return_value
282+
inspector = mock_detect.return_value
248283
inspector.safety_check.return_value = False
284+
inspector.__str__.return_value = 'qcow2'
249285
self.assertRaises(exception.ImageUnacceptable,
250286
images.fetch_to_raw, None, 'href123', '/no.path')
251287
qemu_img_info.assert_not_called()
252-
mock_gi.assert_called_once_with('qcow2')
253-
mock_gi.return_value.from_file.assert_called_once_with('/no.path.part')
288+
mock_detect.assert_called_once_with('/no.path.part')
254289
inspector.safety_check.assert_called_once_with()
255290
mock_glance.get.assert_called_once_with(None, 'href123')
256291

@@ -264,18 +299,17 @@ def test_fetch_to_raw_inspector(self, fetch, qemu_img_info, mock_gi,
264299
# Image claims to be qcow2 in glance, but the image is something else,
265300
# so we abort before qemu-img-info
266301
qemu_img_info.reset_mock()
267-
mock_gi.reset_mock()
302+
mock_detect.reset_mock()
268303
inspector.safety_check.reset_mock()
269-
mock_gi.return_value.from_file.side_effect = (
270-
format_inspector.ImageFormatError)
304+
mock_detect.return_value.__str__.return_value = 'vmdk'
271305
self.assertRaises(exception.ImageUnacceptable,
272306
images.fetch_to_raw, None, 'href123', '/no.path')
273-
mock_gi.assert_called_once_with('qcow2')
274-
inspector.safety_check.assert_not_called()
307+
mock_detect.assert_called_once_with('/no.path.part')
308+
inspector.safety_check.assert_called_once_with()
275309
qemu_img_info.assert_not_called()
276310

277311
@mock.patch.object(images, 'IMAGE_API')
278-
@mock.patch('nova.image.format_inspector.get_inspector')
312+
@mock.patch('nova.image.format_inspector.detect_file_format')
279313
@mock.patch.object(images, 'qemu_img_info')
280314
@mock.patch.object(images, 'fetch')
281315
def test_fetch_to_raw_inspector_disabled(self, fetch, qemu_img_info,
@@ -288,36 +322,41 @@ def test_fetch_to_raw_inspector_disabled(self, fetch, qemu_img_info,
288322
# If deep inspection is disabled, we should never call the inspector
289323
mock_gi.assert_not_called()
290324
# ... and we let qemu-img detect the format itself.
291-
qemu_img_info.assert_called_once_with('/no.path.part',
292-
format=None)
325+
qemu_img_info.assert_called_once_with('/no.path.part')
293326
mock_glance.get.assert_not_called()
294327

295328
@mock.patch.object(images, 'IMAGE_API')
296329
@mock.patch.object(images, 'qemu_img_info')
297-
def test_fetch_inspect_ami(self, imginfo, glance):
330+
@mock.patch('nova.image.format_inspector.detect_file_format')
331+
def test_fetch_inspect_ami(self, detect, imginfo, glance):
298332
glance.get.return_value = {'disk_format': 'ami'}
333+
detect.return_value.__str__.return_value = 'raw'
299334
self.assertRaises(exception.ImageUnacceptable,
300335
images.fetch_to_raw, None, 'href123', '/no.path')
301336
# Make sure 'ami was translated into 'raw' before we call qemu-img
302-
imginfo.assert_called_once_with('/no.path.part', format='raw')
337+
imginfo.assert_called_once_with('/no.path.part')
303338

304339
@mock.patch.object(images, 'IMAGE_API')
305340
@mock.patch.object(images, 'qemu_img_info')
306-
def test_fetch_inspect_aki(self, imginfo, glance):
341+
@mock.patch('nova.image.format_inspector.detect_file_format')
342+
def test_fetch_inspect_aki(self, detect, imginfo, glance):
307343
glance.get.return_value = {'disk_format': 'aki'}
344+
detect.return_value.__str__.return_value = 'raw'
308345
self.assertRaises(exception.ImageUnacceptable,
309346
images.fetch_to_raw, None, 'href123', '/no.path')
310347
# Make sure 'aki was translated into 'raw' before we call qemu-img
311-
imginfo.assert_called_once_with('/no.path.part', format='raw')
348+
imginfo.assert_called_once_with('/no.path.part')
312349

313350
@mock.patch.object(images, 'IMAGE_API')
314351
@mock.patch.object(images, 'qemu_img_info')
315-
def test_fetch_inspect_ari(self, imginfo, glance):
352+
@mock.patch('nova.image.format_inspector.detect_file_format')
353+
def test_fetch_inspect_ari(self, detect, imginfo, glance):
316354
glance.get.return_value = {'disk_format': 'ari'}
355+
detect.return_value.__str__.return_value = 'raw'
317356
self.assertRaises(exception.ImageUnacceptable,
318357
images.fetch_to_raw, None, 'href123', '/no.path')
319358
# Make sure 'aki was translated into 'raw' before we call qemu-img
320-
imginfo.assert_called_once_with('/no.path.part', format='raw')
359+
imginfo.assert_called_once_with('/no.path.part')
321360

322361
@mock.patch.object(images, 'IMAGE_API')
323362
@mock.patch.object(images, 'qemu_img_info')
@@ -330,13 +369,16 @@ def test_fetch_inspect_unknown_format(self, imginfo, glance):
330369

331370
@mock.patch.object(images, 'IMAGE_API')
332371
@mock.patch.object(images, 'qemu_img_info')
333-
@mock.patch('nova.image.format_inspector.get_inspector')
334-
def test_fetch_inspect_disagrees_qemu(self, mock_gi, imginfo, glance):
372+
@mock.patch('nova.image.format_inspector.detect_file_format')
373+
def test_fetch_inspect_disagrees_qemu(self, mock_detect, imginfo, glance):
335374
glance.get.return_value = {'disk_format': 'qcow2'}
375+
mock_detect.return_value.__str__.return_value = 'qcow2'
336376
# Glance and inspector think it is a qcow2 file, but qemu-img does not
337-
# agree. It was forced to interpret as a qcow2, but returned no
338-
# format information as a result.
377+
# agree.
339378
imginfo.return_value.data_file = None
340-
self.assertRaises(exception.ImageUnacceptable,
341-
images.fetch_to_raw, None, 'href123', '/no.path')
342-
imginfo.assert_called_once_with('/no.path.part', format='qcow2')
379+
imginfo.return_value.file_format = 'vmdk'
380+
ex = self.assertRaises(exception.ImageUnacceptable,
381+
images.fetch_to_raw,
382+
None, 'href123', '/no.path')
383+
self.assertIn('content does not match disk_format', str(ex))
384+
imginfo.assert_called_once_with('/no.path.part')

0 commit comments

Comments
 (0)