Fix active image when all uploads fail

This fix is for the case where all stores fails and
the end result is active image with no image data.

Conflicts: Action wrapper does not exists, utilized image_repo
           instead.
Change-Id: Ie7558eeae488406a1696f0ee1bf40b644ae6d8cc
Partial-Bug: #1889640
(cherry picked from commit e8b13ce1b4)
This commit is contained in:
Erno Kuvaja 2020-07-30 17:00:23 +01:00
parent 2b3ce07c32
commit d63d727997
3 changed files with 67 additions and 19 deletions

View File

@ -76,6 +76,12 @@ CONF.register_opts(api_import_opts, group='image_import_opts')
# need to duplicate what we have already for example in base_import.py.
class _NoStoresSucceeded(exception.GlanceException):
def __init__(self, message):
super(_NoStoresSucceeded, self).__init__(message)
class _DeleteFromFS(task.Task):
def __init__(self, task_id, task_type):
@ -324,7 +330,7 @@ class _ImportToStore(task.Task):
break
class _SaveImage(task.Task):
class _VerifyImageState(task.Task):
def __init__(self, task_id, task_type, image_repo, image_id,
import_method):
@ -333,23 +339,24 @@ class _SaveImage(task.Task):
self.image_repo = image_repo
self.image_id = image_id
self.import_method = import_method
super(_SaveImage, self).__init__(
name='%s-SaveImage-%s' % (task_type, task_id))
super(_VerifyImageState, self).__init__(
name='%s-VerifyImageState-%s' % (task_type, task_id))
def execute(self):
"""Transition image status to active
"""Verify we have active image
:param image_id: Glance Image ID
"""
new_image = self.image_repo.get(self.image_id)
if (self.import_method != 'copy-image'
and new_image.status == 'importing'):
# NOTE(flaper87): THIS IS WRONG!
# we should be doing atomic updates to avoid
# race conditions. This happens in other places
# too.
new_image.status = 'active'
self.image_repo.save(new_image)
if new_image.status != 'active':
raise _NoStoresSucceeded(_('None of the uploads finished!'))
def revert(self, result, **kwargs):
"""Set back to queued if this wasn't copy-image job."""
if self.import_method != 'copy-image':
new_image = self.image_repo.get(self.image_id)
new_image.status = 'queued'
self.image_repo.save_image(new_image)
class _CompleteTask(task.Task):
@ -470,12 +477,12 @@ def get_flow(**kwargs):
delete_task = lf.Flow(task_type).add(_DeleteFromFS(task_id, task_type))
flow.add(delete_task)
save_task = _SaveImage(task_id,
task_type,
image_repo,
image_id,
import_method)
flow.add(save_task)
verify_task = _VerifyImageState(task_id,
task_type,
image_repo,
image_id,
import_method)
flow.add(verify_task)
complete_task = _CompleteTask(task_id,
task_type,

View File

@ -219,3 +219,44 @@ class TestDeleteFromFS(test_utils.BaseTestCase):
task = import_flow._DeleteFromFS(TASK_ID1, TASK_TYPE)
task.execute('foo')
mock_unlink.assert_not_called()
class TestVerifyImageStateTask(test_utils.BaseTestCase):
def test_verify_active_status(self):
fake_img = mock.MagicMock(status='active')
mock_repo = mock.MagicMock()
mock_repo.get.return_value = fake_img
task = import_flow._VerifyImageState(TASK_ID1, TASK_TYPE,
mock_repo, IMAGE_ID1,
'anything!')
task.execute()
fake_img.status = 'importing'
self.assertRaises(import_flow._NoStoresSucceeded,
task.execute)
def test_revert_copy_status_unchanged(self):
fake_img = mock.MagicMock(status='active')
mock_repo = mock.MagicMock()
mock_repo.get.return_value = fake_img
task = import_flow._VerifyImageState(TASK_ID1, TASK_TYPE,
mock_repo, IMAGE_ID1,
'copy-image')
task.revert(mock.sentinel.result)
# If we are doing copy-image, no state update should be made
mock_repo.save_image.assert_not_called()
def test_reverts_state_nocopy(self):
fake_img = mock.MagicMock(status='importing')
mock_repo = mock.MagicMock()
mock_repo.get.return_value = fake_img
task = import_flow._VerifyImageState(TASK_ID1, TASK_TYPE,
mock_repo, IMAGE_ID1,
'glance-direct')
task.revert(mock.sentinel.result)
# Except for copy-image, image state should revert to queued
mock_repo.save_image.assert_called_once()

View File

@ -70,7 +70,7 @@ class TestImportTaskFlow(test_utils.BaseTestCase):
self.config(node_staging_uri='file:///tmp/staging')
store.create_stores(CONF)
self.base_flow = ['ConfigureStaging', 'ImportToStore',
'DeleteFromFS', 'SaveImage',
'DeleteFromFS', 'VerifyImageState',
'CompleteTask']
self.import_plugins = ['Convert_Image',
'Decompress_Image',