Add 'create' method to GlanceImageServiceV2

This commit adds support of 'create' method and
related unit tests in GlanceImageServiceV2.

partially implements bp use-glance-v2-api

Change-Id: Ibebc1ec674dff452096a0c8f5c6d9daca5ee8aaa
This commit is contained in:
Mike Fedosin
2016-05-25 16:28:02 +03:00
parent 8eca306ab0
commit 4a39b8b530
2 changed files with 166 additions and 3 deletions

View File

@@ -630,6 +630,65 @@ class GlanceImageServiceV2(object):
if close_file:
data.close()
def create(self, context, image_meta, data=None):
"""Store the image data and return the new image object."""
# Here we workaround the situation when user wants to activate an
# empty image right after the creation. In Glance v1 api (and
# therefore in Nova) it is enough to set 'size = 0'. v2 api
# doesn't allow this hack - we have to send an upload request with
# empty data.
force_activate = data is None and image_meta.get('size') == 0
sent_service_image_meta = _translate_to_glance(image_meta)
try:
image = self._create_v2(context, sent_service_image_meta,
data, force_activate)
except glanceclient.exc.HTTPException:
_reraise_translated_exception()
return _translate_from_glance(image)
def _add_location(self, context, image_id, location):
# 'show_multiple_locations' must be enabled in glance api conf file.
try:
return self._client.call(context, 2, 'add_location', image_id,
location, {})
except glanceclient.exc.HTTPBadRequest:
_reraise_translated_exception()
def _upload_data(self, context, image_id, data):
self._client.call(context, 2, 'upload', image_id, data)
return self._client.call(context, 2, 'get', image_id)
def _create_v2(self, context, sent_service_image_meta, data=None,
force_activate=False):
# Glance v1 allows image activation without setting disk and
# container formats, v2 doesn't. It leads to the dirtiest workaround
# where we have to hardcode this parameters.
if force_activate:
data = ''
if 'disk_format' not in sent_service_image_meta:
sent_service_image_meta['disk_format'] = 'qcow2'
if 'container_format' not in sent_service_image_meta:
sent_service_image_meta['container_format'] = 'bare'
location = sent_service_image_meta.pop('location', None)
image = self._client.call(
context, 2, 'create', **sent_service_image_meta)
image_id = image['id']
# Sending image location in a separate request.
if location:
image = self._add_location(context, image_id, location)
# If we have some data we have to send it in separate request and
# update the image then.
if data is not None:
image = self._upload_data(context, image_id, data)
return image
def delete(self, context, image_id):
"""Delete the given image.
@@ -748,9 +807,34 @@ def _is_image_available(context, image):
def _translate_to_glance(image_meta):
image_meta = _convert_to_string(image_meta)
image_meta = _remove_read_only(image_meta)
# TODO(mfedosin): Remove this check once we move to glance V2
# completely and enable convert to v2 every time.
if not CONF.glance.use_glance_v1:
# v2 requires several additional changes
image_meta = _convert_to_v2(image_meta)
return image_meta
def _convert_to_v2(image_meta):
output = {}
for name, value in six.iteritems(image_meta):
if name == 'properties':
for prop_name, prop_value in six.iteritems(value):
output[prop_name] = str(prop_value)
elif name in ('min_ram', 'min_disk'):
output[name] = int(value)
elif name == 'is_public':
output['visibility'] = 'public' if value else 'private'
elif name == 'size' or name == 'deleted':
continue
elif name in ('kernel_id', 'ramdisk_id') and value == 'None':
output[name] = None
else:
output[name] = value
return output
def _translate_from_glance(image, include_locations=False):
# TODO(mfedosin): Remove this check once we move to glance V2
# completely.

View File

@@ -1979,7 +1979,8 @@ class TestCreate(test.NoDBTestCase):
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_create_success(self, trans_to_mock, trans_from_mock):
def test_create_success_v1(self, trans_to_mock, trans_from_mock):
self.flags(use_glance_v1=True, group='glance')
translated = {
'image_id': mock.sentinel.image_id
}
@@ -1992,7 +1993,7 @@ class TestCreate(test.NoDBTestCase):
service = glance.GlanceImageService(client)
image_meta = service.create(ctx, image_mock)
trans_to_mock.assert_called_once_with(image_mock)
trans_to_mock.assert_called_once_with(image_mock,)
client.call.assert_called_once_with(ctx, 1, 'create',
image_id=mock.sentinel.image_id)
trans_from_mock.assert_called_once_with(mock.sentinel.image_meta)
@@ -2008,11 +2009,69 @@ class TestCreate(test.NoDBTestCase):
image_id=mock.sentinel.image_id,
data=mock.sentinel.data)
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_create_success_v2(
self, trans_to_mock, trans_from_mock):
self.flags(use_glance_v1=False, group='glance')
translated = {
'name': mock.sentinel.name,
}
trans_to_mock.return_value = translated
trans_from_mock.return_value = mock.sentinel.trans_from
image_mock = mock.MagicMock(spec=dict)
client = mock.MagicMock()
client.call.return_value = {'id': '123'}
ctx = mock.sentinel.ctx
service = glance.GlanceImageServiceV2(client)
image_meta = service.create(ctx, image_mock)
trans_to_mock.assert_called_once_with(image_mock)
# Verify that the 'id' element has been removed as a kwarg to
# the call to glanceclient's update (since the image ID is
# supplied as a positional arg), and that the
# purge_props default is True.
client.call.assert_called_once_with(ctx, 2, 'create',
name=mock.sentinel.name)
trans_from_mock.assert_called_once_with({'id': '123'})
self.assertEqual(mock.sentinel.trans_from, image_meta)
# Now verify that if we supply image data to the call,
# that the client is also called with the data kwarg
client.reset_mock()
client.call.return_value = {'id': mock.sentinel.image_id}
service.create(ctx, {}, data=mock.sentinel.data)
self.assertEqual(3, client.call.call_count)
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_create_success_v2_with_location(
self, trans_to_mock, trans_from_mock):
self.flags(use_glance_v1=False, group='glance')
translated = {
'id': mock.sentinel.id,
'name': mock.sentinel.name,
'location': mock.sentinel.location
}
trans_to_mock.return_value = translated
trans_from_mock.return_value = mock.sentinel.trans_from
image_mock = mock.MagicMock(spec=dict)
client = mock.MagicMock()
client.call.return_value = translated
ctx = mock.sentinel.ctx
service = glance.GlanceImageServiceV2(client)
image_meta = service.create(ctx, image_mock)
trans_to_mock.assert_called_once_with(image_mock)
self.assertEqual(2, client.call.call_count)
trans_from_mock.assert_called_once_with(translated)
self.assertEqual(mock.sentinel.trans_from, image_meta)
@mock.patch('nova.image.glance._reraise_translated_exception')
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_create_client_failure(self, trans_to_mock, trans_from_mock,
def test_create_client_failure_v1(self, trans_to_mock, trans_from_mock,
reraise_mock):
self.flags(use_glance_v1=True, group='glance')
translated = {}
trans_to_mock.return_value = translated
image_mock = mock.MagicMock(spec=dict)
@@ -2027,6 +2086,26 @@ class TestCreate(test.NoDBTestCase):
trans_to_mock.assert_called_once_with(image_mock)
self.assertFalse(trans_from_mock.called)
@mock.patch('nova.image.glance._reraise_translated_exception')
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_create_client_failure_v2(self, trans_to_mock, trans_from_mock,
reraise_mock):
self.flags(use_glance_v1=False, group='glance')
translated = {}
trans_to_mock.return_value = translated
image_mock = mock.MagicMock(spec=dict)
raised = exception.Invalid()
client = mock.MagicMock()
client.call.side_effect = glanceclient.exc.BadRequest
ctx = mock.sentinel.ctx
reraise_mock.side_effect = raised
service = glance.GlanceImageServiceV2(client)
self.assertRaises(exception.Invalid, service.create, ctx, image_mock)
trans_to_mock.assert_called_once_with(image_mock)
self.assertFalse(trans_from_mock.called)
class TestUpdate(test.NoDBTestCase):