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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user