Add 'update' method to GlanceImageServiceV2

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

partially implements bp use-glance-v2-api

Change-Id: I47c83cc91b9f862df160030267ecc3d618aa0d65
This commit is contained in:
Mike Fedosin
2016-05-25 19:21:45 +03:00
parent 4a39b8b530
commit 383a0c9cbb
2 changed files with 173 additions and 3 deletions

View File

@@ -689,6 +689,49 @@ class GlanceImageServiceV2(object):
return image
def update(self, context, image_id, image_meta, data=None,
purge_props=True):
"""Modify the given image with the new data."""
sent_service_image_meta = _translate_to_glance(image_meta)
# NOTE(bcwaldon): id is not an editable field, but it is likely to be
# passed in by calling code. Let's be nice and ignore it.
sent_service_image_meta.pop('id', None)
sent_service_image_meta['image_id'] = image_id
try:
if purge_props:
# In Glance v2 we have to explicitly set prop names
# we want to remove.
all_props = set(self.show(
context, image_id)['properties'].keys())
props_to_update = set(
image_meta.get('properties', {}).keys())
remove_props = list(all_props - props_to_update)
sent_service_image_meta['remove_props'] = remove_props
image = self._update_v2(context, sent_service_image_meta, data)
except Exception:
_reraise_translated_image_exception(image_id)
return _translate_from_glance(image)
def _update_v2(self, context, sent_service_image_meta, data=None):
location = sent_service_image_meta.pop('location', None)
image_id = sent_service_image_meta['image_id']
image = self._client.call(
context, 2, 'update', **sent_service_image_meta)
# 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.

View File

@@ -2113,7 +2113,9 @@ class TestUpdate(test.NoDBTestCase):
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_update_success(self, trans_to_mock, trans_from_mock):
def test_update_success_v1(
self, trans_to_mock, trans_from_mock):
self.flags(use_glance_v1=True, group='glance')
translated = {
'id': mock.sentinel.image_id,
'name': mock.sentinel.name
@@ -2125,7 +2127,8 @@ class TestUpdate(test.NoDBTestCase):
client.call.return_value = mock.sentinel.image_meta
ctx = mock.sentinel.ctx
service = glance.GlanceImageService(client)
image_meta = service.update(ctx, mock.sentinel.image_id, image_mock)
image_meta = service.update(
ctx, mock.sentinel.image_id, image_mock, purge_props=True)
trans_to_mock.assert_called_once_with(image_mock)
# Verify that the 'id' element has been removed as a kwarg to
@@ -2151,11 +2154,93 @@ class TestUpdate(test.NoDBTestCase):
purge_props=True,
data=mock.sentinel.data)
@mock.patch('nova.image.glance.GlanceImageServiceV2.show')
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_update_success_v2(
self, trans_to_mock, trans_from_mock, show_mock):
self.flags(use_glance_v1=False, group='glance')
image = {
'id': mock.sentinel.image_id,
'name': mock.sentinel.name,
'properties': {'prop_to_keep': '4'}
}
translated = {
'id': mock.sentinel.image_id,
'name': mock.sentinel.name,
'prop_to_keep': '4'
}
trans_to_mock.return_value = translated
trans_from_mock.return_value = mock.sentinel.trans_from
client = mock.MagicMock()
client.call.return_value = mock.sentinel.image_meta
ctx = mock.sentinel.ctx
show_mock.return_value = {
'image_id': mock.sentinel.image_id,
'properties': {'prop_to_remove': '1',
'prop_to_keep': '3'}
}
service = glance.GlanceImageServiceV2(client)
image_meta = service.update(
ctx, mock.sentinel.image_id, image, purge_props=True)
show_mock.assert_called_once_with(
mock.sentinel.ctx, mock.sentinel.image_id)
trans_to_mock.assert_called_once_with(image)
# 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, 'update',
image_id=mock.sentinel.image_id,
name=mock.sentinel.name,
prop_to_keep='4',
remove_props=['prop_to_remove'])
trans_from_mock.assert_called_once_with(mock.sentinel.image_meta)
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.update(ctx, mock.sentinel.image_id, {},
data=mock.sentinel.data)
self.assertEqual(3, client.call.call_count)
@mock.patch('nova.image.glance.GlanceImageServiceV2.show')
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_update_success_v2_with_location(
self, trans_to_mock, trans_from_mock, show_mock):
self.flags(use_glance_v1=False, group='glance')
translated = {
'id': mock.sentinel.id,
'name': mock.sentinel.name,
'location': mock.sentinel.location
}
show_mock.return_value = {'image_id': mock.sentinel.image_id}
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.update(ctx, mock.sentinel.image_id,
image_mock, purge_props=False)
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_image_exception')
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_update_client_failure(self, trans_to_mock, trans_from_mock,
def test_update_client_failure_v1(self, trans_to_mock, trans_from_mock,
reraise_mock):
self.flags(use_glance_v1=True, group='glance')
translated = {
'name': mock.sentinel.name
}
@@ -2179,6 +2264,48 @@ class TestUpdate(test.NoDBTestCase):
self.assertFalse(trans_from_mock.called)
reraise_mock.assert_called_once_with(mock.sentinel.image_id)
@mock.patch('nova.image.glance.GlanceImageServiceV2.show')
@mock.patch('nova.image.glance._reraise_translated_image_exception')
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._translate_to_glance')
def test_update_client_failure_v2(self, trans_to_mock, trans_from_mock,
reraise_mock, show_mock):
self.flags(use_glance_v1=False, group='glance')
image = {
'id': mock.sentinel.image_id,
'name': mock.sentinel.name,
'properties': {'prop_to_keep': '4'}
}
translated = {
'id': mock.sentinel.image_id,
'name': mock.sentinel.name,
'prop_to_keep': '4'
}
trans_to_mock.return_value = translated
trans_from_mock.return_value = mock.sentinel.trans_from
raised = exception.ImageNotAuthorized(image_id=123)
client = mock.MagicMock()
client.call.side_effect = glanceclient.exc.Forbidden
ctx = mock.sentinel.ctx
reraise_mock.side_effect = raised
show_mock.return_value = {
'image_id': mock.sentinel.image_id,
'properties': {'prop_to_remove': '1',
'prop_to_keep': '3'}
}
service = glance.GlanceImageServiceV2(client)
self.assertRaises(exception.ImageNotAuthorized,
service.update, ctx, mock.sentinel.image_id,
image)
client.call.assert_called_once_with(ctx, 2, 'update',
image_id=mock.sentinel.image_id,
name=mock.sentinel.name,
prop_to_keep='4',
remove_props=['prop_to_remove'])
reraise_mock.assert_called_once_with(mock.sentinel.image_id)
class TestDelete(test.NoDBTestCase):