Merge "image_utils: Detect missing device before calling qemu-img convert"

This commit is contained in:
Zuul
2025-12-03 14:47:19 +00:00
committed by Gerrit Code Review
4 changed files with 44 additions and 0 deletions

View File

@@ -51,6 +51,7 @@ from cinder.i18n import _
from cinder.image import accelerator from cinder.image import accelerator
from cinder.image import glance from cinder.image import glance
import cinder.privsep.format_inspector import cinder.privsep.format_inspector
import cinder.privsep.path
from cinder import utils from cinder import utils
from cinder.volume import throttling from cinder.volume import throttling
from cinder.volume import volume_utils from cinder.volume import volume_utils
@@ -376,6 +377,16 @@ def check_qemu_img_version(minimum_version: str) -> None:
raise exception.VolumeBackendAPIException(data=_msg) raise exception.VolumeBackendAPIException(data=_msg)
def _ensure_exists(dest_path: str) -> None:
# Ensure that "dest" exists if it is in /dev/
# This prevents qemu-img from creating a file in /dev/
# if the block device has disappeared.
if dest_path.startswith('/dev/'):
if not cinder.privsep.path.exists(dest_path):
raise exception.CinderException(
"qemu-img convert destination %s does not exist" % dest_path)
def _convert_image( def _convert_image(
prefix: tuple, prefix: tuple,
source: str, source: str,
@@ -443,6 +454,8 @@ def _convert_image(
src_passphrase_file=src_passphrase_file, src_passphrase_file=src_passphrase_file,
disable_sparse=disable_sparse) disable_sparse=disable_sparse)
_ensure_exists(dest)
start_time = timeutils.utcnow() start_time = timeutils.utcnow()
# If there is not enough space on the conversion partition, include # If there is not enough space on the conversion partition, include

View File

@@ -37,3 +37,8 @@ def symlink(src, dest):
if not os.path.exists(src): if not os.path.exists(src):
raise exception.FileNotFound(file_path=src) raise exception.FileNotFound(file_path=src)
os.symlink(src, dest) os.symlink(src, dest)
@cinder.privsep.sys_admin_pctxt.entrypoint
def exists(path: str | os.PathLike) -> bool:
return os.path.exists(path)

View File

@@ -264,6 +264,8 @@ class TestQemuImgInfo(test.TestCase):
@ddt.ddt @ddt.ddt
class TestConvertImage(test.TestCase): class TestConvertImage(test.TestCase):
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute') @mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=True) @mock.patch('cinder.utils.is_blk_device', return_value=True)
@@ -296,6 +298,8 @@ class TestConvertImage(test.TestCase):
'-O', out_format, source, dest, '-O', out_format, source, dest,
run_as_root=True) run_as_root=True)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute') @mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=True) @mock.patch('cinder.utils.is_blk_device', return_value=True)
@@ -333,6 +337,8 @@ class TestConvertImage(test.TestCase):
'-O', out_format, source, dest, '-O', out_format, source, dest,
run_as_root=True) run_as_root=True)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.volume.volume_utils.check_for_odirect_support', @mock.patch('cinder.volume.volume_utils.check_for_odirect_support',
return_value=True) return_value=True)
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@@ -358,6 +364,8 @@ class TestConvertImage(test.TestCase):
source, dest, source, dest,
run_as_root=True) run_as_root=True)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.volume.volume_utils.check_for_odirect_support', @mock.patch('cinder.volume.volume_utils.check_for_odirect_support',
return_value=True) return_value=True)
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@@ -383,6 +391,8 @@ class TestConvertImage(test.TestCase):
source, dest, source, dest,
run_as_root=True) run_as_root=True)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute') @mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=True) @mock.patch('cinder.utils.is_blk_device', return_value=True)
@@ -403,6 +413,8 @@ class TestConvertImage(test.TestCase):
'-O', out_format, '-t', 'none', '-O', out_format, '-t', 'none',
source, dest, run_as_root=True) source, dest, run_as_root=True)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute') @mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=False) @mock.patch('cinder.utils.is_blk_device', return_value=False)
@@ -422,6 +434,8 @@ class TestConvertImage(test.TestCase):
source, dest, run_as_root=True) source, dest, run_as_root=True)
@ddt.data(True, False) @ddt.data(True, False)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute') @mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=False) @mock.patch('cinder.utils.is_blk_device', return_value=False)
@@ -446,6 +460,8 @@ class TestConvertImage(test.TestCase):
mock_exec.assert_called_once_with(*exec_args, mock_exec.assert_called_once_with(*exec_args,
run_as_root=True) run_as_root=True)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')
@mock.patch('cinder.utils.execute') @mock.patch('cinder.utils.execute')
@mock.patch('cinder.utils.is_blk_device', return_value=False) @mock.patch('cinder.utils.is_blk_device', return_value=False)
@@ -464,6 +480,8 @@ class TestConvertImage(test.TestCase):
'-O', out_format, '-S', '0', source, '-O', out_format, '-S', '0', source,
dest, run_as_root=True) dest, run_as_root=True)
@mock.patch('cinder.image.image_utils._ensure_exists',
new=mock.MagicMock())
@mock.patch('cinder.volume.volume_utils.check_for_odirect_support', @mock.patch('cinder.volume.volume_utils.check_for_odirect_support',
return_value=True) return_value=True)
@mock.patch('cinder.image.image_utils.qemu_img_info') @mock.patch('cinder.image.image_utils.qemu_img_info')

View File

@@ -0,0 +1,8 @@
---
fixes:
- |
`Bug #2132083 <https://bugs.launchpad.net/cinder/+bug/2132083>`_: When
creating a volume from an image, a storage connectivity failure or
similar could result in the local block device node disappearing. In
this situation, avoid writing the image to a file in /dev/ when calling
qemu-img.