Merge "Add insufficient space async error in create vol"
This commit is contained in:
commit
08938c9dfd
@ -257,6 +257,11 @@ class ImageUnacceptable(Invalid):
|
|||||||
message = _("Image %(image_id)s is unacceptable: %(reason)s")
|
message = _("Image %(image_id)s is unacceptable: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ImageTooBig(Invalid):
|
||||||
|
message = _("Image %(image_id)s size exceeded available "
|
||||||
|
"disk space: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
class DeviceUnavailable(Invalid):
|
class DeviceUnavailable(Invalid):
|
||||||
message = _("The device in the path %(path)s is unavailable: %(reason)s")
|
message = _("The device in the path %(path)s is unavailable: %(reason)s")
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ import tempfile
|
|||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
from oslo_utils import imageutils
|
from oslo_utils import imageutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
@ -215,14 +214,15 @@ def fetch(context, image_service, image_id, path, _user_id, _project_id):
|
|||||||
try:
|
try:
|
||||||
image_service.download(context, image_id, image_file)
|
image_service.download(context, image_id, image_file)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
with excutils.save_and_reraise_exception():
|
if e.errno == errno.ENOSPC:
|
||||||
if e.errno == errno.ENOSPC:
|
params = {'path': os.path.dirname(path),
|
||||||
# TODO(eharney): Fire an async error message for this
|
'image': image_id}
|
||||||
LOG.error("No space left in image_conversion_dir "
|
reason = _("No space left in image_conversion_dir "
|
||||||
"path (%(path)s) while fetching "
|
"path (%(path)s) while fetching "
|
||||||
"image %(image)s.",
|
"image %(image)s.") % params
|
||||||
{'path': os.path.dirname(path),
|
LOG.exception(reason)
|
||||||
'image': image_id})
|
raise exception.ImageTooBig(image_id=image_id,
|
||||||
|
reason=reason)
|
||||||
|
|
||||||
duration = timeutils.delta_seconds(start_time, timeutils.utcnow())
|
duration = timeutils.delta_seconds(start_time, timeutils.utcnow())
|
||||||
|
|
||||||
@ -516,7 +516,7 @@ def check_available_space(dest, image_size, image_id):
|
|||||||
msg = ('There is no space to convert image. '
|
msg = ('There is no space to convert image. '
|
||||||
'Requested: %(image_size)s, available: %(free_space)s'
|
'Requested: %(image_size)s, available: %(free_space)s'
|
||||||
) % {'image_size': image_size, 'free_space': free_space}
|
) % {'image_size': image_size, 'free_space': free_space}
|
||||||
raise exception.ImageUnacceptable(image_id=image_id, reason=msg)
|
raise exception.ImageTooBig(image_id=image_id, reason=msg)
|
||||||
|
|
||||||
|
|
||||||
def is_xenserver_format(image_meta):
|
def is_xenserver_format(image_meta):
|
||||||
|
@ -33,11 +33,13 @@ class Action(object):
|
|||||||
ATTACH_VOLUME = ('002', _('attach volume'))
|
ATTACH_VOLUME = ('002', _('attach volume'))
|
||||||
COPY_VOLUME_TO_IMAGE = ('003', _('copy volume to image'))
|
COPY_VOLUME_TO_IMAGE = ('003', _('copy volume to image'))
|
||||||
UPDATE_ATTACHMENT = ('004', _('update attachment'))
|
UPDATE_ATTACHMENT = ('004', _('update attachment'))
|
||||||
|
COPY_IMAGE_TO_VOLUME = ('005', _('copy image to volume'))
|
||||||
|
|
||||||
ALL = (SCHEDULE_ALLOCATE_VOLUME,
|
ALL = (SCHEDULE_ALLOCATE_VOLUME,
|
||||||
ATTACH_VOLUME,
|
ATTACH_VOLUME,
|
||||||
COPY_VOLUME_TO_IMAGE,
|
COPY_VOLUME_TO_IMAGE,
|
||||||
UPDATE_ATTACHMENT)
|
UPDATE_ATTACHMENT,
|
||||||
|
COPY_IMAGE_TO_VOLUME)
|
||||||
|
|
||||||
|
|
||||||
class Detail(object):
|
class Detail(object):
|
||||||
@ -55,13 +57,17 @@ class Detail(object):
|
|||||||
_("Volume's attach mode is invalid."))
|
_("Volume's attach mode is invalid."))
|
||||||
QUOTA_EXCEED = ('006',
|
QUOTA_EXCEED = ('006',
|
||||||
_("Not enough quota resource for operation."))
|
_("Not enough quota resource for operation."))
|
||||||
|
NOT_ENOUGH_SPACE_FOR_IMAGE = ('007',
|
||||||
|
_("Image used for creating volume exceeds "
|
||||||
|
"available space."))
|
||||||
|
|
||||||
ALL = (UNKNOWN_ERROR,
|
ALL = (UNKNOWN_ERROR,
|
||||||
DRIVER_NOT_INITIALIZED,
|
DRIVER_NOT_INITIALIZED,
|
||||||
NO_BACKEND_AVAILABLE,
|
NO_BACKEND_AVAILABLE,
|
||||||
FAILED_TO_UPLOAD_VOLUME,
|
FAILED_TO_UPLOAD_VOLUME,
|
||||||
VOLUME_ATTACH_MODE_INVALID,
|
VOLUME_ATTACH_MODE_INVALID,
|
||||||
QUOTA_EXCEED)
|
QUOTA_EXCEED,
|
||||||
|
NOT_ENOUGH_SPACE_FOR_IMAGE)
|
||||||
|
|
||||||
# Exception and detail mappings
|
# Exception and detail mappings
|
||||||
EXCEPTION_DETAIL_MAPPINGS = {
|
EXCEPTION_DETAIL_MAPPINGS = {
|
||||||
@ -70,7 +76,8 @@ class Detail(object):
|
|||||||
VOLUME_ATTACH_MODE_INVALID: ['InvalidVolumeAttachMode'],
|
VOLUME_ATTACH_MODE_INVALID: ['InvalidVolumeAttachMode'],
|
||||||
QUOTA_EXCEED: ['ImageLimitExceeded',
|
QUOTA_EXCEED: ['ImageLimitExceeded',
|
||||||
'BackupLimitExceeded',
|
'BackupLimitExceeded',
|
||||||
'SnapshotLimitExceeded']
|
'SnapshotLimitExceeded'],
|
||||||
|
NOT_ENOUGH_SPACE_FOR_IMAGE: ['ImageTooBig']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ from oslo_utils import units
|
|||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests import fixtures
|
|
||||||
from cinder.tests.unit import fake_constants as fake
|
from cinder.tests.unit import fake_constants as fake
|
||||||
from cinder.volume import throttling
|
from cinder.volume import throttling
|
||||||
|
|
||||||
@ -298,28 +297,22 @@ class TestFetch(test.TestCase):
|
|||||||
.assert_called_once_with(None, None, None))
|
.assert_called_once_with(None, None, None))
|
||||||
|
|
||||||
def test_fetch_enospc(self):
|
def test_fetch_enospc(self):
|
||||||
stdlog = self.useFixture(fixtures.StandardLogging())
|
|
||||||
|
|
||||||
context = mock.sentinel.context
|
context = mock.sentinel.context
|
||||||
image_service = mock.Mock()
|
image_service = mock.Mock()
|
||||||
e = IOError()
|
image_id = mock.sentinel.image_id
|
||||||
|
e = exception.ImageTooBig(image_id=image_id, reason = "fake")
|
||||||
e.errno = errno.ENOSPC
|
e.errno = errno.ENOSPC
|
||||||
image_service.download.side_effect = e
|
image_service.download.side_effect = e
|
||||||
image_id = mock.sentinel.image_id
|
|
||||||
path = '/test_path'
|
path = '/test_path'
|
||||||
_user_id = mock.sentinel._user_id
|
_user_id = mock.sentinel._user_id
|
||||||
_project_id = mock.sentinel._project_id
|
_project_id = mock.sentinel._project_id
|
||||||
|
|
||||||
with mock.patch('cinder.image.image_utils.open',
|
with mock.patch('cinder.image.image_utils.open',
|
||||||
new=mock.mock_open(), create=True):
|
new=mock.mock_open(), create=True):
|
||||||
self.assertRaises(IOError,
|
self.assertRaises(exception.ImageTooBig,
|
||||||
image_utils.fetch,
|
image_utils.fetch,
|
||||||
context, image_service, image_id, path,
|
context, image_service, image_id, path,
|
||||||
_user_id, _project_id)
|
_user_id, _project_id)
|
||||||
error_message = ('No space left in image_conversion_dir path '
|
|
||||||
'(/) while fetching image %s.' % image_id)
|
|
||||||
|
|
||||||
self.assertTrue(error_message in stdlog.logger.output)
|
|
||||||
|
|
||||||
|
|
||||||
class TestVerifyImage(test.TestCase):
|
class TestVerifyImage(test.TestCase):
|
||||||
@ -1104,11 +1097,11 @@ class TestFetchToVolumeFormat(test.TestCase):
|
|||||||
data.backing_file = None
|
data.backing_file = None
|
||||||
data.virtual_size = units.Gi
|
data.virtual_size = units.Gi
|
||||||
|
|
||||||
mock_check_space.side_effect = exception.ImageUnacceptable(
|
mock_check_space.side_effect = exception.ImageTooBig(
|
||||||
image_id='fake_image_id', reason='test')
|
image_id='fake_image_id', reason='test')
|
||||||
|
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.ImageUnacceptable,
|
exception.ImageTooBig,
|
||||||
image_utils.fetch_to_volume_format,
|
image_utils.fetch_to_volume_format,
|
||||||
ctxt, image_service, image_id, dest, volume_format, blocksize,
|
ctxt, image_service, image_id, dest, volume_format, blocksize,
|
||||||
user_id=user_id, project_id=project_id, size=size,
|
user_id=user_id, project_id=project_id, size=size,
|
||||||
|
@ -24,6 +24,7 @@ from oslo_utils import imageutils
|
|||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
|
from cinder.message import message_field
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests.unit.consistencygroup import fake_consistencygroup
|
from cinder.tests.unit.consistencygroup import fake_consistencygroup
|
||||||
from cinder.tests.unit import fake_constants as fakes
|
from cinder.tests.unit import fake_constants as fakes
|
||||||
@ -984,9 +985,11 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
self.internal_context.user_id = 'abc123'
|
self.internal_context.user_id = 'abc123'
|
||||||
self.internal_context.project_id = 'def456'
|
self.internal_context.project_id = 'def456'
|
||||||
|
|
||||||
|
@mock.patch('cinder.image.image_utils.check_available_space')
|
||||||
def test_create_from_image_clone_image_and_skip_cache(
|
def test_create_from_image_clone_image_and_skip_cache(
|
||||||
self, mock_get_internal_context, mock_create_from_img_dl,
|
self, mock_check_space, mock_get_internal_context,
|
||||||
mock_create_from_src, mock_handle_bootable, mock_fetch_img):
|
mock_create_from_img_dl, mock_create_from_src,
|
||||||
|
mock_handle_bootable, mock_fetch_img):
|
||||||
self.mock_driver.clone_image.return_value = (None, True)
|
self.mock_driver.clone_image.return_value = (None, True)
|
||||||
volume = fake_volume.fake_volume_obj(self.ctxt,
|
volume = fake_volume.fake_volume_obj(self.ctxt,
|
||||||
host='host@backend#pool')
|
host='host@backend#pool')
|
||||||
@ -1009,6 +1012,9 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
image_meta,
|
image_meta,
|
||||||
self.mock_image_service)
|
self.mock_image_service)
|
||||||
|
|
||||||
|
# Make sure check_available_space is always called
|
||||||
|
self.assertTrue(mock_check_space.called)
|
||||||
|
|
||||||
# Make sure clone_image is always called even if the cache is enabled
|
# Make sure clone_image is always called even if the cache is enabled
|
||||||
self.assertTrue(self.mock_driver.clone_image.called)
|
self.assertTrue(self.mock_driver.clone_image.called)
|
||||||
|
|
||||||
@ -1026,8 +1032,9 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('cinder.image.image_utils.qemu_img_info')
|
@mock.patch('cinder.image.image_utils.qemu_img_info')
|
||||||
|
@mock.patch('cinder.image.image_utils.check_available_space')
|
||||||
def test_create_from_image_cannot_use_cache(
|
def test_create_from_image_cannot_use_cache(
|
||||||
self, mock_qemu_info, mock_get_internal_context,
|
self, mock_qemu_info, mock_check_space, mock_get_internal_context,
|
||||||
mock_create_from_img_dl, mock_create_from_src,
|
mock_create_from_img_dl, mock_create_from_src,
|
||||||
mock_handle_bootable, mock_fetch_img):
|
mock_handle_bootable, mock_fetch_img):
|
||||||
mock_get_internal_context.return_value = None
|
mock_get_internal_context.return_value = None
|
||||||
@ -1058,6 +1065,9 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
image_meta,
|
image_meta,
|
||||||
self.mock_image_service)
|
self.mock_image_service)
|
||||||
|
|
||||||
|
# Make sure check_available_space is always called
|
||||||
|
self.assertTrue(mock_check_space.called)
|
||||||
|
|
||||||
# Make sure clone_image is always called
|
# Make sure clone_image is always called
|
||||||
self.assertTrue(self.mock_driver.clone_image.called)
|
self.assertTrue(self.mock_driver.clone_image.called)
|
||||||
|
|
||||||
@ -1162,8 +1172,9 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
mock_volume_update.assert_any_call(self.ctxt, volume.id, {'size': 1})
|
mock_volume_update.assert_any_call(self.ctxt, volume.id, {'size': 1})
|
||||||
self.assertEqual(volume_size, volume.size)
|
self.assertEqual(volume_size, volume.size)
|
||||||
|
|
||||||
|
@mock.patch('cinder.image.image_utils.check_available_space')
|
||||||
def test_create_from_image_bigger_size(
|
def test_create_from_image_bigger_size(
|
||||||
self, mock_get_internal_context,
|
self, mock_check_space, mock_get_internal_context,
|
||||||
mock_create_from_img_dl, mock_create_from_src,
|
mock_create_from_img_dl, mock_create_from_src,
|
||||||
mock_handle_bootable, mock_fetch_img):
|
mock_handle_bootable, mock_fetch_img):
|
||||||
volume = fake_volume.fake_volume_obj(self.ctxt)
|
volume = fake_volume.fake_volume_obj(self.ctxt)
|
||||||
@ -1375,8 +1386,9 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
self.assertFalse(self.mock_cache.create_cache_entry.called)
|
self.assertFalse(self.mock_cache.create_cache_entry.called)
|
||||||
|
|
||||||
@mock.patch('cinder.image.image_utils.qemu_img_info')
|
@mock.patch('cinder.image.image_utils.qemu_img_info')
|
||||||
|
@mock.patch('cinder.image.image_utils.check_available_space')
|
||||||
def test_create_from_image_no_internal_context(
|
def test_create_from_image_no_internal_context(
|
||||||
self, mock_qemu_info, mock_get_internal_context,
|
self, mock_chk_space, mock_qemu_info, mock_get_internal_context,
|
||||||
mock_create_from_img_dl, mock_create_from_src,
|
mock_create_from_img_dl, mock_create_from_src,
|
||||||
mock_handle_bootable, mock_fetch_img):
|
mock_handle_bootable, mock_fetch_img):
|
||||||
self.mock_driver.clone_image.return_value = (None, False)
|
self.mock_driver.clone_image.return_value = (None, False)
|
||||||
@ -1405,6 +1417,9 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
image_meta,
|
image_meta,
|
||||||
self.mock_image_service)
|
self.mock_image_service)
|
||||||
|
|
||||||
|
# Make sure check_available_space is always called
|
||||||
|
self.assertTrue(mock_chk_space.called)
|
||||||
|
|
||||||
# Make sure clone_image is always called
|
# Make sure clone_image is always called
|
||||||
self.assertTrue(self.mock_driver.clone_image.called)
|
self.assertTrue(self.mock_driver.clone_image.called)
|
||||||
|
|
||||||
@ -1481,3 +1496,114 @@ class CreateVolumeFlowManagerImageCacheTestCase(test.TestCase):
|
|||||||
# Make sure we didn't try and create a cache entry
|
# Make sure we didn't try and create a cache entry
|
||||||
self.assertFalse(self.mock_cache.ensure_space.called)
|
self.assertFalse(self.mock_cache.ensure_space.called)
|
||||||
self.assertFalse(self.mock_cache.create_cache_entry.called)
|
self.assertFalse(self.mock_cache.create_cache_entry.called)
|
||||||
|
|
||||||
|
@mock.patch('cinder.image.image_utils.check_available_space')
|
||||||
|
@mock.patch('cinder.image.image_utils.qemu_img_info')
|
||||||
|
@mock.patch('cinder.message.api.API.create')
|
||||||
|
def test_create_from_image_insufficient_space(
|
||||||
|
self, mock_message_create, mock_qemu_info, mock_check_space,
|
||||||
|
mock_get_internal_context,
|
||||||
|
mock_create_from_img_dl, mock_create_from_src,
|
||||||
|
mock_handle_bootable, mock_fetch_img):
|
||||||
|
image_info = imageutils.QemuImgInfo()
|
||||||
|
image_info.virtual_size = '2147483648'
|
||||||
|
mock_qemu_info.return_value = image_info
|
||||||
|
self.mock_driver.clone_image.return_value = (None, False)
|
||||||
|
self.mock_cache.get_entry.return_value = None
|
||||||
|
|
||||||
|
volume = fake_volume.fake_volume_obj(self.ctxt, size=1,
|
||||||
|
host='foo@bar#pool')
|
||||||
|
image_volume = fake_volume.fake_db_volume(size=2)
|
||||||
|
self.mock_db.volume_create.return_value = image_volume
|
||||||
|
|
||||||
|
image_location = 'someImageLocationStr'
|
||||||
|
image_id = fakes.IMAGE_ID
|
||||||
|
image_meta = mock.MagicMock()
|
||||||
|
mock_check_space.side_effect = exception.ImageTooBig(
|
||||||
|
image_id=image_id, reason="fake")
|
||||||
|
|
||||||
|
manager = create_volume_manager.CreateVolumeFromSpecTask(
|
||||||
|
self.mock_volume_manager,
|
||||||
|
self.mock_db,
|
||||||
|
self.mock_driver,
|
||||||
|
image_volume_cache=self.mock_cache
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exception.ImageTooBig,
|
||||||
|
manager._create_from_image,
|
||||||
|
self.ctxt,
|
||||||
|
volume,
|
||||||
|
image_location,
|
||||||
|
image_id,
|
||||||
|
image_meta,
|
||||||
|
self.mock_image_service
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_message_create.assert_called_once_with(
|
||||||
|
self.ctxt, message_field.Action.COPY_IMAGE_TO_VOLUME,
|
||||||
|
resource_uuid=volume.id,
|
||||||
|
detail=message_field.Detail.NOT_ENOUGH_SPACE_FOR_IMAGE,
|
||||||
|
exception=mock.ANY)
|
||||||
|
|
||||||
|
# The volume size should NOT be changed when in this case
|
||||||
|
self.assertFalse(self.mock_db.volume_update.called)
|
||||||
|
|
||||||
|
# Make sure we didn't try and create a cache entry
|
||||||
|
self.assertFalse(self.mock_cache.ensure_space.called)
|
||||||
|
self.assertFalse(self.mock_cache.create_cache_entry.called)
|
||||||
|
|
||||||
|
@mock.patch('cinder.image.image_utils.check_available_space')
|
||||||
|
@mock.patch('cinder.image.image_utils.qemu_img_info')
|
||||||
|
@mock.patch('cinder.message.api.API.create')
|
||||||
|
def test_create_from_image_cache_insufficient_size(
|
||||||
|
self, mock_message_create, mock_qemu_info, mock_check_space,
|
||||||
|
mock_get_internal_context,
|
||||||
|
mock_create_from_img_dl, mock_create_from_src,
|
||||||
|
mock_handle_bootable, mock_fetch_img):
|
||||||
|
image_info = imageutils.QemuImgInfo()
|
||||||
|
image_info.virtual_size = '1073741824'
|
||||||
|
mock_qemu_info.return_value = image_info
|
||||||
|
self.mock_driver.clone_image.return_value = (None, False)
|
||||||
|
self.mock_cache.get_entry.return_value = None
|
||||||
|
volume = fake_volume.fake_volume_obj(self.ctxt, size=1,
|
||||||
|
host='foo@bar#pool')
|
||||||
|
image_volume = fake_volume.fake_db_volume(size=2)
|
||||||
|
self.mock_db.volume_create.return_value = image_volume
|
||||||
|
image_id = fakes.IMAGE_ID
|
||||||
|
mock_create_from_img_dl.side_effect = exception.ImageTooBig(
|
||||||
|
image_id=image_id, reason="fake")
|
||||||
|
|
||||||
|
image_location = 'someImageLocationStr'
|
||||||
|
image_meta = mock.MagicMock()
|
||||||
|
|
||||||
|
manager = create_volume_manager.CreateVolumeFromSpecTask(
|
||||||
|
self.mock_volume_manager,
|
||||||
|
self.mock_db,
|
||||||
|
self.mock_driver,
|
||||||
|
image_volume_cache=self.mock_cache
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exception.ImageTooBig,
|
||||||
|
manager._create_from_image_cache_or_download,
|
||||||
|
self.ctxt,
|
||||||
|
volume,
|
||||||
|
image_location,
|
||||||
|
image_id,
|
||||||
|
image_meta,
|
||||||
|
self.mock_image_service
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_message_create.assert_called_once_with(
|
||||||
|
self.ctxt, message_field.Action.COPY_IMAGE_TO_VOLUME,
|
||||||
|
resource_uuid=volume.id,
|
||||||
|
detail=message_field.Detail.NOT_ENOUGH_SPACE_FOR_IMAGE,
|
||||||
|
exception=mock.ANY)
|
||||||
|
|
||||||
|
# The volume size should NOT be changed when in this case
|
||||||
|
self.assertFalse(self.mock_db.volume_update.called)
|
||||||
|
|
||||||
|
# Make sure we didn't try and create a cache entry
|
||||||
|
self.assertFalse(self.mock_cache.ensure_space.called)
|
||||||
|
self.assertFalse(self.mock_cache.create_cache_entry.called)
|
||||||
|
@ -444,8 +444,12 @@ class GenericVolumeDriverTestCase(BaseDriverTestCase):
|
|||||||
@mock.patch.object(cinder.volume.driver.VolumeDriver, '_detach_volume')
|
@mock.patch.object(cinder.volume.driver.VolumeDriver, '_detach_volume')
|
||||||
@mock.patch.object(cinder.utils, 'brick_attach_volume_encryptor')
|
@mock.patch.object(cinder.utils, 'brick_attach_volume_encryptor')
|
||||||
@mock.patch.object(cinder.utils, 'brick_detach_volume_encryptor')
|
@mock.patch.object(cinder.utils, 'brick_detach_volume_encryptor')
|
||||||
|
@ddt.data(exception.ImageUnacceptable(
|
||||||
|
reason='fake', image_id=fake.IMAGE_ID),
|
||||||
|
exception.ImageTooBig(
|
||||||
|
reason='fake image size exceeded', image_id=fake.IMAGE_ID))
|
||||||
def test_copy_image_to_encrypted_volume_failed_fetch(
|
def test_copy_image_to_encrypted_volume_failed_fetch(
|
||||||
self,
|
self, excep,
|
||||||
mock_detach_encryptor, mock_attach_encryptor,
|
mock_detach_encryptor, mock_attach_encryptor,
|
||||||
mock_detach_volume, mock_attach_volume, mock_fetch_to_raw,
|
mock_detach_volume, mock_attach_volume, mock_fetch_to_raw,
|
||||||
mock_get_connector_properties):
|
mock_get_connector_properties):
|
||||||
@ -464,12 +468,10 @@ class GenericVolumeDriverTestCase(BaseDriverTestCase):
|
|||||||
|
|
||||||
mock_get_connector_properties.return_value = properties
|
mock_get_connector_properties.return_value = properties
|
||||||
mock_attach_volume.return_value = [attach_info, volume]
|
mock_attach_volume.return_value = [attach_info, volume]
|
||||||
raised_exception = exception.ImageUnacceptable(reason='fake',
|
mock_fetch_to_raw.side_effect = excep
|
||||||
image_id=fake.IMAGE_ID)
|
|
||||||
mock_fetch_to_raw.side_effect = raised_exception
|
|
||||||
|
|
||||||
encryption = {'encryption_key_id': fake.ENCRYPTION_KEY_ID}
|
encryption = {'encryption_key_id': fake.ENCRYPTION_KEY_ID}
|
||||||
self.assertRaises(exception.ImageUnacceptable,
|
self.assertRaises(type(excep),
|
||||||
self.volume.driver.copy_image_to_encrypted_volume,
|
self.volume.driver.copy_image_to_encrypted_volume,
|
||||||
self.context, volume, image_service, fake.IMAGE_ID)
|
self.context, volume, image_service, fake.IMAGE_ID)
|
||||||
|
|
||||||
|
@ -804,6 +804,13 @@ class BaseVD(object):
|
|||||||
attach_info['device']['path'],
|
attach_info['device']['path'],
|
||||||
self.configuration.volume_dd_blocksize,
|
self.configuration.volume_dd_blocksize,
|
||||||
size=volume['size'])
|
size=volume['size'])
|
||||||
|
except exception.ImageTooBig:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.exception("Copying image %(image_id)s "
|
||||||
|
"to volume failed due to "
|
||||||
|
"insufficient available space.",
|
||||||
|
{'image_id': image_id})
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if encrypted:
|
if encrypted:
|
||||||
utils.brick_detach_volume_encryptor(attach_info,
|
utils.brick_detach_volume_encryptor(attach_info,
|
||||||
|
@ -16,6 +16,7 @@ import traceback
|
|||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import taskflow.engines
|
import taskflow.engines
|
||||||
from taskflow.patterns import linear_flow
|
from taskflow.patterns import linear_flow
|
||||||
@ -28,6 +29,8 @@ from cinder import flow_utils
|
|||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder.image import glance
|
from cinder.image import glance
|
||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
|
from cinder.message import api as message_api
|
||||||
|
from cinder.message import message_field
|
||||||
from cinder import objects
|
from cinder import objects
|
||||||
from cinder.objects import consistencygroup
|
from cinder.objects import consistencygroup
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
@ -91,6 +94,7 @@ class OnFailureRescheduleTask(flow_utils.CinderTask):
|
|||||||
exception.SnapshotNotFound,
|
exception.SnapshotNotFound,
|
||||||
exception.VolumeTypeNotFound,
|
exception.VolumeTypeNotFound,
|
||||||
exception.ImageUnacceptable,
|
exception.ImageUnacceptable,
|
||||||
|
exception.ImageTooBig,
|
||||||
]
|
]
|
||||||
|
|
||||||
def execute(self, **kwargs):
|
def execute(self, **kwargs):
|
||||||
@ -372,6 +376,7 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
|
|||||||
self.db = db
|
self.db = db
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
self.image_volume_cache = image_volume_cache
|
self.image_volume_cache = image_volume_cache
|
||||||
|
self.message = message_api.API()
|
||||||
|
|
||||||
def _handle_bootable_volume_glance_meta(self, context, volume,
|
def _handle_bootable_volume_glance_meta(self, context, volume,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@ -557,6 +562,11 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
|
|||||||
LOG.exception("Failed to copy image to volume: %(volume_id)s",
|
LOG.exception("Failed to copy image to volume: %(volume_id)s",
|
||||||
{'volume_id': volume.id})
|
{'volume_id': volume.id})
|
||||||
raise exception.ImageUnacceptable(ex)
|
raise exception.ImageUnacceptable(ex)
|
||||||
|
except exception.ImageTooBig as ex:
|
||||||
|
LOG.exception("Failed to copy image %(image_id)s to volume: "
|
||||||
|
"%(volume_id)s",
|
||||||
|
{'volume_id': volume.id, 'image_id': image_id})
|
||||||
|
excutils.save_and_reraise_exception()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.exception("Failed to copy image %(image_id)s to "
|
LOG.exception("Failed to copy image %(image_id)s to "
|
||||||
"volume: %(volume_id)s",
|
"volume: %(volume_id)s",
|
||||||
@ -683,8 +693,14 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
|
|||||||
"%(updates)s",
|
"%(updates)s",
|
||||||
{'volume_id': volume.id,
|
{'volume_id': volume.id,
|
||||||
'updates': model_update})
|
'updates': model_update})
|
||||||
self._copy_image_to_volume(context, volume, image_meta, image_location,
|
try:
|
||||||
image_service)
|
self._copy_image_to_volume(context, volume, image_meta,
|
||||||
|
image_location, image_service)
|
||||||
|
except exception.ImageTooBig:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.exception("Failed to copy image to volume "
|
||||||
|
"%(volume_id)s due to insufficient space",
|
||||||
|
{'volume_id': volume.id})
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
def _create_from_image_cache(self, context, internal_context, volume,
|
def _create_from_image_cache(self, context, internal_context, volume,
|
||||||
@ -757,28 +773,38 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
|
|||||||
backend_name = volume_utils.extract_host(volume.service_topic_queue)
|
backend_name = volume_utils.extract_host(volume.service_topic_queue)
|
||||||
try:
|
try:
|
||||||
if not cloned:
|
if not cloned:
|
||||||
with image_utils.TemporaryImages.fetch(
|
try:
|
||||||
image_service, context, image_id,
|
with image_utils.TemporaryImages.fetch(
|
||||||
backend_name) as tmp_image:
|
image_service, context, image_id,
|
||||||
# Try to create the volume as the minimal size, then we can
|
backend_name) as tmp_image:
|
||||||
# extend once the image has been downloaded.
|
# Try to create the volume as the minimal size,
|
||||||
data = image_utils.qemu_img_info(tmp_image)
|
# then we can extend once the image has been
|
||||||
|
# downloaded.
|
||||||
|
data = image_utils.qemu_img_info(tmp_image)
|
||||||
|
|
||||||
virtual_size = image_utils.check_virtual_size(
|
virtual_size = image_utils.check_virtual_size(
|
||||||
data.virtual_size, volume.size, image_id)
|
data.virtual_size, volume.size, image_id)
|
||||||
|
|
||||||
if should_create_cache_entry:
|
if should_create_cache_entry:
|
||||||
if virtual_size and virtual_size != original_size:
|
if virtual_size and virtual_size != original_size:
|
||||||
volume.size = virtual_size
|
volume.size = virtual_size
|
||||||
volume.save()
|
volume.save()
|
||||||
|
model_update = self._create_from_image_download(
|
||||||
model_update = self._create_from_image_download(
|
context,
|
||||||
context,
|
volume,
|
||||||
volume,
|
image_location,
|
||||||
image_location,
|
image_meta,
|
||||||
image_meta,
|
image_service
|
||||||
image_service
|
)
|
||||||
)
|
except exception.ImageTooBig as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
self.message.create(
|
||||||
|
context,
|
||||||
|
message_field.Action.COPY_IMAGE_TO_VOLUME,
|
||||||
|
resource_uuid=volume.id,
|
||||||
|
detail=
|
||||||
|
message_field.Detail.NOT_ENOUGH_SPACE_FOR_IMAGE,
|
||||||
|
exception=e)
|
||||||
|
|
||||||
if should_create_cache_entry:
|
if should_create_cache_entry:
|
||||||
# Update the newly created volume db entry before we clone it
|
# Update the newly created volume db entry before we clone it
|
||||||
@ -817,8 +843,17 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
|
|||||||
if (CONF.image_conversion_dir and not
|
if (CONF.image_conversion_dir and not
|
||||||
os.path.exists(CONF.image_conversion_dir)):
|
os.path.exists(CONF.image_conversion_dir)):
|
||||||
os.makedirs(CONF.image_conversion_dir)
|
os.makedirs(CONF.image_conversion_dir)
|
||||||
image_utils.check_available_space(CONF.image_conversion_dir,
|
try:
|
||||||
image_meta['size'], image_id)
|
image_utils.check_available_space(CONF.image_conversion_dir,
|
||||||
|
image_meta['size'], image_id)
|
||||||
|
except exception.ImageTooBig as err:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
self.message.create(
|
||||||
|
context,
|
||||||
|
message_field.Action.COPY_IMAGE_TO_VOLUME,
|
||||||
|
resource_uuid=volume.id,
|
||||||
|
detail=message_field.Detail.NOT_ENOUGH_SPACE_FOR_IMAGE,
|
||||||
|
exception=err)
|
||||||
|
|
||||||
virtual_size = image_meta.get('virtual_size')
|
virtual_size = image_meta.get('virtual_size')
|
||||||
if virtual_size:
|
if virtual_size:
|
||||||
|
Loading…
Reference in New Issue
Block a user