Staging area not cleared if image is deleted while importing

If multiple stores configured in glance and Image is deleted while import
operation is in progress then image data stays in staging area
(filesystem backend) and there is no other way than clearing it
manually.

Modified delete method to delete the data from staging area if image is
deleted while import operation is in progress.

Change-Id: Ib58accd6514e589dccde57fe063815b1ab1ce496
Closes-Bug: #1855417
(cherry picked from commit 5d15f07371)
This commit is contained in:
Abhishek Kekane 2019-12-06 10:35:55 +00:00
parent 6e3ced8251
commit 8c2bc60820
4 changed files with 83 additions and 44 deletions

View File

@ -367,41 +367,41 @@ class ImagesController(object):
image_repo = self.gateway.get_repo(req.context)
try:
image = image_repo.get(image_id)
if image.status == 'uploading':
if CONF.enabled_backends:
file_path = "%s/%s" % (getattr(
CONF, 'os_glance_staging_store'
).filesystem_store_datadir, image_id)
# NOTE(abhishekk): Delete the data from staging area
if CONF.enabled_backends:
separator, staging_dir = store_utils.get_dir_separator()
file_path = "%s%s%s" % (staging_dir,
separator,
image_id)
try:
fn_call = glance_store.get_store_from_store_identifier
staging_store = fn_call('os_glance_staging_store')
loc = location.get_location_from_uri_and_backend(
file_path, 'os_glance_staging_store')
staging_store.delete(loc)
except (glance_store.exceptions.NotFound,
glance_store.exceptions.UnknownScheme):
pass
else:
file_path = str(
CONF.node_staging_uri + '/' + image_id)[7:]
if os.path.exists(file_path):
try:
fn_call = glance_store.get_store_from_store_identifier
staging_store = fn_call('os_glance_staging_store')
loc = location.get_location_from_uri_and_backend(
file_path, 'os_glance_staging_store')
staging_store.delete(loc)
except (glance_store.exceptions.NotFound,
glance_store.exceptions.UnknownScheme):
pass
else:
file_path = str(
CONF.node_staging_uri + '/' + image_id)[7:]
if os.path.exists(file_path):
try:
LOG.debug(
"After upload to the backend, deleting staged "
"image data from %(fn)s", {'fn': file_path})
os.unlink(file_path)
except OSError as e:
LOG.error(
"After upload to backend, deletion of staged "
"image data from %(fn)s has failed because "
"[Errno %(en)d]", {'fn': file_path,
'en': e.errno})
else:
LOG.warning(_(
LOG.debug(
"After upload to the backend, deleting staged "
"image data from %(fn)s", {'fn': file_path})
os.unlink(file_path)
except OSError as e:
LOG.error(
"After upload to backend, deletion of staged "
"image data has failed because "
"it cannot be found at %(fn)s"), {'fn': file_path})
"image data from %(fn)s has failed because "
"[Errno %(en)d]", {'fn': file_path,
'en': e.errno})
else:
LOG.warning(_(
"After upload to backend, deletion of staged "
"image data has failed because "
"it cannot be found at %(fn)s"), {'fn': file_path})
image.delete()
self._delete_encryption_key(req.context, image)

View File

@ -29,6 +29,7 @@ import glance.async_.flows.plugins as import_plugins
from glance.common import exception
from glance.common.scripts.image_import import main as image_import
from glance.common.scripts import utils as script_utils
from glance.common import store_utils
from glance.i18n import _, _LE, _LI
@ -344,7 +345,7 @@ def get_flow(**kwargs):
if not uri and import_method == 'glance-direct':
if CONF.enabled_backends:
separator, staging_dir = _get_dir_separator()
separator, staging_dir = store_utils.get_dir_separator()
uri = separator.join((staging_dir, str(image_id)))
else:
uri = separator.join((CONF.node_staging_uri, str(image_id)))
@ -355,7 +356,7 @@ def get_flow(**kwargs):
downloadToStaging = internal_plugins.get_import_plugin(**kwargs)
flow.add(downloadToStaging)
if CONF.enabled_backends:
separator, staging_dir = _get_dir_separator()
separator, staging_dir = store_utils.get_dir_separator()
file_uri = separator.join((staging_dir, str(image_id)))
else:
file_uri = separator.join((CONF.node_staging_uri, str(image_id)))
@ -396,12 +397,3 @@ def get_flow(**kwargs):
image_repo.save(image, from_state=from_state)
return flow
def _get_dir_separator():
separator = ''
staging_dir = "file://%s" % getattr(
CONF, 'os_glance_staging_store').filesystem_store_datadir
if not staging_dir.endswith('/'):
separator = '/'
return separator, staging_dir

View File

@ -193,3 +193,12 @@ def update_store_in_locations(locations, image_id):
'id': image_id})
loc['metadata']['store'] = store_id
def get_dir_separator():
separator = ''
staging_dir = "file://%s" % getattr(
CONF, 'os_glance_staging_store').filesystem_store_datadir
if not staging_dir.endswith('/'):
separator = '/'
return separator, staging_dir

View File

@ -2688,6 +2688,44 @@ class TestImagesController(base.IsolatedUnitTest):
self.assertEqual('deleted', deleted_img['status'])
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
@mock.patch.object(store, 'get_store_from_store_identifier')
@mock.patch.object(store.location, 'get_location_from_uri_and_backend')
@mock.patch.object(store_utils, 'get_dir_separator')
def test_verify_staging_data_deleted_on_image_delete(
self, mock_get_dir_separator, mock_location,
mock_store):
self.config(enabled_backends={'fake-store': 'file'})
fake_staging_store = mock.Mock()
mock_store.return_value = fake_staging_store
mock_get_dir_separator.return_value = (
"/", "/tmp/os_glance_staging_store")
image_id = str(uuid.uuid4())
self.images = [
_db_fixture(image_id, owner=TENANT1,
name='1',
disk_format='raw',
container_format='bare',
status='importing',
checksum=None,
os_hash_algo=None,
os_hash_value=None),
]
self.db.image_create(None, self.images[0])
request = unit_test_utils.get_fake_request()
try:
self.controller.delete(request, image_id)
self.assertEqual(1, mock_store.call_count)
mock_store.assert_called_once_with("os_glance_staging_store")
self.assertEqual(1, mock_location.call_count)
fake_staging_store.delete.assert_called_once()
except Exception as e:
self.fail("Delete raised exception: %s" % e)
deleted_img = self.db.image_get(request.context, image_id,
force_show_deleted=True)
self.assertTrue(deleted_img['deleted'])
self.assertEqual('deleted', deleted_img['status'])
def test_delete_with_tags(self):
request = unit_test_utils.get_fake_request()
changes = [