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
This commit is contained in:
parent
0eacf2da18
commit
5d15f07371
|
@ -367,41 +367,41 @@ class ImagesController(object):
|
||||||
image_repo = self.gateway.get_repo(req.context)
|
image_repo = self.gateway.get_repo(req.context)
|
||||||
try:
|
try:
|
||||||
image = image_repo.get(image_id)
|
image = image_repo.get(image_id)
|
||||||
|
# NOTE(abhishekk): Delete the data from staging area
|
||||||
if image.status == 'uploading':
|
if CONF.enabled_backends:
|
||||||
if CONF.enabled_backends:
|
separator, staging_dir = store_utils.get_dir_separator()
|
||||||
file_path = "%s/%s" % (getattr(
|
file_path = "%s%s%s" % (staging_dir,
|
||||||
CONF, 'os_glance_staging_store'
|
separator,
|
||||||
).filesystem_store_datadir, image_id)
|
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:
|
try:
|
||||||
fn_call = glance_store.get_store_from_store_identifier
|
LOG.debug(
|
||||||
staging_store = fn_call('os_glance_staging_store')
|
"After upload to the backend, deleting staged "
|
||||||
loc = location.get_location_from_uri_and_backend(
|
"image data from %(fn)s", {'fn': file_path})
|
||||||
file_path, 'os_glance_staging_store')
|
os.unlink(file_path)
|
||||||
staging_store.delete(loc)
|
except OSError as e:
|
||||||
except (glance_store.exceptions.NotFound,
|
LOG.error(
|
||||||
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(_(
|
|
||||||
"After upload to backend, deletion of staged "
|
"After upload to backend, deletion of staged "
|
||||||
"image data has failed because "
|
"image data from %(fn)s has failed because "
|
||||||
"it cannot be found at %(fn)s"), {'fn': file_path})
|
"[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()
|
image.delete()
|
||||||
self._delete_encryption_key(req.context, image)
|
self._delete_encryption_key(req.context, image)
|
||||||
|
|
|
@ -29,6 +29,7 @@ import glance.async_.flows.plugins as import_plugins
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
from glance.common.scripts.image_import import main as image_import
|
from glance.common.scripts.image_import import main as image_import
|
||||||
from glance.common.scripts import utils as script_utils
|
from glance.common.scripts import utils as script_utils
|
||||||
|
from glance.common import store_utils
|
||||||
from glance.i18n import _, _LE, _LI
|
from glance.i18n import _, _LE, _LI
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,7 +345,7 @@ def get_flow(**kwargs):
|
||||||
|
|
||||||
if not uri and import_method == 'glance-direct':
|
if not uri and import_method == 'glance-direct':
|
||||||
if CONF.enabled_backends:
|
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)))
|
uri = separator.join((staging_dir, str(image_id)))
|
||||||
else:
|
else:
|
||||||
uri = separator.join((CONF.node_staging_uri, str(image_id)))
|
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)
|
downloadToStaging = internal_plugins.get_import_plugin(**kwargs)
|
||||||
flow.add(downloadToStaging)
|
flow.add(downloadToStaging)
|
||||||
if CONF.enabled_backends:
|
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)))
|
file_uri = separator.join((staging_dir, str(image_id)))
|
||||||
else:
|
else:
|
||||||
file_uri = separator.join((CONF.node_staging_uri, str(image_id)))
|
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)
|
image_repo.save(image, from_state=from_state)
|
||||||
|
|
||||||
return flow
|
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
|
|
||||||
|
|
|
@ -193,3 +193,12 @@ def update_store_in_locations(locations, image_id):
|
||||||
'id': image_id})
|
'id': image_id})
|
||||||
|
|
||||||
loc['metadata']['store'] = store_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
|
||||||
|
|
|
@ -2688,6 +2688,44 @@ class TestImagesController(base.IsolatedUnitTest):
|
||||||
self.assertEqual('deleted', deleted_img['status'])
|
self.assertEqual('deleted', deleted_img['status'])
|
||||||
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
|
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):
|
def test_delete_with_tags(self):
|
||||||
request = unit_test_utils.get_fake_request()
|
request = unit_test_utils.get_fake_request()
|
||||||
changes = [
|
changes = [
|
||||||
|
|
Loading…
Reference in New Issue