driver = self.volume.driver
with mock.patch.object(driver, '_is_cloneable', lambda *args: False):
- image_loc = (mock.Mock(), mock.Mock())
+ image_loc = (mock.Mock(), None)
actual = driver.clone_image(mock.Mock(),
mock.Mock(),
image_loc,
mock_clone.assert_called_once_with(volume,
'fi', 'fo', 'fum')
mock_resize.assert_called_once_with(volume)
+
+ def test_clone_multilocation_success(self):
+ expected = ({'provider_location': None}, True)
+ driver = self.volume.driver
+
+ def cloneable_side_effect(url_location, image_meta):
+ return url_location == 'rbd://fee/fi/fo/fum'
+
+ with mock.patch.object(self.volume.driver, '_is_cloneable') \
+ as mock_is_cloneable, \
+ mock.patch.object(self.volume.driver, '_clone') as mock_clone, \
+ mock.patch.object(self.volume.driver, '_resize') \
+ as mock_resize:
+ mock_is_cloneable.side_effect = cloneable_side_effect
+ image_loc = ('rbd://bee/bi/bo/bum',
+ [{'url': 'rbd://bee/bi/bo/bum'},
+ {'url': 'rbd://fee/fi/fo/fum'}])
+ volume = {'name': 'vol1'}
+ image_meta = mock.sentinel.image_meta
+ image_service = mock.sentinel.image_service
+
+ actual = driver.clone_image(self.context,
+ volume,
+ image_loc,
+ image_meta,
+ image_service)
+
+ self.assertEqual(expected, actual)
+ self.assertEqual(2, mock_is_cloneable.call_count)
+ mock_clone.assert_called_once_with(volume,
+ 'fi', 'fo', 'fum')
+ mock_is_cloneable.assert_called_with('rbd://fee/fi/fo/fum',
+ image_meta)
+ mock_resize.assert_called_once_with(volume)
+
+ def test_clone_multilocation_failure(self):
+ expected = ({}, False)
+ driver = self.volume.driver
+
+ with mock.patch.object(driver, '_is_cloneable', return_value=False) \
+ as mock_is_cloneable, \
+ mock.patch.object(self.volume.driver, '_clone') as mock_clone, \
+ mock.patch.object(self.volume.driver, '_resize') \
+ as mock_resize:
+ image_loc = ('rbd://bee/bi/bo/bum',
+ [{'url': 'rbd://bee/bi/bo/bum'},
+ {'url': 'rbd://fee/fi/fo/fum'}])
+
+ volume = {'name': 'vol1'}
+ image_meta = mock.sentinel.image_meta
+ image_service = mock.sentinel.image_service
+ actual = driver.clone_image(self.context,
+ volume,
+ image_loc,
+ image_meta,
+ image_service)
+
+ self.assertEqual(expected, actual)
+ self.assertEqual(2, mock_is_cloneable.call_count)
+ mock_is_cloneable.assert_any_call('rbd://bee/bi/bo/bum',
+ image_meta)
+ mock_is_cloneable.assert_any_call('rbd://fee/fi/fo/fum',
+ image_meta)
+ self.assertFalse(mock_clone.called)
+ self.assertFalse(mock_resize.called)
def clone_image(self, context, volume,
image_location, image_meta,
image_service):
- image_location = image_location[0] if image_location else None
- if image_location is None or not self._is_cloneable(
- image_location, image_meta):
- return ({}, False)
- _prefix, pool, image, snapshot = self._parse_location(image_location)
- self._clone(volume, pool, image, snapshot)
- self._resize(volume)
- return {'provider_location': None}, True
+ if image_location:
+ # Note: image_location[0] is glance image direct_url.
+ # image_location[1] contains the list of all locations (including
+ # direct_url) or None if show_multiple_locations is False in
+ # glance configuration.
+ if image_location[1]:
+ url_locations = [location['url'] for
+ location in image_location[1]]
+ else:
+ url_locations = [image_location[0]]
+
+ # iterate all locations to look for a cloneable one.
+ for url_location in url_locations:
+ if url_location and self._is_cloneable(
+ url_location, image_meta):
+ _prefix, pool, image, snapshot = \
+ self._parse_location(url_location)
+ self._clone(volume, pool, image, snapshot)
+ self._resize(volume)
+ return {'provider_location': None}, True
+ return ({}, False)
def _image_conversion_dir(self):
tmpdir = (self.configuration.volume_tmp_dir or