Plumb image import functionality through our glance module
This just provides minimal support for calling the import API in Glance. That API can do more things, but it is unlikely Nova would ever need to call them, so this is rather opinionated and could be extended later if needed. Related to blueprint rbd-glance-multistore Change-Id: Icf78fcabad8b966b6b5c289e1b660c01c928272d
This commit is contained in:
parent
1cae0cd722
commit
4a6a366b05
|
@ -591,6 +591,10 @@ class ImageBadRequest(Invalid):
|
|||
"%(response)s")
|
||||
|
||||
|
||||
class ImageImportImpossible(Invalid):
|
||||
msg_fmt = _("Import of image %(image_id)s refused: %(reason)s")
|
||||
|
||||
|
||||
class ImageQuotaExceeded(NovaException):
|
||||
msg_fmt = _("Quota exceeded or out of space for image %(image_id)s "
|
||||
"in the image service.")
|
||||
|
|
|
@ -654,6 +654,41 @@ class GlanceImageServiceV2(object):
|
|||
raise exception.ImageDeleteConflict(reason=six.text_type(exc))
|
||||
return True
|
||||
|
||||
def image_import_copy(self, context, image_id, stores):
|
||||
"""Copy an image to another store using image_import.
|
||||
|
||||
This triggers the Glance image_import API with an opinionated
|
||||
method of 'copy-image' to a list of stores. This will initiate
|
||||
a copy of the image from one of the existing stores to the
|
||||
stores provided.
|
||||
|
||||
:param context: The RequestContext
|
||||
:param image_id: The image to copy
|
||||
:param stores: A list of stores to copy the image to
|
||||
|
||||
:raises: ImageNotFound if the image does not exist.
|
||||
:raises: ImageNotAuthorized if the user is not permitted to
|
||||
import/copy this image
|
||||
:raises: ImageImportImpossible if the image cannot be imported
|
||||
for workflow reasons (not active, etc)
|
||||
:raises: ImageBadRequest if the image is already in the requested
|
||||
store (which may be a race)
|
||||
"""
|
||||
try:
|
||||
self._client.call(context, 2, 'image_import', args=(image_id,),
|
||||
kwargs={'method': 'copy-image',
|
||||
'stores': stores})
|
||||
except glanceclient.exc.NotFound:
|
||||
raise exception.ImageNotFound(image_id=image_id)
|
||||
except glanceclient.exc.HTTPForbidden:
|
||||
raise exception.ImageNotAuthorized(image_id=image_id)
|
||||
except glanceclient.exc.HTTPConflict as exc:
|
||||
raise exception.ImageImportImpossible(image_id=image_id,
|
||||
reason=str(exc))
|
||||
except glanceclient.exc.HTTPBadRequest as exc:
|
||||
raise exception.ImageBadRequest(image_id=image_id,
|
||||
response=str(exc))
|
||||
|
||||
|
||||
def _extract_query_params_v2(params):
|
||||
_params = {}
|
||||
|
@ -1189,3 +1224,13 @@ class API(object):
|
|||
return session.download(context, image_id, data=data,
|
||||
dst_path=dest_path,
|
||||
trusted_certs=trusted_certs)
|
||||
|
||||
def copy_image_to_store(self, context, image_id, store):
|
||||
"""Initiate a store-to-store copy in glance.
|
||||
|
||||
:param context: The RequestContext.
|
||||
:param image_id: The image to copy.
|
||||
:param store: The glance store to target the copy.
|
||||
"""
|
||||
session, image_id = self._get_session_and_image_id(context, image_id)
|
||||
return session.image_import_copy(context, image_id, [store])
|
||||
|
|
|
@ -2087,3 +2087,77 @@ class TestSafeFSync(test.NoDBTestCase):
|
|||
"""Validate fsync not called for socket."""
|
||||
self.common(mock_isfifo, False, mock_issock, True, mock_fstat)
|
||||
mock_fsync.assert_not_called()
|
||||
|
||||
|
||||
class TestImportCopy(test.NoDBTestCase):
|
||||
|
||||
"""Tests the image import/copy methods."""
|
||||
|
||||
def _test_import(self, exception=None):
|
||||
client = mock.MagicMock()
|
||||
if exception:
|
||||
client.call.side_effect = exception
|
||||
else:
|
||||
client.call.return_value = True
|
||||
ctx = mock.sentinel.ctx
|
||||
service = glance.GlanceImageServiceV2(client)
|
||||
service.image_import_copy(ctx, mock.sentinel.image_id,
|
||||
[mock.sentinel.store])
|
||||
return client
|
||||
|
||||
def test_image_import_copy_success(self):
|
||||
client = self._test_import()
|
||||
client.call.assert_called_once_with(
|
||||
mock.sentinel.ctx, 2, 'image_import',
|
||||
args=(mock.sentinel.image_id,),
|
||||
kwargs={'method': 'copy-image',
|
||||
'stores': [mock.sentinel.store]})
|
||||
|
||||
def test_image_import_copy_not_found(self):
|
||||
self.assertRaises(exception.ImageNotFound,
|
||||
self._test_import,
|
||||
glanceclient.exc.NotFound)
|
||||
|
||||
def test_image_import_copy_not_authorized(self):
|
||||
self.assertRaises(exception.ImageNotAuthorized,
|
||||
self._test_import,
|
||||
glanceclient.exc.HTTPForbidden)
|
||||
|
||||
def test_image_import_copy_failed_workflow(self):
|
||||
self.assertRaises(exception.ImageImportImpossible,
|
||||
self._test_import,
|
||||
glanceclient.exc.HTTPConflict)
|
||||
|
||||
def test_image_import_copy_failed_already_imported(self):
|
||||
self.assertRaises(exception.ImageBadRequest,
|
||||
self._test_import,
|
||||
glanceclient.exc.HTTPBadRequest)
|
||||
|
||||
def test_api(self):
|
||||
api = glance.API()
|
||||
with mock.patch.object(api, '_get_session_and_image_id') as g:
|
||||
session = mock.MagicMock()
|
||||
g.return_value = session, mock.sentinel.image_id
|
||||
api.copy_image_to_store(mock.sentinel.ctx,
|
||||
mock.sentinel.image_id,
|
||||
mock.sentinel.store)
|
||||
session.image_import_copy.assert_called_once_with(
|
||||
mock.sentinel.ctx, mock.sentinel.image_id,
|
||||
[mock.sentinel.store])
|
||||
|
||||
def test_api_to_client(self):
|
||||
# Test all the way down to the client to test the interface between
|
||||
# API and GlanceImageServiceV2
|
||||
wrapper = mock.MagicMock()
|
||||
client = glance.GlanceImageServiceV2(client=wrapper)
|
||||
api = glance.API()
|
||||
with mock.patch.object(api, '_get_session_and_image_id') as m:
|
||||
m.return_value = (client, mock.sentinel.image_id)
|
||||
api.copy_image_to_store(mock.sentinel.ctx,
|
||||
mock.sentinel.image_id,
|
||||
mock.sentinel.store)
|
||||
wrapper.call.assert_called_once_with(
|
||||
mock.sentinel.ctx, 2, 'image_import',
|
||||
args=(mock.sentinel.image_id,),
|
||||
kwargs={'method': 'copy-image',
|
||||
'stores': [mock.sentinel.store]})
|
||||
|
|
Loading…
Reference in New Issue